@queryweave/core
Everything below is exported from @queryweave/core. Nothing else is.
Models
Section titled “Models”defineQueryModel(definitions, options?)
Section titled “defineQueryModel(definitions, options?)”function defineQueryModel<TDefs extends QueryParamDefinitions>( definitions: TDefs, options?: QueryModelOptions<TDefs>,): QueryModel<TDefs>;Composes named parameters into one model. Throws a TypeError if a key is the empty string or
$, the reserved modelIssueKey.
options.name labels the model for diagnostics. options.refine takes refinements that run after
every parameter decodes, reporting under modelIssueKey.
QueryModel
Section titled “QueryModel”| Member | Returns | Notes |
|---|---|---|
name |
string | undefined |
Diagnostics only |
params |
Readonly<TDefs> |
The definitions, for introspection |
keys() |
readonly string[] |
Managed keys, in definition order |
defaults() |
QueryModelDefaults<TDefs> |
Only parameters that have a default; required keys absent |
decode(input) |
DecodeResult<Values> |
Never throws; a throwing codec or refinement becomes an issue |
decodeAsync(input) |
Promise<DecodeResult<Values>> |
Required when validation is asynchronous; never rejects |
encode(values) |
QueryOutput |
Canonical; omits defaults; managed keys only |
normalize(input) |
QueryOutput |
decode then encode, recovering on failure |
modelIssueKey
Section titled “modelIssueKey”const modelIssueKey = "$";The key used for model-level issues.
Model types
Section titled “Model types”QueryModelValues, QueryModelDefaults, QueryModelKey, QueryPatch, QueryParamDefinitions,
QueryParamValue, QueryParamPresenceOf, QueryModelOptions.
Parameters
Section titled “Parameters”param.boolean(options?: BooleanParamOptions): QueryParamBuilder<boolean, "required">;param.choice<const T extends string>(choices: readonly T[]): QueryParamBuilder<T, "required">;param.custom<T>(codec: QueryCodec<T>, options?: CustomParamOptions): QueryParamBuilder<T, "required">;param.date(options?: DateParamOptions): QueryParamBuilder<string, "required">;param.datetime(options?: DatetimeParamOptions): QueryParamBuilder<Date, "required">;param.integer(options?: IntegerParamOptions): QueryParamBuilder<number, "required">;param.list<T>(item: QueryParam<T>, options?: ListParamOptions): QueryParamBuilder<readonly T[], "required">;param.number(options?: NumberParamOptions): QueryParamBuilder<number, "required">;param.text(options?: TextParamOptions): QueryParamBuilder<string, "required">;Options by family:
| Family | Options |
|---|---|
text |
allowEmpty, maxLength, minLength, trim |
integer |
max, min |
number |
max, min |
boolean |
truthy, falsy |
date |
max, min as YYYY-MM-DD strings |
datetime |
max, min as Date values |
list |
maxItems, minItems |
custom |
consumesMultipleValues, kind |
Named parameter constructors
Section titled “Named parameter constructors”Each param method also has a named export with identical behavior and inference:
booleanParam(options?: BooleanParamOptions);choiceParam<const T extends string>(choices: readonly T[]);customParam<T>(codec: QueryCodec<T>, options?: CustomParamOptions);dateParam(options?: DateParamOptions);datetimeParam(options?: DatetimeParamOptions);integerParam(options?: IntegerParamOptions);listParam<T>(item: QueryParam<T>, options?: ListParamOptions);numberParam(options?: NumberParamOptions);textParam(options?: TextParamOptions);Use named constructors when bundle size matters and a model only needs some built-in families. A
bundler can then remove unrelated codecs. The param.* registry remains supported as the compact,
ergonomic form when that distinction is not important.
Construction validates options: inverted bounds (min > max, minLength > maxLength,
minItems > maxItems), a date bound that is not a real YYYY-MM-DD date, a datetime bound
that is an invalid Date, an empty or overlapping boolean spelling set, a list whose item consumes
repeated values, and nullable() on a list all throw a TypeError.
date keeps the YYYY-MM-DD string it decodes. datetime decodes RFC 3339 with Z or an
offset into a Date and writes it in UTC, with milliseconds only when they are not zero; a time
without an offset is invalid. Encoding an invalid Date, or one outside the years 0001 to 9999,
throws.
QueryParamBuilder
Section titled “QueryParamBuilder”default(value: Exclude<TValue, undefined>): QueryParam<Exclude<TValue, undefined>, "default">;describe(description: string): QueryParamBuilder<TValue, TPresence>;nullable(): QueryParamBuilder<TValue | null, TPresence>;optional(): QueryParamBuilder<TValue | undefined, "optional">;refine<TOutput extends Decoded>(refinement: QueryRefinement<Decoded, TOutput>): QueryParamBuilder<TOutput | Absent, TPresence>;refine<TOutput>(transform: QueryTransform<Decoded, TOutput>): QueryParamBuilder<TOutput | Absent, TPresence>;Decoded is Exclude<TValue, null | undefined> and Absent is Extract<TValue, null | undefined>:
a refinement never receives null or undefined, and they stay in the value type. The first
overload validates or narrows; the second changes the type and therefore needs encode.
default() returns a QueryParam, not a builder, so it must be last in a chain. It throws a
TypeError when the parameter’s own codec rejects the value, and stores a deep-frozen copy of
arrays and plain objects. A Date default is copied too, and every read of it — a recovery,
model.defaults(), defaultValue — returns a new Date.
QueryParam
Section titled “QueryParam”kind, presence, consumesMultipleValues, description, codec, defaultValue.
QueryParamKind is
"boolean" | "choice" | "custom" | "date" | "datetime" | "integer" | "list" | "number" | "text".
QueryParamPresence is "default" | "optional" | "required". QueryParamBase is the
non-generic part of a parameter (kind, presence, consumesMultipleValues, description), and
QueryParamFactory is the type of param.
Codecs
Section titled “Codecs”QueryCodec<TValue>
Section titled “QueryCodec<TValue>”interface QueryCodec<TValue> { decode(input: readonly string[], context: QueryDecodeContext): QueryValueResult<TValue>; decodeAsync?( input: readonly string[], context: QueryDecodeContext, ): Promise<QueryValueResult<TValue>>; encode(value: TValue, context: QueryEncodeContext): readonly string[];}QueryDecodeContext and QueryEncodeContext both carry key: string and
path: readonly PropertyKey[].
Results
Section titled “Results”function okValue<T>(value: T, issues?: readonly QueryIssue[]): QueryValueResult<T>;function failValue<T>(issues: readonly QueryIssue[]): QueryValueResult<T>;QueryValueResult<T> is one value’s outcome; DecodeResult<T> is a whole model’s, carrying
partial instead of value when ok is false.
Issues
Section titled “Issues”function createQueryIssue(init: QueryIssueInit): QueryIssue;function hasQueryIssueCode(issues: readonly QueryIssue[], code: QueryIssueCode): boolean;QueryIssue carries key, code, optional input, message, and optional path, and is frozen.
QueryIssueInit is the same shape, accepted by createQueryIssue, which copies input and path
and omits them when empty.
QueryIssueCode is "missing" | "empty" | "invalid" | "out_of_range" | "unknown_choice" | "unexpected_multiple_values" | "validation_failed" | "async_required". The union is closed;
adding a member is a minor change under the stability policy.
Refinements
Section titled “Refinements”interface QueryRefineContext { readonly key: string; readonly path: readonly PropertyKey[];}
interface QueryRefinement<TInput, TOutput = TInput> { readonly name?: string | undefined; readonly async?: boolean | undefined; refine( value: TInput, context: QueryRefineContext, ): QueryRefinementResult<TOutput> | Promise<QueryRefinementResult<TOutput>>; encode?(value: TOutput): TInput;}
interface QueryTransform<TInput, TOutput> extends QueryRefinement<TInput, TOutput> { encode(value: TOutput): TInput;}QueryRefinementResult<T> is { ok: true; value: T } or { ok: false; issues: readonly QueryRefinementIssue[] },
where an issue carries message and an optional path. async: true tells the synchronous
decode to report async_required without starting the refinement. encode is the inverse used
when a refined value is written back; QueryTransform makes it mandatory for a refinement that
changes the type.
Query input
Section titled “Query input”function parseQueryString(source: string): QueryOutput;function formatQueryString(entries: QueryOutput): string;function normalizeQueryEntries(input: QueryInput): QueryOutput;function selectQueryValues(entries: QueryOutput, key: string): readonly string[];function queryOutputEquals(left: QueryOutput, right: QueryOutput): boolean;QueryEntry is readonly [key: string, value: string], QueryOutput is readonly QueryEntry[],
and QueryInput is string | Iterable<QueryEntry> | QueryRecordInput, where QueryRecordInput is
Readonly<Record<string, string | readonly string[] | undefined>>. URLSearchParams satisfies the
iterable form structurally, which is how it is accepted without a DOM library.
formatQueryString is byte-identical to URLSearchParams.prototype.toString, replacing a lone
surrogate with U+FFFD as the platform does. parseQueryString decodes + as a space and each run
of percent sequences on its own, so a malformed sequence or an invalid UTF-8 run keeps only itself
verbatim; the platform would substitute U+FFFD for the bad bytes instead.
Runtime
Section titled “Runtime”createQueryRuntime(options)
Section titled “createQueryRuntime(options)”function createQueryRuntime<TDefs extends QueryParamDefinitions>( options: QueryRuntimeOptions<TDefs>,): QueryRuntime<TDefs>;options is { model, adapter, navigation?, throttle? }. navigation defaults to "push".
throttle is the least number of milliseconds between two writes and defaults to 0; a negative
or non-finite value throws.
QueryRuntime
Section titled “QueryRuntime”| Member | Returns |
|---|---|
model |
the bound model |
read() |
QuerySnapshot<Values> |
settled() |
Promise<QuerySnapshot<Values>> |
update(patch, options?) |
Promise<QueryTransitionResult> |
replace(values, options?) |
Promise<QueryTransitionResult> |
remove(keys, options?) |
Promise<QueryTransitionResult> |
reset(keys?, options?) |
Promise<QueryTransitionResult> |
transaction(mutate, options?) |
Promise<QueryTransitionResult> |
subscribe(listener) |
() => void |
dispose() |
void |
Every operation rejects after dispose(). QueryTransitionOptions is { navigation?, signal? };
signal is a QueryAbortSignal, { readonly aborted: boolean; readonly reason?: unknown }, which
any AbortSignal satisfies. Transitions run one at a time per runtime, in call order, each
starting from the settled state; inside a throttle window they are held and written together
when it closes. read() decodes only when the adapter’s entries changed since the last read. settled() resolves
once no asynchronous decode is pending. subscribe takes a QuerySnapshotListener<Values>,
(snapshot: QuerySnapshot<Values>) => void; every listener runs on every notification, and a
listener error is rethrown after the loop.
QuerySnapshot
Section titled “QuerySnapshot”type QueryStatus = "invalid" | "pending" | "valid";
interface QuerySnapshot<TValues> { readonly status: QueryStatus; readonly values: TValues; readonly issues: readonly QueryIssue[]; readonly result: DecodeResult<TValues>;}values is always present and frozen. Use result.ok when you want narrowing. pending means an
asynchronous refinement has not settled; values then carry the synchronous best effort and a
settled snapshot follows through the subscription.
QueryTransitionResult
Section titled “QueryTransitionResult”type QueryTransitionOutcome = "committed" | "redirected" | "refused" | "unchanged" | "cancelled";
interface QueryTransitionResult<TValues> { readonly navigation: "push" | "replace"; readonly outcome: QueryTransitionOutcome; readonly reason?: unknown; readonly output: QueryOutput; readonly snapshot: QuerySnapshot<TValues>;}Two outcomes are decided by the runtime. unchanged: the output equalled the adapter’s entries, so
nothing was written and nobody was notified. cancelled: the transition was abandoned before it
applied its change, because its signal aborted or the runtime was disposed; reason is then the
signal’s reason and output is empty. The other outcomes come from the adapter, with its reason.
Transitions written together in one throttle window share one result.
Adapter contracts
Section titled “Adapter contracts”interface QuerySource { read(): QueryInput;}
type QueryNavigationOutcome = "committed" | "redirected" | "refused";
interface QueryNavigationResult { readonly outcome: QueryNavigationOutcome; readonly reason?: unknown;}
interface QueryAdapter extends QuerySource { push(next: QueryOutput): QueryNavigationResult | void | Promise<QueryNavigationResult | void>; replace(next: QueryOutput): QueryNavigationResult | void | Promise<QueryNavigationResult | void>; subscribe(listener: QueryChangeListener): () => void;}QueryChangeListener is (input: QueryInput) => void. QueryNavigationMode is
"push" | "replace". Returning nothing from push or replace means committed; an adapter
throws only for environment errors, never to report a refusal.
Bindings
Section titled “Bindings”interface QueryBinding<TDefs, TView = QueryModelValues<TDefs>> { readonly runtime: QueryRuntime<TDefs>; readonly values: TView;}The shape every framework binding shares. @queryweave/vue extends it.
Not exported
Section titled “Not exported”There is no useQueryState, useQueryStates, parseAs*, createParser, createLoader,
createSerializer, withDefault, or tuple setter. These identities are rejected by
ADR 0001 and
a repository test fails the build if one appears.