@wrongstack/persistence 0.309.0 → 0.310.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,6 +40,7 @@ export declare class PersistenceFsError extends Error {
40
40
  */
41
41
  export declare function createPersistencePrimitives(options?: PersistencePrimitiveOptions): PersistencePrimitives;
42
42
  export declare const atomicWrite: (targetPath: string, content: string | Uint8Array, opts?: AtomicWriteOptions) => Promise<void>;
43
+ export declare const atomicReplaceWithWriter: <T>(targetPath: string, write: (handle: fs.FileHandle) => Promise<T>, opts?: AtomicWriteOptions) => Promise<T>;
43
44
  export declare const ensureDir: (dir: string) => Promise<void>;
44
45
  export declare const withFileLock: <T>(targetPath: string, fn: () => Promise<T>, opts?: FileLockOptions) => Promise<T>;
45
46
  //# sourceMappingURL=atomic-write.d.ts.map
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export * from './atomic-write.js';
2
2
  export * from './file-permissions.js';
3
3
  export * from './project-endpoint.js';
4
4
  export * from './socket-path.js';
5
+ export * from './sqlite-runtime.js';
5
6
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -61,10 +61,18 @@ function createPersistencePrimitives(options = {}) {
61
61
  await fs.mkdir(path.dirname(targetPath), { recursive: true });
62
62
  const tmp = tempPathFor(targetPath);
63
63
  try {
64
+ const createMode = opts.mode;
64
65
  if (typeof content === "string") {
65
- await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
66
+ await fs.writeFile(tmp, content, {
67
+ flag: "wx",
68
+ encoding: opts.encoding ?? "utf8",
69
+ ...createMode !== void 0 ? { mode: createMode } : {}
70
+ });
66
71
  } else {
67
- await fs.writeFile(tmp, content, { flag: "wx" });
72
+ await fs.writeFile(tmp, content, {
73
+ flag: "wx",
74
+ ...createMode !== void 0 ? { mode: createMode } : {}
75
+ });
68
76
  }
69
77
  await commitTemp(tmp, targetPath, opts);
70
78
  } catch (error) {
@@ -72,11 +80,11 @@ function createPersistencePrimitives(options = {}) {
72
80
  throw error;
73
81
  }
74
82
  }
75
- async function atomicReplaceWithWriter(targetPath, write, opts = {}) {
83
+ async function atomicReplaceWithWriter2(targetPath, write, opts = {}) {
76
84
  await fs.mkdir(path.dirname(targetPath), { recursive: true });
77
85
  const tmp = tempPathFor(targetPath);
78
86
  try {
79
- const handle = await fs.open(tmp, "wx");
87
+ const handle = await fs.open(tmp, "wx", opts.mode);
80
88
  let result;
81
89
  try {
82
90
  result = await write(handle);
@@ -127,7 +135,9 @@ function createPersistencePrimitives(options = {}) {
127
135
  await retryAfterBackoff();
128
136
  continue;
129
137
  }
130
- if (code !== "EEXIST" && code !== "EPERM") throw error;
138
+ if (code !== "EEXIST" && code !== "EPERM" && code !== "EACCES" && code !== "EBUSY") {
139
+ throw error;
140
+ }
131
141
  try {
132
142
  const stat2 = await fs.stat(lockPath);
133
143
  if (Date.now() - stat2.mtimeMs > staleMs) {
@@ -163,7 +173,7 @@ function createPersistencePrimitives(options = {}) {
163
173
  await fs.unlink(lockPath).catch(() => void 0);
164
174
  }
165
175
  }
166
- return { atomicWrite: atomicWrite2, atomicReplaceWithWriter, ensureDir: ensureDir2, withFileLock: withFileLock2 };
176
+ return { atomicWrite: atomicWrite2, atomicReplaceWithWriter: atomicReplaceWithWriter2, ensureDir: ensureDir2, withFileLock: withFileLock2 };
167
177
  }
168
178
  async function waitForLockRelease(lockPath, remainingMs) {
169
179
  const parentDir = path.dirname(lockPath);
@@ -243,6 +253,7 @@ async function renameWithRetry(from, to) {
243
253
  }
244
254
  var defaultPrimitives = createPersistencePrimitives();
245
255
  var atomicWrite = defaultPrimitives.atomicWrite;
256
+ var atomicReplaceWithWriter = defaultPrimitives.atomicReplaceWithWriter;
246
257
  var ensureDir = defaultPrimitives.ensureDir;
247
258
  var withFileLock = defaultPrimitives.withFileLock;
248
259
 
@@ -424,17 +435,67 @@ async function bindProjectEndpoint(options) {
424
435
  )
425
436
  };
426
437
  }
438
+
439
+ // src/sqlite-runtime.ts
440
+ import { createRequire } from "node:module";
441
+ function databaseSyncFromNode(module) {
442
+ if (typeof module !== "object" || module === null || !("DatabaseSync" in module)) return null;
443
+ return typeof module.DatabaseSync === "function" ? module.DatabaseSync : null;
444
+ }
445
+ function databaseSyncFromBun(module) {
446
+ if (typeof module !== "object" || module === null || !("Database" in module)) return null;
447
+ if (typeof module.Database !== "function") return null;
448
+ const BunDatabase = module.Database;
449
+ function BunDatabaseSync(filename, options) {
450
+ const bunOptions = options?.readOnly ? { readonly: true, create: false, readwrite: false } : void 0;
451
+ return new BunDatabase(filename, bunOptions);
452
+ }
453
+ return BunDatabaseSync;
454
+ }
455
+ function loadRuntimeDatabaseSync(loadModule = createRequire(import.meta.url)) {
456
+ let nodeError;
457
+ try {
458
+ const Database = databaseSyncFromNode(loadModule("node:sqlite"));
459
+ if (Database) return Database;
460
+ nodeError = new Error("node:sqlite did not export DatabaseSync");
461
+ } catch (error) {
462
+ nodeError = error;
463
+ }
464
+ let bunError;
465
+ try {
466
+ const Database = databaseSyncFromBun(loadModule("bun:sqlite"));
467
+ if (Database) return Database;
468
+ bunError = new Error("bun:sqlite did not export Database");
469
+ } catch (error) {
470
+ bunError = error;
471
+ }
472
+ const describe = (error) => error instanceof Error ? error.message : String(error);
473
+ throw new Error(
474
+ `No supported synchronous SQLite runtime is available (node:sqlite: ${describe(nodeError)}; bun:sqlite: ${describe(bunError)}).`
475
+ );
476
+ }
477
+ function isRuntimeSqliteAvailable(loadModule) {
478
+ try {
479
+ loadRuntimeDatabaseSync(loadModule);
480
+ return true;
481
+ } catch {
482
+ return false;
483
+ }
484
+ }
427
485
  export {
428
486
  PersistenceFsError,
429
487
  SECRET_DIR_MODE,
430
488
  SECRET_FILE_MODE,
431
489
  assertUnixSocketPathWithinLimit,
490
+ atomicReplaceWithWriter,
432
491
  atomicWrite,
433
492
  bindProjectEndpoint,
434
493
  checkUnixSocketPath,
435
494
  createPersistencePrimitives,
436
495
  ensureDir,
437
496
  isProjectEndpointLive,
497
+ isRuntimeSqliteAvailable,
498
+ loadRuntimeDatabaseSync,
438
499
  restrictDirPermissions,
439
500
  restrictFilePermissions,
440
501
  unixSocketPathLimit,
@@ -0,0 +1,13 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ type DatabaseSyncConstructor = typeof DatabaseSync;
3
+ type ModuleLoader = (specifier: string) => unknown;
4
+ /**
5
+ * Resolve the synchronous SQLite constructor for the active JavaScript runtime.
6
+ * Node uses `node:sqlite`; Bun uses a small constructor adapter over `bun:sqlite`.
7
+ * The SQL surface WrongStack relies on (`exec`, `prepare`, `get`, `all`, `run`,
8
+ * and `close`) is shared by both implementations.
9
+ */
10
+ export declare function loadRuntimeDatabaseSync(loadModule?: ModuleLoader): DatabaseSyncConstructor;
11
+ export declare function isRuntimeSqliteAvailable(loadModule?: ModuleLoader): boolean;
12
+ export {};
13
+ //# sourceMappingURL=sqlite-runtime.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/persistence",
3
- "version": "0.309.0",
3
+ "version": "0.310.0",
4
4
  "license": "MIT",
5
5
  "description": "Dependency-free filesystem persistence primitives shared across WrongStack packages.",
6
6
  "repository": {
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "scripts": {
37
37
  "build": "node ../../scripts/build-package.mjs",
38
- "typecheck": "tsc --noEmit",
38
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
39
39
  "test": "vitest run --config vitest.config.ts",
40
40
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
41
41
  }