@powerhousedao/reactor-api 6.2.3-dev.43 → 6.2.3-dev.45

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.
@@ -0,0 +1,573 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="55b8b30a-561d-551d-9fe3-6288c8b09ae1")}catch(e){}}();
3
+ import path from "node:path";
4
+ import nodeFs, { promises } from "node:fs";
5
+ import { PGlite, protocol } from "@electric-sql/pglite";
6
+ import { NodeFS } from "@electric-sql/pglite/nodefs";
7
+ import { ERRNO_CODES } from "@electric-sql/pglite/basefs";
8
+ //#region src/pglite/maintenance.ts
9
+ const DEFAULT_MAINTENANCE_INTERVAL_MS = 3e5;
10
+ const DEFAULT_VACUUM_FULL_ABOVE_BYTES = 268435456;
11
+ const MAINTENANCE_RETRY_MS = 1e3;
12
+ var PgliteMaintenance = class {
13
+ intervalMs;
14
+ vacuumFullAboveBytes;
15
+ logger;
16
+ pg;
17
+ label = "";
18
+ closing = false;
19
+ syncsSinceMaintenance = 0;
20
+ vacuumFullPending = false;
21
+ timer;
22
+ inFlight;
23
+ constructor(options = {}) {
24
+ this.intervalMs = Math.max(0, options.intervalMs ?? 3e5);
25
+ this.vacuumFullAboveBytes = Math.max(0, options.vacuumFullAboveBytes ?? 268435456);
26
+ this.logger = options.logger;
27
+ }
28
+ get scheduled() {
29
+ return this.timer !== void 0;
30
+ }
31
+ get running() {
32
+ return this.inFlight !== void 0;
33
+ }
34
+ start(pg, label, baseBytes) {
35
+ this.pg = pg;
36
+ this.label = label;
37
+ this.vacuumFullPending = this.vacuumFullAboveBytes > 0 && baseBytes > this.vacuumFullAboveBytes;
38
+ this.schedule(this.intervalMs);
39
+ }
40
+ noteSync() {
41
+ this.syncsSinceMaintenance++;
42
+ }
43
+ async stop() {
44
+ this.closing = true;
45
+ this.cancel();
46
+ await this.inFlight;
47
+ }
48
+ async run() {
49
+ const pg = this.pg;
50
+ if (this.closing || !pg?.ready) return "not-ready";
51
+ const isReady = () => pg.ready && !this.closing;
52
+ if (this.syncsSinceMaintenance === 0 && !this.vacuumFullPending) return "idle";
53
+ const full = this.vacuumFullPending;
54
+ let outcome;
55
+ try {
56
+ outcome = await pg._runExclusiveQuery(async () => {
57
+ if (!isReady()) return "not-ready";
58
+ const { messages } = await pg.execProtocol(protocol.serialize.query(""), { syncToFs: false });
59
+ if (transactionStatus(messages) !== "I") return "in-transaction";
60
+ if (full) this.logger?.warn(`DurableNodeFs: ${this.label}/base exceeds ${this.vacuumFullAboveBytes} bytes; running VACUUM FULL`);
61
+ for (const statement of [full ? "VACUUM FULL" : "VACUUM", "CHECKPOINT"]) {
62
+ if (!isReady()) return "not-ready";
63
+ await pg.execProtocol(protocol.serialize.query(statement), { syncToFs: false });
64
+ }
65
+ return full ? "vacuum-full" : "vacuum";
66
+ });
67
+ } catch (err) {
68
+ this.logger?.warn(`DurableNodeFs maintenance failed: ${errorMessage(err)}`);
69
+ return "failed";
70
+ }
71
+ if (outcome !== "vacuum" && outcome !== "vacuum-full") return outcome;
72
+ this.syncsSinceMaintenance = 0;
73
+ this.vacuumFullPending = false;
74
+ return outcome;
75
+ }
76
+ schedule(delayMs) {
77
+ if (this.intervalMs === 0 || this.closing) return;
78
+ this.timer = setTimeout(() => {
79
+ this.timer = void 0;
80
+ this.inFlight = this.run().then((outcome) => {
81
+ this.schedule(outcome === "in-transaction" ? Math.min(MAINTENANCE_RETRY_MS, this.intervalMs) : this.intervalMs);
82
+ return outcome;
83
+ }).finally(() => {
84
+ this.inFlight = void 0;
85
+ });
86
+ }, delayMs);
87
+ this.timer.unref();
88
+ }
89
+ cancel() {
90
+ if (!this.timer) return;
91
+ clearTimeout(this.timer);
92
+ this.timer = void 0;
93
+ }
94
+ };
95
+ /** Total size of the regular files under `dir`; 0 when `dir` is missing. */
96
+ async function dirBytes(dir) {
97
+ let entries;
98
+ try {
99
+ entries = await promises.readdir(dir, { withFileTypes: true });
100
+ } catch (err) {
101
+ if (err.code === "ENOENT") return 0;
102
+ throw err;
103
+ }
104
+ let total = 0;
105
+ for (const entry of entries) {
106
+ const full = path.join(dir, entry.name);
107
+ if (entry.isDirectory()) total += await dirBytes(full);
108
+ else if (entry.isFile()) total += (await promises.stat(full)).size;
109
+ }
110
+ return total;
111
+ }
112
+ function transactionStatus(messages) {
113
+ for (let i = messages.length - 1; i >= 0; i--) {
114
+ const m = messages[i];
115
+ if (m.name === "readyForQuery") return m.status;
116
+ }
117
+ }
118
+ function errorMessage(err) {
119
+ return err instanceof Error ? err.message : String(err);
120
+ }
121
+ //#endregion
122
+ //#region src/pglite/sync-tree.ts
123
+ /** One pass over a finished tree; interleaving fsync with writes costs seconds. */
124
+ function syncTree(root, hostFs = nodeFs) {
125
+ const result = {
126
+ files: 0,
127
+ dirs: 0
128
+ };
129
+ const pending = [path.resolve(root)];
130
+ while (pending.length > 0) {
131
+ const dir = pending.pop();
132
+ for (const entry of nodeFs.readdirSync(dir, { withFileTypes: true })) {
133
+ const full = path.join(dir, entry.name);
134
+ if (entry.isDirectory()) pending.push(full);
135
+ else if (entry.isFile()) {
136
+ syncFile(full, hostFs);
137
+ result.files++;
138
+ }
139
+ }
140
+ syncDirectory(dir, hostFs);
141
+ result.dirs++;
142
+ }
143
+ return result;
144
+ }
145
+ function syncFile(file, hostFs) {
146
+ const fd = nodeFs.openSync(file, "r+");
147
+ try {
148
+ hostFs.fsyncSync(fd);
149
+ } finally {
150
+ nodeFs.closeSync(fd);
151
+ }
152
+ }
153
+ /** fsyncs a directory's entries; false where the platform refuses (Windows). */
154
+ function syncDirectory(dir, hostFs = nodeFs) {
155
+ let fd;
156
+ try {
157
+ fd = nodeFs.openSync(dir, "r");
158
+ } catch {
159
+ return false;
160
+ }
161
+ try {
162
+ hostFs.fsyncSync(fd);
163
+ return true;
164
+ } catch {
165
+ return false;
166
+ } finally {
167
+ nodeFs.closeSync(fd);
168
+ }
169
+ }
170
+ //#endregion
171
+ //#region src/pglite/durable-node-fs.ts
172
+ const EIO = 29;
173
+ function resolvePgliteFsync(env) {
174
+ return env.PH_PGLITE_FSYNC !== "0";
175
+ }
176
+ const WASM_ENTRY_POINTS = [
177
+ "_interactive_one",
178
+ "_interactive_write",
179
+ "_pgl_backend",
180
+ "_pgl_initdb",
181
+ "_pgl_shutdown"
182
+ ];
183
+ function poisonAfterAbort(mod, what) {
184
+ if (!mod) return;
185
+ const message = `PGlite aborted: ${String(what) || "wasm runtime abort"}`;
186
+ for (const name of WASM_ENTRY_POINTS) {
187
+ if (typeof mod[name] !== "function") continue;
188
+ mod[name] = () => {
189
+ throw new Error(message);
190
+ };
191
+ }
192
+ try {
193
+ mod.FS.quit();
194
+ } catch {}
195
+ }
196
+ /** Built over the passed-in class so PG16 dirs on `pglite-legacy-02` get the same behaviour. */
197
+ function createDurableNodeFs(Base, dataDir, options = {}) {
198
+ const fsync = options.fsync ?? true;
199
+ const hostFs = options.hostFs ?? nodeFs;
200
+ const logger = options.logger;
201
+ const onAbort = options.onAbort;
202
+ const resolvedDir = path.resolve(dataDir);
203
+ const maintenance = new PgliteMaintenance({
204
+ intervalMs: options.maintenanceIntervalMs,
205
+ vacuumFullAboveBytes: options.vacuumFullAboveBytes,
206
+ logger
207
+ });
208
+ class Durable extends Base {
209
+ maintenance = maintenance;
210
+ mod;
211
+ hostSyncs = fsync;
212
+ freshInit = false;
213
+ async init(pg, opts) {
214
+ const { emscriptenOpts: base } = await super.init(pg, opts);
215
+ const baseOpts = base;
216
+ const inner = baseOpts.instantiateWasm;
217
+ if (fsync && !inner) throw new Error("PGlite did not supply instantiateWasm");
218
+ return { emscriptenOpts: {
219
+ ...baseOpts,
220
+ onAbort: (what) => {
221
+ poisonAfterAbort(this.mod, what);
222
+ maintenance.stop();
223
+ onAbort?.(what);
224
+ },
225
+ preRun: [...baseOpts.preRun ?? [], (mod) => {
226
+ this.mod = mod;
227
+ if (!fsync) return;
228
+ mod.FS.filesystems.NODEFS.stream_ops.fsync = (stream) => this.fsyncStream(stream);
229
+ }],
230
+ instantiateWasm: fsync && inner ? (imports, done) => {
231
+ const original = imports.env.__syscall_fdatasync;
232
+ const replacement = (fd) => this.fdatasync(fd);
233
+ replacement.sig = original?.sig;
234
+ imports.env.__syscall_fdatasync = replacement;
235
+ return inner(imports, done);
236
+ } : inner
237
+ } };
238
+ }
239
+ async initialSyncFs() {
240
+ await super.initialSyncFs();
241
+ this.freshInit = fsync && !nodeFs.existsSync(path.join(resolvedDir, "PG_VERSION"));
242
+ if (this.freshInit) this.hostSyncs = false;
243
+ const baseBytes = await dirBytes(path.join(resolvedDir, "base"));
244
+ maintenance.start(this.pg, resolvedDir, baseBytes);
245
+ }
246
+ async syncToFs(relaxedDurability) {
247
+ maintenance.noteSync();
248
+ await super.syncToFs(relaxedDurability);
249
+ if (!this.freshInit) return;
250
+ this.freshInit = false;
251
+ this.hostSyncs = true;
252
+ syncTree(resolvedDir, hostFs);
253
+ }
254
+ async closeFs() {
255
+ try {
256
+ await maintenance.stop();
257
+ } finally {
258
+ await super.closeFs();
259
+ }
260
+ }
261
+ fsyncStream(stream) {
262
+ if (!this.hostSyncs) return 0;
263
+ if (typeof stream.nfd !== "number") return 0;
264
+ try {
265
+ hostFs.fsyncSync(stream.nfd);
266
+ } catch (err) {
267
+ logger?.warn(`DurableNodeFs fsync failed: ${String(err)}`);
268
+ return EIO;
269
+ }
270
+ return 0;
271
+ }
272
+ fdatasync(fd) {
273
+ if (!this.hostSyncs) return 0;
274
+ let stream;
275
+ try {
276
+ stream = this.mod.FS.getStreamChecked(fd);
277
+ } catch {
278
+ return -ERRNO_CODES.EBADF;
279
+ }
280
+ if (typeof stream.nfd !== "number") return 0;
281
+ try {
282
+ hostFs.fdatasyncSync(stream.nfd);
283
+ } catch (err) {
284
+ logger?.warn(`DurableNodeFs fdatasync failed: ${String(err)}`);
285
+ return -29;
286
+ }
287
+ return 0;
288
+ }
289
+ }
290
+ return new Durable(dataDir);
291
+ }
292
+ //#endregion
293
+ //#region src/pglite/snapshot-reader.ts
294
+ const SNAPSHOT_FILE_NAME = "snapshot.bin";
295
+ const MAGIC = new Uint8Array([
296
+ 80,
297
+ 71,
298
+ 76,
299
+ 65
300
+ ]);
301
+ const FORMAT_VERSION = 1;
302
+ const HEADER_SIZE = 12;
303
+ const ENTRY_PREFIX_SIZE = 9;
304
+ const SKIPPED_ENTRIES = /* @__PURE__ */ new Set(["postmaster.pid"]);
305
+ const DEFAULT_IO_CHUNK_SIZE = 67108864;
306
+ /** Unpacks a snapshot into the existing `outDir`. */
307
+ async function extractSnapshot(snapshotPath, outDir, options = {}) {
308
+ const fh = await promises.open(snapshotPath, "r");
309
+ try {
310
+ const { size } = await fh.stat();
311
+ return await extractFromHandle(fh, size, outDir, options);
312
+ } finally {
313
+ await fh.close();
314
+ }
315
+ }
316
+ async function extractFromHandle(fh, fileSize, outDir, options) {
317
+ const root = path.resolve(outDir);
318
+ const reader = new ChunkedReader(fh, Math.min(options.chunkSize ?? DEFAULT_IO_CHUNK_SIZE, Math.max(1, fileSize)));
319
+ const header = await reader.take(HEADER_SIZE);
320
+ for (let i = 0; i < MAGIC.length; i++) if (header[i] !== MAGIC[i]) throw new Error("snapshot.bin: invalid snapshot magic");
321
+ const version = header.readUInt32LE(4);
322
+ if (version !== FORMAT_VERSION) throw new Error(`snapshot.bin: unsupported snapshot version ${version}`);
323
+ const count = header.readUInt32LE(8);
324
+ const decoder = new TextDecoder();
325
+ let pgVersionText;
326
+ let pgControl;
327
+ let written = 0;
328
+ for (let i = 0; i < count; i++) {
329
+ const prefix = await reader.take(ENTRY_PREFIX_SIZE);
330
+ const type = prefix.readUInt8(0);
331
+ const mode = prefix.readUInt32LE(1) & 4095;
332
+ const pathLen = prefix.readUInt32LE(5);
333
+ const relPath = decoder.decode(await reader.take(pathLen));
334
+ const dataLen = (await reader.take(4)).readUInt32LE(0);
335
+ const data = await reader.take(dataLen);
336
+ const full = resolveInside(root, relPath);
337
+ if (SKIPPED_ENTRIES.has(relPath)) continue;
338
+ if (type === 0) {
339
+ await promises.mkdir(full, { recursive: true });
340
+ await promises.chmod(full, dirMode(mode));
341
+ } else if (type === 1) {
342
+ await promises.mkdir(path.dirname(full), { recursive: true });
343
+ await promises.writeFile(full, data);
344
+ await promises.chmod(full, mode);
345
+ if (relPath === "PG_VERSION") pgVersionText = data.toString("utf8");
346
+ if (relPath === "global/pg_control") pgControl = data;
347
+ } else throw new Error(`snapshot.bin: unknown entry type ${type}`);
348
+ written++;
349
+ }
350
+ if (pgVersionText === void 0) throw new Error("snapshot.bin: no PG_VERSION entry");
351
+ const pgVersionMajor = Number.parseInt(pgVersionText.trim(), 10);
352
+ if (!Number.isInteger(pgVersionMajor) || pgVersionMajor <= 0) throw new Error(`snapshot.bin: unreadable PG_VERSION ${JSON.stringify(pgVersionText)}`);
353
+ if (options.sync ?? true) syncTree(root, options.hostFs);
354
+ return {
355
+ pgVersionMajor,
356
+ pgControl,
357
+ entries: written,
358
+ bytes: fileSize
359
+ };
360
+ }
361
+ function resolveInside(root, relPath) {
362
+ const full = path.resolve(root, relPath);
363
+ if (full !== root && !full.startsWith(root + path.sep)) throw new Error(`snapshot.bin: entry escapes the data dir: ${relPath}`);
364
+ return full;
365
+ }
366
+ /** NTFS has no execute bit; a Windows snapshot can carry untraversable dirs. */
367
+ function dirMode(mode) {
368
+ return mode & 4095 | 73;
369
+ }
370
+ var ChunkedReader = class {
371
+ fh;
372
+ buf;
373
+ start = 0;
374
+ end = 0;
375
+ position = 0;
376
+ constructor(fh, chunkSize) {
377
+ this.fh = fh;
378
+ this.buf = Buffer.allocUnsafe(Math.max(1, chunkSize));
379
+ }
380
+ async take(n) {
381
+ const out = Buffer.allocUnsafe(n);
382
+ let filled = 0;
383
+ while (filled < n) {
384
+ if (this.start === this.end) {
385
+ if (n - filled >= this.buf.byteLength) {
386
+ filled += await this.readInto(out, filled, n - filled);
387
+ continue;
388
+ }
389
+ this.start = 0;
390
+ this.end = await this.readInto(this.buf, 0, this.buf.byteLength);
391
+ }
392
+ const k = Math.min(this.end - this.start, n - filled);
393
+ this.buf.copy(out, filled, this.start, this.start + k);
394
+ this.start += k;
395
+ filled += k;
396
+ }
397
+ return out;
398
+ }
399
+ async readInto(target, offset, length) {
400
+ const { bytesRead } = await this.fh.read(target, offset, Math.min(length, this.buf.byteLength), this.position);
401
+ if (bytesRead <= 0) throw new Error("snapshot.bin: truncated snapshot");
402
+ this.position += bytesRead;
403
+ return bytesRead;
404
+ }
405
+ };
406
+ //#endregion
407
+ //#region src/pglite/convert-snapshot-dir.ts
408
+ const RM_OPTIONS = {
409
+ recursive: true,
410
+ force: true,
411
+ maxRetries: 5,
412
+ retryDelay: 100
413
+ };
414
+ function siblingsOf(dir) {
415
+ const resolved = path.resolve(dir);
416
+ return {
417
+ dir: resolved,
418
+ converting: `${resolved}.converting`,
419
+ old: `${resolved}.old`,
420
+ snapshot: path.join(resolved, SNAPSHOT_FILE_NAME)
421
+ };
422
+ }
423
+ /** Settles the debris of an interrupted conversion; runs before it at boot. */
424
+ async function recoverConversion(dir, deps) {
425
+ const s = siblingsOf(dir);
426
+ const [dirExists, convertingExists, oldExists] = await Promise.all([
427
+ exists(s.dir),
428
+ exists(s.converting),
429
+ exists(s.old)
430
+ ]);
431
+ if (!dirExists) {
432
+ if (convertingExists) {
433
+ await promises.rename(s.converting, s.dir);
434
+ if (oldExists) await promises.rm(s.old, RM_OPTIONS);
435
+ deps.logger.warn(`PGlite conversion of ${s.dir} was interrupted after the swap started; completed it`);
436
+ return;
437
+ }
438
+ if (oldExists) throw new Error(`PGlite data dir ${s.dir} is missing but ${s.old} exists; refusing to initialize beside it. Inspect and rename or remove ${s.old}.`);
439
+ return;
440
+ }
441
+ if (await exists(s.snapshot)) {
442
+ if (convertingExists) await promises.rm(s.converting, RM_OPTIONS);
443
+ if (oldExists) await promises.rm(s.old, RM_OPTIONS);
444
+ return;
445
+ }
446
+ if (oldExists) await promises.rm(s.old, RM_OPTIONS);
447
+ if (convertingExists) {
448
+ deps.logger.warn(`Removing stray ${s.converting}; ${s.dir} holds no snapshot and is authoritative`);
449
+ await promises.rm(s.converting, RM_OPTIONS);
450
+ }
451
+ }
452
+ /** Extracts into `.converting`, verifies by opening it, swaps through `.old`. */
453
+ async function convertSnapshotDir(dir, deps) {
454
+ const s = siblingsOf(dir);
455
+ if (!await exists(s.snapshot)) return "none";
456
+ const startedAt = performance.now();
457
+ await promises.rm(s.converting, RM_OPTIONS);
458
+ await promises.mkdir(s.converting, { mode: 448 });
459
+ let snapshotBytes;
460
+ let entries;
461
+ try {
462
+ ({bytes: snapshotBytes, entries} = await extractAndVerify(s, deps));
463
+ } catch (err) {
464
+ await promises.rm(s.converting, RM_OPTIONS);
465
+ throw err;
466
+ }
467
+ const parent = path.dirname(s.dir);
468
+ await promises.rename(s.dir, s.old);
469
+ syncDirectory(parent, deps.hostFs);
470
+ await deps.afterStep?.("renameOld");
471
+ await promises.rename(s.converting, s.dir);
472
+ syncDirectory(parent, deps.hostFs);
473
+ await deps.afterStep?.("renameNew");
474
+ try {
475
+ await promises.rm(s.old, RM_OPTIONS);
476
+ } catch (err) {
477
+ deps.logger.warn(`Could not remove ${s.old} after conversion; it is removed at the next boot: ${String(err)}`);
478
+ }
479
+ await deps.afterStep?.("removeOld");
480
+ const convertedBytes = await treeBytes(s.dir);
481
+ const durationMs = Math.round(performance.now() - startedAt);
482
+ deps.logger.info(`Converted PGlite snapshot at ${s.dir}: snapshot ${snapshotBytes} bytes -> ${convertedBytes} bytes on disk, ${entries} entries, ${durationMs} ms`);
483
+ return "converted";
484
+ }
485
+ async function extractAndVerify(s, deps) {
486
+ const extracted = await extractSnapshot(s.snapshot, s.converting, { sync: false });
487
+ await deps.afterStep?.("extract");
488
+ if (!extracted.pgControl) throw new Error(`${s.snapshot} has no global/pg_control entry`);
489
+ const expectedIdentifier = extracted.pgControl.readBigUInt64LE(0).toString();
490
+ const handle = await deps.openForVerify(extracted.pgVersionMajor, s.converting);
491
+ try {
492
+ await verifyOpenStore(handle, expectedIdentifier);
493
+ } finally {
494
+ await handle.close();
495
+ }
496
+ await promises.rm(path.join(s.converting, "postmaster.pid"), { force: true });
497
+ if (await exists(path.join(s.converting, "snapshot.bin"))) throw new Error(`Extracted PGlite data dir ${s.converting} contains ${SNAPSHOT_FILE_NAME}`);
498
+ syncTree(s.converting, deps.hostFs);
499
+ await deps.afterStep?.("verify");
500
+ return {
501
+ bytes: extracted.bytes,
502
+ entries: extracted.entries
503
+ };
504
+ }
505
+ async function verifyOpenStore(handle, expectedIdentifier) {
506
+ const actual = (await handle.query("SELECT system_identifier::text AS system_identifier FROM pg_control_system()")).rows[0]?.system_identifier;
507
+ if (actual !== expectedIdentifier) throw new Error(`PGlite system identifier mismatch after extraction: pg_control says ${expectedIdentifier}, server says ${String(actual)}`);
508
+ const tables = await handle.query("SELECT format('%I.%I', schemaname, tablename) AS qualified FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema') ORDER BY 1");
509
+ for (const { qualified } of tables.rows) await handle.query(`SELECT count(*) FROM ${qualified}`);
510
+ }
511
+ async function exists(p) {
512
+ try {
513
+ await promises.lstat(p);
514
+ return true;
515
+ } catch {
516
+ return false;
517
+ }
518
+ }
519
+ async function treeBytes(dir) {
520
+ let total = 0;
521
+ const entries = await promises.readdir(dir, { withFileTypes: true });
522
+ for (const entry of entries) {
523
+ const full = path.join(dir, entry.name);
524
+ if (entry.isDirectory()) total += await treeBytes(full);
525
+ else if (entry.isFile()) total += (await promises.stat(full)).size;
526
+ }
527
+ return total;
528
+ }
529
+ //#endregion
530
+ //#region src/pglite/preflight.ts
531
+ /** PG major of the pinned @electric-sql/pglite; it exports none, so the test checks initdb. */
532
+ const CURRENT_PGLITE_MAJOR = 17;
533
+ /** `openForVerify` over the loaded PGlite module only. */
534
+ async function openCurrentPgliteForVerify(major, dataDir) {
535
+ if (major !== 17) throw new Error(`PGlite snapshot at ${dataDir} is PG${major}; this process only opens PG17 data dirs`);
536
+ const pg = new PGlite({ fs: new NodeFS(dataDir) });
537
+ await pg.waitReady;
538
+ return pg;
539
+ }
540
+ /** Converts a snapshot store and clears the files a previous run leaves behind. */
541
+ async function preparePgliteDataDir(dir, deps) {
542
+ await recoverConversion(dir, deps);
543
+ await convertSnapshotDir(dir, deps);
544
+ await removeStalePgliteFiles(dir, deps.logger);
545
+ }
546
+ /** PGlite leaves `postmaster.pid` after every close and `pg_wal/xlogtemp.*` after some. */
547
+ async function removeStalePgliteFiles(dir, logger) {
548
+ const lockfile = path.join(dir, "postmaster.pid");
549
+ let hadLockfile = true;
550
+ try {
551
+ await promises.unlink(lockfile);
552
+ } catch {
553
+ hadLockfile = false;
554
+ }
555
+ if (hadLockfile) logger.debug(`Removed PGlite lockfile ${lockfile}`);
556
+ const walDir = path.join(dir, "pg_wal");
557
+ let names;
558
+ try {
559
+ names = await promises.readdir(walDir);
560
+ } catch {
561
+ return;
562
+ }
563
+ for (const name of names) {
564
+ if (!name.startsWith("xlogtemp.")) continue;
565
+ await promises.rm(path.join(walDir, name), { force: true });
566
+ logger.debug(`Removed PGlite temp WAL file ${path.join(walDir, name)}`);
567
+ }
568
+ }
569
+ //#endregion
570
+ export { convertSnapshotDir as a, extractSnapshot as c, syncDirectory as d, syncTree as f, PgliteMaintenance as h, removeStalePgliteFiles as i, createDurableNodeFs as l, DEFAULT_VACUUM_FULL_ABOVE_BYTES as m, openCurrentPgliteForVerify as n, recoverConversion as o, DEFAULT_MAINTENANCE_INTERVAL_MS as p, preparePgliteDataDir as r, SNAPSHOT_FILE_NAME as s, CURRENT_PGLITE_MAJOR as t, resolvePgliteFsync as u };
571
+
572
+ //# sourceMappingURL=pglite-node-Ow9i5Rb4.mjs.map
573
+ //# debugId=55b8b30a-561d-551d-9fe3-6288c8b09ae1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pglite-node-Ow9i5Rb4.mjs","sources":["../src/pglite/maintenance.ts","../src/pglite/sync-tree.ts","../src/pglite/durable-node-fs.ts","../src/pglite/snapshot-reader.ts","../src/pglite/convert-snapshot-dir.ts","../src/pglite/preflight.ts"],"sourcesContent":["import { protocol, type PGlite } from \"@electric-sql/pglite\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\nexport const DEFAULT_MAINTENANCE_INTERVAL_MS = 5 * 60 * 1000;\nexport const DEFAULT_VACUUM_FULL_ABOVE_BYTES = 256 * 1024 * 1024;\nconst MAINTENANCE_RETRY_MS = 1000;\n\nexport type MaintenanceOutcome =\n | \"vacuum\"\n | \"vacuum-full\"\n | \"idle\"\n | \"not-ready\"\n | \"in-transaction\"\n | \"failed\";\n\nexport interface MaintenanceLogger {\n warn(message: string): void;\n}\n\nexport interface MaintenanceOptions {\n /** VACUUM + CHECKPOINT cadence; PGlite has no autovacuum. Default 5 min; 0 disables. */\n intervalMs?: number;\n /** First pass runs VACUUM FULL when `base/` exceeds this at open. Default 256 MB; 0 disables. */\n vacuumFullAboveBytes?: number;\n logger?: MaintenanceLogger;\n}\n\nexport class PgliteMaintenance {\n private readonly intervalMs: number;\n private readonly vacuumFullAboveBytes: number;\n private readonly logger?: MaintenanceLogger;\n\n private pg?: PGlite;\n private label = \"\";\n private closing = false;\n private syncsSinceMaintenance = 0;\n private vacuumFullPending = false;\n private timer?: ReturnType<typeof setTimeout>;\n private inFlight?: Promise<MaintenanceOutcome>;\n\n constructor(options: MaintenanceOptions = {}) {\n this.intervalMs = Math.max(\n 0,\n options.intervalMs ?? DEFAULT_MAINTENANCE_INTERVAL_MS,\n );\n this.vacuumFullAboveBytes = Math.max(\n 0,\n options.vacuumFullAboveBytes ?? DEFAULT_VACUUM_FULL_ABOVE_BYTES,\n );\n this.logger = options.logger;\n }\n\n get scheduled(): boolean {\n return this.timer !== undefined;\n }\n\n get running(): boolean {\n return this.inFlight !== undefined;\n }\n\n start(pg: PGlite, label: string, baseBytes: number): void {\n this.pg = pg;\n this.label = label;\n this.vacuumFullPending =\n this.vacuumFullAboveBytes > 0 && baseBytes > this.vacuumFullAboveBytes;\n this.schedule(this.intervalMs);\n }\n\n noteSync(): void {\n this.syncsSinceMaintenance++;\n }\n\n async stop(): Promise<void> {\n this.closing = true;\n this.cancel();\n await this.inFlight;\n }\n\n async run(): Promise<MaintenanceOutcome> {\n const pg = this.pg;\n if (this.closing || !pg?.ready) return \"not-ready\";\n // pg.close() does not take the query mutex; recheck before each statement.\n const isReady = (): boolean => pg.ready && !this.closing;\n if (this.syncsSinceMaintenance === 0 && !this.vacuumFullPending) {\n return \"idle\";\n }\n const full = this.vacuumFullPending;\n let outcome: MaintenanceOutcome;\n try {\n outcome = await pg._runExclusiveQuery(async () => {\n // isInTransaction() misses START TRANSACTION; ReadyForQuery status does not.\n if (!isReady()) return \"not-ready\";\n const { messages } = await pg.execProtocol(\n protocol.serialize.query(\"\"),\n { syncToFs: false },\n );\n if (transactionStatus(messages) !== \"I\") return \"in-transaction\";\n if (full) {\n this.logger?.warn(\n `DurableNodeFs: ${this.label}/base exceeds ${this.vacuumFullAboveBytes} bytes; running VACUUM FULL`,\n );\n }\n for (const statement of [\n full ? \"VACUUM FULL\" : \"VACUUM\",\n \"CHECKPOINT\",\n ]) {\n if (!isReady()) return \"not-ready\";\n await pg.execProtocol(protocol.serialize.query(statement), {\n syncToFs: false,\n });\n }\n return full ? \"vacuum-full\" : \"vacuum\";\n });\n } catch (err) {\n this.logger?.warn(\n `DurableNodeFs maintenance failed: ${errorMessage(err)}`,\n );\n return \"failed\";\n }\n if (outcome !== \"vacuum\" && outcome !== \"vacuum-full\") return outcome;\n this.syncsSinceMaintenance = 0;\n this.vacuumFullPending = false;\n return outcome;\n }\n\n private schedule(delayMs: number): void {\n if (this.intervalMs === 0 || this.closing) return;\n this.timer = setTimeout(() => {\n this.timer = undefined;\n this.inFlight = this.run()\n .then((outcome) => {\n this.schedule(\n outcome === \"in-transaction\"\n ? Math.min(MAINTENANCE_RETRY_MS, this.intervalMs)\n : this.intervalMs,\n );\n return outcome;\n })\n .finally(() => {\n this.inFlight = undefined;\n });\n }, delayMs);\n this.timer.unref();\n }\n\n private cancel(): void {\n if (!this.timer) return;\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n}\n\n/** Total size of the regular files under `dir`; 0 when `dir` is missing. */\nexport async function dirBytes(dir: string): Promise<number> {\n let entries;\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return 0;\n throw err;\n }\n let total = 0;\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n total += await dirBytes(full);\n } else if (entry.isFile()) {\n total += (await fs.stat(full)).size;\n }\n }\n return total;\n}\n\nfunction transactionStatus(messages: readonly { name: string }[]): unknown {\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i];\n if (m.name === \"readyForQuery\") return (m as { status?: unknown }).status;\n }\n return undefined;\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","import nodeFs from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface SyncTreeHostFs {\n fsyncSync(fd: number): void;\n}\n\nexport interface SyncTreeResult {\n files: number;\n dirs: number;\n}\n\n/** One pass over a finished tree; interleaving fsync with writes costs seconds. */\nexport function syncTree(\n root: string,\n hostFs: SyncTreeHostFs = nodeFs,\n): SyncTreeResult {\n const result: SyncTreeResult = { files: 0, dirs: 0 };\n const pending = [path.resolve(root)];\n while (pending.length > 0) {\n const dir = pending.pop()!;\n for (const entry of nodeFs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n pending.push(full);\n } else if (entry.isFile()) {\n syncFile(full, hostFs);\n result.files++;\n }\n }\n syncDirectory(dir, hostFs);\n result.dirs++;\n }\n return result;\n}\n\n// Windows refuses FlushFileBuffers on a read-only handle (EPERM).\nfunction syncFile(file: string, hostFs: SyncTreeHostFs): void {\n const fd = nodeFs.openSync(file, \"r+\");\n try {\n hostFs.fsyncSync(fd);\n } finally {\n nodeFs.closeSync(fd);\n }\n}\n\n/** fsyncs a directory's entries; false where the platform refuses (Windows). */\nexport function syncDirectory(\n dir: string,\n hostFs: SyncTreeHostFs = nodeFs,\n): boolean {\n let fd: number;\n try {\n fd = nodeFs.openSync(dir, \"r\");\n } catch {\n return false;\n }\n try {\n hostFs.fsyncSync(fd);\n return true;\n } catch {\n return false;\n } finally {\n nodeFs.closeSync(fd);\n }\n}\n","import type { PGlite } from \"@electric-sql/pglite\";\nimport { ERRNO_CODES } from \"@electric-sql/pglite/basefs\";\nimport type { NodeFS } from \"@electric-sql/pglite/nodefs\";\nimport nodeFs from \"node:fs\";\nimport path from \"node:path\";\nimport { dirBytes, PgliteMaintenance } from \"./maintenance.js\";\nimport { syncTree } from \"./sync-tree.js\";\n\nexport type NodeFsClass = typeof NodeFS;\n\n// Emscripten/WASI errno; the basefs ERRNO_CODES export lacks it.\nconst EIO = 29;\n\nexport interface DurableNodeFsOptions {\n /** Default true; `PH_PGLITE_FSYNC=0` turns it off (power-loss durability off). */\n fsync?: boolean;\n /** Default 5 min; 0 disables. */\n maintenanceIntervalMs?: number;\n /** `base/` size at open; default 256 MB; 0 disables. */\n vacuumFullAboveBytes?: number;\n logger?: { warn(message: string): void };\n /** Emscripten abort (a Postgres PANIC, e.g. ENOSPC on WAL). */\n onAbort?: (what: unknown) => void;\n hostFs?: Pick<typeof nodeFs, \"fsyncSync\" | \"fdatasyncSync\">;\n}\n\nexport type DurableNodeFs = InstanceType<NodeFsClass> & {\n readonly maintenance: PgliteMaintenance;\n};\n\nexport function resolvePgliteFsync(\n env: Record<string, string | undefined>,\n): boolean {\n return env.PH_PGLITE_FSYNC !== \"0\";\n}\n\ninterface NodeFsStream {\n nfd?: number;\n}\n\ninterface EmscriptenFs {\n filesystems: { NODEFS: { stream_ops: Record<string, unknown> } };\n getStreamChecked(fd: number): NodeFsStream;\n quit(): void;\n}\n\ninterface EmscriptenMod {\n FS: EmscriptenFs;\n [wasmExport: string]: unknown;\n}\n\n// PGlite re-enters wasm from its own error handler after a query fails; on an\n// aborted runtime that spins forever, so every entry point is cut off first.\nconst WASM_ENTRY_POINTS = [\n \"_interactive_one\",\n \"_interactive_write\",\n \"_pgl_backend\",\n \"_pgl_initdb\",\n \"_pgl_shutdown\",\n];\n\nfunction poisonAfterAbort(mod: EmscriptenMod | undefined, what: unknown): void {\n if (!mod) return;\n const message = `PGlite aborted: ${String(what) || \"wasm runtime abort\"}`;\n for (const name of WASM_ENTRY_POINTS) {\n if (typeof mod[name] !== \"function\") continue;\n mod[name] = () => {\n throw new Error(message);\n };\n }\n // pg.close() rejects before closeFs(), so release the host fds here.\n try {\n mod.FS.quit();\n } catch {\n // already quit by an earlier abort\n }\n}\n\ntype SyscallImport = ((fd: number) => number) & { sig?: string };\n\ninterface WasmImports {\n env: Record<string, unknown> & { __syscall_fdatasync?: SyscallImport };\n}\n\ntype InstantiateWasm = (\n imports: WasmImports,\n done: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void,\n) => unknown;\n\ninterface EmscriptenOpts {\n preRun?: ((mod: EmscriptenMod) => void)[];\n instantiateWasm?: InstantiateWasm;\n onAbort?: (what: unknown) => void;\n}\n\ntype InitOpts = Parameters<InstanceType<NodeFsClass>[\"init\"]>[1];\n\n/** Built over the passed-in class so PG16 dirs on `pglite-legacy-02` get the same behaviour. */\nexport function createDurableNodeFs(\n Base: NodeFsClass,\n dataDir: string,\n options: DurableNodeFsOptions = {},\n): DurableNodeFs {\n const fsync = options.fsync ?? true;\n const hostFs = options.hostFs ?? nodeFs;\n const logger = options.logger;\n const onAbort = options.onAbort;\n const resolvedDir = path.resolve(dataDir);\n const maintenance = new PgliteMaintenance({\n intervalMs: options.maintenanceIntervalMs,\n vacuumFullAboveBytes: options.vacuumFullAboveBytes,\n logger,\n });\n\n class Durable extends Base {\n readonly maintenance = maintenance;\n private mod?: EmscriptenMod;\n // Off from initialSyncFs of a fresh dir until PGlite's first syncToFs.\n private hostSyncs = fsync;\n private freshInit = false;\n\n async init(\n pg: PGlite,\n opts: InitOpts,\n ): Promise<{ emscriptenOpts: InitOpts }> {\n const { emscriptenOpts: base } = await super.init(pg, opts);\n const baseOpts = base as EmscriptenOpts;\n const inner = baseOpts.instantiateWasm;\n if (fsync && !inner) {\n throw new Error(\"PGlite did not supply instantiateWasm\");\n }\n const emscriptenOpts: EmscriptenOpts = {\n ...baseOpts,\n onAbort: (what) => {\n poisonAfterAbort(this.mod, what);\n void maintenance.stop();\n onAbort?.(what);\n },\n preRun: [\n ...(baseOpts.preRun ?? []),\n (mod) => {\n this.mod = mod;\n if (!fsync) return;\n mod.FS.filesystems.NODEFS.stream_ops.fsync = (\n stream: NodeFsStream,\n ) => this.fsyncStream(stream);\n },\n ],\n instantiateWasm:\n fsync && inner\n ? (imports, done) => {\n const original = imports.env.__syscall_fdatasync;\n const replacement: SyscallImport = (fd) => this.fdatasync(fd);\n replacement.sig = original?.sig;\n imports.env.__syscall_fdatasync = replacement;\n return inner(imports, done);\n }\n : inner,\n };\n return { emscriptenOpts: emscriptenOpts as InitOpts };\n }\n\n async initialSyncFs(): Promise<void> {\n await super.initialSyncFs();\n // No PG_VERSION: initdb runs next; PGlite calls syncToFs once before ready.\n this.freshInit =\n fsync && !nodeFs.existsSync(path.join(resolvedDir, \"PG_VERSION\"));\n if (this.freshInit) this.hostSyncs = false;\n const baseBytes = await dirBytes(path.join(resolvedDir, \"base\"));\n maintenance.start(this.pg!, resolvedDir, baseBytes);\n }\n\n async syncToFs(relaxedDurability?: boolean): Promise<void> {\n maintenance.noteSync();\n await super.syncToFs(relaxedDurability);\n if (!this.freshInit) return;\n this.freshInit = false;\n this.hostSyncs = true;\n syncTree(resolvedDir, hostFs);\n }\n\n async closeFs(): Promise<void> {\n try {\n await maintenance.stop();\n } finally {\n await super.closeFs();\n }\n }\n\n // WASI fd_sync convention: 0 or a positive errno.\n private fsyncStream(stream: NodeFsStream): number {\n if (!this.hostSyncs) return 0;\n // Directory streams carry no host fd; only regular files do.\n if (typeof stream.nfd !== \"number\") return 0;\n try {\n hostFs.fsyncSync(stream.nfd);\n } catch (err) {\n logger?.warn(`DurableNodeFs fsync failed: ${String(err)}`);\n return EIO;\n }\n return 0;\n }\n\n // Linux syscall convention: 0 or a negative errno.\n private fdatasync(fd: number): number {\n if (!this.hostSyncs) return 0;\n let stream: NodeFsStream;\n try {\n stream = this.mod!.FS.getStreamChecked(fd);\n } catch {\n return -ERRNO_CODES.EBADF;\n }\n if (typeof stream.nfd !== \"number\") return 0;\n try {\n hostFs.fdatasyncSync(stream.nfd);\n } catch (err) {\n logger?.warn(`DurableNodeFs fdatasync failed: ${String(err)}`);\n return -EIO;\n }\n return 0;\n }\n }\n\n return new Durable(dataDir) as DurableNodeFs;\n}\n","import { promises as fs } from \"node:fs\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { syncTree, type SyncTreeHostFs } from \"./sync-tree.js\";\n\n// v1: magic, u32 version, u32 count; then u8 type, u32 mode, path, u32 len, data.\nexport const SNAPSHOT_FILE_NAME = \"snapshot.bin\";\nconst MAGIC = new Uint8Array([0x50, 0x47, 0x4c, 0x41]);\nconst FORMAT_VERSION = 1;\nconst HEADER_SIZE = 12;\nconst ENTRY_PREFIX_SIZE = 9;\nconst SKIPPED_ENTRIES = new Set([\"postmaster.pid\"]);\n// Node rejects a single read above 2^31-1 bytes.\nconst DEFAULT_IO_CHUNK_SIZE = 64 * 1024 * 1024;\n\nexport interface ExtractSnapshotOptions {\n chunkSize?: number;\n /** Default true: one fsync pass over the tree after the last write. */\n sync?: boolean;\n hostFs?: SyncTreeHostFs;\n}\n\nexport interface ExtractedSnapshot {\n pgVersionMajor: number;\n /** `global/pg_control` bytes; absent only in synthetic trees. */\n pgControl?: Buffer;\n /** Entries written to disk; skipped entries are not counted. */\n entries: number;\n /** Size of the snapshot file. */\n bytes: number;\n}\n\n/** Unpacks a snapshot into the existing `outDir`. */\nexport async function extractSnapshot(\n snapshotPath: string,\n outDir: string,\n options: ExtractSnapshotOptions = {},\n): Promise<ExtractedSnapshot> {\n const fh = await fs.open(snapshotPath, \"r\");\n try {\n const { size } = await fh.stat();\n return await extractFromHandle(fh, size, outDir, options);\n } finally {\n await fh.close();\n }\n}\n\nasync function extractFromHandle(\n fh: FileHandle,\n fileSize: number,\n outDir: string,\n options: ExtractSnapshotOptions,\n): Promise<ExtractedSnapshot> {\n const root = path.resolve(outDir);\n const chunkSize = Math.min(\n options.chunkSize ?? DEFAULT_IO_CHUNK_SIZE,\n Math.max(1, fileSize),\n );\n const reader = new ChunkedReader(fh, chunkSize);\n\n const header = await reader.take(HEADER_SIZE);\n for (let i = 0; i < MAGIC.length; i++) {\n if (header[i] !== MAGIC[i]) {\n throw new Error(\"snapshot.bin: invalid snapshot magic\");\n }\n }\n const version = header.readUInt32LE(4);\n if (version !== FORMAT_VERSION) {\n throw new Error(`snapshot.bin: unsupported snapshot version ${version}`);\n }\n const count = header.readUInt32LE(8);\n\n const decoder = new TextDecoder();\n let pgVersionText: string | undefined;\n let pgControl: Buffer | undefined;\n let written = 0;\n\n for (let i = 0; i < count; i++) {\n const prefix = await reader.take(ENTRY_PREFIX_SIZE);\n const type = prefix.readUInt8(0);\n const mode = prefix.readUInt32LE(1) & 0o7777;\n const pathLen = prefix.readUInt32LE(5);\n const relPath = decoder.decode(await reader.take(pathLen));\n const dataLen = (await reader.take(4)).readUInt32LE(0);\n const data = await reader.take(dataLen);\n\n const full = resolveInside(root, relPath);\n if (SKIPPED_ENTRIES.has(relPath)) continue;\n\n if (type === 0) {\n await fs.mkdir(full, { recursive: true });\n await fs.chmod(full, dirMode(mode));\n } else if (type === 1) {\n await fs.mkdir(path.dirname(full), { recursive: true });\n await fs.writeFile(full, data);\n await fs.chmod(full, mode);\n if (relPath === \"PG_VERSION\") pgVersionText = data.toString(\"utf8\");\n if (relPath === \"global/pg_control\") pgControl = data;\n } else {\n throw new Error(`snapshot.bin: unknown entry type ${type}`);\n }\n written++;\n }\n\n if (pgVersionText === undefined) {\n throw new Error(\"snapshot.bin: no PG_VERSION entry\");\n }\n const pgVersionMajor = Number.parseInt(pgVersionText.trim(), 10);\n if (!Number.isInteger(pgVersionMajor) || pgVersionMajor <= 0) {\n throw new Error(\n `snapshot.bin: unreadable PG_VERSION ${JSON.stringify(pgVersionText)}`,\n );\n }\n\n if (options.sync ?? true) syncTree(root, options.hostFs);\n\n return { pgVersionMajor, pgControl, entries: written, bytes: fileSize };\n}\n\nfunction resolveInside(root: string, relPath: string): string {\n const full = path.resolve(root, relPath);\n if (full !== root && !full.startsWith(root + path.sep)) {\n throw new Error(`snapshot.bin: entry escapes the data dir: ${relPath}`);\n }\n return full;\n}\n\n/** NTFS has no execute bit; a Windows snapshot can carry untraversable dirs. */\nexport function dirMode(mode: number): number {\n return (mode & 0o7777) | 0o111;\n}\n\nclass ChunkedReader {\n private readonly buf: Buffer;\n private start = 0;\n private end = 0;\n private position = 0;\n\n constructor(\n private readonly fh: Pick<FileHandle, \"read\">,\n chunkSize: number,\n ) {\n this.buf = Buffer.allocUnsafe(Math.max(1, chunkSize));\n }\n\n async take(n: number): Promise<Buffer> {\n const out = Buffer.allocUnsafe(n);\n let filled = 0;\n while (filled < n) {\n if (this.start === this.end) {\n if (n - filled >= this.buf.byteLength) {\n filled += await this.readInto(out, filled, n - filled);\n continue;\n }\n this.start = 0;\n this.end = await this.readInto(this.buf, 0, this.buf.byteLength);\n }\n const k = Math.min(this.end - this.start, n - filled);\n this.buf.copy(out, filled, this.start, this.start + k);\n this.start += k;\n filled += k;\n }\n return out;\n }\n\n private async readInto(\n target: Buffer,\n offset: number,\n length: number,\n ): Promise<number> {\n const { bytesRead } = await this.fh.read(\n target,\n offset,\n Math.min(length, this.buf.byteLength),\n this.position,\n );\n if (bytesRead <= 0) {\n throw new Error(\"snapshot.bin: truncated snapshot\");\n }\n this.position += bytesRead;\n return bytesRead;\n }\n}\n","import type { ILogger } from \"document-model\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { extractSnapshot, SNAPSHOT_FILE_NAME } from \"./snapshot-reader.js\";\nimport { syncDirectory, syncTree, type SyncTreeHostFs } from \"./sync-tree.js\";\n\nexport interface VerifyHandle {\n query<T>(sql: string): Promise<{ rows: T[] }>;\n close(): Promise<void>;\n}\n\nexport type ConversionStep =\n | \"extract\"\n | \"verify\"\n | \"renameOld\"\n | \"renameNew\"\n | \"removeOld\";\n\nexport interface ConversionDeps {\n /** Opens `dataDir` with the stock NodeFS of the PGlite module for `major`. */\n openForVerify: (major: number, dataDir: string) => Promise<VerifyHandle>;\n logger: ILogger;\n /** Test hook: throw after a named step to simulate a crash there. */\n afterStep?: (step: ConversionStep) => void | Promise<void>;\n hostFs?: SyncTreeHostFs;\n}\n\n// Backoff doubles per retry; five keeps a scanner-held file to ~3 s.\nconst RM_OPTIONS = {\n recursive: true,\n force: true,\n maxRetries: 5,\n retryDelay: 100,\n} as const;\n\ninterface Siblings {\n dir: string;\n converting: string;\n old: string;\n snapshot: string;\n}\n\nfunction siblingsOf(dir: string): Siblings {\n const resolved = path.resolve(dir);\n return {\n dir: resolved,\n converting: `${resolved}.converting`,\n old: `${resolved}.old`,\n snapshot: path.join(resolved, SNAPSHOT_FILE_NAME),\n };\n}\n\n/** Settles the debris of an interrupted conversion; runs before it at boot. */\nexport async function recoverConversion(\n dir: string,\n deps: ConversionDeps,\n): Promise<void> {\n const s = siblingsOf(dir);\n const [dirExists, convertingExists, oldExists] = await Promise.all([\n exists(s.dir),\n exists(s.converting),\n exists(s.old),\n ]);\n\n if (!dirExists) {\n if (convertingExists) {\n // Died between the two renames: finish the swap.\n await fs.rename(s.converting, s.dir);\n if (oldExists) await fs.rm(s.old, RM_OPTIONS);\n deps.logger.warn(\n `PGlite conversion of ${s.dir} was interrupted after the swap started; completed it`,\n );\n return;\n }\n if (oldExists) {\n throw new Error(\n `PGlite data dir ${s.dir} is missing but ${s.old} exists; refusing to initialize beside it. Inspect and rename or remove ${s.old}.`,\n );\n }\n return;\n }\n\n const snapshotExists = await exists(s.snapshot);\n if (snapshotExists) {\n // The snapshot is authoritative; anything beside it is debris.\n if (convertingExists) await fs.rm(s.converting, RM_OPTIONS);\n if (oldExists) await fs.rm(s.old, RM_OPTIONS);\n return;\n }\n\n // dir is a converted PGDATA; anything beside it is superseded.\n if (oldExists) await fs.rm(s.old, RM_OPTIONS);\n if (convertingExists) {\n deps.logger.warn(\n `Removing stray ${s.converting}; ${s.dir} holds no snapshot and is authoritative`,\n );\n await fs.rm(s.converting, RM_OPTIONS);\n }\n}\n\n/** Extracts into `.converting`, verifies by opening it, swaps through `.old`. */\nexport async function convertSnapshotDir(\n dir: string,\n deps: ConversionDeps,\n): Promise<\"converted\" | \"none\"> {\n const s = siblingsOf(dir);\n if (!(await exists(s.snapshot))) return \"none\";\n\n const startedAt = performance.now();\n await fs.rm(s.converting, RM_OPTIONS);\n await fs.mkdir(s.converting, { mode: 0o700 });\n\n let snapshotBytes: number;\n let entries: number;\n try {\n ({ bytes: snapshotBytes, entries } = await extractAndVerify(s, deps));\n } catch (err) {\n await fs.rm(s.converting, RM_OPTIONS);\n throw err;\n }\n\n // Parent synced after each rename: a power loss lands on one recovery row.\n const parent = path.dirname(s.dir);\n await fs.rename(s.dir, s.old);\n syncDirectory(parent, deps.hostFs);\n await deps.afterStep?.(\"renameOld\");\n await fs.rename(s.converting, s.dir);\n syncDirectory(parent, deps.hostFs);\n await deps.afterStep?.(\"renameNew\");\n try {\n await fs.rm(s.old, RM_OPTIONS);\n } catch (err) {\n // The converted dir is in place; recoverConversion removes .old next boot.\n deps.logger.warn(\n `Could not remove ${s.old} after conversion; it is removed at the next boot: ${String(err)}`,\n );\n }\n await deps.afterStep?.(\"removeOld\");\n\n const convertedBytes = await treeBytes(s.dir);\n const durationMs = Math.round(performance.now() - startedAt);\n deps.logger.info(\n `Converted PGlite snapshot at ${s.dir}: snapshot ${snapshotBytes} bytes -> ${convertedBytes} bytes on disk, ${entries} entries, ${durationMs} ms`,\n );\n return \"converted\";\n}\n\nasync function extractAndVerify(\n s: Siblings,\n deps: ConversionDeps,\n): Promise<{ bytes: number; entries: number }> {\n const extracted = await extractSnapshot(s.snapshot, s.converting, {\n sync: false,\n });\n await deps.afterStep?.(\"extract\");\n\n if (!extracted.pgControl) {\n throw new Error(`${s.snapshot} has no global/pg_control entry`);\n }\n const expectedIdentifier = extracted.pgControl.readBigUInt64LE(0).toString();\n const handle = await deps.openForVerify(\n extracted.pgVersionMajor,\n s.converting,\n );\n try {\n await verifyOpenStore(handle, expectedIdentifier);\n } finally {\n // Must close before the renames; Windows refuses to rename an open dir.\n await handle.close();\n }\n await fs.rm(path.join(s.converting, \"postmaster.pid\"), { force: true });\n if (await exists(path.join(s.converting, SNAPSHOT_FILE_NAME))) {\n throw new Error(\n `Extracted PGlite data dir ${s.converting} contains ${SNAPSHOT_FILE_NAME}`,\n );\n }\n // One pass covers the extraction and the verify open's own writes.\n syncTree(s.converting, deps.hostFs);\n await deps.afterStep?.(\"verify\");\n return { bytes: extracted.bytes, entries: extracted.entries };\n}\n\nasync function verifyOpenStore(\n handle: VerifyHandle,\n expectedIdentifier: string,\n): Promise<void> {\n // numeric parses to a float above 2^53; compare as text.\n const ident = await handle.query<{ system_identifier: string }>(\n \"SELECT system_identifier::text AS system_identifier FROM pg_control_system()\",\n );\n const actual = ident.rows[0]?.system_identifier;\n if (actual !== expectedIdentifier) {\n throw new Error(\n `PGlite system identifier mismatch after extraction: pg_control says ${expectedIdentifier}, server says ${String(actual)}`,\n );\n }\n const tables = await handle.query<{ qualified: string }>(\n \"SELECT format('%I.%I', schemaname, tablename) AS qualified FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema') ORDER BY 1\",\n );\n for (const { qualified } of tables.rows) {\n await handle.query(`SELECT count(*) FROM ${qualified}`);\n }\n}\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await fs.lstat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function treeBytes(dir: string): Promise<number> {\n let total = 0;\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n total += await treeBytes(full);\n } else if (entry.isFile()) {\n total += (await fs.stat(full)).size;\n }\n }\n return total;\n}\n","import { PGlite } from \"@electric-sql/pglite\";\nimport { NodeFS } from \"@electric-sql/pglite/nodefs\";\nimport type { ILogger } from \"document-model\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n convertSnapshotDir,\n recoverConversion,\n type ConversionDeps,\n type VerifyHandle,\n} from \"./convert-snapshot-dir.js\";\n\n/** PG major of the pinned @electric-sql/pglite; it exports none, so the test checks initdb. */\nexport const CURRENT_PGLITE_MAJOR = 17;\n\n/** `openForVerify` over the loaded PGlite module only. */\nexport async function openCurrentPgliteForVerify(\n major: number,\n dataDir: string,\n): Promise<VerifyHandle> {\n if (major !== CURRENT_PGLITE_MAJOR) {\n throw new Error(\n `PGlite snapshot at ${dataDir} is PG${major}; this process only opens PG${CURRENT_PGLITE_MAJOR} data dirs`,\n );\n }\n const pg = new PGlite({ fs: new NodeFS(dataDir) });\n await pg.waitReady;\n return pg;\n}\n\n/** Converts a snapshot store and clears the files a previous run leaves behind. */\nexport async function preparePgliteDataDir(\n dir: string,\n deps: ConversionDeps,\n): Promise<void> {\n await recoverConversion(dir, deps);\n await convertSnapshotDir(dir, deps);\n await removeStalePgliteFiles(dir, deps.logger);\n}\n\n/** PGlite leaves `postmaster.pid` after every close and `pg_wal/xlogtemp.*` after some. */\nexport async function removeStalePgliteFiles(\n dir: string,\n logger: Pick<ILogger, \"debug\">,\n): Promise<void> {\n const lockfile = path.join(dir, \"postmaster.pid\");\n let hadLockfile = true;\n try {\n await fs.unlink(lockfile);\n } catch {\n hadLockfile = false;\n }\n if (hadLockfile) logger.debug(`Removed PGlite lockfile ${lockfile}`);\n\n const walDir = path.join(dir, \"pg_wal\");\n let names: string[];\n try {\n names = await fs.readdir(walDir);\n } catch {\n return;\n }\n for (const name of names) {\n if (!name.startsWith(\"xlogtemp.\")) continue;\n await fs.rm(path.join(walDir, name), { force: true });\n logger.debug(`Removed PGlite temp WAL file ${path.join(walDir, name)}`);\n }\n}\n"],"names":["fs","fs","fs","fs"],"mappings":"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","debug_id":"55b8b30a-561d-551d-9fe3-6288c8b09ae1"}
@@ -1 +1 @@
1
- {"version":3,"file":"vite-loader.d.mts","names":[],"sources":["../../../src/packages/vite-loader.mts"],"mappings":";;;;;;wBA8BgB,iBAAiB,QAAQ,SAAS,kBAAW;iBA2B5C;;;EAGf;;qBAQW,6BAA6B;;mBACvB;mBAEA;mBAEA;WAER;SAEF,MAAM,MAAM,eAAe,UAAU,2BAAwB;EAIpE,YAAY,MAAM,eAAe,UAAS;UAyDlC;UAIA;UAIA;UAIA;UAMA;UASA;EAcD,mBAAmB,oBAAoB,sBAAiB,QAAA;EA8BzD,qBACJ,qBACC,QAAQ;EAoBL,cAAc,qBAAqB,QAAQ;EAyB3C,eACJ,qBACC,QAAQ;EAkBL,WAAW,qBAAqB,QAAQ;EAsB9C,uBACE,oBACA,UAAU,gBAAgB,gCAC1B,UAAU;EAwBZ,kBACE,oBACA,UAAU,WAAW,0BACrB,UAAU;EAuBZ,mBACE,oBACA,UAAU,YAAY,yCACtB,UAAU;EAuBZ,eACE,oBACA,UAAU,QAAQ,8BAClB,UAAU;;wBA2BQ,gBAAgB,cAAc,SAAS,SAAM,QAAA"}
1
+ {"version":3,"file":"vite-loader.d.mts","names":[],"sources":["../../../src/packages/vite-loader.mts"],"mappings":";;;;;;wBA+BgB,iBAAiB,QAAQ,SAAS,kBAAW;iBA2B5C;;;EAGf;;qBAQW,6BAA6B;;mBACvB;mBAEA;mBAEA;WAER;SAEF,MAAM,MAAM,eAAe,UAAU,2BAAwB;EAIpE,YAAY,MAAM,eAAe,UAAS;UAyDlC;UAIA;UAIA;UAIA;UAMA;UASA;EAcD,mBAAmB,oBAAoB,sBAAiB,QAAA;EA0BzD,qBACJ,qBACC,QAAQ;EAoBL,cAAc,qBAAqB,QAAQ;EAiB3C,eACJ,qBACC,QAAQ;EAkBL,WAAW,qBAAqB,QAAQ;EAsB9C,uBACE,oBACA,UAAU,gBAAgB,gCAC1B,UAAU;EAwBZ,kBACE,oBACA,UAAU,WAAW,0BACrB,UAAU;EAuBZ,mBACE,oBACA,UAAU,YAAY,yCACtB,UAAU;EAuBZ,eACE,oBACA,UAAU,QAAQ,8BAClB,UAAU;;wBA2BQ,gBAAgB,cAAc,SAAS,SAAM,QAAA"}