Metreeca Keep
    Preparing search index...

    Model-driven storage API.

    Splits the storage surface across two interfaces: StoreClient carries the CRUD and data-loading methods that read and write linked-data resources described as Resource states and Template retrieval templates from the @metreeca/qest data-modelling library, validated against shapes defined using the @metreeca/blue validation library; Store extends it with management facilities (mutation events, transactional execution, lifecycle). Connectors compose the two — typically via createManagingStore — into a single store exposing both surfaces through one object.

    StoreClient implementations are expected to fully support the query language defined by @metreeca/qest, including property selection, linked resource expansion, filtering, ordering, and pagination.

    CRUD Operations

    The StoreClient interface supports conditional resource operations for standard CRUD workflows:

    • lookup — Retrieve a resource matching a validated retrieval template
    • create — Create a resource from a validated state
    • update — Replace a resource state with a validated state
    • delete — Delete a resource identified by a validated entry

    Data Loading

    For direct data loading, the following unconditional operations bypass existence checks:

    • insert — Unconditionally upsert a resource from a validated state
    • remove — Unconditionally remove a resource identified by a validated entry

    Transactions

    execute groups multiple operations into an atomic unit of work; see Store for isolation semantics.

    Mutation Events

    observe registers a StoreObserver for resource mutations, optionally filtered by resource identifiers and their descendants.

    Lifecycle

    Every store exposes a close method that releases underlying resources (database connections, file handles, observer subscriptions). Implementations with no resources to release MAY return a resolved no-op.

    Shape Validation

    Resource data is automatically validated against the supplied shape:

    Validation failures surface as a TraceError carrying the collected failure trace.

    Error Channel

    Every StoreClient method returns a Promise<…>; all errors are delivered as promise rejections, regardless of origin:

    • RangeError — malformed entry (not an absolute IRI, contains ? or #) or a state carrying an id differing from entry
    • TraceErrormodel or state fails validate against the shape
    • Problem — network, storage, or other processing failures

    Callers should await and try/catch (or chain .catch) at the call site; the rejection type discriminates logic errors from process errors.

    Retrieving Resources

    // single resource retrieval with explicit template

    const product = await store.lookup({
    entry: "http://example.com/products/1",
    shape: ProductShape,
    model: {
    name: "",
    price: 0,
    vendor: { id: "", name: "" }
    }
    });

    // retrieval using the shape's own model as template

    const full = await store.lookup({
    entry: "http://example.com/products/1",
    shape: ProductShape,
    model: model(ProductShape)
    });

    // collection retrieval with filtering, ordering, and pagination

    const catalog = await store.lookup({
    entry: "http://example.com/products/",
    shape: ProductShape,
    model: {
    products: [{
    id: "",
    name: "",
    price: 0,
    ">=price": 50, // price ≥ 50
    "~name": "widget", // name contains "widget"
    "^price": 1, // sort by price ascending
    "@": 0, // offset
    "#": 25 // limit
    }]
    }
    });

    Creating and Updating Resources

    await store.create({ entry: "http://example.com/products/42", shape: ProductShape, state: {
    id: "http://example.com/products/42",
    name: "Widget",
    price: 29.99,
    vendor: "http://example.com/vendors/acme"
    } });

    await store.update({ entry: "http://example.com/products/42", shape: ProductShape, state: {
    id: "http://example.com/products/42",
    name: "Widget",
    price: 39.99,
    vendor: "http://example.com/vendors/acme"
    } });

    Deleting Resources

    await store.delete({ entry: "http://example.com/products/42", shape: ProductShape });
    

    Loading Data

    // unconditionally upsert (create or replace)
    await store.insert({ entry: "http://example.com/products/42", shape: ProductShape, state: {
    id: "http://example.com/products/42",
    name: "Widget",
    price: 39.99,
    vendor: "http://example.com/vendors/acme"
    } });

    // unconditionally remove (silently succeeds if absent)
    await store.remove({ entry: "http://example.com/products/42", shape: ProductShape });

    Observing Mutations

    const unsubscribe = store.observe(mutations => {
    for (const [id, exists] of Object.entries(mutations)) {
    console.log(exists ? "upserted" : "removed", id);
    }
    }, "http://example.com/products/");

    unsubscribe(); // stop receiving events

    Executing Transactions

    await store.execute(async store => {
    await store.create({ entry: product.id, shape: ProductShape, state: product });
    await store.update({ entry: inventory.id, shape: InventoryShape, state: inventory });
    });
    Note

    The Transaction Design companion document covers the design rationale for transaction semantics, including cross-backend isolation levels and concurrency models.

    Interfaces

    Store

    Model-driven resource store.

    StoreClient

    Model-driven resource CRUD operations.

    StoreObserver

    Store mutation observer.

    Documents

    Transaction Design

    Cross-backend transaction isolation semantics for Store