UpsertBuilder Overview
Transaction Required
Always used within transactionsGuarantees data consistency
UBRef References
Automatic foreign key resolutionDefine relationships without IDs
Sequential Saving
Save in dependency orderSatisfy foreign key constraints
Type Safety
Compile-time validationTable schema verification
Basic Concept
Problems with Regular INSERT
Copy
// ❌ Problem: Cannot know foreign key values in advance
await db.transaction(async (trx) => {
// 1. Create Company
const [companyId] = await trx
.table("companies")
.insert({ name: "Tech Corp" })
.returning("id");
// 2. Create Department (needs company_id)
const [deptId] = await trx
.table("departments")
.insert({
name: "Engineering",
company_id: companyId, // ← Use ID from above
})
.returning("id");
// 3. Create User
const [userId] = await trx
.table("users")
.insert({
email: "john@test.com",
username: "john",
password: "pass",
role: "normal",
})
.returning("id");
// 4. Create Employee (needs user_id, department_id)
await trx.table("employees").insert({
user_id: userId, // ← ID from above
department_id: deptId, // ← ID from above
employee_number: "E001",
});
});
// Code is long and complex
// Need to track IDs every time
UpsertBuilder Solution
Copy
// ✅ Solution: Define relationships with UBRef
await db.transaction(async (trx) => {
// 1. Register data (get UBRef)
const companyRef = trx.ubRegister("companies", {
name: "Tech Corp",
});
const deptRef = trx.ubRegister("departments", {
name: "Engineering",
company_id: companyRef, // ← Use UBRef
});
const userRef = trx.ubRegister("users", {
email: "john@test.com",
username: "john",
password: "pass",
role: "normal",
});
trx.ubRegister("employees", {
user_id: userRef, // ← Use UBRef
department_id: deptRef, // ← Use UBRef
employee_number: "E001",
});
// 2. Save in order
await trx.ubUpsert("companies");
await trx.ubUpsert("departments");
await trx.ubUpsert("users");
await trx.ubUpsert("employees");
});
// Clean and clear
// No ID tracking needed
ubRegister - Data Registration
ubRegister registers data to save and returns a UBRef.
Basic Usage
Copy
await db.transaction(async (trx) => {
// Specify table and data
const userRef = trx.ubRegister("users", {
email: "john@test.com",
username: "john",
password: "hashed_password",
role: "normal",
});
// userRef is UBRef type
// A reference object, not an actual ID
});
Type Safety
Copy
// ✅ OK: Correct fields
trx.ubRegister("users", {
email: "john@test.com",
username: "john",
password: "pass",
role: "normal",
});
// ❌ Type Error: Invalid field
trx.ubRegister("users", {
email: "john@test.com",
invalid_field: "value", // Property 'invalid_field' does not exist
});
// ❌ Type Error: Missing required field
trx.ubRegister("users", {
email: "john@test.com",
// username missing → Type Error
});
ubUpsert - Actual Save
ubUpsert actually saves registered data to the DB.
Basic Usage
Copy
await db.transaction(async (trx) => {
trx.ubRegister("users", {
email: "john@test.com",
username: "john",
password: "pass",
role: "normal",
});
// Save to DB and return array of created IDs
const userIds = await trx.ubUpsert("users");
console.log(userIds); // [1]
});
Return Value
Copy
// Single record
const [userId] = await trx.ubUpsert("users");
console.log(userId); // 1
// Multiple records
const userIds = await trx.ubUpsert("users");
console.log(userIds); // [1, 2, 3]
Save Order
Copy
await db.transaction(async (trx) => {
// Registration (order doesn't matter)
const userRef = trx.ubRegister("users", { ... });
const companyRef = trx.ubRegister("companies", { ... });
const deptRef = trx.ubRegister("departments", {
company_id: companyRef,
});
const empRef = trx.ubRegister("employees", {
user_id: userRef,
department_id: deptRef,
});
// Save (dependency order required)
await trx.ubUpsert("companies"); // 1. No dependencies
await trx.ubUpsert("departments"); // 2. Needs company_id
await trx.ubUpsert("users"); // 3. No dependencies
await trx.ubUpsert("employees"); // 4. Needs user_id, department_id
});
Save Order Matters!Tables referenced by foreign keys must be saved first.
Wrong order will cause foreign key constraint violation errors.
Practical Examples
User Registration
Copy
async createUser(data: {
email: string;
username: string;
password: string;
}) {
return await this.getPuri("w").transaction(async (trx) => {
// Register User
trx.ubRegister("users", {
email: data.email,
username: data.username,
password: data.password,
role: "normal",
is_verified: false,
});
// Save
const [userId] = await trx.ubUpsert("users");
return userId;
});
}
Create Company + Department
Copy
async createCompanyWithDepartment(data: {
companyName: string;
departmentName: string;
}) {
return await this.getPuri("w").transaction(async (trx) => {
// 1. Register Company
const companyRef = trx.ubRegister("companies", {
name: data.companyName,
});
// 2. Register Department (references Company)
trx.ubRegister("departments", {
name: data.departmentName,
company_id: companyRef,
});
// 3. Save in order
const [companyId] = await trx.ubUpsert("companies");
const [departmentId] = await trx.ubUpsert("departments");
return { companyId, departmentId };
});
}
Create Employee (User + Employee)
Copy
async createEmployee(data: {
email: string;
username: string;
password: string;
departmentId: number;
employeeNumber: string;
salary: string;
}) {
return await this.getPuri("w").transaction(async (trx) => {
// 1. Register User
const userRef = trx.ubRegister("users", {
email: data.email,
username: data.username,
password: data.password,
role: "normal",
});
// 2. Register Employee
trx.ubRegister("employees", {
user_id: userRef,
department_id: data.departmentId, // Use existing ID
employee_number: data.employeeNumber,
salary: data.salary,
});
// 3. Save
const [userId] = await trx.ubUpsert("users");
const [employeeId] = await trx.ubUpsert("employees");
return { userId, employeeId };
});
}
UBRef Type
UBRef is a reference object, not an actual ID:Copy
// UBRef type
type UBRef = {
_type: "UBRef";
table: string;
index: number;
};
// Usage example
const userRef = trx.ubRegister("users", { ... });
console.log(userRef);
// { _type: "UBRef", table: "users", index: 0 }
// ❌ Cannot use directly
const userId = userRef; // Type Error
await db.table("posts").insert({
user_id: userRef, // ❌ Runtime Error
});
// ✅ Only usable within UpsertBuilder
trx.ubRegister("employees", {
user_id: userRef, // ✅ OK
});
Error Handling
Transaction Rollback
Copy
try {
await db.transaction(async (trx) => {
trx.ubRegister("users", { ... });
trx.ubRegister("employees", { ... });
await trx.ubUpsert("users");
// Auto rollback on error
throw new Error("Something went wrong");
});
} catch (error) {
console.error("Transaction failed:", error);
// users insert also rolled back
}
Foreign Key Constraint Violation
Copy
await db.transaction(async (trx) => {
const userRef = trx.ubRegister("users", { ... });
trx.ubRegister("employees", {
user_id: userRef,
department_id: 999, // Non-existent ID
});
await trx.ubUpsert("users");
// ❌ Foreign key constraint violation error
await trx.ubUpsert("employees");
// Error: FOREIGN KEY constraint failed
});