@powerhousedao/pglite-fs 6.2.3-dev.34 → 6.2.3-dev.36

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.
package/dist/index.d.ts CHANGED
@@ -20,6 +20,27 @@ interface AtomicNodeFsOptions {
20
20
  * Default `0` preserves the original per-call synchronous behavior.
21
21
  */
22
22
  flushIntervalMs?: number;
23
+ /**
24
+ * Called on every failed snapshot write, in either mode. While the last
25
+ * write has failed, each `syncToFs` retries synchronously and rejects if the
26
+ * retry fails too; the first successful write clears the failure.
27
+ */
28
+ onFlushError?: (error: unknown) => void;
29
+ /**
30
+ * PGLite has no autovacuum and never checkpoints on WAL size, so dead
31
+ * tuples and WAL grow without bound. Every `maintenanceIntervalMs`, if
32
+ * anything was synced since the last pass and no transaction is open, run
33
+ * VACUUM then CHECKPOINT and flush the snapshot. Default 5 minutes; `0`
34
+ * disables.
35
+ */
36
+ maintenanceIntervalMs?: number;
37
+ /**
38
+ * When the snapshot loaded at startup is larger than this, the first
39
+ * maintenance pass runs VACUUM FULL instead of VACUUM to reclaim existing
40
+ * bloat. It holds the database for roughly 2s per GB and needs transient
41
+ * memory about the size of the live data. Default 256MB; `0` disables.
42
+ */
43
+ vacuumFullAboveBytes?: number;
23
44
  }
24
45
  /**
25
46
  * PGLite Filesystem that holds the working data dir in Emscripten MEMFS and
@@ -35,13 +56,32 @@ export declare class AtomicNodeFs extends MemoryFS {
35
56
  private readonly hostDir;
36
57
  private readonly logger?;
37
58
  private readonly flushIntervalMs;
59
+ private readonly onFlushError?;
60
+ private readonly maintenanceIntervalMs;
61
+ private readonly vacuumFullAboveBytes;
38
62
  private dirty;
63
+ private failed;
39
64
  private flushTimer?;
40
65
  private flushInFlight?;
66
+ private writing?;
67
+ private closing;
68
+ private syncsSinceMaintenance;
69
+ private vacuumFullPending;
70
+ private maintenanceTimer?;
71
+ private maintenanceInFlight?;
41
72
  constructor(hostDir: string, optionsOrLogger?: AtomicNodeFsOptions | AtomicNodeFsLogger);
42
73
  initialSyncFs(): Promise<void>;
74
+ private loadSnapshot;
43
75
  syncToFs(relaxedDurability?: boolean): Promise<void>;
44
76
  closeFs(): Promise<void>;
77
+ private persist;
78
+ private scheduleMaintenance;
79
+ private cancelMaintenance;
80
+ private runMaintenance;
81
+ private cancelDeferredFlush;
82
+ private drainInFlight;
83
+ private flush;
84
+ private reportFlushError;
45
85
  private scheduleDeferredFlush;
46
86
  private drainDirty;
47
87
  private writeSnapshot;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/atomic-node-fs.ts"],"mappings":";;UA2BiB;EACf,KAAK;;UAGU;EACf,SAAS;;;;;;;;;;;;;;;EAeT;;;;;;;;;;;;qBAaW,qBAAqB;mBACf;mBACA;mBACA;UAET;UACA;UACA;EAER,YACE,iBACA,kBAAkB,sBAAsB;EASpC,iBAAiB;EA0BjB,SAAS,8BAA8B;EASvC,WAAW;UAaT;UAmBM;UAOA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/atomic-node-fs.ts"],"mappings":";;UAwCiB;EACf,KAAK;;UAGU;EACf,SAAS;;;;;;;;;;;;;;;EAeT;;;;;;EAMA,gBAAgB;;;;;;;;EAQhB;;;;;;;EAOA;;;;;;;;;;;;qBAqBW,qBAAqB;mBACf;mBACA;mBACA;mBACA;mBACA;mBACA;UAET;UACA;UACA;UACA;UACA;UAEA;UACA;UACA;UACA;UACA;EAER,YACE,iBACA,kBAAkB,sBAAsB;EAkBpC,iBAAiB;UAKT;EAiCR,SAAS,8BAA8B;EAKvC,WAAW;UAcH;UAgBN;UAoBA;UAMM;UA0DN;UAMM;UAMA;UAqBN;UAWA;UAmBM;UAYA"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { MemoryFS } from "@electric-sql/pglite";
1
+ import { MemoryFS, protocol } from "@electric-sql/pglite";
2
2
  import { promises } from "node:fs";
3
3
  import path from "node:path";
4
4
  //#region src/atomic-node-fs.ts
@@ -12,6 +12,13 @@ const MAGIC = new Uint8Array([
12
12
  65
13
13
  ]);
14
14
  const FORMAT_VERSION = 1;
15
+ const HEADER_SIZE = 12;
16
+ const ENTRY_PREFIX_SIZE = 9;
17
+ const DEFAULT_IO_CHUNK_SIZE = 67108864;
18
+ const DEFAULT_MAINTENANCE_INTERVAL_MS = 3e5;
19
+ const DEFAULT_VACUUM_FULL_ABOVE_BYTES = 268435456;
20
+ const MAINTENANCE_RETRY_MS = 1e3;
21
+ let ioChunkSize = DEFAULT_IO_CHUNK_SIZE;
15
22
  /**
16
23
  * PGLite Filesystem that holds the working data dir in Emscripten MEMFS and
17
24
  * atomically swaps a single-file on-disk snapshot on `syncToFs`. A SIGKILL
@@ -26,25 +33,48 @@ var AtomicNodeFs = class extends MemoryFS {
26
33
  hostDir;
27
34
  logger;
28
35
  flushIntervalMs;
36
+ onFlushError;
37
+ maintenanceIntervalMs;
38
+ vacuumFullAboveBytes;
29
39
  dirty = false;
40
+ failed = false;
30
41
  flushTimer;
31
42
  flushInFlight;
43
+ writing;
44
+ closing = false;
45
+ syncsSinceMaintenance = 0;
46
+ vacuumFullPending = false;
47
+ maintenanceTimer;
48
+ maintenanceInFlight;
32
49
  constructor(hostDir, optionsOrLogger) {
33
50
  super();
34
51
  this.hostDir = path.resolve(hostDir);
35
52
  const options = normalizeOptions(optionsOrLogger);
36
53
  this.logger = options.logger;
37
54
  this.flushIntervalMs = Math.max(0, options.flushIntervalMs ?? 0);
55
+ this.onFlushError = options.onFlushError;
56
+ this.maintenanceIntervalMs = Math.max(0, options.maintenanceIntervalMs ?? DEFAULT_MAINTENANCE_INTERVAL_MS);
57
+ this.vacuumFullAboveBytes = Math.max(0, options.vacuumFullAboveBytes ?? DEFAULT_VACUUM_FULL_ABOVE_BYTES);
38
58
  }
39
59
  async initialSyncFs() {
60
+ await this.loadSnapshot();
61
+ this.scheduleMaintenance(this.maintenanceIntervalMs);
62
+ }
63
+ async loadSnapshot() {
40
64
  await promises.mkdir(this.hostDir, { recursive: true });
41
65
  const snapPath = path.join(this.hostDir, SNAPSHOT_NAME);
42
66
  const tmpPath = path.join(this.hostDir, SNAPSHOT_TMP);
43
67
  await promises.rm(tmpPath, { force: true });
44
68
  const memFs = this.pg.Module.FS;
45
69
  if (await fileExists(snapPath)) {
46
- const bytes = await promises.readFile(snapPath);
47
- restoreMemfs(memFs, PGDATA, bytes);
70
+ const fh = await promises.open(snapPath, "r");
71
+ try {
72
+ const { size } = await fh.stat();
73
+ await restoreMemfs(memFs, PGDATA, fh, size);
74
+ this.vacuumFullPending = this.vacuumFullAboveBytes > 0 && size > this.vacuumFullAboveBytes;
75
+ } finally {
76
+ await fh.close();
77
+ }
48
78
  return;
49
79
  }
50
80
  if (await fileExists(path.join(this.hostDir, "PG_VERSION"))) {
@@ -54,29 +84,126 @@ var AtomicNodeFs = class extends MemoryFS {
54
84
  }
55
85
  }
56
86
  async syncToFs(relaxedDurability) {
87
+ this.syncsSinceMaintenance++;
88
+ await this.persist(relaxedDurability ?? false);
89
+ }
90
+ async closeFs() {
91
+ try {
92
+ this.closing = true;
93
+ this.cancelMaintenance();
94
+ await this.maintenanceInFlight;
95
+ this.cancelDeferredFlush();
96
+ await this.drainInFlight();
97
+ this.dirty = false;
98
+ await this.flush(false);
99
+ } finally {
100
+ await super.closeFs();
101
+ }
102
+ }
103
+ async persist(relaxedDurability) {
57
104
  if (this.flushIntervalMs === 0) {
58
- await this.writeSnapshot(relaxedDurability ?? false);
105
+ await this.flush(relaxedDurability);
59
106
  return;
60
107
  }
61
108
  this.dirty = true;
62
- this.scheduleDeferredFlush(relaxedDurability ?? false);
109
+ if (!this.failed) {
110
+ this.scheduleDeferredFlush(relaxedDurability);
111
+ return;
112
+ }
113
+ this.cancelDeferredFlush();
114
+ await this.drainInFlight();
115
+ this.dirty = false;
116
+ await this.flush(relaxedDurability);
63
117
  }
64
- async closeFs() {
65
- if (this.flushTimer) {
66
- clearTimeout(this.flushTimer);
67
- this.flushTimer = void 0;
118
+ scheduleMaintenance(delayMs) {
119
+ if (this.maintenanceIntervalMs === 0 || this.closing) return;
120
+ this.maintenanceTimer = setTimeout(() => {
121
+ this.maintenanceTimer = void 0;
122
+ this.maintenanceInFlight = this.runMaintenance().then((outcome) => {
123
+ this.scheduleMaintenance(outcome === "in-transaction" ? Math.min(MAINTENANCE_RETRY_MS, this.maintenanceIntervalMs) : this.maintenanceIntervalMs);
124
+ return outcome;
125
+ }).finally(() => {
126
+ this.maintenanceInFlight = void 0;
127
+ });
128
+ }, delayMs);
129
+ this.maintenanceTimer.unref();
130
+ }
131
+ cancelMaintenance() {
132
+ if (!this.maintenanceTimer) return;
133
+ clearTimeout(this.maintenanceTimer);
134
+ this.maintenanceTimer = void 0;
135
+ }
136
+ async runMaintenance() {
137
+ const pg = this.pg;
138
+ if (this.closing || !pg?.ready) return "not-ready";
139
+ const isReady = () => pg.ready;
140
+ if (this.syncsSinceMaintenance === 0 && !this.vacuumFullPending) return "idle";
141
+ const full = this.vacuumFullPending;
142
+ let outcome;
143
+ try {
144
+ outcome = await pg._runExclusiveQuery(async () => {
145
+ if (!isReady()) return "not-ready";
146
+ const { messages } = await pg.execProtocol(protocol.serialize.query(""), { syncToFs: false });
147
+ if (transactionStatus(messages) !== "I") return "in-transaction";
148
+ if (full) this.logger?.warn(`AtomicNodeFs: snapshot at ${this.hostDir} exceeds ${this.vacuumFullAboveBytes} bytes; running VACUUM FULL`);
149
+ for (const statement of [full ? "VACUUM FULL" : "VACUUM", "CHECKPOINT"]) {
150
+ if (!isReady()) return "not-ready";
151
+ await pg.execProtocol(protocol.serialize.query(statement), { syncToFs: false });
152
+ }
153
+ return full ? "vacuum-full" : "vacuum";
154
+ });
155
+ } catch (err) {
156
+ this.logger?.warn(`AtomicNodeFs maintenance failed: ${errorMessage(err)}`);
157
+ return "failed";
158
+ }
159
+ if (outcome !== "vacuum" && outcome !== "vacuum-full") return outcome;
160
+ this.syncsSinceMaintenance = 0;
161
+ this.vacuumFullPending = false;
162
+ try {
163
+ await this.persist(false);
164
+ } catch (err) {
165
+ this.logger?.warn(`AtomicNodeFs flush after maintenance failed: ${errorMessage(err)}`);
166
+ return "failed";
68
167
  }
168
+ return outcome;
169
+ }
170
+ cancelDeferredFlush() {
171
+ if (!this.flushTimer) return;
172
+ clearTimeout(this.flushTimer);
173
+ this.flushTimer = void 0;
174
+ }
175
+ async drainInFlight() {
69
176
  while (this.flushInFlight) await this.flushInFlight;
70
- this.dirty = false;
71
- await this.writeSnapshot(false);
72
- await super.closeFs();
177
+ }
178
+ async flush(relaxedDurability) {
179
+ while (this.writing) await this.writing;
180
+ const write = this.writeSnapshot(relaxedDurability);
181
+ this.writing = write.then(() => void 0, () => void 0).finally(() => {
182
+ this.writing = void 0;
183
+ });
184
+ try {
185
+ await write;
186
+ } catch (err) {
187
+ this.failed = true;
188
+ this.reportFlushError(err);
189
+ throw err;
190
+ }
191
+ this.failed = false;
192
+ }
193
+ reportFlushError(err) {
194
+ if (!this.onFlushError) return;
195
+ try {
196
+ this.onFlushError(err);
197
+ } catch (callbackErr) {
198
+ this.logger?.warn(`AtomicNodeFs onFlushError callback threw: ${errorMessage(callbackErr)}`);
199
+ }
73
200
  }
74
201
  scheduleDeferredFlush(relaxedDurability) {
75
202
  if (this.flushTimer || this.flushInFlight) return;
76
203
  this.flushTimer = setTimeout(() => {
77
204
  this.flushTimer = void 0;
78
205
  this.flushInFlight = this.drainDirty(relaxedDurability).catch((err) => {
79
- this.logger?.warn(`AtomicNodeFs deferred flush failed: ${err instanceof Error ? err.message : String(err)}`);
206
+ this.logger?.warn(`AtomicNodeFs deferred flush failed: ${errorMessage(err)}`);
80
207
  }).finally(() => {
81
208
  this.flushInFlight = void 0;
82
209
  });
@@ -86,17 +213,22 @@ var AtomicNodeFs = class extends MemoryFS {
86
213
  async drainDirty(relaxedDurability) {
87
214
  while (this.dirty) {
88
215
  this.dirty = false;
89
- await this.writeSnapshot(relaxedDurability);
216
+ try {
217
+ await this.flush(relaxedDurability);
218
+ } catch (err) {
219
+ this.dirty = true;
220
+ throw err;
221
+ }
90
222
  }
91
223
  }
92
224
  async writeSnapshot(relaxedDurability) {
93
225
  const memFs = this.pg.Module.FS;
94
- const bytes = serializeMemfs(memFs, PGDATA);
226
+ const entries = collectEntries(memFs, PGDATA);
95
227
  const snapPath = path.join(this.hostDir, SNAPSHOT_NAME);
96
228
  const tmpPath = path.join(this.hostDir, SNAPSHOT_TMP);
97
229
  const fh = await promises.open(tmpPath, "w");
98
230
  try {
99
- await fh.write(bytes);
231
+ await writeEntries(fh, entries);
100
232
  if (!relaxedDurability) await fh.sync();
101
233
  } finally {
102
234
  await fh.close();
@@ -120,6 +252,15 @@ function normalizeOptions(optionsOrLogger) {
120
252
  function isLogger(value) {
121
253
  return "warn" in value && typeof value.warn === "function";
122
254
  }
255
+ function transactionStatus(messages) {
256
+ for (let i = messages.length - 1; i >= 0; i--) {
257
+ const m = messages[i];
258
+ if (m.name === "readyForQuery") return m.status;
259
+ }
260
+ }
261
+ function errorMessage(err) {
262
+ return err instanceof Error ? err.message : String(err);
263
+ }
123
264
  async function fileExists(p) {
124
265
  try {
125
266
  await promises.stat(p);
@@ -136,7 +277,7 @@ function ensureDir(FS, dir) {
136
277
  if (!FS.analyzePath(cur).exists) FS.mkdir(cur);
137
278
  }
138
279
  }
139
- function serializeMemfs(FS, root) {
280
+ function collectEntries(FS, root) {
140
281
  const entries = [];
141
282
  const walk = (dir, rel) => {
142
283
  const names = FS.readdir(dir);
@@ -164,76 +305,138 @@ function serializeMemfs(FS, root) {
164
305
  }
165
306
  };
166
307
  walk(root, "");
308
+ return entries;
309
+ }
310
+ async function writeEntries(fh, entries) {
167
311
  const encoder = new TextEncoder();
168
312
  const encodedPaths = entries.map((e) => encoder.encode(e.relPath));
169
- let size = 12;
313
+ let size = HEADER_SIZE;
170
314
  for (let i = 0; i < entries.length; i++) {
171
- size += 9 + encodedPaths[i].byteLength + 4;
315
+ size += ENTRY_PREFIX_SIZE + encodedPaths[i].byteLength + 4;
172
316
  size += entries[i].data?.byteLength ?? 0;
173
317
  }
174
- const out = new Uint8Array(size);
175
- const view = new DataView(out.buffer);
176
- let off = 0;
177
- out.set(MAGIC, off);
178
- off += 4;
179
- view.setUint32(off, FORMAT_VERSION, true);
180
- off += 4;
181
- view.setUint32(off, entries.length, true);
182
- off += 4;
318
+ const writer = new ChunkedWriter(fh, Math.min(ioChunkSize, size));
319
+ const header = Buffer.allocUnsafe(HEADER_SIZE);
320
+ header.set(MAGIC, 0);
321
+ header.writeUInt32LE(FORMAT_VERSION, 4);
322
+ header.writeUInt32LE(entries.length, 8);
323
+ await writer.append(header);
183
324
  for (let i = 0; i < entries.length; i++) {
184
325
  const e = entries[i];
185
326
  const pathBytes = encodedPaths[i];
186
- view.setUint8(off, e.type);
187
- off += 1;
188
- view.setUint32(off, e.mode, true);
189
- off += 4;
190
- view.setUint32(off, pathBytes.byteLength, true);
191
- off += 4;
192
- out.set(pathBytes, off);
193
- off += pathBytes.byteLength;
194
327
  const dataLen = e.data?.byteLength ?? 0;
195
- view.setUint32(off, dataLen, true);
196
- off += 4;
197
- if (dataLen > 0 && e.data) {
198
- out.set(e.data, off);
199
- off += dataLen;
200
- }
328
+ const prefix = Buffer.allocUnsafe(ENTRY_PREFIX_SIZE + pathBytes.byteLength + 4);
329
+ prefix.writeUInt8(e.type, 0);
330
+ prefix.writeUInt32LE(e.mode, 1);
331
+ prefix.writeUInt32LE(pathBytes.byteLength, 5);
332
+ prefix.set(pathBytes, ENTRY_PREFIX_SIZE);
333
+ prefix.writeUInt32LE(dataLen, ENTRY_PREFIX_SIZE + pathBytes.byteLength);
334
+ await writer.append(prefix);
335
+ if (dataLen > 0 && e.data) await writer.append(e.data);
201
336
  }
202
- return out;
337
+ await writer.flush();
203
338
  }
204
- function restoreMemfs(FS, root, bytes) {
339
+ var ChunkedWriter = class {
340
+ fh;
341
+ buf;
342
+ used = 0;
343
+ position = 0;
344
+ constructor(fh, chunkSize) {
345
+ this.fh = fh;
346
+ this.buf = Buffer.allocUnsafe(Math.max(1, chunkSize));
347
+ }
348
+ async append(bytes) {
349
+ const size = this.buf.byteLength;
350
+ let off = 0;
351
+ while (off < bytes.byteLength) {
352
+ if (this.used === 0 && bytes.byteLength - off >= size) {
353
+ await this.writeAll(bytes.subarray(off, off + size));
354
+ off += size;
355
+ continue;
356
+ }
357
+ const n = Math.min(size - this.used, bytes.byteLength - off);
358
+ this.buf.set(bytes.subarray(off, off + n), this.used);
359
+ this.used += n;
360
+ off += n;
361
+ if (this.used === size) await this.flush();
362
+ }
363
+ }
364
+ async flush() {
365
+ if (this.used === 0) return;
366
+ await this.writeAll(this.buf.subarray(0, this.used));
367
+ this.used = 0;
368
+ }
369
+ async writeAll(bytes) {
370
+ let off = 0;
371
+ while (off < bytes.byteLength) {
372
+ const { bytesWritten } = await this.fh.write(bytes, off, bytes.byteLength - off, this.position);
373
+ if (bytesWritten <= 0) throw new Error("AtomicNodeFs: snapshot write made no progress");
374
+ off += bytesWritten;
375
+ this.position += bytesWritten;
376
+ }
377
+ }
378
+ };
379
+ var ChunkedReader = class {
380
+ fh;
381
+ buf;
382
+ start = 0;
383
+ end = 0;
384
+ position = 0;
385
+ constructor(fh, chunkSize) {
386
+ this.fh = fh;
387
+ this.buf = Buffer.allocUnsafe(Math.max(1, chunkSize));
388
+ }
389
+ async take(n) {
390
+ const out = Buffer.allocUnsafe(n);
391
+ let filled = 0;
392
+ while (filled < n) {
393
+ if (this.start === this.end) {
394
+ if (n - filled >= this.buf.byteLength) {
395
+ filled += await this.readInto(out, filled, n - filled);
396
+ continue;
397
+ }
398
+ this.start = 0;
399
+ this.end = await this.readInto(this.buf, 0, this.buf.byteLength);
400
+ }
401
+ const k = Math.min(this.end - this.start, n - filled);
402
+ this.buf.copy(out, filled, this.start, this.start + k);
403
+ this.start += k;
404
+ filled += k;
405
+ }
406
+ return out;
407
+ }
408
+ async readInto(target, offset, length) {
409
+ const { bytesRead } = await this.fh.read(target, offset, Math.min(length, this.buf.byteLength), this.position);
410
+ if (bytesRead <= 0) throw new Error("AtomicNodeFs: truncated snapshot");
411
+ this.position += bytesRead;
412
+ return bytesRead;
413
+ }
414
+ };
415
+ async function restoreMemfs(FS, root, fh, fileSize) {
205
416
  ensureDir(FS, root);
206
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
207
- let off = 0;
208
- for (let i = 0; i < 4; i++) if (bytes[off + i] !== MAGIC[i]) throw new Error("AtomicNodeFs: invalid snapshot magic");
209
- off += 4;
210
- const version = view.getUint32(off, true);
211
- off += 4;
417
+ const reader = new ChunkedReader(fh, Math.min(ioChunkSize, fileSize));
418
+ const header = await reader.take(HEADER_SIZE);
419
+ for (let i = 0; i < 4; i++) if (header[i] !== MAGIC[i]) throw new Error("AtomicNodeFs: invalid snapshot magic");
420
+ const version = header.readUInt32LE(4);
212
421
  if (version !== FORMAT_VERSION) throw new Error(`AtomicNodeFs: unsupported snapshot version ${version}`);
213
- const count = view.getUint32(off, true);
214
- off += 4;
422
+ const count = header.readUInt32LE(8);
215
423
  const decoder = new TextDecoder();
216
424
  for (let i = 0; i < count; i++) {
217
- const type = view.getUint8(off);
218
- off += 1;
219
- const mode = view.getUint32(off, true);
220
- off += 4;
221
- const pathLen = view.getUint32(off, true);
222
- off += 4;
223
- const relPath = decoder.decode(bytes.subarray(off, off + pathLen));
224
- off += pathLen;
225
- const dataLen = view.getUint32(off, true);
226
- off += 4;
425
+ const prefix = await reader.take(ENTRY_PREFIX_SIZE);
426
+ const type = prefix.readUInt8(0);
427
+ const mode = prefix.readUInt32LE(1);
428
+ const pathLen = prefix.readUInt32LE(5);
429
+ const relPath = decoder.decode(await reader.take(pathLen));
430
+ const dataLen = (await reader.take(4)).readUInt32LE(0);
431
+ const data = await reader.take(dataLen);
227
432
  const full = root + "/" + relPath;
228
433
  if (type === 0) {
229
434
  if (!FS.analyzePath(full).exists) FS.mkdir(full, dirMode(mode));
230
435
  else FS.chmod(full, dirMode(mode));
231
436
  } else {
232
- const data = bytes.subarray(off, off + dataLen);
233
437
  FS.writeFile(full, data);
234
438
  FS.chmod(full, mode);
235
439
  }
236
- off += dataLen;
237
440
  }
238
441
  }
239
442
  /**
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["fs"],"sources":["../src/atomic-node-fs.ts"],"sourcesContent":["import { MemoryFS } from \"@electric-sql/pglite\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\n// PGDATA in PGLite 0.3.x's MEMFS layout. The compiled bundle uses\n// `/tmp/pglite/base` (PG install prefix `/tmp/pglite`, data dir `base/` inside\n// it). Not exported as a runtime constant, so we hardcode.\nconst PGDATA = \"/tmp/pglite/base\";\nconst SNAPSHOT_NAME = \"snapshot.bin\";\nconst SNAPSHOT_TMP = \"snapshot.bin.tmp\";\nconst MAGIC = new Uint8Array([0x50, 0x47, 0x4c, 0x41]); // \"PGLA\"\nconst FORMAT_VERSION = 1;\n\ntype EntryType = 0 | 1; // 0=dir, 1=file\n\ninterface MemFs {\n readdir(path: string): string[];\n stat(path: string): { mode: number; size: number };\n readFile(path: string, opts?: { encoding: \"binary\" }): Uint8Array;\n writeFile(path: string, data: Uint8Array): void;\n mkdir(path: string, mode?: number): void;\n chmod(path: string, mode: number): void;\n analyzePath(path: string): { exists: boolean };\n isDir(mode: number): boolean;\n isFile(mode: number): boolean;\n}\n\nexport interface AtomicNodeFsLogger {\n warn(message: string): void;\n}\n\nexport interface AtomicNodeFsOptions {\n logger?: AtomicNodeFsLogger;\n /**\n * Coalesce `syncToFs` calls into a trailing-edge disk write at most every\n * `flushIntervalMs` milliseconds. PGLite calls `syncToFs` after every\n * non-transactional query (including each BEGIN/INSERT/COMMIT emitted by\n * Kysely), so a synchronous full-tree snapshot per call caps write\n * throughput at one query per snapshot duration. Deferred mode trades\n * crash durability for sustained throughput: a crash loses every write\n * since the last completed snapshot, which is up to `flushIntervalMs` plus\n * the duration of the snapshot in flight (a full-tree write, so it grows\n * with the database). `closeFs` always drains pending writes durably before\n * returning; a process that exits without calling it gets no final flush.\n *\n * Default `0` preserves the original per-call synchronous behavior.\n */\n flushIntervalMs?: number;\n}\n\n/**\n * PGLite Filesystem that holds the working data dir in Emscripten MEMFS and\n * atomically swaps a single-file on-disk snapshot on `syncToFs`. A SIGKILL\n * mid-write leaves the previous snapshot intact, so the next startup loads\n * cleanly rather than aborting on torn WAL.\n *\n * Intended for local dev use — full-tree snapshots are fine at dev volume but\n * won't scale to production write rates. For write-heavy workloads, pass\n * `flushIntervalMs` to coalesce multiple PGLite syncs into one disk write.\n */\nexport class AtomicNodeFs extends MemoryFS {\n private readonly hostDir: string;\n private readonly logger?: AtomicNodeFsLogger;\n private readonly flushIntervalMs: number;\n\n private dirty = false;\n private flushTimer?: ReturnType<typeof setTimeout>;\n private flushInFlight?: Promise<void>;\n\n constructor(\n hostDir: string,\n optionsOrLogger?: AtomicNodeFsOptions | AtomicNodeFsLogger,\n ) {\n super();\n this.hostDir = path.resolve(hostDir);\n const options = normalizeOptions(optionsOrLogger);\n this.logger = options.logger;\n this.flushIntervalMs = Math.max(0, options.flushIntervalMs ?? 0);\n }\n\n async initialSyncFs(): Promise<void> {\n await fs.mkdir(this.hostDir, { recursive: true });\n const snapPath = path.join(this.hostDir, SNAPSHOT_NAME);\n const tmpPath = path.join(this.hostDir, SNAPSHOT_TMP);\n\n // Drop any leftover staging file from a prior crashed write.\n await fs.rm(tmpPath, { force: true });\n\n const memFs = this.pg!.Module.FS as MemFs;\n\n if (await fileExists(snapPath)) {\n const bytes = await fs.readFile(snapPath);\n restoreMemfs(memFs, PGDATA, bytes);\n return;\n }\n\n const legacyMarker = path.join(this.hostDir, \"PG_VERSION\");\n if (await fileExists(legacyMarker)) {\n this.logger?.warn(\n `Migrating legacy PGLite data dir at ${this.hostDir} to atomic snapshot. Original files retained alongside snapshot.bin as a backup; remove them once the new snapshot is verified.`,\n );\n await loadLegacyIntoMemfs(memFs, PGDATA, this.hostDir);\n return;\n }\n }\n\n async syncToFs(relaxedDurability?: boolean): Promise<void> {\n if (this.flushIntervalMs === 0) {\n await this.writeSnapshot(relaxedDurability ?? false);\n return;\n }\n this.dirty = true;\n this.scheduleDeferredFlush(relaxedDurability ?? false);\n }\n\n async closeFs(): Promise<void> {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = undefined;\n }\n while (this.flushInFlight) {\n await this.flushInFlight;\n }\n this.dirty = false;\n await this.writeSnapshot(false);\n await super.closeFs();\n }\n\n private scheduleDeferredFlush(relaxedDurability: boolean): void {\n if (this.flushTimer || this.flushInFlight) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = undefined;\n this.flushInFlight = this.drainDirty(relaxedDurability)\n .catch((err) => {\n this.logger?.warn(\n `AtomicNodeFs deferred flush failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n })\n .finally(() => {\n this.flushInFlight = undefined;\n });\n }, this.flushIntervalMs);\n // Don't keep the event loop alive solely for a pending flush; closeFs is\n // responsible for draining before shutdown.\n this.flushTimer.unref?.();\n }\n\n private async drainDirty(relaxedDurability: boolean): Promise<void> {\n while (this.dirty) {\n this.dirty = false;\n await this.writeSnapshot(relaxedDurability);\n }\n }\n\n private async writeSnapshot(relaxedDurability: boolean): Promise<void> {\n const memFs = this.pg!.Module.FS as MemFs;\n const bytes = serializeMemfs(memFs, PGDATA);\n const snapPath = path.join(this.hostDir, SNAPSHOT_NAME);\n const tmpPath = path.join(this.hostDir, SNAPSHOT_TMP);\n\n const fh = await fs.open(tmpPath, \"w\");\n try {\n await fh.write(bytes);\n if (!relaxedDurability) await fh.sync();\n } finally {\n await fh.close();\n }\n\n await fs.rename(tmpPath, snapPath);\n\n if (!relaxedDurability) {\n try {\n const dirFh = await fs.open(this.hostDir, \"r\");\n try {\n await dirFh.sync();\n } finally {\n await dirFh.close();\n }\n } catch {\n // Some platforms reject fsync on a directory fd. The rename itself is\n // still atomic at the inode level; durability of the directory entry\n // is best-effort.\n }\n }\n }\n}\n\nfunction normalizeOptions(\n optionsOrLogger: AtomicNodeFsOptions | AtomicNodeFsLogger | undefined,\n): AtomicNodeFsOptions {\n if (!optionsOrLogger) return {};\n if (isLogger(optionsOrLogger)) {\n return { logger: optionsOrLogger };\n }\n return optionsOrLogger;\n}\n\nfunction isLogger(\n value: AtomicNodeFsOptions | AtomicNodeFsLogger,\n): value is AtomicNodeFsLogger {\n return (\n \"warn\" in value && typeof (value as AtomicNodeFsLogger).warn === \"function\"\n );\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await fs.stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction ensureDir(FS: MemFs, dir: string): void {\n const parts = dir.split(\"/\").filter(Boolean);\n let cur = \"\";\n for (const p of parts) {\n cur += \"/\" + p;\n if (!FS.analyzePath(cur).exists) FS.mkdir(cur);\n }\n}\n\ninterface Entry {\n type: EntryType;\n mode: number;\n relPath: string;\n data?: Uint8Array;\n}\n\nfunction serializeMemfs(FS: MemFs, root: string): Uint8Array {\n const entries: Entry[] = [];\n\n const walk = (dir: string, rel: string) => {\n const names = FS.readdir(dir);\n for (const name of names) {\n if (name === \".\" || name === \"..\") continue;\n const full = dir + \"/\" + name;\n const r = rel === \"\" ? name : rel + \"/\" + name;\n const stat = FS.stat(full);\n if (FS.isDir(stat.mode)) {\n entries.push({ type: 0, mode: stat.mode & 0o7777, relPath: r });\n walk(full, r);\n } else if (FS.isFile(stat.mode)) {\n const data = FS.readFile(full, { encoding: \"binary\" });\n entries.push({\n type: 1,\n mode: stat.mode & 0o7777,\n relPath: r,\n data,\n });\n }\n // skip symlinks, sockets, etc. — PGLite doesn't create them in PGDATA.\n }\n };\n walk(root, \"\");\n\n const encoder = new TextEncoder();\n const encodedPaths = entries.map((e) => encoder.encode(e.relPath));\n\n let size = 4 + 4 + 4; // magic + version + count\n for (let i = 0; i < entries.length; i++) {\n size += 1 + 4 + 4 + encodedPaths[i].byteLength + 4;\n size += entries[i].data?.byteLength ?? 0;\n }\n\n const out = new Uint8Array(size);\n const view = new DataView(out.buffer);\n let off = 0;\n\n out.set(MAGIC, off);\n off += 4;\n view.setUint32(off, FORMAT_VERSION, true);\n off += 4;\n view.setUint32(off, entries.length, true);\n off += 4;\n\n for (let i = 0; i < entries.length; i++) {\n const e = entries[i];\n const pathBytes = encodedPaths[i];\n view.setUint8(off, e.type);\n off += 1;\n view.setUint32(off, e.mode, true);\n off += 4;\n view.setUint32(off, pathBytes.byteLength, true);\n off += 4;\n out.set(pathBytes, off);\n off += pathBytes.byteLength;\n const dataLen = e.data?.byteLength ?? 0;\n view.setUint32(off, dataLen, true);\n off += 4;\n if (dataLen > 0 && e.data) {\n out.set(e.data, off);\n off += dataLen;\n }\n }\n\n return out;\n}\n\nfunction restoreMemfs(FS: MemFs, root: string, bytes: Uint8Array): void {\n ensureDir(FS, root);\n\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n let off = 0;\n\n for (let i = 0; i < 4; i++) {\n if (bytes[off + i] !== MAGIC[i]) {\n throw new Error(\"AtomicNodeFs: invalid snapshot magic\");\n }\n }\n off += 4;\n\n const version = view.getUint32(off, true);\n off += 4;\n if (version !== FORMAT_VERSION) {\n throw new Error(`AtomicNodeFs: unsupported snapshot version ${version}`);\n }\n const count = view.getUint32(off, true);\n off += 4;\n\n const decoder = new TextDecoder();\n\n for (let i = 0; i < count; i++) {\n const type = view.getUint8(off);\n off += 1;\n const mode = view.getUint32(off, true);\n off += 4;\n const pathLen = view.getUint32(off, true);\n off += 4;\n const relPath = decoder.decode(bytes.subarray(off, off + pathLen));\n off += pathLen;\n const dataLen = view.getUint32(off, true);\n off += 4;\n const full = root + \"/\" + relPath;\n\n if (type === 0) {\n // dirMode: a snapshot written right after a Windows migration can carry\n // execute-less directory modes, which would make it unopenable.\n if (!FS.analyzePath(full).exists) FS.mkdir(full, dirMode(mode));\n else FS.chmod(full, dirMode(mode));\n } else {\n const data = bytes.subarray(off, off + dataLen);\n FS.writeFile(full, data);\n FS.chmod(full, mode);\n }\n off += dataLen;\n }\n}\n\n/**\n * Permission bits for a directory created in MEMFS.\n *\n * Windows has no execute bit, so `fs.stat` reports every directory as 0o40666.\n * Copying that mode verbatim produces a MEMFS directory that cannot be\n * traversed, and every lookup beneath it fails with ENOENT -- which surfaces\n * as PGLite throwing a bare `ErrnoError { errno: 2 }` on the first query after\n * a legacy migration. Force the traverse bit on. MEMFS is process-local and\n * rebuilt from disk on every open, so host permissions carry no meaning here.\n */\nfunction dirMode(mode: number): number {\n return (mode & 0o7777) | 0o111;\n}\n\nasync function loadLegacyIntoMemfs(\n FS: MemFs,\n root: string,\n hostDir: string,\n): Promise<void> {\n ensureDir(FS, root);\n\n const skip = new Set([SNAPSHOT_NAME, SNAPSHOT_TMP, \"postmaster.pid\"]);\n\n const walk = async (diskPath: string, memPath: string) => {\n const ents = await fs.readdir(diskPath, { withFileTypes: true });\n for (const ent of ents) {\n if (skip.has(ent.name)) continue;\n const diskFull = path.join(diskPath, ent.name);\n const memFull = memPath + \"/\" + ent.name;\n const stat = await fs.stat(diskFull);\n if (ent.isDirectory()) {\n if (!FS.analyzePath(memFull).exists) {\n FS.mkdir(memFull, dirMode(stat.mode));\n }\n await walk(diskFull, memFull);\n } else if (ent.isFile()) {\n const data = await fs.readFile(diskFull);\n FS.writeFile(memFull, data);\n FS.chmod(memFull, stat.mode & 0o7777);\n }\n }\n };\n\n await walk(hostDir, root);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["fs"],"sources":["../src/atomic-node-fs.ts"],"sourcesContent":["import { MemoryFS, protocol } from \"@electric-sql/pglite\";\nimport { promises as fs } from \"node:fs\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport path from \"node:path\";\n\n// PGDATA in PGLite 0.3.x's MEMFS layout. The compiled bundle uses\n// `/tmp/pglite/base` (PG install prefix `/tmp/pglite`, data dir `base/` inside\n// it). Not exported as a runtime constant, so we hardcode.\nconst PGDATA = \"/tmp/pglite/base\";\nconst SNAPSHOT_NAME = \"snapshot.bin\";\nconst SNAPSHOT_TMP = \"snapshot.bin.tmp\";\nconst MAGIC = new Uint8Array([0x50, 0x47, 0x4c, 0x41]); // \"PGLA\"\nconst FORMAT_VERSION = 1;\nconst HEADER_SIZE = 12;\nconst ENTRY_PREFIX_SIZE = 9;\n// Node rejects a single read or write above 2^31-1 bytes.\nconst DEFAULT_IO_CHUNK_SIZE = 64 * 1024 * 1024;\nconst DEFAULT_MAINTENANCE_INTERVAL_MS = 5 * 60 * 1000;\nconst DEFAULT_VACUUM_FULL_ABOVE_BYTES = 256 * 1024 * 1024;\nconst MAINTENANCE_RETRY_MS = 1000;\nlet ioChunkSize = DEFAULT_IO_CHUNK_SIZE;\n\nexport function setIoChunkSizeForTests(size?: number): void {\n ioChunkSize = size ?? DEFAULT_IO_CHUNK_SIZE;\n}\n\ntype EntryType = 0 | 1; // 0=dir, 1=file\n\ninterface MemFs {\n readdir(path: string): string[];\n stat(path: string): { mode: number; size: number };\n readFile(path: string, opts?: { encoding: \"binary\" }): Uint8Array;\n writeFile(path: string, data: Uint8Array): void;\n mkdir(path: string, mode?: number): void;\n chmod(path: string, mode: number): void;\n analyzePath(path: string): { exists: boolean };\n isDir(mode: number): boolean;\n isFile(mode: number): boolean;\n}\n\nexport interface AtomicNodeFsLogger {\n warn(message: string): void;\n}\n\nexport interface AtomicNodeFsOptions {\n logger?: AtomicNodeFsLogger;\n /**\n * Coalesce `syncToFs` calls into a trailing-edge disk write at most every\n * `flushIntervalMs` milliseconds. PGLite calls `syncToFs` after every\n * non-transactional query (including each BEGIN/INSERT/COMMIT emitted by\n * Kysely), so a synchronous full-tree snapshot per call caps write\n * throughput at one query per snapshot duration. Deferred mode trades\n * crash durability for sustained throughput: a crash loses every write\n * since the last completed snapshot, which is up to `flushIntervalMs` plus\n * the duration of the snapshot in flight (a full-tree write, so it grows\n * with the database). `closeFs` always drains pending writes durably before\n * returning; a process that exits without calling it gets no final flush.\n *\n * Default `0` preserves the original per-call synchronous behavior.\n */\n flushIntervalMs?: number;\n /**\n * Called on every failed snapshot write, in either mode. While the last\n * write has failed, each `syncToFs` retries synchronously and rejects if the\n * retry fails too; the first successful write clears the failure.\n */\n onFlushError?: (error: unknown) => void;\n /**\n * PGLite has no autovacuum and never checkpoints on WAL size, so dead\n * tuples and WAL grow without bound. Every `maintenanceIntervalMs`, if\n * anything was synced since the last pass and no transaction is open, run\n * VACUUM then CHECKPOINT and flush the snapshot. Default 5 minutes; `0`\n * disables.\n */\n maintenanceIntervalMs?: number;\n /**\n * When the snapshot loaded at startup is larger than this, the first\n * maintenance pass runs VACUUM FULL instead of VACUUM to reclaim existing\n * bloat. It holds the database for roughly 2s per GB and needs transient\n * memory about the size of the live data. Default 256MB; `0` disables.\n */\n vacuumFullAboveBytes?: number;\n}\n\ntype MaintenanceOutcome =\n | \"vacuum\"\n | \"vacuum-full\"\n | \"idle\"\n | \"not-ready\"\n | \"in-transaction\"\n | \"failed\";\n\n/**\n * PGLite Filesystem that holds the working data dir in Emscripten MEMFS and\n * atomically swaps a single-file on-disk snapshot on `syncToFs`. A SIGKILL\n * mid-write leaves the previous snapshot intact, so the next startup loads\n * cleanly rather than aborting on torn WAL.\n *\n * Intended for local dev use — full-tree snapshots are fine at dev volume but\n * won't scale to production write rates. For write-heavy workloads, pass\n * `flushIntervalMs` to coalesce multiple PGLite syncs into one disk write.\n */\nexport class AtomicNodeFs extends MemoryFS {\n private readonly hostDir: string;\n private readonly logger?: AtomicNodeFsLogger;\n private readonly flushIntervalMs: number;\n private readonly onFlushError?: (error: unknown) => void;\n private readonly maintenanceIntervalMs: number;\n private readonly vacuumFullAboveBytes: number;\n\n private dirty = false;\n private failed = false;\n private flushTimer?: ReturnType<typeof setTimeout>;\n private flushInFlight?: Promise<void>;\n private writing?: Promise<void>;\n\n private closing = false;\n private syncsSinceMaintenance = 0;\n private vacuumFullPending = false;\n private maintenanceTimer?: ReturnType<typeof setTimeout>;\n private maintenanceInFlight?: Promise<MaintenanceOutcome>;\n\n constructor(\n hostDir: string,\n optionsOrLogger?: AtomicNodeFsOptions | AtomicNodeFsLogger,\n ) {\n super();\n this.hostDir = path.resolve(hostDir);\n const options = normalizeOptions(optionsOrLogger);\n this.logger = options.logger;\n this.flushIntervalMs = Math.max(0, options.flushIntervalMs ?? 0);\n this.onFlushError = options.onFlushError;\n this.maintenanceIntervalMs = Math.max(\n 0,\n options.maintenanceIntervalMs ?? DEFAULT_MAINTENANCE_INTERVAL_MS,\n );\n this.vacuumFullAboveBytes = Math.max(\n 0,\n options.vacuumFullAboveBytes ?? DEFAULT_VACUUM_FULL_ABOVE_BYTES,\n );\n }\n\n async initialSyncFs(): Promise<void> {\n await this.loadSnapshot();\n this.scheduleMaintenance(this.maintenanceIntervalMs);\n }\n\n private async loadSnapshot(): Promise<void> {\n await fs.mkdir(this.hostDir, { recursive: true });\n const snapPath = path.join(this.hostDir, SNAPSHOT_NAME);\n const tmpPath = path.join(this.hostDir, SNAPSHOT_TMP);\n\n // Drop any leftover staging file from a prior crashed write.\n await fs.rm(tmpPath, { force: true });\n\n const memFs = this.pg!.Module.FS as MemFs;\n\n if (await fileExists(snapPath)) {\n const fh = await fs.open(snapPath, \"r\");\n try {\n const { size } = await fh.stat();\n await restoreMemfs(memFs, PGDATA, fh, size);\n this.vacuumFullPending =\n this.vacuumFullAboveBytes > 0 && size > this.vacuumFullAboveBytes;\n } finally {\n await fh.close();\n }\n return;\n }\n\n const legacyMarker = path.join(this.hostDir, \"PG_VERSION\");\n if (await fileExists(legacyMarker)) {\n this.logger?.warn(\n `Migrating legacy PGLite data dir at ${this.hostDir} to atomic snapshot. Original files retained alongside snapshot.bin as a backup; remove them once the new snapshot is verified.`,\n );\n await loadLegacyIntoMemfs(memFs, PGDATA, this.hostDir);\n return;\n }\n }\n\n async syncToFs(relaxedDurability?: boolean): Promise<void> {\n this.syncsSinceMaintenance++;\n await this.persist(relaxedDurability ?? false);\n }\n\n async closeFs(): Promise<void> {\n try {\n this.closing = true;\n this.cancelMaintenance();\n await this.maintenanceInFlight;\n this.cancelDeferredFlush();\n await this.drainInFlight();\n this.dirty = false;\n await this.flush(false);\n } finally {\n await super.closeFs();\n }\n }\n\n private async persist(relaxedDurability: boolean): Promise<void> {\n if (this.flushIntervalMs === 0) {\n await this.flush(relaxedDurability);\n return;\n }\n this.dirty = true;\n if (!this.failed) {\n this.scheduleDeferredFlush(relaxedDurability);\n return;\n }\n this.cancelDeferredFlush();\n await this.drainInFlight();\n this.dirty = false;\n await this.flush(relaxedDurability);\n }\n\n private scheduleMaintenance(delayMs: number): void {\n if (this.maintenanceIntervalMs === 0 || this.closing) return;\n this.maintenanceTimer = setTimeout(() => {\n this.maintenanceTimer = undefined;\n this.maintenanceInFlight = this.runMaintenance()\n .then((outcome) => {\n this.scheduleMaintenance(\n outcome === \"in-transaction\"\n ? Math.min(MAINTENANCE_RETRY_MS, this.maintenanceIntervalMs)\n : this.maintenanceIntervalMs,\n );\n return outcome;\n })\n .finally(() => {\n this.maintenanceInFlight = undefined;\n });\n }, delayMs);\n this.maintenanceTimer.unref();\n }\n\n private cancelMaintenance(): void {\n if (!this.maintenanceTimer) return;\n clearTimeout(this.maintenanceTimer);\n this.maintenanceTimer = undefined;\n }\n\n private async runMaintenance(): 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;\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 // VACUUM inside an open transaction aborts it. isInTransaction()\n // misses START TRANSACTION; ReadyForQuery status does not.\n if (!isReady()) return \"not-ready\";\n const { messages } = await pg.execProtocol(\n protocol.serialize.query(\"\"),\n {\n syncToFs: false,\n },\n );\n if (transactionStatus(messages) !== \"I\") return \"in-transaction\";\n if (full) {\n this.logger?.warn(\n `AtomicNodeFs: snapshot at ${this.hostDir} 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 `AtomicNodeFs 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 try {\n await this.persist(false);\n } catch (err) {\n this.logger?.warn(\n `AtomicNodeFs flush after maintenance failed: ${errorMessage(err)}`,\n );\n return \"failed\";\n }\n return outcome;\n }\n\n private cancelDeferredFlush(): void {\n if (!this.flushTimer) return;\n clearTimeout(this.flushTimer);\n this.flushTimer = undefined;\n }\n\n private async drainInFlight(): Promise<void> {\n while (this.flushInFlight) {\n await this.flushInFlight;\n }\n }\n\n private async flush(relaxedDurability: boolean): Promise<void> {\n while (this.writing) await this.writing;\n const write = this.writeSnapshot(relaxedDurability);\n this.writing = write\n .then(\n () => undefined,\n () => undefined,\n )\n .finally(() => {\n this.writing = undefined;\n });\n try {\n await write;\n } catch (err) {\n this.failed = true;\n this.reportFlushError(err);\n throw err;\n }\n this.failed = false;\n }\n\n private reportFlushError(err: unknown): void {\n if (!this.onFlushError) return;\n try {\n this.onFlushError(err);\n } catch (callbackErr) {\n this.logger?.warn(\n `AtomicNodeFs onFlushError callback threw: ${errorMessage(callbackErr)}`,\n );\n }\n }\n\n private scheduleDeferredFlush(relaxedDurability: boolean): void {\n if (this.flushTimer || this.flushInFlight) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = undefined;\n this.flushInFlight = this.drainDirty(relaxedDurability)\n .catch((err: unknown) => {\n this.logger?.warn(\n `AtomicNodeFs deferred flush failed: ${errorMessage(err)}`,\n );\n })\n .finally(() => {\n this.flushInFlight = undefined;\n });\n }, this.flushIntervalMs);\n // Don't keep the event loop alive solely for a pending flush; closeFs is\n // responsible for draining before shutdown.\n this.flushTimer.unref?.();\n }\n\n private async drainDirty(relaxedDurability: boolean): Promise<void> {\n while (this.dirty) {\n this.dirty = false;\n try {\n await this.flush(relaxedDurability);\n } catch (err) {\n this.dirty = true;\n throw err;\n }\n }\n }\n\n private async writeSnapshot(relaxedDurability: boolean): Promise<void> {\n const memFs = this.pg!.Module.FS as MemFs;\n const entries = collectEntries(memFs, PGDATA);\n const snapPath = path.join(this.hostDir, SNAPSHOT_NAME);\n const tmpPath = path.join(this.hostDir, SNAPSHOT_TMP);\n\n const fh = await fs.open(tmpPath, \"w\");\n try {\n await writeEntries(fh, entries);\n if (!relaxedDurability) await fh.sync();\n } finally {\n await fh.close();\n }\n\n await fs.rename(tmpPath, snapPath);\n\n if (!relaxedDurability) {\n try {\n const dirFh = await fs.open(this.hostDir, \"r\");\n try {\n await dirFh.sync();\n } finally {\n await dirFh.close();\n }\n } catch {\n // Some platforms reject fsync on a directory fd. The rename itself is\n // still atomic at the inode level; durability of the directory entry\n // is best-effort.\n }\n }\n }\n}\n\nfunction normalizeOptions(\n optionsOrLogger: AtomicNodeFsOptions | AtomicNodeFsLogger | undefined,\n): AtomicNodeFsOptions {\n if (!optionsOrLogger) return {};\n if (isLogger(optionsOrLogger)) {\n return { logger: optionsOrLogger };\n }\n return optionsOrLogger;\n}\n\nfunction isLogger(\n value: AtomicNodeFsOptions | AtomicNodeFsLogger,\n): value is AtomicNodeFsLogger {\n return (\n \"warn\" in value && typeof (value as AtomicNodeFsLogger).warn === \"function\"\n );\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\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await fs.stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction ensureDir(FS: MemFs, dir: string): void {\n const parts = dir.split(\"/\").filter(Boolean);\n let cur = \"\";\n for (const p of parts) {\n cur += \"/\" + p;\n if (!FS.analyzePath(cur).exists) FS.mkdir(cur);\n }\n}\n\ninterface Entry {\n type: EntryType;\n mode: number;\n relPath: string;\n data?: Uint8Array;\n}\n\nexport function collectEntries(FS: MemFs, root: string): Entry[] {\n const entries: Entry[] = [];\n\n const walk = (dir: string, rel: string) => {\n const names = FS.readdir(dir);\n for (const name of names) {\n if (name === \".\" || name === \"..\") continue;\n const full = dir + \"/\" + name;\n const r = rel === \"\" ? name : rel + \"/\" + name;\n const stat = FS.stat(full);\n if (FS.isDir(stat.mode)) {\n entries.push({ type: 0, mode: stat.mode & 0o7777, relPath: r });\n walk(full, r);\n } else if (FS.isFile(stat.mode)) {\n const data = FS.readFile(full, { encoding: \"binary\" });\n entries.push({\n type: 1,\n mode: stat.mode & 0o7777,\n relPath: r,\n data,\n });\n }\n // skip symlinks, sockets, etc. — PGLite doesn't create them in PGDATA.\n }\n };\n walk(root, \"\");\n\n return entries;\n}\n\ntype WriteHandle = Pick<FileHandle, \"write\">;\ntype ReadHandle = Pick<FileHandle, \"read\">;\n\nexport async function writeEntries(\n fh: WriteHandle,\n entries: Entry[],\n): Promise<void> {\n const encoder = new TextEncoder();\n const encodedPaths = entries.map((e) => encoder.encode(e.relPath));\n\n let size = HEADER_SIZE;\n for (let i = 0; i < entries.length; i++) {\n size += ENTRY_PREFIX_SIZE + encodedPaths[i].byteLength + 4;\n size += entries[i].data?.byteLength ?? 0;\n }\n\n const writer = new ChunkedWriter(fh, Math.min(ioChunkSize, size));\n\n const header = Buffer.allocUnsafe(HEADER_SIZE);\n header.set(MAGIC, 0);\n header.writeUInt32LE(FORMAT_VERSION, 4);\n header.writeUInt32LE(entries.length, 8);\n await writer.append(header);\n\n for (let i = 0; i < entries.length; i++) {\n const e = entries[i];\n const pathBytes = encodedPaths[i];\n const dataLen = e.data?.byteLength ?? 0;\n const prefix = Buffer.allocUnsafe(\n ENTRY_PREFIX_SIZE + pathBytes.byteLength + 4,\n );\n prefix.writeUInt8(e.type, 0);\n prefix.writeUInt32LE(e.mode, 1);\n prefix.writeUInt32LE(pathBytes.byteLength, 5);\n prefix.set(pathBytes, ENTRY_PREFIX_SIZE);\n prefix.writeUInt32LE(dataLen, ENTRY_PREFIX_SIZE + pathBytes.byteLength);\n await writer.append(prefix);\n if (dataLen > 0 && e.data) await writer.append(e.data);\n }\n\n await writer.flush();\n}\n\nclass ChunkedWriter {\n private readonly buf: Buffer;\n private used = 0;\n private position = 0;\n\n constructor(\n private readonly fh: WriteHandle,\n chunkSize: number,\n ) {\n this.buf = Buffer.allocUnsafe(Math.max(1, chunkSize));\n }\n\n async append(bytes: Uint8Array): Promise<void> {\n const size = this.buf.byteLength;\n let off = 0;\n while (off < bytes.byteLength) {\n if (this.used === 0 && bytes.byteLength - off >= size) {\n await this.writeAll(bytes.subarray(off, off + size));\n off += size;\n continue;\n }\n const n = Math.min(size - this.used, bytes.byteLength - off);\n this.buf.set(bytes.subarray(off, off + n), this.used);\n this.used += n;\n off += n;\n if (this.used === size) await this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.used === 0) return;\n await this.writeAll(this.buf.subarray(0, this.used));\n this.used = 0;\n }\n\n private async writeAll(bytes: Uint8Array): Promise<void> {\n let off = 0;\n while (off < bytes.byteLength) {\n const { bytesWritten } = await this.fh.write(\n bytes,\n off,\n bytes.byteLength - off,\n this.position,\n );\n if (bytesWritten <= 0) {\n throw new Error(\"AtomicNodeFs: snapshot write made no progress\");\n }\n off += bytesWritten;\n this.position += bytesWritten;\n }\n }\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: ReadHandle,\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(\"AtomicNodeFs: truncated snapshot\");\n }\n this.position += bytesRead;\n return bytesRead;\n }\n}\n\nexport async function restoreMemfs(\n FS: MemFs,\n root: string,\n fh: ReadHandle,\n fileSize: number,\n): Promise<void> {\n ensureDir(FS, root);\n\n const reader = new ChunkedReader(fh, Math.min(ioChunkSize, fileSize));\n\n const header = await reader.take(HEADER_SIZE);\n for (let i = 0; i < 4; i++) {\n if (header[i] !== MAGIC[i]) {\n throw new Error(\"AtomicNodeFs: invalid snapshot magic\");\n }\n }\n\n const version = header.readUInt32LE(4);\n if (version !== FORMAT_VERSION) {\n throw new Error(`AtomicNodeFs: unsupported snapshot version ${version}`);\n }\n const count = header.readUInt32LE(8);\n\n const decoder = new TextDecoder();\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);\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 const full = root + \"/\" + relPath;\n\n if (type === 0) {\n // dirMode: a snapshot written right after a Windows migration can carry\n // execute-less directory modes, which would make it unopenable.\n if (!FS.analyzePath(full).exists) FS.mkdir(full, dirMode(mode));\n else FS.chmod(full, dirMode(mode));\n } else {\n FS.writeFile(full, data);\n FS.chmod(full, mode);\n }\n }\n}\n\n/**\n * Permission bits for a directory created in MEMFS.\n *\n * Windows has no execute bit, so `fs.stat` reports every directory as 0o40666.\n * Copying that mode verbatim produces a MEMFS directory that cannot be\n * traversed, and every lookup beneath it fails with ENOENT -- which surfaces\n * as PGLite throwing a bare `ErrnoError { errno: 2 }` on the first query after\n * a legacy migration. Force the traverse bit on. MEMFS is process-local and\n * rebuilt from disk on every open, so host permissions carry no meaning here.\n */\nfunction dirMode(mode: number): number {\n return (mode & 0o7777) | 0o111;\n}\n\nasync function loadLegacyIntoMemfs(\n FS: MemFs,\n root: string,\n hostDir: string,\n): Promise<void> {\n ensureDir(FS, root);\n\n const skip = new Set([SNAPSHOT_NAME, SNAPSHOT_TMP, \"postmaster.pid\"]);\n\n const walk = async (diskPath: string, memPath: string) => {\n const ents = await fs.readdir(diskPath, { withFileTypes: true });\n for (const ent of ents) {\n if (skip.has(ent.name)) continue;\n const diskFull = path.join(diskPath, ent.name);\n const memFull = memPath + \"/\" + ent.name;\n const stat = await fs.stat(diskFull);\n if (ent.isDirectory()) {\n if (!FS.analyzePath(memFull).exists) {\n FS.mkdir(memFull, dirMode(stat.mode));\n }\n await walk(diskFull, memFull);\n } else if (ent.isFile()) {\n const data = await fs.readFile(diskFull);\n FS.writeFile(memFull, data);\n FS.chmod(memFull, stat.mode & 0o7777);\n }\n }\n };\n\n await walk(hostDir, root);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/pglite-fs",
3
- "version": "6.2.3-dev.34",
3
+ "version": "6.2.3-dev.36",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "repository": {