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:
Developers seek these features in frameworks like GraphQL; @metreeca/qest brings them to REST/JSON, achieving:
npm install @metreeca/qest
TypeScript consumers must use "moduleResolution": "nodenext"/"node16"/"bundler" in tsconfig.json.
The legacy "node" resolver is not supported.
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.
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:
id, name, pricevendor with only id and name (not its full state)price between 50 and 150price ascending{
"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"
}
}
]
}
@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 (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
This project is licensed under the Apache 2.0 License – see LICENSE file for details.