@omgbase/oqx 0.1.0 → 0.3.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 (51) hide show
  1. package/README.md +23 -15
  2. package/dist/adapters/indexed.d.ts +11 -0
  3. package/dist/adapters/indexed.d.ts.map +1 -0
  4. package/dist/adapters/indexed.js +57 -0
  5. package/dist/adapters/indexed.js.map +1 -0
  6. package/dist/adapters/sqlite.d.ts +23 -0
  7. package/dist/adapters/sqlite.d.ts.map +1 -0
  8. package/dist/adapters/sqlite.js +113 -0
  9. package/dist/adapters/sqlite.js.map +1 -0
  10. package/dist/ast.d.ts +121 -0
  11. package/dist/ast.d.ts.map +1 -0
  12. package/dist/ast.js +7 -0
  13. package/dist/ast.js.map +1 -0
  14. package/dist/context.d.ts +38 -0
  15. package/dist/context.d.ts.map +1 -0
  16. package/dist/context.js +47 -0
  17. package/dist/context.js.map +1 -0
  18. package/dist/engine.d.ts +49 -0
  19. package/dist/engine.d.ts.map +1 -0
  20. package/dist/engine.js +437 -0
  21. package/dist/engine.js.map +1 -0
  22. package/dist/errors.d.ts +6 -0
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +13 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/index.d.ts +34 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +74 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/lexer.d.ts +17 -0
  31. package/dist/lexer.d.ts.map +1 -0
  32. package/dist/lexer.js +187 -0
  33. package/dist/lexer.js.map +1 -0
  34. package/dist/parser.d.ts +6 -0
  35. package/dist/parser.d.ts.map +1 -0
  36. package/dist/parser.js +584 -0
  37. package/dist/parser.js.map +1 -0
  38. package/dist/plan.d.ts +21 -0
  39. package/dist/plan.d.ts.map +1 -0
  40. package/dist/plan.js +56 -0
  41. package/dist/plan.js.map +1 -0
  42. package/dist/planner.d.ts +24 -0
  43. package/dist/planner.d.ts.map +1 -0
  44. package/dist/planner.js +27 -0
  45. package/dist/planner.js.map +1 -0
  46. package/dist/semantics.d.ts +22 -0
  47. package/dist/semantics.d.ts.map +1 -0
  48. package/dist/semantics.js +142 -0
  49. package/dist/semantics.js.map +1 -0
  50. package/package.json +16 -6
  51. package/src/engine.ts +78 -26
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # oqx
1
+ # @omgbase/oqx
2
2
 
3
3
  **Generic Object Query eXpression engine for JavaScript.**
4
4
 
@@ -9,7 +9,7 @@ the OQX language semantics separated from any particular data model, exposed as
9
9
  JavaScript tagged template.
10
10
 
11
11
  ```js
12
- import { oqx } from "oqx";
12
+ import { oqx } from "@omgbase/oqx";
13
13
 
14
14
  const people = [
15
15
  { name: "Bob", id: 124, title: "Engineer",
@@ -301,21 +301,29 @@ oqx`id, depth: $depth from ${tree} follow children order by $depth, id`;
301
301
  ```
302
302
 
303
303
  Reached rows expose recursion **intrinsics** in `select` / `order by`:
304
- `$depth` (1-based), `$leaf` (no successors), `$frontier` (hit a boundary), and
305
- `$stop` (`"continue"` / `"leaf"` / `"depth"` / `"frontier"`). Options go in a
306
- trailing block:
304
+ `$depth` (1-based), `$leaf` (no successors), `$frontier` (there is unfollowed
305
+ graph beyond a boundary or the depth cap), `$ordinal` (a deterministic 1..N
306
+ rank over the walk, ordered by depth then path), and `$stop`
307
+ (`"interior"` / `"leaf"` / `"frontier"` / `"depth"` / `"cycle"`, precedence
308
+ cycle > frontier > depth > leaf > interior — only `interior` rows expand). Options
309
+ go in a trailing block:
307
310
 
308
311
  ```js
309
- oqx`id, stop: $stop from ${tree} follow children { depth 2 } order by id`;
312
+ oqx`id, stop: $stop from ${tree} follow children { depth 2 } order by $ordinal`;
310
313
  // a1 is never reached; a and b report stop:"depth"
311
314
  ```
312
315
 
316
+ The walk is **per-path**: a node reached by N distinct paths yields N
317
+ occurrences, and revisiting an identity already on the current path is admitted
318
+ **once** as `$stop == "cycle"` and never re-expanded, so cycles terminate without
319
+ runaway. `follow distinct` collapses occurrences to reached nodes (the minimal
320
+ `(depth, path)` per identity).
321
+
313
322
  The block accepts: `where <succ>` (which successors keep participating),
314
323
  `frontier <pred>` (cut a relation that could continue), `depth <n>` (1–8), and
315
- `by <expr>` (the identity used for cycle/duplicate detection — default `.id` or the
316
- object reference). Reached rows are de-duplicated by that identity, so cycles
317
- terminate. Give `follow` a stable identity (`.id` or `by`) when your relation
318
- returns fresh objects rather than shared references.
324
+ `by <expr>` (the identity used for cycle detection + `distinct` — default `.id`
325
+ or the object reference). Give `follow` a stable identity (`.id` or `by`) when
326
+ your relation returns fresh objects rather than shared references.
319
327
 
320
328
  ### Cheat-sheet
321
329
 
@@ -336,7 +344,7 @@ When you don't need interpolation, `execute` runs a plain string query against a
336
344
  **data context** of named roots:
337
345
 
338
346
  ```js
339
- import { execute } from "oqx";
347
+ import { execute } from "@omgbase/oqx";
340
348
 
341
349
  execute("name from people where age >= 18", { people });
342
350
  // `from people` resolves the `people` root
@@ -370,7 +378,7 @@ compute identity. Implement it to query an ORM graph, a remote API, or lazily
370
378
  loaded relations — the query *semantics* stay in OQX:
371
379
 
372
380
  ```js
373
- import { parse, run } from "oqx";
381
+ import { parse, run } from "@omgbase/oqx";
374
382
 
375
383
  const graph = {
376
384
  root: (name) => name === "tree" ? [nodes.get(1)] : undefined,
@@ -391,14 +399,14 @@ likes and stay correct. Two adapters ship:
391
399
 
392
400
  - `IndexedCollection` — hash-indexes a collection and answers equality predicates
393
401
  from the index instead of scanning, leaving other predicates as residual.
394
- - `oqx/sqlite` — real pushdown to a `node:sqlite` database: the flat query core
402
+ - `@omgbase/oqx/sqlite` — real pushdown to a `node:sqlite` database: the flat query core
395
403
  (scan + translatable conjunctive predicates, `LIMIT` for unordered
396
404
  `first`/`single`) becomes SQL; `matches()`, nested consumer ops, `follow`, etc.
397
405
  fall back to the in-memory residual.
398
406
 
399
407
  ```js
400
- import { parse, PlannedEngine } from "oqx";
401
- import { SqliteTable } from "oqx/sqlite";
408
+ import { parse, PlannedEngine } from "@omgbase/oqx";
409
+ import { SqliteTable } from "@omgbase/oqx/sqlite";
402
410
 
403
411
  const planner = new SqliteTable(db, "emp", { columns: ["id", "name", "dept", "level"] });
404
412
  new PlannedEngine(planner).run(parse('name from emp where dept == "eng" && level >= 5'), []);
@@ -0,0 +1,11 @@
1
+ import type { Query } from "../ast.js";
2
+ import type { Plan, QueryPlanner } from "../planner.js";
3
+ export declare class IndexedCollection implements QueryPlanner {
4
+ private name;
5
+ private rows;
6
+ private indexes;
7
+ /** Index `rows` (exposed as root `name`) on each field in `indexFields`. */
8
+ constructor(name: string, rows: readonly unknown[], indexFields: readonly string[]);
9
+ plan(query: Query, params: readonly unknown[]): Plan | null;
10
+ }
11
+ //# sourceMappingURL=indexed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexed.d.ts","sourceRoot":"","sources":["../../src/adapters/indexed.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxD,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,OAAO,CAA8C;IAE7D,4EAA4E;gBAChE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,MAAM,EAAE;IAelF,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,GAAG,IAAI;CAoB5D"}
@@ -0,0 +1,57 @@
1
+ // An in-memory optimizing planner: it hash-indexes a named root collection on
2
+ // chosen fields and, for a query whose where-clause contains equality predicates
3
+ // on those fields, answers from the index (candidate intersection) instead of a
4
+ // full scan. Everything it can't turn into an index probe is left as a residual
5
+ // query the in-memory engine finishes over the candidate rows.
6
+ //
7
+ // This is the smallest honest demonstration of plan optimization: same answers
8
+ // as a naive scan, far fewer rows examined, and partial-pushdown correctness via
9
+ // the residual.
10
+ import { partitionPushable, residualQuery, asEquality, constValue } from "../plan.js";
11
+ export class IndexedCollection {
12
+ name;
13
+ rows;
14
+ indexes = new Map();
15
+ /** Index `rows` (exposed as root `name`) on each field in `indexFields`. */
16
+ constructor(name, rows, indexFields) {
17
+ this.name = name;
18
+ this.rows = rows;
19
+ for (const field of indexFields) {
20
+ const idx = new Map();
21
+ for (const row of rows) {
22
+ const key = row?.[field];
23
+ let bucket = idx.get(key);
24
+ if (!bucket)
25
+ idx.set(key, (bucket = []));
26
+ bucket.push(row);
27
+ }
28
+ this.indexes.set(field, idx);
29
+ }
30
+ }
31
+ plan(query, params) {
32
+ if (query.source.kind !== "ident" || query.source.name !== this.name)
33
+ return null;
34
+ if (query.from.length > 0 || query.follow)
35
+ return null;
36
+ const { pushed, residual } = partitionPushable(query.where, (e) => {
37
+ const eq = asEquality(e);
38
+ return eq != null && this.indexes.has(eq.field);
39
+ });
40
+ if (pushed.length === 0)
41
+ return null; // no index probe available — let the scan handle it
42
+ // Intersect the candidate sets from each indexed equality (smallest first).
43
+ let candidate = null;
44
+ for (const e of pushed) {
45
+ const eq = asEquality(e);
46
+ const bucket = this.indexes.get(eq.field).get(constValue(eq.value, params)) ?? [];
47
+ candidate = candidate === null ? bucket.slice() : intersect(candidate, bucket);
48
+ }
49
+ const rows = candidate ?? [];
50
+ return { rows: () => rows, residual: residualQuery(query, residual) };
51
+ }
52
+ }
53
+ function intersect(a, b) {
54
+ const set = new Set(b);
55
+ return a.filter((x) => set.has(x));
56
+ }
57
+ //# sourceMappingURL=indexed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexed.js","sourceRoot":"","sources":["../../src/adapters/indexed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import type { Query } from "../ast.js";
3
+ import type { Plan, QueryPlanner } from "../planner.js";
4
+ export interface SqliteTableOptions {
5
+ /** Columns that map to bare OQX fields (only these are pushable). */
6
+ columns: readonly string[];
7
+ /** Columns whose stored text should be JSON.parse'd back into row values. */
8
+ jsonColumns?: readonly string[];
9
+ /** Custom mapper from a raw SQL row to a query row (overrides jsonColumns). */
10
+ map?: (raw: Record<string, unknown>) => unknown;
11
+ }
12
+ export declare class SqliteTable implements QueryPlanner {
13
+ private db;
14
+ private table;
15
+ private columns;
16
+ private opts;
17
+ constructor(db: DatabaseSync, table: string, opts: SqliteTableOptions);
18
+ plan(query: Query, params: readonly unknown[]): Plan | null;
19
+ private translatable;
20
+ private translate;
21
+ private mapRow;
22
+ }
23
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAQ,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxD,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,+EAA+E;IAC/E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;CACjD;AAOD,qBAAa,WAAY,YAAW,YAAY;IAC9C,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,IAAI,CAAqB;gBAErB,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB;IAOrE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,GAAG,IAAI;IAsB3D,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,MAAM;CAWf"}
@@ -0,0 +1,113 @@
1
+ // A real storage adapter: pushes the flat core of an OQX query (source scan +
2
+ // translatable conjunctive predicates, and a LIMIT for unordered first/single)
3
+ // into SQL over Node's built-in `node:sqlite`, and leaves everything it can't
4
+ // translate — nested consumer ops, `follow`, negation/disjunction, method calls
5
+ // like matches(), custom functions — as a residual the in-memory engine finishes
6
+ // over the rows SQL returned.
7
+ //
8
+ // This is imported on its own subpath (`oqx/sqlite`) so that plain consumers of
9
+ // `oqx` never pull in the experimental `node:sqlite` module.
10
+ //
11
+ // Semantics note: OQX equality is typed and strict (see semantics.ts). SQL `=`
12
+ // uses column affinity, so this adapter assumes a well-typed schema for the
13
+ // columns it pushes; anything it cannot translate faithfully stays residual.
14
+ import { DatabaseSync } from "node:sqlite";
15
+ import { partitionPushable, residualQuery } from "../plan.js";
16
+ const RELOP_SQL = {
17
+ "==": "=", "!=": "<>", "<": "<", "<=": "<=", ">": ">", ">=": ">=",
18
+ };
19
+ const ARITH_SQL = new Set(["+", "-", "*", "/", "%"]);
20
+ export class SqliteTable {
21
+ db;
22
+ table;
23
+ columns;
24
+ opts;
25
+ constructor(db, table, opts) {
26
+ this.db = db;
27
+ this.table = table;
28
+ this.columns = new Set(opts.columns);
29
+ this.opts = opts;
30
+ }
31
+ plan(query, params) {
32
+ if (query.source.kind !== "ident" || query.source.name !== this.table)
33
+ return null;
34
+ if (query.from.length > 0 || query.follow)
35
+ return null;
36
+ const { pushed, residual } = partitionPushable(query.where, (e) => this.translatable(e));
37
+ const sqlParams = [];
38
+ const whereSql = pushed.map((e) => this.translate(e, params, sqlParams)).join(" AND ");
39
+ // A LIMIT is only safe when nothing is left to filter in-memory and the
40
+ // result is unordered (first/single are "some row" without an order by).
41
+ let tail = "";
42
+ if (!residual && !query.orderBy) {
43
+ if (query.consumer === "first")
44
+ tail = " LIMIT 1";
45
+ else if (query.consumer === "single")
46
+ tail = " LIMIT 2";
47
+ }
48
+ const sql = `SELECT * FROM "${this.table}"${whereSql ? ` WHERE ${whereSql}` : ""}${tail}`;
49
+ const raw = this.db.prepare(sql).all(...sqlParams.map(toSqlParam));
50
+ const rows = raw.map((r) => this.mapRow(r));
51
+ return { rows: () => rows, residual: residualQuery(query, residual) };
52
+ }
53
+ translatable(e) {
54
+ switch (e.kind) {
55
+ case "lit":
56
+ case "binding": return true;
57
+ case "ident": return this.columns.has(e.name);
58
+ case "unary": return (e.op === "!" || e.op === "-") && this.translatable(e.expr);
59
+ case "logical": return this.translatable(e.left) && this.translatable(e.right);
60
+ case "binary":
61
+ return (e.op in RELOP_SQL || ARITH_SQL.has(e.op)) && this.translatable(e.left) && this.translatable(e.right);
62
+ default: return false; // member/index/call/in → residual
63
+ }
64
+ }
65
+ translate(e, params, out) {
66
+ switch (e.kind) {
67
+ case "lit":
68
+ out.push(e.value);
69
+ return "?";
70
+ case "binding":
71
+ out.push(params[e.index]);
72
+ return "?";
73
+ case "ident": return `"${e.name}"`;
74
+ case "unary": return e.op === "!" ? `(NOT ${this.translate(e.expr, params, out)})` : `(-${this.translate(e.expr, params, out)})`;
75
+ case "logical": {
76
+ const op = e.op === "&&" ? "AND" : "OR";
77
+ return `(${this.translate(e.left, params, out)} ${op} ${this.translate(e.right, params, out)})`;
78
+ }
79
+ case "binary": {
80
+ const op = e.op in RELOP_SQL ? RELOP_SQL[e.op] : e.op;
81
+ return `(${this.translate(e.left, params, out)} ${op} ${this.translate(e.right, params, out)})`;
82
+ }
83
+ default: throw new Error(`sqlite: not translatable: ${e.kind}`);
84
+ }
85
+ }
86
+ mapRow(raw) {
87
+ if (this.opts.map)
88
+ return this.opts.map(raw);
89
+ if (this.opts.jsonColumns) {
90
+ const out = { ...raw };
91
+ for (const c of this.opts.jsonColumns) {
92
+ if (typeof out[c] === "string")
93
+ out[c] = JSON.parse(out[c]);
94
+ }
95
+ return out;
96
+ }
97
+ return raw;
98
+ }
99
+ }
100
+ // node:sqlite accepts null | number | bigint | string | Uint8Array. Map the
101
+ // common host values that appear as bound params.
102
+ function toSqlParam(v) {
103
+ if (v === undefined || v === null)
104
+ return null;
105
+ if (typeof v === "boolean")
106
+ return v ? 1 : 0;
107
+ if (typeof v === "number" || typeof v === "bigint" || typeof v === "string")
108
+ return v;
109
+ if (v instanceof Uint8Array)
110
+ return v;
111
+ return String(v);
112
+ }
113
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,+EAA+E;AAC/E,8EAA8E;AAC9E,gFAAgF;AAChF,iFAAiF;AACjF,8BAA8B;AAC9B,EAAE;AACF,gFAAgF;AAChF,6DAA6D;AAC7D,EAAE;AACF,+EAA+E;AAC/E,4EAA4E;AAC5E,6EAA6E;AAE7E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAW9D,MAAM,SAAS,GAA2B;IACxC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI;CAClE,CAAC;AACF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAErD,MAAM,OAAO,WAAW;IACd,EAAE,CAAe;IACjB,KAAK,CAAS;IACd,OAAO,CAAc;IACrB,IAAI,CAAqB;IAEjC,YAAY,EAAgB,EAAE,KAAa,EAAE,IAAwB;QACnE,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,IAAI,CAAC,KAAY,EAAE,MAA0B;QAC3C,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACnF,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAEvD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;QACzF,MAAM,SAAS,GAAc,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEvF,wEAAwE;QACxE,yEAAyE;QACzE,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YAChC,IAAI,KAAK,CAAC,QAAQ,KAAK,OAAO;gBAAE,IAAI,GAAG,UAAU,CAAC;iBAC7C,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ;gBAAE,IAAI,GAAG,UAAU,CAAC;QAC1D,CAAC;QAED,MAAM,GAAG,GAAG,kBAAkB,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,UAAU,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC;QAC1F,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAA8B,CAAC;QAChG,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;IACxE,CAAC;IAEO,YAAY,CAAC,CAAO;QAC1B,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;YACf,KAAK,KAAK,CAAC;YAAC,KAAK,SAAS,CAAC,CAAC,OAAO,IAAI,CAAC;YACxC,KAAK,OAAO,CAAC,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC9C,KAAK,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACjF,KAAK,SAAS,CAAC,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC/E,KAAK,QAAQ;gBACX,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,SAAS,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC/G,OAAO,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,kCAAkC;QAC3D,CAAC;IACH,CAAC;IAEO,SAAS,CAAC,CAAO,EAAE,MAA0B,EAAE,GAAc;QACnE,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;YACf,KAAK,KAAK;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;gBAAC,OAAO,GAAG,CAAC;YAC1C,KAAK,SAAS;gBAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;gBAAC,OAAO,GAAG,CAAC;YACtD,KAAK,OAAO,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC;YACnC,KAAK,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC;YACjI,KAAK,SAAS,CAAC,CAAC,CAAC;gBACf,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;gBACxC,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC;YAClG,CAAC;YACD,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvD,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC;YAClG,CAAC;YACD,OAAO,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,GAA4B;QACzC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1B,MAAM,GAAG,GAA4B,EAAE,GAAG,GAAG,EAAE,CAAC;YAChD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;gBACtC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ;oBAAE,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAW,CAAC,CAAC;YACxE,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF;AAED,4EAA4E;AAC5E,kDAAkD;AAClD,SAAS,UAAU,CAAC,CAAU;IAC5B,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACtF,IAAI,CAAC,YAAY,UAAU;QAAE,OAAO,CAAC,CAAC;IACtC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC"}
package/dist/ast.d.ts ADDED
@@ -0,0 +1,121 @@
1
+ /** Query consumers — how a (sub)query's row set is shaped. */
2
+ export type Consumer = "collect" | "exists" | "count" | "first" | "single";
3
+ /** Comparison operators usable in a `count { … } <op> <int>` test. */
4
+ export type RelOp = "==" | "!=" | "<" | "<=" | ">" | ">=";
5
+ /** Scalar value/predicate expression, evaluated against a row scope + bindings. */
6
+ export type Expr = {
7
+ kind: "lit";
8
+ value: string | number | boolean | null;
9
+ } | {
10
+ kind: "ident";
11
+ name: string;
12
+ } | {
13
+ kind: "outer";
14
+ levels: number;
15
+ name: string;
16
+ } | {
17
+ kind: "binding";
18
+ index: number;
19
+ } | {
20
+ kind: "member";
21
+ recv: Expr;
22
+ name: string;
23
+ } | {
24
+ kind: "index";
25
+ recv: Expr;
26
+ index: Expr;
27
+ } | {
28
+ kind: "call";
29
+ recv: Expr | null;
30
+ name: string;
31
+ args: Expr[];
32
+ } | {
33
+ kind: "unary";
34
+ op: "!" | "-";
35
+ expr: Expr;
36
+ } | {
37
+ kind: "binary";
38
+ op: string;
39
+ left: Expr;
40
+ right: Expr;
41
+ } | {
42
+ kind: "logical";
43
+ op: "&&" | "||";
44
+ left: Expr;
45
+ right: Expr;
46
+ } | {
47
+ kind: "in";
48
+ left: Expr;
49
+ right: Expr;
50
+ };
51
+ export interface OrderSpec {
52
+ expr: Expr;
53
+ desc: boolean;
54
+ }
55
+ /** One projection item: a named scalar/navigation value, or a named nested
56
+ * collection consumer. `lift` marks a `^name` one-scope lift. */
57
+ export type SelectItem = {
58
+ kind: "field";
59
+ name: string;
60
+ expr: Expr;
61
+ lift: number;
62
+ } | {
63
+ kind: "collect";
64
+ name: string;
65
+ op: OpNode;
66
+ };
67
+ /** A postfix consumer directive over a receiver collection:
68
+ * `<receiver> <op> { <sub> }`, optionally `count { … } <relop> <int>`. */
69
+ export interface OpNode {
70
+ kind: "op";
71
+ receiver: Expr;
72
+ op: Consumer;
73
+ sub: Subquery;
74
+ countCmp?: {
75
+ op: RelOp;
76
+ value: number;
77
+ };
78
+ }
79
+ /** The recursive `follow` clause. */
80
+ export interface Follow {
81
+ receiver: Expr;
82
+ distinct: boolean;
83
+ where: Expr | null;
84
+ frontier: Expr | null;
85
+ depth: number | null;
86
+ by: Expr | null;
87
+ }
88
+ /** A nested (sub)query body. */
89
+ export interface Subquery {
90
+ from: Expr[];
91
+ where: Where | null;
92
+ select: SelectItem[];
93
+ orderBy: OrderSpec[] | null;
94
+ follow: Follow | null;
95
+ }
96
+ /** The where-clause boolean tree: OQX owns &&/||/!/grouping so consumer ops
97
+ * (invisible to the scalar evaluator) compose with scalar predicates. */
98
+ export type Where = {
99
+ kind: "and";
100
+ parts: Where[];
101
+ } | {
102
+ kind: "or";
103
+ parts: Where[];
104
+ } | {
105
+ kind: "not";
106
+ expr: Where;
107
+ } | {
108
+ kind: "scalar";
109
+ expr: Expr;
110
+ } | OpNode;
111
+ /** A top-level OQX query. */
112
+ export interface Query {
113
+ source: Expr;
114
+ from: Expr[];
115
+ where: Where | null;
116
+ select: SelectItem[];
117
+ orderBy: OrderSpec[] | null;
118
+ consumer: Consumer;
119
+ follow: Follow | null;
120
+ }
121
+ //# sourceMappingURL=ast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"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"}
package/dist/ast.js ADDED
@@ -0,0 +1,7 @@
1
+ // OQX query AST for the generic kernel. The parser produces this directly and
2
+ // the interpreter walks it — there is no separate lowering/IR pass, because a
3
+ // "relation" in the generic kernel is just an expression evaluated against the
4
+ // current row (property navigation on the host object model), resolved at run
5
+ // time rather than against a fixed schema.
6
+ export {};
7
+ //# sourceMappingURL=ast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.js","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,8EAA8E;AAC9E,+EAA+E;AAC/E,8EAA8E;AAC9E,2CAA2C"}
@@ -0,0 +1,38 @@
1
+ /** Result of a context-provided function/method call: `handled: false` tells the
2
+ * engine to fall back to the builtin table (or error if none). */
3
+ export interface CallResult {
4
+ handled: boolean;
5
+ value?: unknown;
6
+ }
7
+ export interface DataContext {
8
+ /** Resolve a named root collection (the `from <name>` / directive receiver). */
9
+ root(name: string): unknown;
10
+ /** Read a property/relation off a row (`.field`, or a nav-expression segment). */
11
+ get(row: unknown, key: string): unknown;
12
+ /** Whether a row has a property — decides scope-climbing for a bare identifier
13
+ * (a present-but-undefined field stops the climb). Defaults to a key check. */
14
+ has?(row: unknown, key: string): boolean;
15
+ /** Coerce a relation/source value into rows (may be lazy). */
16
+ toRows(value: unknown): Iterable<unknown>;
17
+ /** Identity of a row for `follow` cycle detection / dedup. */
18
+ identity(row: unknown): unknown;
19
+ /** Optional custom free function; return `{ handled: false }` to defer. */
20
+ callFunction?(name: string, args: unknown[]): CallResult;
21
+ /** Optional custom method; return `{ handled: false }` to defer. */
22
+ callMethod?(name: string, recv: unknown, args: unknown[]): CallResult;
23
+ }
24
+ /** The default context: ordinary JavaScript objects. Named roots come from a
25
+ * plain `{ name: collection }` map; properties are own/inherited keys; identity
26
+ * is `.id` when present, else the object itself (reference identity). */
27
+ export declare class DefaultContext implements DataContext {
28
+ private roots;
29
+ constructor(roots?: Record<string, unknown>);
30
+ root(name: string): unknown;
31
+ get(row: unknown, key: string): unknown;
32
+ has(row: unknown, key: string): boolean;
33
+ toRows(value: unknown): Iterable<unknown>;
34
+ identity(row: unknown): unknown;
35
+ callFunction(name: string, args: unknown[]): CallResult;
36
+ callMethod(name: string, recv: unknown, args: unknown[]): CallResult;
37
+ }
38
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAYA;kEACkE;AAClE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,gFAAgF;IAChF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,kFAAkF;IAClF,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC;mFAC+E;IAC/E,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACzC,8DAA8D;IAC9D,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC1C,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;IAChC,2EAA2E;IAC3E,YAAY,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IACzD,oEAAoE;IACpE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;CACvE;AAED;;yEAEyE;AACzE,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,KAAK,CAA0B;gBAE3B,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;IAI/C,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI3B,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAKvC,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAIvC,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;IAIzC,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO;IAK/B,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU;IAKvD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU;CAIrE"}
@@ -0,0 +1,47 @@
1
+ // The tier-2 seam: a DataContext binds OQX's query semantics to a concrete data
2
+ // model. The engine never touches host objects directly — it asks the context to
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
6
+ // plain objects, a lazy ORM graph, or a remote API without changing the engine.
7
+ //
8
+ // (Performant execution over a real store is the tier-3 seam — see planner.ts —
9
+ // which pushes work into the store instead of driving it row-by-row here.)
10
+ import { coerceCollection, BUILTIN_FUNCTIONS, BUILTIN_METHODS } from "./semantics.js";
11
+ /** The default context: ordinary JavaScript objects. Named roots come from a
12
+ * plain `{ name: collection }` map; properties are own/inherited keys; identity
13
+ * is `.id` when present, else the object itself (reference identity). */
14
+ export class DefaultContext {
15
+ roots;
16
+ constructor(roots = {}) {
17
+ this.roots = roots;
18
+ }
19
+ root(name) {
20
+ return this.roots[name];
21
+ }
22
+ get(row, key) {
23
+ if (row == null)
24
+ return undefined;
25
+ return Object(row)[key];
26
+ }
27
+ has(row, key) {
28
+ return row != null && typeof row === "object" && key in row;
29
+ }
30
+ toRows(value) {
31
+ return coerceCollection(value);
32
+ }
33
+ identity(row) {
34
+ if (row != null && typeof row === "object" && "id" in row)
35
+ return row.id;
36
+ return row;
37
+ }
38
+ callFunction(name, args) {
39
+ const fn = BUILTIN_FUNCTIONS[name];
40
+ return fn ? { handled: true, value: fn(args) } : { handled: false };
41
+ }
42
+ callMethod(name, recv, args) {
43
+ const fn = BUILTIN_METHODS[name];
44
+ return fn ? { handled: true, value: fn(recv, args) } : { handled: false };
45
+ }
46
+ }
47
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,iFAAiF;AACjF,gFAAgF;AAChF,4EAA4E;AAC5E,gFAAgF;AAChF,gFAAgF;AAChF,EAAE;AACF,gFAAgF;AAChF,2EAA2E;AAE3E,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AA2BtF;;yEAEyE;AACzE,MAAM,OAAO,cAAc;IACjB,KAAK,CAA0B;IAEvC,YAAY,QAAiC,EAAE;QAC7C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,IAAI,CAAC,IAAY;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,GAAG,CAAC,GAAY,EAAE,GAAW;QAC3B,IAAI,GAAG,IAAI,IAAI;YAAE,OAAO,SAAS,CAAC;QAClC,OAAQ,MAAM,CAAC,GAAG,CAA6B,CAAC,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,GAAG,CAAC,GAAY,EAAE,GAAW;QAC3B,OAAO,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,IAAK,GAAc,CAAC;IAC1E,CAAC;IAED,MAAM,CAAC,KAAc;QACnB,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,QAAQ,CAAC,GAAY;QACnB,IAAI,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,IAAI,IAAI,GAAG;YAAE,OAAQ,GAA+B,CAAC,EAAE,CAAC;QACtG,OAAO,GAAG,CAAC;IACb,CAAC;IAED,YAAY,CAAC,IAAY,EAAE,IAAe;QACxC,MAAM,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACtE,CAAC;IAED,UAAU,CAAC,IAAY,EAAE,IAAa,EAAE,IAAe;QACrD,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5E,CAAC;CACF"}
@@ -0,0 +1,49 @@
1
+ import type { Query } from "./ast.js";
2
+ import type { DataContext } from "./context.js";
3
+ /** The shaped result of a top-level query, discriminated by consumer. */
4
+ export type OqxResult = {
5
+ consumer: "collect";
6
+ rows: unknown[];
7
+ } | {
8
+ consumer: "exists";
9
+ exists: boolean;
10
+ } | {
11
+ consumer: "count";
12
+ count: number;
13
+ } | {
14
+ consumer: "first";
15
+ row: unknown | null;
16
+ } | {
17
+ consumer: "single";
18
+ row: unknown | null;
19
+ };
20
+ /** A backend that runs a parsed Query with the given positional bindings. */
21
+ export interface Engine {
22
+ run(query: Query, bindings: readonly unknown[]): OqxResult;
23
+ }
24
+ export declare class InMemoryEngine implements Engine {
25
+ private ctx;
26
+ constructor(context?: DataContext);
27
+ run(query: Query, bindings?: readonly unknown[]): OqxResult;
28
+ private matches;
29
+ private rowsOf;
30
+ private child;
31
+ private runFollow;
32
+ private followWalk;
33
+ private shape;
34
+ private projectRow;
35
+ private evalWhere;
36
+ private evalWhereOp;
37
+ private anyMatch;
38
+ private iterMatchRows;
39
+ private matchRows;
40
+ private evalCollectValue;
41
+ private sortScopes;
42
+ private evalExpr;
43
+ private resolveFrom;
44
+ private has;
45
+ private evalCall;
46
+ }
47
+ /** Convenience: run a query with plain-object roots (the default context). */
48
+ export declare function runQuery(query: Query, bindings: readonly unknown[], roots: unknown): OqxResult;
49
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,KAAK,EACN,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAOhD,yEAAyE;AACzE,MAAM,MAAM,SAAS,GACjB;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,EAAE,CAAA;CAAE,GACxC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACvC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,GAC1C;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAAC;AAEhD,6EAA6E;AAC7E,MAAM,WAAW,MAAM;IACrB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;CAC5D;AAaD,qBAAa,cAAe,YAAW,MAAM;IAC3C,OAAO,CAAC,GAAG,CAAc;gBAEb,OAAO,GAAE,WAAkC;IAIvD,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,GAAE,SAAS,OAAO,EAAO,GAAG,SAAS;IAoC/D,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,SAAS;IAuBjB,OAAO,CAAC,UAAU;IA6DlB,OAAO,CAAC,KAAK;IAYb,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAE,aAAa;IAStB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,gBAAgB;IAwBxB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,QAAQ;IAsChB,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,GAAG;IAKX,OAAO,CAAC,QAAQ;CAYjB;AAiED,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,CAG9F"}