@omgbase/oqx 0.1.0 → 0.2.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 +7 -7
  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 +396 -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 +12 -1
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",
@@ -336,7 +336,7 @@ When you don't need interpolation, `execute` runs a plain string query against a
336
336
  **data context** of named roots:
337
337
 
338
338
  ```js
339
- import { execute } from "oqx";
339
+ import { execute } from "@omgbase/oqx";
340
340
 
341
341
  execute("name from people where age >= 18", { people });
342
342
  // `from people` resolves the `people` root
@@ -370,7 +370,7 @@ compute identity. Implement it to query an ORM graph, a remote API, or lazily
370
370
  loaded relations — the query *semantics* stay in OQX:
371
371
 
372
372
  ```js
373
- import { parse, run } from "oqx";
373
+ import { parse, run } from "@omgbase/oqx";
374
374
 
375
375
  const graph = {
376
376
  root: (name) => name === "tree" ? [nodes.get(1)] : undefined,
@@ -391,14 +391,14 @@ likes and stay correct. Two adapters ship:
391
391
 
392
392
  - `IndexedCollection` — hash-indexes a collection and answers equality predicates
393
393
  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
394
+ - `@omgbase/oqx/sqlite` — real pushdown to a `node:sqlite` database: the flat query core
395
395
  (scan + translatable conjunctive predicates, `LIMIT` for unordered
396
396
  `first`/`single`) becomes SQL; `matches()`, nested consumer ops, `follow`, etc.
397
397
  fall back to the in-memory residual.
398
398
 
399
399
  ```js
400
- import { parse, PlannedEngine } from "oqx";
401
- import { SqliteTable } from "oqx/sqlite";
400
+ import { parse, PlannedEngine } from "@omgbase/oqx";
401
+ import { SqliteTable } from "@omgbase/oqx/sqlite";
402
402
 
403
403
  const planner = new SqliteTable(db, "emp", { columns: ["id", "name", "dept", "level"] });
404
404
  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":"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"}
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;IAUjB,OAAO,CAAC,UAAU;IAiClB,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"}