@omgbase/core 0.11.1 → 0.19.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 (72) hide show
  1. package/README.md +1 -1
  2. package/dist/src/core/ids.d.ts +4 -0
  3. package/dist/src/core/ids.js +17 -0
  4. package/dist/src/core/ingest.d.ts +39 -3
  5. package/dist/src/core/ingest.js +233 -202
  6. package/dist/src/core/store/schema.d.ts +3 -2
  7. package/dist/src/core/store/schema.js +28 -3
  8. package/dist/src/core/store/size.d.ts +76 -0
  9. package/dist/src/core/store/size.js +223 -0
  10. package/dist/src/core/store/store.d.ts +9 -0
  11. package/dist/src/core/store/store.js +11 -0
  12. package/dist/src/graph/history.js +15 -12
  13. package/dist/src/index.d.ts +12 -5
  14. package/dist/src/index.js +18 -7
  15. package/dist/src/mcp/arguments.d.ts +9 -2
  16. package/dist/src/mcp/arguments.js +15 -2
  17. package/dist/src/mcp/errors.d.ts +1 -1
  18. package/dist/src/mcp/errors.js +3 -0
  19. package/dist/src/mcp/reference.d.ts +1 -1
  20. package/dist/src/mcp/reference.js +27 -6
  21. package/dist/src/mcp/server.d.ts +56 -1
  22. package/dist/src/mcp/server.js +365 -203
  23. package/dist/src/mutate/apply.d.ts +93 -12
  24. package/dist/src/mutate/apply.js +203 -113
  25. package/dist/src/mutate/canonical.d.ts +27 -0
  26. package/dist/src/mutate/canonical.js +138 -0
  27. package/dist/src/mutate/commit.d.ts +150 -0
  28. package/dist/src/mutate/commit.js +349 -0
  29. package/dist/src/mutate/doc-store.d.ts +3 -3
  30. package/dist/src/mutate/doc-store.js +21 -6
  31. package/dist/src/mutate/docs.d.ts +42 -87
  32. package/dist/src/mutate/docs.js +50 -204
  33. package/dist/src/mutate/effects.d.ts +363 -0
  34. package/dist/src/mutate/effects.js +1161 -0
  35. package/dist/src/mutate/materialize.d.ts +49 -0
  36. package/dist/src/mutate/materialize.js +174 -0
  37. package/dist/src/mutate/opset.d.ts +27 -2
  38. package/dist/src/mutate/opset.js +12 -5
  39. package/dist/src/mutate/plan-update.d.ts +1 -46
  40. package/dist/src/mutate/plan-update.js +19 -67
  41. package/dist/src/mutate/plan.d.ts +210 -0
  42. package/dist/src/mutate/plan.js +512 -0
  43. package/dist/src/mutate/select.d.ts +89 -0
  44. package/dist/src/mutate/select.js +879 -0
  45. package/dist/src/mutate/sugar.d.ts +135 -0
  46. package/dist/src/mutate/sugar.js +167 -0
  47. package/dist/src/mutate/tree.d.ts +4 -2
  48. package/dist/src/oqx-js/planner.js +4 -0
  49. package/dist/src/oqx-js/run.d.ts +28 -1
  50. package/dist/src/oqx-js/run.js +85 -22
  51. package/dist/src/spec-versions.d.ts +6 -6
  52. package/dist/src/spec-versions.js +5 -5
  53. package/dist/src/surface-paths.d.ts +17 -26
  54. package/dist/src/surface-paths.js +27 -22
  55. package/dist/src/sync/checkpoint.d.ts +5 -2
  56. package/dist/src/sync/checkpoint.js +10 -6
  57. package/dist/src/sync/freshness.d.ts +10 -7
  58. package/dist/src/sync/freshness.js +15 -17
  59. package/dist/src/sync/observe.d.ts +4 -1
  60. package/dist/src/sync/observe.js +56 -5
  61. package/dist/src/sync/recovery.d.ts +4 -0
  62. package/dist/src/sync/recovery.js +4 -1
  63. package/dist/src/sync/stat-cache.d.ts +7 -0
  64. package/dist/src/sync/stat-cache.js +22 -0
  65. package/dist/src/sync/tombstone.js +23 -19
  66. package/dist/src/sync/watcher.d.ts +2 -0
  67. package/dist/src/sync/watcher.js +4 -1
  68. package/dist/src/sync/writer-lock.d.ts +15 -0
  69. package/dist/src/sync/writer-lock.js +104 -17
  70. package/dist/src/version.d.ts +7 -2
  71. package/dist/src/version.js +5 -2
  72. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
- import { openSync, closeSync, readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from "node:fs";
2
- import { join } from "node:path";
1
+ import { openSync, closeSync, readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync, realpathSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
3
  // Cross-process writer lock (11 §3.2). The write protocol's per-repo writer
4
4
  // mutex (04 §6 step 1) generalizes to an advisory lock on
5
5
  // <workspace>/.omgbase/writer.lock, held for steps 2–7. All writers — one-shot
@@ -72,33 +72,120 @@ const SLEEP_CELL = new Int32Array(new SharedArrayBuffer(4));
72
72
  function sleepSync(ms) {
73
73
  Atomics.wait(SLEEP_CELL, 0, 0, ms);
74
74
  }
75
- /**
76
- * Run fn while holding the workspace writer lock. Blocks (polling) until the
77
- * lock is free or the timeout elapses, then releases the lock even if fn throws.
78
- */
79
- export function withWriterLock(omgbaseDir, fn, opts = {}) {
75
+ // Re-entrancy (one acquire per host write, spec/sync §7). The lock is a
76
+ // per-PROCESS claim — the record names a pid and liveness is the pid's — so a
77
+ // second `withWriterLock` on the same directory from the holder must not poll
78
+ // against itself (it would see its own live pid and time out). The process
79
+ // keeps a depth per lock path: the first entry acquires the file, nested
80
+ // entries run their fn directly, and the exit that brings the depth back to
81
+ // zero releases. Nesting happens by design — a tool-boundary lock around a
82
+ // whole MCP call (`mcp/server.ts`) encloses the kernel's own commit-phase lock
83
+ // (`mutate/apply.ts`), and a watcher checkpoint may run while a verb holds the
84
+ // lock — and since the file is one claim per process, the depth also lets an
85
+ // async holder (a checkpoint awaiting the adapter) and a tool call interleave
86
+ // on the event loop without either releasing the other's claim. Cross-process
87
+ // semantics are unchanged: a foreign live holder is waited for, never stolen.
88
+ const held = new Map();
89
+ // The depth is keyed by the directory's real path, so two spellings of one
90
+ // workspace (relative/absolute, a symlinked tree) are one claim; a directory
91
+ // that does not exist yet (a probe before the first write) keys by its resolved path.
92
+ function lockPathOf(omgbaseDir) {
93
+ let dir;
94
+ try {
95
+ dir = realpathSync.native(omgbaseDir);
96
+ }
97
+ catch {
98
+ dir = resolve(omgbaseDir);
99
+ }
100
+ return join(dir, LOCK_NAME);
101
+ }
102
+ // Process-wide defaults for the wait (spec/sync §7: 5 s, polled every 25 ms).
103
+ // A host may override them for the whole process — the CLI's
104
+ // `OMGBASE_WRITER_LOCK_TIMEOUT_MS` test seam does, so a verb's kernel-level lock
105
+ // and `omg mcp`'s tool-boundary lock shorten together, like the Rust binary's.
106
+ // Per-call `opts` still win.
107
+ let defaults = { timeoutMs: 5000, pollMs: 25 };
108
+ /** Override the process-wide wait defaults (a test seam; the poll never outlasts the timeout). */
109
+ export function setWriterLockDefaults(opts) {
80
110
  const timeoutMs = opts.timeoutMs ?? 5000;
81
- const pollMs = opts.pollMs ?? 25;
82
- mkdirSync(omgbaseDir, { recursive: true });
83
- const lockPath = join(omgbaseDir, LOCK_NAME);
111
+ defaults = { timeoutMs, pollMs: Math.min(opts.pollMs ?? 25, timeoutMs) };
112
+ }
113
+ /** The process-wide wait defaults in force. */
114
+ export function writerLockDefaults() {
115
+ return { ...defaults };
116
+ }
117
+ function acquire(lockPath, opts) {
118
+ const timeoutMs = opts.timeoutMs ?? defaults.timeoutMs;
119
+ const pollMs = opts.pollMs ?? defaults.pollMs;
84
120
  const deadline = Date.now() + timeoutMs;
85
121
  while (!tryAcquire(lockPath)) {
86
122
  if (Date.now() >= deadline)
87
123
  throw new WriterLockTimeout(lockPath, readHolderPid(lockPath));
88
124
  sleepSync(pollMs);
89
125
  }
126
+ }
127
+ function release(lockPath) {
128
+ try {
129
+ if (existsSync(lockPath))
130
+ unlinkSync(lockPath);
131
+ }
132
+ catch {
133
+ /* best-effort release */
134
+ }
135
+ }
136
+ /** Enter the lock: acquire the file at depth 0, otherwise just deepen. */
137
+ function enter(omgbaseDir, opts) {
138
+ mkdirSync(omgbaseDir, { recursive: true });
139
+ const lockPath = lockPathOf(omgbaseDir);
140
+ const depth = held.get(lockPath) ?? 0;
141
+ if (depth === 0)
142
+ acquire(lockPath, opts);
143
+ held.set(lockPath, depth + 1);
144
+ return lockPath;
145
+ }
146
+ /** Leave the lock: shallow, and release the file when the depth reaches 0. */
147
+ function exit(lockPath) {
148
+ const depth = (held.get(lockPath) ?? 1) - 1;
149
+ if (depth > 0) {
150
+ held.set(lockPath, depth);
151
+ return;
152
+ }
153
+ held.delete(lockPath);
154
+ release(lockPath);
155
+ }
156
+ /**
157
+ * Run fn while holding the workspace writer lock. Blocks (polling) until the
158
+ * lock is free or the timeout elapses, then releases the lock even if fn throws.
159
+ * Re-entrant within the process: a nested call on the same directory runs fn
160
+ * directly and the outermost exit releases.
161
+ */
162
+ export function withWriterLock(omgbaseDir, fn, opts = {}) {
163
+ const lockPath = enter(omgbaseDir, opts);
90
164
  try {
91
165
  return fn();
92
166
  }
93
167
  finally {
94
- try {
95
- if (existsSync(lockPath))
96
- unlinkSync(lockPath);
97
- }
98
- catch {
99
- /* best-effort release */
100
- }
168
+ exit(lockPath);
169
+ }
170
+ }
171
+ /**
172
+ * The async form: the lock is held until the promise `fn` returns settles (a
173
+ * sync `withWriterLock` around an async fn would release at the first await).
174
+ * Acquisition still polls synchronously — the wait is the same bounded
175
+ * contention window as the sync form's. Same re-entrancy.
176
+ */
177
+ export async function withWriterLockAsync(omgbaseDir, fn, opts = {}) {
178
+ const lockPath = enter(omgbaseDir, opts);
179
+ try {
180
+ return await fn();
101
181
  }
182
+ finally {
183
+ exit(lockPath);
184
+ }
185
+ }
186
+ /** True while THIS process holds the lock (at any depth) — a test seam for "ran under the lock". */
187
+ export function writerLockHeld(omgbaseDir) {
188
+ return (held.get(lockPathOf(omgbaseDir)) ?? 0) > 0;
102
189
  }
103
190
  /** True if no live writer holds the lock (probe without acquiring). */
104
191
  export function writerLockFree(omgbaseDir) {
@@ -1,5 +1,6 @@
1
1
  import type { Store } from "./core/store/store.js";
2
2
  import { SPEC_VERSIONS } from "./spec-versions.js";
3
+ import type { ToolsProfile } from "./mcp/server.js";
3
4
  export interface HostInfo {
4
5
  /** the serving binary's own version — `omgbase`'s package.json (what `--version` prints) */
5
6
  version: string;
@@ -25,6 +26,8 @@ export interface VersionInfo {
25
26
  runtime: string;
26
27
  commit: string | null;
27
28
  built: string | null;
29
+ /** the catalog profile the server serves (spec/surface §4 "Profiles", 4.3): `omg version` itself reports `full` */
30
+ profile: ToolsProfile;
28
31
  }
29
32
  /** The omgbase packages the reference binary is built from (spec/surface §4). */
30
33
  export declare const COMPONENT_PACKAGES: readonly ["omgbase", "@omgbase/core", "@omgbase/oqx", "@omgbase/sync", "@omgbase/fs-adapter"];
@@ -34,6 +37,8 @@ export declare function nearestPackageVersion(fromUrl: string, name: string): st
34
37
  export declare function coreVersion(): string;
35
38
  /**
36
39
  * The `version` result. `store` supplies `schema`; `host` the binary's own
37
- * version, build info and resolution anchor (see the module comment).
40
+ * version, build info and resolution anchor (see the module comment);
41
+ * `profile` the catalog profile served (the MCP server's `--tools`; the CLI's
42
+ * own `version` verb is the full library and says `full`).
38
43
  */
39
- export declare function versionInfo(store?: Store | null, host?: HostInfo): VersionInfo;
44
+ export declare function versionInfo(store?: Store | null, host?: HostInfo, profile?: ToolsProfile): VersionInfo;
@@ -73,9 +73,11 @@ export function coreVersion() {
73
73
  }
74
74
  /**
75
75
  * The `version` result. `store` supplies `schema`; `host` the binary's own
76
- * version, build info and resolution anchor (see the module comment).
76
+ * version, build info and resolution anchor (see the module comment);
77
+ * `profile` the catalog profile served (the MCP server's `--tools`; the CLI's
78
+ * own `version` verb is the full library and says `full`).
77
79
  */
78
- export function versionInfo(store, host) {
80
+ export function versionInfo(store, host, profile = "full") {
79
81
  const anchors = [...(host?.resolveFrom ? [host.resolveFrom] : []), import.meta.url];
80
82
  const components = {};
81
83
  for (const name of COMPONENT_PACKAGES) {
@@ -98,6 +100,7 @@ export function versionInfo(store, host) {
98
100
  runtime: `node ${process.versions.node}`,
99
101
  commit: host?.commit ?? null,
100
102
  built: host?.built ?? null,
103
+ profile,
101
104
  };
102
105
  }
103
106
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omgbase/core",
3
- "version": "0.11.1",
3
+ "version": "0.19.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.20.0",
58
+ "@omgbase/oqx": "^0.21.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",