@omgbase/core 0.28.0 → 0.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +7 -5
  2. package/dist/src/core/read/document.d.ts +11 -10
  3. package/dist/src/core/read/document.js +12 -11
  4. package/dist/src/core/read/nodes.d.ts +10 -10
  5. package/dist/src/core/read/nodes.js +11 -11
  6. package/dist/src/core/read/reader.d.ts +37 -24
  7. package/dist/src/core/read/reader.js +44 -31
  8. package/dist/src/graph/history.d.ts +25 -22
  9. package/dist/src/graph/history.js +17 -17
  10. package/dist/src/graph/inbound-links.d.ts +7 -7
  11. package/dist/src/graph/inbound-links.js +7 -7
  12. package/dist/src/graph/link-health.d.ts +9 -9
  13. package/dist/src/graph/link-health.js +9 -9
  14. package/dist/src/graph/links.d.ts +3 -3
  15. package/dist/src/graph/links.js +4 -4
  16. package/dist/src/mcp/arguments.d.ts +10 -3
  17. package/dist/src/mcp/arguments.js +32 -9
  18. package/dist/src/mcp/errors.d.ts +1 -1
  19. package/dist/src/mcp/graph.d.ts +22 -22
  20. package/dist/src/mcp/graph.js +54 -47
  21. package/dist/src/mcp/reference.d.ts +29 -1
  22. package/dist/src/mcp/reference.js +398 -255
  23. package/dist/src/mcp/server.d.ts +9 -0
  24. package/dist/src/mcp/server.js +170 -108
  25. package/dist/src/mutate/apply.d.ts +6 -6
  26. package/dist/src/mutate/apply.js +1 -1
  27. package/dist/src/mutate/canonical.d.ts +1 -1
  28. package/dist/src/mutate/canonical.js +2 -2
  29. package/dist/src/mutate/commit.d.ts +3 -3
  30. package/dist/src/mutate/commit.js +4 -4
  31. package/dist/src/mutate/docs.d.ts +1 -1
  32. package/dist/src/mutate/docs.js +8 -8
  33. package/dist/src/mutate/effects.d.ts +17 -17
  34. package/dist/src/mutate/effects.js +113 -113
  35. package/dist/src/mutate/macros.d.ts +5 -5
  36. package/dist/src/mutate/macros.js +1 -1
  37. package/dist/src/mutate/materialize.js +2 -2
  38. package/dist/src/mutate/ops.js +2 -2
  39. package/dist/src/mutate/opset.d.ts +7 -7
  40. package/dist/src/mutate/plan-update.js +2 -2
  41. package/dist/src/mutate/plan.d.ts +8 -8
  42. package/dist/src/mutate/plan.js +25 -25
  43. package/dist/src/mutate/select.d.ts +4 -4
  44. package/dist/src/mutate/select.js +57 -56
  45. package/dist/src/mutate/sugar.d.ts +10 -10
  46. package/dist/src/mutate/sugar.js +21 -21
  47. package/dist/src/oqx-js/context.d.ts +8 -4
  48. package/dist/src/oqx-js/context.js +206 -189
  49. package/dist/src/oqx-js/names.d.ts +28 -17
  50. package/dist/src/oqx-js/names.js +115 -78
  51. package/dist/src/oqx-js/planner.d.ts +4 -8
  52. package/dist/src/oqx-js/planner.js +6 -10
  53. package/dist/src/oqx-js/root-row.d.ts +6 -5
  54. package/dist/src/oqx-js/root-row.js +15 -16
  55. package/dist/src/oqx-js/run.d.ts +18 -17
  56. package/dist/src/oqx-js/run.js +57 -54
  57. package/dist/src/oqx-js/sql/translate.d.ts +5 -5
  58. package/dist/src/oqx-js/sql/translate.js +162 -161
  59. package/dist/src/oqx-js/store-index.d.ts +1 -1
  60. package/dist/src/oqx-js/store-index.js +28 -30
  61. package/dist/src/search/external.d.ts +10 -0
  62. package/dist/src/search/external.js +20 -2
  63. package/dist/src/search/resolve.d.ts +2 -2
  64. package/dist/src/search/resolve.js +1 -1
  65. package/dist/src/search/rrf.js +2 -2
  66. package/dist/src/search/text.d.ts +5 -5
  67. package/dist/src/search/text.js +2 -2
  68. package/dist/src/spec-versions.d.ts +7 -7
  69. package/dist/src/spec-versions.js +6 -6
  70. package/dist/src/surface-paths.d.ts +18 -17
  71. package/dist/src/surface-paths.js +23 -21
  72. package/dist/src/sync/admin.d.ts +2 -2
  73. package/dist/src/sync/admin.js +1 -1
  74. package/package.json +2 -2
@@ -5,233 +5,269 @@
5
5
  // tool handler stays thin. Semantics are the @omgbase/oqx contract (ADR-013);
6
6
  // when this text and the corpus (corpus/oqx/alchemy.test.ts) disagree, the
7
7
  // corpus wins.
8
+ //
9
+ // Three texts (spec/surface §4 `query_syntax`, 5.1): `QUERY_SYNTAX_CARD` is
10
+ // what `query_syntax {}` returns — a ~3 kB card an agent can hold in context
11
+ // (the 2026-10-21 agent-bench fetched the 38 kB manual twelve times and
12
+ // replayed it as cached prefix on every later turn); `QUERY_SYNTAX` is the
13
+ // whole manual (`section: "all"`); `querySyntaxSection(slug)` is one chapter of
14
+ // it, by the slugs of `QUERY_SYNTAX_SECTIONS` (the manual's `##` headings). The
15
+ // Rust twin (`crates/omgbase-surface/src/reference.rs`) mirrors all three byte
16
+ // for byte; its tests compare against this file.
8
17
  export const QUERY_SYNTAX = `# query — OQX syntax reference
9
18
 
10
19
  The \`query\` tool takes ONE plain OQX string (+ optional \`limit\`/\`cursor\`, and
11
- \`bare: true\` to drop the automatic \`id\`/\`path\` envelope — needs a \`select\`).
20
+ \`bare: true\` to drop the automatic \`_id\`/\`_path\` envelope — needs a \`select\`).
12
21
  There is no {from, filter, order} envelope: every concern is a clause of the
13
22
  string. Two rules tell the two syntaxes apart: DOT navigation belongs to the
14
- host object model (\`doc.layer\`, \`section.blocks\`, \`dst.title\`); WHITESPACE
23
+ host object model (\`_doc.layer\`, \`_section._blocks\`, \`_dst._title\`); WHITESPACE
15
24
  query directives belong to OQX — a nested query is \`<receiver> collect|exists|
16
25
  count|first|single { <block> }\` (a postfix directive + a \`{ … }\` block), never a
17
26
  method. Its distinctive power is the receiver-constrained nested query that
18
- correlates to the current row: \`from docs where nodes exists { where kind ==
19
- "md:task" }\` returns only the docs that themselves contain a matching node.
20
-
21
- [select <items>] from <target> [where <pred>] [frontier <pred>] [follow <dest> [{ … }], …]
22
- [flatten <expr> [{ … }] [as <name>]]… [group by <key>, …] [having <pred>]
23
- [order by <expr> [asc|desc], …] [limit N] [offset N]
24
- <target> count|exists|none|first|single { <block> } (scalar/one-row form: a bare target at the root scope)
25
-
27
+ correlates to the current row: \`docs { _nodes exists { _kind == "md:task" } }\`
28
+ returns only the docs that themselves contain a matching node.
29
+
30
+ <target> { [select <items>] [where <pred>] [frontier <pred>] [follow <dest> [{ … }], …]
31
+ [flatten <expr> [{ … }] [as <name>]]… [group by <key>, …] [having <pred>]
32
+ [order by <expr> [asc|desc], …] [limit N] [offset N] }
33
+ <target> count|exists|none|first|single { <block> } (a scalar or one row; <target> is docs, blocks, nodes, links or revisions)
34
+
35
+ THE COLLECTION HEADS THE BODY (OQX 0.26): \`docs { … }\` reads the documents and
36
+ shapes them with the clauses inside the braces; a consumer word between the
37
+ target and the brace (\`docs count { … }\`) shapes the whole result; nothing may
38
+ follow the closing brace. The pre-0.26 clause form \`select … from docs where …\`
39
+ is still read as the same query — write the braces.
26
40
  CLAUSE ORDER IS FIXED: select, from, where, frontier, follow, flatten, group by, having, order by, limit, offset —
27
- each at most once (\`flatten\` may repeat); an out-of-order clause is a parse error naming the order.
41
+ each at most once (\`flatten\` may repeat; \`from\` inside a body RE-PROJECTS the
42
+ rows through a relation, see Nested — it never names the source); an
43
+ out-of-order clause is a parse error naming the order.
28
44
  Only \`select\` may drop its keyword, and only as the first clause
29
- (\`path, era from docs where …\`). A top-level predicate needs \`where\`, because
30
- \`where\` follows \`from\` (\`era > 1600 from docs\` is the clause-order error); a
31
- block may LEAD with a predicate (see Sugar), but a predicate after a projection
32
- still needs the keyword (\`{ name where kind == "md:task" }\`, not
33
- \`{ name, kind == "md:task" }\`); \`from docs count\` is an error (write
34
- \`docs count { … }\`). \`where\` and later \`select\` items may use the same body's
35
- \`select\` aliases (\`select path, old: era < 1000 from docs where old\`; an alias
36
- shadows a same-named field there). \`and\` / \`or\` / \`not\` are the connectives;
45
+ (\`docs { _path, era where … }\`). A body may LEAD with a predicate — \`docs { era
46
+ > 1600 }\` filters (see Sugar) — but a predicate after a projection still needs
47
+ the keyword (\`{ _name where _kind == "md:task" }\`, not \`{ _name, _kind ==
48
+ "md:task" }\`); in the clause form a top-level predicate needs \`where\` (\`era >
49
+ 1600 from docs\` is the clause-order error), and \`from docs count\` is an error
50
+ (write \`docs count { … }\`). \`where\` and later \`select\` items may use the same
51
+ body's \`select\` aliases (\`docs { select _path, old: era < 1000 where old }\`; an
52
+ alias shadows a same-named field there). \`and\` / \`or\` / \`not\` are the connectives;
37
53
  the symbols \`&&\` / \`||\` / \`!\` are accepted synonyms (the words are canonical and
38
54
  what \`print\` writes).
39
55
 
40
56
  ## Sugar (OQX 0.17)
41
57
 
42
58
  Every form here is shorthand for an explicit directive (the AST is the explicit form):
43
- nodes { kind == "md:task" } ≡ nodes collect { where kind == "md:task" } — a receiver block with
59
+ nodes { _kind == "md:task" } ≡ nodes collect { where _kind == "md:task" } — a receiver block with
44
60
  no consumer is collect; a block whose LEADING expression is a predicate
45
61
  (comparison, call, !/is/not, in, literal, (…), a consumer test — anything
46
- but a bare name, a dotted path or a ^lift) is where-first. Bare names still
47
- project: nodes { name } ≡ nodes collect { name }; filter a bare field with
48
- nodes { is checked }. Wherever a value stands (0.20), call arguments included:
49
- size(children { is checked }); x distinct { … } ≡ x collect distinct { … }. After a
50
- follow destination too: follow children { depth 2 } is that destination's block.
62
+ but a bare name, a dotted path or a ^lift) is where-first. Names still
63
+ project: _nodes { _name } ≡ _nodes collect { _name }; filter a bare field with
64
+ _nodes { is checked }. Wherever a value stands (0.20), call arguments included:
65
+ size(_children { is checked }); x distinct { … } ≡ x collect distinct { … }. After a
66
+ follow destination too: follow _children { depth 2 } is that destination's block.
51
67
  (a, b) a tuple (0.20): two or more expressions, concatenated one level flat — a value
52
68
  (size((tags, aliases)), "x" in (tags, aliases)) and a receiver ((a, b) { … },
53
69
  follow (before, after) { depth 2 }); (a) alone is grouping
54
70
  refs(x)[0] ≡ refs(x) first { offset 0 } positional (integer literal or \${binding});
55
- out of range ⇒ absent; refs(x)[0].path navigates the row
56
- ^docs[path == ^company] ≡ ^docs first { where path == ^company } first match or absent
57
- ^docs[path == ^company]! ≡ … single { … } required: exactly one, else filter_invalid
58
- title! required: title, or an error naming the expression (and the row's $key — its id)
71
+ out of range ⇒ absent; refs(x)[0]._path navigates the row
72
+ ^docs[_path == ^company] ≡ ^docs first { where _path == ^company } first match or absent
73
+ ^docs[_path == ^company]! ≡ … single { … } required: exactly one, else filter_invalid
74
+ _title! required: _title, or an error naming the expression (and the row's $key — its _id)
59
75
  — never a filter, never a coercion (0!, ""! are values); tightest: a!.b vs a.b!
60
76
  is x / not x truthiness of x, and its negation (prefix; \`not\` ≡ \`!\`)
61
77
  x is y / x is not y identity ($key: a store row's id, else structural) — compares rows, which == does not;
62
78
  x is null = absent; for scalars is ≡ ==; comparison precedence, no chaining
63
- a and b / a or b the connectives (≡ && / ||: same precedence, short-circuit, value: data.title or path coalesces)
64
- boss: ^docs[path == ^manager], bossName: boss.title a select item may use the items to its LEFT
79
+ a and b / a or b the connectives (≡ && / ||: same precedence, short-circuit, value: title or _title coalesces)
80
+ boss: ^docs[_path == ^manager], bossName: boss._title a select item may use the items to its LEFT
65
81
  Reserved words (never a bare field name): from where select is not and or, true false null.
66
82
  Contextual words (limit order offset follow collect count first distinct desc by in frontier depth group having
67
- flatten as) parse as fields in expression position (select o: order from docs).
83
+ flatten as) parse as fields in expression position (docs { select o: order }).
68
84
 
69
- Results are LEAN hits — { id, path } + whatever \`select\` projects (or \`values\`,
85
+ Results are LEAN hits — { _id, _path } + whatever \`select\` projects (or \`values\`,
70
86
  \`count\`, \`exists\`, \`none\`). The envelope keys ARE the attributes: copy a hit's
71
- \`id\` straight into \`where id == "…"\` (no translation); \`select id, path\` is simply
72
- the envelope (no duplicate columns); any OTHER projection whose output key would
73
- be \`id\` or \`path\` must be aliased — \`select data.path\` is refused ("projection
74
- \`path\` collides with the hit envelope — alias it (\`p: data.path\`)"); a row
75
- surfacing as a value (an empty \`collect { }\`) is the same \`{ id, path }\`. Every
76
- other tool spells a row the same way (\`docs_read\` → \`{ id, path, rev, properties,
77
- raw }\`, \`blocks_read\` → \`{ id, type, text, raw, hash, … }\`, \`docs_list\` → \`{ id,
78
- path, updated_at, blocks }\`, a revision → \`{ id, path, hash }\`): hydrate by id via
79
- blocks_read/docs_read.
87
+ \`_id\` straight into \`where _id == "…"\` (no translation); \`select _id, _path\` is
88
+ simply the envelope (no duplicate columns); an alias \`_id:\`/\`_path:\` on any OTHER
89
+ expression is refused — \`blocks { select _path: _doc._path }\` fails ("projection
90
+ \`_path\` collides with the hit envelope — alias it (\`p: _doc._path\`)"); a row
91
+ surfacing as a value (an empty \`collect { }\`) is the same \`{ _id, _path }\`. Every
92
+ other tool spells a row the same way (\`docs_read\` → \`{ _id, _path, rev, properties,
93
+ _raw }\`, \`blocks_read\` → \`{ _id, _type, _text, _raw, _hash, … }\`, \`docs_list\` → \`{ _id,
94
+ _path, _updated_at, blocks }\`, a revision → \`{ _id, _path, _hash }\`): hydrate by \`_id\`
95
+ via blocks_read/docs_read.
80
96
 
81
97
  ## Paths
82
98
 
83
99
  Every path the surface returns is \`/\`-rooted — the form a reference is
84
100
  written in (\`[x](/projects/oqx.md)\`, \`before: [/timeline/kickoff.md]\`): a
85
- hit's \`path\`, an edge's \`dst_path\`, every tool's paths. Every path it
101
+ hit's \`_path\`, an edge's \`_dst_path\`, every tool's paths. Every path it
86
102
  accepts tolerates both forms (\`/a.md\` or \`a.md\`): tool arguments, \`refs(x)\`,
87
- \`within(...)\`, and a string LITERAL compared with \`path\`/\`dst_path\` (bare, or
88
- through a row handle: \`doc.path\`) by \`==\`/\`!=\` or passed to their
89
- \`.startsWith(...)\` (it is rooted first, so \`path == "a.md"\` and \`path == "/a.md"\`
90
- both match). A property holding a reference compares directly — and is never
91
- rooted: \`where ^path in list(after)\`, \`where customer == ^path\`,
92
- \`where data.path == "/x.md"\` (the author's \`path:\` key, as written) — no
93
- \`"/" + ...\` glue.
103
+ \`within(...)\`, and a string LITERAL compared with \`_path\`/\`_dst_path\` (on the row,
104
+ or through a row handle: \`_doc._path\`, \`_dst._path\`, \`$it._path\`) by \`==\`/\`!=\` or
105
+ passed to their \`.startsWith(...)\` (it is rooted first, so \`_path == "a.md"\` and
106
+ \`_path == "/a.md"\` both match). A property holding a reference compares directly —
107
+ and is never rooted: \`where ^_path in list(after)\`, \`where customer == ^_path\`,
108
+ \`where path == "/x.md"\` (YOUR authored \`path:\` key — the bare name — compared as
109
+ written) — no \`"/" + ...\` glue.
94
110
 
95
111
  ## Targets & field namespaces
96
112
 
97
- THE NAME RULE: \`$\` belongs to OQX — $it, $key, $depth, $stop, $leaf, $frontier,
98
- $ordinal are values the LANGUAGE injects into a scope; omgbase adds NO $ names
99
- (a \`$id\` in a query is an unknown name, absent). Every engine attribute is a BARE
100
- snake_case name that WINS on collision — the convention the relations (children,
101
- blocks, out, in, doc, frontmatter, …) always followed: a bare name that is an
102
- engine attribute reads the engine's value, never yours. Your data is bare too;
103
- where a key of yours collides with an engine name, reach it through a source
104
- handle: \`data.<k>\` (the merged authored view — frontmatter + inline, exactly the
105
- read a bare non-colliding key performs), \`frontmatter.<k>\`, \`inline.<k>\`. So
106
- \`path\` is the engine's /-rooted location and \`data.path\` your \`path:\` key;
107
- \`tags\` is your tags ∪ the body #hashtags and \`data.tags\` the authored list alone;
108
- \`title\` the computed title (the first H1) and \`data.title\` your \`title:\`;
109
- \`entries(data)\` lists every authored top-level key. Same value, same name
110
- everywhere: a hit's \`id\` is docs_read's \`id\` and the \`id\` argument of blocks_read.
111
-
112
- Reserved bare names per kind (engine attributes; handles; self; relations):
113
- docs id path raw hash updated_at created_at title tags format | data frontmatter inline | doc | blocks nodes out_links in_links revisions
114
- blocks id path doc_id raw hash updated_at created_at type text attrs depth ordinal | block doc | children nodes out_links section
115
- nodes id path doc_id block_id kind name value attrs | section doc block | blocks subsections children
116
- links id path doc_id block_id via_id dst_id dst_path uri field dst_kind predicate provenance anchor | doc dst block
117
- revisions id path rev seq doc_id commit ts origin actor reason hash is_current archived | doc
118
- A key of yours with any of these names is \`data.<k>\` (or frontmatter.<k> /
119
- inline.<k> by source; attrs.<k> on a block or node).
120
- UNKNOWN NAMES: a name that is none of the above AND that no object of the
121
- kind in this repository carries is reported before any row is read — on
122
- links as a filter_invalid error naming the fields; on docs/blocks/nodes the
123
- result gains \`hints\` (one line per name, nearest match when there is one)
124
- and the rows still come back; in a plan/apply/execute selector it is refused.
125
- A property some objects carry is known (partial presence is normal).
113
+ THE NAME RULE — one rule on every record kind: a BARE name is YOURS (an authored
114
+ property: frontmatter + inline on a doc, an attrs key on a block or node), an
115
+ UNDERSCORE-prefixed name is the ENGINE's (however derived: \`_id\`, \`_path\`,
116
+ \`_title\`, \`_updated_at\`, \`_doc\`), and \`$\` belongs to OQX — $it, $key, $depth,
117
+ $stop, $leaf, $frontier, $ordinal are values the LANGUAGE injects into a scope;
118
+ omgbase adds NO $ names (a \`$id\` in a query is an unknown name, absent). Nothing
119
+ bare is reserved: \`title\`, \`tags\`, \`path\`, \`id\`, \`format\`, \`doc\` are your keys
120
+ when you wrote them. Collections stay bare (\`docs { … }\`, \`^docs\`, \`docs count { … }\`,
121
+ \`entries(^$it)\`) — the collection before a body is a root, not a row property — and so do the
122
+ row functions in call position (\`text("x")\`, \`under(…)\`, \`refs(x)\`): a call is
123
+ never an entry read. There is NO self alias (the 4.x \`doc\`/\`block\`/\`section\`):
124
+ \`$it\` is the self, and \`_doc\`/\`_block\`/\`_dst\` reach ANOTHER row. So \`_path\` is the
125
+ engine's /-rooted location and \`path\` your \`path:\` key; \`_tags\` is your tags ∪ the
126
+ body #hashtags and \`tags\` the authored list alone; \`_title\` the computed title (the
127
+ first H1) and \`title\` your \`title:\`; \`_data.<k>\` ≡ \`<k>\` (the merged authored view —
128
+ kept for \`entries(_data)\` and to reach an authored key that itself starts with
129
+ \`_\`), \`_frontmatter.<k>\` / \`_inline.<k>\` one source each. Same value, same name
130
+ everywhere: a hit's \`_id\` is docs_read's \`_id\` and the \`_id\` argument of blocks_read.
131
+
132
+ Engine names per kind (attributes | handles | relations):
133
+ docs _id _path _raw _hash _updated_at _created_at _title _tags _format | _data _frontmatter _inline | _blocks _nodes _out_links _in_links _revisions
134
+ blocks _id _path _doc_id _raw _hash _updated_at _created_at _type _text _attrs _depth _ordinal | _doc | _children _nodes _out_links _section
135
+ nodes _id _path _doc_id _block_id _kind _name _value _attrs | _doc _block | _blocks _subsections _children
136
+ links _id _path _doc_id _block_id _via_id _dst_id _dst_path _uri _field _dst_kind _predicate _provenance _anchor | _doc _dst _block | —
137
+ revisions _id _path _rev _seq _doc_id _commit _ts _origin _actor _reason _hash _is_current _archived | _doc | —
138
+ root _id (^_id, 0^_id) | — | docs blocks nodes links revisions
139
+ Every bare name is yours; \`_attrs.<k>\` on a block or node is the same read as
140
+ the bare attrs key \`<k>\`.
141
+ UNKNOWN NAMES: a bare name that no object of the kind in this repository
142
+ carries is a property read that yields absent — on docs/blocks/nodes the result
143
+ gains \`hints\` (one line per name, the nearest match drawn from your keys and the
144
+ engine names: "no document in this repository carries a property \`title\`; did
145
+ you mean \`_title\`?") and the rows still come back; in a plan/apply/execute
146
+ selector it is refused. On links and revisions (closed: no authored data reaches
147
+ them) a bare name is a filter_invalid error. A \`_x\` that is no engine name of
148
+ the kind is filter_invalid naming the kind's engine names. A property some
149
+ objects carry is known (partial presence is normal).
126
150
 
127
151
  docs:
128
152
  - bare identifier = a document PROPERTY (frontmatter + inline \`key:: value\`,
129
153
  unioned; nested via dots: meta.owner). YAML/JSON docs
130
- expose the parsed object's keys the same way.
131
- - source handles = data.<k> (merged authored view) / frontmatter.<k> /
132
- inline.<k>; the whole bag as a collection via entries(data)
133
- / entries(frontmatter) / entries(inline) (see entries()).
134
- - attributes = id, path (/-rooted, see Paths), updated_at (ISO-8601,
135
- sorts chronologically), raw (the whole file, frontmatter
136
- included), hash (its sha256, 64 hex), title (the computed
137
- title: the first H1; data.title is the authored key), tags
138
- (authored tags ∪ body #hashtags, deduped, authored first;
139
- data.tags is the authored list), format (the doc's format —
140
- "markdown"/"yaml"/"json"; present on EVERY doc, so
141
- has(format) is always true and \`format == "yaml"\` is the
142
- filter).
143
- - relations = nodes, blocks, doc.out_links / doc.in_links (a doc's
144
- links as rows; the documents at their other end are
145
- out_links collect distinct { from dst } and
146
- in_links collect distinct { from doc } — there is no
147
- out / in relation), revisions (the document's stored
154
+ expose the parsed object's keys the same way. Nothing is
155
+ reserved: \`title\`, \`tags\`, \`path\`, \`id\` are your keys.
156
+ - source handles = _data.<k> (the merged authored view — the same read as the
157
+ bare key) / _frontmatter.<k> / _inline.<k>; the whole bag
158
+ as a collection via entries(_data) / entries(_frontmatter)
159
+ / entries(_inline) (see entries()).
160
+ - attributes = _id, _path (/-rooted, see Paths), _updated_at (ISO-8601,
161
+ sorts chronologically), _created_at, _raw (the whole file,
162
+ frontmatter included), _hash (its sha256, 64 hex), _title
163
+ (the computed title: the first H1; \`title\` is the authored
164
+ key), _tags (authored tags ∪ body #hashtags, deduped,
165
+ authored first; \`tags\` is the authored list), _format (the
166
+ doc's format — "markdown"/"yaml"/"json"; present on EVERY
167
+ doc, so has(_format) is always true and \`_format == "yaml"\`
168
+ is the filter).
169
+ - relations = _nodes, _blocks, _out_links / _in_links (a doc's links as
170
+ rows; the documents at their other end are
171
+ _out_links distinct { from _dst } and
172
+ _in_links distinct { from _doc } — there is no
173
+ out / in relation), _revisions (the document's stored
148
174
  revisions, oldest first — see revisions below).
149
175
 
150
176
  blocks:
151
- - bare fields = doc_id (the owning doc), type, text (normalized), ordinal
152
- (0-based among siblings), depth (0 at the top level), and
153
- every attrs key FLATTENED onto the row (bare \`checked\`
154
- reads attrs.checked; \`attrs.<k>\` still works; the
155
- structural fields win on collision; absent ⇒ false).
156
- - attributes = id, path (the owning doc's path), raw (the block's raw
157
- source — a container's raw holds its children), hash
177
+ - bare fields = every attrs key FLATTENED onto the row (bare \`checked\`;
178
+ \`_attrs.<k>\` is the same read; absent ⇒ false) — a block's
179
+ authored side.
180
+ - attributes = _id, _path (the owning doc's path), _doc_id (the owning
181
+ doc), _type, _text (normalized), _ordinal (0-based among
182
+ siblings), _depth (0 at the top level), _raw (the block's
183
+ raw source — a container's raw holds its children), _hash
158
184
  (its sha256 — the value update/split expect as
159
- expect.content_hash), updated_at.
160
- - reach-through = doc.<key> / doc.path / doc.id / doc.format = the
161
- CONTAINING doc (doc.id and doc.path cost nothing — they
162
- read the row's own doc_id / joined path, no lookup).
163
- - relations = block.children, block.nodes, block.out_links, section.
185
+ expect.content_hash), _updated_at, _created_at, _attrs.
186
+ - reach-through = _doc.<key> / _doc._path / _doc._id / _doc._format = the
187
+ CONTAINING doc (_doc._id and _doc._path cost nothing — they
188
+ read the row's own _doc_id / joined path, no lookup).
189
+ - relations = _children, _nodes, _out_links, _section (no self alias:
190
+ the 4.x \`block.children\` is \`_children\`).
164
191
  NOTE: inside a \`follow\`, $depth/$ordinal are ALWAYS the walk's metadata; a
165
- block's own nesting is \`depth\` / \`ordinal\` (no $) everywhere.
192
+ block's own nesting is \`_depth\` / \`_ordinal\` everywhere.
166
193
 
167
194
  nodes:
168
- - bare fields = doc_id, block_id (null for a block-less node), kind
169
- (md:task / md:link / md:section / yaml:…), name, value,
170
- + attrs keys flattened (checked, level).
171
- - attributes = id, path.
172
- - reach-through = doc.<key> / doc.id / doc.path, block.type / block.text
173
- / block.id / block.path.
174
- - relations = section.blocks, section.children, section.subsections.
195
+ - bare fields = the attrs keys flattened onto the row (checked, level,
196
+ first_ordinal; \`_attrs.<k>\` is the same read).
197
+ - attributes = _id, _path, _doc_id, _block_id (null for a block-less
198
+ node), _kind (md:task / md:link / md:section / yaml:…),
199
+ _name, _value, _attrs.
200
+ - reach-through = _doc.<key> / _doc._id / _doc._path, _block._type /
201
+ _block._text / _block._id / _block._path.
202
+ - relations = _blocks, _children, _subsections (a section node's; the
203
+ 4.x \`section.blocks\` is \`_blocks\`).
175
204
 
176
205
  links: (the authored link graph as rows — one per open link: the relation a link
177
- establishes; the md:link NODE is its occurrence in the text and via_id
206
+ establishes; the md:link NODE is its occurrence in the text and _via_id
178
207
  points at it — a frontmatter link has a row and no node)
179
- - bare fields = doc_id (the SOURCE doc), block_id (the source block; null
180
- for a frontmatter edge), via_id, dst_id (the destination
181
- node id: a doc id, an external node id or phantom:<path>),
182
- dst_path (target doc path, /-rooted; NULL when
183
- dangling/external), uri (external URL; NULL otherwise),
184
- predicate ("references"/"embeds"/freeform), provenance
185
- ("link"/"frontmatter"/"inline_field"/…), dst_kind
186
- ("document"/"external"/"collection"), anchor, field (the
208
+ - attributes = _id, _path (the SOURCE doc's path), _doc_id (the SOURCE
209
+ doc), _block_id (the source block; null for a frontmatter
210
+ edge), _via_id, _dst_id (the destination node id: a doc id,
211
+ an external node id or phantom:<path>), _dst_path (target
212
+ doc path, /-rooted; NULL when dangling/external), _uri
213
+ (external URL; NULL otherwise), _predicate
214
+ ("references"/"embeds"/freeform), _provenance
215
+ ("link"/"frontmatter"/"inline_field"/…), _dst_kind
216
+ ("document"/"external"/"collection"), _anchor, _field (the
187
217
  frontmatter key / inline field it was authored in).
188
- - attributes = id, path (the SOURCE doc's path); doc.<key> reaches the
189
- source document. There is no from_commit: a query sees
190
- open edges only (history is blocks_history/changes_since).
218
+ - reach-through = _doc.<key> (the source document), _dst.<key> (the
219
+ destination document; absent when dangling/external),
220
+ _block.<key> (the source block). Closed: links carry no
221
+ authored data, so a bare name is filter_invalid. There is
222
+ no from_commit: a query sees open edges only (history is
223
+ blocks_history/changes_since).
191
224
  - text()/semantic() are N/A on links.
192
225
 
193
226
  revisions: (history as rows — one per stored revision of every document of the
194
227
  repository, deleted documents included: what docs_history lists, queryable)
195
- - bare fields = rev (= id), seq (1, 2, … per document), doc_id, commit,
196
- ts (the commit's time, sorts chronologically), origin
197
- ("observed"/"api"/"import"/"projection"), actor, reason,
198
- hash (the rendered file's sha256 — docs_history's hash),
199
- is_current (true for the document's current revision),
200
- archived (true when the bodies moved to an archive segment:
201
- the row stays, docs_read { at } reads through the segment).
202
- - attributes = id (the revision id, r_…), path (the path the revision
203
- was committed under — a move re-paths only the current
204
- revision, so earlier ones keep the old path). doc.<key>
205
- reaches the document's CURRENT row; absent for a deleted
206
- document, so where doc == null is the revisions of deleted documents.
207
- - on docs = revisions is a relation (oldest first): a document with
208
- more than one version is docs { revisions count { } > 1 }.
228
+ - attributes = _id (the revision id, r_…), _rev (= _id), _seq (1, 2, … per
229
+ document), _doc_id, _commit, _ts (the commit's time, sorts
230
+ chronologically), _origin ("observed"/"api"/"import"/
231
+ "projection"), _actor, _reason, _hash (the rendered file's
232
+ sha256 — docs_history's _hash), _is_current (true for the
233
+ document's current revision), _archived (true when the
234
+ bodies moved to an archive segment: the row stays,
235
+ docs_read { at } reads through the segment), _path (the
236
+ path the revision was committed under — a move re-paths
237
+ only the current revision, so earlier ones keep the old
238
+ path).
239
+ - reach-through = _doc.<key> reaches the document's CURRENT row; absent for
240
+ a deleted document, so where _doc == null is the revisions
241
+ of deleted documents. Closed: a bare name is filter_invalid.
242
+ - on docs = _revisions is a relation (oldest first): a document with
243
+ more than one version is docs { _revisions count > 1 }.
209
244
  - text()/semantic() are N/A on revisions. The body as of a revision is
210
- docs_read { path, at: "<rev>" } (docs_read_at is the same call).
245
+ docs_read { _path, at: "<rev>" } (docs_read_at is the same call).
211
246
 
212
247
  ## Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL
213
248
 
214
249
  name the CURRENT row only — never falls through to an enclosing row
215
250
  or the repository; an absent field is simply absent.
216
251
  ^name one scope OUT (the enclosing row's fields/intrinsics/lifts);
217
- ^^name two scopes out. \`^path\` = the enclosing row's path.
252
+ ^^name two scopes out. \`^_path\` = the enclosing row's path.
218
253
  ^docs ^nodes ^blocks ^links ^revisions
219
254
  THE ROOT ROW (surface 2.0, oqx 0.18): the repository is the root
220
255
  scope's row, one scope out from a top-level row, so ^docs there is
221
256
  the whole documents scan (uncorrelated until you add a ^ predicate);
222
257
  one caret per enclosing block (^^docs from depth two), or the
223
- absolute 0^docs from ANY depth. ^id / 0^id = the repository id.
258
+ absolute 0^docs from ANY depth. ^_id / 0^_id = the repository id.
224
259
  ^$it = the root object: ^$it.docs ≡ ^docs, entries(^$it) names the
225
260
  five collections. At the root scope itself a bare target is the scan
226
- (docs count { … }, from docs, entries(docs)).
261
+ (docs { … }, docs count { … }, entries(docs)).
227
262
  ERRORS: a bare \`docs\` INSIDE a block reads a property of the current
228
- row, which has none — refused ("did you mean ^docs"); \`^docs\` AT THE
229
- TOP LEVEL reaches past the root — refused (write the bare \`docs\`);
230
- so is \`$repo\` (surface < 2.0; the message names the replacement).
231
- nodes/blocks on a doc row are that doc's RELATIONS, not the root.
263
+ row, which has none — refused ("did you mean ^docs"); so is a bare
264
+ \`blocks\`/\`nodes\`/\`revisions\` there (a doc's own relations are
265
+ \`_blocks\`/\`_nodes\`/\`_revisions\`); \`^docs\` AT THE TOP LEVEL reaches
266
+ past the root — refused (write the bare \`docs\`); so is \`$repo\`
267
+ (surface < 2.0; the message names the replacement).
232
268
  $it the current item itself (a row, or each ELEMENT when the
233
- receiver is a list property); ^$it = the enclosing row.
234
- $value is NOT an intrinsic (since oqx 0.15): it reads a
269
+ receiver is a list property); ^$it = the enclosing row, ^$it._id
270
+ its id. $value is NOT an intrinsic (since oqx 0.15): it reads a
235
271
  property literally named $value, absent when there is none.
236
272
  $key the row's identity (0.20): a store row's id (doc/block/node/edge), an entries(x)
237
273
  entry's key, a follow occurrence's walk identity; project $key: <expr> to rekey the
@@ -258,7 +294,7 @@ revisions: (history as rows — one per stored revision of every document of th
258
294
  range-shaped STRING property as an interval: "2026-01-15" in range(window).
259
295
  strings x.contains("s") x.startsWith("p") x.endsWith("s") x.matches("^re$")
260
296
  (real regex) x.lower() x.upper() size(x). ALL CASE-SENSITIVE —
261
- fold with .lower(): title.lower().contains("aurora").
297
+ fold with .lower(): _title.lower().contains("aurora").
262
298
  Infix regex: x ~ "re" is x.matches("re"); x ~* "re" is the
263
299
  case-insensitive match; x !~ "re" and x !~* "re" negate them.
264
300
  Unanchored unless the pattern anchors; an absent x is false
@@ -277,7 +313,7 @@ revisions: (history as rows — one per stored revision of every document of th
277
313
  n.dur_iso() → "PT1H30M". Total: a non-date is absent, so
278
314
  \`where date(due) < date("today").minus(dur("30 days"))\` just
279
315
  filters. A datetime is never < its own date: compare
280
- updated_at.date() == date("today"), or < date("tomorrow").
316
+ _updated_at.date() == date("today"), or < date("tomorrow").
281
317
  literals "str" 'str' 123 1.5 true false null
282
318
  determinism no random; the only clock is date("today"/"now"/…), read once per
283
319
  query. Lists/structs are not literals; ternary is not supported.
@@ -312,8 +348,8 @@ revisions: (history as rows — one per stored revision of every document of th
312
348
  a source, a receiver or a follow destination:
313
349
  follow refs(before), refs(after) walks a timeline both ways.
314
350
  The reverse direction needs no function: a document's
315
- path is already the reference form, so
316
- ^docs { where ^path in list(after) } is
351
+ _path is already the reference form, so
352
+ ^docs { ^_path in list(after) } is
317
353
  "the documents whose \`after\` names me".
318
354
  A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails
319
355
  (filter_invalid). \`follow before\` over a list of paths
@@ -322,58 +358,64 @@ revisions: (history as rows — one per stored revision of every document of th
322
358
  ## Nested queries & consumers
323
359
 
324
360
  <receiver> collect|exists|none|count|first|single { <block> }
325
- receiver = a relation (nodes, blocks, doc.out_links, section.blocks, …), a
326
- reach-through row (dst, doc: one row — dst { title }), a
361
+ The block may be omitted after a consumer word (OQX 0.26): the omitted block is
362
+ the empty block — \`_nodes exists\`, \`_revisions count > 1\`, \`_blocks first\`,
363
+ \`docs count\` as a whole query; \`x { }\` keeps its braces (the brace is what
364
+ makes a collect). A projection block over a lookup is the lookup's projection:
365
+ \`_blocks[0] { _text }\` ≡ \`_blocks first { _text offset 0 }\`.
366
+ receiver = a relation (_nodes, _blocks, _doc._out_links, _section._blocks, …), a
367
+ reach-through row (_dst, _doc: one row — _dst { _title }), a
327
368
  list property (tags), entries(x), or a root collection (^docs; 0^docs at depth ≥ 2).
328
369
  block = [select …] [from E] [where …] [frontier …] [follow …] [flatten …] [group by …] [having …] [order by …] [limit N] [offset N]
329
- — the same fixed order as the top level (\`from\` optional; the
330
- receiver supplies the rows). A leading name/alias list is the
331
- projection (\`select\` dropped); a predicate always needs \`where\`.
332
- A \`from E\` inside a block RE-PROJECTS the rows through a relation
333
- (a typed flatMap): \`docs collect { from nodes where kind == "md:task" }\`
334
- starts at docs, projects each through \`nodes\`, and queries the
335
- resulting nodes; a scalar relation (\`from doc\`) contributes one
336
- row; a missing or non-navigable relation is a loud error, never a
337
- global scan.
338
- exists ≥1 row nodes exists { where kind == "md:task" and not checked }
339
- none 0 rows nodes none { where kind == "md:task" and not checked } ("every task done")
340
- count a number nodes count { where kind == "md:task" } >= 2
370
+ — the same fixed order as the whole query, whose body is a block
371
+ too (the target is its receiver). A leading name/alias list is the
372
+ projection (\`select\` dropped); a predicate needs \`where\` unless
373
+ it LEADS the block (Sugar). A \`from E\` inside a block RE-PROJECTS
374
+ the rows through a relation (a typed flatMap):
375
+ \`docs { from _nodes where _kind == "md:task" }\` starts at docs,
376
+ projects each through \`_nodes\`, and queries the resulting nodes;
377
+ a scalar relation (\`from _doc\`) contributes one row; a missing or
378
+ non-navigable relation is a loud error, never a global scan. \`from\`
379
+ never names the source — the collection before the body does.
380
+ exists ≥1 row _nodes exists { _kind == "md:task" and not checked }
381
+ none 0 rows _nodes none { _kind == "md:task" and not checked } ("every task done")
382
+ count a number _nodes count { _kind == "md:task" } >= 2
341
383
  collect rows (default) — in select for nested results; in where with a lift
342
384
  first / single zero-or-one (single errors on >1) — lookups in select
343
- distinct on any consumer: dedup by projected value — nodes count distinct { select kind }
385
+ distinct on any consumer: dedup by projected value — _nodes count distinct { select _kind }
344
386
  limit/offset bound the block's rows AFTER where/order/distinct, BEFORE the
345
- consumer reduces: nodes exists { offset 1 } = at least two;
387
+ consumer reduces: _nodes exists { offset 1 } = at least two;
346
388
  first { … offset 1 } = the second.
347
389
  lift \`^name:\` in a where-collect binds values outward:
348
- select path, open from docs where nodes collect { ^open: value where kind == "md:task" and not checked }
349
- joins ^<t> exists { where slug == ^ref } semi-join; not … exists anti-join;
350
- ^nodes single { where kind == "person" and attrs.id == ^owner_id } lookup;
390
+ docs { select _path, open where _nodes { ^open: _value where _kind == "md:task" and not checked } }
391
+ joins ^<t> exists { slug == ^ref } semi-join; not … exists anti-join;
392
+ ^nodes single { _kind == "person" and _attrs.id == ^owner_id } lookup;
351
393
  x in ^keys membership over a lifted set.
352
394
 
353
395
  ## select — projection
354
396
 
355
- select a, alias: expr, nested: nodes collect { … }
356
- bare key → the attribute when it is one (id, path, title, …), else the property
357
- (scalar-authored → scalar; list → array); data.<k> forces the property;
397
+ select a, alias: expr, nested: _nodes collect { … }
398
+ bare key → your property (scalar-authored → scalar; list → array); _name →
399
+ the engine's field (_id, _path, _title, …); _data.<k> is the bare key's read;
358
400
  a call/arithmetic needs an alias (n: size(tags)) unless in values mode;
359
- an output key of id/path other than the attribute itself needs an alias.
360
- raw whole file bytes (docs) — docs_read is cheaper for one doc.
361
- distinct select distinct type (dedup hits by projected value)
401
+ an output key of _id/_path other than the attribute itself needs an alias.
402
+ _raw whole file bytes (docs) — docs_read is cheaper for one doc.
403
+ distinct select distinct _type (dedup hits by projected value)
362
404
  values select <ONE item> values → bare values instead of records:
363
- select distinct type values from docs → values: ["hub","lab-note",…]
364
- select path values from docs → values: ["/index.md", …]
365
- tags: tags collect { $it values } → a plain array
366
- h: nodes first { name values where kind == "md:section" order by first_ordinal }
405
+ docs { select distinct type values } → values: ["hub","lab-note",…]
406
+ docs { select _path values } → values: ["/index.md", …]
407
+ tags: tags { $it values } → a plain array
408
+ h: _nodes first { _name values where _kind == "md:section" order by first_ordinal }
367
409
  entries(x) a record as a collection ($key / $it per entry), key order:
368
- select fm: entries(data) collect { k: $key, v: $it }
369
- where entries(frontmatter) exists { where $key == "era" and $it > 1600 }
370
- entries(inline), entries(attrs), a nested map, or a list (index keys)
410
+ select fm: entries(_data) { k: $key, v: $it }
411
+ where entries(_frontmatter) exists { $key == "era" and $it > 1600 }
412
+ entries(_inline), entries(_attrs), a nested map, or a list (index keys)
371
413
  $semantic_score is not a field — project semantic("…") under an alias instead.
372
414
 
373
415
  ## flatten (OQX 0.25)
374
416
 
375
417
  flatten <expr> [{ … }] [as <name>] after follow, before group by; may repeat
376
- Splits one value into rows. Every hit is still the PARENT row — its id/path
418
+ Splits one value into rows. Every hit is still the PARENT row — its _id/_path
377
419
  repeat per element, as Dataview's TABLE … FLATTEN — with <name> bound beside
378
420
  its fields at the same depth: an array → one row per element (an empty list
379
421
  yields none), a missing field → one row with the name absent (never dropped),
@@ -383,44 +425,44 @@ revisions: (history as rows — one per stored revision of every document of th
383
425
  projection, order by, limit, distinct), applied first. Later clauses read the
384
426
  name — a later flatten (flatten jobs as j flatten j.tags as t), group by,
385
427
  having, select, order by; where runs before it. A flattened query pages by
386
- position (cursor null); as id / as path is refused (the envelope reads them).
387
- select path, tags from docs where type == "process" flatten tags order by path, tags
388
- select path, t: b.text from docs flatten blocks { where type == "task" and not checked } as b
389
- select tags, n: count() from docs flatten tags group by tags order by count() desc
428
+ position (cursor null); as _id / as _path is refused (the envelope reads them).
429
+ docs { select _path, tags where type == "process" flatten tags order by _path, tags }
430
+ docs { select _path, t: b._text flatten _blocks { _type == "task" and not checked } as b }
431
+ docs { select tags, n: count() flatten tags group by tags order by count() desc }
390
432
 
391
433
  ## order by, limit/offset, pagination
392
434
 
393
- order by era desc, path asc absent values sort LAST (asc); ties → (path, id)
435
+ order by era desc, _path asc absent values sort LAST (asc); ties → (_path, _id)
394
436
  limit N / offset N define the result SET (integer literals);
395
437
  the tool's limit/cursor then page WITHIN it
396
- cursor opaque keyset on (path,id); a custom order by
438
+ cursor opaque keyset on (_path, _id); a custom order by
397
439
  disables it (you get the top page + truncated)
398
440
 
399
441
  ## Graph traversal (follow) and links
400
442
 
401
443
  The citation graph is the LINK ROWS walked through a destination block:
402
- dst is a link's destination document (absent for an external or dangling
403
- link — it then contributes no successor), doc its source.
404
- from docs where <seed> follow out_links collect { from dst } docs the seed links TO
405
- from docs where <seed> follow in_links collect { from doc } backlinks
406
- follow out_links collect { from dst depth 3 } bound (1..8, default 8) — a clause of the block
407
- follow out_links collect { from dst where layer == "canon" } keep only matching successors
408
- follow in_links collect { from doc where ^path in list(before) } ^ = the row being expanded
444
+ _dst is a link's destination document (absent for an external or dangling
445
+ link — it then contributes no successor), _doc its source.
446
+ docs { where <seed> follow _out_links { from _dst } } docs the seed links TO
447
+ docs { where <seed> follow _in_links { from _doc } } backlinks
448
+ follow _out_links { from _dst depth 3 } bound (1..8, default 8) — a clause of the block
449
+ follow _out_links { from _dst where layer == "canon" } keep only matching successors
450
+ follow _in_links { from _doc where ^_path in list(before) } ^ = the row being expanded
409
451
  (^^ = the walk's enclosing scope)
410
- follow out_links collect { from dst where predicate == "depends_on" } one predicate only
411
- follow out_links collect { from dst }, in_links collect { from doc }
452
+ follow _out_links { from _dst where _predicate == "depends_on" } one predicate only
453
+ follow _out_links { from _dst }, _in_links { from _doc }
412
454
  several destinations: their union
413
455
  (one step dedups by identity)
414
- follow ^docs { where out_links exists { where dst_path == ^^path } }
456
+ follow ^docs { _out_links exists { _dst_path == ^^_path } }
415
457
  a destination block over the root's
416
458
  documents, re-read per frontier row
417
459
  (^ = that row) = the backlinks walk
418
- follow out_links collect { from dst frontier type == "practitioner" } cut, keeping the frontier row
419
- from docs where type == "process" frontier !verified follow out_links collect { from dst }
460
+ follow _out_links { from _dst frontier type == "practitioner" } cut, keeping the frontier row
461
+ docs { type == "process" frontier !verified follow _out_links { from _dst } }
420
462
  the BODY's frontier applies to the seeds:
421
463
  an unverified seed is kept, not expanded
422
- follow distinct out_links collect { from dst } / … { $key: group from dst } identity control ($key)
423
- follow refs(before), ^docs { where ^path in list(after) } forward over \`before\`, backward
464
+ follow distinct _out_links { from _dst } / … { $key: group from _dst } identity control ($key)
465
+ follow refs(before), ^docs { ^_path in list(after) } forward over \`before\`, backward
424
466
  over every doc whose \`after\` names me
425
467
  Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|
426
468
  depth|cycle), $leaf/$frontier, $ordinal (a deterministic 1..N rank over the
@@ -428,14 +470,15 @@ revisions: (history as rows — one per stored revision of every document of th
428
470
  \`where $ordinal <= N\` for a deterministic budget cut), $key — usable in
429
471
  select/order by and the top-level post-walk where (a where conjunct that
430
472
  reads one filters the walk's RESULT; the other conjuncts remain the seed
431
- predicate), NOT metadata of the candidates a destination block's where reads,
432
- nor valid inside a consumer directive. Cyclic graphs are safe: a revisited
473
+ predicate), NOT metadata of the candidates a destination block's where reads;
474
+ a consumer directive may carry a follow of its own (its block yields the
475
+ walk: \`_children count { follow _children } > 5\`). Cyclic graphs are safe: a revisited
433
476
  node is admitted once as a \`$stop == "cycle"\` occurrence and never
434
477
  re-expanded. The default keeps one occurrence per distinct walk path;
435
478
  \`follow distinct\` dedups reached rows by identity.
436
479
  Destination blocks (OQX 0.20): a brace after ANY destination opens that
437
- destination's block — children { depth 2 }, refs(x) { depth 2 }, out_links
438
- collect { from dst depth 2 }, ^docs { where ^path in list(after) } — with the
480
+ destination's block — _children { depth 2 }, refs(x) { depth 2 }, _out_links
481
+ { from _dst depth 2 }, ^docs { ^_path in list(after) } — with the
439
482
  0.17 body rules (where-first, projections) and two clauses of its own after
440
483
  \`where\`: frontier <pred> (occurrences reached through this destination that
441
484
  match are frontiers; ^ there is the walk's enclosing scope) and depth <n>
@@ -446,55 +489,155 @@ revisions: (history as rows — one per stored revision of every document of th
446
489
  expanded; each destination's frontier is its own. There is no options block;
447
490
  the old \`by\` is $key. A bare follow ^docs (no brace, no consumer) is an
448
491
  error naming both block spellings.
449
- Type-preserving relations: block.children, section.children, section.subsections;
450
- the citation graph is out_links collect { from dst } / in_links collect { from doc }.
451
- from links where dst_path == "/notes/x.md" inbound links to a doc, as rows
452
- select d: dst.title, k: dst_kind from links the destination document's title
453
- from docs where out_links exists { where dst.layer == "draft" } links into drafts
454
- select b: in_links collect distinct { from doc } from docs backlinks as { id, path }
455
- select doc_id, dst_path from links where predicate == "depends_on"
456
- from docs where not doc.in_links exists { } orphans (nothing links in)
492
+ Type-preserving relations: a block's _children; a section node's _children and
493
+ _subsections; the citation graph is _out_links { from _dst } / _in_links { from _doc }.
494
+ links { _dst_path == "/notes/x.md" } inbound links to a doc, as rows
495
+ links { select d: _dst._title, k: _dst_kind } the destination document's title
496
+ docs { _out_links exists { _dst.layer == "draft" } } links into drafts
497
+ docs { select b: _in_links distinct { from _doc } } backlinks as { _id, _path }
498
+ links { select _doc_id, _dst_path where _predicate == "depends_on" }
499
+ docs { not _in_links exists } orphans (nothing links in)
457
500
  (The \`graph\` tool is a convenience wrapper that compiles to a follow query.)
458
501
 
459
502
  ## semantic grain
460
503
 
461
- from docs ranks whole DOCUMENTS (one hit per file); from blocks ranks PASSAGES.
504
+ docs { … } ranks whole DOCUMENTS (one hit per file); blocks { … } ranks PASSAGES.
462
505
  text()/where prune the candidate set; only order by reweights.
463
506
 
464
507
  ## Examples
465
508
 
466
- from docs where layer == "working"
467
- from docs where path.startsWith("/guides/") and updated_at >= "2026-08-01"
468
- select layer, tags from docs where "pricing" in list(tags)
469
- from docs where inline.owner == "alice"
470
- from docs where title.lower().contains("q3 plan")
471
- from docs where format == "yaml"
472
- from docs where type == "practitioner" order by era desc limit 2
473
- from docs where nodes none { where kind == "md:task" and not checked }
474
- from docs where tags exists { where $it == "tria-prima" }
475
- select fm: entries(data) collect { k: $key, v: $it } from docs where path == "/x.md"
476
- select owner_id, owner: ^nodes single { where kind == "person" and attrs.id == ^owner_id } from docs
477
- from blocks where type == "task" and not checked and under_heading("Launch") and doc.layer == "working"
478
- from blocks where type == "paragraph" and has_edge("references", "d_92aaaaa")
479
- select path, $depth from docs where path == "/timeline/review.md" follow refs(before), refs(after)
480
- select path, next: ^docs collect { path where ^path in list(before) } from docs where type == "milestone"
481
- select path, n: size(^docs), repo: ^id from docs limit 1
482
- select p: data.path, t: data.title from docs where has(data.path)
509
+ docs { layer == "working" }
510
+ docs { _path.startsWith("/guides/") and _updated_at >= "2026-08-01" }
511
+ docs { select layer, tags where "pricing" in list(tags) }
512
+ docs { _inline.owner == "alice" }
513
+ docs { _title.lower().contains("q3 plan") }
514
+ docs { _format == "yaml" }
515
+ docs { type == "practitioner" order by era desc limit 2 }
516
+ docs { _nodes none { _kind == "md:task" and not checked } }
517
+ docs { tags exists { $it == "tria-prima" } }
518
+ docs { select fm: entries(_data) { k: $key, v: $it } where _path == "/x.md" }
519
+ docs { select owner_id, owner: ^nodes single { _kind == "person" and _attrs.id == ^owner_id } }
520
+ blocks { _type == "task" and not checked and under_heading("Launch") and _doc.layer == "working" }
521
+ blocks { _type == "paragraph" and has_edge("references", "d_92aaaaa") }
522
+ docs { select _path, $depth where _path == "/timeline/review.md" follow refs(before), refs(after) }
523
+ docs { select _path, next: ^docs { _path where ^_path in list(before) } where type == "milestone" }
524
+ docs { select _path, n: size(^docs), repo: ^_id limit 1 }
525
+ docs { select path, title where has(path) }
483
526
  docs { type == "substance" and not verified }
484
- select subject, process: ^docs[slug == ^subject]!.path from docs where type == "lab-note"
485
- select path, back: nodes first { select p: 0^docs collect { path where ^^path in list(after) } values } from docs where type == "milestone"
486
- select path, prior: refs(before) collect { path, when } from docs where type == "milestone"
487
- from blocks order by semantic("identity preservation across edits") desc limit 10
488
- select uri from links where dst_kind == "external"
489
- docs count { where layer == "canon" }
490
- select seq, origin, ts, hash from revisions where path == "/processes/calcination.md" order by seq desc
491
- from docs where revisions count { where origin == "api" } > 0
492
- select p: path, d: doc.path from revisions where doc == null
493
- select layer, n: count(), titles: collect(title), open: rows count { where nodes exists { where kind == "md:task" and not checked } } from docs group by layer having count() > 1 order by count() desc
494
- select path, tag: tags from docs where "process" in tags flatten tags order by path, tags
495
-
496
- Common mistake: \`$path\`/\`$id\` (the 2.x spelling) — omgbase has no $ names; write
497
- \`path\`/\`id\`. Another: \`where tags == "x"\` on a list-valued key is false — write
498
- \`"x" in tags\`. Another: \`select data.path\` — alias it (\`p: data.path\`).
527
+ docs { select subject, process: ^docs[slug == ^subject]!._path where type == "lab-note" }
528
+ docs { select _path, back: _nodes first { select p: 0^docs { _path where ^^_path in list(after) } values } where type == "milestone" }
529
+ docs { select _path, prior: refs(before) { _path, when } where type == "milestone" }
530
+ blocks { order by semantic("identity preservation across edits") desc limit 10 }
531
+ links { select _uri where _dst_kind == "external" }
532
+ docs count { layer == "canon" }
533
+ revisions { select _seq, _origin, _ts, _hash where _path == "/processes/calcination.md" order by _seq desc }
534
+ docs { _revisions count { _origin == "api" } > 0 }
535
+ revisions { select p: _path, d: _doc._path where _doc == null }
536
+ docs { select layer, n: count(), titles: collect(_title), open: rows count { _nodes exists { _kind == "md:task" and not checked } } group by layer having count() > 1 order by count() desc }
537
+ docs { select _path, tag: tags where "process" in tags flatten tags order by _path, tags }
538
+
539
+ Common mistake: \`$path\`/\`$id\` — omgbase has no $ names; the engine's location and
540
+ identity are \`_path\`/\`_id\`. Another: the 4.x bare \`path\`/\`id\`/\`title\`/\`tags\` — a
541
+ bare \`path\` is now YOUR \`path:\` key (absent when you wrote none, with a hint
542
+ pointing at \`_path\`); the engine's are \`_path\`/\`_id\`/\`_title\`/\`_tags\`; \`select
543
+ data.path\` is gone — write \`path\`. Another: \`where tags == "x"\` on a list-valued
544
+ key is false — write \`"x" in tags\`. Another: \`select _path: _doc._path\` — alias it
545
+ (\`p: _doc._path\`).
546
+ `;
547
+ /**
548
+ * The manual's chapters (spec/surface §4 `query_syntax`, 5.1): one entry per
549
+ * `## ` heading of `QUERY_SYNTAX`, in order — the `section` argument's names.
550
+ * `querySyntaxSection(slug)` is the text from that heading up to the next one.
551
+ * The Rust twin pins the same table (its test checks every pair against this
552
+ * source).
553
+ */
554
+ export const QUERY_SYNTAX_SECTIONS = [
555
+ { slug: "sugar", heading: "Sugar (OQX 0.17)" },
556
+ { slug: "paths", heading: "Paths" },
557
+ { slug: "targets", heading: "Targets & field namespaces" },
558
+ { slug: "scoping", heading: "Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL" },
559
+ { slug: "scalars", heading: "Scalar semantics (@omgbase/oqx)" },
560
+ { slug: "predicates", heading: "Domain predicates (omgbase)" },
561
+ { slug: "nested", heading: "Nested queries & consumers" },
562
+ { slug: "select", heading: "select — projection" },
563
+ { slug: "flatten", heading: "flatten (OQX 0.25)" },
564
+ { slug: "order", heading: "order by, limit/offset, pagination" },
565
+ { slug: "follow", heading: "Graph traversal (follow) and links" },
566
+ { slug: "semantic", heading: "semantic grain" },
567
+ { slug: "examples", heading: "Examples" },
568
+ ];
569
+ /** The `section` names a caller may pass: every slug, then `all` (the whole manual). */
570
+ export const QUERY_SYNTAX_SECTION_NAMES = [...QUERY_SYNTAX_SECTIONS.map((s) => s.slug), "all"];
571
+ /**
572
+ * One chapter of the manual: the lines from `## <heading>` up to (not
573
+ * including) the next `## ` heading or the end, trailing blank lines dropped,
574
+ * one trailing newline. `"all"` is the whole manual; an unknown slug is `null`.
575
+ */
576
+ export function querySyntaxSection(slug) {
577
+ if (slug === "all")
578
+ return QUERY_SYNTAX;
579
+ const entry = QUERY_SYNTAX_SECTIONS.find((s) => s.slug === slug);
580
+ if (!entry)
581
+ return null;
582
+ const lines = QUERY_SYNTAX.split("\n");
583
+ const start = lines.indexOf(`## ${entry.heading}`);
584
+ if (start < 0)
585
+ return null;
586
+ let end = start + 1;
587
+ while (end < lines.length && !lines[end].startsWith("## "))
588
+ end++;
589
+ while (end > start + 1 && lines[end - 1].trim() === "")
590
+ end--;
591
+ return lines.slice(start, end).join("\n") + "\n";
592
+ }
593
+ /**
594
+ * The card `query_syntax {}` returns (5.1): the grammar skeleton, the names
595
+ * rule and the engine names per kind, the handles, the consumers, follow in
596
+ * one line, the examples agents reach for first, and where the chapters are.
597
+ * Every indented example parses (reference.test.ts extracts them exactly as it
598
+ * extracts the manual's). Keep every line earning its place: this is the most
599
+ * read text on the surface.
600
+ */
601
+ export const QUERY_SYNTAX_CARD = `# query — OQX card. Chapters: query_syntax { section: "<name>" }; the manual: { section: "all" }
602
+
603
+ One OQX string; limit/cursor page the hits; bare: true drops the { _id, _path } envelope.
604
+ <target> { [select <items>] [where <pred>] [follow <dest> { … }] [order by <expr> [desc]] [limit N] [offset N] }
605
+ <target> count|exists|none|first|single [{ <block> }] a scalar or one row; no block = the empty block (docs count)
606
+ The collection (docs blocks nodes links revisions) heads the body; nothing follows its closing brace.
607
+ CLAUSE ORDER IS FIXED: select, from, where, frontier, follow, flatten, group by, having, order by, limit, offset.
608
+ and / or / not. Comparisons strict and typed; null == a missing field. A list key: "x" in tags, never tags == "x".
609
+ Strings .contains("s") .startsWith("p") .lower(); x ~* "re" (ci regex), x !~ "re". Dates date("today"),
610
+ date(due) < date("today").minus(dur("30 days")), _updated_at >= "2026-08-01".
611
+
612
+ NAMES: a BARE name is YOURS (frontmatter, inline key:: value, a block's attrs); an UNDERSCORE name is the ENGINE's;
613
+ $ is OQX's ($it $key $depth $stop $ordinal). title tags path id are your keys; _title _tags _path _id the engine's.
614
+ docs _id _path _raw _hash _updated_at _created_at _title _tags _format | _data _frontmatter _inline | _blocks _nodes _out_links _in_links _revisions
615
+ blocks _id _path _doc_id _raw _hash _type _text _attrs _depth _ordinal | _doc | _children _nodes _out_links _section
616
+ nodes _id _path _doc_id _block_id _kind _name _value _attrs | _doc _block | _blocks _subsections _children
617
+ links _id _path _doc_id _block_id _via_id _dst_id _dst_path _uri _dst_kind _predicate _provenance _anchor _field | _doc _dst _block
618
+ revisions _id _path _rev _seq _doc_id _commit _ts _origin _actor _reason _hash _is_current _archived | _doc
619
+ Handles _data.<k> ≡ <k>, _frontmatter.<k> / _inline.<k>; entries(_data) the bag as rows ($key, $it). Reach-through
620
+ _doc.<k> the owning document, _dst.<k> a link's target. Scoping: a bare name is the CURRENT row; ^name one scope
621
+ out; ^docs ^blocks ^nodes ^links the repository from a row. An unknown bare name is absent plus a hints line.
622
+
623
+ NESTED: <receiver> collect|exists|none|count|first|single { [select …] [where …] [order by …] [limit N] } over a
624
+ relation, a list key, entries(x) or ^docs; no consumer = collect, a leading predicate = where: docs { type == "x" }.
625
+ FOLLOW: docs { where <seed> follow _out_links { from _dst depth 3 } } links out; _in_links { from _doc }
626
+ backlinks; refs(before) a property's documents; $depth per row. SEARCH: text("words") prunes; order by semantic("…") desc.
627
+
628
+ docs { layer == "working" }
629
+ docs { select _title, stage where "process" in tags order by _path limit 5 }
630
+ blocks { select _type, _text where _path == "/processes/calcination.md" }
631
+ nodes { select _path, _value where _kind == "md:task" and not checked }
632
+ links { select _path, _block_id where _dst_path == "/processes/calcination.md" }
633
+ docs { _title.lower().contains("salt") and _updated_at >= "2026-01-01" }
634
+ docs { _nodes exists { _kind == "md:task" and not checked } }
635
+ docs { select _path, $depth where _path == "/timeline/review.md" follow refs(before), refs(after) }
636
+ docs count { layer == "canon" }
637
+ revisions { select _seq, _ts, _hash where _path == "/processes/calcination.md" order by _seq desc }
638
+ docs { select tags, n: count() flatten tags group by tags order by count() desc }
639
+
640
+ Every error is filter_invalid naming what to change; when the engine can correct the call, data.fix IS the call.
641
+ Chapters: sugar paths targets scoping scalars predicates nested select flatten order follow semantic examples; all.
499
642
  `;
500
643
  //# sourceMappingURL=reference.js.map