@vendoai/store 0.1.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 (112) hide show
  1. package/README.md +81 -0
  2. package/dist/blobs.d.ts +5 -0
  3. package/dist/blobs.d.ts.map +1 -0
  4. package/dist/blobs.js +62 -0
  5. package/dist/blobs.js.map +1 -0
  6. package/dist/crypto.d.ts +10 -0
  7. package/dist/crypto.d.ts.map +1 -0
  8. package/dist/crypto.js +44 -0
  9. package/dist/crypto.js.map +1 -0
  10. package/dist/db.d.ts +26 -26
  11. package/dist/db.d.ts.map +1 -0
  12. package/dist/db.js +335 -86
  13. package/dist/db.js.map +1 -0
  14. package/dist/erase.d.ts +33 -0
  15. package/dist/erase.d.ts.map +1 -0
  16. package/dist/erase.js +107 -0
  17. package/dist/erase.js.map +1 -0
  18. package/dist/helpers/approvals.d.ts +12 -0
  19. package/dist/helpers/approvals.d.ts.map +1 -0
  20. package/dist/helpers/approvals.js +37 -0
  21. package/dist/helpers/approvals.js.map +1 -0
  22. package/dist/helpers/apps.d.ts +15 -0
  23. package/dist/helpers/apps.d.ts.map +1 -0
  24. package/dist/helpers/apps.js +42 -0
  25. package/dist/helpers/apps.js.map +1 -0
  26. package/dist/helpers/audit.d.ts +24 -0
  27. package/dist/helpers/audit.d.ts.map +1 -0
  28. package/dist/helpers/audit.js +79 -0
  29. package/dist/helpers/audit.js.map +1 -0
  30. package/dist/helpers/grants.d.ts +14 -0
  31. package/dist/helpers/grants.d.ts.map +1 -0
  32. package/dist/helpers/grants.js +46 -0
  33. package/dist/helpers/grants.js.map +1 -0
  34. package/dist/helpers/rows.d.ts +30 -0
  35. package/dist/helpers/rows.d.ts.map +1 -0
  36. package/dist/helpers/rows.js +191 -0
  37. package/dist/helpers/rows.js.map +1 -0
  38. package/dist/helpers/runs.d.ts +19 -0
  39. package/dist/helpers/runs.d.ts.map +1 -0
  40. package/dist/helpers/runs.js +47 -0
  41. package/dist/helpers/runs.js.map +1 -0
  42. package/dist/helpers/state.d.ts +9 -0
  43. package/dist/helpers/state.d.ts.map +1 -0
  44. package/dist/helpers/state.js +22 -0
  45. package/dist/helpers/state.js.map +1 -0
  46. package/dist/helpers/subjects.d.ts +32 -0
  47. package/dist/helpers/subjects.d.ts.map +1 -0
  48. package/dist/helpers/subjects.js +78 -0
  49. package/dist/helpers/subjects.js.map +1 -0
  50. package/dist/helpers/threads.d.ts +19 -0
  51. package/dist/helpers/threads.d.ts.map +1 -0
  52. package/dist/helpers/threads.js +38 -0
  53. package/dist/helpers/threads.js.map +1 -0
  54. package/dist/helpers/types.d.ts +60 -0
  55. package/dist/helpers/types.d.ts.map +1 -0
  56. package/dist/helpers/types.js +2 -0
  57. package/dist/helpers/types.js.map +1 -0
  58. package/dist/helpers/utils.d.ts +37 -0
  59. package/dist/helpers/utils.d.ts.map +1 -0
  60. package/dist/helpers/utils.js +94 -0
  61. package/dist/helpers/utils.js.map +1 -0
  62. package/dist/index.d.ts +13 -11
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +17 -15
  65. package/dist/index.js.map +1 -0
  66. package/dist/records.d.ts +7 -0
  67. package/dist/records.d.ts.map +1 -0
  68. package/dist/records.js +208 -0
  69. package/dist/records.js.map +1 -0
  70. package/dist/routing.d.ts +7 -0
  71. package/dist/routing.d.ts.map +1 -0
  72. package/dist/routing.js +393 -0
  73. package/dist/routing.js.map +1 -0
  74. package/dist/schema.d.ts +18 -1420
  75. package/dist/schema.d.ts.map +1 -0
  76. package/dist/schema.js +206 -114
  77. package/dist/schema.js.map +1 -0
  78. package/dist/secrets.d.ts +13 -0
  79. package/dist/secrets.d.ts.map +1 -0
  80. package/dist/secrets.js +86 -0
  81. package/dist/secrets.js.map +1 -0
  82. package/dist/sessions.d.ts +53 -0
  83. package/dist/sessions.d.ts.map +1 -0
  84. package/dist/sessions.js +85 -0
  85. package/dist/sessions.js.map +1 -0
  86. package/dist/store.d.ts +24 -0
  87. package/dist/store.d.ts.map +1 -0
  88. package/dist/store.js +49 -0
  89. package/dist/store.js.map +1 -0
  90. package/dist/validate.d.ts +25 -0
  91. package/dist/validate.d.ts.map +1 -0
  92. package/dist/validate.js +144 -0
  93. package/dist/validate.js.map +1 -0
  94. package/package.json +15 -14
  95. package/dist/automation-store.d.ts +0 -62
  96. package/dist/automation-store.js +0 -675
  97. package/dist/connections-store.d.ts +0 -25
  98. package/dist/connections-store.js +0 -102
  99. package/dist/decision-store.d.ts +0 -8
  100. package/dist/decision-store.js +0 -39
  101. package/dist/meta.d.ts +0 -5
  102. package/dist/meta.js +0 -20
  103. package/dist/thread-store.d.ts +0 -5
  104. package/dist/thread-store.js +0 -209
  105. package/dist/vendo-registry.d.ts +0 -5
  106. package/dist/vendo-registry.js +0 -52
  107. package/migrations/.gitkeep +0 -0
  108. package/migrations/0000_living_kate_bishop.sql +0 -110
  109. package/migrations/0001_brainy_tinkerer.sql +0 -13
  110. package/migrations/meta/0000_snapshot.json +0 -757
  111. package/migrations/meta/0001_snapshot.json +0 -859
  112. package/migrations/meta/_journal.json +0 -20
package/dist/db.js CHANGED
@@ -1,105 +1,354 @@
1
1
  import { PGlite } from "@electric-sql/pglite";
2
- import { drizzle as drizzlePglite } from "drizzle-orm/pglite";
3
- import { migrate as migratePglite } from "drizzle-orm/pglite/migrator";
4
- import { drizzle as drizzlePg } from "drizzle-orm/node-postgres";
5
- import { migrate as migratePg } from "drizzle-orm/node-postgres/migrator";
6
- import { sql } from "drizzle-orm";
7
2
  import { Client, Pool } from "pg";
8
- import { fileURLToPath } from "node:url";
9
3
  import fs from "node:fs";
10
- import path from "node:path";
4
+ import { join, resolve } from "node:path";
11
5
  const SERVERLESS_ENVS = ["VERCEL", "CF_PAGES", "AWS_LAMBDA_FUNCTION_NAME"];
12
- const MIGRATIONS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "migrations");
13
- const ADVISORY_LOCK_KEY = 7461001;
14
- const registry = (globalThis["__vendoStoreRegistry"] ??= {
15
- instances: new Map(),
16
- migrated: new Map(),
17
- });
18
- /**
19
- * pg.Pool re-emits idle-client errors (backend restart, dropped connection)
20
- * on itself; with no 'error' listener Node escalates that to an uncaught
21
- * exception and kills the host process. The pool already discards the dead
22
- * client log and keep serving.
23
- */
24
- export function createPgPool(connectionString) {
25
- const pool = new Pool({ connectionString });
26
- pool.on("error", (err) => console.error("[vendo] postgres pool: idle connection error (recovering)", err));
27
- return pool;
6
+ const ADVISORY_LOCK_KEY = 7_461_001;
7
+ const pgUrls = new WeakMap();
8
+ function preparePgliteDir(dataDir) {
9
+ if (dataDir.startsWith("memory://"))
10
+ return;
11
+ try {
12
+ fs.mkdirSync(dataDir, { recursive: true });
13
+ fs.accessSync(dataDir, fs.constants.W_OK);
14
+ }
15
+ catch (error) {
16
+ const cause = error instanceof Error ? error.message : String(error);
17
+ throw new Error(`[vendo] PGlite data directory "${dataDir}" is not writable — fix its permissions or choose another dataDir. Cause: ${cause}`);
18
+ }
28
19
  }
29
- export function createVendoDatabase(config = {}) {
30
- // Precedence: explicit connectionString > explicit pglite > env DATABASE_URL
31
- // > default PGlite an explicitly passed `pglite` config must not lose to
32
- // an ambient DATABASE_URL env var.
33
- // `||` (not `??`): a set-but-empty DATABASE_URL means "no connection string",
34
- // and must not become a shared "" cache key across different PGlite dirs.
35
- const conn = config.connectionString || (config.pglite ? undefined : process.env["DATABASE_URL"] || undefined);
36
- const dataDir = config.pglite?.dataDir ?? process.env["VENDO_DATA_DIR"] ?? ".vendo/data";
37
- const cacheKey = conn ?? `pglite:${dataDir}`;
38
- const existing = registry.instances.get(cacheKey);
39
- if (existing)
40
- return existing;
41
- const created = (async () => {
42
- if (conn)
43
- return { kind: "pg", db: drizzlePg(createPgPool(conn)), cacheKey };
44
- const onServerless = SERVERLESS_ENVS.find((e) => process.env[e]);
45
- if (onServerless) {
46
- throw new Error(`[vendo] PGlite (the zero-config store) cannot run on ${onServerless} — filesystems there are ephemeral. ` +
47
- `Set DATABASE_URL to a hosted Postgres (Supabase, Neon, …) instead.`);
20
+ /** ENG-350 a dev server killed uncleanly leaves postmaster.pid in the data
21
+ dir, and PGlite.create can then hard-abort with a raw `Aborted()`
22
+ (electric-sql/pglite#2, #794). PGlite's wasm postgres always records
23
+ Emscripten's fake pid (-42), so a non-positive or unparseable pid can never
24
+ belong to a live owner stale by definition. A real positive pid (a native
25
+ postgres dir, or a custom writer) is only stale once that process is gone. */
26
+ function readPgliteLock(dataDir) {
27
+ if (dataDir.startsWith("memory://"))
28
+ return undefined;
29
+ const lockPath = join(dataDir, "postmaster.pid");
30
+ let firstLine;
31
+ try {
32
+ firstLine = fs.readFileSync(lockPath, "utf8").split("\n", 1)[0] ?? "";
33
+ }
34
+ catch {
35
+ return undefined; // no lock file — nothing to heal
36
+ }
37
+ const pid = Number.parseInt(firstLine.trim(), 10);
38
+ if (Number.isFinite(pid) && pid > 0 && isPidAlive(pid))
39
+ return { path: lockPath, livePid: pid };
40
+ return { path: lockPath };
41
+ }
42
+ function isPidAlive(pid) {
43
+ try {
44
+ process.kill(pid, 0);
45
+ return true;
46
+ }
47
+ catch (error) {
48
+ // EPERM = exists but owned by someone else — still alive.
49
+ return error.code === "EPERM";
50
+ }
51
+ }
52
+ const errorMessage = (error) => (error instanceof Error ? error.message : String(error));
53
+ /* ENG-351 — single-writer discipline for the PGlite default.
54
+
55
+ Under `next dev` the host's server module graph is evaluated by more than
56
+ one OS process (Turbopack spawns transient evaluator workers) and, after an
57
+ HMR edit, more than once within one process. Each evaluation runs
58
+ `createVendo` → `createStore` → PGlite on the SAME data dir. PGlite has no
59
+ cross-process locking, and even a second instance's STARTUP (WAL recovery,
60
+ pg_control writes) tears the live writer's state — the dir later reopens
61
+ with a raw `Aborted()` and the last turns are gone (ENG-351's field trace).
62
+
63
+ Two layers, both keyed by the resolved data dir:
64
+ - In-process: a globalThis registry shares ONE live driver across every
65
+ handle (and every HMR copy of this module), refcounted so closing one
66
+ handle never tears the driver out from under another.
67
+ - Cross-process: a `<dataDir>/.vendo-writer.lock` pidfile taken BEFORE
68
+ PGlite.create. A second process WAITS (it never touches postgres files)
69
+ and takes over only once the holder is provably gone — recorded pid dead,
70
+ or the holder's mtime refresh stopped long ago (pid-reuse fallback). Lock
71
+ release rides close(); a SIGKILLed holder self-heals via the dead-pid
72
+ check, which is also what makes the ENG-350 postmaster.pid heal safe:
73
+ only the lock holder ever reaches it. */
74
+ const LOCK_TOUCH_MS = 5_000;
75
+ const LOCK_STALE_MTIME_MS = 30_000;
76
+ const LOCK_POLL_MS = 250;
77
+ const LOCK_WARN_AFTER_MS = 5_000;
78
+ const LOCK_REWARN_EVERY_MS = 60_000;
79
+ function readLockPid(lockPath) {
80
+ try {
81
+ const pid = Number.parseInt(fs.readFileSync(lockPath, "utf8").split("\n", 1)[0] ?? "", 10);
82
+ return Number.isFinite(pid) ? pid : undefined;
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ }
88
+ /** The holder is provably gone: its recorded pid is dead, it recorded THIS
89
+ process (a crashed predecessor with our reused pid — a live foreign holder
90
+ can never be us), or it stopped refreshing the lock's mtime long ago. */
91
+ function lockIsStale(lockPath) {
92
+ const pid = readLockPid(lockPath);
93
+ if (pid !== undefined && (pid === process.pid || !isPidAlive(pid)))
94
+ return true;
95
+ try {
96
+ return Date.now() - fs.statSync(lockPath).mtimeMs > LOCK_STALE_MTIME_MS;
97
+ }
98
+ catch {
99
+ return false; // vanished — the acquire loop just retries
100
+ }
101
+ }
102
+ async function acquirePgliteDirLock(dataDir) {
103
+ const lockPath = join(resolve(dataDir), ".vendo-writer.lock");
104
+ const startedAt = Date.now();
105
+ let warnedAt;
106
+ for (;;) {
107
+ try {
108
+ fs.writeFileSync(lockPath, `${process.pid}\n`, { flag: "wx" });
109
+ break;
110
+ }
111
+ catch (error) {
112
+ if (error.code !== "EEXIST") {
113
+ throw new Error(`[vendo] cannot create the PGlite lock file "${lockPath}" — fix permissions on its directory or choose another dataDir. Cause: ${errorMessage(error)}`);
114
+ }
115
+ }
116
+ if (lockIsStale(lockPath)) {
117
+ fs.rmSync(lockPath, { force: true });
118
+ continue; // re-race the wx create; exactly one contender wins
48
119
  }
49
- if (!dataDir.startsWith("memory://")) {
120
+ const waitedMs = Date.now() - startedAt;
121
+ if (waitedMs >= LOCK_WARN_AFTER_MS && (warnedAt === undefined || Date.now() - warnedAt >= LOCK_REWARN_EVERY_MS)) {
122
+ warnedAt = Date.now();
123
+ console.warn(`[vendo] PGlite data directory "${dataDir}" is held by another process (pid ${readLockPid(lockPath) ?? "unknown"}) — waiting for it to release the lock. If a second server runs on this dataDir, stop it or point it at its own dataDir / a Postgres url.`);
124
+ }
125
+ await wait(LOCK_POLL_MS);
126
+ }
127
+ // Refresh mtime while held so waiters can tell a live holder from a
128
+ // pid-reused ghost; unref'd so the lock never keeps the process alive.
129
+ const touch = setInterval(() => {
130
+ try {
131
+ const now = new Date();
132
+ fs.utimesSync(lockPath, now, now);
133
+ }
134
+ catch {
135
+ // Lock stolen or dir gone — nothing to refresh; release() still guards on pid.
136
+ }
137
+ }, LOCK_TOUCH_MS);
138
+ touch.unref?.();
139
+ let released = false;
140
+ return {
141
+ release() {
142
+ if (released)
143
+ return;
144
+ released = true;
145
+ clearInterval(touch);
50
146
  try {
51
- fs.mkdirSync(dataDir, { recursive: true });
52
- fs.accessSync(dataDir, fs.constants.W_OK);
147
+ if (readLockPid(lockPath) === process.pid)
148
+ fs.rmSync(lockPath, { force: true });
53
149
  }
54
- catch (err) {
55
- // Fail boot loudly never fall back to a silent ephemeral store.
56
- throw new Error(`[vendo] PGlite data directory "${dataDir}" is not writable — ` +
57
- `fix its permissions or point VENDO_DATA_DIR elsewhere. Cause: ${err instanceof Error ? err.message : String(err)}`);
150
+ catch {
151
+ // Best-effort: a leftover lock self-heals via the dead-pid check.
58
152
  }
59
- }
60
- const client = await PGlite.create(dataDir);
61
- return { kind: "pglite", db: drizzlePglite(client), cacheKey };
62
- })();
63
- created.catch(() => registry.instances.delete(cacheKey)); // failed boots retry
64
- registry.instances.set(cacheKey, created);
65
- return created;
153
+ },
154
+ };
155
+ }
156
+ const wait = (ms) => new Promise((done) => setTimeout(done, ms));
157
+ const PGLITE_REGISTRY_KEY = Symbol.for("vendoai.store.pglite-registry@1");
158
+ function pgliteRegistry() {
159
+ const holder = globalThis;
160
+ return (holder[PGLITE_REGISTRY_KEY] ??= new Map());
66
161
  }
67
- /** Idempotent, race-safe (advisory lock on real PG), memoized per handle. */
68
- export function migrateVendoDatabase(handle) {
69
- const memo = registry.migrated.get(handle.cacheKey);
70
- if (memo)
71
- return memo;
72
- const run = (async () => {
73
- if (handle.kind === "pglite") {
74
- await migratePglite(handle.db, { migrationsFolder: MIGRATIONS_DIR, migrationsSchema: "vendo" });
75
- return;
162
+ function acquireSharedPglite(dataDir) {
163
+ const registry = pgliteRegistry();
164
+ const key = resolve(dataDir);
165
+ let entry = registry.get(key);
166
+ if (!entry) {
167
+ const created = { refs: 0, driver: Promise.resolve(undefined) };
168
+ created.driver = (async () => {
169
+ const lock = await acquirePgliteDirLock(dataDir);
170
+ created.lock = lock;
171
+ try {
172
+ return await createPgliteHealingStaleLock(dataDir);
173
+ }
174
+ catch (error) {
175
+ lock.release();
176
+ throw error;
177
+ }
178
+ })();
179
+ // A failed open must not poison the dir for later attempts.
180
+ created.driver.catch(() => {
181
+ if (registry.get(key) === created)
182
+ registry.delete(key);
183
+ });
184
+ registry.set(key, created);
185
+ entry = created;
186
+ }
187
+ entry.refs += 1;
188
+ return entry;
189
+ }
190
+ async function releaseSharedPglite(dataDir, entry) {
191
+ entry.refs -= 1;
192
+ if (entry.refs > 0)
193
+ return;
194
+ const registry = pgliteRegistry();
195
+ const key = resolve(dataDir);
196
+ if (registry.get(key) === entry)
197
+ registry.delete(key);
198
+ const driver = await entry.driver.catch(() => undefined);
199
+ try {
200
+ await driver?.close();
201
+ }
202
+ finally {
203
+ entry.lock?.release();
204
+ }
205
+ }
206
+ /** ENG-350 — self-heal a stale lock instead of hard-aborting boot. */
207
+ async function createPgliteHealingStaleLock(dataDir) {
208
+ try {
209
+ return await PGlite.create(dataDir);
210
+ }
211
+ catch (error) {
212
+ const lock = readPgliteLock(dataDir);
213
+ if (!lock)
214
+ throw error;
215
+ if (lock.livePid !== undefined) {
216
+ throw new Error(`[vendo] PGlite data directory "${dataDir}" is locked by a live process (pid ${lock.livePid}) — stop that process or point dataDir elsewhere. Cause: ${errorMessage(error)}`);
76
217
  }
77
- // Advisory locks are SESSION-scoped, and the handle's db wraps a pg.Pool:
78
- // lock, migrate, and unlock could each run on a DIFFERENT pooled session,
79
- // so the lock would protect nothing (and the unlock could target a session
80
- // that never held it, leaking the lock on the one that did). Run the whole
81
- // lock → migrate → unlock sequence on ONE dedicated connection instead.
82
- // For the pg kind, cacheKey IS the connection string (see createVendoDatabase).
83
- const client = new Client({ connectionString: handle.cacheKey });
84
- await client.connect();
218
+ console.warn(`[vendo] PGlite data directory "${dataDir}" failed to open with a stale postmaster.pid left by an unclean shutdown — removing the stale lock and retrying.`);
219
+ fs.rmSync(lock.path, { force: true });
85
220
  try {
86
- const db = drizzlePg(client);
87
- await db.execute(sql `select pg_advisory_lock(${ADVISORY_LOCK_KEY})`);
221
+ return await PGlite.create(dataDir);
222
+ }
223
+ catch (retryError) {
224
+ throw new Error(`[vendo] PGlite data directory "${dataDir}" failed to open even after clearing its stale lock — the store is likely corrupt. Back up and delete the directory to start fresh, or set a Postgres url. Cause: ${errorMessage(retryError)}`);
225
+ }
226
+ }
227
+ }
228
+ /** 02-store §4 */
229
+ export function createDb(config = {}) {
230
+ const kind = config.url ? "pg" : "pglite";
231
+ const dataDir = config.dataDir ?? ".vendo/data";
232
+ let driver;
233
+ let opening;
234
+ let shared;
235
+ let closed = false;
236
+ if (config.url) {
237
+ const pool = new Pool({ connectionString: config.url });
238
+ pool.on("error", (error) => {
239
+ console.error("[vendo] postgres pool: idle connection error (recovering)", error);
240
+ });
241
+ driver = pool;
242
+ }
243
+ const open = () => {
244
+ if (closed)
245
+ return Promise.reject(new Error("[vendo] store is closed"));
246
+ if (driver)
247
+ return Promise.resolve(driver);
248
+ if (opening)
249
+ return opening;
250
+ opening = (async () => {
251
+ const serverlessEnv = SERVERLESS_ENVS.find((name) => process.env[name]);
252
+ if (serverlessEnv) {
253
+ throw new Error(`[vendo] PGlite cannot run on ${serverlessEnv} because its filesystem is ephemeral. Set a Postgres url instead.`);
254
+ }
255
+ preparePgliteDir(dataDir);
256
+ // memory:// dirs are private to this handle; a real dir goes through the
257
+ // shared single-writer registry (ENG-351).
258
+ if (dataDir.startsWith("memory://")) {
259
+ const pglite = await createPgliteHealingStaleLock(dataDir);
260
+ driver = pglite;
261
+ return pglite;
262
+ }
263
+ shared = acquireSharedPglite(dataDir);
88
264
  try {
89
- await migratePg(db, { migrationsFolder: MIGRATIONS_DIR, migrationsSchema: "vendo" });
265
+ const pglite = await shared.driver;
266
+ driver = pglite;
267
+ return pglite;
268
+ }
269
+ catch (error) {
270
+ const failed = shared;
271
+ shared = undefined;
272
+ await releaseSharedPglite(dataDir, failed);
273
+ throw error;
274
+ }
275
+ })();
276
+ opening.catch(() => {
277
+ if (!closed)
278
+ opening = undefined;
279
+ });
280
+ return opening;
281
+ };
282
+ const db = {
283
+ kind,
284
+ async query(text, params = []) {
285
+ const active = await open();
286
+ const result = active instanceof Pool
287
+ ? await active.query(text, params)
288
+ : await active.query(text, params);
289
+ return { rows: result.rows };
290
+ },
291
+ async close() {
292
+ if (closed)
293
+ return;
294
+ closed = true;
295
+ if (!opening && !driver)
296
+ return;
297
+ const active = driver ?? (opening ? await opening.catch(() => undefined) : undefined);
298
+ driver = undefined;
299
+ if (shared) {
300
+ // Shared PGlite: drop this handle's ref; the last one out closes the
301
+ // wasm instance and releases the dir lock (ENG-351).
302
+ const held = shared;
303
+ shared = undefined;
304
+ await releaseSharedPglite(dataDir, held);
305
+ return;
90
306
  }
91
- finally {
92
- await db.execute(sql `select pg_advisory_unlock(${ADVISORY_LOCK_KEY})`);
307
+ if (!active)
308
+ return;
309
+ if (active instanceof Pool)
310
+ await active.end();
311
+ else
312
+ await active.close();
313
+ },
314
+ raw() {
315
+ if (!driver) {
316
+ throw new Error("[vendo] store not opened yet — run ensureSchema() or any query first");
93
317
  }
318
+ return driver;
319
+ },
320
+ };
321
+ if (config.url)
322
+ pgUrls.set(db, config.url);
323
+ return db;
324
+ }
325
+ /** 02-store §4 */
326
+ export async function withSchemaLock(db, work) {
327
+ return withAdvisoryLock(db, ADVISORY_LOCK_KEY, work);
328
+ }
329
+ async function withAdvisoryLock(db, key, work) {
330
+ if (db.kind === "pglite")
331
+ return work(db.query.bind(db));
332
+ const url = pgUrls.get(db);
333
+ if (!url)
334
+ throw new Error("[vendo] missing Postgres connection string");
335
+ const client = new Client({ connectionString: url });
336
+ await client.connect();
337
+ const query = async (text, params = []) => {
338
+ const result = await client.query(text, params);
339
+ return { rows: result.rows };
340
+ };
341
+ try {
342
+ await query("SELECT pg_advisory_lock($1)", [key]);
343
+ try {
344
+ return await work(query);
94
345
  }
95
346
  finally {
96
- await client.end();
347
+ await query("SELECT pg_advisory_unlock($1)", [key]);
97
348
  }
98
- })().catch((err) => {
99
- throw new Error(`[vendo] migration failed — if this is a permissions error, grant the role CREATE on the database ` +
100
- `or run migrations out-of-band (autoMigrate: false + migrateVendoDatabase). Cause: ${err instanceof Error ? err.message : String(err)}`);
101
- });
102
- run.catch(() => registry.migrated.delete(handle.cacheKey));
103
- registry.migrated.set(handle.cacheKey, run);
104
- return run;
349
+ }
350
+ finally {
351
+ await client.end();
352
+ }
105
353
  }
354
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type VendoStore } from "./store.js";
2
+ /** 02-store §5 — every table in §2's public map. The erase API cascades the
3
+ * matching data across all of them; `vendo_meta` holds schema metadata (schema
4
+ * version, boot id), never user data, so no selector ever matches it and its
5
+ * count stays 0 — as does `vendo_secrets` (name-keyed host config, likewise
6
+ * never matched by a subject or app selector). Listed so the report provably
7
+ * covers the whole map. */
8
+ export declare const ERASE_TABLES: readonly ["vendo_meta", "vendo_apps", "vendo_records", "vendo_blobs", "vendo_state", "vendo_threads", "vendo_grants", "vendo_approvals", "vendo_audit", "vendo_runs", "vendo_secrets", "vendo_mcp_clients", "vendo_mcp_grants", "vendo_sessions"];
9
+ export type EraseTable = typeof ERASE_TABLES[number];
10
+ /** Rows deleted per table. */
11
+ export type EraseReport = Record<EraseTable, number>;
12
+ /**
13
+ * 02-store §5 — the store-level erase API: by subject (full erasure) or by
14
+ * app, cascading the matching data across all 14 tables of §2's map. It is
15
+ * the ONLY sanctioned deletion path for `vendo_audit` rows — the routed door
16
+ * refuses audit deletion (§2); this API reaches the tables directly.
17
+ * Ephemeral subjects are erased the same way (their rows are ordinary disk
18
+ * rows — kill-list B3); the TTL sweep (sessions.ts) is built on this cascade.
19
+ * Policy engines and schedulers stay out of scope: hosts call this from their
20
+ * own jobs, and host SQL remains available for everything else.
21
+ */
22
+ export declare function eraseStore(store: VendoStore): {
23
+ /** Full erasure of one subject: their apps (and each app's records, blobs,
24
+ state, and runs), plus every subject-keyed or subject-ref'd row and the
25
+ subject's session registration (§4). */
26
+ bySubject(subject: string): Promise<EraseReport>;
27
+ /** Erase one app: its row, record collections, blob namespaces, state, runs,
28
+ app-scoped grants and audit rows, and app-ref'd generic/door rows.
29
+ (Threads and approvals have no app axis in §2 — the subject selector
30
+ covers them.) */
31
+ byApp(appId: string): Promise<EraseReport>;
32
+ };
33
+ //# sourceMappingURL=erase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erase.d.ts","sourceRoot":"","sources":["../src/erase.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAGpD;;;;;4BAK4B;AAC5B,eAAO,MAAM,YAAY,mPAef,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AAErD,8BAA8B;AAC9B,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAMrD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG;IAC7C;;+CAE2C;IAC3C,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACjD;;;wBAGoB;IACpB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CAC5C,CA8EA"}
package/dist/erase.js ADDED
@@ -0,0 +1,107 @@
1
+ import { escapeLike } from "./helpers/utils.js";
2
+ import { dbFor } from "./store.js";
3
+ import { invalid } from "./validate.js";
4
+ /** 02-store §5 — every table in §2's public map. The erase API cascades the
5
+ * matching data across all of them; `vendo_meta` holds schema metadata (schema
6
+ * version, boot id), never user data, so no selector ever matches it and its
7
+ * count stays 0 — as does `vendo_secrets` (name-keyed host config, likewise
8
+ * never matched by a subject or app selector). Listed so the report provably
9
+ * covers the whole map. */
10
+ export const ERASE_TABLES = [
11
+ "vendo_meta",
12
+ "vendo_apps",
13
+ "vendo_records",
14
+ "vendo_blobs",
15
+ "vendo_state",
16
+ "vendo_threads",
17
+ "vendo_grants",
18
+ "vendo_approvals",
19
+ "vendo_audit",
20
+ "vendo_runs",
21
+ "vendo_secrets",
22
+ "vendo_mcp_clients",
23
+ "vendo_mcp_grants",
24
+ "vendo_sessions",
25
+ ];
26
+ function emptyReport() {
27
+ return Object.fromEntries(ERASE_TABLES.map((table) => [table, 0]));
28
+ }
29
+ /**
30
+ * 02-store §5 — the store-level erase API: by subject (full erasure) or by
31
+ * app, cascading the matching data across all 14 tables of §2's map. It is
32
+ * the ONLY sanctioned deletion path for `vendo_audit` rows — the routed door
33
+ * refuses audit deletion (§2); this API reaches the tables directly.
34
+ * Ephemeral subjects are erased the same way (their rows are ordinary disk
35
+ * rows — kill-list B3); the TTL sweep (sessions.ts) is built on this cascade.
36
+ * Policy engines and schedulers stay out of scope: hosts call this from their
37
+ * own jobs, and host SQL remains available for everything else.
38
+ */
39
+ export function eraseStore(store) {
40
+ const db = dbFor(store);
41
+ const del = async (report, table, where, params) => {
42
+ const result = await db.query(`DELETE FROM ${table} WHERE ${where} RETURNING 1`, params);
43
+ report[table] += result.rows.length;
44
+ };
45
+ /** App-scoped data shared by the subject and app cascades: the app's record
46
+ collections and blob namespaces (`app:<appId>:...` — §3's naming
47
+ convention), its per-user state, and its run records. */
48
+ const eraseAppData = async (report, appId) => {
49
+ const prefix = `app:${escapeLike(appId)}:%`;
50
+ await del(report, "vendo_records", "collection LIKE $1 ESCAPE '\\'", [prefix]);
51
+ await del(report, "vendo_blobs", "namespace LIKE $1 ESCAPE '\\'", [prefix]);
52
+ await del(report, "vendo_state", "app_id = $1", [appId]);
53
+ await del(report, "vendo_runs", "app_id = $1", [appId]);
54
+ };
55
+ return {
56
+ async bySubject(subject) {
57
+ if (typeof subject !== "string" || subject === "") {
58
+ invalid("erase subject must be a non-empty string");
59
+ }
60
+ const report = emptyReport();
61
+ const subjectRef = JSON.stringify({ subject });
62
+ // The subject's apps drive the app-scoped cascade (records/blobs/state/runs
63
+ // carry the app id, not the subject). The app ROWS are deleted FIRST:
64
+ // once they are gone, no new gated write (records/blobs WHERE EXISTS)
65
+ // can land, so the data deletes below collect any stragglers — the
66
+ // remaining race residue is a write statement already in flight.
67
+ const owned = (await db.query("SELECT id FROM vendo_apps WHERE subject = $1", [subject])).rows
68
+ .map((row) => String(row["id"]));
69
+ await del(report, "vendo_apps", "subject = $1", [subject]);
70
+ for (const appId of owned)
71
+ await eraseAppData(report, appId);
72
+ // Ordering matters for accurate counts: the app cascade above already
73
+ // removed the subject's own state/run rows, so the subject-level deletes
74
+ // below only count rows the cascade did not reach (e.g. this subject's
75
+ // state under ANOTHER owner's app).
76
+ await del(report, "vendo_state", "subject = $1", [subject]);
77
+ await del(report, "vendo_threads", "subject = $1", [subject]);
78
+ await del(report, "vendo_grants", "subject = $1", [subject]);
79
+ await del(report, "vendo_approvals", "subject = $1", [subject]);
80
+ await del(report, "vendo_audit", "subject = $1", [subject]);
81
+ // Generic and door-owned rows carry the subject only as a ref (§2/§3).
82
+ await del(report, "vendo_records", "refs @> $1::jsonb", [subjectRef]);
83
+ await del(report, "vendo_mcp_clients", "refs @> $1::jsonb", [subjectRef]);
84
+ await del(report, "vendo_mcp_grants", "refs @> $1::jsonb", [subjectRef]);
85
+ // The session registration (if any) is retired with the data (§4).
86
+ await del(report, "vendo_sessions", "subject = $1", [subject]);
87
+ return report;
88
+ },
89
+ async byApp(appId) {
90
+ if (typeof appId !== "string" || appId === "") {
91
+ invalid("erase appId must be a non-empty string");
92
+ }
93
+ const report = emptyReport();
94
+ const appRef = JSON.stringify({ app_id: appId });
95
+ // App row first (same gate-closing order as bySubject), then its data.
96
+ await del(report, "vendo_apps", "id = $1", [appId]);
97
+ await eraseAppData(report, appId);
98
+ await del(report, "vendo_grants", "app_id = $1", [appId]);
99
+ await del(report, "vendo_audit", "app_id = $1", [appId]);
100
+ await del(report, "vendo_records", "refs @> $1::jsonb", [appRef]);
101
+ await del(report, "vendo_mcp_clients", "refs @> $1::jsonb", [appRef]);
102
+ await del(report, "vendo_mcp_grants", "refs @> $1::jsonb", [appRef]);
103
+ return report;
104
+ },
105
+ };
106
+ }
107
+ //# sourceMappingURL=erase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erase.js","sourceRoot":"","sources":["../src/erase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { ApprovalId, ApprovalRequest, IsoDateTime, Principal } from "@vendoai/core";
2
+ import { type VendoStore } from "../store.js";
3
+ import type { ApprovalRow } from "./types.js";
4
+ /** 02-store §3 */
5
+ export declare function approvalStore(store: VendoStore): {
6
+ create(request: ApprovalRequest): Promise<void>;
7
+ get(id: ApprovalId): Promise<ApprovalRow | null>;
8
+ pending(principal: Principal): Promise<ApprovalRequest[]>;
9
+ decide(ids: ApprovalId | ApprovalId[], status: "approved" | "denied", decidedAt: IsoDateTime): Promise<ApprovalId[]>;
10
+ };
11
+ export type { ApprovalRow } from "./types.js";
12
+ //# sourceMappingURL=approvals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../../src/helpers/approvals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACzF,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK9C,kBAAkB;AAClB,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG;IAChD,MAAM,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,GAAG,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAC1D,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,UAAU,EAAE,EAAE,MAAM,EAAE,UAAU,GAAG,QAAQ,EAAE,SAAS,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;CACtH,CAoCA;AAED,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}