Update Operations

Update operations in Routier leverage the framework’s powerful change tracking system. Entities returned from queries are proxy objects that automatically track changes, making updates simple and efficient.

Quick Navigation

Overview

Routier’s update system works through:

  1. Proxy-based change tracking - Entities automatically track modifications
  2. No manual update calls - Changes are detected automatically
  3. Batch change management - Multiple changes are saved together
  4. Type-safe updates - Full TypeScript support for property modifications
  5. Efficient persistence - Changes are optimized for database operations

How Change Tracking Works

Proxy Entities

When you query entities in Routier, they are returned as proxy objects that automatically track changes:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Proxy entities - entities returned from queries are proxy objects
const user = await ctx.users.firstAsync();
console.log("User before update:", user);

// Direct property modification - changes are automatically tracked
user.name = "Updated Name";
user.email = "[email protected]";
user.age = 30;

console.log("User after update:", user);
console.log("Changes tracked automatically");

// Save changes to persist updates
await ctx.saveChangesAsync();

Automatic Change Detection

Routier automatically detects property changes without requiring manual update calls:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Automatic change detection - no manual update calls needed
const user = await ctx.users.firstAsync();

// Modify properties directly - Routier automatically detects changes
user.name = "John Updated";
user.email = "[email protected]";
user.age = 31;

// Changes are tracked automatically - no need to call update methods
console.log("User updated:", user);

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

Basic Update Operations

Single Property Updates

Update individual properties on entities:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Single property updates
const user = await ctx.users.firstAsync();

// Update individual properties
user.name = "New Name";
console.log("Updated name:", user.name);

user.email = "[email protected]";
console.log("Updated email:", user.email);

user.age = 25;
console.log("Updated age:", user.age);

// Save changes
await ctx.saveChangesAsync();

Multiple Property Updates

Update multiple properties on a single entity:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Multiple property updates
const user = await ctx.users.firstAsync();

// Update multiple properties at once
user.name = "John Doe";
user.email = "[email protected]";
user.age = 30;
user.isActive = true;

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

// All changes are tracked and will be saved together
await ctx.saveChangesAsync();

Nested Object Updates

Update nested objects and their properties:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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({}),
}).compile();

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

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

const ctx = new AppContext();

// Nested object updates
const user = await ctx.users.firstAsync();

// Update nested object properties
user.profile.firstName = "John";
user.profile.lastName = "Doe";
user.profile.bio = "Software developer";

// Update preferences
user.preferences.theme = "dark";
user.preferences.notifications = false;

console.log("Updated user with nested objects:", user);

// Save changes
await ctx.saveChangesAsync();

Array Updates

Modify arrays within entities:

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

// Define a user schema with arrays
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    tags: s.array(s.string()).default([]),
    scores: s.array(s.number()).default([]),
}).compile();

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

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

const ctx = new AppContext();

// Array updates
const user = await ctx.users.firstAsync();

// Update array properties
user.tags = ["developer", "javascript", "typescript"];
user.scores = [85, 92, 78, 96];

console.log("Updated user with arrays:", user);

// Modify existing arrays
user.tags.push("react");
user.scores.push(88);

console.log("Added to arrays:", user);

// Save changes
await ctx.saveChangesAsync();

Batch Update Operations

Update Multiple Entities

Update multiple entities efficiently:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Update multiple entities
const users = await ctx.users.where(u => u.age >= 18).toArrayAsync();

// Update multiple entities in batch
users.forEach(user => {
    user.isActive = true;
    user.name = user.name.toUpperCase();
});

console.log("Updated users:", users);

// All changes are tracked and saved together
await ctx.saveChangesAsync();

Conditional Batch Updates

Apply updates based on conditions:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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 batch updates
const users = await ctx.users.toArrayAsync();

// Apply conditional updates
users.forEach(user => {
    if (user.age >= 18) {
        user.isActive = true;
    }

    if (user.email.includes("admin")) {
        user.name = `Admin: ${user.name}`;
    }

    if (user.createdAt < new Date("2023-01-01")) {
        user.name = `Legacy: ${user.name}`;
    }
});

console.log("Conditionally updated users:", users);

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

Batch Updates with Transformations

Apply transformations to multiple entities:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    score: s.number().default(0),
    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 updates with transformations
const users = await ctx.users.toArrayAsync();

// Apply transformations to multiple users
users.forEach(user => {
    // Transform name to uppercase
    user.name = user.name.toUpperCase();

    // Calculate score based on age
    user.score = user.age * 10;

    // Add prefix to email
    user.email = `user_${user.email}`;
});

console.log("Transformed users:", users);

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

Advanced Update Patterns

Computed Updates

Update entities with computed or derived values:

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

// Define a user schema with computed properties
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    firstName: s.string(),
    lastName: s.string(),
    email: s.string().distinct(),
    age: s.number(),
}).modify(w => ({
    // Computed property that updates when firstName or lastName changes
    fullName: w.computed((entity) => `${entity.firstName} ${entity.lastName}`).tracked(),
})).compile();

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

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

const ctx = new AppContext();

// Computed updates - computed properties update automatically
const user = await ctx.users.firstAsync();

console.log("Before update:", user.fullName); // "John Doe"

// Update the underlying properties
user.firstName = "Jane";
user.lastName = "Smith";

console.log("After update:", user.fullName); // "Jane Smith" - automatically computed

// Save changes
await ctx.saveChangesAsync();

Incremental Updates

Apply incremental changes to numeric fields:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    score: s.number().default(0),
    level: s.number().default(1),
    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();

// Incremental updates
const user = await ctx.users.firstAsync();

// Increment numeric values
user.score += 10;
user.level += 1;

console.log("Incremented values:", { score: user.score, level: user.level });

// Decrement values
user.score -= 5;

console.log("Decremented score:", user.score);

// Save changes
await ctx.saveChangesAsync();

Conditional Field Updates

Update fields based on specific conditions:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    isActive: s.boolean().default(true),
    role: s.string("admin", "user", "guest").default("user"),
    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 field updates
const user = await ctx.users.firstAsync();

// Update fields based on conditions
if (user.age >= 18) {
    user.isActive = true;
}

if (user.email.includes("admin")) {
    user.role = "admin";
}

if (user.createdAt < new Date("2023-01-01")) {
    user.name = `Legacy: ${user.name}`;
}

// Only update if certain conditions are met
if (user.score && user.score > 100) {
    user.role = "premium";
}

console.log("Conditionally updated user:", user);

// Save changes
await ctx.saveChangesAsync();

Change Management

Checking for Changes

Monitor and check for pending changes:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Checking for changes
const user = await ctx.users.firstAsync();

// Check if there are any pending changes
const hasChanges = ctx.hasChanges();
console.log("Has changes:", hasChanges); // false

// Make some changes
user.name = "Updated Name";
user.email = "[email protected]";

// Check again
const hasChangesAfter = ctx.hasChanges();
console.log("Has changes after update:", hasChangesAfter); // true

// Save changes
await ctx.saveChangesAsync();

// Check after save
const hasChangesAfterSave = ctx.hasChanges();
console.log("Has changes after save:", hasChangesAfterSave); // false

Saving Changes

Persist tracked changes to the database:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Saving changes
const user = await ctx.users.firstAsync();

// Make changes
user.name = "Updated Name";
user.email = "[email protected]";
user.age = 30;

console.log("Changes made:", user);

// Save all tracked changes
await ctx.saveChangesAsync();
console.log("Changes saved successfully");

// Verify changes were persisted
const updatedUser = await ctx.users.firstAsync();
console.log("Persisted user:", updatedUser);

Partial Saves

Save changes in batches or selectively:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Partial saves - save changes incrementally
const user1 = await ctx.users.firstAsync();
const user2 = await ctx.users.skip(1).firstAsync();

// Update first user
user1.name = "Updated User 1";
user1.email = "[email protected]";

// Save first user's changes
await ctx.saveChangesAsync();
console.log("First user saved");

// Update second user
user2.name = "Updated User 2";
user2.email = "[email protected]";

// Save second user's changes
await ctx.saveChangesAsync();
console.log("Second user saved");

// Both users are now updated and persisted

Update Type Safety

Schema Type Checking

Ensure type safety when updating entities:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferType } 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(),
    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-safe updates with TypeScript
type User = InferType<typeof userSchema>;

async function updateUser(userId: string, updates: Partial<User>) {
    const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);

    if (!user) {
        throw new Error("User not found");
    }

    // TypeScript ensures type safety for property assignments
    if (updates.name !== undefined) {
        user.name = updates.name; // TypeScript ensures this is a string
    }

    if (updates.email !== undefined) {
        user.email = updates.email; // TypeScript ensures this is a string
    }

    if (updates.age !== undefined) {
        user.age = updates.age; // TypeScript ensures this is a number
    }

    await ctx.saveChangesAsync();
    return user;
}

// Usage with type safety
await updateUser("user-123", {
    name: "John Doe",
    age: 30
    // email: 123 // TypeScript error: Type 'number' is not assignable to type 'string'
});

Business Logic Type Checking

Implement business logic validation during updates:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferType } 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(),
    role: s.string("admin", "user", "guest").default("user"),
    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();

// Business logic type checking
type User = InferType<typeof userSchema>;

async function promoteUser(userId: string) {
    const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);

    if (!user) {
        throw new Error("User not found");
    }

    // Business logic validation
    if (user.age < 18) {
        throw new Error("Cannot promote users under 18");
    }

    if (user.role === "admin") {
        throw new Error("User is already an admin");
    }

    // Type-safe update
    user.role = "admin"; // TypeScript ensures this is a valid role

    await ctx.saveChangesAsync();
    return user;
}

// Usage with business logic
try {
    const promotedUser = await promoteUser("user-123");
    console.log("User promoted:", promotedUser);
} catch (error) {
    console.error("Promotion failed:", error.message);
}

Performance Considerations

Batch Updates

Optimize performance with batch update operations:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Batch updates for better performance
const users = await ctx.users.where(u => u.age >= 18).toArrayAsync();

// Update multiple entities efficiently
users.forEach(user => {
    user.isActive = true;
    user.name = user.name.toUpperCase();
});

console.log(`Updated ${users.length} users`);

// Save all changes in one operation
await ctx.saveChangesAsync();
console.log("All changes saved efficiently");

Change Batching

Manage change batching for optimal performance:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Change batching - group related changes together
const user1 = await ctx.users.firstAsync();
const user2 = await ctx.users.skip(1).firstAsync();

// Make changes to multiple entities
user1.name = "Updated User 1";
user1.email = "[email protected]";

user2.name = "Updated User 2";
user2.email = "[email protected]";

// All changes are batched and saved together
await ctx.saveChangesAsync();
console.log("All changes batched and saved");

// This is more efficient than:
// await ctx.saveChangesAsync(); // after user1 changes
// await ctx.saveChangesAsync(); // after user2 changes

Best Practices

1. Leverage Change Tracking

Take advantage of automatic change tracking:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Leverage change tracking - no manual update methods needed
const user = await ctx.users.firstAsync();

// ✅ Good - direct property modification
user.name = "Updated Name";
user.email = "[email protected]";
user.age = 30;

// ✅ Good - changes are automatically tracked
console.log("Changes tracked automatically");

// ✅ Good - save all changes at once
await ctx.saveChangesAsync();

// ❌ Avoid - manual update methods (don't exist in Routier)
// user.update({ name: "New Name" }); // This doesn't exist
// user.save(); // This doesn't exist

Update related fields in a single operation:

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

// Define a user schema
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    firstName: s.string(),
    lastName: s.string(),
    email: s.string().distinct(),
    age: s.number(),
    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();

// Update related fields together
const user = await ctx.users.firstAsync();

// ✅ Good - update related fields together
user.firstName = "John";
user.lastName = "Doe";
user.email = "[email protected]";

// ✅ Good - all related changes are tracked together
console.log("Related fields updated together");

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

// ❌ Avoid - updating fields separately and saving multiple times
// user.firstName = "John";
// await ctx.saveChangesAsync();
// user.lastName = "Doe";
// await ctx.saveChangesAsync();

3. Validate Before Updating

Implement validation before applying updates:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s, InferType } 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(),
    role: s.string("admin", "user", "guest").default("user"),
    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();

// Validate before updating
type User = InferType<typeof userSchema>;

async function updateUserSafely(userId: string, updates: Partial<User>) {
    const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);

    if (!user) {
        throw new Error("User not found");
    }

    // Validate business rules before updating
    if (updates.age !== undefined && updates.age < 0) {
        throw new Error("Age cannot be negative");
    }

    if (updates.email !== undefined && !updates.email.includes("@")) {
        throw new Error("Invalid email format");
    }

    if (updates.role !== undefined && !["admin", "user", "guest"].includes(updates.role)) {
        throw new Error("Invalid role");
    }

    // Apply updates
    if (updates.name !== undefined) user.name = updates.name;
    if (updates.email !== undefined) user.email = updates.email;
    if (updates.age !== undefined) user.age = updates.age;
    if (updates.role !== undefined) user.role = updates.role;

    await ctx.saveChangesAsync();
    return user;
}

// Usage with validation
try {
    const updatedUser = await updateUserSafely("user-123", {
        name: "John Doe",
        age: 30,
        role: "admin"
    });
    console.log("User updated successfully:", updatedUser);
} catch (error) {
    console.error("Update failed:", error.message);
}

4. Use Meaningful Update Patterns

Follow consistent patterns for updates:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    score: s.number().default(0),
    level: s.number().default(1),
    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 meaningful update patterns
const user = await ctx.users.firstAsync();

// ✅ Good - meaningful update patterns
function updateUserProfile(user: any, profileData: { name: string; email: string }) {
    user.name = profileData.name;
    user.email = profileData.email;
}

function incrementUserScore(user: any, points: number) {
    user.score += points;

    // Level up logic
    if (user.score >= 100) {
        user.level += 1;
        user.score = 0; // Reset score
    }
}

function activateUser(user: any) {
    user.isActive = true;
}

// Usage
updateUserProfile(user, { name: "John Doe", email: "[email protected]" });
incrementUserScore(user, 25);
activateUser(user);

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

// ❌ Avoid - meaningless direct assignments without context
// user.name = "John";
// user.email = "[email protected]";
// user.score = 100;

Error Handling

Update Error Handling

Handle errors gracefully during update operations:

import { DataStore } from "@routier/datastore";
import { MemoryPlugin } from "@routier/memory-plugin";
import { s } 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(),
    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();

// Update error handling
async function updateUserSafely(userId: string, updates: { name?: string; email?: string; age?: number }) {
    try {
        const user = await ctx.users.firstOrUndefinedAsync(u => u.id === userId);

        if (!user) {
            throw new Error(`User with ID ${userId} not found`);
        }

        // Apply updates
        if (updates.name !== undefined) user.name = updates.name;
        if (updates.email !== undefined) user.email = updates.email;
        if (updates.age !== undefined) user.age = updates.age;

        // Save changes
        await ctx.saveChangesAsync();

        console.log("User updated successfully:", user);
        return user;

    } catch (error) {
        console.error("Update failed:", error.message);

        // Handle different types of errors
        if (error.message.includes("not found")) {
            console.error("User not found - check user ID");
        } else if (error.message.includes("network")) {
            console.error("Network error - retry later");
        } else if (error.message.includes("database")) {
            console.error("Database error - check connection");
        } else {
            console.error("Unexpected error:", error);
        }

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

// Usage with error handling
try {
    const updatedUser = await updateUserSafely("user-123", {
        name: "John Doe",
        email: "[email protected]",
        age: 30
    });
} catch (error) {
    console.error("Failed to update user:", error.message);
}

Common Update Patterns

User Profile Updates

const user = await ctx.users.where((u) => u.id === userId).firstAsync();
if (user) {
  user.name = newData.name;
  user.email = newData.email;
  user.updatedAt = new Date();
  await ctx.saveChangesAsync();
}

Status Updates

const orders = await ctx.orders
  .where((o) => o.status === "pending")
  .toArrayAsync();
orders.forEach((order) => {
  order.status = "processing";
  order.processedAt = new Date();
});
await ctx.saveChangesAsync();

Batch Price Updates

const products = await ctx.products
  .where((p) => p.category === "electronics")
  .toArrayAsync();
products.forEach((product) => {
  product.price = product.price * 1.1; // 10% increase
});
await ctx.saveChangesAsync();

Next Steps