Attachments and Dirty Tracking

Goal: Understand how to inspect and control the internal change tracking set for a collection, force-mark items dirty, and transfer tracking metadata across DataStore instances.

When to use

  • You need to persist an entity even if no properties changed (force dirty)
  • You want to include/exclude specific entities from the next save
  • You run multiple DataStore instances and need to transfer the tracked set between them

Key ideas

  • Attachments collection: Each collection exposes an attachments helper for interacting with the change-tracked set
  • Dirty state: Use getChangeType(entity) to inspect state and markDirty(entity) to force persistence
  • Add/Remove: Use set(...entities) and remove(entity) to manage which entities are tracked
  • Transfer: Move attachments between DataStore instances to keep tracking metadata in sync

Example

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

const productSchema = s
    .define("products", {
        id: s.string().key().identity(),
        name: s.string(),
        category: s.string(),
        price: s.number(),
        inStock: s.boolean(),
    })
    .compile();

class Ctx extends DataStore {
    products = this.collection(productSchema).create();
    constructor(dbName: string) {
        super(new MemoryPlugin(dbName));
    }
}

// Two contexts pointing at the same database
const ctx1 = new Ctx("app-db");
const ctx2 = new Ctx("app-db");

// Seed and save
const [created] = await ctx1.products.addAsync({
    name: "Mouse",
    category: "accessory",
    price: 49.99,
    inStock: true,
});
await ctx1.saveChangesAsync();

// Force-mark an entity as dirty without changing fields
const first = await ctx1.products.firstAsync();
ctx1.products.attachments.markDirty(first);

// Save will include the entity even if no field changed
await ctx1.saveChangesAsync();

// Transfer attachments between contexts
// Attach in ctx2 so it can manage/save this entity's changes
ctx2.products.attachments.set(first);

// Change and save from ctx2
first.price = 44.99;
await ctx2.saveChangesAsync();

// Inspect attachment state
const changeType = ctx2.products.attachments.getChangeType(first);
// changeType could be: "notModified", "propertiesChanged", "markedDirty", etc.

Notes

  • markDirty is useful when a plugin requires an update write even if values are identical
  • Transferring attachments works best when both contexts reference the same physical database/plugin config