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

    Module model

    Client-driven resource retrieval.

    Defines types for specifying the data envelope to retrieve in REST/JSON APIs, including property projection, 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.

    Retrieval model

    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: {}, // numeric property
    available: {}, // boolean property
    vendor: { // nested resource, expanded
    id: {},
    name: {}
    }
    };

    Every request is an object and every leaf is {}, the Atomic standing for the value as it comes: a literal, the reference of a linked resource left unexpanded, or the coalesced label of a localised property. A template carries no data of its own, so what a client writes is a pure statement of what it wants back. The empty request {} states nothing and brings back the server defaults.

    A collection is reached through the resource that owns it, following REST/JSON practice, and is constrained there: the entry naming it carries the Criteria keys that filter, sort, and paginate it alongside the per-item keys. A single call retrieves filtered, sorted, and paginated results with arbitrarily deep expansions:

    const template: Template = {
    items: {

    id: {}, // per-item keys
    name: {},
    price: {},
    vendor: { id: {}, name: {} }, // nested resource

    ">=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

    }
    };

    Cardinality is not stated by the notation: the same entry shape serves a single-valued and a multi-valued property, and which one a field names is settled by the model. Constraints are simply meaningless on a single-valued property and are rejected there.

    A localised property is retrieved in either of two ways. An Atomic yields its coalesced label, the plain string resolved under the request's negotiated language priority; a Locale map yields the tagged values structurally, as a Dictionary restricted to the locales its TagRange keys select:

    const template: Template = {
    id: {},
    label: {}, // coalesced under language negotiation
    title: { "*": {} }, // all available languages
    description: { "en": {}, "fr": {} } // English and French
    };

    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). Per-tag cardinality follows the property, not the template. Tag ranges select retrieved content only and are independent of Criteria: a Locale map carries TagRange keys, never constraint keys. Resource matching by localised text is done separately, at the enclosing collection 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 literals, linked resource references, nested templates expanding a linked resource inline, or Locale tag-range maps declaring a localised cell that yields a complete Dictionary value per row:

    const projection: Projection = {
    "id=id": {},
    "name=name": {},
    "price=price": {},
    "vendorName=vendor.name": {}, // property path
    "releaseYear=year:releaseDate": {}, // 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. A collection is evaluated under grouped semantics when at least one Projection binding resolves to an aggregate Expression; otherwise it stays ungrouped, every item is projected on its own, and an aggregate constraint reduces over the values its path gathers from the item under evaluation, filtering, sorting, or ranking the items by that reduction.

    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:": {}, // count of items per vendor
    "avgPrice=avg:price": {} // average price per vendor
    }
    };

    The ungrouped reduction states cardinality constraints over a plain template, retrieving the vendors carrying at least three products:

    const template: Template = {
    vendors: {
    id: {},
    name: {},
    ">=count:products": 3
    }
    };

    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:": {},
    "^count:": "desc"
    }
    };

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

    // Price range for slider bounds

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

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

    // Total product count

    const productCount: Template = {
    items: {
    "count=count:": {}
    }
    };

    // → { 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. Branch out only where the alternatives want different shapes; where one shape serves them all, a plain Placeholder addresses the property directly:

    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
    • boolean — false < true
    • number — standard numeric ordering; NaN is unordered
    • string — Unicode codepoint collation
    Warning

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

    Result Typing

    A template says what to retrieve, not what the retrieved values are: an Atomic leaf stands for whatever the property holds, so a template alone cannot tell a string property from a number one. Result types come from the declared model instead. The keys a client writes and the keys it reads back differ too: Criteria constraints carry nothing back, and a Binding lands under its result name, the part before the =.

    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.

    Criteria objects are serialised as application/x-www-form-urlencoded strings via encodeCriteria / decodeCriteria for transmission as URL query strings in GET requests.

    Warning

    Form serialisation carries constraints alone; servers are expected to convert to a collection template by merging them into the entry naming the target endpoint's collection property, alongside a default per-item retrieval template.

    The format encodes queries as label=value pairs where:

    • Labels use the same prefixed operator syntax as Criteria 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.

    Projection

    Collection property projection.

    Slot

    Template value model.

    Cell

    Projection value model.

    Placeholder

    Property value template.

    Atomic

    Atomic value template.

    Locale

    Localised text map template.

    Union

    Union-typed property template.

    Branch

    Union branch key.

    Query

    Constrained retrieval node.

    Criteria

    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 Criteria or Projection key.

    Operator

    Constraint operator symbols.

    Transform

    Value transforms for computed expressions.

    Aggregate

    Aggregate transform.

    TransformSignature

    Static typing profile of a Transform.

    Documents

    QEST: Queryable REST/JSON APIs

    A REST/JSON data model and a client-driven retrieval model for projection, filtering, and aggregation

    Functions

    isTemplate

    Checks if a value is a Template.

    isProjection

    Checks if a value is a Projection.

    isSlot

    Checks if a value is a Slot.

    isCell

    Checks if a value is a Cell.

    isPlaceholder

    Checks if a value is a Placeholder.

    isAtomic

    Checks if a value is an Atomic.

    isLocale

    Checks if a value is a Locale template.

    isUnion

    Checks if a value is a Union.

    isBranch

    Checks if a value is a Branch key.

    isQuery

    Checks if a value is a Query over a given retrieval form.

    isCriteria

    Checks if a value is a Criteria.

    isCriterion

    Checks if an entry is a valid Criteria constraint.

    isSelector

    Checks if a value is a Criteria constraint key.

    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.

    isOperator

    Checks if a value is an Operator.

    isTransform

    Checks if a value is a Transform.

    isAggregate

    Checks if a value is an Aggregate.

    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.

    encodeCriteria

    Encodes criteria as a URL-safe string.

    decodeCriteria

    Decodes criteria from a URL-safe string.

    encodeProbe

    Encodes a probe as a key string.

    decodeProbe

    Decodes a probe from a key string.