@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.
Files changed (214) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/cli/eval-matcher.d.ts +13 -0
  3. package/dist/src/cli/eval-matcher.js +36 -0
  4. package/dist/src/core/attach.d.ts +5 -0
  5. package/dist/src/core/attach.js +38 -0
  6. package/dist/src/core/cursor.d.ts +11 -0
  7. package/dist/src/core/cursor.js +35 -0
  8. package/dist/src/core/hash.d.ts +23 -0
  9. package/dist/src/core/hash.js +82 -0
  10. package/dist/src/core/ids.d.ts +6 -0
  11. package/dist/src/core/ids.js +26 -0
  12. package/dist/src/core/index.d.ts +1 -0
  13. package/dist/src/core/index.js +2 -0
  14. package/dist/src/core/ingest.d.ts +43 -0
  15. package/dist/src/core/ingest.js +287 -0
  16. package/dist/src/core/order-key.d.ts +8 -0
  17. package/dist/src/core/order-key.js +72 -0
  18. package/dist/src/core/parse/parse.d.ts +3 -0
  19. package/dist/src/core/parse/parse.js +104 -0
  20. package/dist/src/core/parse/render.d.ts +3 -0
  21. package/dist/src/core/parse/render.js +20 -0
  22. package/dist/src/core/parse/tree.d.ts +8 -0
  23. package/dist/src/core/parse/tree.js +43 -0
  24. package/dist/src/core/parse/types.d.ts +52 -0
  25. package/dist/src/core/parse/types.js +9 -0
  26. package/dist/src/core/read/document.d.ts +107 -0
  27. package/dist/src/core/read/document.js +191 -0
  28. package/dist/src/core/read/nodes.d.ts +47 -0
  29. package/dist/src/core/read/nodes.js +102 -0
  30. package/dist/src/core/read/outline.d.ts +12 -0
  31. package/dist/src/core/read/outline.js +76 -0
  32. package/dist/src/core/read/reader.d.ts +125 -0
  33. package/dist/src/core/read/reader.js +206 -0
  34. package/dist/src/core/read/refs.d.ts +15 -0
  35. package/dist/src/core/read/refs.js +51 -0
  36. package/dist/src/core/store/edges.d.ts +26 -0
  37. package/dist/src/core/store/edges.js +101 -0
  38. package/dist/src/core/store/fts.d.ts +10 -0
  39. package/dist/src/core/store/fts.js +22 -0
  40. package/dist/src/core/store/gc.d.ts +18 -0
  41. package/dist/src/core/store/gc.js +69 -0
  42. package/dist/src/core/store/nodes.d.ts +5 -0
  43. package/dist/src/core/store/nodes.js +57 -0
  44. package/dist/src/core/store/properties.d.ts +38 -0
  45. package/dist/src/core/store/properties.js +177 -0
  46. package/dist/src/core/store/rebuild.d.ts +3 -0
  47. package/dist/src/core/store/rebuild.js +29 -0
  48. package/dist/src/core/store/schema.d.ts +9 -0
  49. package/dist/src/core/store/schema.js +386 -0
  50. package/dist/src/core/store/sections.d.ts +2 -0
  51. package/dist/src/core/store/sections.js +29 -0
  52. package/dist/src/core/store/store.d.ts +24 -0
  53. package/dist/src/core/store/store.js +164 -0
  54. package/dist/src/core/store/writers.d.ts +52 -0
  55. package/dist/src/core/store/writers.js +89 -0
  56. package/dist/src/core/vec.d.ts +10 -0
  57. package/dist/src/core/vec.js +33 -0
  58. package/dist/src/format/adapter.d.ts +97 -0
  59. package/dist/src/format/adapter.js +14 -0
  60. package/dist/src/format/index.d.ts +6 -0
  61. package/dist/src/format/index.js +15 -0
  62. package/dist/src/format/json.d.ts +3 -0
  63. package/dist/src/format/json.js +324 -0
  64. package/dist/src/format/markdown.d.ts +5 -0
  65. package/dist/src/format/markdown.js +217 -0
  66. package/dist/src/format/registry.d.ts +6 -0
  67. package/dist/src/format/registry.js +24 -0
  68. package/dist/src/format/yaml.d.ts +5 -0
  69. package/dist/src/format/yaml.js +413 -0
  70. package/dist/src/graph/extract.d.ts +38 -0
  71. package/dist/src/graph/extract.js +242 -0
  72. package/dist/src/graph/history.d.ts +103 -0
  73. package/dist/src/graph/history.js +210 -0
  74. package/dist/src/graph/inbound-links.d.ts +33 -0
  75. package/dist/src/graph/inbound-links.js +130 -0
  76. package/dist/src/graph/link-destinations.d.ts +22 -0
  77. package/dist/src/graph/link-destinations.js +84 -0
  78. package/dist/src/graph/link-health.d.ts +79 -0
  79. package/dist/src/graph/link-health.js +146 -0
  80. package/dist/src/graph/links.d.ts +23 -0
  81. package/dist/src/graph/links.js +42 -0
  82. package/dist/src/index.d.ts +60 -0
  83. package/dist/src/index.js +70 -0
  84. package/dist/src/mcp/errors.d.ts +17 -0
  85. package/dist/src/mcp/errors.js +18 -0
  86. package/dist/src/mcp/graph.d.ts +63 -0
  87. package/dist/src/mcp/graph.js +212 -0
  88. package/dist/src/mcp/reference.d.ts +1 -0
  89. package/dist/src/mcp/reference.js +227 -0
  90. package/dist/src/mcp/server.d.ts +30 -0
  91. package/dist/src/mcp/server.js +1076 -0
  92. package/dist/src/mcp/stdio.d.ts +13 -0
  93. package/dist/src/mcp/stdio.js +24 -0
  94. package/dist/src/migrate/mrplex.d.ts +27 -0
  95. package/dist/src/migrate/mrplex.js +29 -0
  96. package/dist/src/mutate/apply.d.ts +83 -0
  97. package/dist/src/mutate/apply.js +302 -0
  98. package/dist/src/mutate/doc-store.d.ts +49 -0
  99. package/dist/src/mutate/doc-store.js +71 -0
  100. package/dist/src/mutate/docs.d.ts +60 -0
  101. package/dist/src/mutate/docs.js +232 -0
  102. package/dist/src/mutate/known-ids.d.ts +12 -0
  103. package/dist/src/mutate/known-ids.js +154 -0
  104. package/dist/src/mutate/load.d.ts +3 -0
  105. package/dist/src/mutate/load.js +66 -0
  106. package/dist/src/mutate/lower.d.ts +32 -0
  107. package/dist/src/mutate/lower.js +232 -0
  108. package/dist/src/mutate/macros.d.ts +91 -0
  109. package/dist/src/mutate/macros.js +230 -0
  110. package/dist/src/mutate/ops.d.ts +40 -0
  111. package/dist/src/mutate/ops.js +513 -0
  112. package/dist/src/mutate/opset.d.ts +92 -0
  113. package/dist/src/mutate/opset.js +79 -0
  114. package/dist/src/mutate/plan-update.d.ts +51 -0
  115. package/dist/src/mutate/plan-update.js +187 -0
  116. package/dist/src/mutate/tree.d.ts +46 -0
  117. package/dist/src/mutate/tree.js +167 -0
  118. package/dist/src/oqx/run.d.ts +1 -0
  119. package/dist/src/oqx/run.js +6 -0
  120. package/dist/src/oqx-js/context.d.ts +23 -0
  121. package/dist/src/oqx-js/context.js +564 -0
  122. package/dist/src/oqx-js/planner.d.ts +10 -0
  123. package/dist/src/oqx-js/planner.js +91 -0
  124. package/dist/src/oqx-js/run.d.ts +41 -0
  125. package/dist/src/oqx-js/run.js +332 -0
  126. package/dist/src/oqx-js/sql/translate.d.ts +27 -0
  127. package/dist/src/oqx-js/sql/translate.js +290 -0
  128. package/dist/src/reconcile/crossdoc.d.ts +34 -0
  129. package/dist/src/reconcile/crossdoc.js +76 -0
  130. package/dist/src/reconcile/eval/generator.d.ts +21 -0
  131. package/dist/src/reconcile/eval/generator.js +129 -0
  132. package/dist/src/reconcile/eval/metrics.d.ts +32 -0
  133. package/dist/src/reconcile/eval/metrics.js +74 -0
  134. package/dist/src/reconcile/flatten.d.ts +12 -0
  135. package/dist/src/reconcile/flatten.js +37 -0
  136. package/dist/src/reconcile/phase5.d.ts +8 -0
  137. package/dist/src/reconcile/phase5.js +167 -0
  138. package/dist/src/reconcile/phase6a.d.ts +2 -0
  139. package/dist/src/reconcile/phase6a.js +159 -0
  140. package/dist/src/reconcile/phases.d.ts +28 -0
  141. package/dist/src/reconcile/phases.js +0 -0
  142. package/dist/src/reconcile/reconcile.d.ts +21 -0
  143. package/dist/src/reconcile/reconcile.js +115 -0
  144. package/dist/src/reconcile/similarity.d.ts +7 -0
  145. package/dist/src/reconcile/similarity.js +41 -0
  146. package/dist/src/reconcile/types.d.ts +46 -0
  147. package/dist/src/reconcile/types.js +19 -0
  148. package/dist/src/search/cel/ast.d.ts +74 -0
  149. package/dist/src/search/cel/ast.js +3 -0
  150. package/dist/src/search/cel/compile.d.ts +60 -0
  151. package/dist/src/search/cel/compile.js +836 -0
  152. package/dist/src/search/cel/lexer.d.ts +9 -0
  153. package/dist/src/search/cel/lexer.js +139 -0
  154. package/dist/src/search/cel/parser.d.ts +7 -0
  155. package/dist/src/search/cel/parser.js +219 -0
  156. package/dist/src/search/drain.d.ts +39 -0
  157. package/dist/src/search/drain.js +97 -0
  158. package/dist/src/search/embeddings.d.ts +131 -0
  159. package/dist/src/search/embeddings.js +263 -0
  160. package/dist/src/search/external.d.ts +13 -0
  161. package/dist/src/search/external.js +177 -0
  162. package/dist/src/search/fts-query.d.ts +1 -0
  163. package/dist/src/search/fts-query.js +64 -0
  164. package/dist/src/search/provider.d.ts +17 -0
  165. package/dist/src/search/provider.js +13 -0
  166. package/dist/src/search/resolve.d.ts +18 -0
  167. package/dist/src/search/resolve.js +23 -0
  168. package/dist/src/search/rrf.d.ts +36 -0
  169. package/dist/src/search/rrf.js +84 -0
  170. package/dist/src/search/tasks.d.ts +12 -0
  171. package/dist/src/search/tasks.js +131 -0
  172. package/dist/src/search/text.d.ts +16 -0
  173. package/dist/src/search/text.js +21 -0
  174. package/dist/src/search/vector.d.ts +18 -0
  175. package/dist/src/search/vector.js +49 -0
  176. package/dist/src/sync/admin.d.ts +59 -0
  177. package/dist/src/sync/admin.js +53 -0
  178. package/dist/src/sync/attach.d.ts +13 -0
  179. package/dist/src/sync/attach.js +29 -0
  180. package/dist/src/sync/checkpoint.d.ts +21 -0
  181. package/dist/src/sync/checkpoint.js +52 -0
  182. package/dist/src/sync/driver.d.ts +25 -0
  183. package/dist/src/sync/driver.js +80 -0
  184. package/dist/src/sync/external-source.d.ts +14 -0
  185. package/dist/src/sync/external-source.js +137 -0
  186. package/dist/src/sync/freshness.d.ts +42 -0
  187. package/dist/src/sync/freshness.js +152 -0
  188. package/dist/src/sync/fs-util.d.ts +12 -0
  189. package/dist/src/sync/fs-util.js +55 -0
  190. package/dist/src/sync/git-heuristics.d.ts +6 -0
  191. package/dist/src/sync/git-heuristics.js +16 -0
  192. package/dist/src/sync/observe.d.ts +63 -0
  193. package/dist/src/sync/observe.js +84 -0
  194. package/dist/src/sync/plugin.d.ts +54 -0
  195. package/dist/src/sync/plugin.js +12 -0
  196. package/dist/src/sync/reconciling-ingest.d.ts +12 -0
  197. package/dist/src/sync/reconciling-ingest.js +209 -0
  198. package/dist/src/sync/recovery.d.ts +6 -0
  199. package/dist/src/sync/recovery.js +34 -0
  200. package/dist/src/sync/settings.d.ts +18 -0
  201. package/dist/src/sync/settings.js +58 -0
  202. package/dist/src/sync/sources.d.ts +37 -0
  203. package/dist/src/sync/sources.js +75 -0
  204. package/dist/src/sync/tombstone.d.ts +9 -0
  205. package/dist/src/sync/tombstone.js +55 -0
  206. package/dist/src/sync/watch-lease.d.ts +14 -0
  207. package/dist/src/sync/watch-lease.js +91 -0
  208. package/dist/src/sync/watcher.d.ts +19 -0
  209. package/dist/src/sync/watcher.js +34 -0
  210. package/dist/src/sync/workspace.d.ts +34 -0
  211. package/dist/src/sync/workspace.js +120 -0
  212. package/dist/src/sync/writer-lock.d.ts +16 -0
  213. package/dist/src/sync/writer-lock.js +121 -0
  214. package/package.json +80 -0
@@ -0,0 +1,9 @@
1
+ export type TokenType = "ident" | "field" | "outer" | "string" | "int" | "double" | "bool" | "null" | "op" | "lparen" | "rparen" | "comma" | "dot" | "eof";
2
+ export interface Token {
3
+ type: TokenType;
4
+ value: string;
5
+ pos: number;
6
+ }
7
+ export declare class LexError extends Error {
8
+ }
9
+ export declare function lex(src: string): Token[];
@@ -0,0 +1,139 @@
1
+ // Lexer for the CEL subset (query-language §3.1). Produces a flat token
2
+ // stream for the recursive-descent parser.
3
+ export class LexError extends Error {
4
+ }
5
+ const OPS_2 = new Set(["&&", "||", "==", "!=", "<=", ">="]);
6
+ // Arithmetic operators are lexed so the parser can reject them with a precise
7
+ // "arithmetic is not supported" message (10 §3.1) rather than a lex error.
8
+ const OPS_1 = new Set(["!", "<", ">", "+", "-", "*", "/"]);
9
+ export function lex(src) {
10
+ const tokens = [];
11
+ let i = 0;
12
+ const n = src.length;
13
+ const push = (type, value, pos) => {
14
+ tokens.push({ type, value, pos });
15
+ };
16
+ while (i < n) {
17
+ const c = src[i];
18
+ if (c === " " || c === "\t" || c === "\n" || c === "\r") {
19
+ i++;
20
+ continue;
21
+ }
22
+ if (c === "(") {
23
+ push("lparen", c, i);
24
+ i++;
25
+ continue;
26
+ }
27
+ if (c === ")") {
28
+ push("rparen", c, i);
29
+ i++;
30
+ continue;
31
+ }
32
+ if (c === ",") {
33
+ push("comma", c, i);
34
+ i++;
35
+ continue;
36
+ }
37
+ if (c === ".") {
38
+ push("dot", c, i);
39
+ i++;
40
+ continue;
41
+ }
42
+ // two-char operators
43
+ const two = src.slice(i, i + 2);
44
+ if (OPS_2.has(two)) {
45
+ push("op", two, i);
46
+ i += 2;
47
+ continue;
48
+ }
49
+ if (OPS_1.has(c)) {
50
+ push("op", c, i);
51
+ i++;
52
+ continue;
53
+ }
54
+ // strings
55
+ if (c === '"' || c === "'") {
56
+ const quote = c;
57
+ const start = i;
58
+ i++;
59
+ let value = "";
60
+ while (i < n && src[i] !== quote) {
61
+ if (src[i] === "\\") {
62
+ i++;
63
+ const esc = src[i];
64
+ if (esc === undefined)
65
+ throw new LexError(`unterminated escape at ${i}`);
66
+ value += esc === "n" ? "\n" : esc === "t" ? "\t" : esc;
67
+ i++;
68
+ }
69
+ else {
70
+ value += src[i];
71
+ i++;
72
+ }
73
+ }
74
+ if (i >= n)
75
+ throw new LexError(`unterminated string starting at ${start}`);
76
+ i++; // closing quote
77
+ push("string", value, start);
78
+ continue;
79
+ }
80
+ // numbers
81
+ if (c >= "0" && c <= "9") {
82
+ const start = i;
83
+ while (i < n && src[i] >= "0" && src[i] <= "9")
84
+ i++;
85
+ let isDouble = false;
86
+ if (src[i] === ".") {
87
+ isDouble = true;
88
+ i++;
89
+ while (i < n && src[i] >= "0" && src[i] <= "9")
90
+ i++;
91
+ }
92
+ push(isDouble ? "double" : "int", src.slice(start, i), start);
93
+ continue;
94
+ }
95
+ // ^name — a one-scope-outward binding reference (the symmetric read form of
96
+ // the `^name:` lift). OQX resolves the name against the enclosing query
97
+ // scope's bindings; a bare `^` with no name is a lex error.
98
+ if (c === "^") {
99
+ const start = i;
100
+ i++;
101
+ const nameStart = i;
102
+ while (i < n && /[A-Za-z0-9_]/.test(src[i]))
103
+ i++;
104
+ if (i === nameStart)
105
+ throw new LexError(`expected a binding name after '^' at ${start}`);
106
+ push("outer", src.slice(nameStart, i), start);
107
+ continue;
108
+ }
109
+ // $-intrinsic: $ident(.ident)*
110
+ if (c === "$") {
111
+ const start = i;
112
+ i++;
113
+ while (i < n && /[A-Za-z0-9_]/.test(src[i]))
114
+ i++;
115
+ // allow dotted continuation only when followed by ident (handled by parser via dot tokens);
116
+ // here we capture just the leading $ident. Dots handled as separate tokens.
117
+ push("field", src.slice(start, i), start);
118
+ continue;
119
+ }
120
+ // identifiers / keywords
121
+ if (/[A-Za-z_]/.test(c)) {
122
+ const start = i;
123
+ while (i < n && /[A-Za-z0-9_]/.test(src[i]))
124
+ i++;
125
+ const word = src.slice(start, i);
126
+ if (word === "true" || word === "false")
127
+ push("bool", word, start);
128
+ else if (word === "null")
129
+ push("null", word, start);
130
+ else
131
+ push("ident", word, start);
132
+ continue;
133
+ }
134
+ throw new LexError(`unexpected character '${c}' at ${i}`);
135
+ }
136
+ push("eof", "", n);
137
+ return tokens;
138
+ }
139
+ //# sourceMappingURL=lexer.js.map
@@ -0,0 +1,7 @@
1
+ import type { Node } from "./ast.js";
2
+ export declare class FilterInvalid extends Error {
3
+ reason: string;
4
+ hint: string;
5
+ constructor(reason: string, hint?: string);
6
+ }
7
+ export declare function parseFilter(src: string): Node;
@@ -0,0 +1,219 @@
1
+ import { lex, LexError } from "./lexer.js";
2
+ // Recursive-descent parser for the CEL subset (10 §3.1). Throws FilterInvalid
3
+ // with a reason + hint on anything outside the grammar.
4
+ export class FilterInvalid extends Error {
5
+ reason;
6
+ hint;
7
+ constructor(reason, hint = "see query-language.md §3") {
8
+ super(reason);
9
+ this.reason = reason;
10
+ this.hint = hint;
11
+ }
12
+ }
13
+ const RELOPS = new Set(["==", "!=", "<", "<=", ">", ">="]);
14
+ export function parseFilter(src) {
15
+ let tokens;
16
+ try {
17
+ tokens = lex(src);
18
+ }
19
+ catch (e) {
20
+ throw new FilterInvalid(e instanceof LexError ? e.message : String(e));
21
+ }
22
+ const p = new Parser(tokens);
23
+ const node = p.parseExpr();
24
+ p.expect("eof");
25
+ return node;
26
+ }
27
+ class Parser {
28
+ tokens;
29
+ pos = 0;
30
+ constructor(tokens) {
31
+ this.tokens = tokens;
32
+ }
33
+ peek() { return this.tokens[this.pos]; }
34
+ next() { return this.tokens[this.pos++]; }
35
+ expect(type) {
36
+ const t = this.peek();
37
+ if (t.type !== type)
38
+ throw new FilterInvalid(`expected ${type} but found '${t.value || t.type}' at ${t.pos}`);
39
+ return this.next();
40
+ }
41
+ parseExpr() { return this.parseOr(); }
42
+ parseOr() {
43
+ let left = this.parseAnd();
44
+ while (this.peek().type === "op" && this.peek().value === "||") {
45
+ this.next();
46
+ left = { kind: "or", left, right: this.parseAnd() };
47
+ }
48
+ return left;
49
+ }
50
+ parseAnd() {
51
+ let left = this.parseUnary();
52
+ while (this.peek().type === "op" && this.peek().value === "&&") {
53
+ this.next();
54
+ left = { kind: "and", left, right: this.parseUnary() };
55
+ }
56
+ return left;
57
+ }
58
+ parseUnary() {
59
+ if (this.peek().type === "op" && this.peek().value === "!") {
60
+ this.next();
61
+ return { kind: "not", operand: this.parseUnary() };
62
+ }
63
+ return this.parseComparison();
64
+ }
65
+ // comparison | membership | (primary that may stand alone as boolean)
66
+ parseComparison() {
67
+ const left = this.parsePostfix(this.parsePrimary());
68
+ // membership: literal in list(field) — detect the `in` keyword
69
+ if (this.peek().type === "ident" && this.peek().value === "in") {
70
+ this.next();
71
+ // `<value> in ^name`: membership over a one-scope-outward collection
72
+ // binding (a lift). The left operand may be any scalar (field/value/lit).
73
+ if (this.peek().type === "outer") {
74
+ const ref = this.next();
75
+ return { kind: "outerMembership", value: left, collection: { kind: "outerref", name: ref.value } };
76
+ }
77
+ const listCall = this.parsePrimary();
78
+ if (listCall.kind !== "call" || listCall.name !== "list") {
79
+ throw new FilterInvalid("`in` is only allowed as `<literal> in list(field)`", "10 §4");
80
+ }
81
+ if (left.kind !== "literal") {
82
+ throw new FilterInvalid("left side of `in list()` must be a literal", "10 §4");
83
+ }
84
+ const fieldArg = listCall.args[0];
85
+ if (!fieldArg || fieldArg.kind !== "field") {
86
+ throw new FilterInvalid("list() takes a single field argument", "10 §4");
87
+ }
88
+ return { kind: "membership", value: left, field: fieldArg };
89
+ }
90
+ if (this.peek().type === "op" && RELOPS.has(this.peek().value)) {
91
+ const op = this.next().value;
92
+ const right = this.parsePostfix(this.parsePrimary());
93
+ return { kind: "comparison", op, left, right };
94
+ }
95
+ if (this.peek().type === "op" && ["+", "-", "*", "/"].includes(this.peek().value)) {
96
+ throw new FilterInvalid("arithmetic is not supported", "10 §3.1");
97
+ }
98
+ return left;
99
+ }
100
+ // handle method calls: receiver.name(args) and quantifiers .exists/.all
101
+ parsePostfix(node) {
102
+ let cur = node;
103
+ while (this.peek().type === "dot") {
104
+ this.next();
105
+ // A `$`-segment after a dot is an intrinsic reach-through continuation
106
+ // (`doc.$path`, `doc.$updated_at`): the lexer emits it as a `field` token
107
+ // (value keeps the `$`). It is never a method receiver, so append it as a
108
+ // field segment directly. The compiler routes `doc.$…` to docs intrinsics.
109
+ if (this.peek().type === "field") {
110
+ const seg = this.next().value;
111
+ if (cur.kind !== "field") {
112
+ throw new FilterInvalid(`unexpected '.${seg}' — intrinsic segment requires a field receiver`, "10 §2");
113
+ }
114
+ cur = { kind: "field", segments: [...cur.segments, seg], intrinsic: cur.intrinsic };
115
+ continue;
116
+ }
117
+ const name = this.expect("ident").value;
118
+ if (this.peek().type !== "lparen") {
119
+ // dotted field continuation (only valid if receiver was a field)
120
+ if (cur.kind === "field") {
121
+ cur = { kind: "field", segments: [...cur.segments, name], intrinsic: cur.intrinsic };
122
+ continue;
123
+ }
124
+ throw new FilterInvalid(`unexpected '.${name}' — method call requires '('`);
125
+ }
126
+ const args = this.parseArgs();
127
+ if (name === "exists" || name === "all") {
128
+ // first arg must be an identifier (the bound var), rest is the predicate
129
+ const [varArg, pred] = args;
130
+ if (!varArg || varArg.kind !== "field" || varArg.segments.length !== 1 || varArg.intrinsic) {
131
+ throw new FilterInvalid(`${name}() first argument must be a bound variable name`, "10 §3.2");
132
+ }
133
+ if (!pred)
134
+ throw new FilterInvalid(`${name}() requires a predicate`, "10 §3.2");
135
+ cur = { kind: "quantifier", collection: cur, op: name, varName: varArg.segments[0], predicate: pred };
136
+ }
137
+ else {
138
+ cur = { kind: "method", receiver: cur, name, args };
139
+ }
140
+ }
141
+ return cur;
142
+ }
143
+ parseArgs() {
144
+ this.expect("lparen");
145
+ const args = [];
146
+ if (this.peek().type !== "rparen") {
147
+ args.push(this.parseExpr());
148
+ while (this.peek().type === "comma") {
149
+ this.next();
150
+ args.push(this.parseExpr());
151
+ }
152
+ }
153
+ this.expect("rparen");
154
+ return args;
155
+ }
156
+ parsePrimary() {
157
+ const t = this.peek();
158
+ if (t.type === "lparen") {
159
+ this.next();
160
+ const e = this.parseExpr();
161
+ this.expect("rparen");
162
+ return e;
163
+ }
164
+ if (t.type === "string") {
165
+ this.next();
166
+ return lit(t.value, "string");
167
+ }
168
+ if (t.type === "int") {
169
+ this.next();
170
+ return { kind: "literal", value: parseInt(t.value, 10), type: "int" };
171
+ }
172
+ if (t.type === "double") {
173
+ this.next();
174
+ return { kind: "literal", value: parseFloat(t.value), type: "double" };
175
+ }
176
+ if (t.type === "bool") {
177
+ this.next();
178
+ return { kind: "literal", value: t.value === "true", type: "bool" };
179
+ }
180
+ if (t.type === "null") {
181
+ this.next();
182
+ return { kind: "literal", value: null, type: "null" };
183
+ }
184
+ if (t.type === "field") {
185
+ this.next();
186
+ return { kind: "field", segments: [t.value], intrinsic: true };
187
+ }
188
+ if (t.type === "outer") {
189
+ this.next();
190
+ return { kind: "outerref", name: t.value };
191
+ }
192
+ if (t.type === "ident") {
193
+ if (t.value === "in")
194
+ throw new FilterInvalid("unexpected keyword `in`");
195
+ this.next();
196
+ // function call?
197
+ if (this.peek().type === "lparen") {
198
+ const args = this.parseArgs();
199
+ rejectClock(t.value);
200
+ return { kind: "call", name: t.value, args };
201
+ }
202
+ // bare field (frontmatter key or bare block field)
203
+ return { kind: "field", segments: [t.value], intrinsic: false };
204
+ }
205
+ if (t.type === "op" && (t.value === "+" || t.value === "-" || t.value === "*" || t.value === "/")) {
206
+ throw new FilterInvalid("arithmetic is not supported", "10 §3.1");
207
+ }
208
+ throw new FilterInvalid(`unexpected token '${t.value || t.type}' at ${t.pos}`);
209
+ }
210
+ }
211
+ function lit(value, type) {
212
+ return { kind: "literal", value, type };
213
+ }
214
+ function rejectClock(name) {
215
+ if (name === "now" || name === "random" || name === "rand") {
216
+ throw new FilterInvalid(`${name}() is rejected — determinism is load-bearing`, "10 §3.1");
217
+ }
218
+ }
219
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1,39 @@
1
+ import type { Store } from "../core/store/store.js";
2
+ import { EmbeddingWorker } from "./embeddings.js";
3
+ export interface DrainerOptions {
4
+ /** Coalesce a burst of mutations into one drain (default 500ms). */
5
+ debounceMs?: number;
6
+ /** Fired after a drain that embedded ≥1 block (progress/log hook). */
7
+ onDrain?: (result: {
8
+ embedded: number;
9
+ cached: number;
10
+ }) => void;
11
+ /** A drain threw (provider down, etc.). The drainer stays alive. */
12
+ onError?: (err: unknown) => void;
13
+ }
14
+ export declare class EmbedDrainer {
15
+ private store;
16
+ private repoId;
17
+ private worker;
18
+ private opts;
19
+ private readonly debounceMs;
20
+ private timer;
21
+ private dirty;
22
+ private running;
23
+ private closed;
24
+ constructor(store: Store, repoId: string, worker: EmbeddingWorker, opts?: DrainerOptions);
25
+ /**
26
+ * Mark the repo dirty and (re)arm the debounce timer. Cheap and synchronous —
27
+ * safe to call from a mutation-tool handler or a watcher checkpoint. Bursts
28
+ * within the debounce window collapse into a single drain. Does no DB work and
29
+ * never throws; the actual embedding happens later, off the caller's path.
30
+ */
31
+ schedule(): void;
32
+ /** Cancel any pending debounce and drain now; resolves when the drain (and any
33
+ * re-run triggered by mutations arriving mid-drain) completes. */
34
+ flush(): Promise<void>;
35
+ /** Cancel the timer and await any in-flight drain; no drains start after. */
36
+ close(): Promise<void>;
37
+ private kick;
38
+ private loop;
39
+ }
@@ -0,0 +1,97 @@
1
+ import { EmbeddingWorker } from "./embeddings.js";
2
+ import { buildEmbedTasks, buildDocEmbedTasks } from "./tasks.js";
3
+ export class EmbedDrainer {
4
+ store;
5
+ repoId;
6
+ worker;
7
+ opts;
8
+ debounceMs;
9
+ timer = null;
10
+ dirty = false;
11
+ running = null;
12
+ closed = false;
13
+ constructor(store, repoId, worker, opts = {}) {
14
+ this.store = store;
15
+ this.repoId = repoId;
16
+ this.worker = worker;
17
+ this.opts = opts;
18
+ this.debounceMs = opts.debounceMs ?? 500;
19
+ }
20
+ /**
21
+ * Mark the repo dirty and (re)arm the debounce timer. Cheap and synchronous —
22
+ * safe to call from a mutation-tool handler or a watcher checkpoint. Bursts
23
+ * within the debounce window collapse into a single drain. Does no DB work and
24
+ * never throws; the actual embedding happens later, off the caller's path.
25
+ */
26
+ schedule() {
27
+ if (this.closed || !this.worker.available)
28
+ return;
29
+ this.dirty = true;
30
+ if (this.timer)
31
+ clearTimeout(this.timer);
32
+ this.timer = setTimeout(() => {
33
+ this.timer = null;
34
+ void this.kick();
35
+ }, this.debounceMs);
36
+ // Don't keep the process alive solely for a pending drain (hosts own their
37
+ // own lifetime via the transport/lease).
38
+ this.timer.unref?.();
39
+ }
40
+ /** Cancel any pending debounce and drain now; resolves when the drain (and any
41
+ * re-run triggered by mutations arriving mid-drain) completes. */
42
+ async flush() {
43
+ if (this.timer) {
44
+ clearTimeout(this.timer);
45
+ this.timer = null;
46
+ }
47
+ if (this.closed || !this.worker.available)
48
+ return;
49
+ this.dirty = true;
50
+ await this.kick();
51
+ }
52
+ /** Cancel the timer and await any in-flight drain; no drains start after. */
53
+ async close() {
54
+ this.closed = true;
55
+ if (this.timer) {
56
+ clearTimeout(this.timer);
57
+ this.timer = null;
58
+ }
59
+ await this.running;
60
+ }
61
+ // Single-flight: at most one drain runs at a time. If mutations mark the repo
62
+ // dirty again while a drain is in flight, the loop re-runs once more after it,
63
+ // so the last edit is never left unembedded.
64
+ kick() {
65
+ if (this.running)
66
+ return this.running;
67
+ this.running = this.loop().finally(() => { this.running = null; });
68
+ return this.running;
69
+ }
70
+ async loop() {
71
+ while (this.dirty && !this.closed) {
72
+ this.dirty = false;
73
+ try {
74
+ // Blocks first: the pooled doc-embedding fallback reuses block vectors,
75
+ // so embedding blocks before docs means a large doc can pool from
76
+ // freshly-cached vectors in the same drain.
77
+ const tasks = buildEmbedTasks(this.store, this.repoId);
78
+ const result = await this.worker.process(tasks);
79
+ // Then docs: recompute any document whose content changed (a block edit
80
+ // shifts the whole-doc input, invalidating its cached vector), so doc
81
+ // vectors never drift from content.
82
+ const docTasks = buildDocEmbedTasks(this.store, this.repoId);
83
+ const docResult = await this.worker.processDocs(docTasks);
84
+ if (result.embedded > 0 || docResult.embedded > 0 || docResult.pooled > 0) {
85
+ this.opts.onDrain?.({ embedded: result.embedded + docResult.embedded, cached: result.cached });
86
+ }
87
+ }
88
+ catch (err) {
89
+ this.opts.onError?.(err);
90
+ // Swallow — a transient provider failure must not crash the host. The
91
+ // next schedule() (or flush at shutdown) retries.
92
+ return;
93
+ }
94
+ }
95
+ }
96
+ }
97
+ //# sourceMappingURL=drain.js.map
@@ -0,0 +1,131 @@
1
+ import type { Store } from "../core/store/store.js";
2
+ export interface EmbeddingProvider {
3
+ model: string;
4
+ dim: number;
5
+ /**
6
+ * Max input length (in tokens) the model accepts before it truncates. Used to
7
+ * decide the doc-embedding strategy: inputs within budget take the whole-doc
8
+ * path, larger ones fall back to pooling block vectors. Absent ⇒ the caller's
9
+ * configured/default budget applies (05 §6; don't hardcode the threshold).
10
+ */
11
+ maxInputTokens?: number;
12
+ /** Embed a batch of input strings → float32 vectors. */
13
+ embed(texts: string[]): Promise<number[][]>;
14
+ }
15
+ export declare class SemanticUnavailable extends Error {
16
+ code: "semantic_unavailable";
17
+ }
18
+ export declare function contextPrefix(input: {
19
+ docTitle: string;
20
+ path: string;
21
+ headingChain: string[];
22
+ blockType: string;
23
+ }): string;
24
+ export declare function embedInput(ctx: string, blockText: string): string;
25
+ export declare function ctxHashHex(ctx: string): string;
26
+ export interface EmbedTask {
27
+ blockId: string;
28
+ contentHashHex: string;
29
+ ctx: string;
30
+ text: string;
31
+ }
32
+ export declare function shouldEmbed(text: string, minTokens?: number): boolean;
33
+ export declare function estimateTokens(text: string): number;
34
+ export declare const DEFAULT_DOC_TOKEN_BUDGET = 512;
35
+ export interface DocEmbedBlockRef {
36
+ contentHashHex: string;
37
+ ctx: string;
38
+ tokens: number;
39
+ }
40
+ export interface DocEmbedTask {
41
+ docId: string;
42
+ input: string;
43
+ blocks: DocEmbedBlockRef[];
44
+ }
45
+ export type DocEmbedMethod = "whole" | "pooled";
46
+ export interface DocVectorRow {
47
+ vec: Float32Array;
48
+ method: DocEmbedMethod;
49
+ }
50
+ export declare class EmbeddingWorker {
51
+ private store;
52
+ private provider;
53
+ constructor(store: Store, provider: EmbeddingProvider | null);
54
+ get available(): boolean;
55
+ private cacheKey;
56
+ /** Look up a cached vector; null if absent (stale/missing). */
57
+ getCached(contentHashHex: string, ctx: string): Float32Array | null;
58
+ /**
59
+ * Embed a batch (skipping cache hits) and persist. Throws if no provider.
60
+ * Cache misses are embedded in chunks of `batchSize` (default:
61
+ * DEFAULT_EMBED_BATCH); `onProgress` fires after each chunk is persisted so
62
+ * callers can show live progress on a long drain.
63
+ */
64
+ process(tasks: EmbedTask[], opts?: {
65
+ batchSize?: number;
66
+ onProgress?: (p: {
67
+ embedded: number;
68
+ total: number;
69
+ }) => void;
70
+ }): Promise<{
71
+ embedded: number;
72
+ cached: number;
73
+ }>;
74
+ /** Embed a bare query string (no context prefix, 05 §6). */
75
+ embedQuery(query: string): Promise<Float32Array>;
76
+ private docTokenBudget;
77
+ /** The freshness key for a doc task: sha256 of the whole-doc embed input. */
78
+ private docInputHash;
79
+ /** Look up a cached doc vector; null if absent or stale (input changed). */
80
+ getCachedDoc(docId: string, input: string): DocVectorRow | null;
81
+ /** Doc ids whose current input has no fresh cached vector — the doc queue. */
82
+ staleDocs(tasks: DocEmbedTask[]): string[];
83
+ /**
84
+ * Compute one vector per document and persist. Whole-doc path (primary):
85
+ * embed the doc's input directly. Pooled fallback: only when the input
86
+ * exceeds the provider's token budget, pool the doc's already-cached block
87
+ * vectors (token-weighted mean, L2-normalized) — zero embedding calls. Skips
88
+ * docs whose fresh vector is already cached. Throws if no provider.
89
+ */
90
+ processDocs(tasks: DocEmbedTask[], opts?: {
91
+ batchSize?: number;
92
+ onProgress?: (p: {
93
+ embedded: number;
94
+ total: number;
95
+ }) => void;
96
+ }): Promise<{
97
+ embedded: number;
98
+ cached: number;
99
+ pooled: number;
100
+ }>;
101
+ /**
102
+ * Token-weighted mean of a document's cached block vectors, L2-normalized.
103
+ * Pool = Σ(wᵢ·vᵢ)/Σ(wᵢ) with wᵢ = block token count, then normalized (pooling
104
+ * breaks unit norm; downstream cosine assumes unit vectors). Returns null when
105
+ * no referenced block has a cached vector yet.
106
+ */
107
+ private poolBlockVectors;
108
+ /**
109
+ * Staleness: a heading rename changes descendants' context prefixes, so their
110
+ * cached vectors (keyed by the OLD ctx_hash) no longer match the current
111
+ * context. This returns the block ids in a doc whose current ctx has no cached
112
+ * vector — i.e. stale/pending — for the worker queue.
113
+ */
114
+ staleBlocks(tasks: EmbedTask[]): string[];
115
+ /**
116
+ * Vectors left behind by a previous model. The cache and both search paths key
117
+ * on model name, so after switching models the old model's rows are dead
118
+ * weight: never read, never overwritten (the new model writes under its own
119
+ * key). Reports how many block + doc vectors belong to a model OTHER than the
120
+ * current provider's, so callers can offer to reclaim the space.
121
+ */
122
+ foreignVectorCount(): {
123
+ blocks: number;
124
+ docs: number;
125
+ };
126
+ /** Delete every cached vector not produced by the current model. */
127
+ pruneForeignVectors(): {
128
+ blocks: number;
129
+ docs: number;
130
+ };
131
+ }