@omgbase/oqx 0.18.0 → 0.21.0

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 (57) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +177 -64
  3. package/dist/ast.d.ts +28 -5
  4. package/dist/ast.d.ts.map +1 -1
  5. package/dist/ast.js +1 -1
  6. package/dist/ast.js.map +1 -1
  7. package/dist/build.d.ts +11 -2
  8. package/dist/build.d.ts.map +1 -1
  9. package/dist/build.js +10 -3
  10. package/dist/build.js.map +1 -1
  11. package/dist/context.d.ts +19 -5
  12. package/dist/context.d.ts.map +1 -1
  13. package/dist/context.js +6 -9
  14. package/dist/context.js.map +1 -1
  15. package/dist/engine.d.ts +12 -1
  16. package/dist/engine.d.ts.map +1 -1
  17. package/dist/engine.js +263 -84
  18. package/dist/engine.js.map +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +2 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/lexer.d.ts +3 -3
  23. package/dist/lexer.d.ts.map +1 -1
  24. package/dist/lexer.js +79 -2
  25. package/dist/lexer.js.map +1 -1
  26. package/dist/optimize/analysis.d.ts +3 -2
  27. package/dist/optimize/analysis.d.ts.map +1 -1
  28. package/dist/optimize/analysis.js +18 -15
  29. package/dist/optimize/analysis.js.map +1 -1
  30. package/dist/parser.d.ts.map +1 -1
  31. package/dist/parser.js +250 -118
  32. package/dist/parser.js.map +1 -1
  33. package/dist/print.d.ts.map +1 -1
  34. package/dist/print.js +47 -29
  35. package/dist/print.js.map +1 -1
  36. package/dist/resolve.d.ts.map +1 -1
  37. package/dist/resolve.js +15 -8
  38. package/dist/resolve.js.map +1 -1
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. package/dist/walk.d.ts +5 -3
  42. package/dist/walk.d.ts.map +1 -1
  43. package/dist/walk.js +34 -11
  44. package/dist/walk.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/ast.ts +33 -6
  47. package/src/build.ts +12 -8
  48. package/src/context.ts +22 -11
  49. package/src/engine.ts +258 -91
  50. package/src/index.ts +2 -1
  51. package/src/lexer.ts +76 -6
  52. package/src/optimize/analysis.ts +17 -12
  53. package/src/parser.ts +227 -101
  54. package/src/print.ts +44 -16
  55. package/src/resolve.ts +15 -7
  56. package/src/version.ts +1 -1
  57. package/src/walk.ts +39 -14
package/dist/walk.d.ts CHANGED
@@ -3,12 +3,14 @@ import type { AstKind, AstNode, Expr, Query, QueryJson, Subquery } from "./ast.j
3
3
  * root collection (`from <source>` or the directive receiver); `from` the further
4
4
  * re-projections; `follow` the `Follow` node itself and `follow.*` its parts.
5
5
  * The root node has no clause (`null`). */
6
- export type Clause = "source" | "from" | "where" | "select" | "orderBy" | "limit" | "offset" | "follow" | "follow.destination" | "follow.where" | "follow.frontier" | "follow.by";
6
+ export type Clause = "source" | "from" | "where" | "select" | "orderBy" | "limit" | "offset" | "follow" | "follow.destination" | "follow.frontier";
7
7
  /** Where a visited node sits. `depth` is the scope depth at which the node is
8
8
  * evaluated (0 = the root scope: a top-level `source`, `limit`, `offset`; 1 =
9
9
  * a top-level row: its `where`, `select`, `order by`, `from` re-projections and
10
- * `follow`; a block's rows are one deeper than the block's receiver; a follow
11
- * `where` is one deeper than the frontier row it tests successors of). */
10
+ * `follow`; a block's rows are one deeper than the block's receiver — a follow
11
+ * destination block's `where` therefore one deeper than the frontier row —
12
+ * and a destination block's `frontier` one level out of its rows, under the
13
+ * walk's enclosing scope). */
12
14
  export interface VisitContext {
13
15
  /** The ancestors of the node, root first (empty for the root). */
14
16
  readonly path: readonly AstNode[];
@@ -1 +1 @@
1
- {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAInF;;;2CAG2C;AAC3C,MAAM,MAAM,MAAM,GACd,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GACvE,QAAQ,GAAG,oBAAoB,GAAG,cAAc,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAEvF;;;;0EAI0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB;iDAC6C;IAC7C,KAAK,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,GAAG,IAAI,CAAC;IACzD,sCAAsC;IACtC,KAAK,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI,CAAC;CAChD;AAED;oEACoE;AACpE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;6DAC6D;AAC7D,eAAO,MAAM,QAAQ,EAAE;IAAE,QAAQ,EAAE,CAAC,IAAI,OAAO,GAAG,SAAS,QAAQ,EAAE;CA8CpE,CAAC;AAQF,yDAAyD;AACzD,wBAAgB,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAoB3D;AAED;;;4EAG4E;AAC5E,wBAAgB,SAAS,CAAC,CAAC,SAAS,KAAK,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,YAAY,KAAK,IAAI,GAAG,CAAC,CAuB5G;AAED;4EAC4E;AAC5E,MAAM,MAAM,QAAQ,CAAC,CAAC,IACpB,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,GAC5C,CAAC,SAAS,MAAM,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,MAAM,GAAG,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACrF,CAAC,CAAC;AAEN,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAWlD;AAED;;gDAEgD;AAChD,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAE9C"}
1
+ {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAInF;;;2CAG2C;AAC3C,MAAM,MAAM,MAAM,GACd,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GACvE,QAAQ,GAAG,oBAAoB,GAAG,iBAAiB,CAAC;AAExD;;;;;;8BAM8B;AAC9B,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB;iDAC6C;IAC7C,KAAK,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,GAAG,IAAI,CAAC;IACzD,sCAAsC;IACtC,KAAK,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI,CAAC;CAChD;AAED;oEACoE;AACpE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;6DAC6D;AAC7D,eAAO,MAAM,QAAQ,EAAE;IAAE,QAAQ,EAAE,CAAC,IAAI,OAAO,GAAG,SAAS,QAAQ,EAAE;CAmDpE,CAAC;AA0BF,yDAAyD;AACzD,wBAAgB,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAoB3D;AAED;;;4EAG4E;AAC5E,wBAAgB,SAAS,CAAC,CAAC,SAAS,KAAK,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,YAAY,KAAK,IAAI,GAAG,CAAC,CAuB5G;AAED;4EAC4E;AAC5E,MAAM,MAAM,QAAQ,CAAC,CAAC,IACpB,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,GAC5C,CAAC,SAAS,MAAM,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,MAAM,GAAG,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACrF,CAAC,CAAC;AAEN,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAWlD;AAED;;gDAEgD;AAChD,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAE9C"}
package/dist/walk.js CHANGED
@@ -13,6 +13,8 @@ export const CHILDREN = {
13
13
  { key: "source", clause: "source" },
14
14
  { key: "from", clause: "from", depth: 1 },
15
15
  { key: "where", clause: "where", depth: 1 },
16
+ // The body's `frontier` (0.20) reads each seed in the row scope, like `where`.
17
+ { key: "frontier", clause: "follow.frontier", depth: 1 },
16
18
  { key: "follow", clause: "follow", depth: 1 },
17
19
  { key: "orderBy", clause: "orderBy", depth: 1 },
18
20
  { key: "limit", clause: "limit" },
@@ -22,18 +24,18 @@ export const CHILDREN = {
22
24
  { key: "select", clause: "select" },
23
25
  { key: "from", clause: "from" },
24
26
  { key: "where", clause: "where" },
27
+ // A destination block's `frontier` reads the occurrence under the walk's
28
+ // enclosing scope — one level OUT of the block's rows (SEMANTICS §20); any
29
+ // other block's reads its seeds in the row scope, like `where`
30
+ // (`childDepth` tells the two apart by the block's position).
31
+ { key: "frontier", clause: "follow.frontier", depth: -1 },
25
32
  { key: "follow", clause: "follow" },
26
33
  { key: "orderBy", clause: "orderBy" },
27
34
  { key: "limit", clause: "limit" },
28
35
  { key: "offset", clause: "offset" },
29
36
  ],
30
37
  op: [{ key: "receiver" }, { key: "sub", depth: 1 }],
31
- follow: [
32
- { key: "destinations", clause: "follow.destination" },
33
- { key: "where", clause: "follow.where", depth: 1 },
34
- { key: "frontier", clause: "follow.frontier" },
35
- { key: "by", clause: "follow.by" },
36
- ],
38
+ follow: [{ key: "destinations", clause: "follow.destination" }],
37
39
  order: [{ key: "expr" }],
38
40
  field: [{ key: "expr" }],
39
41
  collect: [{ key: "op" }],
@@ -53,10 +55,28 @@ export const CHILDREN = {
53
55
  logical: [{ key: "left" }, { key: "right" }],
54
56
  in: [{ key: "left" }, { key: "right" }],
55
57
  range: [{ key: "lo" }, { key: "hi" }],
58
+ tuple: [{ key: "items" }],
59
+ // The interpolations; the text parts between them are strings, not nodes.
60
+ template: [{ key: "parts" }],
56
61
  };
57
62
  function slot(node, key) {
58
63
  return node[key] ?? null;
59
64
  }
65
+ /** A block is a follow destination block when its `op` is a direct destination
66
+ * of a `follow` node. `path` is the ancestors with the block itself last. */
67
+ function isDestinationBlock(path) {
68
+ const op = path[path.length - 2];
69
+ const owner = path[path.length - 3];
70
+ return op?.kind === "op" && owner?.kind === "follow";
71
+ }
72
+ /** How much deeper a child's scope is: the table's value, except a block's
73
+ * `frontier`, which is one level OUT only for a destination block (the walk's
74
+ * enclosing scope) and the row scope itself for a body with its own `follow`. */
75
+ function childDepth(node, child, path) {
76
+ if (node.kind === "subquery" && child.key === "frontier" && !isDestinationBlock(path))
77
+ return 0;
78
+ return child.depth ?? 0;
79
+ }
60
80
  /** Walk a tree depth-first in canonical source order. */
61
81
  export function visit(root, visitor) {
62
82
  const path = [];
@@ -70,10 +90,12 @@ export function visit(root, visitor) {
70
90
  if (v === null)
71
91
  continue;
72
92
  const c = child.clause ?? clause;
73
- const d = depth + (child.depth ?? 0);
74
- if (Array.isArray(v))
93
+ const d = depth + childDepth(node, child, path);
94
+ if (Array.isArray(v)) {
75
95
  for (const x of v)
76
- go(x, c, d);
96
+ if (typeof x !== "string")
97
+ go(x, c, d);
98
+ }
77
99
  else
78
100
  go(v, c, d);
79
101
  }
@@ -98,11 +120,12 @@ export function transform(root, f) {
98
120
  if (v === null)
99
121
  continue;
100
122
  const c = child.clause ?? clause;
101
- const d = depth + (child.depth ?? 0);
123
+ const d = depth + childDepth(node, child, path);
102
124
  let next;
103
125
  if (Array.isArray(v)) {
104
126
  let changed = false;
105
- const mapped = v.map((x) => { const y = go(x, c, d); if (y !== x)
127
+ const mapped = v.map((x) => { if (typeof x === "string")
128
+ return x; const y = go(x, c, d); if (y !== x)
106
129
  changed = true; return y; });
107
130
  next = changed ? mapped : v;
108
131
  }
package/dist/walk.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"walk.js","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"walk.js","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omgbase/oqx",
3
- "version": "0.18.0",
3
+ "version": "0.21.0",
4
4
  "description": "OQX (omgbase Query eXpressions): a composable query language and engine for JavaScript collections",
5
5
  "keywords": [
6
6
  "query",
package/src/ast.ts CHANGED
@@ -35,6 +35,10 @@ export type UnaryOp = "!" | "-";
35
35
  /** The operator of a `logical` expression. */
36
36
  export type LogicalOp = "&&" | "||";
37
37
 
38
+ /** One part of a template literal (since 0.21): a decoded text run, or an
39
+ * interpolated expression. The strings are not nodes (no `kind`, no span). */
40
+ export type TemplatePart = string | Expr;
41
+
38
42
  /** Scalar value/predicate expression, evaluated against a row scope + bindings. */
39
43
  export type Expr =
40
44
  | { kind: "lit"; span: Span; value: string | number | boolean | null }
@@ -53,6 +57,17 @@ export type Expr =
53
57
  // (`..5` / `5..`); `exclusiveEnd` distinguishes `1...5` from `1..5`. Evaluates
54
58
  // to a runtime range value (see semantics.makeRange); primarily the RHS of `in`.
55
59
  | { kind: "range"; span: Span; lo: Expr | null; hi: Expr | null; exclusiveEnd: boolean }
60
+ // A tuple `(e1, e2, …)` (since 0.20): two or more expressions whose values
61
+ // concatenate with one level of flattening (SEMANTICS §3b) — a value and a
62
+ // receiver (`(a, b) { … }`, `follow (a, b) { depth 2 }`). One expression in
63
+ // parentheses is grouping and leaves no node.
64
+ | { kind: "tuple"; span: Span; items: Expr[] }
65
+ // A template literal `` `text ${expr} text` `` (since 0.21): `parts` alternates
66
+ // strictly — text, interpolation, text, … — so it always has an odd length and
67
+ // begins and ends with a (possibly empty) text part; the text is decoded (the
68
+ // GRAMMAR §1 escapes resolved). Evaluates to the concatenation of the text and
69
+ // the interpolated values' string forms (SEMANTICS §7b).
70
+ | { kind: "template"; span: Span; parts: TemplatePart[] }
56
71
  // A value-position directive (since 0.17): `recv first { … }.pay`, and what a
57
72
  // bracket lookup `x[…]` desugars to. `isExpr` is false for it — an op is an
58
73
  // expression only by position; discriminate on `kind === "op"`.
@@ -104,16 +119,13 @@ export interface OpNode {
104
119
  * per frontier row, inside which `^` is that row. Discriminate on `kind === "op"`. */
105
120
  export type FollowDestination = Expr | OpNode;
106
121
 
107
- /** The recursive `follow` clause. */
122
+ /** The recursive `follow` clause (since 0.20 just the destinations: a
123
+ * destination's `where`, `frontier` and `depth` live in its own block). */
108
124
  export interface Follow {
109
125
  kind: "follow";
110
126
  span: Span;
111
127
  destinations: FollowDestination[]; // ≥ 1, in source order; the walk is their union
112
128
  distinct: boolean;
113
- where: Expr | null; // successor predicate: which successors keep participating
114
- frontier: Expr | null; // boundary predicate: cut a relation that could continue
115
- depth: number | null; // 1..8 cap; null = the hard cap
116
- by: Expr | null; // identity expression for cycle detection / dedup
117
129
  }
118
130
 
119
131
  /** A nested (sub)query body — the `{ … }` of a directive. */
@@ -125,6 +137,16 @@ export interface Subquery {
125
137
  select: SelectItem[];
126
138
  orderBy: OrderSpec[] | null;
127
139
  follow: Follow | null;
140
+ /** `frontier P` / `depth n` (since 0.20). In a `follow` destination block
141
+ * `frontier` marks the occurrences reached through this destination for
142
+ * which `P` holds (not expanded; `P` read one level out, under the walk's
143
+ * enclosing scope), and `depth n` makes the destination expand a frontier
144
+ * row only while that row's `$depth < n` (1..8; `null` = the hard cap 8).
145
+ * In any other block `frontier` is legal only with a `follow` and marks the
146
+ * walk's SEEDS, read in the seed's scope like `where` (as on `Query`);
147
+ * `depth` is destination-only. Both `null` when absent. */
148
+ frontier: Expr | null;
149
+ depth: number | null;
128
150
  /** `values` — scalar projection mode: the (single) projected expression is the
129
151
  * row's result itself rather than being wrapped in a `{ name: value }` record,
130
152
  * so `name values` yields `["Bob", …]` and `$it values` yields the rows. */
@@ -159,6 +181,11 @@ export interface Query {
159
181
  orderBy: OrderSpec[] | null;
160
182
  consumer: Consumer;
161
183
  follow: Follow | null;
184
+ /** `frontier P` (since 0.20) — legal only when the body has a `follow`: a
185
+ * seed for which `P` holds (read in the seed's own scope, `^` the enclosing
186
+ * scope, like `where`) is a frontier (`$stop == "frontier"`, not expanded).
187
+ * `null` without one. */
188
+ frontier: Expr | null;
162
189
  /** `distinct` — dedup the result rows by their projected value (see OpNode). */
163
190
  distinct: boolean;
164
191
  /** `values` — scalar projection mode (see Subquery). */
@@ -176,7 +203,7 @@ export type AstKind = AstNode["kind"];
176
203
 
177
204
  /** The expression kinds (`Expr["kind"]`), for `isExpr`. */
178
205
  const EXPR_KINDS: ReadonlySet<string> = new Set([
179
- "lit", "ident", "outer", "binding", "member", "call", "unary", "required", "binary", "logical", "in", "range",
206
+ "lit", "ident", "outer", "binding", "member", "call", "unary", "required", "binary", "logical", "in", "range", "tuple", "template",
180
207
  ]);
181
208
 
182
209
  /** Whether a node is a scalar `Expr` (an `op` in expression position is not: it
package/src/build.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  import type {
7
7
  BinaryOp, Consumer, CountCmp, Expr, Follow, FollowDestination, LogicalOp, OpNode, OrderSpec, Query, SelectItem, Span,
8
- Subquery, UnaryOp, Where,
8
+ Subquery, TemplatePart, UnaryOp, Where,
9
9
  } from "./ast.ts";
10
10
 
11
11
  /** A fresh empty span (never shared: spans are mutable tuples). */
@@ -26,6 +26,11 @@ export const binary = (op: BinaryOp, left: Expr, right: Expr): Expr => ({ kind:
26
26
  export const logical = (op: LogicalOp, left: Expr, right: Expr): Expr => ({ kind: "logical", span: span(), op, left, right });
27
27
  export const inOp = (left: Expr, right: Expr): Expr => ({ kind: "in", span: span(), left, right });
28
28
  export const range = (lo: Expr | null, hi: Expr | null, exclusiveEnd = false): Expr => ({ kind: "range", span: span(), lo, hi, exclusiveEnd });
29
+ /** `(e1, e2, …)` — a tuple of two or more expressions (SEMANTICS §3b). */
30
+ export const tuple = (items: Expr[]): Expr => ({ kind: "tuple", span: span(), items });
31
+ /** `` `text ${expr} …` `` — a template literal (SEMANTICS §7b); `parts` alternates
32
+ * text, expression, text, … (odd length, text first and last). */
33
+ export const template = (parts: TemplatePart[]): Expr => ({ kind: "template", span: span(), parts });
29
34
 
30
35
  export const scalar = (expr: Expr): Where => ({ kind: "scalar", span: span(), expr });
31
36
  export const and = (parts: Where[]): Where => ({ kind: "and", span: span(), parts });
@@ -40,7 +45,7 @@ export const order = (expr: Expr, desc = false): OrderSpec => ({ kind: "order",
40
45
  export function subquery(parts: Partial<Omit<Subquery, "kind" | "span">> = {}): Subquery {
41
46
  return {
42
47
  kind: "subquery", span: span(),
43
- from: [], where: null, select: [], orderBy: null, follow: null, values: false, limit: null, offset: null,
48
+ from: [], where: null, select: [], orderBy: null, follow: null, frontier: null, depth: null, values: false, limit: null, offset: null,
44
49
  ...parts,
45
50
  };
46
51
  }
@@ -53,11 +58,10 @@ export function op(
53
58
  return { kind: "op", span: span(), receiver, op: consumer, sub, countCmp: opts.countCmp ?? null, distinct: opts.distinct ?? false };
54
59
  }
55
60
 
56
- export function follow(
57
- destinations: FollowDestination[],
58
- opts: Partial<Omit<Follow, "kind" | "span" | "destinations">> = {},
59
- ): Follow {
60
- return { kind: "follow", span: span(), destinations, distinct: false, where: null, frontier: null, depth: null, by: null, ...opts };
61
+ /** `follow [distinct] <destinations>`; a destination's `where`/`frontier`/`depth`
62
+ * belong to its block (`op(recv, "collect", subquery({ depth: 2 }))`). */
63
+ export function follow(destinations: FollowDestination[], opts: { distinct?: boolean } = {}): Follow {
64
+ return { kind: "follow", span: span(), destinations, distinct: opts.distinct ?? false };
61
65
  }
62
66
 
63
67
  /** A top-level query over `source`; every other clause defaults to empty/absent
@@ -65,7 +69,7 @@ export function follow(
65
69
  export function query(source: Expr, parts: Partial<Omit<Query, "kind" | "span" | "source">> = {}): Query {
66
70
  return {
67
71
  kind: "query", span: span(), source,
68
- from: [], where: null, select: [], orderBy: null, consumer: "collect", follow: null,
72
+ from: [], where: null, select: [], orderBy: null, consumer: "collect", follow: null, frontier: null,
69
73
  distinct: false, values: false, limit: null, offset: null,
70
74
  ...parts,
71
75
  };
package/src/context.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  // The tier-2 seam: a DataContext binds OQX's query semantics to a concrete data
2
2
  // model. The engine never touches host objects directly — it asks the context to
3
3
  // resolve named roots, read properties/relations, coerce a relation result into
4
- // rows, compute identity (for `follow` dedup), and optionally supply custom
5
- // scalar functions/methods. This is what lets the same query semantics run over
4
+ // rows (and say which objects are one row rather than a collection), name a
5
+ // row's identity (`$key`: `follow` dedup, `distinct`, `is`), and optionally
6
+ // supply custom scalar functions/methods. This is what lets the same query semantics run over
6
7
  // plain objects, a lazy ORM graph, or a remote API without changing the engine.
7
8
  //
8
9
  // (Performant execution over a real store is the tier-3 seam — see planner.ts —
@@ -45,6 +46,13 @@ export interface DataContext {
45
46
  get(row: unknown, key: string): unknown;
46
47
  /** Coerce a relation/source value into rows (may be lazy). */
47
48
  toRows(value: unknown): Iterable<unknown>;
49
+ /** Optional: whether an object this context handed out in row position is
50
+ * ONE row — a record, not a collection (SEMANTICS §3, since 0.20) — so a block
51
+ * over it yields one result rather than a one-element list (`doc { title }`
52
+ * is a record; `docs { title }` a list). Asked only for non-array objects;
53
+ * absent, any non-iterable object is one row and any iterable (a `Set`, a
54
+ * lazy handle with `Symbol.iterator`) is a collection. */
55
+ isSingle?(value: unknown): boolean;
48
56
  /** Optional: a value this context handed out as a stand-in for a collection
49
57
  * it has not read yet (a lazy table handle), resolved to what it stands for.
50
58
  * The engine calls it on every value it is about to observe AS A VALUE — an
@@ -57,8 +65,15 @@ export interface DataContext {
57
65
  * seen by the language. Absent = identity; a context whose handle already
58
66
  * behaves as the collection (a `Proxy` over an array) needs none. */
59
67
  materialize?(value: unknown): unknown;
60
- /** Identity of a row for `follow` cycle detection / dedup. */
61
- identity(row: unknown): unknown;
68
+ /** Optional: the identity a row is known by — `$key` (SEMANTICS §16, since
69
+ * 0.20): what `follow` cycle detection, the per-step union, `follow distinct`,
70
+ * `distinct` over unprojected rows and `is` compare, and what reading `$key`
71
+ * on the row returns. Absent, a row's identity is its structural value (an
72
+ * entry's is its key either way). A store-backed context answers its row id. */
73
+ identityOf?(row: unknown): unknown;
74
+ /** @deprecated The pre-0.20 spelling of `identityOf`; read when `identityOf`
75
+ * is absent. Removed in the next minor. */
76
+ identity?(row: unknown): unknown;
62
77
  /** Optional custom free function; return `{ handled: false }` to defer. */
63
78
  callFunction?(name: string, args: unknown[]): CallResult;
64
79
  /** Optional custom method; return `{ handled: false }` to defer. */
@@ -92,8 +107,9 @@ export interface DefaultContextOptions {
92
107
  /** The default context: ordinary JavaScript objects. Named roots come from a
93
108
  * plain `{ name: collection }` map, which is also the root object (`^$it` from
94
109
  * a top-level row); properties are OWN keys only (an array exposes only its
95
- * integer indices, a primitive has none); identity is `.id` when present, else
96
- * the row itself (the engine keys it structurally). */
110
+ * integer indices, a primitive has none); it names no identity of its own, so a
111
+ * row's `$key` is its structural value (SEMANTICS §16) unless a query projects
112
+ * one (`$key: id`). */
97
113
  export class DefaultContext implements DataContext {
98
114
  private roots: Record<string, unknown>;
99
115
  readonly regexDialect: RegexDialect;
@@ -121,11 +137,6 @@ export class DefaultContext implements DataContext {
121
137
  return coerceCollection(value);
122
138
  }
123
139
 
124
- identity(row: unknown): unknown {
125
- if (row != null && typeof row === "object" && "id" in row) return (row as Record<string, unknown>).id;
126
- return row;
127
- }
128
-
129
140
  callFunction(name: string, args: unknown[]): CallResult {
130
141
  const fn = Object.hasOwn(BUILTIN_FUNCTIONS, name) ? BUILTIN_FUNCTIONS[name] : undefined;
131
142
  return fn ? { handled: true, value: fn(args) } : { handled: false };