Create Operations

Create operations in Routier allow you to add new entities to your collections. The framework provides both synchronous and asynchronous methods, with automatic change tracking and type safety.

Quick Navigation

Overview

When you create entities in Routier:

  1. Entities are type-checked against your schema
  2. Default values are applied automatically
  3. Identity fields are generated if specified
  4. Changes are tracked for later persistence
  5. Entities are returned with all properties set

⚠️ Important: Persistence Requires Save

Note: When you call addAsync(), the entity is added to the collection in memory, but it is NOT automatically persisted to the database. You must call saveChanges() or saveChangesAsync() to persist the changes.

Basic Create Operations

Adding Single Entities

The simplest way to create a new entity is using addAsync():

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    age: s.number().optional(),
    createdAt: s.date().default(() => new Date()),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Adding a single entity
const newUser = await ctx.users.addAsync({
    name: "John Doe",
    email: "[email protected]",
    age: 30
});

console.log("Created user:", newUser);
// Output: [{ id: "generated-uuid", name: "John Doe", email: "[email protected]", age: 30, createdAt: Date }]

// Don't forget to save changes!
await ctx.saveChangesAsync();

Adding Multiple Entities

You can add multiple entities in a single operation for better performance:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a product schema
const productSchema = s.define("products", {
    id: s.string().key().identity(),
    name: s.string(),
    price: s.number(),
    category: s.string("electronics", "books", "clothing"),
    inStock: s.boolean().default(true),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    products = this.collection(productSchema).create();
}

const ctx = new AppContext();

// Adding multiple entities at once
const newProducts = await ctx.products.addAsync(
    {
        name: "Laptop",
        price: 999.99,
        category: "electronics"
    },
    {
        name: "JavaScript Book",
        price: 29.99,
        category: "books"
    },
    {
        name: "T-Shirt",
        price: 19.99,
        category: "clothing"
    }
);

console.log("Created products:", newProducts);
// Output: Array of 3 products with generated IDs and default values

// Save all changes
await ctx.saveChangesAsync();

Adding with Callbacks

For advanced scenarios, you can use callback-based operations with error handling:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    createdAt: s.date().default(() => new Date()),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Adding with callback (discriminated union result pattern)
ctx.users.add([{
    name: "Jane Doe",
    email: "[email protected]"
}], (result) => {
    if (result.ok === "success") {
        console.log("User created successfully:", result.data);
        // result.data is InferType<typeof userSchema>[]
    } else {
        console.error("Failed to create user:", result.error);
        // result.error contains the error details
    }
});

// Save changes after callback completes
await ctx.saveChangesAsync();

Schema-Driven Creation

Automatic Default Values

Routier automatically applies default values defined in your schema:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a post schema with default values
const postSchema = s.define("posts", {
    id: s.string().key().identity(),
    title: s.string(),
    content: s.string(),
    status: s.string("draft", "published", "archived").default("draft"),
    views: s.number().default(0),
    publishedAt: s.date().optional(),
    createdAt: s.date().default(() => new Date()),
    updatedAt: s.date().default(() => new Date()),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    posts = this.collection(postSchema).create();
}

const ctx = new AppContext();

// Creating with minimal data - defaults are applied automatically
const newPost = await ctx.posts.addAsync({
    title: "My First Post",
    content: "This is the content of my first post."
});

console.log("Created post:", newPost);
// Output: [{ 
//   id: "generated-uuid", 
//   title: "My First Post", 
//   content: "This is the content of my first post.",
//   status: "draft",        // ← Default applied
//   views: 0,              // ← Default applied  
//   publishedAt: undefined, // ← Optional field
//   createdAt: Date,       // ← Default applied
//   updatedAt: Date        // ← Default applied
// }]

await ctx.saveChangesAsync();

Identity Field Generation

Identity fields are automatically generated when creating new entities:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema with identity field
const userSchema = s.define("users", {
    id: s.string().key().identity(), // Identity field - datastore generates value
    email: s.string().distinct(),
    name: s.string(),
    createdAt: s.date().default(() => new Date()),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Creating without providing ID - identity field is auto-generated
const newUser = await ctx.users.addAsync({
    email: "[email protected]",
    name: "Alice Smith"
    // No id provided - datastore will generate it
});

console.log("Created user:", newUser);
// Output: [{ 
//   id: "generated-uuid",  // ← Auto-generated by datastore
//   email: "[email protected]", 
//   name: "Alice Smith",
//   createdAt: Date
// }]

await ctx.saveChangesAsync();

Nested Object Creation

You can create entities with nested objects and arrays:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema with nested objects
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    profile: s.object({
        firstName: s.string(),
        lastName: s.string(),
        bio: s.string().optional(),
        avatar: s.string().optional(),
    }),
    preferences: s.object({
        theme: s.string("light", "dark").default("light"),
        notifications: s.boolean().default(true),
    }).default({}),
    createdAt: s.date().default(() => new Date()),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Creating with nested objects
const newUser = await ctx.users.addAsync({
    name: "John Doe",
    email: "[email protected]",
    profile: {
        firstName: "John",
        lastName: "Doe",
        bio: "Software developer",
        // avatar is optional
    },
    // preferences will use default value {}
});

console.log("Created user:", newUser);
// Output: [{ 
//   id: "generated-uuid",
//   name: "John Doe", 
//   email: "[email protected]",
//   profile: {
//     firstName: "John",
//     lastName: "Doe", 
//     bio: "Software developer",
//     avatar: undefined
//   },
//   preferences: { theme: "light", notifications: true }, // ← Default applied
//   createdAt: Date
// }]

await ctx.saveChangesAsync();

Type Safety and Error Handling

Schema Type Checking

Routier provides compile-time type safety through TypeScript:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema with distinct constraint
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    email: s.string().distinct(), // Creates database index for uniqueness
    name: s.string(),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// TypeScript will catch type mismatches at compile time
type CreateUser = InferCreateType<typeof userSchema>;

// ✅ Valid - TypeScript ensures correct types
const validUser: CreateUser = {
    email: "[email protected]",
    name: "John Doe"
};

// ❌ Invalid - TypeScript will show error
// const invalidUser: CreateUser = {
//   email: 123,        // Error: Type 'number' is not assignable to type 'string'
//   name: "John Doe"
// };

// Note: Routier does not enforce unique constraints at runtime
// The .distinct() modifier only creates database indexes
// Duplicate emails will be allowed in memory and may cause issues at database level
const duplicateUser = await ctx.users.addAsync({
    email: "[email protected]", // This will be allowed even if another user has this email
    name: "Jane Doe"
});

await ctx.saveChangesAsync();

TypeScript Type Safety

Use InferCreateType for proper type inference:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    age: s.number(),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// TypeScript type safety example
type CreateUser = InferCreateType<typeof userSchema>;

function createUserSafely(userData: CreateUser) {
    // TypeScript ensures userData has correct structure
    return ctx.users.addAsync(userData);
}

// ✅ TypeScript will catch these errors at compile time:
// createUserSafely({ name: "John" }); // Error: Missing required field 'email'
// createUserSafely({ name: "John", email: "[email protected]", age: "thirty" }); // Error: Wrong type for age

// ✅ This will work - TypeScript ensures type safety
const user = await createUserSafely({
    name: "John Doe",
    email: "[email protected]",
    age: 30
});

console.log("Created user:", user);

await ctx.saveChangesAsync();

Constraint Enforcement

Constraint enforcement depends on your plugin implementation:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema with constraints
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    email: s.string().distinct(), // Unique constraint
    age: s.number(18, 19, 20, 21), // Literal constraint
    role: s.string("admin", "user", "guest").default("user"),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Constraint enforcement is handled by plugins, not Routier core
// Different plugins may behave differently:

// Memory plugin - allows duplicates and invalid values
const user1 = await ctx.users.addAsync({
    email: "[email protected]",
    age: 25, // Not in allowed literals (18,19,20,21) but will be allowed
});

const user2 = await ctx.users.addAsync({
    email: "[email protected]", // Duplicate email - will be allowed in memory
    age: 18, // Valid literal
});

console.log("Both users created:", user1, user2);

// Note: 
// - SQLite plugins may enforce unique constraints at database level
// - Memory plugins allow duplicates in memory
// - Check your specific plugin's documentation for constraint behavior

await ctx.saveChangesAsync();

Advanced Create Patterns

Conditional Creation

Create entities based on conditions or business logic:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    isActive: s.boolean().default(true),
    createdAt: s.date().default(() => new Date()),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Conditional creation based on business logic
async function createUserIfNotExists(userData: InferCreateType<typeof userSchema>) {
    // Check if user already exists
    const existingUser = await ctx.users.firstOrUndefinedAsync(u => u.email === userData.email);

    if (existingUser) {
        console.log("User already exists:", existingUser);
        return existingUser;
    }

    // Create new user
    const newUser = await ctx.users.addAsync(userData);
    console.log("Created new user:", newUser);
    return newUser;
}

// Usage
const user = await createUserIfNotExists({
    name: "John Doe",
    email: "[email protected]"
});

await ctx.saveChangesAsync();

Batch Creation with Type Safety

Create multiple entities with proper type checking:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a product schema
const productSchema = s.define("products", {
    id: s.string().key().identity(),
    name: s.string(),
    price: s.number(),
    category: s.string("electronics", "books", "clothing"),
    inStock: s.boolean().default(true),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    products = this.collection(productSchema).create();
}

const ctx = new AppContext();

// Batch creation with type safety
async function createProductsBatch(productsData: InferCreateType<typeof productSchema>[]) {
    try {
        // TypeScript ensures all items have correct structure
        const createdProducts = await ctx.products.addAsync(...productsData);

        console.log(`Successfully created ${createdProducts.length} products`);
        return createdProducts;
    } catch (error) {
        console.error("Failed to create products:", error);
        throw error;
    }
}

// Usage with type-safe data
const productsToCreate: InferCreateType<typeof productSchema>[] = [
    { name: "Laptop", price: 999.99, category: "electronics" },
    { name: "Book", price: 29.99, category: "books" },
    { name: "Shirt", price: 19.99, category: "clothing" }
];

const createdProducts = await createProductsBatch(productsToCreate);
await ctx.saveChangesAsync();

Creation with Computed Fields

Create entities that include computed or derived fields:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a post schema with computed fields
const postSchema = s.define("posts", {
    id: s.string().key().identity(),
    title: s.string(),
    content: s.string(),
    author: s.string(),
    publishedAt: s.date().optional(),
}).modify(w => ({
    // Computed field - automatically calculated
    slug: w.computed((entity) =>
        entity.title.toLowerCase().replace(/\s+/g, '-')
    ).tracked(),

    // Function field - non-persisted method
    isPublished: w.function((entity) => entity.publishedAt != null),
})).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    posts = this.collection(postSchema).create();
}

const ctx = new AppContext();

// Creating with computed fields
const newPost = await ctx.posts.addAsync({
    title: "My First Post",
    content: "This is the content of my first post.",
    author: "John Doe"
});

console.log("Created post:", newPost);
// Output: [{ 
//   id: "generated-uuid",
//   title: "My First Post",
//   content: "This is the content of my first post.",
//   author: "John Doe",
//   publishedAt: undefined,
//   slug: "my-first-post",  // ← Computed field
//   isPublished: function   // ← Function field
// }]

// Access computed and function fields
const post = newPost[0];
console.log("Slug:", post.slug); // "my-first-post"
console.log("Is published:", post.isPublished()); // false

await ctx.saveChangesAsync();

Performance Considerations

Batch Creation

Batch creation is more efficient than individual creates:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    createdAt: s.date().default(() => new Date()),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Batch creation for better performance
async function createUsersBatch(userDataList: InferCreateType<typeof userSchema>[]) {
    const startTime = performance.now();

    // Create all users in a single operation
    const createdUsers = await ctx.users.addAsync(...userDataList);

    const endTime = performance.now();
    console.log(`Created ${createdUsers.length} users in ${endTime - startTime}ms`);

    return createdUsers;
}

// Generate test data
const usersToCreate: InferCreateType<typeof userSchema>[] = Array.from({ length: 100 }, (_, i) => ({
    name: `User ${i + 1}`,
    email: `user${i + 1}@example.com`
}));

// Batch create all users at once
const createdUsers = await createUsersBatch(usersToCreate);

// Save all changes in one operation
await ctx.saveChangesAsync();

console.log(`Total users created: ${createdUsers.length}`);

Memory Management

Consider memory usage when creating large numbers of entities:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    profile: s.object({
        bio: s.string().optional(),
        avatar: s.string().optional(),
    }).optional(),
    createdAt: s.date().default(() => new Date()),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Memory-efficient creation patterns
async function createUserEfficiently(userData: InferCreateType<typeof userSchema>) {
    // Create user with minimal memory footprint
    const user = await ctx.users.addAsync(userData);

    // Access properties directly (no copying)
    console.log("User ID:", user[0].id);
    console.log("User name:", user[0].name);

    return user[0];
}

// Avoid creating unnecessary intermediate objects
const userData: InferCreateType<typeof userSchema> = {
    name: "John Doe",
    email: "[email protected]"
    // profile is optional - don't create empty object
};

const user = await createUserEfficiently(userData);

// Direct property access is memory efficient
user.name = "John Updated"; // Direct modification, no copying

await ctx.saveChangesAsync();

Best Practices

1. Type-Check Data Before Creation

Validate data before creating entities:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    age: s.number().optional(),
    createdAt: s.date().default(() => new Date()),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Type-check data before creation
function validateUserData(data: any): data is InferCreateType<typeof userSchema> {
    return (
        typeof data === 'object' &&
        typeof data.name === 'string' &&
        typeof data.email === 'string' &&
        (data.age === undefined || typeof data.age === 'number')
    );
}

async function createUserSafely(userData: any) {
    // Validate data structure before creation
    if (!validateUserData(userData)) {
        throw new Error("Invalid user data structure");
    }

    // TypeScript now knows userData is valid
    const user = await ctx.users.addAsync(userData);
    console.log("User created successfully:", user);
    return user;
}

// Usage
try {
    const user = await createUserSafely({
        name: "John Doe",
        email: "[email protected]",
        age: 30
    });

    await ctx.saveChangesAsync();
} catch (error) {
    console.error("Failed to create user:", error);
}

2. Use Appropriate Default Values

Define meaningful default values in your schema:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema with appropriate defaults
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    role: s.string("admin", "user", "guest").default("user"), // Sensible default
    isActive: s.boolean().default(true), // Sensible default
    lastLogin: s.date().optional(), // No default - let it be undefined
    createdAt: s.date().default(() => new Date()),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Use appropriate default values
async function createUserWithDefaults(userData: InferCreateType<typeof userSchema>) {
    // Only provide required fields - defaults handle the rest
    const user = await ctx.users.addAsync({
        name: userData.name,
        email: userData.email
        // role defaults to "user"
        // isActive defaults to true
        // createdAt defaults to current date
        // lastLogin remains undefined (optional)
    });

    console.log("User created with defaults:", user);
    return user;
}

// Usage
const user = await createUserWithDefaults({
    name: "Jane Doe",
    email: "[email protected]"
});

await ctx.saveChangesAsync();

3. Handle Errors Gracefully

Implement proper error handling for create operations:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    createdAt: s.date().default(() => new Date()),
}).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Handle errors gracefully
async function createUserWithErrorHandling(userData: InferCreateType<typeof userSchema>) {
    try {
        const user = await ctx.users.addAsync(userData);
        console.log("User created successfully:", user);
        return user;
    } catch (error) {
        // Handle different types of errors
        if (error instanceof Error) {
            console.error("Error creating user:", error.message);

            // Handle specific error types
            if (error.message.includes("network")) {
                console.error("Network error occurred");
                // Handle network issues
            } else if (error.message.includes("database")) {
                console.error("Database error occurred");
                // Handle database issues
            } else {
                console.error("Unexpected error:", error);
                // Handle unexpected errors
            }
        }

        throw error; // Re-throw for caller to handle
    }
}

// Usage with error handling
try {
    const user = await createUserWithErrorHandling({
        name: "John Doe",
        email: "[email protected]"
    });

    await ctx.saveChangesAsync();
} catch (error) {
    console.error("Failed to create and save user:", error);
    // Handle the error appropriately (retry, show user message, etc.)
}

4. Leverage Schema Features

Use schema features like enums, defaults, and constraints effectively:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferCreateType } from "@routier/core/schema";

// Define a user schema leveraging schema features
const userSchema = s.define("users", {
    id: s.string().key().identity(), // Auto-generated IDs
    email: s.string().distinct(), // Unique constraint with indexing
    name: s.string(),
    role: s.string("admin", "user", "guest").default("user"), // Literal constraints
    isActive: s.boolean().default(true), // Default values
    createdAt: s.date().default(() => new Date()), // Function defaults
    lastLogin: s.date().optional(), // Optional fields
}).modify(w => ({
    // Computed properties
    displayName: w.computed((entity) =>
        `${entity.name} (${entity.role})`
    ).tracked(),

    // Function methods
    isAdmin: w.function((entity) => entity.role === "admin"),
})).compile();

// Create DataStore with collection
class AppContext extends DataStore {
    constructor() {
        super(new MemoryPlugin("app-db"));
    }

    users = this.collection(userSchema).create();
}

const ctx = new AppContext();

// Leverage schema features for powerful creation
async function createUserWithSchemaFeatures(userData: InferCreateType<typeof userSchema>) {
    // Schema handles:
    // - Auto-generated ID
    // - Default values (role, isActive, createdAt)
    // - Type checking
    // - Computed properties
    // - Function methods

    const user = await ctx.users.addAsync(userData);

    // Access computed and function properties
    const createdUser = user[0];
    console.log("Display name:", createdUser.displayName); // Computed
    console.log("Is admin:", createdUser.isAdmin()); // Function

    return createdUser;
}

// Usage
const user = await createUserWithSchemaFeatures({
    email: "[email protected]",
    name: "Admin User"
    // role defaults to "user" (but we can override)
});

await ctx.saveChangesAsync();

Common Patterns

User Registration

const newUser = await ctx.users.addAsync({
  name: userData.name,
  email: userData.email,
  passwordHash: await hashPassword(userData.password),
});
await ctx.saveChangesAsync();

Product Catalog Management

const products = await ctx.products.addAsync(
  ...productData.map((p) => ({
    name: p.name,
    price: p.price,
    category: p.category,
    inStock: true,
  }))
);
await ctx.saveChangesAsync();

Bulk Data Import

const batchSize = 100;
for (let i = 0; i < data.length; i += batchSize) {
  const batch = data.slice(i, i + batchSize);
  await ctx.items.addAsync(...batch);
  await ctx.saveChangesAsync();
}

Next Steps