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

    Module template

    Client-driven resource retrieval.

    Defines types for specifying the data envelope to retrieve in REST/JSON APIs, including property selection, linked resource expansion, and, for collections, filtering, sorting, and pagination.

    Model type hierarchy
    Note

    QEST's design rationale covers the client-driven retrieval approach; Appendix A covers cross-backend semantics and normalisation.

    Data model

    Type inference

    Type guards

    Accessors

    Codecs

    Retrieval Patterns

    A Template specifies which properties to retrieve from a single Resource and how deeply to expand linked resources. No over-fetching of unwanted fields, no under-fetching requiring additional calls:

    const template: Template = {
    id: "", // resource identifier
    name: "", // string property
    price: 0, // numeric property
    available: true, // boolean property
    vendor: { // nested resource
    id: "",
    name: ""
    }
    };

    A Query specifies how to retrieve a Resource collection — the collection-shaped counterpart of Template, combining a per-item element (a scalar, a nested Template (or per-branch Union), or a Projection for computed aggregates) with a Selection for filtering, ordering, and pagination. Collection queries appear inside a managing resource that owns the collection, following REST/JSON best practices. A collection is a tuple pairing the per-item element with an optional Selection — a single call retrieves filtered, sorted, and paginated results with arbitrarily deep expansions, no over-fetching, no under-fetching:

    const template: Template = {
    items: [
    { // per-item element (a nested Template)
    id: "",
    name: "",
    price: 0,
    vendor: { id: "", name: "" } // nested resource
    },
    { // collection-wide Selection
    ">=price": 50, // price ≥ 50
    "<=price": 150, // price ≤ 150
    "~name": "widget", // name contains "widget"
    "?category": ["electronics", "home"], // category in list
    "^price": 1, // sort by price ascending
    "^name": -2, // then by name descending
    "@": 0, // skip first 0 results
    "#": 25 // return at most 25 results
    }
    ]
    };

    For multilingual properties, use Locales templates with TagRange keys to select language tags to retrieve. Within a single map, all tag-range-keyed values must be uniformly scalar or uniformly array:

    const template: Template = {
    id: "",
    title: { "*": "" }, // all available languages
    description: { "en": "", "fr": "" }, // English or French
    keywords: { "en": [""], "fr": [""] } // multi-valued, English or French
    };

    A localised property is a single structured value, the localised counterpart of a nested resource rather than a multi-valued property: a Dictionary reached through Locales as its own Placeholders arm. The TagRange keys are RFC 4647 basic language ranges that filter which locales populate the map (the standalone * matches every tag, and a plain range such as en also matches more specific tags like en-US), while the per-tag value shape ("" or [""]) only selects each entry's cardinality. Tag ranges select retrieved content only and are independent of Selection: a Locales map carries TagRange keys, never Selection operator keys. Resource matching by localised text is done separately, at the enclosing collection's Selection via ?/!.

    Important

    The @none key for non-localised values is not supported; use the und tag for language-neutral values.

    Projections can define computed properties using expressions combining property paths with Transform.

    Plain transforms operate on individual values and may project scalar literals, linked resource references, nested templates expanding a linked resource inline, or Locales tag-range maps declaring a localised cell that yields a complete Dictionary value per row:

    const projection: Projection = {
    "id=id": "",
    "name=name": "",
    "price=price": 0,
    "vendorName=vendor.name": "", // property path
    "releaseYear=year:releaseDate": 0, // transform
    "vendorRow=vendor": { id: "", name: "" }, // nested template
    "label=title": { "*": "" } // localised cell (full Dictionary value per row)
    };

    A projection emits distinct rows: rows with the same combination of cell values collapse into one, so the result is the set of distinct binding tuples rather than a multiset. Distinctness spans the whole collection, folding both multi-valued fan-out duplicates and equal tuples from different items; include an identifying binding such as id (as above) to keep otherwise-equal items on separate rows.

    Aggregate transforms operate on sets of values. When at least one aggregate Expression appears in a Projection or the sibling Selection, the query is evaluated under grouped semantics; otherwise every row is projected independently and no grouping applies.

    Under grouped semantics, each operator's role is determined by whether its expression references an aggregate transform:

    • Projection bindings — non-aggregate bindings contribute to the grouping key and appear verbatim in the output row; aggregate bindings compute per-group summaries
    • Filter constraints — non-aggregate filters restrict the input set before grouping; aggregate filters select groups after aggregation
    • Ordering expressions — a non-aggregate ordering expression sorts the groups by one of the grouping keys; an aggregate ordering expression sorts them by its post-aggregation value

    Grouping is fixed by the projection alone and is never inferred from a sort key: a non-aggregate ordering expression must reference an existing grouping key, and processors must reject one that matches none.

    Rows sharing the same grouping-key values collapse into a single group. Aggregate filter and ordering constraints are independent of any projected bindings: an aggregate may appear in a constraint without being projected, and a projected aggregate may appear without being constrained.

    Aggregate expressions use bag semantics over their inputs: count: (empty path) returns the number of rows in scope; a non-empty path (for example, sum:price) ranges over the values resolved by the path for each input row, with multi-valued path fan-outs contributing every resolved value individually. No implicit deduplication is applied — clients needing distinct-value aggregates project the value of interest as a non-aggregate grouping binding.

    const template: Template = {
    items: [{
    "vendor=vendor": { id: "", name: "" }, // group by vendor
    "items=count:": 0, // count of items per vendor
    "avgPrice=avg:price": 0 // average price per vendor
    }]
    };

    See Aggregate Transforms for the transform catalogue and cross-backend semantics.

    Aggregates enable faceted search patterns, computing category counts, value ranges, and totals in a single call:

    // Category facet with product counts

    const categoryFacet: Template = {
    items: [{
    "category=category": "",
    "count=count:": 0,
    "^count": "desc"
    }]
    };

    // → { items: [
    // { category: "Electronics", count: 150 },
    // { category: "Home", count: 89 }
    // ]}

    // Price range for slider bounds

    const priceRange: Template = {
    items: [{
    "min=min:price": 0,
    "max=max:price": 0
    }]
    };

    // → { items: [{ min: 9.99, max: 1299.00 }] }

    // Total product count

    const productCount: Template = {
    items: [{
    "count=count:": 0
    }]
    };

    // → { items: [{ count: 284 }] }

    For properties whose declared range is a union type, a Union declares per-branch retrieval by mapping opaque keys to the Placeholder to fetch for each variant of interest. The keys carry no positional or nominal meaning: the variant a placeholder retrieves is fixed by matching it against the property's declared variants, and an unmatched variant is skipped at runtime:

    const template: Template = {
    id: "",
    creator: {
    "0": { id: "", name: "" }, // a person-shaped branch
    "1": { id: "", legalName: "" } // an organisation-shaped branch
    }
    };

    Expressions

    A Projection Binding key pairs a result name with a computed value: a pipeline of Transform and a property path (together an Expression). The result name and its = are mandatory; a bare identifier is not a binding:

    binding     = name '=' expression
    name        = identifier
    expression  = transform* path?
    transform   = identifier ':'
    path        = identifier ( '.' identifier )*
    
    • Identifiers follow Identifier rules (ECMAScript names)
    • Transforms form a pipeline applied right-to-left (functional composition order)
    • An empty path computes aggregates over the input collection
    vendorName=vendor.name       // named nested property path
    total=sum:items.price        // named computed aggregate
    result=round:avg:scores      // pipeline: inner transform applied first
    count=count:                 // empty path (aggregate over the collection)
    

    Value Ordering

    Comparison (<, >, <=, >=) and sort (^) operators rely on a total ordering over values defined by the XPath 2.0 comparison operators, which are in turn based on XSD ordered value spaces. These operators, along with sort focus (+), target Literal values only; Reference values, nested Template resources, and Dictionary values are neither comparable nor sortable:

    • null — undefined values sort before all defined values
    • booleanfalse < true
    • numberstandard numeric ordering; NaN is unordered
    • stringUnicode codepoint collation
    Warning

    Cross-type comparisons and values that fall outside these rules produce unpredictable, system-dependent results.

    Type Inference

    Instance infers the type of the Resource fetched by a given Template, so client code can declare strongly-typed result variables without restating the type:

    const template = {
    id: "",
    name: "",
    tags: [""],
    vendor: { id: "", name: "" }
    } satisfies Template;

    type ProductView = Instance<typeof template>;
    // → {
    // readonly id: Reference;
    // readonly name: string;
    // readonly tags: readonly string[];
    // readonly vendor: { readonly id: Reference; readonly name: string };
    // }

    The projection drops Selection metadata, widens collection tuples into homogeneous arrays, projects a Union into a union of per-branch results, recurses through nested templates and collection projections, and reduces each Binding key in a nested Projection to its Identifier portion, mirroring how the runtime materialises a fetched resource.

    Serialisation

    Template objects are serialised as JSON via encodeTemplate / decodeTemplate, with IRI internalisation and resolution handled transparently. The encoder emits plain JSON by default and optionally URL-encoded JSON or URL-safe base64url-encoded JSON for transport; the decoder auto-detects the input encoding.

    Selection objects are serialised as application/x-www-form-urlencoded strings via encodeSelection / decodeSelection for transmission as URL query strings in GET requests.

    Warning

    Form serialisation specifies only selection constraints; servers are expected to convert to a collection template by wrapping inside the target endpoint's collection property and providing a default resource retrieval template.

    The format encodes queries as label=value pairs where:

    • Labels use the same prefixed operator syntax as Selection constraint keys
    • Each pair carries a single value; repeated labels are merged into arrays where accepted
    • Postfix aliases provide natural form syntax for some operators:
      • expression=value for ?expression=value (disjunctive matching)
      • expression<=value for <=expression=value (less than or equal)
      • expression>=value for >=expression=value (greater than or equal)

    Values use JSON primitive syntax, with a Dictionary entry inlined through a single postfix @tag suffix:

    value       = null | literal | tagged
    literal     = boolean | number | string
    tagged      = string '@' tag
    tag         = BCP 47 language tag
    
    • References are serialised as strings
    • A string may carry a single @tag suffix, lifting it into a one-entry Dictionary (for example, "text"@en decodes to { en: "text" })
    • The encoder always produces double-quoted strings; the decoder accepts unquoted strings as a shorthand

    Encoding notes:

    • Some operator characters are unreserved in RFC 3986 and remain unencoded: ~ (like), ! (all)
    • Reserved characters in values are percent-encoded: & (separator), = (key/value), + (space), % (escape)
    Warning

    Numeric-looking values like 123 are parsed as numbers unless double-quoted.

    category=electronics
      &category=home
      &~name=widget
      &price>=50
      &price<=150
      &^price=asc
      &@=0
      &#=25
    

    This query:

    1. Filters items where category is "electronics" OR "home"
    2. Filters items where name contains "widget"
    3. Filters items where price is between 50 and 150 (inclusive)
    4. Sorts results by price ascending
    5. Returns the first 25 items (offset 0, limit 25)

    Variables

    Transforms

    Transform signature table.

    Type Aliases

    Template

    Resource retrieval template.

    Placeholders

    Property value set template.

    Placeholder

    Property value template.

    Model

    Value retrieval template.

    Query

    Collection retrieval template.

    Locales

    Localised text map template.

    Union

    Union-typed property template.

    Branch

    Union branch key.

    Projection

    Collection property projection.

    Selection

    Collection retrieval constraints.

    Binding

    Named computed expression.

    Expression

    Computed expression.

    Pipe

    Transform pipe.

    Path

    Property path.

    Options

    Constraint option set.

    Option

    Constraint option.

    Order

    Sort order.

    Probe

    Parsed Selection or Projection key.

    Operator

    Constraint operator symbols.

    Transform

    Value transforms for computed expressions.

    TransformSignature

    Static typing profile of a Transform.

    Instance

    Infers the Resource type fetched by a Template.

    Index

    Extracts the numeric-literal keys of a union frame, in both string ("0") and numeric (0) form.

    Slots

    Projects a template-shaped object through Instance.

    Name

    Projects a Instance property key to its output name.

    Documents

    QEST: Queryable REST/JSON APIs

    A REST/JSON data model and client-driven template language for retrieval, filtering, and aggregation

    Functions

    isTemplate

    Checks if a value is a Template.

    isPlaceholders

    Checks if a value is a Placeholders set.

    isPlaceholder

    Checks if a value is a Placeholder.

    isModel

    Checks if a value is a Model single-value template.

    isQuery

    Checks if a value is a Query.

    isLocales

    Checks if a value is a Locales template.

    isUnion

    Checks if a value is a Union.

    isBranch

    Checks if a value is a Union branch key.

    isProjection

    Checks if a value is a Projection.

    isSelection

    Checks if a value is a Selection.

    isBinding

    Checks if a value is a Binding.

    isExpression

    Checks if a value is an Expression.

    isOptions

    Checks if a value is an Options set.

    isOption

    Checks if a value is an Option.

    isOrder

    Checks if a value is an Order.

    isProbe

    Checks if a value is a Probe.

    isSelector

    Checks if a value is a valid Selection entry key.

    isOperator

    Checks if a value is an Operator.

    isTransform

    Checks if a value is a Transform.

    isAggregate

    Checks whether a value is an aggregate Transform.

    isVacuous

    Checks if a value is vacuous per the template elision rule.

    getOrderPrecedence

    Resolves an Order to its sort precedence.

    getOrderDirection

    Resolves an Order to its sort direction.

    encodeTemplate

    Encodes a template as a JSON string.

    decodeTemplate

    Decodes a template from an encoded string.

    encodeSelection

    Encodes a selection as a URL-safe string.

    decodeSelection

    Decodes a selection from a URL-safe string.

    encodeProbe

    Encodes a probe as a key string.

    decodeProbe

    Decodes a key string into a probe.