@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
@@ -1,12 +1,14 @@
1
1
  import type { Query, ReferenceStep } from "@omgbase/oqx";
2
2
  import { type Kind } from "./context.js";
3
3
  import type { Store } from "../core/store/store.js";
4
- /** What to do with a tier-3 name on an open kind: record it in `hints` (every read) or refuse the query (a write selector). */
4
+ /** What to do with an unknown bare name on an open kind: record it in `hints` (every read) or refuse the query (a write selector). */
5
5
  export type UnknownNames = "hint" | "refuse";
6
6
  /** The relations of each kind and the kind of the rows they yield (spec/surface §1.2). */
7
7
  export declare const RELATIONS: Readonly<Record<Kind, Readonly<Record<string, Kind>>>>;
8
- /** The self and reach-through handles of each kind and the kind of the row they reach (§1.2). */
8
+ /** The reach-through handles of each kind and the kind of the row they reach (§1.2; the self aliases are gone in 5.0 — `$it` is the self). */
9
9
  export declare const HANDLES: Readonly<Record<Kind, Readonly<Record<string, Kind>>>>;
10
+ /** Every engine name of a kind, in the §1.2 table's order: attributes, reach-throughs, source handles (docs), relations. */
11
+ export declare function engineNames(target: Kind): string[];
10
12
  /** The vocabulary of a kind in a repository (cached on the head commit; the SQL runs only when a name is not intrinsic). */
11
13
  export declare function vocabulary(store: Store, repoId: string, target: Kind): {
12
14
  present: boolean;
@@ -17,24 +19,33 @@ export declare function kindOfScope(steps: readonly ReferenceStep[]): Kind | nul
17
19
  /** Optimal string alignment distance (Damerau–Levenshtein with adjacent transpositions, each edit 1). */
18
20
  export declare function editDistance(a: string, b: string): number;
19
21
  /**
20
- * The nearest match of `name` among `candidates` (§1.5): case-insensitive; a
21
- * candidate qualifies when its distance is ≤ 2, or when one of the two is a
22
- * substring of the other and the shorter has at least three characters; the
23
- * lowest distance wins, ties broken by the shorter candidate, then
24
- * alphabetically. Null when nothing qualifies.
22
+ * The nearest match of `name` among `candidates` (§1.5). First, the one hint
23
+ * the name rule makes certain: a candidate that is exactly `name` with the
24
+ * engine prefix added or removed (`id` → `_id`, `_layer` → `layer`) wins
25
+ * outright, whatever its length. Otherwise case-insensitive and **prefix-blind**
26
+ * — the `_` is a declaration, not spelling, so `commited` is one edit from
27
+ * `_commit` and `titel` one from `_title` as from `title`: a candidate
28
+ * qualifies when its distance is ≤ 2, or when one of the two is a substring of
29
+ * the other and the shorter has at least three characters; the lowest distance
30
+ * wins, ties broken by the shorter candidate (so the bare authored `title`
31
+ * beats `_title` when both are one edit away), then alphabetically. Null when
32
+ * nothing qualifies.
25
33
  */
26
34
  export declare function nearest(name: string, candidates: readonly string[]): string | null;
27
- /** The error of a closed kind: `` `target` is not a field of links (fields: id, path, …); did you mean `…`? ``. */
28
- export declare function fieldErrorMessage(store: Store, repoId: string, target: Kind, name: string): string;
29
- /** The hint of an open kind: `` `preview` is not a field of blocks and no block in this repository carries a property by that name; did you mean `…`? ``.
30
- * Under a source handle the name is an authored lookup by construction, so the
31
- * hint says only that: `` no document in this repository carries a property `path` (`data.path`); did you mean `…`? ``,
32
- * the nearest match drawn from the authored keys alone. */
35
+ /** The error of an unknown engine name: `` `_target` is not an engine name of links (engine names: _id, _path, …); did you mean `_dst_path`? ``
36
+ * — the nearest match drawn from the engine names and the authored keys, so a `_layer` written for the authored `layer` is pointed at it. */
37
+ export declare function engineNameErrorMessage(store: Store, repoId: string, target: Kind, name: string): string;
38
+ /** The error of a bare name on a closed kind: `` `target` reads an authored property, and a link carries none (engine names: _id, _path, …); did you mean `_dst_path`? ``. */
39
+ export declare function closedKindErrorMessage(target: Kind, name: string, handle?: string): string;
40
+ /** The hint of an open kind: `` no block in this repository carries a property `preview`; did you mean `_text`? ``.
41
+ * Under a source handle the name is shown with it — `` no document in this repository carries a property `x` (`_data.x`); did you mean `y`? `` —
42
+ * and the nearest match is drawn from the authored keys alone. */
33
43
  export declare function propertyHintMessage(store: Store, repoId: string, target: Kind, name: string, handle?: string): string;
34
44
  /**
35
- * Judge every name the query reads. Returns the hints (one per offending name
36
- * of an open kind, first-reference order, deduplicated) under `"hint"`; throws
37
- * `FilterInvalid` for a tier-3 name on a closed kind in either mode, and for
38
- * one on an open kind under `"refuse"`.
45
+ * Judge every name the query reads. Returns the hints (one per unknown bare
46
+ * name of an open kind, first-reference order, deduplicated) under `"hint"`;
47
+ * throws `FilterInvalid` for an unknown engine name (`_x`) and for a bare name
48
+ * on a closed kind in either mode, and for an unknown bare name on an open kind
49
+ * under `"refuse"`.
39
50
  */
40
51
  export declare function checkNames(store: Store, repoId: string, raw: Query, mode: UnknownNames): string[];
@@ -1,52 +1,63 @@
1
- // Unknown names (spec/surface §1.5, 4.6). A query that projects, filters or
2
- // orders by a name no row of the scope's kind can carry — `select path, target
3
- // from links` (links have `dst_path`), `where preview != ""` on blocks — used
4
- // to run silently: an absent column in every row, a predicate true or false of
5
- // every row. This pass judges every name the query reads (`@omgbase/oqx`'s
6
- // `references`, AST.md §5b) against the VOCABULARY of the row kind it is read
7
- // from, statically — before any row is read, so the verdict never depends on
8
- // the query's own filter — and either records a hint or refuses the query.
1
+ // Unknown names (spec/surface §1.5, 4.6; simplified in 5.0). A query that
2
+ // projects, filters or orders by a name no row of the scope's kind can carry —
3
+ // `select _path, target from links` (links have `_dst_path`), `where preview !=
4
+ // ""` on blocks — used to run silently: an absent column in every row, a
5
+ // predicate true or false of every row. This pass judges every name the query
6
+ // reads (`@omgbase/oqx`'s `references`, AST.md §5b) against the row kind it is
7
+ // read from, statically — before any row is read, so the verdict never depends
8
+ // on the query's own filter — and either records a hint or refuses the query.
9
9
  //
10
- // The three tiers of a name at a scope whose kind is known:
11
- // 1. an engine attribute, handle or relation of the kind — known;
12
- // 2. an authored property at least one live object of the kind in this
13
- // repository carries (docs: a frontmatter or inline key — `stage` on three
14
- // processes is enough; blocks and nodes: a key of a live row's `attrs`,
15
- // the bare-name read of those kinds) — known, however partial;
16
- // 3. a name no object of the kind carries — the complaint.
17
- // Severity: `links` has a closed row shape (no authored data reaches a link
18
- // row), so a tier-3 name there is `filter_invalid`; docs, blocks and nodes are
19
- // open — on a read the result carries one hint per offending name and the rows
20
- // still come back; a write selector (the AST entry, `unknownNames: "refuse"`)
21
- // is refused, since a name no row carries would select every row. A repository
22
- // with no live object of the kind has no vocabulary: the check is skipped for
23
- // that kind. The vocabulary is read once per (repository, kind) and cached on
24
- // the repository's head commit (`MAX(commits.seq)`), the one thing every
25
- // property or attrs change moves.
10
+ // The name rule (§1.2, 5.0) makes the judgement two-sided:
11
+ // • an UNDERSCORE-prefixed name is an engine name — an attribute, a
12
+ // reach-through, a source handle or a relation of the kind, a CLOSED set —
13
+ // so a `_x` that is none of them is `filter_invalid` naming the kind's
14
+ // engine names (the one loud error of this pass; both modes);
15
+ // • a BARE name is the author's — on a document a frontmatter or inline key,
16
+ // on a block or node a key of its `attrs` — and is judged against the
17
+ // VOCABULARY of the kind in this repository: a name at least one live object
18
+ // carries is known (however partial: `stage` on three processes out of
19
+ // twenty documents is the data model working); a name no object carries is
20
+ // the complaint — on a read the result carries one hint per offending name
21
+ // and the rows still come back; a write selector (the AST entry,
22
+ // `unknownNames: "refuse"`) is refused, since a name no row carries would
23
+ // select every row. The hint's nearest match is drawn from the authored keys
24
+ // AND the engine names, so the one hint worth keeping survives: a bare
25
+ // `title` / `id` / `path` / `tags` / `format` that spells an engine attribute
26
+ // without its prefix says "did you mean `_title`?". A `links` or `revisions`
27
+ // row carries no authored data at all, so a bare name there is
28
+ // `filter_invalid` either way (closed row shape). A repository with no live
29
+ // object of an open kind has no vocabulary: the check is skipped for that
30
+ // kind. The vocabulary is read once per (repository, kind) and cached on the
31
+ // repository's head commit (`MAX(commits.seq)`), the one thing every
32
+ // property or attrs change moves.
26
33
  import { references } from "@omgbase/oqx";
27
- import { ATTRIBUTES, SOURCE_HANDLES } from "./context.js";
34
+ import { ATTRIBUTES, REACH_THROUGHS, SOURCE_HANDLES } from "./context.js";
28
35
  import { ROOTS } from "./root-row.js";
29
36
  import { FROM, guards } from "./sql/scan.js";
30
37
  import { FilterInvalid } from "../search/cel/parser.js";
31
38
  /** The relations of each kind and the kind of the rows they yield (spec/surface §1.2). */
32
39
  export const RELATIONS = {
33
- docs: { blocks: "blocks", nodes: "nodes", out_links: "links", in_links: "links", revisions: "revisions" },
34
- blocks: { children: "blocks", nodes: "nodes", out_links: "links", section: "nodes" },
35
- nodes: { blocks: "blocks", subsections: "nodes", children: "nodes" },
40
+ docs: { _blocks: "blocks", _nodes: "nodes", _out_links: "links", _in_links: "links", _revisions: "revisions" },
41
+ blocks: { _children: "blocks", _nodes: "nodes", _out_links: "links", _section: "nodes" },
42
+ nodes: { _blocks: "blocks", _subsections: "nodes", _children: "nodes" },
36
43
  links: {},
37
44
  revisions: {},
38
45
  };
39
- /** The self and reach-through handles of each kind and the kind of the row they reach (§1.2). */
46
+ /** The reach-through handles of each kind and the kind of the row they reach (§1.2; the self aliases are gone in 5.0 — `$it` is the self). */
40
47
  export const HANDLES = {
41
- docs: { doc: "docs" },
42
- blocks: { block: "blocks", doc: "docs" },
43
- nodes: { section: "nodes", doc: "docs", block: "blocks" },
44
- links: { doc: "docs", dst: "docs", block: "blocks" },
45
- revisions: { doc: "docs" },
48
+ docs: {},
49
+ blocks: { _doc: "docs" },
50
+ nodes: { _doc: "docs", _block: "blocks" },
51
+ links: { _doc: "docs", _dst: "docs", _block: "blocks" },
52
+ revisions: { _doc: "docs" },
46
53
  };
47
54
  /** The kinds whose row shape is closed: no authored data reaches a row by bare name. */
48
55
  const CLOSED = new Set(["links", "revisions"]);
49
56
  const NOUN = { docs: "document", blocks: "block", nodes: "node", links: "link", revisions: "revision" };
57
+ /** Every engine name of a kind, in the §1.2 table's order: attributes, reach-throughs, source handles (docs), relations. */
58
+ export function engineNames(target) {
59
+ return [...ATTRIBUTES[target], ...REACH_THROUGHS[target], ...(target === "docs" ? [...SOURCE_HANDLES] : []), ...Object.keys(RELATIONS[target])];
60
+ }
50
61
  const CACHE = new WeakMap();
51
62
  function headSeq(store, repoId) {
52
63
  const row = store.db.prepare("SELECT COALESCE(MAX(seq), 0) AS seq FROM commits WHERE repo_id = ?").get(repoId);
@@ -65,7 +76,7 @@ function repoCache(store, repoId) {
65
76
  /** The authored vocabulary of a kind: docs — every live frontmatter or inline
66
77
  * key (the merged read a bare name performs); blocks and nodes — every key a
67
78
  * live row's `attrs` holds (the bare-name read of those kinds). Computed keys
68
- * (`$title`, `$tags`) are the intrinsics `title`/`tags`, not authored keys. */
79
+ * (`$title`, `$tags`) are the attributes `_title`/`_tags`, not authored keys. */
69
80
  function authoredKeys(store, repoId, target) {
70
81
  let sql;
71
82
  switch (target) {
@@ -150,7 +161,7 @@ function dotted(e) {
150
161
  const inner = dotted(e.recv);
151
162
  return inner === null ? null : { head: inner.head, rest: [...inner.rest, e.name] };
152
163
  }
153
- /** Follow a dotted path from a row kind through handles and relations (`doc.blocks` on a link → blocks). */
164
+ /** Follow a dotted path from a row kind through reach-throughs and relations (`_doc._blocks` on a link → blocks). */
154
165
  function kindOfPath(on, path) {
155
166
  let cur = on;
156
167
  for (let i = 0; i < path.length; i++) {
@@ -216,15 +227,19 @@ export function kindOfScope(steps) {
216
227
  /** Judge a dotted name read on rows of `target`; null = known. */
217
228
  function judge(store, repoId, target, path) {
218
229
  const [head, ...rest] = path;
219
- // An engine attribute wins on collision; what follows it (`attrs.checked`, `tags.size`) is the value's own navigation.
220
- if (ATTRIBUTES[target].has(head))
221
- return null;
222
- // The source handles reach the authored data under them.
223
- if (target === "docs" && SOURCE_HANDLES.has(head))
224
- return rest.length === 0 ? null : judgeAuthored(store, repoId, "docs", rest, head);
225
- const via = HANDLES[target][head] ?? RELATIONS[target][head];
226
- if (via !== undefined)
227
- return rest.length === 0 ? null : judge(store, repoId, via, rest);
230
+ if (head.startsWith("_")) {
231
+ // An engine attribute: what follows it (`_attrs.checked`, `_tags.size`) is the value's own navigation.
232
+ if (ATTRIBUTES[target].has(head))
233
+ return null;
234
+ // The source handles reach the authored data under them.
235
+ if (target === "docs" && SOURCE_HANDLES.has(head))
236
+ return rest.length === 0 ? null : judgeAuthored(store, repoId, "docs", rest, head);
237
+ const via = HANDLES[target][head] ?? RELATIONS[target][head];
238
+ if (via !== undefined)
239
+ return rest.length === 0 ? null : judge(store, repoId, via, rest);
240
+ // Not an engine name of this kind: the closed set is the whole set.
241
+ return { target, name: head, why: "engine" };
242
+ }
228
243
  // A bare target name on a row is the context's own loud error (root-row.ts); not doubled here.
229
244
  if (ROOTS.has(head))
230
245
  return null;
@@ -233,22 +248,15 @@ function judge(store, repoId, target, path) {
233
248
  function judgeAuthored(store, repoId, target, path, handle) {
234
249
  const name = path.join(".");
235
250
  if (CLOSED.has(target))
236
- return { target, name, closed: true };
251
+ return { target, name, why: "closed", ...(handle !== undefined ? { handle } : {}) };
237
252
  const v = vocabularyOf(store, repoId, target);
238
253
  if (!v.present)
239
254
  return null; // no object of the kind: no vocabulary to judge by
240
255
  if (v.keys.has(name))
241
256
  return null;
242
- return { target, name, closed: false, ...(handle !== undefined ? { handle } : {}) };
257
+ return { target, name, why: "authored", ...(handle !== undefined ? { handle } : {}) };
243
258
  }
244
259
  // ---- messages --------------------------------------------------------------
245
- /** The names a kind knows, for the nearest match: attributes, handles, relations, the authored keys. */
246
- function candidates(store, repoId, target) {
247
- const out = [...ATTRIBUTES[target], ...Object.keys(HANDLES[target]), ...(target === "docs" ? [...SOURCE_HANDLES] : []), ...Object.keys(RELATIONS[target])];
248
- if (!CLOSED.has(target))
249
- out.push(...vocabularyOf(store, repoId, target).list);
250
- return out;
251
- }
252
260
  /** Optimal string alignment distance (Damerau–Levenshtein with adjacent transpositions, each edit 1). */
253
261
  export function editDistance(a, b) {
254
262
  const s = Array.from(a), t = Array.from(b);
@@ -270,21 +278,35 @@ export function editDistance(a, b) {
270
278
  return d[m][n];
271
279
  }
272
280
  /**
273
- * The nearest match of `name` among `candidates` (§1.5): case-insensitive; a
274
- * candidate qualifies when its distance is ≤ 2, or when one of the two is a
275
- * substring of the other and the shorter has at least three characters; the
276
- * lowest distance wins, ties broken by the shorter candidate, then
277
- * alphabetically. Null when nothing qualifies.
281
+ * The nearest match of `name` among `candidates` (§1.5). First, the one hint
282
+ * the name rule makes certain: a candidate that is exactly `name` with the
283
+ * engine prefix added or removed (`id` → `_id`, `_layer` → `layer`) wins
284
+ * outright, whatever its length. Otherwise case-insensitive and **prefix-blind**
285
+ * — the `_` is a declaration, not spelling, so `commited` is one edit from
286
+ * `_commit` and `titel` one from `_title` as from `title`: a candidate
287
+ * qualifies when its distance is ≤ 2, or when one of the two is a substring of
288
+ * the other and the shorter has at least three characters; the lowest distance
289
+ * wins, ties broken by the shorter candidate (so the bare authored `title`
290
+ * beats `_title` when both are one edit away), then alphabetically. Null when
291
+ * nothing qualifies.
278
292
  */
279
293
  export function nearest(name, candidates) {
280
- const needle = name.toLowerCase();
294
+ const lower = name.toLowerCase();
295
+ const prefixed = `_${lower}`;
296
+ const unprefixed = lower.startsWith("_") ? lower.slice(1) : null;
297
+ for (const c of candidates) {
298
+ const hay = c.toLowerCase();
299
+ if (hay === prefixed || (unprefixed !== null && hay === unprefixed))
300
+ return c;
301
+ }
302
+ const needle = lower.replace(/^_/, "");
281
303
  // A name under three characters is within distance 2 of nearly anything, so
282
304
  // only a substring match (which needs three characters anyway) could qualify.
283
305
  if (needle.length < 3)
284
306
  return null;
285
307
  let best = null;
286
308
  for (const c of new Set(candidates)) {
287
- const hay = c.toLowerCase();
309
+ const hay = c.toLowerCase().replace(/^_/, "");
288
310
  if (hay === needle)
289
311
  continue;
290
312
  const d = editDistance(needle, hay);
@@ -294,9 +316,9 @@ export function nearest(name, candidates) {
294
316
  if (!(d <= 2 || sub))
295
317
  continue;
296
318
  // One typo (distance 1) wins first; then a substring match outranks a
297
- // distance-2 match (`via` → `via_id`, not `id`; `dst_pth` → `dst_path`, not
298
- // `dst`); within a class the lowest distance, then the shorter candidate,
299
- // then alphabetical.
319
+ // distance-2 match (`_via` → `_via_id`, not `_id`; `_dst_pth` → `_dst_path`,
320
+ // not `_dst`); within a class the lowest distance, then the shorter
321
+ // candidate, then alphabetical.
300
322
  const rank = (x) => (x.d === 1 ? 0 : x.sub ? 1 : 2);
301
323
  const better = best === null ||
302
324
  rank({ d, sub }) < rank(best) ||
@@ -310,26 +332,39 @@ function suggestion(name, candidates) {
310
332
  const c = nearest(name, candidates);
311
333
  return c === null ? "" : `; did you mean \`${c}\`?`;
312
334
  }
313
- /** The error of a closed kind: `` `target` is not a field of links (fields: id, path, …); did you mean `…`? ``. */
314
- export function fieldErrorMessage(store, repoId, target, name) {
315
- return `\`${name}\` is not a field of ${target} (fields: ${[...ATTRIBUTES[target]].join(", ")})${suggestion(name, candidates(store, repoId, target))}`;
335
+ /** The candidates of a bare name: the kind's engine names (so `title` finds `_title`) and its authored keys. */
336
+ function candidates(store, repoId, target) {
337
+ const out = engineNames(target);
338
+ if (!CLOSED.has(target))
339
+ out.push(...vocabularyOf(store, repoId, target).list);
340
+ return out;
341
+ }
342
+ /** The error of an unknown engine name: `` `_target` is not an engine name of links (engine names: _id, _path, …); did you mean `_dst_path`? ``
343
+ * — the nearest match drawn from the engine names and the authored keys, so a `_layer` written for the authored `layer` is pointed at it. */
344
+ export function engineNameErrorMessage(store, repoId, target, name) {
345
+ return `\`${name}\` is not an engine name of ${target} (engine names: ${engineNames(target).join(", ")})${suggestion(name, candidates(store, repoId, target))}`;
346
+ }
347
+ /** The error of a bare name on a closed kind: `` `target` reads an authored property, and a link carries none (engine names: _id, _path, …); did you mean `_dst_path`? ``. */
348
+ export function closedKindErrorMessage(target, name, handle) {
349
+ const shown = handle !== undefined ? ` (\`${handle}.${name}\`)` : "";
350
+ return `\`${name}\`${shown} reads an authored property, and a ${NOUN[target]} carries none (engine names: ${engineNames(target).join(", ")})${suggestion(name, engineNames(target))}`;
316
351
  }
317
- /** The hint of an open kind: `` `preview` is not a field of blocks and no block in this repository carries a property by that name; did you mean `…`? ``.
318
- * Under a source handle the name is an authored lookup by construction, so the
319
- * hint says only that: `` no document in this repository carries a property `path` (`data.path`); did you mean `…`? ``,
320
- * the nearest match drawn from the authored keys alone. */
352
+ /** The hint of an open kind: `` no block in this repository carries a property `preview`; did you mean `_text`? ``.
353
+ * Under a source handle the name is shown with it — `` no document in this repository carries a property `x` (`_data.x`); did you mean `y`? `` —
354
+ * and the nearest match is drawn from the authored keys alone. */
321
355
  export function propertyHintMessage(store, repoId, target, name, handle) {
322
356
  if (handle !== undefined) {
323
357
  return `no ${NOUN[target]} in this repository carries a property \`${name}\` (\`${handle}.${name}\`)${suggestion(name, vocabularyOf(store, repoId, target).list)}`;
324
358
  }
325
- return `\`${name}\` is not a field of ${target} and no ${NOUN[target]} in this repository carries a property by that name${suggestion(name, candidates(store, repoId, target))}`;
359
+ return `no ${NOUN[target]} in this repository carries a property \`${name}\`${suggestion(name, candidates(store, repoId, target))}`;
326
360
  }
327
361
  // ---- the pass --------------------------------------------------------------
328
362
  /**
329
- * Judge every name the query reads. Returns the hints (one per offending name
330
- * of an open kind, first-reference order, deduplicated) under `"hint"`; throws
331
- * `FilterInvalid` for a tier-3 name on a closed kind in either mode, and for
332
- * one on an open kind under `"refuse"`.
363
+ * Judge every name the query reads. Returns the hints (one per unknown bare
364
+ * name of an open kind, first-reference order, deduplicated) under `"hint"`;
365
+ * throws `FilterInvalid` for an unknown engine name (`_x`) and for a bare name
366
+ * on a closed kind in either mode, and for an unknown bare name on an open kind
367
+ * under `"refuse"`.
333
368
  */
334
369
  export function checkNames(store, repoId, raw, mode) {
335
370
  const hints = [];
@@ -343,8 +378,10 @@ export function checkNames(store, repoId, raw, mode) {
343
378
  const verdict = judge(store, repoId, kind, ref.name.split("."));
344
379
  if (verdict === null)
345
380
  continue;
346
- if (verdict.closed)
347
- throw new FilterInvalid(fieldErrorMessage(store, repoId, verdict.target, verdict.name), "OQX");
381
+ if (verdict.why === "engine")
382
+ throw new FilterInvalid(engineNameErrorMessage(store, repoId, verdict.target, verdict.name), "OQX");
383
+ if (verdict.why === "closed")
384
+ throw new FilterInvalid(closedKindErrorMessage(verdict.target, verdict.name, verdict.handle), "OQX");
348
385
  const message = propertyHintMessage(store, repoId, verdict.target, verdict.name, verdict.handle);
349
386
  if (mode === "refuse")
350
387
  throw new FilterInvalid(message, "OQX");
@@ -12,15 +12,11 @@ export declare function rootTarget(source: Expr): Target | null;
12
12
  * • any `call` node (a function or method: unknown name, bad regex, wrong target…)
13
13
  * • any nested block whose consumer is `single` (raises on 0 or >1 rows)
14
14
  * • any `^`-escaped name: an `outer` reference or a `^name:` lift
15
- * • a bare reserved docs basename (`id path updated_at hash raw`): as
16
- * an `ident` at the ROOT scope when the target is `docs`, or as `doc.<name>`
17
- * on any target at any depth (the reach-through row is a doc)
18
15
  * • a bare root name (`docs blocks nodes links revisions`, root-row.ts) read
19
- * as a property: as an `ident` at the ROOT scope when it is not a relation
20
- * of the target's rows (`blocks` on blocks, `nodes` on nodes, any on links),
21
- * as an `ident` at any depth below (the rows there are unknown), or as
22
- * `<head>.<name>` unless the head is a reach-through whose row has that
23
- * relation (`doc.nodes`, `doc.blocks`, `block.nodes`, `section.blocks`)
16
+ * as a property: as an `ident` at any depth (since 5.0 a bare root name is
17
+ * never a relation — the relations are `_blocks`, `_nodes`, `_revisions`),
18
+ * or as `<head>.<name>` (`_doc.blocks`, `_block.nodes`: the loud error of a
19
+ * bare target name on the reached row)
24
20
  *
25
21
  * A residual made only of comparisons, logical operators, `in`/ranges, `!`,
26
22
  * literals, bindings and plain reads (including exists/none/count/collect/first
@@ -39,15 +39,11 @@ export function rootTarget(source) {
39
39
  * • any `call` node (a function or method: unknown name, bad regex, wrong target…)
40
40
  * • any nested block whose consumer is `single` (raises on 0 or >1 rows)
41
41
  * • any `^`-escaped name: an `outer` reference or a `^name:` lift
42
- * • a bare reserved docs basename (`id path updated_at hash raw`): as
43
- * an `ident` at the ROOT scope when the target is `docs`, or as `doc.<name>`
44
- * on any target at any depth (the reach-through row is a doc)
45
42
  * • a bare root name (`docs blocks nodes links revisions`, root-row.ts) read
46
- * as a property: as an `ident` at the ROOT scope when it is not a relation
47
- * of the target's rows (`blocks` on blocks, `nodes` on nodes, any on links),
48
- * as an `ident` at any depth below (the rows there are unknown), or as
49
- * `<head>.<name>` unless the head is a reach-through whose row has that
50
- * relation (`doc.nodes`, `doc.blocks`, `block.nodes`, `section.blocks`)
43
+ * as a property: as an `ident` at any depth (since 5.0 a bare root name is
44
+ * never a relation — the relations are `_blocks`, `_nodes`, `_revisions`),
45
+ * or as `<head>.<name>` (`_doc.blocks`, `_block.nodes`: the loud error of a
46
+ * bare target name on the reached row)
51
47
  *
52
48
  * A residual made only of comparisons, logical operators, `in`/ranges, `!`,
53
49
  * literals, bindings and plain reads (including exists/none/count/collect/first
@@ -70,8 +66,8 @@ function opMayRaise(op, target, root) {
70
66
  return true;
71
67
  return exprMayRaise(op.receiver, target, root) || subMayRaise(op.sub, target);
72
68
  }
73
- // Inside a block the rows are a different scope (a relation's rows), so a bare
74
- // reserved name is an ordinary read there — only `doc.<reserved>` still raises.
69
+ // Inside a block the rows are a different scope (a relation's rows); `root` is
70
+ // false there, so a bare root name raises whatever the target.
75
71
  function subMayRaise(s, target) {
76
72
  if (s.from.some((e) => exprMayRaise(e, target, false)))
77
73
  return true;
@@ -10,12 +10,13 @@ export declare const TARGETS: ReadonlySet<string>;
10
10
  /** The root collections, in the root object's entry order (§1.1). */
11
11
  export declare const ROOT_NAMES: readonly Root[];
12
12
  export declare const ROOTS: ReadonlySet<string>;
13
- /** The root names that are a relation of each kind's rows (§1.2): `revisions`
14
- * on a document is its revision list (4.7). */
13
+ /** The root names, as relations of each kind's rows (§1.2, `_`-prefixed since
14
+ * 5.0): `_revisions` on a document is its revision list (4.7). A BARE root name
15
+ * on a row is never a relation (it is the loud error below). */
15
16
  export declare const TARGET_RELATIONS: Readonly<Record<Root, ReadonlySet<string>>>;
16
- /** The reach-through heads and the target of the row they reach (`doc.x`, `block.x`, `section.x`, and a link's `dst.x`). */
17
+ /** The reach-through heads and the target of the row they reach (`_doc.x`, `_block.x`, and a link's `_dst.x`; spec/surface §1.2, 5.0 — the self aliases are gone). */
17
18
  export declare const REACH_THROUGH: Readonly<Record<string, Target>>;
18
- export declare const REPO_REMOVED_MESSAGE = "`$repo` was removed in surface 2.0 \u2014 reach the repository's collections through the root row: `^docs` from a top-level row (one caret per enclosing block, or the absolute `0^docs`), and the repository id as `^id` / `0^id`";
19
+ export declare const REPO_REMOVED_MESSAGE = "`$repo` was removed in surface 2.0 \u2014 reach the repository's collections through the root row: `^docs` from a top-level row (one caret per enclosing block, or the absolute `0^docs`), and the repository id as `^_id` / `0^_id`";
19
20
  /**
20
21
  * The bare-target error. `depth` is the scope depth the name was read at when
21
22
  * known statically (the carets in the hint count it); `null` when raised at read
@@ -35,5 +36,5 @@ export declare function pastRootMessage(name: string, levels: number): string;
35
36
  * target's name before 3.0, gets its new name spelled out.
36
37
  */
37
38
  export declare function unknownTargetMessage(name: string): string;
38
- /** Whether `name`, read as `<head>.<name>` or bare on a row of `target`, is one of that row's relations named after a target. */
39
+ /** Whether `name`, read as `<head>.<name>` or bare on a row of `target`, is one of that row's relations named after a target (`_blocks`, `_nodes`, `_revisions`). */
39
40
  export declare function isTargetRelationOf(target: Root | undefined, name: string): boolean;
@@ -1,6 +1,6 @@
1
1
  // The root row (spec/surface §1.1, 2.0; spec/oqx 0.18). The repository is OQX's
2
2
  // root row: from a top-level row `^docs` reaches the documents (one caret per
3
- // enclosing block, or the absolute `0^docs`), `^id` the repository id, and the
3
+ // enclosing block, or the absolute `0^docs`), `^_id` the repository id, and the
4
4
  // root object (`^$it`) exposes the five collections (`revisions` since 4.7). Two spellings are refused
5
5
  // as `filter_invalid`, with the messages below, in both engines:
6
6
  //
@@ -10,12 +10,10 @@
10
10
  // `checkRootSpellings`);
11
11
  // • a bare target name at scope depth ≥ 1 (inside a block, or in a top-level
12
12
  // row's clauses), where a bare name reads a property of the current row,
13
- // which has none: `docs` and `links` are no row's relation, so they are
14
- // refused statically by the same walk; `blocks`, `nodes` and `revisions`
15
- // ARE relations of some rows (`docs.blocks`, `docs.nodes`, `blocks.nodes`,
16
- // `section.blocks`, `docs.revisions`), so the context raises the same error
17
- // at read time on a row that lacks the relation (context.ts). A frontmatter key named after a
18
- // target is unreachable by bare name (use `frontmatter.<k>`).
13
+ // which has none — refused statically for every root name since 5.0 (the
14
+ // relations are `_blocks`, `_nodes`, `_revisions`: a bare `blocks` is never
15
+ // one), and by the context at read time should one reach it (context.ts). A frontmatter key named after a
16
+ // target is unreachable by bare name (use `_frontmatter.<k>`).
19
17
  /** The four row kinds whose names the unknown-names pass judges (names.ts keys
20
18
  * its vocabulary tables by them); `revisions` joins when that pass learns the
21
19
  * kind. Every OTHER root-collection test uses `ROOTS`. */
@@ -23,19 +21,20 @@ export const TARGETS = new Set(["docs", "blocks", "nodes", "links"]);
23
21
  /** The root collections, in the root object's entry order (§1.1). */
24
22
  export const ROOT_NAMES = ["docs", "blocks", "nodes", "links", "revisions"];
25
23
  export const ROOTS = new Set(ROOT_NAMES);
26
- /** The root names that are a relation of each kind's rows (§1.2): `revisions`
27
- * on a document is its revision list (4.7). */
24
+ /** The root names, as relations of each kind's rows (§1.2, `_`-prefixed since
25
+ * 5.0): `_revisions` on a document is its revision list (4.7). A BARE root name
26
+ * on a row is never a relation (it is the loud error below). */
28
27
  export const TARGET_RELATIONS = {
29
- docs: new Set(["nodes", "blocks", "revisions"]),
30
- blocks: new Set(["nodes"]),
31
- nodes: new Set(["blocks"]),
28
+ docs: new Set(["_nodes", "_blocks", "_revisions"]),
29
+ blocks: new Set(["_nodes"]),
30
+ nodes: new Set(["_blocks"]),
32
31
  links: new Set(),
33
32
  revisions: new Set(),
34
33
  };
35
- /** The reach-through heads and the target of the row they reach (`doc.x`, `block.x`, `section.x`, and a link's `dst.x`). */
36
- export const REACH_THROUGH = { doc: "docs", block: "blocks", section: "nodes", dst: "docs" };
34
+ /** The reach-through heads and the target of the row they reach (`_doc.x`, `_block.x`, and a link's `_dst.x`; spec/surface §1.2, 5.0 — the self aliases are gone). */
35
+ export const REACH_THROUGH = { _doc: "docs", _block: "blocks", _dst: "docs" };
37
36
  const NOUN = { docs: "documents", blocks: "blocks", nodes: "nodes", links: "links", revisions: "revisions" };
38
- export const REPO_REMOVED_MESSAGE = "`$repo` was removed in surface 2.0 — reach the repository's collections through the root row: `^docs` from a top-level row (one caret per enclosing block, or the absolute `0^docs`), and the repository id as `^id` / `0^id`";
37
+ export const REPO_REMOVED_MESSAGE = "`$repo` was removed in surface 2.0 — reach the repository's collections through the root row: `^docs` from a top-level row (one caret per enclosing block, or the absolute `0^docs`), and the repository id as `^_id` / `0^_id`";
39
38
  /**
40
39
  * The bare-target error. `depth` is the scope depth the name was read at when
41
40
  * known statically (the carets in the hint count it); `null` when raised at read
@@ -66,7 +65,7 @@ export function unknownTargetMessage(name) {
66
65
  const was = name === "edges" ? " (`edges` was renamed `links` in surface 3.0)" : "";
67
66
  return `\`${name}\` is not a target — the repository's collections are ${ROOT_NAMES.map((r) => `\`${r}\``).join(", ")}${was}`;
68
67
  }
69
- /** Whether `name`, read as `<head>.<name>` or bare on a row of `target`, is one of that row's relations named after a target. */
68
+ /** Whether `name`, read as `<head>.<name>` or bare on a row of `target`, is one of that row's relations named after a target (`_blocks`, `_nodes`, `_revisions`). */
70
69
  export function isTargetRelationOf(target, name) {
71
70
  return target !== undefined && TARGET_RELATIONS[target].has(name);
72
71
  }
@@ -8,20 +8,20 @@ export type EmbedQuery = (text: string) => Promise<{
8
8
  model: string;
9
9
  vec: Float32Array;
10
10
  }>;
11
- /** A query hit (spec/surface §1.4, 3.0): the row's identity under the SAME
12
- * bare names the expression language reads — `id`, `path` — plus whatever
11
+ /** A query hit (spec/surface §1.4, 5.0): the row's identity under the SAME
12
+ * engine names the expression language reads — `_id`, `_path` — plus whatever
13
13
  * `select` projected. A key copied from a hit is valid in the next `where` with
14
- * no translation (`where id == "<hit.id>"`); a `select id, path` is simply the
15
- * envelope, and any other projection whose output key would be `id` or `path`
16
- * must be aliased (`envelopeCollision`). */
14
+ * no translation (`where _id == "<hit._id>"`); a `select _id, _path` is simply
15
+ * the envelope, and any other projection whose output key would be `_id` or
16
+ * `_path` must be aliased (`envelopeCollision`). */
17
17
  export interface OqxHit {
18
- id: string;
19
- path: string;
18
+ _id: string;
19
+ _path: string;
20
20
  [k: string]: unknown;
21
21
  }
22
22
  export interface OqxResult {
23
- /** `{ id, path, ...projection }` per hit — or, under `OqxOptions.bare`, the
24
- * projection alone (`id`/`path` present only when the query projected them;
23
+ /** `{ _id, _path, ...projection }` per hit — or, under `OqxOptions.bare`, the
24
+ * projection alone (`_id`/`_path` present only when the query projected them;
25
25
  * typed as `OqxHit` for the one hit shape every consumer handles, a bare
26
26
  * caller knows it asked). */
27
27
  hits: OqxHit[];
@@ -47,16 +47,17 @@ export interface OqxResult {
47
47
  export interface OqxOptions {
48
48
  limit?: number;
49
49
  cursor?: string | null;
50
- /** Return each hit as its PROJECTION only — no automatic `id`/`path`
51
- * members (spec/surface §1.4, 4.2): `select title, era from docs` yields
52
- * `{ title, era }` rows. Needs a `select` (without a projection a hit is
50
+ /** Return each hit as its PROJECTION only — no automatic `_id`/`_path`
51
+ * members (spec/surface §1.4, 4.2): `select _title, era from docs` yields
52
+ * `{ _title, era }` rows. Needs a `select` (without a projection a hit is
53
53
  * only its envelope) and is refused with `values` (already the bare-values
54
54
  * form); both are `filter_invalid` before the engine runs. A projected
55
- * `id`/`path` is the intrinsic as everywhere (`select id` → `{ id }`), and
56
- * with no envelope there is nothing to collide with (`select path: layer`
57
- * is fine). Paging, `distinct`, `truncated` and `cursor` are unchanged —
58
- * the runner keeps identity internally and strips the envelope last. The
59
- * scalar consumers (`count`/`exists`/`none`) have no hits and ignore it. */
55
+ * `_id`/`_path` is the attribute as everywhere (`select _id` → `{ _id }`),
56
+ * and with no envelope there is nothing to collide with (`select _path:
57
+ * layer` is fine). Paging, `distinct`, `truncated` and `cursor` are
58
+ * unchanged — the runner keeps identity internally and strips the envelope
59
+ * last. The scalar consumers (`count`/`exists`/`none`) have no hits and
60
+ * ignore it. */
60
61
  bare?: boolean;
61
62
  semanticVectors?: Map<string, SemanticVec>;
62
63
  /** Force the pure in-memory engine (skip tier-3 pushdown). Internal — used by