@omgbase/core 0.3.0 → 0.4.1

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 (45) hide show
  1. package/dist/src/core/ids.d.ts +34 -3
  2. package/dist/src/core/ids.js +79 -6
  3. package/dist/src/core/ingest.js +15 -12
  4. package/dist/src/core/read/document.js +6 -1
  5. package/dist/src/core/read/nodes.js +5 -1
  6. package/dist/src/core/store/fts.d.ts +47 -7
  7. package/dist/src/core/store/fts.js +73 -9
  8. package/dist/src/core/store/rebuild.js +5 -4
  9. package/dist/src/core/store/store.d.ts +4 -0
  10. package/dist/src/core/store/store.js +40 -1
  11. package/dist/src/format/index.d.ts +12 -1
  12. package/dist/src/format/index.js +14 -1
  13. package/dist/src/format/markdown.d.ts +7 -0
  14. package/dist/src/format/markdown.js +7 -0
  15. package/dist/src/graph/history.d.ts +21 -0
  16. package/dist/src/graph/history.js +48 -6
  17. package/dist/src/graph/links.d.ts +2 -0
  18. package/dist/src/graph/links.js +22 -11
  19. package/dist/src/index.d.ts +8 -6
  20. package/dist/src/index.js +7 -5
  21. package/dist/src/mcp/errors.d.ts +8 -0
  22. package/dist/src/mcp/errors.js +17 -3
  23. package/dist/src/mcp/server.js +50 -57
  24. package/dist/src/mutate/apply.js +12 -4
  25. package/dist/src/mutate/doc-store.js +4 -1
  26. package/dist/src/mutate/docs.d.ts +19 -0
  27. package/dist/src/mutate/docs.js +64 -16
  28. package/dist/src/mutate/ops.js +25 -13
  29. package/dist/src/oqx-js/context.d.ts +11 -0
  30. package/dist/src/oqx-js/context.js +16 -0
  31. package/dist/src/oqx-js/planner.d.ts +20 -1
  32. package/dist/src/oqx-js/planner.js +95 -2
  33. package/dist/src/oqx-js/run.js +27 -2
  34. package/dist/src/oqx-js/sql/translate.d.ts +94 -1
  35. package/dist/src/oqx-js/sql/translate.js +263 -79
  36. package/dist/src/search/tasks.d.ts +15 -1
  37. package/dist/src/search/tasks.js +31 -3
  38. package/dist/src/sync/admin.d.ts +13 -0
  39. package/dist/src/sync/admin.js +11 -0
  40. package/dist/src/sync/external-source.js +20 -7
  41. package/dist/src/sync/plugin.d.ts +30 -5
  42. package/dist/src/sync/plugin.js +13 -1
  43. package/dist/src/sync/watcher.d.ts +2 -0
  44. package/dist/src/sync/watcher.js +10 -2
  45. package/package.json +1 -1
@@ -17,7 +17,7 @@ export async function createExternalSource(spec) {
17
17
  }
18
18
  const rl = createInterface({ input: child.stdout });
19
19
  let watchListener = null;
20
- const router = messageRouter(rl, (paths) => watchListener?.(paths));
20
+ const router = messageRouter(rl, (ev) => watchListener?.(ev));
21
21
  const died = new Promise((_, reject) => {
22
22
  child.once("error", (err) => reject(new Error(`sync adapter '${spec.command}' failed to spawn: ${err.message}`)));
23
23
  child.once("exit", (code) => reject(new Error(`sync adapter '${spec.command}' exited early (code ${code ?? "?"})`)));
@@ -69,10 +69,19 @@ export async function createExternalSource(spec) {
69
69
  },
70
70
  ...(caps.watch
71
71
  ? {
72
- async watch(onBatch) {
73
- watchListener = onBatch;
72
+ async watch(listener) {
73
+ // `ready` resolves on the adapter's {"event":"ready"}; the listener sees
74
+ // it as an event too, in wire order with the batches (spec/sync §5, §8).
75
+ let markReady = () => { };
76
+ const ready = new Promise((resolve) => { markReady = resolve; });
77
+ watchListener = (ev) => {
78
+ if (ev.event === "ready")
79
+ markReady();
80
+ listener(ev);
81
+ };
74
82
  await call("watch");
75
83
  return {
84
+ ready,
76
85
  async stop() {
77
86
  watchListener = null;
78
87
  try {
@@ -105,9 +114,9 @@ export async function createExternalSource(spec) {
105
114
  };
106
115
  return source;
107
116
  }
108
- // Demultiplex adapter stdout: {"event":"batch"} lines feed the watch listener;
109
- // every other line is an id-addressed response buffered for nextResponse().
110
- function messageRouter(rl, onBatch) {
117
+ // Demultiplex adapter stdout: {"event":…} lines feed the watch listener (ready,
118
+ // batch); every other line is an id-addressed response buffered for nextResponse().
119
+ function messageRouter(rl, onEvent) {
111
120
  const buffered = [];
112
121
  const waiters = [];
113
122
  rl.on("line", (line) => {
@@ -117,7 +126,11 @@ function messageRouter(rl, onBatch) {
117
126
  try {
118
127
  const peek = JSON.parse(trimmed);
119
128
  if (peek.event === "batch") {
120
- onBatch(peek.paths ?? []);
129
+ onEvent({ event: "batch", paths: peek.paths ?? [] });
130
+ return;
131
+ }
132
+ if (peek.event === "ready") {
133
+ onEvent({ event: "ready" });
121
134
  return;
122
135
  }
123
136
  }
@@ -25,13 +25,38 @@ export interface SourceItem extends SourceEntry {
25
25
  /** The engine hashes this itself for the authoritative echo/convergence gate. */
26
26
  content: string;
27
27
  }
28
- /** A batch of changed storage-keys delivered by a live watch (13 §4.3). */
29
- export type WatchListener = (paths: string[]) => void;
28
+ /**
29
+ * An unsolicited event of a live watch (spec/sync §5): `ready` once — the feed
30
+ * is primed and every change from now on will be reported — then `batch` lines
31
+ * of changed storage keys. An adapter built before sync 1.2 never sends `ready`.
32
+ */
33
+ export type WatchEvent = {
34
+ event: "ready";
35
+ } | {
36
+ event: "batch";
37
+ paths: string[];
38
+ };
39
+ /** Receives every event of a live watch, in wire order. */
40
+ export type WatchListener = (event: WatchEvent) => void;
30
41
  /** A live subscription created by SyncSource.watch. */
31
42
  export interface SourceWatch {
32
- /** Stop delivering batches and release resources. */
43
+ /**
44
+ * Resolves when the source reported `ready` (spec/sync §5). An in-process
45
+ * source resolves it at once; an adapter that never reports leaves it pending,
46
+ * so a host bounds the wait (`awaitReady`).
47
+ */
48
+ ready: Promise<void>;
49
+ /** Stop delivering events and release resources. */
33
50
  stop(): Promise<void>;
34
51
  }
52
+ /** How long a host waits for `ready` before proceeding as if ready (spec/sync §5: unpinned; 30 s). */
53
+ export declare const WATCH_READY_PATIENCE_MS = 30000;
54
+ /**
55
+ * Wait for a watch's `ready` with bounded patience: `true` when it resolved in
56
+ * time, `false` on timeout (the host proceeds as if ready, with a warning — an
57
+ * adapter without `ready` still works, with the old window).
58
+ */
59
+ export declare function awaitReady(ready: Promise<void>, patienceMs?: number): Promise<boolean>;
35
60
  /**
36
61
  * The in-engine handle to an external source process. Only capabilities/
37
62
  * enumerate/fetch are always meaningful; watch exists when capabilities().watch,
@@ -43,8 +68,8 @@ export interface SyncSource {
43
68
  enumerate(): Promise<SourceEntry[]>;
44
69
  /** Current state of one member, or null if it left the scope (a delete). */
45
70
  fetch(path: string): Promise<SourceItem | null>;
46
- /** Subscribe to debounced change batches (push sources). */
47
- watch?(onBatch: WatchListener): Promise<SourceWatch>;
71
+ /** Subscribe to the change feed (push sources): `ready` once, then debounced `batch` events. */
72
+ watch?(listener: WatchListener): Promise<SourceWatch>;
48
73
  /** Persist engine-authored bytes back to the source (writeThrough only). */
49
74
  write?(path: string, content: string): Promise<void>;
50
75
  /** Remove a member from the source (writeThrough only). */
@@ -8,5 +8,17 @@
8
8
  // reconciliation, commits, convergence, and durable revision/cursor state — so a
9
9
  // source only reports membership and transports bytes; it can never write a
10
10
  // commit, mint identity, or bypass the convergence check.
11
- export {};
11
+ /** How long a host waits for `ready` before proceeding as if ready (spec/sync §5: unpinned; 30 s). */
12
+ export const WATCH_READY_PATIENCE_MS = 30_000;
13
+ /**
14
+ * Wait for a watch's `ready` with bounded patience: `true` when it resolved in
15
+ * time, `false` on timeout (the host proceeds as if ready, with a warning — an
16
+ * adapter without `ready` still works, with the old window).
17
+ */
18
+ export function awaitReady(ready, patienceMs = WATCH_READY_PATIENCE_MS) {
19
+ return new Promise((resolve) => {
20
+ const timer = setTimeout(() => resolve(false), patienceMs);
21
+ ready.then(() => { clearTimeout(timer); resolve(true); }, () => { clearTimeout(timer); resolve(false); });
22
+ });
23
+ }
12
24
  //# sourceMappingURL=plugin.js.map
@@ -14,6 +14,8 @@ export declare class Watcher {
14
14
  private sub;
15
15
  constructor(store: Store, repoId: string, source: SyncSource, opts?: WatcherOptions);
16
16
  start(): Promise<void>;
17
+ /** The live subscription's `ready` (spec/sync §5); throws before `start()`. */
18
+ ready(): Promise<void>;
17
19
  private ingest;
18
20
  stop(): Promise<void>;
19
21
  }
@@ -14,11 +14,19 @@ export class Watcher {
14
14
  async start() {
15
15
  if (!this.source.watch)
16
16
  throw new Error("source does not support watch()");
17
- this.sub = await this.source.watch((paths) => {
17
+ this.sub = await this.source.watch((ev) => {
18
+ if (ev.event !== "batch")
19
+ return; // `ready` is surfaced through ready()
18
20
  // A batch arrives from the adapter's stream; reconcile it off the callback.
19
- void this.ingest(paths).catch((err) => this.opts.onError?.(err));
21
+ void this.ingest(ev.paths).catch((err) => this.opts.onError?.(err));
20
22
  });
21
23
  }
24
+ /** The live subscription's `ready` (spec/sync §5); throws before `start()`. */
25
+ ready() {
26
+ if (!this.sub)
27
+ throw new Error("watcher not started");
28
+ return this.sub.ready;
29
+ }
22
30
  async ingest(paths) {
23
31
  if (paths.length === 0)
24
32
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omgbase/core",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Open Markdown Graph Base — embedded engine",
5
5
  "license": "MIT",
6
6
  "repository": {