@metreeca/qest - v0.9.2
    Preparing search index...

    @metreeca/qest

    npm

    Minimalist foundations for client-driven, queryable REST/JSON APIs.

    @metreeca/qest standardizes critical capabilities that vanilla REST/JSON APIs typically lack or implement in ad‑hoc, non‑portable ways:

    • Client-Driven: clients specify what they need, retrieving complex envelopes in a single call
    • Queryable: advanced filtering and aggregation, supporting faceted search and analytics

    Developers seek these features in frameworks like GraphQL; @metreeca/qest brings them to REST/JSON, achieving:

    • Familiar Patterns: standard REST and JSON conventions, no new paradigms to learn
    • Simple Clients: no specialized libraries, preprocessors, or code generators
    • Automated Servers: model-driven development, dramatically reducing implementation effort
    • Standard Caching: compatibility with CDNs and browser caches using standard GET requests
    • URL-Based Versioning: standard REST versioning without field deprecation complexity

    Installation

    npm install @metreeca/qest
    
    Warning

    TypeScript consumers must use "moduleResolution": "nodenext"/"node16"/"bundler" in tsconfig.json. The legacy "node" resolver is not supported.

    Usage

    Note

    This section introduces essential concepts; for complete coverage, see the API reference:

    Module Description
    @metreeca/qest Shared values, types, and guards
    @metreeca/qest/state Resource state management
    @metreeca/qest/model Client-driven retrieval

    @metreeca/qest types define payload semantics and formats for standard REST operations:

    Method Type Description
    GET Resource Resource retrieval
    GET Resource Collection retrieval
    GET Model Client-driven resource retrieval
    GET Query Client-driven collection retrieval
    POST Resource Resource creation
    PUT Resource Complete resource state update
    PATCH Patch Partial resource state update
    DELETE IRI Resource deletion

    A Resource is a property map describing data returned by a REST endpoint, with optional links to other endpoints:

    GET https://data.example.com/products/123
    
    {
    "id": "https://data.example.com/products/123",
    "name": "Widget",
    "category": "Electronics",
    "tags": [
    "gadget",
    "featured"
    ],
    "vendor": "https://data.example.com/vendors/456",
    "price": 99.99,
    "inStock": true
    }

    The same format is used for complete resource updates:

    PUT https://data.example.com/products/123
    
    ({
    name: "Widget",
    category: "Electronics",
    tags: ["gadget", "premium"],
    vendor: "https://data.example.com/vendors/456",
    price: 79.99,
    // inStock // not included → deleted
    });

    A Patch describes partial updates with the same effect:

    PATCH https://data.example.com/products/123
    
    ({
    tags: ["gadget", "premium"], // updated
    price: 79.99, // updated
    inStock: null, // deleted
    });

    Properties set to null are deleted; properties not included are unchanged.

    Client-driven retrieval lets clients specify exactly what data to retrieve from both single resources and collections. Expansions and nested queries can be arbitrarily deep: no over-fetching of unwanted fields, no under-fetching requiring additional calls to resolve linked resources.

    This is the core contribution of @metreeca/qest: vanilla REST/JSON APIs lack a standard way for clients to control retrieval, forcing them to accept fixed server responses or rely on ad-hoc query parameters. Client-driven retrieval fills this gap, supporting precise control over responses while remaining fully compatible with standard HTTP caching.

    Important

    Client-driven retrieval is fully optional. Servers may provide defaults, typically derived from the underlying data model, preserving standard REST/JSON behavior while enabling advanced capabilities when needed.

    Resources — A Model defines the data retrieval envelope: which properties to include and how deeply and in how much detail to expand linked resources.

    GET https://data.example.com/products/123?<model>
    

    where <model> is the following URL-encoded JSON:

    ({
    id: "",
    name: "",
    price: 0,
    vendor: {
    id: "",
    name: "",
    },
    });

    The response includes only the requested properties, with the linked vendor expanded to show just id and name:

    {
    "id": "https://data.example.com/products/123",
    "name": "Widget",
    "price": 99.99,
    "vendor": {
    "id": "https://data.example.com/vendors/145",
    "name": "Acme"
    }
    }

    Collections — A Query combines a projection model with filtering, ordering, and pagination criteria, also supporting computed projections including aggregates for faceted search and analytics.

    GET https://data.example.com/products/?<query>
    

    where <query> is the following URL-encoded JSON:

    ({
    items: [
    {
    id: "",
    name: "",
    price: 0,
    vendor: {
    id: "",
    name: "",
    },
    ">=price": 50, // filter: price ≥ 50
    "<=price": 150, // filter: price ≤ 150
    "^price": "asc", // sort: by price ascending
    "#": 25, // limit: 25 results
    },
    ],
    });

    A single call returns exactly what the client requested:

    • projected: product id, name, price
    • expanded: linked vendor with only id and name (not its full state)
    • filtered: price between 50 and 150
    • sorted: by price ascending
    • paginated: up to 25 results
    {
    "items": [
    {
    "id": "https://data.example.com/products/456",
    "name": "Gadget",
    "price": 59.99,
    "vendor": {
    "id": "https://data.example.com/vendors/145",
    "name": "Acme"
    }
    },
    {
    "id": "https://data.example.com/products/123",
    "name": "Widget",
    "price": 99.99,
    "vendor": {
    "id": "https://data.example.com/vendors/145",
    "name": "Acme"
    }
    },
    {
    "id": "https://data.example.com/products/789",
    "name": "Gizmo",
    "price": 129.99,
    "vendor": {
    "id": "https://data.example.com/vendors/236",
    "name": "Globex"
    }
    }
    ]
    }

    Integrated Ecosystem

    Important

    @metreeca/qest defines data types only; applications are absolutely free to handle validation, storage, and publishing as they see fit.

    @metreeca/qest is also the foundation of an integrated ecosystem for rapid development of linked data applications, turning those same types into a complete model-driven stack:

    Package Description
    @metreeca/qest Data types for client-driven, queryable REST/JSON APIs
    @metreeca/blue Declarative blueprints for model-driven linked data processing
    @metreeca/keep (upcoming) Shape-driven storage framework with pluggable adapters
    @metreeca/gate (upcoming) Shape-driven REST/JSON API publishing

    JSON-LD Foundations

    JSON-LD (JSON for Linked Data) is a W3C standard for publishing linked data on the web. It extends JSON with web identifiers (IRIs) to link resources across systems and domains, and to give property names precise, machine-readable meaning by mapping them to shared vocabularies — a capability at the heart of the Web Data Activity ( Semantic Web) and modern knowledge graphs.

    @metreeca/qest defines a controlled JSON-LD subset designed to feel like plain idiomatic JSON, letting JavaScript developers work with linked data using familiar REST/JSON patterns without mastering JSON-LD technicalities, while retaining full compatibility with standard JSON-LD processors.

    This controlled subset is specified by:

    • compacted documents with short property names and nested objects, just like regular JSON

    • ECMAScript identifiers as property names (terms), enabling dot notation access; JSON-LD keywords (@id, @type, etc.) and blank node identifiers are not allowed and must be mapped to identifiers via an application-provided @context (for instance, "id": "@id"); @context must also maps property names to IRIs for semantic interoperability

    • native JSON primitives (boolean, number, string) as values; typed literals with arbitrary datatypes are not allowed and must be represented as strings with datatype coercion declared in @context

    • language maps for localised text; @none keys for non-localised values in language maps are not allowed and must be handled using string | Local union types or the zxx language tag

    • index maps for key-indexed property values; indexed semantics must be signalled by application-provided @context declarations, as indexed values are otherwise indistinguishable from nested resources

    • IRI references for linking resources across systems and domains; data structures require absolute IRIs; codec functions handle conversion to/from root-relative forms

    Support

    • open an issue to report a problem or to suggest a new feature
    • start a discussion to ask a how-to question or to share an idea

    License

    This project is licensed under the Apache 2.0 License – see LICENSE file for details.