@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.
- package/README.md +23 -15
- package/dist/adapters/indexed.d.ts +11 -0
- package/dist/adapters/indexed.d.ts.map +1 -0
- package/dist/adapters/indexed.js +57 -0
- package/dist/adapters/indexed.js.map +1 -0
- package/dist/adapters/sqlite.d.ts +23 -0
- package/dist/adapters/sqlite.d.ts.map +1 -0
- package/dist/adapters/sqlite.js +113 -0
- package/dist/adapters/sqlite.js.map +1 -0
- package/dist/ast.d.ts +121 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +7 -0
- package/dist/ast.js.map +1 -0
- package/dist/context.d.ts +38 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +47 -0
- package/dist/context.js.map +1 -0
- package/dist/engine.d.ts +49 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +437 -0
- package/dist/engine.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +74 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer.d.ts +17 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +187 -0
- package/dist/lexer.js.map +1 -0
- package/dist/parser.d.ts +6 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +584 -0
- package/dist/parser.js.map +1 -0
- package/dist/plan.d.ts +21 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +56 -0
- package/dist/plan.js.map +1 -0
- package/dist/planner.d.ts +24 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +27 -0
- package/dist/planner.js.map +1 -0
- package/dist/semantics.d.ts +22 -0
- package/dist/semantics.d.ts.map +1 -0
- package/dist/semantics.js +142 -0
- package/dist/semantics.js.map +1 -0
- package/package.json +16 -6
- 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` (
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
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
|
|
316
|
-
object reference).
|
|
317
|
-
|
|
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
|
-
-
|
|
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":"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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
|
package/dist/ast.js.map
ADDED
|
@@ -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"}
|
package/dist/context.js
ADDED
|
@@ -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"}
|
package/dist/engine.d.ts
ADDED
|
@@ -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"}
|