@metreeca/keep
    Preparing search index...

    Module @metreeca/keep - v0.12.2

    Core model-driven storage API.

    Persists and retrieves linked data resources independently of the backend holding them. Resources are described by shapes defined with the @metreeca/blue validation library. They are read and written as Resource states, Template retrieval templates and Projection queries, following the data and query models of the @metreeca/qest data-modelling library.

    A StoreClient defines the data operations a connector implements against its backend. A Store adds mutation events, transactional execution and lifecycle management, and exposes both surfaces through one object. Connectors typically obtain the management surface from createManagingStore.

    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, narrowed to a validated retrieval template
    • create — Add a resource from a validated state to a collection held by another resource
    • 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 a single unit of work; see Store for atomicity and isolation semantics.

    Mutation Events

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

    Lifecycle

    close releases the resources held by a store, such as database connections, file handles and observer subscriptions.

    Shape Validation

    Request data is validated against the supplied shape before it reaches the backend:

    • model is validated for lookup and create
    • state is validated for update and insert against the supplied shape, and for create against the shape of the collected resources

    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 #), a state whose id conflicts with entry (not nested under it on creation, differing from it otherwise), or a state member filling an identifier slot that is not a single path segment
    • TraceError — model or state fails validate against the shape
    • Problem — network, storage, or other processing failures

    Errors are therefore handled at the call site, with await and try/catch or a chained .catch. The rejection type tells logic errors apart 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: {},
    vendor: { id: {}, name: {} }
    }
    });

    // collection retrieval with filtering, ordering, and pagination

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

    Creating and Updating Resources

    // creation within the collection holding the new resource, its identifier minted by the store unless stated

    const product = await store.create({
    entry: "http://example.com/products/",
    shape: CatalogueShape,
    model: { products: {} },
    state: {
    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: catalogue, shape: CatalogueShape, model: { products: {} }, 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.

    Modules

    batching

    Batching store client.

    caching

    Caching store wrapper.

    index

    Core model-driven storage API.

    managing

    Managing store wrapper.

    validating

    Validating store wrapper.