@omgbase/core 0.21.0 → 0.23.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 (36) hide show
  1. package/dist/src/core/ids.d.ts +1 -1
  2. package/dist/src/core/read/document.d.ts +4 -1
  3. package/dist/src/core/read/document.js +18 -6
  4. package/dist/src/core/store/archive.d.ts +196 -0
  5. package/dist/src/core/store/archive.js +606 -0
  6. package/dist/src/core/store/gc.d.ts +25 -4
  7. package/dist/src/core/store/gc.js +43 -28
  8. package/dist/src/core/store/rebuild.d.ts +2 -0
  9. package/dist/src/core/store/rebuild.js +8 -2
  10. package/dist/src/core/store/retention.d.ts +138 -0
  11. package/dist/src/core/store/retention.js +409 -0
  12. package/dist/src/core/store/schema.d.ts +3 -2
  13. package/dist/src/core/store/schema.js +30 -4
  14. package/dist/src/core/store/size.d.ts +21 -2
  15. package/dist/src/core/store/size.js +24 -3
  16. package/dist/src/core/store/store.d.ts +1 -0
  17. package/dist/src/core/store/store.js +53 -1
  18. package/dist/src/graph/history.d.ts +2 -0
  19. package/dist/src/graph/history.js +8 -4
  20. package/dist/src/index.d.ts +3 -1
  21. package/dist/src/index.js +3 -0
  22. package/dist/src/mcp/errors.d.ts +1 -1
  23. package/dist/src/mcp/errors.js +4 -0
  24. package/dist/src/mcp/reference.d.ts +1 -1
  25. package/dist/src/mcp/reference.js +24 -6
  26. package/dist/src/mcp/server.js +2 -2
  27. package/dist/src/oqx-js/context.js +21 -5
  28. package/dist/src/oqx-js/dates.d.ts +105 -0
  29. package/dist/src/oqx-js/dates.js +427 -0
  30. package/dist/src/oqx-js/names.js +14 -3
  31. package/dist/src/oqx-js/planner.js +17 -2
  32. package/dist/src/oqx-js/run.d.ts +1 -0
  33. package/dist/src/oqx-js/run.js +66 -1
  34. package/dist/src/spec-versions.d.ts +4 -4
  35. package/dist/src/spec-versions.js +3 -3
  36. package/package.json +2 -2
@@ -9,6 +9,7 @@ import { makeStoreContext, rowRef } from "./context.js";
9
9
  import { SQLiteQueryPlanner } from "./planner.js";
10
10
  import { REPO_REMOVED_MESSAGE, ROOTS, bareTargetMessage, pastRootMessage, unknownTargetMessage } from "./root-row.js";
11
11
  import { checkNames } from "./names.js";
12
+ import { DATE_FUNCTIONS, DATE_METHODS, dateFunction, dateMethod } from "./dates.js";
12
13
  import { FilterInvalid } from "../search/cel/parser.js";
13
14
  import { float32ToBlob } from "../core/vec.js";
14
15
  import { encodeCursor as encodeKeyset, decodePathCursor } from "../core/cursor.js";
@@ -91,8 +92,32 @@ export function checkRootSpellings(q) {
91
92
  },
92
93
  });
93
94
  }
95
+ // Dates over literals fold to literals (spec/surface §1.3 "Dates and
96
+ // durations", 4.8): `date("today")`, `dur("30 days")`, and a date method whose
97
+ // receiver and arguments are literals (`date("today").minus(dur("30 days"))`,
98
+ // folded inside-out since `transform` maps children first) become the string /
99
+ // number / null they evaluate to — by the SAME functions the context runs, so
100
+ // nothing changes in meaning — before the planner sees the tree. So `where
101
+ // updated_at < date("today")` is a text-vs-text comparison the translator
102
+ // pushes down (a call is otherwise an unconditional decline), and a named point
103
+ // is resolved ONCE per query. A call with the wrong arity is left in place: it
104
+ // raises when evaluated, and an empty source never evaluates it (spec/oqx §11).
105
+ const isLit = (e) => e.kind === "lit";
106
+ export function foldDates(e) {
107
+ if (e.kind !== "call" || !e.args.every(isLit))
108
+ return e;
109
+ const args = e.args.map((a) => a.value);
110
+ if (e.recv === null) {
111
+ if (!Object.hasOwn(DATE_FUNCTIONS, e.name) || DATE_FUNCTIONS[e.name] !== args.length)
112
+ return e;
113
+ return build.lit(dateFunction(e.name, args).value);
114
+ }
115
+ if (!isLit(e.recv) || !Object.hasOwn(DATE_METHODS, e.name) || DATE_METHODS[e.name] !== args.length)
116
+ return e;
117
+ return build.lit(dateMethod(e.name, e.recv.value, args).value);
118
+ }
94
119
  function rewriteQuery(q) {
95
- return transform(q, (e) => (e.kind === "call" && e.recv === null && ROW_FNS.has(e.name) ? { ...e, recv: build.ident("$self") } : rootPathLiterals(e)));
120
+ return transform(q, (e) => (e.kind === "call" && e.recv === null && ROW_FNS.has(e.name) ? { ...e, recv: build.ident("$self") } : foldDates(rootPathLiterals(e))));
96
121
  }
97
122
  // A top-level `limit`/`offset` on the collect path is applied by the runner (see
98
123
  // oqxRunInner), so it must be a plain number literal here — there is no row to
@@ -347,6 +372,18 @@ function runResolved(store, repoId, raw, opts) {
347
372
  const res = engine.run(parsed, []);
348
373
  return { hits: [], truncated: false, cursor: null, consumer: "none", none: res.consumer === "none" ? res.none : true };
349
374
  }
375
+ // A grouped query (oqx 0.24, SEMANTICS §24) yields GROUPS, not store rows:
376
+ // the envelope does not apply — there is no `id`/`path` to inject and no
377
+ // keyset to page by — so a hit is the group's projection as the query wrote
378
+ // it (`{ layer, n }`), exactly what `bare` yields for a row query; `bare` is
379
+ // accepted and changes nothing. Rows that surface as values (`rows collect {
380
+ // }` with an empty projection, `collect(doc)`) render as `{ id, path }` refs
381
+ // like every row-as-value. Paging is by position: the query's own
382
+ // `limit`/`offset` define the set, the tool's `limit` takes the first page,
383
+ // `truncated` says whether more groups follow, the cursor is always null (as
384
+ // under a custom `order by`). `distinct` cannot occur (a parse error).
385
+ if (parsed.groupBy)
386
+ return runGrouped(engine, parsed, consumer, opts);
350
387
  // collect / first / single: inject id + path so every hit carries them. A
351
388
  // top-level `select distinct` is applied HERE, not in the engine: the injected
352
389
  // id/path are unique per row and would defeat the engine's projection dedup, so
@@ -417,6 +454,34 @@ function runResolved(store, repoId, raw, opts) {
417
454
  return { hits: [], truncated, cursor, consumer: "collect", values: page.map((s) => s.proj[VALUE_KEY]) };
418
455
  return { hits: page.map(render), truncated, cursor, consumer: "collect" };
419
456
  }
457
+ // The collect/first/single path of a grouped query (see `runResolved`).
458
+ function runGrouped(engine, parsed, consumer, opts) {
459
+ const topValues = parsed.values;
460
+ // A `values` projection rides as a record under VALUE_KEY and is peeled off
461
+ // the page, as on the row path.
462
+ const select = topValues ? [{ ...parsed.select[0], name: VALUE_KEY }] : parsed.select;
463
+ const { limit: topLimit, offset: topOffset } = parsed;
464
+ const base = consumer === "collect" ? { ...parsed, limit: null, offset: null } : parsed;
465
+ const res = engine.run({ ...base, values: false, select }, []);
466
+ const project = (row) => renderValue(row);
467
+ if (consumer === "first" || consumer === "single") {
468
+ const row = res.consumer === "first" || res.consumer === "single" ? res.row : null;
469
+ if (topValues)
470
+ return { hits: [], truncated: false, cursor: null, consumer, values: row == null ? [] : [project(row)[VALUE_KEY]] };
471
+ return { hits: row == null ? [] : [project(row)], truncated: false, cursor: null, consumer };
472
+ }
473
+ let groups = (res.consumer === "collect" ? res.rows : []).map(project);
474
+ const offset = constBound(topOffset, "offset") ?? 0;
475
+ const limit = constBound(topLimit, "limit");
476
+ if (offset || limit != null)
477
+ groups = groups.slice(offset, limit == null ? undefined : offset + limit);
478
+ const cap = opts.limit ?? 50;
479
+ const truncated = groups.length > cap;
480
+ const page = groups.slice(0, cap);
481
+ if (topValues)
482
+ return { hits: [], truncated, cursor: null, consumer: "collect", values: page.map((g) => g[VALUE_KEY]) };
483
+ return { hits: page, truncated, cursor: null, consumer: "collect" };
484
+ }
420
485
  export async function oqxRunAsync(store, repoId, source, opts = {}, embedQuery) {
421
486
  const phrases = collectSemanticPhrases(source);
422
487
  if (phrases.length === 0)
@@ -1,14 +1,14 @@
1
1
  export declare const SPEC_VERSIONS: {
2
- readonly oqx: "0.23";
2
+ readonly oqx: "0.24";
3
3
  readonly format: "0.2";
4
4
  readonly reconcile: "2.3";
5
- readonly store: "14.2";
5
+ readonly store: "15.0";
6
6
  readonly properties: "1.1";
7
7
  readonly graph: "1.1";
8
8
  readonly search: "2.0";
9
9
  readonly mutate: "3.5";
10
10
  readonly sync: "2.2";
11
- readonly surface: "4.7";
12
- readonly cli: "4.5";
11
+ readonly surface: "4.9";
12
+ readonly cli: "4.6";
13
13
  };
14
14
  export type SpecName = keyof typeof SPEC_VERSIONS;
@@ -13,13 +13,13 @@ export const SPEC_VERSIONS = {
13
13
  oqx: LANGUAGE_VERSION,
14
14
  format: "0.2",
15
15
  reconcile: "2.3",
16
- store: "14.2",
16
+ store: "15.0",
17
17
  properties: "1.1",
18
18
  graph: "1.1",
19
19
  search: "2.0",
20
20
  mutate: "3.5",
21
21
  sync: "2.2",
22
- surface: "4.7",
23
- cli: "4.5",
22
+ surface: "4.9",
23
+ cli: "4.6",
24
24
  };
25
25
  //# sourceMappingURL=spec-versions.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omgbase/core",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "description": "Open Markdown Graph Base — embedded engine",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@modelcontextprotocol/sdk": "^1.30.0",
58
- "@omgbase/oqx": "^0.23.0",
58
+ "@omgbase/oqx": "^0.24.0",
59
59
  "better-sqlite3": "^13.0.3",
60
60
  "mdast-util-from-markdown": "^2.0.2",
61
61
  "mdast-util-frontmatter": "^2.0.1",