@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
@@ -28,8 +28,8 @@
28
28
  // KINDS of its two operands are provably compared the same way — see
29
29
  // `comparable` (the spec/surface §1 decline (a)) — and a name that is a relation, reach-through
30
30
  // or WHOLE source handle is never read as a property (decline (b),
31
- // `NON_PROPERTY_NAMES`; a key read THROUGH a handle — `data.k`,
32
- // `frontmatter.k` — is a property read, `handleRead`). A bool or num literal against a JSON or property
31
+ // `NON_PROPERTY_NAMES`; a key read THROUGH a handle — `_data.k`,
32
+ // `_frontmatter.k` — is a property read, `handleRead`). A bool or num literal against a JSON or property
33
33
  // read is a TYPED push (the 1.2 patch): the stored type is tested in SQL
34
34
  // before the value (`json_type(x) = 'true'`, `p.type = 'number' AND
35
35
  // p.val_num >= ?`) and the whole test is wrapped `(…) IS 1` / `IS NOT 1`,
@@ -44,36 +44,36 @@ import { storagePath } from "../../core/paths.js";
44
44
  import { TARGETS } from "../root-row.js";
45
45
  // ---- names that are never property reads ------------------------------------
46
46
  /**
47
- * Per target, the bare identifiers (and `doc.<k>` keys, via the docs set) that
47
+ * Per target, the engine names (and `_doc.<k>` keys, via the docs set) that
48
48
  * `context.ts` resolves to something other than a scalar field or property: the
49
- * self alias, the reach-through handles, the relations, the property source
50
- * handles and (blocks/nodes) the whole `attrs` object. A comparison naming one of
51
- * these is declined (spec/surface §1 decline (b)) — read as a property it would
52
- * be `NULL`, where in memory the value is a row, an array or an object.
49
+ * reach-through handles, the relations, the property source handles and
50
+ * (blocks/nodes) the whole `_attrs` object. A comparison naming one of these is
51
+ * declined (spec/surface §1 decline (b)) — read as a property it would be
52
+ * `NULL`, where in memory the value is a row, an array or an object.
53
53
  * `translate.test.ts` asserts this set against the store context.
54
54
  */
55
55
  export const NON_PROPERTY_NAMES = {
56
- docs: new Set(["doc", "nodes", "blocks", "out_links", "in_links", "revisions", "data", "frontmatter", "inline"]),
57
- blocks: new Set(["block", "doc", "children", "nodes", "out_links", "section", "attrs"]),
58
- nodes: new Set(["section", "doc", "block", "blocks", "subsections", "children", "attrs"]),
59
- links: new Set(["doc", "dst", "block"]),
56
+ docs: new Set(["_nodes", "_blocks", "_out_links", "_in_links", "_revisions", "_data", "_frontmatter", "_inline"]),
57
+ blocks: new Set(["_doc", "_children", "_nodes", "_out_links", "_section", "_attrs"]),
58
+ nodes: new Set(["_doc", "_block", "_blocks", "_subsections", "_children", "_attrs"]),
59
+ links: new Set(["_doc", "_dst", "_block"]),
60
60
  };
61
61
  // ---- engine attributes ------------------------------------------------------
62
- // The engine attributes (spec/surface §1.2, 3.0: bare, snake_case, winning on
63
- // collision — `id`, `path`, `raw`, `hash`, `updated_at`, `created_at` on docs
64
- // and blocks, `title`/`tags` on docs, `id`/`path` on nodes and edges) → a param-free SQL
65
- // scalar, per target, with its kind. The structural columns and foreign keys are
66
- // translated in `translateOperand`. Anything not mapped (`raw`, reconstructed or
67
- // a blob; `title`, computed; `tags`, the union of the authored list and the
68
- // computed hashtags) returns null → residual, and the caller must NOT fall
69
- // through to a property read of the same name (the attribute wins in memory).
70
- // `updated_at` and `created_at` (4.10) are correlated subqueries over the
71
- // document's revisions (`current_rev` / `seq = 1`); a block's are residual, as
72
- // before. `path` (and the edge field `dst_path`)
73
- // is the REFERENCE form on the surface (spec/surface §1 "Paths", 2.0) — `'/' ||
74
- // <storage column>` in SQL — so every comparison, `startsWith`, `contains`,
75
- // `lower()`… sees exactly the string the in-memory attribute yields;
76
- // `pathColumn` keeps the bare indexed column at hand for the equality fast path.
62
+ // The engine attributes (spec/surface §1.2, 5.0: `_`-prefixed, snake_case —
63
+ // `_id`, `_path`, `_raw`, `_hash`, `_updated_at`, `_created_at` on docs and
64
+ // blocks, `_title`/`_tags` on docs, `_id`/`_path` on nodes and edges) → a
65
+ // param-free SQL scalar, per target, with its kind. The structural columns and
66
+ // foreign keys are translated in `translateOperand`. Anything not mapped
67
+ // (`_raw`, reconstructed or a blob; `_title`, computed; `_tags`, the union of
68
+ // the authored list and the computed hashtags) returns null → residual, and the
69
+ // caller must NOT fall through to a property read (an engine name is never the
70
+ // author's). `_updated_at` and `_created_at` (4.10) are correlated subqueries
71
+ // over the document's revisions (`current_rev` / `seq = 1`); a block's are
72
+ // residual, as before. `_path` (and the edge field `_dst_path`) is the REFERENCE
73
+ // form on the surface (spec/surface §1 "Paths", 2.0) — `'/' || <storage
74
+ // column>` in SQL — so every comparison, `startsWith`, `contains`, `lower()`…
75
+ // sees exactly the string the in-memory attribute yields; `pathColumn` keeps
76
+ // the bare indexed column at hand for the equality fast path.
77
77
  /** The rooted path read over a storage path column. */
78
78
  export function pathColumn(col) {
79
79
  return { sql: `('/' || ${col})`, params: [], kind: "text", pathCol: col };
@@ -81,57 +81,57 @@ export function pathColumn(col) {
81
81
  /** The identity/bytes/time attributes per kind — the names `attributeSql` owns
82
82
  * (the structural columns are handled per target in `translateOperand`). */
83
83
  export const ROW_ATTRIBUTES = {
84
- docs: new Set(["id", "path", "raw", "hash", "updated_at", "created_at", "title", "tags"]),
85
- blocks: new Set(["id", "path", "raw", "hash", "updated_at", "created_at"]),
86
- nodes: new Set(["id", "path"]),
87
- links: new Set(["id", "path"]),
84
+ docs: new Set(["_id", "_path", "_raw", "_hash", "_updated_at", "_created_at", "_title", "_tags"]),
85
+ blocks: new Set(["_id", "_path", "_raw", "_hash", "_updated_at", "_created_at"]),
86
+ nodes: new Set(["_id", "_path"]),
87
+ links: new Set(["_id", "_path"]),
88
88
  };
89
89
  function attributeSql(name, ctx) {
90
90
  const { self, doc, target } = ctx;
91
91
  const text = (sql) => ({ sql, params: [], kind: "text" });
92
92
  if (target === "docs")
93
93
  switch (name) {
94
- case "id": return text(`${self}.doc_id`);
95
- case "path": return pathColumn(`${doc}.path`);
96
- case "hash": return text(`lower(hex(${self}.file_hash))`);
97
- case "updated_at": return text(`(SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.rev_id = ${self}.current_rev)`);
98
- case "created_at": return text(`(SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.doc_id = ${self}.doc_id AND r.seq = 1)`);
94
+ case "_id": return text(`${self}.doc_id`);
95
+ case "_path": return pathColumn(`${doc}.path`);
96
+ case "_hash": return text(`lower(hex(${self}.file_hash))`);
97
+ case "_updated_at": return text(`(SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.rev_id = ${self}.current_rev)`);
98
+ case "_created_at": return text(`(SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.doc_id = ${self}.doc_id AND r.seq = 1)`);
99
99
  }
100
100
  else if (target === "blocks")
101
101
  switch (name) {
102
- case "id": return text(`${self}.block_id`);
103
- case "path": return pathColumn(`${doc}.path`);
104
- case "hash": return text(`lower(hex(${self}.raw_hash))`);
102
+ case "_id": return text(`${self}.block_id`);
103
+ case "_path": return pathColumn(`${doc}.path`);
104
+ case "_hash": return text(`lower(hex(${self}.raw_hash))`);
105
105
  }
106
106
  else if (target === "nodes")
107
107
  switch (name) {
108
- case "id": return text(`${self}.node_id`);
109
- case "path": return pathColumn(`${doc}.path`);
108
+ case "_id": return text(`${self}.node_id`);
109
+ case "_path": return pathColumn(`${doc}.path`);
110
110
  }
111
111
  else if (target === "links")
112
112
  switch (name) {
113
- case "id": return text(`${self}.edge_id`);
114
- case "path": return pathColumn(`${doc}.path`);
113
+ case "_id": return text(`${self}.edge_id`);
114
+ case "_path": return pathColumn(`${doc}.path`);
115
115
  }
116
116
  return null;
117
117
  }
118
- /** The links target's plain fields → the store's columns (spec/surface §1.2,
119
- * 3.0: a foreign key is named for what it is, never `$`-prefixed). `dst_path`
120
- * and `uri` are correlated subqueries; `dst_path` is the reference form. */
118
+ /** The links target's plain fields → the store's columns (spec/surface §1.2:
119
+ * a foreign key is named for what it is, `_`-prefixed since 5.0). `_dst_path`
120
+ * and `_uri` are correlated subqueries; `_dst_path` is the reference form. */
121
121
  function edgeField(name, self) {
122
122
  const text = (sql) => ({ sql, params: [], kind: "text" });
123
123
  switch (name) {
124
- case "doc_id": return text(`${self}.src_doc`);
125
- case "block_id": return text(`${self}.src_block`);
126
- case "via_id": return text(`${self}.via_node`);
127
- case "dst_id": return text(`${self}.dst_node`);
128
- case "dst_path": return text(`(SELECT '/' || dd.path FROM docs dd WHERE dd.doc_id = ${self}.dst_node)`);
129
- case "uri": return text(`(SELECT xn.uri FROM external_nodes xn WHERE xn.node_id = ${self}.dst_node)`);
130
- case "field": return text(`${self}.src_field`);
131
- case "predicate":
132
- case "provenance":
133
- case "dst_kind":
134
- case "anchor": return text(`${self}.${name}`);
124
+ case "_doc_id": return text(`${self}.src_doc`);
125
+ case "_block_id": return text(`${self}.src_block`);
126
+ case "_via_id": return text(`${self}.via_node`);
127
+ case "_dst_id": return text(`${self}.dst_node`);
128
+ case "_dst_path": return text(`(SELECT '/' || dd.path FROM docs dd WHERE dd.doc_id = ${self}.dst_node)`);
129
+ case "_uri": return text(`(SELECT xn.uri FROM external_nodes xn WHERE xn.node_id = ${self}.dst_node)`);
130
+ case "_field": return text(`${self}.src_field`);
131
+ case "_predicate":
132
+ case "_provenance":
133
+ case "_dst_kind":
134
+ case "_anchor": return text(`${self}.${name.slice(1)}`);
135
135
  default: return null;
136
136
  }
137
137
  }
@@ -140,14 +140,14 @@ const SEG = /^[A-Za-z_][A-Za-z0-9_]*$/; // injection-safe inlined identifier
140
140
  // rule): the row for `key` on the document only when the key has exactly one
141
141
  // row in scope and it is card='scalar' — a list-valued or nested key has none.
142
142
  // `select` is the projected expression over the alias `p`. `source` narrows
143
- // the scope to one authored source — the read `frontmatter.<k>` / `inline.<k>`
143
+ // the scope to one authored source — the read `_frontmatter.<k>` / `_inline.<k>`
144
144
  // performs (spec/surface §1.2; the in-memory `docProp(docId, key, source)`):
145
145
  // both the row and the count are of that source's rows only. The merged
146
- // `data.<k>` read is the bare read, so it passes no source. `docId` is the SQL
146
+ // `_data.<k>` read is the bare read, so it passes no source. `docId` is the SQL
147
147
  // expression naming the document: the scan's `d.doc_id`, or a link's
148
- // `e.dst_node` for a `dst.<k>` read (the destination's rows by its id — a
148
+ // `e.dst_node` for a `_dst.<k>` read (the destination's rows by its id — a
149
149
  // non-document or phantom destination has none, so the read is NULL exactly
150
- // where `dst` is absent in memory).
150
+ // where `_dst` is absent in memory).
151
151
  function propSubquery(docId, key, select, source) {
152
152
  const src = source ? ` AND p.source = '${source}'` : "";
153
153
  const src2 = source ? ` AND p2.source = '${source}'` : "";
@@ -173,12 +173,12 @@ function propScalarOf(docId, key, source) {
173
173
  prop: source ? { docId, key, source } : { docId, key },
174
174
  };
175
175
  }
176
- // A key read through a source handle (spec/surface §1.2, 3.0): `data.<k>` is
177
- // the merged authored read — exactly the bare property `<k>`; `frontmatter.<k>`
178
- // / `inline.<k>` the same read scoped to one source. Pushed since 3.0 (batch 3;
176
+ // A key read through a source handle (spec/surface §1.2): `_data.<k>` is the
177
+ // merged authored read — exactly the bare property `<k>`; `_frontmatter.<k>` /
178
+ // `_inline.<k>` the same read scoped to one source. Pushed since 3.0 (batch 3;
179
179
  // before, every handle member was residual). A whole-handle reference
180
- // (`frontmatter == null`) and `entries(<handle>)` still decline.
181
- const SOURCE_HANDLES = { data: undefined, frontmatter: "frontmatter", inline: "inline" };
180
+ // (`_frontmatter == null`) and `entries(<handle>)` still decline.
181
+ const SOURCE_HANDLES = { _data: undefined, _frontmatter: "frontmatter", _inline: "inline" };
182
182
  function handleRead(docAlias, handle, key) {
183
183
  return handleReadOf(`${docAlias}.doc_id`, handle, key);
184
184
  }
@@ -207,8 +207,8 @@ function constOperand(v) {
207
207
  default: return v === null ? { sql: "?", params: [null], kind: "null", value: null } : null;
208
208
  }
209
209
  }
210
- // The dotted `attrs.a.b` / `doc.x` receiver chain as segments, or null if it is
211
- // not a plain identifier navigation.
210
+ // The dotted `_attrs.a.b` / `_doc.x` receiver chain as segments, or null if it
211
+ // is not a plain identifier navigation.
212
212
  function memberSegments(e) {
213
213
  if (e.kind === "ident")
214
214
  return [e.name];
@@ -232,41 +232,45 @@ export function translateOperand(e, ctx) {
232
232
  return constOperand(ctx.params[e.index]);
233
233
  case "ident": {
234
234
  // An OQX `$` name (`$it`, `$key`, the follow intrinsics) is the language's,
235
- // never a column; omgbase adds no `$` names (spec/surface §1.2, 3.0).
235
+ // never a column; omgbase adds no `$` names (spec/surface §1.2).
236
236
  if (e.name.startsWith("$"))
237
237
  return null;
238
- // The row's own attributes win on collision (§1.2): translated or declined
239
- // (`raw`, `title`, `tags`), never read as a property or an attr.
240
- if (ROW_ATTRIBUTES[target].has(e.name))
241
- return attributeSql(e.name, ctx);
242
- // A relation, reach-through, source handle or the whole attrs object is not a
243
- // scalar read (decline (b)); a bare target name is a relation or a loud
238
+ // An engine name (§1.2, 5.0: `_`-prefixed) is translated or declined
239
+ // (`_raw`, `_title`, `_tags`; a relation, a reach-through, a source handle,
240
+ // the whole `_attrs` object — decline (b)); never read as a property or
241
+ // an attr. A `_x` that is no engine name of the kind is refused before
242
+ // planning (names.ts), so it declines here. A bare target name is a loud
244
243
  // error (root-row.ts), never a property.
245
- if (NON_PROPERTY_NAMES[target].has(e.name) || TARGETS.has(e.name))
244
+ if (e.name.startsWith("_")) {
245
+ if (ROW_ATTRIBUTES[target].has(e.name))
246
+ return attributeSql(e.name, ctx);
247
+ if (NON_PROPERTY_NAMES[target].has(e.name))
248
+ return null;
249
+ if (target === "docs")
250
+ return e.name === "_format" ? text(`${self}.format`) : null; // a column
251
+ if (target === "blocks") {
252
+ if (e.name === "_doc_id" || e.name === "_type" || e.name === "_text")
253
+ return text(`${self}.${e.name.slice(1)}`);
254
+ if (e.name === "_ordinal" || e.name === "_depth")
255
+ return { sql: `${self}.${e.name.slice(1)}`, params: [], kind: "int" };
256
+ return null;
257
+ }
258
+ if (target === "nodes")
259
+ return ["_doc_id", "_block_id", "_kind", "_name", "_value"].includes(e.name) ? text(`${self}.${e.name.slice(1)}`) : null;
260
+ if (target === "links")
261
+ return edgeField(e.name, self);
246
262
  return null;
247
- // Bare field per target.
248
- if (target === "docs") {
249
- if (e.name === "format")
250
- return text(`${self}.format`); // a column, not a property
251
- return propScalar(doc, e.name);
252
- }
253
- if (target === "blocks") {
254
- if (e.name === "doc_id" || e.name === "type" || e.name === "text")
255
- return text(`${self}.${e.name}`);
256
- if (e.name === "ordinal" || e.name === "depth")
257
- return { sql: `${self}.${e.name}`, params: [], kind: "int" };
258
- // Bare non-structural identifier flattens into attrs — same pushdown as
259
- // the `attrs.<k>` member form (json_extract), so `checked` == `attrs.checked`.
260
- return jsonExtract(`${self}.attrs`, [e.name]);
261
263
  }
262
- if (target === "nodes") {
263
- if (["doc_id", "block_id", "kind", "name", "value"].includes(e.name))
264
- return text(`${self}.${e.name}`);
265
- // Bare non-structural identifier flattens into attrs (see blocks above).
264
+ if (TARGETS.has(e.name))
265
+ return null;
266
+ // A bare name is the author's (§1.2): a document property, or on blocks
267
+ // and nodes an `attrs` key — the same pushdown as the `_attrs.<k>` member
268
+ // form (json_extract), so `checked` == `_attrs.checked`. A link row
269
+ // carries no authored data (names.ts refuses the read).
270
+ if (target === "docs")
271
+ return propScalar(doc, e.name);
272
+ if (target === "blocks" || target === "nodes")
266
273
  return jsonExtract(`${self}.attrs`, [e.name]);
267
- }
268
- if (target === "links")
269
- return edgeField(e.name, self);
270
274
  return null;
271
275
  }
272
276
  case "member": {
@@ -274,56 +278,55 @@ export function translateOperand(e, ctx) {
274
278
  if (!segs || segs.length < 2)
275
279
  return null;
276
280
  const [head, ...rest] = segs;
277
- // attrs.<path> → json_extract on the row's attrs (blocks/nodes).
278
- if (head === "attrs" && (target === "blocks" || target === "nodes")) {
281
+ // _attrs.<path> → json_extract on the row's attrs (blocks/nodes).
282
+ if (head === "_attrs" && (target === "blocks" || target === "nodes")) {
279
283
  return jsonExtract(`${self}.attrs`, rest);
280
284
  }
281
- // <handle>.<k> on docs — `data.layer`, `frontmatter.layer`, `inline.element`
282
- // — and `doc.<handle>.<k>` through the owning document on the other
283
- // targets: a property read, scoped to the handle's source.
285
+ // <handle>.<k> on docs — `_data.layer`, `_frontmatter.layer`,
286
+ // `_inline.element` — and `_doc.<handle>.<k>` through the owning document
287
+ // on the other targets: a property read, scoped to the handle's source.
284
288
  if (target === "docs" && head in SOURCE_HANDLES && rest.length === 1)
285
289
  return handleRead(doc, head, rest[0]);
286
- if (head === "doc" && rest.length === 2 && rest[0] in SOURCE_HANDLES)
290
+ if (head === "_doc" && target !== "docs" && rest.length === 2 && rest[0] in SOURCE_HANDLES)
287
291
  return handleRead(doc, rest[0], rest[1]);
288
- // doc.<x> reach-through — the owning doc (alias `doc`, which is `d`). On the
289
- // docs target `doc` is the row itself; either way it resolves against `doc`.
290
- // FOREIGN-KEY ELIMINATION (spec/surface §1, 3.0): `doc.id` is the row's
291
- // own FK column (`b.doc_id`, `n.doc_id`, `e.src_doc`) and `doc.path` /
292
- // `doc.hash` / `doc.updated_at` the scan's already-joined document
293
- // columns — no extra join, the same SQL as the bare `doc_id`.
294
- if (head === "doc") {
292
+ // _doc.<x> reach-through — the owning doc (alias `doc`, which is `d`) on
293
+ // blocks, nodes and links (a document has no `_doc` since 5.0: `$it` is
294
+ // the self). FOREIGN-KEY ELIMINATION (spec/surface §1, 3.0): `_doc._id` is
295
+ // the row's own FK column (`b.doc_id`, `n.doc_id`, `e.src_doc`) and
296
+ // `_doc._path` / `_doc._hash` / `_doc._updated_at` the scan's
297
+ // already-joined document columns — no extra join, the same SQL as the
298
+ // bare `_doc_id`.
299
+ if (head === "_doc" && target !== "docs") {
295
300
  if (rest.length !== 1)
296
301
  return null;
297
302
  const k = rest[0];
298
- if (k === "id")
303
+ if (k === "_id")
299
304
  return text(target === "links" ? `${self}.src_doc` : `${self}.doc_id`);
300
- if (k === "path")
305
+ if (k === "_path")
301
306
  return pathColumn(`${doc}.path`);
302
- if (k === "hash")
307
+ if (k === "_hash")
303
308
  return text(`lower(hex(${doc}.file_hash))`);
304
- if (k === "updated_at")
309
+ if (k === "_updated_at")
305
310
  return text(`(SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.rev_id = ${doc}.current_rev)`);
306
- if (k === "created_at")
311
+ if (k === "_created_at")
307
312
  return text(`(SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.doc_id = ${doc}.doc_id AND r.seq = 1)`);
308
- if (k === "format")
313
+ if (k === "_format")
309
314
  return text(`${doc}.format`);
310
- if (k.startsWith("$"))
311
- return null;
312
- if (ROW_ATTRIBUTES.docs.has(k))
313
- return null; // `doc.raw`, `doc.title`, `doc.tags`: the attribute, not pushable
314
- if (NON_PROPERTY_NAMES.docs.has(k) || TARGETS.has(k))
315
- return null; // `doc.nodes`, `doc.data`, `doc.docs` … (decline (b))
315
+ if (k.startsWith("$") || k.startsWith("_"))
316
+ return null; // `_doc._raw`, `_doc._title`, `_doc._nodes`, an unknown engine name: not pushable (decline (b))
317
+ if (TARGETS.has(k))
318
+ return null; // a bare target name: the loud error, never a property
316
319
  return propScalar(doc, k);
317
320
  }
318
- // dst.<x> reach-through from a link (spec/surface §1.2, 3.0 follow-up): the
319
- // destination DOCUMENT, keyed by the row's own `dst_node` — the lookup
320
- // `dst_path` already runs. A property read scopes `properties` by
321
+ // _dst.<x> reach-through from a link (spec/surface §1.2, 3.0 follow-up):
322
+ // the destination DOCUMENT, keyed by the row's own `dst_node` — the lookup
323
+ // `_dst_path` already runs. A property read scopes `properties` by
321
324
  // `e.dst_node` directly; the document's own columns are a primary-key
322
325
  // probe `(SELECT … FROM docs dd WHERE dd.doc_id = e.dst_node)` — NULL
323
- // wherever `dst` is absent in memory (a block or external destination, a
324
- // phantom). `dst.raw` / `dst.title` / `dst.tags` and the relations stay
325
- // residual, as `doc.*`'s do.
326
- if (head === "dst" && target === "links") {
326
+ // wherever `_dst` is absent in memory (a block or external destination, a
327
+ // phantom). `_dst._raw` / `_dst._title` / `_dst._tags` and the relations
328
+ // stay residual, as `_doc.*`'s do.
329
+ if (head === "_dst" && target === "links") {
327
330
  const dstId = `${self}.dst_node`;
328
331
  if (rest.length === 2 && rest[0] in SOURCE_HANDLES)
329
332
  return handleReadOf(dstId, rest[0], rest[1]);
@@ -331,58 +334,56 @@ export function translateOperand(e, ctx) {
331
334
  return null;
332
335
  const k = rest[0];
333
336
  const probe = (col) => text(`(SELECT ${col} FROM docs dd WHERE dd.doc_id = ${dstId})`);
334
- if (k === "id")
337
+ if (k === "_id")
335
338
  return probe("dd.doc_id");
336
- if (k === "path")
339
+ if (k === "_path")
337
340
  return probe("'/' || dd.path");
338
- if (k === "hash")
341
+ if (k === "_hash")
339
342
  return probe("lower(hex(dd.file_hash))");
340
- if (k === "updated_at")
343
+ if (k === "_updated_at")
341
344
  return probe("(SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.rev_id = dd.current_rev)");
342
- if (k === "created_at")
345
+ if (k === "_created_at")
343
346
  return probe("(SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.doc_id = dd.doc_id AND r.seq = 1)");
344
- if (k === "format")
347
+ if (k === "_format")
345
348
  return probe("dd.format");
346
- if (k.startsWith("$"))
347
- return null;
348
- if (ROW_ATTRIBUTES.docs.has(k) || NON_PROPERTY_NAMES.docs.has(k) || TARGETS.has(k))
349
+ if (k.startsWith("$") || k.startsWith("_") || TARGETS.has(k))
349
350
  return null;
350
351
  return propScalarOf(dstId, k);
351
352
  }
352
- // block.<x> reach-through from a node: the block's identity is the node's
353
- // FK (`block.id` → `n.block_id`, `block.path` → the joined `d.path`,
354
- // `block.doc_id` → `n.doc_id` — FK elimination again); `type`/`text` are
355
- // a correlated read of the block row.
356
- if (head === "block" && target === "nodes" && rest.length === 1) {
353
+ // _block.<x> reach-through from a node: the block's identity is the node's
354
+ // FK (`_block._id` → `n.block_id`, `_block._path` → the joined `d.path`,
355
+ // `_block._doc_id` → `n.doc_id` — FK elimination again); `_type`/`_text`
356
+ // are a correlated read of the block row.
357
+ if (head === "_block" && target === "nodes" && rest.length === 1) {
357
358
  const k = rest[0];
358
- if (k === "id")
359
+ if (k === "_id")
359
360
  return text(`${self}.block_id`);
360
- if (k === "path")
361
+ if (k === "_path")
361
362
  return pathColumn(`${doc}.path`);
362
- if (k === "doc_id")
363
+ if (k === "_doc_id")
363
364
  return text(`${self}.doc_id`);
364
- if (k === "type" || k === "text")
365
- return text(`(SELECT bb.${k} FROM blocks bb WHERE bb.block_id = ${self}.block_id)`);
365
+ if (k === "_type" || k === "_text")
366
+ return text(`(SELECT bb.${k.slice(1)} FROM blocks bb WHERE bb.block_id = ${self}.block_id)`);
366
367
  return null;
367
368
  }
368
- // block.<x> reach-through from a link (spec/surface §1.2, 4.11): the SOURCE
369
- // block, keyed by the row's own `src_block` — `block.id` is that FK column
370
- // (NULL for a frontmatter link, exactly where `block` is absent in memory);
371
- // everything else is a primary-key probe of the block row, NULL for the
372
- // same rows (`block.path` reads the scan's joined source document inside
373
- // the probe, so it too is NULL when there is no block). `raw`, `hash`,
374
- // `updated_at`, `attrs` and the relations stay residual.
375
- if (head === "block" && target === "links" && rest.length === 1) {
369
+ // _block.<x> reach-through from a link (spec/surface §1.2, 4.11): the
370
+ // SOURCE block, keyed by the row's own `src_block` — `_block._id` is that
371
+ // FK column (NULL for a frontmatter link, exactly where `_block` is absent
372
+ // in memory); everything else is a primary-key probe of the block row,
373
+ // NULL for the same rows (`_block._path` reads the scan's joined source
374
+ // document inside the probe, so it too is NULL when there is no block).
375
+ // `_raw`, `_hash`, `_updated_at`, `_attrs` and the relations stay residual.
376
+ if (head === "_block" && target === "links" && rest.length === 1) {
376
377
  const k = rest[0];
377
378
  const probe = (col) => text(`(SELECT ${col} FROM blocks bb WHERE bb.block_id = ${self}.src_block)`);
378
- if (k === "id")
379
+ if (k === "_id")
379
380
  return text(`${self}.src_block`);
380
- if (k === "path")
381
+ if (k === "_path")
381
382
  return probe(`'/' || ${doc}.path`);
382
- if (k === "doc_id")
383
+ if (k === "_doc_id")
383
384
  return probe("bb.doc_id");
384
- if (k === "type" || k === "text")
385
- return probe(`bb.${k}`);
385
+ if (k === "_type" || k === "_text")
386
+ return probe(`bb.${k.slice(1)}`);
386
387
  return null;
387
388
  }
388
389
  return null;
@@ -537,7 +538,7 @@ function translateComparison(e, ctx) {
537
538
  }
538
539
  const FLIP = { "<": ">", "<=": ">=", ">": "<", ">=": "<=" };
539
540
  /**
540
- * The path equality fast path: `path == "/a.md"` (or `!=`, either side) against
541
+ * The path equality fast path: `_path == "/a.md"` (or `!=`, either side) against
541
542
  * a rooted text constant is `d.path IS ?` with the constant's storage form, so
542
543
  * the `(repo_id, path)` index serves it; `'/' || d.path IS ?` — what the general
543
544
  * form would emit — is the same predicate without the index. Only a constant
@@ -14,7 +14,7 @@ export interface StoreIndexDeps {
14
14
  }
15
15
  /** A store-backed `RowIndex` for the root scan of `target` on `path`, or
16
16
  * `undefined` when SQLite has no index for the path (per-target table above;
17
- * multi-segment paths; `format`, edge `predicate`/`field`, `tags`, …). */
17
+ * multi-segment paths; `_format`, edge `_predicate`/`_field`, `_tags`, …). */
18
18
  export declare function storeIndexFor(target: Target, path: readonly string[], deps: StoreIndexDeps): RowIndex | undefined;
19
19
  /** The paths `storeIndexFor` answers from SQLite, per target (for tests and docs). */
20
20
  export declare function indexablePaths(target: Target): readonly string[];
@@ -1,8 +1,8 @@
1
1
  // Store-backed equality indexes for the OQX engine (`DataContext.indexFor`):
2
2
  // when a nested block's receiver is a ROOT SCAN (`^docs`, `^links`,
3
3
  // a bare `docs` at the root scope) and its correlated or constant equality is on
4
- // a column or property SQLite can look up — `where path == ^^path`,
5
- // `where customer == ^path`, `where dst_id == ^id`, `where type == "order"` —
4
+ // a column or property SQLite can look up — `where _path == ^^_path`,
5
+ // `where customer == ^_path`, `where _dst_id == ^_id`, `where _type == "order"` —
6
6
  // the probe runs ONE prepared statement against the store's indexes instead of
7
7
  // materializing every row of the target and hashing it. The rows come back
8
8
  // exactly as the root scan constructs them (same columns, same `__path`, same
@@ -12,11 +12,12 @@
12
12
  //
13
13
  // Typed equality (OQX SEMANTICS §5) is preserved by probing per VALUE KIND:
14
14
  //
15
- // • a text column (`id`, `path`, `type`, `kind`, `name`, `doc_id`, `dst_id`, …)
16
- // matches a string probe by `= ?` (TEXT = TEXT, exact); a number, boolean,
17
- // object or any other non-string probe matches NO row (the column holds
18
- // strings or NULL and `5 == "5"` is false); an absent probe (`null`,
19
- // `undefined`: `name == null`) is answered by the in-memory fallback below;
15
+ // • a text column (`_id`, `_path`, `_type`, `_kind`, `_name`, `_doc_id`,
16
+ // `_dst_id`, …) matches a string probe by `= ?` (TEXT = TEXT, exact); a
17
+ // number, boolean, object or any other non-string probe matches NO row (the
18
+ // column holds strings or NULL and `5 == "5"` is false); an absent probe
19
+ // (`null`, `undefined`: `_name == null`) is answered by the in-memory
20
+ // fallback below;
20
21
  // • a document property (`customer`, `title`) matches a string probe only on
21
22
  // a `type = 'string'` row (`val_text = ?`), a number probe only on a
22
23
  // `type = 'number'` row (`val_num = ?`), a boolean only on a `type = 'bool'`
@@ -38,7 +39,6 @@
38
39
  // contract but the engine never calls it for these indexes: it probes
39
40
  // `lookupRows` first and so never reads the root whole.
40
41
  import { semantics } from "@omgbase/oqx";
41
- import { NON_PROPERTY_NAMES, ROW_ATTRIBUTES } from "./sql/translate.js";
42
42
  import { COLS, FROM, FROM_BY_DOC, ORDER, guards, guardsByDoc } from "./sql/scan.js";
43
43
  import { storagePath } from "../core/paths.js";
44
44
  // ---- which paths SQLite answers ------------------------------------------------
@@ -52,16 +52,16 @@ import { storagePath } from "../core/paths.js";
52
52
  * `idx_edges_dst (dst_node, …)`; `path` on a joined target reaches the docs
53
53
  * `(repo_id, path)` key and then the target's `doc_id`/`src_doc` index. */
54
54
  const COLUMN_PROBES = {
55
- docs: { id: "d.doc_id = ?", path: "d.path = ?" },
56
- blocks: { id: "b.block_id = ?", doc_id: "b.doc_id = ?", type: "b.type = ?", path: "d.path = ?" },
57
- nodes: { id: "n.node_id = ?", doc_id: "n.doc_id = ?", kind: "n.kind = ?", name: "n.name = ?", path: "d.path = ?" },
55
+ docs: { _id: "d.doc_id = ?", _path: "d.path = ?" },
56
+ blocks: { _id: "b.block_id = ?", _doc_id: "b.doc_id = ?", _type: "b.type = ?", _path: "d.path = ?" },
57
+ nodes: { _id: "n.node_id = ?", _doc_id: "n.doc_id = ?", _kind: "n.kind = ?", _name: "n.name = ?", _path: "d.path = ?" },
58
58
  links: {
59
- id: "e.edge_id = ?", doc_id: "e.src_doc = ?", dst_id: "e.dst_node = ?", path: "d.path = ?",
60
- // `dst_path` is the destination document's path (deleted or not — the
59
+ _id: "e.edge_id = ?", _doc_id: "e.src_doc = ?", _dst_id: "e.dst_node = ?", _path: "d.path = ?",
60
+ // `_dst_path` is the destination document's path (deleted or not — the
61
61
  // field reads `docs` by id without a liveness guard). A destination is
62
62
  // resolved within the edge's repo (spec/graph §3), where `(repo_id, path)`
63
63
  // is UNIQUE, so the probe is that one document; `?r` is the repo id.
64
- dst_path: "e.dst_node IN (SELECT doc_id FROM docs WHERE repo_id = ?r AND path = ?)",
64
+ _dst_path: "e.dst_node IN (SELECT doc_id FROM docs WHERE repo_id = ?r AND path = ?)",
65
65
  },
66
66
  };
67
67
  /** How a one-segment path on `target` is probed, or null when SQLite has no
@@ -75,17 +75,15 @@ function probeFor(target, path) {
75
75
  return { kind: "column", sql: column };
76
76
  if (target !== "docs")
77
77
  return null;
78
- // A docs property: any bare name the context reads through `docProp` — not a
79
- // relation / handle (`out_links`, `data`, `frontmatter`, …), not the `format` column
80
- // (not indexed), not an OQX `$` name, and of the engine attributes only
81
- // `title` (the computed `$title` row, a scalar property the author's `title:`
82
- // never reaches — `data.title` does): `raw`, `hash`, `updated_at`, `created_at` and `tags`
83
- // (authored ∪ hashtags) have no index and are never property probes.
84
- if (key === "title")
78
+ // A docs property: any BARE name (spec/surface §1.2, 5.0: the author's data —
79
+ // `title`, `tags`, `layer`, …; an OQX `$` name is never a row read). Of the
80
+ // engine names only `_title` probes (the computed `$title` row, a scalar
81
+ // property): `_raw`, `_hash`, `_updated_at`, `_created_at`, `_tags` (authored
82
+ // ∪ hashtags), `_format` (an unindexed column), the handles and relations have
83
+ // no index and are never property probes.
84
+ if (key === "_title")
85
85
  return { kind: "property", key: "$title", source: "computed" };
86
- if (key.startsWith("$") || key === "format")
87
- return null;
88
- if (NON_PROPERTY_NAMES.docs.has(key) || ROW_ATTRIBUTES.docs.has(key))
86
+ if (key.startsWith("$") || key.startsWith("_"))
89
87
  return null;
90
88
  return { kind: "property", key, source: null };
91
89
  }
@@ -110,10 +108,10 @@ class StoreIndex {
110
108
  if (this.probe.kind === "column") {
111
109
  if (typeof v !== "string")
112
110
  return [];
113
- // `path` / `dst_path` are the reference form in memory (`/a.md`,
111
+ // `_path` / `_dst_path` are the reference form in memory (`/a.md`,
114
112
  // spec/surface §1 "Paths"); the column holds the storage form. A rooted
115
113
  // probe is de-rooted; a bare one can equal no rooted path — no row.
116
- if (this.path === "path" || this.path === "dst_path")
114
+ if (this.path === "_path" || this.path === "_dst_path")
117
115
  return v.startsWith("/") ? this.column(storagePath(v)) : [];
118
116
  return this.column(v);
119
117
  }
@@ -137,13 +135,13 @@ class StoreIndex {
137
135
  }
138
136
  return out.sort((a, b) => a - b);
139
137
  }
140
- // A `path` probe on a joined target drives from the document (`FROM_BY_DOC`),
138
+ // A `_path` probe on a joined target drives from the document (`FROM_BY_DOC`),
141
139
  // every other column probe from the target's own index. `?r` in a probe is
142
140
  // the repo id, bound ahead of the value.
143
141
  column(value) {
144
142
  const t = this.target;
145
143
  const probe = this.probe.sql;
146
- const byDoc = t !== "docs" && this.path === "path";
144
+ const byDoc = t !== "docs" && this.path === "_path";
147
145
  const g = byDoc ? guardsByDoc(t, this.deps.repoId) : guards(t, this.deps.repoId);
148
146
  const from = byDoc ? FROM_BY_DOC[t] : FROM[t];
149
147
  const params = probe.includes("?r") ? [this.deps.repoId, value] : [value];
@@ -182,7 +180,7 @@ class StoreIndex {
182
180
  }
183
181
  /** A store-backed `RowIndex` for the root scan of `target` on `path`, or
184
182
  * `undefined` when SQLite has no index for the path (per-target table above;
185
- * multi-segment paths; `format`, edge `predicate`/`field`, `tags`, …). */
183
+ * multi-segment paths; `_format`, edge `_predicate`/`_field`, `_tags`, …). */
186
184
  export function storeIndexFor(target, path, deps) {
187
185
  const probe = probeFor(target, path);
188
186
  return probe ? new StoreIndex(target, path[0], probe, deps) : undefined;
@@ -190,6 +188,6 @@ export function storeIndexFor(target, path, deps) {
190
188
  /** The paths `storeIndexFor` answers from SQLite, per target (for tests and docs). */
191
189
  export function indexablePaths(target) {
192
190
  const cols = Object.keys(COLUMN_PROBES[target]);
193
- return target === "docs" ? [...cols, "title", "<property key>"] : cols;
191
+ return target === "docs" ? [...cols, "_title", "<property key>"] : cols;
194
192
  }
195
193
  //# sourceMappingURL=store-index.js.map