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
- How Change Tracking Works
- Basic Update Operations
- Batch Update Operations
- Advanced Update Patterns
- Change Management
- Update Type Safety
- Performance Considerations
- Best Practices
- Error Handling
- Common Update Patterns
- Next Steps
Overview
Routier’s update system works through:
- Proxy-based change tracking - Entities automatically track modifications
- No manual update calls - Changes are detected automatically
- Batch change management - Multiple changes are saved together
- Type-safe updates - Full TypeScript support for property modifications
- 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); // falseSaving 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 persistedUpdate 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 changesBest 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 exist2. Update Related Fields Together
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
- Data Manipulation - Learn about proxy-based updates and array/object manipulation
- Create Operations - Learn how to add new entities
- Read Operations - Learn how to query and retrieve data
- Delete Operations - Learn how to remove entities
- Bulk Operations - Learn how to handle multiple entities efficiently