@omgbase/core 0.1.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/LICENSE +21 -0
- package/dist/src/cli/eval-matcher.d.ts +13 -0
- package/dist/src/cli/eval-matcher.js +36 -0
- package/dist/src/core/attach.d.ts +5 -0
- package/dist/src/core/attach.js +38 -0
- package/dist/src/core/cursor.d.ts +11 -0
- package/dist/src/core/cursor.js +35 -0
- package/dist/src/core/hash.d.ts +23 -0
- package/dist/src/core/hash.js +82 -0
- package/dist/src/core/ids.d.ts +6 -0
- package/dist/src/core/ids.js +26 -0
- package/dist/src/core/index.d.ts +1 -0
- package/dist/src/core/index.js +2 -0
- package/dist/src/core/ingest.d.ts +43 -0
- package/dist/src/core/ingest.js +287 -0
- package/dist/src/core/order-key.d.ts +8 -0
- package/dist/src/core/order-key.js +72 -0
- package/dist/src/core/parse/parse.d.ts +3 -0
- package/dist/src/core/parse/parse.js +104 -0
- package/dist/src/core/parse/render.d.ts +3 -0
- package/dist/src/core/parse/render.js +20 -0
- package/dist/src/core/parse/tree.d.ts +8 -0
- package/dist/src/core/parse/tree.js +43 -0
- package/dist/src/core/parse/types.d.ts +52 -0
- package/dist/src/core/parse/types.js +9 -0
- package/dist/src/core/read/document.d.ts +107 -0
- package/dist/src/core/read/document.js +191 -0
- package/dist/src/core/read/nodes.d.ts +47 -0
- package/dist/src/core/read/nodes.js +102 -0
- package/dist/src/core/read/outline.d.ts +12 -0
- package/dist/src/core/read/outline.js +76 -0
- package/dist/src/core/read/reader.d.ts +125 -0
- package/dist/src/core/read/reader.js +206 -0
- package/dist/src/core/read/refs.d.ts +15 -0
- package/dist/src/core/read/refs.js +51 -0
- package/dist/src/core/store/edges.d.ts +26 -0
- package/dist/src/core/store/edges.js +101 -0
- package/dist/src/core/store/fts.d.ts +10 -0
- package/dist/src/core/store/fts.js +22 -0
- package/dist/src/core/store/gc.d.ts +18 -0
- package/dist/src/core/store/gc.js +69 -0
- package/dist/src/core/store/nodes.d.ts +5 -0
- package/dist/src/core/store/nodes.js +57 -0
- package/dist/src/core/store/properties.d.ts +38 -0
- package/dist/src/core/store/properties.js +177 -0
- package/dist/src/core/store/rebuild.d.ts +3 -0
- package/dist/src/core/store/rebuild.js +29 -0
- package/dist/src/core/store/schema.d.ts +9 -0
- package/dist/src/core/store/schema.js +386 -0
- package/dist/src/core/store/sections.d.ts +2 -0
- package/dist/src/core/store/sections.js +29 -0
- package/dist/src/core/store/store.d.ts +24 -0
- package/dist/src/core/store/store.js +164 -0
- package/dist/src/core/store/writers.d.ts +52 -0
- package/dist/src/core/store/writers.js +89 -0
- package/dist/src/core/vec.d.ts +10 -0
- package/dist/src/core/vec.js +33 -0
- package/dist/src/format/adapter.d.ts +97 -0
- package/dist/src/format/adapter.js +14 -0
- package/dist/src/format/index.d.ts +6 -0
- package/dist/src/format/index.js +15 -0
- package/dist/src/format/json.d.ts +3 -0
- package/dist/src/format/json.js +324 -0
- package/dist/src/format/markdown.d.ts +5 -0
- package/dist/src/format/markdown.js +217 -0
- package/dist/src/format/registry.d.ts +6 -0
- package/dist/src/format/registry.js +24 -0
- package/dist/src/format/yaml.d.ts +5 -0
- package/dist/src/format/yaml.js +413 -0
- package/dist/src/graph/extract.d.ts +38 -0
- package/dist/src/graph/extract.js +242 -0
- package/dist/src/graph/history.d.ts +103 -0
- package/dist/src/graph/history.js +210 -0
- package/dist/src/graph/inbound-links.d.ts +33 -0
- package/dist/src/graph/inbound-links.js +130 -0
- package/dist/src/graph/link-destinations.d.ts +22 -0
- package/dist/src/graph/link-destinations.js +84 -0
- package/dist/src/graph/link-health.d.ts +79 -0
- package/dist/src/graph/link-health.js +146 -0
- package/dist/src/graph/links.d.ts +23 -0
- package/dist/src/graph/links.js +42 -0
- package/dist/src/index.d.ts +60 -0
- package/dist/src/index.js +70 -0
- package/dist/src/mcp/errors.d.ts +17 -0
- package/dist/src/mcp/errors.js +18 -0
- package/dist/src/mcp/graph.d.ts +63 -0
- package/dist/src/mcp/graph.js +212 -0
- package/dist/src/mcp/reference.d.ts +1 -0
- package/dist/src/mcp/reference.js +227 -0
- package/dist/src/mcp/server.d.ts +30 -0
- package/dist/src/mcp/server.js +1076 -0
- package/dist/src/mcp/stdio.d.ts +13 -0
- package/dist/src/mcp/stdio.js +24 -0
- package/dist/src/migrate/mrplex.d.ts +27 -0
- package/dist/src/migrate/mrplex.js +29 -0
- package/dist/src/mutate/apply.d.ts +83 -0
- package/dist/src/mutate/apply.js +302 -0
- package/dist/src/mutate/doc-store.d.ts +49 -0
- package/dist/src/mutate/doc-store.js +71 -0
- package/dist/src/mutate/docs.d.ts +60 -0
- package/dist/src/mutate/docs.js +232 -0
- package/dist/src/mutate/known-ids.d.ts +12 -0
- package/dist/src/mutate/known-ids.js +154 -0
- package/dist/src/mutate/load.d.ts +3 -0
- package/dist/src/mutate/load.js +66 -0
- package/dist/src/mutate/lower.d.ts +32 -0
- package/dist/src/mutate/lower.js +232 -0
- package/dist/src/mutate/macros.d.ts +91 -0
- package/dist/src/mutate/macros.js +230 -0
- package/dist/src/mutate/ops.d.ts +40 -0
- package/dist/src/mutate/ops.js +513 -0
- package/dist/src/mutate/opset.d.ts +92 -0
- package/dist/src/mutate/opset.js +79 -0
- package/dist/src/mutate/plan-update.d.ts +51 -0
- package/dist/src/mutate/plan-update.js +187 -0
- package/dist/src/mutate/tree.d.ts +46 -0
- package/dist/src/mutate/tree.js +167 -0
- package/dist/src/oqx/run.d.ts +1 -0
- package/dist/src/oqx/run.js +6 -0
- package/dist/src/oqx-js/context.d.ts +23 -0
- package/dist/src/oqx-js/context.js +564 -0
- package/dist/src/oqx-js/planner.d.ts +10 -0
- package/dist/src/oqx-js/planner.js +91 -0
- package/dist/src/oqx-js/run.d.ts +41 -0
- package/dist/src/oqx-js/run.js +332 -0
- package/dist/src/oqx-js/sql/translate.d.ts +27 -0
- package/dist/src/oqx-js/sql/translate.js +290 -0
- package/dist/src/reconcile/crossdoc.d.ts +34 -0
- package/dist/src/reconcile/crossdoc.js +76 -0
- package/dist/src/reconcile/eval/generator.d.ts +21 -0
- package/dist/src/reconcile/eval/generator.js +129 -0
- package/dist/src/reconcile/eval/metrics.d.ts +32 -0
- package/dist/src/reconcile/eval/metrics.js +74 -0
- package/dist/src/reconcile/flatten.d.ts +12 -0
- package/dist/src/reconcile/flatten.js +37 -0
- package/dist/src/reconcile/phase5.d.ts +8 -0
- package/dist/src/reconcile/phase5.js +167 -0
- package/dist/src/reconcile/phase6a.d.ts +2 -0
- package/dist/src/reconcile/phase6a.js +159 -0
- package/dist/src/reconcile/phases.d.ts +28 -0
- package/dist/src/reconcile/phases.js +0 -0
- package/dist/src/reconcile/reconcile.d.ts +21 -0
- package/dist/src/reconcile/reconcile.js +115 -0
- package/dist/src/reconcile/similarity.d.ts +7 -0
- package/dist/src/reconcile/similarity.js +41 -0
- package/dist/src/reconcile/types.d.ts +46 -0
- package/dist/src/reconcile/types.js +19 -0
- package/dist/src/search/cel/ast.d.ts +74 -0
- package/dist/src/search/cel/ast.js +3 -0
- package/dist/src/search/cel/compile.d.ts +60 -0
- package/dist/src/search/cel/compile.js +836 -0
- package/dist/src/search/cel/lexer.d.ts +9 -0
- package/dist/src/search/cel/lexer.js +139 -0
- package/dist/src/search/cel/parser.d.ts +7 -0
- package/dist/src/search/cel/parser.js +219 -0
- package/dist/src/search/drain.d.ts +39 -0
- package/dist/src/search/drain.js +97 -0
- package/dist/src/search/embeddings.d.ts +131 -0
- package/dist/src/search/embeddings.js +263 -0
- package/dist/src/search/external.d.ts +13 -0
- package/dist/src/search/external.js +177 -0
- package/dist/src/search/fts-query.d.ts +1 -0
- package/dist/src/search/fts-query.js +64 -0
- package/dist/src/search/provider.d.ts +17 -0
- package/dist/src/search/provider.js +13 -0
- package/dist/src/search/resolve.d.ts +18 -0
- package/dist/src/search/resolve.js +23 -0
- package/dist/src/search/rrf.d.ts +36 -0
- package/dist/src/search/rrf.js +84 -0
- package/dist/src/search/tasks.d.ts +12 -0
- package/dist/src/search/tasks.js +131 -0
- package/dist/src/search/text.d.ts +16 -0
- package/dist/src/search/text.js +21 -0
- package/dist/src/search/vector.d.ts +18 -0
- package/dist/src/search/vector.js +49 -0
- package/dist/src/sync/admin.d.ts +59 -0
- package/dist/src/sync/admin.js +53 -0
- package/dist/src/sync/attach.d.ts +13 -0
- package/dist/src/sync/attach.js +29 -0
- package/dist/src/sync/checkpoint.d.ts +21 -0
- package/dist/src/sync/checkpoint.js +52 -0
- package/dist/src/sync/driver.d.ts +25 -0
- package/dist/src/sync/driver.js +80 -0
- package/dist/src/sync/external-source.d.ts +14 -0
- package/dist/src/sync/external-source.js +137 -0
- package/dist/src/sync/freshness.d.ts +42 -0
- package/dist/src/sync/freshness.js +152 -0
- package/dist/src/sync/fs-util.d.ts +12 -0
- package/dist/src/sync/fs-util.js +55 -0
- package/dist/src/sync/git-heuristics.d.ts +6 -0
- package/dist/src/sync/git-heuristics.js +16 -0
- package/dist/src/sync/observe.d.ts +63 -0
- package/dist/src/sync/observe.js +84 -0
- package/dist/src/sync/plugin.d.ts +54 -0
- package/dist/src/sync/plugin.js +12 -0
- package/dist/src/sync/reconciling-ingest.d.ts +12 -0
- package/dist/src/sync/reconciling-ingest.js +209 -0
- package/dist/src/sync/recovery.d.ts +6 -0
- package/dist/src/sync/recovery.js +34 -0
- package/dist/src/sync/settings.d.ts +18 -0
- package/dist/src/sync/settings.js +58 -0
- package/dist/src/sync/sources.d.ts +37 -0
- package/dist/src/sync/sources.js +75 -0
- package/dist/src/sync/tombstone.d.ts +9 -0
- package/dist/src/sync/tombstone.js +55 -0
- package/dist/src/sync/watch-lease.d.ts +14 -0
- package/dist/src/sync/watch-lease.js +91 -0
- package/dist/src/sync/watcher.d.ts +19 -0
- package/dist/src/sync/watcher.js +34 -0
- package/dist/src/sync/workspace.d.ts +34 -0
- package/dist/src/sync/workspace.js +120 -0
- package/dist/src/sync/writer-lock.d.ts +16 -0
- package/dist/src/sync/writer-lock.js +121 -0
- package/package.json +80 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Query } from "@omgbase/oqx";
|
|
2
|
+
import type { Store } from "../core/store/store.js";
|
|
3
|
+
import type { SemanticVec } from "../search/cel/compile.js";
|
|
4
|
+
export type OqxConsumer = "collect" | "count" | "exists" | "none" | "first" | "single";
|
|
5
|
+
export type EmbedQuery = (text: string) => Promise<{
|
|
6
|
+
model: string;
|
|
7
|
+
vec: Float32Array;
|
|
8
|
+
}>;
|
|
9
|
+
export interface OqxHit {
|
|
10
|
+
id: string;
|
|
11
|
+
path: string;
|
|
12
|
+
[k: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface OqxResult {
|
|
15
|
+
hits: OqxHit[];
|
|
16
|
+
truncated: boolean;
|
|
17
|
+
cursor: string | null;
|
|
18
|
+
consumer: OqxConsumer;
|
|
19
|
+
count?: number;
|
|
20
|
+
exists?: boolean;
|
|
21
|
+
/** The `none` consumer's result: true iff the query yields no rows. */
|
|
22
|
+
none?: boolean;
|
|
23
|
+
/** Present for a top-level `values` projection (`select <expr> values`): the
|
|
24
|
+
* bare projected values in page order, in place of `hits` (which is then
|
|
25
|
+
* empty). Paged/deduped exactly like hits — `truncated`/`cursor` apply. For
|
|
26
|
+
* first/single it holds zero or one value. */
|
|
27
|
+
values?: unknown[];
|
|
28
|
+
}
|
|
29
|
+
export interface OqxOptions {
|
|
30
|
+
limit?: number;
|
|
31
|
+
cursor?: string | null;
|
|
32
|
+
semanticVectors?: Map<string, SemanticVec>;
|
|
33
|
+
/** Force the pure in-memory engine (skip tier-3 pushdown). Internal — used by
|
|
34
|
+
* the differential conformance suite to prove planned == in-memory. */
|
|
35
|
+
plan?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare function collectSemanticPhrases(source: string): string[];
|
|
38
|
+
export declare function oqxRun(store: Store, repoId: string, source: string, opts?: OqxOptions): OqxResult;
|
|
39
|
+
export declare function oqxRunAsync(store: Store, repoId: string, source: string, opts?: OqxOptions, embedQuery?: EmbedQuery): Promise<OqxResult>;
|
|
40
|
+
/** Parse an OQX source into the `@omgbase/oqx` AST (re-exported for callers). */
|
|
41
|
+
export declare function parseOqx(source: string): Query;
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
// omgbase's OQX entry point, served by `@omgbase/oqx`: parse the source, run it
|
|
2
|
+
// through the in-memory engine over a store-backed DataContext (context.ts), and
|
|
3
|
+
// shape the engine's result into omgbase's OqxResult (lean {id, path, …} hits,
|
|
4
|
+
// keyset pagination, count/exists scalars). Same signature + shape as the former
|
|
5
|
+
// in-tree compiler, so every caller and the corpus are unchanged.
|
|
6
|
+
import { parse, PlannedEngine, InMemoryEngine, OqxError } from "@omgbase/oqx";
|
|
7
|
+
import { makeStoreContext } from "./context.js";
|
|
8
|
+
import { SQLiteQueryPlanner } from "./planner.js";
|
|
9
|
+
import { FilterInvalid } from "../search/cel/parser.js";
|
|
10
|
+
import { float32ToBlob } from "../core/vec.js";
|
|
11
|
+
import { encodeCursor as encodeKeyset, decodeCursor as decodeKeyset } from "../core/cursor.js";
|
|
12
|
+
// Row-scoped domain functions: authored as free calls (`text("x")`) that
|
|
13
|
+
// implicitly reference the current row. `@omgbase/oqx` free functions receive no
|
|
14
|
+
// row, so we rewrite them to `$self.fn(…)` (a method whose receiver is the row).
|
|
15
|
+
const ROW_FNS = new Set([
|
|
16
|
+
"text", "semantic", "under", "under_heading", "within", "under_kind",
|
|
17
|
+
"yaml_path", "json_pointer", "has_edge", "has_anchor", "child_count", "parent_type",
|
|
18
|
+
]);
|
|
19
|
+
const selfRef = { kind: "ident", name: "$self" };
|
|
20
|
+
function rewriteExpr(e) {
|
|
21
|
+
switch (e.kind) {
|
|
22
|
+
case "member": return { ...e, recv: rewriteExpr(e.recv) };
|
|
23
|
+
case "index": return { ...e, recv: rewriteExpr(e.recv), index: rewriteExpr(e.index) };
|
|
24
|
+
case "unary": return { ...e, expr: rewriteExpr(e.expr) };
|
|
25
|
+
case "binary":
|
|
26
|
+
case "logical":
|
|
27
|
+
case "in":
|
|
28
|
+
return { ...e, left: rewriteExpr(e.left), right: rewriteExpr(e.right) };
|
|
29
|
+
case "range":
|
|
30
|
+
return { ...e, lo: e.lo ? rewriteExpr(e.lo) : null, hi: e.hi ? rewriteExpr(e.hi) : null };
|
|
31
|
+
case "call": {
|
|
32
|
+
const recv = e.recv ? rewriteExpr(e.recv) : null;
|
|
33
|
+
const args = e.args.map(rewriteExpr);
|
|
34
|
+
if (recv === null && ROW_FNS.has(e.name))
|
|
35
|
+
return { kind: "call", recv: selfRef, name: e.name, args };
|
|
36
|
+
return { kind: "call", recv, name: e.name, args };
|
|
37
|
+
}
|
|
38
|
+
default: return e; // lit, ident, outer, binding
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function rewriteWhere(w) {
|
|
42
|
+
switch (w.kind) {
|
|
43
|
+
case "and": return { kind: "and", parts: w.parts.map(rewriteWhere) };
|
|
44
|
+
case "or": return { kind: "or", parts: w.parts.map(rewriteWhere) };
|
|
45
|
+
case "not": return { kind: "not", expr: rewriteWhere(w.expr) };
|
|
46
|
+
case "scalar": return { kind: "scalar", expr: rewriteExpr(w.expr) };
|
|
47
|
+
case "op": return rewriteOp(w);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function rewriteOp(op) {
|
|
51
|
+
return { ...op, receiver: rewriteExpr(op.receiver), sub: rewriteSub(op.sub) };
|
|
52
|
+
}
|
|
53
|
+
function rewriteFollow(f) {
|
|
54
|
+
return {
|
|
55
|
+
...f,
|
|
56
|
+
receiver: rewriteExpr(f.receiver),
|
|
57
|
+
where: f.where ? rewriteExpr(f.where) : null,
|
|
58
|
+
frontier: f.frontier ? rewriteExpr(f.frontier) : null,
|
|
59
|
+
by: f.by ? rewriteExpr(f.by) : null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function rewriteSelect(items) {
|
|
63
|
+
return items.map((it) => it.kind === "field" ? { ...it, expr: rewriteExpr(it.expr) } : { ...it, op: rewriteOp(it.op) });
|
|
64
|
+
}
|
|
65
|
+
function rewriteSub(s) {
|
|
66
|
+
return {
|
|
67
|
+
from: s.from.map(rewriteExpr),
|
|
68
|
+
where: s.where ? rewriteWhere(s.where) : null,
|
|
69
|
+
select: rewriteSelect(s.select),
|
|
70
|
+
orderBy: s.orderBy ? s.orderBy.map((o) => ({ ...o, expr: rewriteExpr(o.expr) })) : null,
|
|
71
|
+
follow: s.follow ? rewriteFollow(s.follow) : null,
|
|
72
|
+
...(s.values ? { values: true } : {}),
|
|
73
|
+
...(s.limit ? { limit: rewriteExpr(s.limit) } : {}),
|
|
74
|
+
...(s.offset ? { offset: rewriteExpr(s.offset) } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function rewriteQuery(q) {
|
|
78
|
+
return {
|
|
79
|
+
source: rewriteExpr(q.source),
|
|
80
|
+
from: q.from.map(rewriteExpr),
|
|
81
|
+
where: q.where ? rewriteWhere(q.where) : null,
|
|
82
|
+
select: rewriteSelect(q.select),
|
|
83
|
+
orderBy: q.orderBy ? q.orderBy.map((o) => ({ ...o, expr: rewriteExpr(o.expr) })) : null,
|
|
84
|
+
consumer: q.consumer,
|
|
85
|
+
follow: q.follow ? rewriteFollow(q.follow) : null,
|
|
86
|
+
...(q.distinct ? { distinct: true } : {}),
|
|
87
|
+
...(q.values ? { values: true } : {}),
|
|
88
|
+
...(q.limit ? { limit: rewriteExpr(q.limit) } : {}),
|
|
89
|
+
...(q.offset ? { offset: rewriteExpr(q.offset) } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// A top-level `limit`/`offset` on the collect path is applied by the runner (see
|
|
93
|
+
// oqxRunInner), so it must be a plain number literal here — there is no row to
|
|
94
|
+
// evaluate anything else against at the root, and omgbase queries carry no
|
|
95
|
+
// bindings.
|
|
96
|
+
function constBound(e, word) {
|
|
97
|
+
if (!e)
|
|
98
|
+
return null;
|
|
99
|
+
if (e.kind === "lit" && typeof e.value === "number" && Number.isInteger(e.value) && e.value >= 0)
|
|
100
|
+
return e.value;
|
|
101
|
+
throw new FilterInvalid(`top-level ${word} must be a non-negative integer literal`, "OQX");
|
|
102
|
+
}
|
|
103
|
+
// Distinct phrases referenced by `semantic("…")` (free calls in the raw parse).
|
|
104
|
+
export function collectSemanticPhrases(source) {
|
|
105
|
+
const phrases = new Set();
|
|
106
|
+
const visitExpr = (e) => {
|
|
107
|
+
switch (e.kind) {
|
|
108
|
+
case "call":
|
|
109
|
+
if (e.recv === null && e.name === "semantic" && e.args[0]?.kind === "lit" && typeof e.args[0].value === "string") {
|
|
110
|
+
phrases.add(e.args[0].value);
|
|
111
|
+
}
|
|
112
|
+
if (e.recv)
|
|
113
|
+
visitExpr(e.recv);
|
|
114
|
+
e.args.forEach(visitExpr);
|
|
115
|
+
return;
|
|
116
|
+
case "member":
|
|
117
|
+
visitExpr(e.recv);
|
|
118
|
+
return;
|
|
119
|
+
case "index":
|
|
120
|
+
visitExpr(e.recv);
|
|
121
|
+
visitExpr(e.index);
|
|
122
|
+
return;
|
|
123
|
+
case "unary":
|
|
124
|
+
visitExpr(e.expr);
|
|
125
|
+
return;
|
|
126
|
+
case "binary":
|
|
127
|
+
case "logical":
|
|
128
|
+
case "in":
|
|
129
|
+
visitExpr(e.left);
|
|
130
|
+
visitExpr(e.right);
|
|
131
|
+
return;
|
|
132
|
+
case "range":
|
|
133
|
+
if (e.lo)
|
|
134
|
+
visitExpr(e.lo);
|
|
135
|
+
if (e.hi)
|
|
136
|
+
visitExpr(e.hi);
|
|
137
|
+
return;
|
|
138
|
+
default: return;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const visitWhere = (w) => {
|
|
142
|
+
switch (w.kind) {
|
|
143
|
+
case "and":
|
|
144
|
+
case "or":
|
|
145
|
+
w.parts.forEach(visitWhere);
|
|
146
|
+
return;
|
|
147
|
+
case "not":
|
|
148
|
+
visitWhere(w.expr);
|
|
149
|
+
return;
|
|
150
|
+
case "scalar":
|
|
151
|
+
visitExpr(w.expr);
|
|
152
|
+
return;
|
|
153
|
+
case "op":
|
|
154
|
+
visitOp(w);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const visitOp = (op) => { visitExpr(op.receiver); visitSub(op.sub); };
|
|
159
|
+
const visitSub = (s) => {
|
|
160
|
+
s.from.forEach(visitExpr);
|
|
161
|
+
if (s.where)
|
|
162
|
+
visitWhere(s.where);
|
|
163
|
+
s.select.forEach((it) => it.kind === "field" ? visitExpr(it.expr) : visitOp(it.op));
|
|
164
|
+
if (s.orderBy)
|
|
165
|
+
s.orderBy.forEach((o) => visitExpr(o.expr));
|
|
166
|
+
if (s.follow) {
|
|
167
|
+
visitExpr(s.follow.receiver);
|
|
168
|
+
[s.follow.where, s.follow.frontier, s.follow.by].forEach((x) => x && visitExpr(x));
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
let q;
|
|
172
|
+
try {
|
|
173
|
+
q = parse(source);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
visitExpr(q.source);
|
|
179
|
+
q.from.forEach(visitExpr);
|
|
180
|
+
if (q.where)
|
|
181
|
+
visitWhere(q.where);
|
|
182
|
+
q.select.forEach((it) => it.kind === "field" ? visitExpr(it.expr) : visitOp(it.op));
|
|
183
|
+
if (q.orderBy)
|
|
184
|
+
q.orderBy.forEach((o) => visitExpr(o.expr));
|
|
185
|
+
if (q.follow) {
|
|
186
|
+
visitExpr(q.follow.receiver);
|
|
187
|
+
[q.follow.where, q.follow.frontier, q.follow.by].forEach((x) => x && visitExpr(x));
|
|
188
|
+
}
|
|
189
|
+
return [...phrases];
|
|
190
|
+
}
|
|
191
|
+
const ID_ITEM = { kind: "field", name: "__oqx_id", expr: { kind: "ident", name: "$id" }, lift: 0 };
|
|
192
|
+
const PATH_ITEM = { kind: "field", name: "__oqx_path", expr: { kind: "ident", name: "$path" }, lift: 0 };
|
|
193
|
+
// The reserved key a top-level `values` projection's single item is renamed to,
|
|
194
|
+
// so it rides through id/path injection, keyset paging, and distinct as an
|
|
195
|
+
// ordinary record field and is peeled off at the end.
|
|
196
|
+
const VALUE_KEY = "__oqx_value";
|
|
197
|
+
function toHit(row) {
|
|
198
|
+
const o = row;
|
|
199
|
+
const { __oqx_id, __oqx_path, ...rest } = o;
|
|
200
|
+
return { id: String(__oqx_id), path: String(__oqx_path ?? ""), ...rest };
|
|
201
|
+
}
|
|
202
|
+
// Top-level `select distinct`: dedup hits by their USER projection (every field
|
|
203
|
+
// except the injected id/path), keeping the first (so the retained hit carries a
|
|
204
|
+
// real id/path). Order-preserving.
|
|
205
|
+
function dedupHitsByProjection(hits) {
|
|
206
|
+
const seen = new Set();
|
|
207
|
+
const out = [];
|
|
208
|
+
for (const h of hits) {
|
|
209
|
+
const { id: _id, path: _path, ...proj } = h;
|
|
210
|
+
void _id;
|
|
211
|
+
void _path;
|
|
212
|
+
const key = JSON.stringify(Object.keys(proj).sort().map((k) => [k, proj[k]]));
|
|
213
|
+
if (seen.has(key))
|
|
214
|
+
continue;
|
|
215
|
+
seen.add(key);
|
|
216
|
+
out.push(h);
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
export function oqxRun(store, repoId, source, opts = {}) {
|
|
221
|
+
try {
|
|
222
|
+
return oqxRunInner(store, repoId, source, opts);
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
// Normalize the library's errors to omgbase's contract (filter_invalid).
|
|
226
|
+
if (e instanceof OqxError)
|
|
227
|
+
throw new FilterInvalid(e.message, "OQX");
|
|
228
|
+
throw e;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function oqxRunInner(store, repoId, source, opts) {
|
|
232
|
+
const parsed = rewriteQuery(parse(source));
|
|
233
|
+
const consumer = parsed.consumer;
|
|
234
|
+
const ctxOpts = opts.semanticVectors ? { semanticVectors: opts.semanticVectors } : {};
|
|
235
|
+
const ctx = makeStoreContext(store, repoId, ctxOpts);
|
|
236
|
+
// Tier-3 pushdown reduces the scan in SQL; the in-memory fallback finishes the
|
|
237
|
+
// residual (and runs anything not pushable), so results match a pure scan.
|
|
238
|
+
const engine = opts.plan === false
|
|
239
|
+
? new InMemoryEngine(ctx)
|
|
240
|
+
: new PlannedEngine(new SQLiteQueryPlanner(store, repoId, ctxOpts), ctx);
|
|
241
|
+
if (consumer === "exists") {
|
|
242
|
+
const res = engine.run(parsed, []);
|
|
243
|
+
return { hits: [], truncated: false, cursor: null, consumer: "exists", exists: res.consumer === "exists" ? res.exists : false };
|
|
244
|
+
}
|
|
245
|
+
if (consumer === "count") {
|
|
246
|
+
const res = engine.run(parsed, []);
|
|
247
|
+
return { hits: [], truncated: false, cursor: null, consumer: "count", count: res.consumer === "count" ? res.count : 0 };
|
|
248
|
+
}
|
|
249
|
+
if (consumer === "none") {
|
|
250
|
+
const res = engine.run(parsed, []);
|
|
251
|
+
return { hits: [], truncated: false, cursor: null, consumer: "none", none: res.consumer === "none" ? res.none : true };
|
|
252
|
+
}
|
|
253
|
+
// collect / first / single: inject id + path so every hit carries them. A
|
|
254
|
+
// top-level `select distinct` is applied HERE, not in the engine: the injected
|
|
255
|
+
// id/path are unique per row and would defeat the engine's projection dedup, so
|
|
256
|
+
// we run without engine-distinct and dedup hits by their USER projection below,
|
|
257
|
+
// keeping the first row's id/path.
|
|
258
|
+
const topDistinct = !!parsed.distinct;
|
|
259
|
+
// A top-level `values` projection runs as a RECORD projection whose single
|
|
260
|
+
// item is renamed to VALUE_KEY (the engine's own values mode is switched off),
|
|
261
|
+
// so pagination and distinct work unchanged; the bare values are peeled off
|
|
262
|
+
// the final page below and returned as `values` with `hits` empty.
|
|
263
|
+
const topValues = !!parsed.values;
|
|
264
|
+
const userSelect = topValues ? [{ ...parsed.select[0], name: VALUE_KEY }] : parsed.select;
|
|
265
|
+
// On the collect path a top-level `limit`/`offset` is ALSO taken out of the
|
|
266
|
+
// engine query and applied here, after the runner's own distinct — the engine
|
|
267
|
+
// would otherwise bound the raw rows before dedup (`select distinct type
|
|
268
|
+
// limit 3` must be three distinct types). first/single keep theirs: the
|
|
269
|
+
// engine's offset-aware cap is exactly right for them.
|
|
270
|
+
const { limit: topLimit, offset: topOffset, ...unbounded } = parsed;
|
|
271
|
+
const base = consumer === "collect" ? unbounded : parsed;
|
|
272
|
+
const q = { ...base, distinct: false, values: false, select: [ID_ITEM, PATH_ITEM, ...userSelect] };
|
|
273
|
+
const res = engine.run(q, []);
|
|
274
|
+
if (consumer === "first" || consumer === "single") {
|
|
275
|
+
const row = res.consumer === "first" || res.consumer === "single" ? res.row : null;
|
|
276
|
+
if (topValues)
|
|
277
|
+
return { hits: [], truncated: false, cursor: null, consumer, values: row == null ? [] : [toHit(row)[VALUE_KEY]] };
|
|
278
|
+
return { hits: row == null ? [] : [toHit(row)], truncated: false, cursor: null, consumer };
|
|
279
|
+
}
|
|
280
|
+
// collect: keyset pagination on (path, id) when the order is the default.
|
|
281
|
+
let rows = (res.consumer === "collect" ? res.rows : []).map(toHit);
|
|
282
|
+
if (topDistinct)
|
|
283
|
+
rows = dedupHitsByProjection(rows);
|
|
284
|
+
// The query's own bound defines the result SET; the `limit`/`cursor` options
|
|
285
|
+
// then page within it.
|
|
286
|
+
const offset = constBound(topOffset, "offset") ?? 0;
|
|
287
|
+
const limit = constBound(topLimit, "limit");
|
|
288
|
+
if (offset || limit != null)
|
|
289
|
+
rows = rows.slice(offset, limit == null ? undefined : offset + limit);
|
|
290
|
+
const custom = !!parsed.orderBy && parsed.orderBy.length > 0;
|
|
291
|
+
const cap = opts.limit ?? 50;
|
|
292
|
+
let page = rows;
|
|
293
|
+
if (!custom && opts.cursor) {
|
|
294
|
+
const { path, id } = decodeCursor(opts.cursor);
|
|
295
|
+
page = page.filter((h) => h.path > path || (h.path === path && h.id > id));
|
|
296
|
+
}
|
|
297
|
+
const truncated = page.length > cap;
|
|
298
|
+
page = page.slice(0, cap);
|
|
299
|
+
const last = page[page.length - 1];
|
|
300
|
+
const cursor = truncated && last && !custom ? encodeCursor(last.path, last.id) : null;
|
|
301
|
+
if (topValues)
|
|
302
|
+
return { hits: [], truncated, cursor, consumer: "collect", values: page.map((h) => h[VALUE_KEY]) };
|
|
303
|
+
return { hits: page, truncated, cursor, consumer: "collect" };
|
|
304
|
+
}
|
|
305
|
+
export async function oqxRunAsync(store, repoId, source, opts = {}, embedQuery) {
|
|
306
|
+
const phrases = collectSemanticPhrases(source);
|
|
307
|
+
if (phrases.length === 0)
|
|
308
|
+
return oqxRun(store, repoId, source, opts);
|
|
309
|
+
if (!embedQuery)
|
|
310
|
+
throw new FilterInvalid("semantic(...) needs an embedding provider; none is configured", "OQX semantic");
|
|
311
|
+
const semanticVectors = new Map();
|
|
312
|
+
for (const phrase of phrases) {
|
|
313
|
+
const { model, vec } = await embedQuery(phrase);
|
|
314
|
+
semanticVectors.set(phrase, { model, vec: float32ToBlob(vec) });
|
|
315
|
+
}
|
|
316
|
+
return oqxRun(store, repoId, source, { ...opts, semanticVectors });
|
|
317
|
+
}
|
|
318
|
+
// The collect page's keyset is (path, id): the kernel's shared cursor encoding
|
|
319
|
+
// (core/cursor.ts) with a two-part tuple. A malformed cursor is CursorInvalid,
|
|
320
|
+
// which the MCP layer maps to filter_invalid.
|
|
321
|
+
function encodeCursor(path, id) {
|
|
322
|
+
return encodeKeyset([path, id]);
|
|
323
|
+
}
|
|
324
|
+
function decodeCursor(cursor) {
|
|
325
|
+
const [path, id] = decodeKeyset(cursor, "query", 2);
|
|
326
|
+
return { path: path, id: id };
|
|
327
|
+
}
|
|
328
|
+
/** Parse an OQX source into the `@omgbase/oqx` AST (re-exported for callers). */
|
|
329
|
+
export function parseOqx(source) {
|
|
330
|
+
return parse(source);
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Expr } from "@omgbase/oqx";
|
|
2
|
+
/** SQL row domain, matching the store's tables. */
|
|
3
|
+
export type Target = "docs" | "blocks" | "nodes" | "edges";
|
|
4
|
+
/** The SQL aliases the compiler assigned to the current scope's row (`self`) and
|
|
5
|
+
* its owning document (`doc`). On the `docs` target the row IS the document, so
|
|
6
|
+
* both are the same alias. */
|
|
7
|
+
export interface Aliases {
|
|
8
|
+
self: string;
|
|
9
|
+
doc: string;
|
|
10
|
+
}
|
|
11
|
+
export interface TranslateCtx extends Aliases {
|
|
12
|
+
target: Target;
|
|
13
|
+
/** Query bindings, used to resolve `${…}` interpolations (`{kind:"binding"}`)
|
|
14
|
+
* to their values (mirrors `@omgbase/oqx`'s `constValue`). */
|
|
15
|
+
params: readonly unknown[];
|
|
16
|
+
}
|
|
17
|
+
/** A SQL fragment plus its positional bind params, in statement order. */
|
|
18
|
+
export interface Frag {
|
|
19
|
+
sql: string;
|
|
20
|
+
params: unknown[];
|
|
21
|
+
}
|
|
22
|
+
/** Translate an expression used as a VALUE (comparison operand, method receiver,
|
|
23
|
+
* function argument) to a SQL scalar. Returns null if not faithfully translatable. */
|
|
24
|
+
export declare function translateValue(e: Expr, ctx: TranslateCtx): Frag | null;
|
|
25
|
+
/** Translate an expression used as a boolean PREDICATE to a SQL boolean, or null
|
|
26
|
+
* if it cannot be pushed faithfully. Only positive, AND-safe forms are handled. */
|
|
27
|
+
export declare function translatePredicate(e: Expr, ctx: TranslateCtx): Frag | null;
|