Expand description
Query-directed planning (spec/mutate/README.md §11.8, mutate 3.1;
spec/surface 4.1): an OQX selector plus effect templates compiled
into the concrete effect list the planner lowers. The selector is a
producer of effects, never a second write engine — the plan that comes
out has today’s shape plus a frozen Selection. The reference is
packages/core/src/mutate/select.ts; every rule and message here mirrors
it.
Pipeline. parse(select) → check_root_spellings → the static
admission (compile: consumer, root, clauses, reserved aliases, the
templates’ shapes) → the AST rewrite (one select item per { oqx }
slot appended after the user’s items, named __omg_sel_<t>_<slot>,
on the raw tree so the runner’s resolve_aliases inlines a user alias a
template reads) → the runner’s AST entry (crate::query::query_ast,
limit = bound + 1) → the hits → the row binding (bind: hits in
result order × templates in order, k = h·T + t), which the store’s
lowering loop consumes unchanged, so the conflict tracker gives the
cross-hit semantics for free (two hits writing one scalar with equal
values dedupe with a diagnostic, different values are
conflicting_effects, inserts into one shared target are positional in
hit order).
Expressions. OQX has no expression entry point: each { oqx: e } is
parsed through the wrapper select __omg_x: <e> from docs and its one
item taken — a top-level item is scope depth 1, exactly the hit’s scope,
so ^ and N^ validate as they will evaluate. Parse-error offsets are
shifted back by the wrapper’s head (16 code points).
Binding (§11.8’s table). A template’s primary target that is absent
(target / into / targets) is the hit by the root’s kind (document
/ block / node; targets → the one-element list); a { kind, id: { oqx } } or { kind: "document", path: { oqx } } slot evaluates to a
string (that member as written) or a row-as-value { id, path } (→ { kind, id } whichever member held the expression); absent / null / []
→ target_missing { effect_index, template_index, row, expression }; a
list of one or more → new ambiguous_target { …, count } (a
one-element list is still a list); any other type → filter_invalid { hint: "selection" }. at.before / at.after, markdown, to, path,
separator must be strings; value and a create’s top-level
frontmatter values may be any JSON as the runner rendered it (a row value
lands as the record { id, path }), never absent. Every error raised from
an expanded effect carries effect_index (the expanded position) plus
template_index and row.
Sugar (spec/mutate §11.8 “Sugar”, 3.2). In every expression slot a
JSON string beginning exactly LEADER (-> ) desugars to { oqx: rest } before the admission — typing follows the value as { oqx } does; a
string not beginning -> is literal, always (no escaping, no ${
scanning; -> "-> …" spells a literal that begins with the leader). A
leader whose expression is a bare string literal or a template literal
without an interpolation folds to the plain literal before planning
(-> "x" and "x" give one plan_id). A bare target string (with or
without a selector) normalizes before resolution: a path-looking string
(a / or ending .md) is { kind: "document", path }, a b_ / d_ /
n_ id its kind, "$new.n" the symbol’s kind (an into is settled from
the prefix plan’s new_ids, [settle_into_symbols]), "-> expr" the
identity the value names (a row → kind by id prefix, a path string → a
document), a # locator is refused with a fix. Without a selector a
-> / { oqx } slot evaluates in its own target’s row scope: the
effects are grouped by resolved target identity and one synthetic query
per identity (from <root> where id == "<id>", the slot items appended as
__omg_sel_<effectIndex>_<slot>) runs through the AST runner; the plan
has no selection, planner_v stays p3 and the intent record gains
templates (the effects as written) — a plain request without the leader
produces none, so no existing digest moves. A -> slot on a create
without a selector, a -> target without a selector, an unparsable
expression (offsets relative to the slot string, past the leader) and a
leader in expect / field / op are filter_invalid { hint: "selection" }.
Cardinality. expect.max (a non-negative integer; required on a
one-shot apply, optional on plan / execute) bounds the selection; the
query runs with limit = max + 1 (or SELECTION_CEILING + 1 without a
max) and more hits than the bound is selection_too_large { cardinality, max, implicit? } — the engine never plans a partial set. Zero hits are a
plan with no effects (execute / apply → commit: null).
Structs§
- Bound
- What
bindproduced: the concrete effects and each hit’s(id, path)in result order. - Compiled
- A compiled request: the selector as parsed, its root, the admitted templates, the rewritten tree and the bound.
- Expansion
- What the selector (or the selector-less sugar) produced: the concrete
effects, the frozen record (a selector’s), the hits’ identities for error
annotation, and the effects as written when a selector-less request
evaluated an expression (the plan’s and the intent record’s
templates). - Select
Request plan/apply/executewith aselect— the selector, the templates (the call’seffects) and the optional bound, as given — or, since 3.2, a selector-less call whoseeffectscarry sugar (selectisnull: the effects are desugared and their->/{ oqx }slots evaluated in their own targets’ row scopes).- Template
- One admitted template: the template with every slot removed (the binder
copies it and fills the slots), its slots, the default target and the
target members written as a bare
$new.n.
Enums§
- Member
- Which target member a bare string sits in: the member decides the
provisional kind a
$new.ntakes until the symbol table settles it (anintois ambiguous — document or block;set’s follows its field;targets[k]is a block).
Constants§
- HINT
- The
hintof everyfilter_invalidthe selector admission raises. - LEADER
- §11.8 “Sugar” (3.2): the leader a string slot begins with to be an expression — exactly dash, greater-than, space.
- SLOT_
PREFIX - The prefix of the slot projections the rewrite appends:
__omg_sel_<t>_<slot>.
Functions§
- annotate
- An error from the lowering of an expanded effect gains
template_indexandrownext to the planner’seffect_index(module docs). - bind
- The concrete effects of
hits × templates(§11.8’s expansion rule);settleresolves the bare$new.nkinds (settle_symbols). - compile
- The static half (§11.8 steps 1 and 2; the reference’s
validateExpect+admitRequest):expect, thenselectand the templates, then the selector’s admission, the templates’ desugar (3.2) and admission, and the rewrite. Nothing is read. - expand
- Run the selector and expand it (module docs; the reference’s
compileSelection), or — without a selector (3.2) — desugar the effects and evaluate their slots in their own targets’ row scopes (expand_plain).semanticis the embedded phrases of the selector (crate::query::embed_phrases). The store is borrowed mutably only for the prefix plans that settle bare$new.nkinds; nothing is written. - expand_
plain - Compile a plain
effectslist (no selector; the reference’scompilePlain): desugar the->leaders, then — when nothing is an expression — the shape check as before (3.1-identical, notemplates). Otherwise each effect with expression slots is admitted as a template over its own primary target’s row ([scope_of]): the effects are grouped by that row’s identity, one synthetic query per identity —from <root> where id == "<id>"with the slot items appended, named__omg_sel_<effectIndex>_<slot>— runs (limit 2), and the row binds the slots exactly as a selector’s hit would. Bare$new.nkinds are settled last, in request order, each against the effects before it. - no_
settle - A settlement that leaves every provisional kind in place (tests; a path with no store at hand).
- plan_
selected - Expand and plan in one write scope: the store callback the catalog hands
to
Store::plan_selected,Store::execute_replannedandStore::apply_planned. Without a selector (3.2) the plan carries noselectionbut records the effects as written astemplateswhen one evaluated an expression. - require_
max - The one-shot
applyrule (§11.8): a selector must be bounded — the caller never saw the hits (a preview is aplan, which needs no bound). The selector-less sugar path names its targets and needs none. - settle_
symbols - The settlement over a store:
planthe prefix (nothing written, no id minted) and read the symbols’ kinds off itsnew_ids. A prefix that does not plan, or a symbol it did not mint, is left provisional — the real plan reports it (target_missing { symbol }for one nothing created). - unbounded_
apply - The one-shot
applyrule’s refusal with its corrected example (3.3): the same call bounded at the selection’s actual size — the caller never saw the hits, so the engine counts them (the selector compiled, nothing written) and spells the bound.