@openclaw/fs-safe 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +58 -2
  3. package/dist/absolute-path.d.ts +14 -0
  4. package/dist/absolute-path.d.ts.map +1 -1
  5. package/dist/absolute-path.js +203 -2
  6. package/dist/advanced.d.ts +2 -1
  7. package/dist/advanced.d.ts.map +1 -1
  8. package/dist/advanced.js +2 -1
  9. package/dist/archive-staging.d.ts.map +1 -1
  10. package/dist/archive-staging.js +81 -7
  11. package/dist/archive.d.ts.map +1 -1
  12. package/dist/archive.js +25 -42
  13. package/dist/bounded-read-stream.d.ts +8 -0
  14. package/dist/bounded-read-stream.d.ts.map +1 -0
  15. package/dist/bounded-read-stream.js +20 -0
  16. package/dist/config.d.ts +1 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +1 -0
  19. package/dist/directory-guard.d.ts +18 -0
  20. package/dist/directory-guard.d.ts.map +1 -0
  21. package/dist/directory-guard.js +70 -0
  22. package/dist/file-lock.d.ts +4 -2
  23. package/dist/file-lock.d.ts.map +1 -1
  24. package/dist/file-lock.js +13 -2
  25. package/dist/file-store-boundary.d.ts +25 -0
  26. package/dist/file-store-boundary.d.ts.map +1 -0
  27. package/dist/file-store-boundary.js +168 -0
  28. package/dist/file-store-prune.d.ts +12 -0
  29. package/dist/file-store-prune.d.ts.map +1 -0
  30. package/dist/file-store-prune.js +86 -0
  31. package/dist/file-store-source.d.ts +5 -0
  32. package/dist/file-store-source.d.ts.map +1 -0
  33. package/dist/file-store-source.js +30 -0
  34. package/dist/file-store.d.ts +2 -6
  35. package/dist/file-store.d.ts.map +1 -1
  36. package/dist/file-store.js +122 -125
  37. package/dist/guarded-mkdir.d.ts +6 -0
  38. package/dist/guarded-mkdir.d.ts.map +1 -0
  39. package/dist/guarded-mkdir.js +45 -0
  40. package/dist/guarded-mutation.d.ts +33 -0
  41. package/dist/guarded-mutation.d.ts.map +1 -0
  42. package/dist/guarded-mutation.js +76 -0
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +2 -0
  46. package/dist/json-document-store.d.ts +2 -0
  47. package/dist/json-document-store.d.ts.map +1 -1
  48. package/dist/json-document-store.js +7 -3
  49. package/dist/json-durable-queue.d.ts +45 -0
  50. package/dist/json-durable-queue.d.ts.map +1 -0
  51. package/dist/json-durable-queue.js +200 -0
  52. package/dist/json.d.ts +35 -5
  53. package/dist/json.d.ts.map +1 -1
  54. package/dist/json.js +69 -2
  55. package/dist/local-roots.d.ts.map +1 -1
  56. package/dist/local-roots.js +17 -3
  57. package/dist/lock-config.d.ts +10 -0
  58. package/dist/lock-config.d.ts.map +1 -0
  59. package/dist/lock-config.js +12 -0
  60. package/dist/move-path.d.ts +1 -0
  61. package/dist/move-path.d.ts.map +1 -1
  62. package/dist/move-path.js +195 -7
  63. package/dist/output.d.ts +13 -0
  64. package/dist/output.d.ts.map +1 -0
  65. package/dist/output.js +69 -0
  66. package/dist/path-stat.d.ts +4 -0
  67. package/dist/path-stat.d.ts.map +1 -0
  68. package/dist/path-stat.js +15 -0
  69. package/dist/path.d.ts +1 -0
  70. package/dist/path.d.ts.map +1 -1
  71. package/dist/path.js +6 -3
  72. package/dist/pinned-write.d.ts.map +1 -1
  73. package/dist/pinned-write.js +44 -19
  74. package/dist/private-file-store.d.ts +5 -7
  75. package/dist/private-file-store.d.ts.map +1 -1
  76. package/dist/private-file-store.js +21 -34
  77. package/dist/private-temp-workspace.d.ts.map +1 -1
  78. package/dist/private-temp-workspace.js +28 -15
  79. package/dist/regular-file.d.ts.map +1 -1
  80. package/dist/regular-file.js +52 -8
  81. package/dist/replace-directory.d.ts.map +1 -1
  82. package/dist/replace-directory.js +5 -4
  83. package/dist/replace-file.d.ts +1 -1
  84. package/dist/replace-file.d.ts.map +1 -1
  85. package/dist/replace-file.js +93 -31
  86. package/dist/root-context.d.ts +23 -0
  87. package/dist/root-context.d.ts.map +1 -0
  88. package/dist/root-context.js +62 -0
  89. package/dist/root-errors.d.ts +4 -0
  90. package/dist/root-errors.d.ts.map +1 -0
  91. package/dist/root-errors.js +21 -0
  92. package/dist/root-impl.d.ts +108 -0
  93. package/dist/root-impl.d.ts.map +1 -0
  94. package/dist/{safe-root.js → root-impl.js} +484 -265
  95. package/dist/root.d.ts +1 -107
  96. package/dist/root.d.ts.map +1 -1
  97. package/dist/root.js +1 -1332
  98. package/dist/safe-path-segment.d.ts +9 -0
  99. package/dist/safe-path-segment.d.ts.map +1 -0
  100. package/dist/safe-path-segment.js +51 -0
  101. package/dist/secret-file.d.ts.map +1 -1
  102. package/dist/secret-file.js +6 -1
  103. package/dist/sibling-temp.d.ts +1 -0
  104. package/dist/sibling-temp.d.ts.map +1 -1
  105. package/dist/sibling-temp.js +32 -7
  106. package/dist/sidecar-lock.d.ts +2 -0
  107. package/dist/sidecar-lock.d.ts.map +1 -1
  108. package/dist/sidecar-lock.js +93 -13
  109. package/dist/store.d.ts +1 -0
  110. package/dist/store.d.ts.map +1 -1
  111. package/dist/store.js +1 -0
  112. package/dist/temp-target.d.ts.map +1 -1
  113. package/dist/temp-target.js +7 -4
  114. package/dist/test-hooks.d.ts +6 -0
  115. package/dist/test-hooks.d.ts.map +1 -1
  116. package/dist/text-atomic.d.ts +7 -0
  117. package/dist/text-atomic.d.ts.map +1 -1
  118. package/dist/text-atomic.js +3 -2
  119. package/dist/trash.d.ts.map +1 -1
  120. package/dist/trash.js +48 -13
  121. package/docs/advanced.md +110 -0
  122. package/docs/archive.md +199 -0
  123. package/docs/atomic.md +191 -0
  124. package/docs/config.md +94 -0
  125. package/docs/contributing.md +80 -0
  126. package/docs/errors.md +154 -0
  127. package/docs/file-store.md +213 -0
  128. package/docs/filename.md +0 -0
  129. package/docs/index.md +77 -0
  130. package/docs/install-path.md +167 -0
  131. package/docs/install.md +150 -0
  132. package/docs/json-store.md +204 -0
  133. package/docs/json.md +192 -0
  134. package/docs/local-roots.md +164 -0
  135. package/docs/output.md +92 -0
  136. package/docs/path-scope.md +130 -0
  137. package/docs/path.md +191 -0
  138. package/docs/permissions.md +95 -0
  139. package/docs/private-file-store.md +45 -0
  140. package/docs/python-helper.md +103 -0
  141. package/docs/quickstart.md +147 -0
  142. package/docs/reading.md +174 -0
  143. package/docs/regular-file.md +175 -0
  144. package/docs/root.md +205 -0
  145. package/docs/secret-file.md +154 -0
  146. package/docs/secure-file.md +82 -0
  147. package/docs/security-model.md +99 -0
  148. package/docs/sidecar-lock.md +230 -0
  149. package/docs/store.md +77 -0
  150. package/docs/temp.md +284 -0
  151. package/docs/test-hooks.md +74 -0
  152. package/docs/testing.md +178 -0
  153. package/docs/timing.md +121 -0
  154. package/docs/types.md +155 -0
  155. package/docs/walk.md +63 -0
  156. package/docs/writing.md +222 -0
  157. package/package.json +10 -2
  158. package/dist/archive-utils.d.ts +0 -3
  159. package/dist/archive-utils.d.ts.map +0 -1
  160. package/dist/archive-utils.js +0 -26
  161. package/dist/boundary-file-read.d.ts +0 -44
  162. package/dist/boundary-file-read.d.ts.map +0 -1
  163. package/dist/boundary-file-read.js +0 -129
  164. package/dist/boundary-path.d.ts +0 -39
  165. package/dist/boundary-path.d.ts.map +0 -1
  166. package/dist/boundary-path.js +0 -598
  167. package/dist/fs-pinned-path-helper.d.ts +0 -7
  168. package/dist/fs-pinned-path-helper.d.ts.map +0 -1
  169. package/dist/fs-pinned-path-helper.js +0 -182
  170. package/dist/fs-pinned-write-helper.d.ts +0 -21
  171. package/dist/fs-pinned-write-helper.d.ts.map +0 -1
  172. package/dist/fs-pinned-write-helper.js +0 -263
  173. package/dist/hardlink-guards.d.ts +0 -7
  174. package/dist/hardlink-guards.d.ts.map +0 -1
  175. package/dist/hardlink-guards.js +0 -30
  176. package/dist/install-safe-path.d.ts +0 -20
  177. package/dist/install-safe-path.d.ts.map +0 -1
  178. package/dist/install-safe-path.js +0 -94
  179. package/dist/json-file.d.ts +0 -3
  180. package/dist/json-file.d.ts.map +0 -1
  181. package/dist/json-file.js +0 -123
  182. package/dist/json-files.d.ts +0 -20
  183. package/dist/json-files.d.ts.map +0 -1
  184. package/dist/json-files.js +0 -153
  185. package/dist/path-alias-guards.d.ts +0 -19
  186. package/dist/path-alias-guards.d.ts.map +0 -1
  187. package/dist/path-alias-guards.js +0 -21
  188. package/dist/path-guards.d.ts +0 -7
  189. package/dist/path-guards.d.ts.map +0 -1
  190. package/dist/path-guards.js +0 -49
  191. package/dist/path-safety.d.ts +0 -12
  192. package/dist/path-safety.d.ts.map +0 -1
  193. package/dist/path-safety.js +0 -50
  194. package/dist/safe-open-sync.d.ts +0 -24
  195. package/dist/safe-open-sync.d.ts.map +0 -1
  196. package/dist/safe-open-sync.js +0 -71
  197. package/dist/safe-root.d.ts +0 -123
  198. package/dist/safe-root.d.ts.map +0 -1
  199. package/dist/secure-temp-workspace.d.ts +0 -25
  200. package/dist/secure-temp-workspace.d.ts.map +0 -1
  201. package/dist/secure-temp-workspace.js +0 -136
  202. package/dist/sibling-temp-file.d.ts +0 -16
  203. package/dist/sibling-temp-file.d.ts.map +0 -1
  204. package/dist/sibling-temp-file.js +0 -73
  205. package/dist/sibling-temp-write.d.ts +0 -8
  206. package/dist/sibling-temp-write.d.ts.map +0 -1
  207. package/dist/sibling-temp-write.js +0 -40
@@ -0,0 +1,45 @@
1
+ export type JsonDurableQueueEntryPaths = {
2
+ jsonPath: string;
3
+ deliveredPath: string;
4
+ };
5
+ export type JsonDurableQueueReadResult<T> = {
6
+ entry: T;
7
+ migrated?: boolean;
8
+ };
9
+ export type JsonDurableQueueLoadOptions<T> = {
10
+ queueDir: string;
11
+ tempPrefix: string;
12
+ read?: (entry: T, filePath: string) => Promise<JsonDurableQueueReadResult<T>>;
13
+ cleanupTmpMaxAgeMs?: number;
14
+ maxBytes?: number;
15
+ };
16
+ export declare const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES: number;
17
+ export declare function unlinkBestEffort(filePath: string): Promise<void>;
18
+ export declare function jsonDurableQueueEntryExists(filePath: string): Promise<boolean>;
19
+ export declare function resolveJsonDurableQueueEntryPaths(queueDir: string, id: string): JsonDurableQueueEntryPaths;
20
+ export declare function ensureJsonDurableQueueDirs(params: {
21
+ queueDir: string;
22
+ failedDir: string;
23
+ }): Promise<void>;
24
+ export declare function writeJsonDurableQueueEntry(params: {
25
+ filePath: string;
26
+ entry: unknown;
27
+ tempPrefix: string;
28
+ }): Promise<void>;
29
+ export declare function readJsonDurableQueueEntry<T>(filePath: string, options?: {
30
+ maxBytes?: number;
31
+ }): Promise<T>;
32
+ export declare function ackJsonDurableQueueEntry(paths: JsonDurableQueueEntryPaths): Promise<void>;
33
+ export declare function loadJsonDurableQueueEntry<T>(params: {
34
+ paths: JsonDurableQueueEntryPaths;
35
+ tempPrefix: string;
36
+ read?: (entry: T, filePath: string) => Promise<JsonDurableQueueReadResult<T>>;
37
+ maxBytes?: number;
38
+ }): Promise<T | null>;
39
+ export declare function loadPendingJsonDurableQueueEntries<T>(options: JsonDurableQueueLoadOptions<T>): Promise<T[]>;
40
+ export declare function moveJsonDurableQueueEntryToFailed(params: {
41
+ queueDir: string;
42
+ failedDir: string;
43
+ id: string;
44
+ }): Promise<void>;
45
+ //# sourceMappingURL=json-durable-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,0CAA0C,QAAmB,CAAC;AAY3E,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUpF;AAmBD,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,0BAA0B,CAM5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhB;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB;AAiDD,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/F;AAED,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,MAAM,EAAE;IACzD,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAwBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CAkDd;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB"}
@@ -0,0 +1,200 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { sameFileIdentity } from "./file-identity.js";
4
+ import { replaceFileAtomic } from "./replace-file.js";
5
+ import { assertSafePathSegment } from "./safe-path-segment.js";
6
+ export const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES = 16 * 1024 * 1024;
7
+ function getErrnoCode(error) {
8
+ return error && typeof error === "object" && "code" in error
9
+ ? String(error.code)
10
+ : null;
11
+ }
12
+ function assertSafeQueueEntryId(id) {
13
+ assertSafePathSegment(id, { label: "queue entry id" });
14
+ }
15
+ export async function unlinkBestEffort(filePath) {
16
+ await fs.promises.unlink(filePath).catch(() => undefined);
17
+ }
18
+ export async function jsonDurableQueueEntryExists(filePath) {
19
+ try {
20
+ const stat = await fs.promises.lstat(filePath);
21
+ return stat.isFile();
22
+ }
23
+ catch (error) {
24
+ if (getErrnoCode(error) === "ENOENT") {
25
+ return false;
26
+ }
27
+ throw error;
28
+ }
29
+ }
30
+ async function unlinkStaleTmpBestEffort(filePath, now, maxAgeMs) {
31
+ try {
32
+ const stat = await fs.promises.stat(filePath);
33
+ if (stat.isFile() && now - stat.mtimeMs >= maxAgeMs) {
34
+ await unlinkBestEffort(filePath);
35
+ }
36
+ }
37
+ catch (error) {
38
+ if (getErrnoCode(error) !== "ENOENT") {
39
+ throw error;
40
+ }
41
+ }
42
+ }
43
+ export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
44
+ assertSafeQueueEntryId(id);
45
+ return {
46
+ jsonPath: path.join(queueDir, `${id}.json`),
47
+ deliveredPath: path.join(queueDir, `${id}.delivered`),
48
+ };
49
+ }
50
+ export async function ensureJsonDurableQueueDirs(params) {
51
+ await fs.promises.mkdir(params.queueDir, { recursive: true, mode: 0o700 });
52
+ await fs.promises.mkdir(params.failedDir, { recursive: true, mode: 0o700 });
53
+ }
54
+ export async function writeJsonDurableQueueEntry(params) {
55
+ await replaceFileAtomic({
56
+ filePath: params.filePath,
57
+ content: JSON.stringify(params.entry, null, 2),
58
+ mode: 0o600,
59
+ tempPrefix: params.tempPrefix,
60
+ });
61
+ }
62
+ async function readBoundedUtf8File(params) {
63
+ const initialStat = await fs.promises.lstat(params.filePath);
64
+ if (initialStat.isSymbolicLink() || !initialStat.isFile()) {
65
+ throw new Error("queue entry is not a regular file");
66
+ }
67
+ if (initialStat.size > params.maxBytes) {
68
+ throw new Error(`queue entry exceeds ${params.maxBytes} bytes`);
69
+ }
70
+ const noFollow = typeof fs.constants.O_NOFOLLOW === "number" && process.platform !== "win32"
71
+ ? fs.constants.O_NOFOLLOW
72
+ : 0;
73
+ const handle = await fs.promises.open(params.filePath, fs.constants.O_RDONLY | noFollow);
74
+ try {
75
+ const openedStat = await handle.stat();
76
+ const pathStat = await fs.promises.lstat(params.filePath);
77
+ if (!openedStat.isFile() ||
78
+ pathStat.isSymbolicLink() ||
79
+ !pathStat.isFile() ||
80
+ !sameFileIdentity(initialStat, openedStat) ||
81
+ !sameFileIdentity(pathStat, openedStat)) {
82
+ throw new Error("queue entry changed during read");
83
+ }
84
+ const chunks = [];
85
+ const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, params.maxBytes + 1));
86
+ let total = 0;
87
+ while (true) {
88
+ const { bytesRead } = await handle.read(scratch, 0, scratch.length, null);
89
+ if (bytesRead === 0) {
90
+ return Buffer.concat(chunks, total).toString("utf8");
91
+ }
92
+ total += bytesRead;
93
+ if (total > params.maxBytes) {
94
+ throw new Error(`queue entry exceeds ${params.maxBytes} bytes`);
95
+ }
96
+ chunks.push(Buffer.from(scratch.subarray(0, bytesRead)));
97
+ }
98
+ }
99
+ finally {
100
+ await handle.close();
101
+ }
102
+ }
103
+ export async function readJsonDurableQueueEntry(filePath, options = {}) {
104
+ return JSON.parse(await readBoundedUtf8File({
105
+ filePath,
106
+ maxBytes: options.maxBytes ?? DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES,
107
+ }));
108
+ }
109
+ export async function ackJsonDurableQueueEntry(paths) {
110
+ try {
111
+ await fs.promises.rename(paths.jsonPath, paths.deliveredPath);
112
+ }
113
+ catch (error) {
114
+ if (getErrnoCode(error) === "ENOENT") {
115
+ await unlinkBestEffort(paths.deliveredPath);
116
+ return;
117
+ }
118
+ throw error;
119
+ }
120
+ await unlinkBestEffort(paths.deliveredPath);
121
+ }
122
+ export async function loadJsonDurableQueueEntry(params) {
123
+ try {
124
+ const stat = await fs.promises.lstat(params.paths.jsonPath);
125
+ if (!stat.isFile()) {
126
+ return null;
127
+ }
128
+ const raw = await readJsonDurableQueueEntry(params.paths.jsonPath, {
129
+ maxBytes: params.maxBytes,
130
+ });
131
+ const result = params.read ? await params.read(raw, params.paths.jsonPath) : { entry: raw };
132
+ if (result.migrated) {
133
+ await writeJsonDurableQueueEntry({
134
+ filePath: params.paths.jsonPath,
135
+ entry: result.entry,
136
+ tempPrefix: params.tempPrefix,
137
+ });
138
+ }
139
+ return result.entry;
140
+ }
141
+ catch (error) {
142
+ if (getErrnoCode(error) === "ENOENT") {
143
+ return null;
144
+ }
145
+ throw error;
146
+ }
147
+ }
148
+ export async function loadPendingJsonDurableQueueEntries(options) {
149
+ let files;
150
+ try {
151
+ files = await fs.promises.readdir(options.queueDir);
152
+ }
153
+ catch (error) {
154
+ if (getErrnoCode(error) === "ENOENT") {
155
+ return [];
156
+ }
157
+ throw error;
158
+ }
159
+ const now = Date.now();
160
+ for (const file of files) {
161
+ if (file.endsWith(".delivered")) {
162
+ await unlinkBestEffort(path.join(options.queueDir, file));
163
+ }
164
+ else if (options.cleanupTmpMaxAgeMs !== undefined && file.endsWith(".tmp")) {
165
+ await unlinkStaleTmpBestEffort(path.join(options.queueDir, file), now, options.cleanupTmpMaxAgeMs);
166
+ }
167
+ }
168
+ const entries = [];
169
+ for (const file of files) {
170
+ if (!file.endsWith(".json")) {
171
+ continue;
172
+ }
173
+ const filePath = path.join(options.queueDir, file);
174
+ try {
175
+ const stat = await fs.promises.lstat(filePath);
176
+ if (!stat.isFile()) {
177
+ continue;
178
+ }
179
+ const raw = await readJsonDurableQueueEntry(filePath, { maxBytes: options.maxBytes });
180
+ const result = options.read ? await options.read(raw, filePath) : { entry: raw };
181
+ if (result.migrated) {
182
+ await writeJsonDurableQueueEntry({
183
+ filePath,
184
+ entry: result.entry,
185
+ tempPrefix: options.tempPrefix,
186
+ });
187
+ }
188
+ entries.push(result.entry);
189
+ }
190
+ catch {
191
+ continue;
192
+ }
193
+ }
194
+ return entries;
195
+ }
196
+ export async function moveJsonDurableQueueEntryToFailed(params) {
197
+ assertSafeQueueEntryId(params.id);
198
+ await fs.promises.mkdir(params.failedDir, { recursive: true, mode: 0o700 });
199
+ await fs.promises.rename(path.join(params.queueDir, `${params.id}.json`), path.join(params.failedDir, `${params.id}.json`));
200
+ }
package/dist/json.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import fsSync from "node:fs";
2
+ import { type RootFileOpenFailure } from "./root-file.js";
3
+ import { type WriteTextAtomicOptions } from "./text-atomic.js";
1
4
  export declare function tryReadJsonSync<T = unknown>(pathname: string): T | null;
2
5
  export declare function writeJsonSync(pathname: string, data: unknown): void;
3
6
  export declare class JsonFileReadError extends Error {
@@ -5,13 +8,40 @@ export declare class JsonFileReadError extends Error {
5
8
  readonly reason: "read" | "parse";
6
9
  constructor(filePath: string, reason: "read" | "parse", cause: unknown);
7
10
  }
11
+ export type RootStructuredFileReadResult<T> = {
12
+ ok: true;
13
+ value: T;
14
+ stat: fsSync.Stats;
15
+ path: string;
16
+ rootRealPath: string;
17
+ } | {
18
+ ok: false;
19
+ reason: "open";
20
+ failure: RootFileOpenFailure;
21
+ } | {
22
+ ok: false;
23
+ reason: "invalid" | "parse";
24
+ error: string;
25
+ };
26
+ export type ReadRootStructuredFileSyncOptions<T> = {
27
+ rootDir: string;
28
+ rootRealPath?: string;
29
+ relativePath: string;
30
+ boundaryLabel: string;
31
+ rejectHardlinks?: boolean;
32
+ maxBytes?: number;
33
+ parse: (raw: string) => unknown;
34
+ validate?: (value: unknown) => value is T;
35
+ invalidMessage?: string | ((relativePath: string) => string);
36
+ };
37
+ export type ReadRootJsonSyncOptions = Omit<ReadRootStructuredFileSyncOptions<unknown>, "parse" | "validate" | "invalidMessage">;
38
+ export declare function readRootStructuredFileSync<T>(options: ReadRootStructuredFileSyncOptions<T>): RootStructuredFileReadResult<T>;
39
+ export declare function readRootJsonSync<T = unknown>(options: ReadRootJsonSyncOptions): RootStructuredFileReadResult<T>;
40
+ export declare function readRootJsonObjectSync(options: ReadRootJsonSyncOptions): RootStructuredFileReadResult<Record<string, unknown>>;
8
41
  export declare function tryReadJson<T>(filePath: string): Promise<T | null>;
9
42
  export declare function readJson<T>(filePath: string): Promise<T>;
10
43
  export declare function readJsonIfExists<T>(filePath: string): Promise<T | null>;
11
44
  export declare function readJsonSync<T = unknown>(filePath: string): T;
12
- export declare function writeJson(filePath: string, value: unknown, options?: {
13
- mode?: number;
14
- trailingNewline?: boolean;
15
- dirMode?: number;
16
- }): Promise<void>;
45
+ export type WriteJsonOptions = Pick<WriteTextAtomicOptions, "dirMode" | "durable" | "mode" | "trailingNewline">;
46
+ export declare function writeJson(filePath: string, value: unknown, options?: WriteJsonOptions): Promise<void>;
17
47
  //# sourceMappingURL=json.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AA8FA,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAOvE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,QAmB5D;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;gBAEtB,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO;CAMvE;AAED,wBAAsB,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAOxE;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAY9D;AAED,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAe7E;AAED,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,CAY7D;AAED,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,iBAQzE"}
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAG7B,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AA0FhF,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAOvE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,QAmB5D;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;gBAEtB,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO;CAMvE;AAED,MAAM,MAAM,4BAA4B,CAAC,CAAC,IACtC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC9E;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAE,GAC3D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,MAAM,MAAM,iCAAiC,CAAC,CAAC,IAAI;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC;IAC1C,cAAc,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;CAC9D,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACxC,iCAAiC,CAAC,OAAO,CAAC,EAC1C,OAAO,GAAG,UAAU,GAAG,gBAAgB,CACxC,CAAC;AAgBF,wBAAgB,0BAA0B,CAAC,CAAC,EAC1C,OAAO,EAAE,iCAAiC,CAAC,CAAC,CAAC,GAC5C,4BAA4B,CAAC,CAAC,CAAC,CAwCjC;AAED,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAC1C,OAAO,EAAE,uBAAuB,GAC/B,4BAA4B,CAAC,CAAC,CAAC,CAKjC;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,4BAA4B,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAOvD;AAED,wBAAsB,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAOxE;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAY9D;AAED,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAe7E;AAED,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,CAY7D;AAED,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,sBAAsB,EACtB,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,iBAAiB,CACnD,CAAC;AAEF,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,gBAAgB,iBAS3B"}
package/dist/json.js CHANGED
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import fsSync from "node:fs";
3
3
  import path from "node:path";
4
4
  import { readRegularFile, readRegularFileSync } from "./regular-file.js";
5
+ import { openRootFileSync } from "./root-file.js";
5
6
  import { writeTextAtomic } from "./text-atomic.js";
6
7
  const JSON_FILE_MODE = 0o600;
7
8
  const JSON_DIR_MODE = 0o700;
@@ -74,8 +75,8 @@ function renameJsonFileWithFallback(tmpPath, pathname) {
74
75
  fsSync.renameSync(tmpPath, pathname);
75
76
  return;
76
77
  }
77
- fsSync.copyFileSync(tmpPath, pathname);
78
- fsSync.rmSync(tmpPath, { force: true });
78
+ fsSync.rmSync(pathname, { force: true });
79
+ fsSync.renameSync(tmpPath, pathname);
79
80
  return;
80
81
  }
81
82
  throw error;
@@ -131,6 +132,71 @@ export class JsonFileReadError extends Error {
131
132
  this.reason = reason;
132
133
  }
133
134
  }
135
+ function isRecord(value) {
136
+ return typeof value === "object" && value !== null && !Array.isArray(value);
137
+ }
138
+ function resolveInvalidMessage(invalidMessage, relativePath) {
139
+ if (typeof invalidMessage === "function") {
140
+ return invalidMessage(relativePath);
141
+ }
142
+ return invalidMessage ?? `${relativePath} has an unexpected shape`;
143
+ }
144
+ export function readRootStructuredFileSync(options) {
145
+ const absolutePath = path.resolve(options.rootDir, options.relativePath);
146
+ const opened = openRootFileSync({
147
+ absolutePath,
148
+ rootPath: options.rootDir,
149
+ ...(options.rootRealPath !== undefined ? { rootRealPath: options.rootRealPath } : {}),
150
+ boundaryLabel: options.boundaryLabel,
151
+ rejectHardlinks: options.rejectHardlinks,
152
+ maxBytes: options.maxBytes,
153
+ allowedType: "file",
154
+ });
155
+ if (!opened.ok) {
156
+ return { ok: false, reason: "open", failure: opened };
157
+ }
158
+ try {
159
+ const parsed = options.parse(fsSync.readFileSync(opened.fd, "utf8"));
160
+ if (options.validate && !options.validate(parsed)) {
161
+ return {
162
+ ok: false,
163
+ reason: "invalid",
164
+ error: resolveInvalidMessage(options.invalidMessage, options.relativePath),
165
+ };
166
+ }
167
+ return {
168
+ ok: true,
169
+ value: parsed,
170
+ stat: opened.stat,
171
+ path: opened.path,
172
+ rootRealPath: opened.rootRealPath,
173
+ };
174
+ }
175
+ catch (error) {
176
+ return {
177
+ ok: false,
178
+ reason: "parse",
179
+ error: `failed to parse ${options.relativePath}: ${String(error)}`,
180
+ };
181
+ }
182
+ finally {
183
+ fsSync.closeSync(opened.fd);
184
+ }
185
+ }
186
+ export function readRootJsonSync(options) {
187
+ return readRootStructuredFileSync({
188
+ ...options,
189
+ parse: (raw) => JSON.parse(raw),
190
+ });
191
+ }
192
+ export function readRootJsonObjectSync(options) {
193
+ return readRootStructuredFileSync({
194
+ ...options,
195
+ parse: (raw) => JSON.parse(raw),
196
+ validate: isRecord,
197
+ invalidMessage: (relativePath) => `${relativePath} must contain a JSON object`,
198
+ });
199
+ }
134
200
  export async function tryReadJson(filePath) {
135
201
  try {
136
202
  const raw = (await readRegularFile({ filePath })).buffer.toString("utf8");
@@ -194,5 +260,6 @@ export async function writeJson(filePath, value, options) {
194
260
  mode: options?.mode,
195
261
  dirMode: options?.dirMode,
196
262
  trailingNewline: options?.trailingNewline,
263
+ durable: options?.durable,
197
264
  });
198
265
  }
@@ -1 +1 @@
1
- {"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAMA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AA0FF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA6B7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAgCtC"}
1
+ {"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAMA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAmGF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA6B7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAqCtC"}
@@ -52,8 +52,10 @@ function resolveRootRealSync(rootDir) {
52
52
  }
53
53
  }
54
54
  function resolveCandidateCanonicalSync(filePath) {
55
+ let sawExistingLeaf = false;
55
56
  try {
56
57
  const stat = fsSync.lstatSync(filePath);
58
+ sawExistingLeaf = true;
57
59
  return {
58
60
  exists: true,
59
61
  canonicalPath: fsSync.realpathSync(filePath),
@@ -65,6 +67,11 @@ function resolveCandidateCanonicalSync(filePath) {
65
67
  throw err;
66
68
  }
67
69
  }
70
+ if (sawExistingLeaf) {
71
+ // lstat succeeded but realpath failed: this is an existing dangling
72
+ // symlink, not a missing path callers may safely create through.
73
+ throw new FsSafeError("symlink", "local roots candidate is a dangling symlink");
74
+ }
68
75
  let cursor = filePath;
69
76
  const missingSegments = [];
70
77
  while (true) {
@@ -84,6 +91,8 @@ function resolveCandidateCanonicalSync(filePath) {
84
91
  }
85
92
  catch (err) {
86
93
  if (err.code !== "ENOENT") {
94
+ // Existing ancestors that cannot be canonicalized are symlink/error
95
+ // terrain; do not reconstruct a trusted missing path through them.
87
96
  throw err;
88
97
  }
89
98
  }
@@ -134,12 +143,17 @@ export async function readLocalFileFromRoots(options) {
134
143
  continue;
135
144
  }
136
145
  try {
137
- const result = await scopedRoot.read(relativePath, {
146
+ const readOptions = {
138
147
  hardlinks: options.hardlinks,
139
- maxBytes: options.maxBytes,
140
148
  nonBlockingRead: options.nonBlockingRead,
141
149
  symlinks: options.symlinks,
142
- });
150
+ };
151
+ // Leave maxBytes absent when the caller omits it so Root's own default
152
+ // cap remains in force instead of being overwritten by undefined.
153
+ if (options.maxBytes !== undefined) {
154
+ readOptions.maxBytes = options.maxBytes;
155
+ }
156
+ const result = await scopedRoot.read(relativePath, readOptions);
143
157
  return { ...result, root: scopedRoot.rootReal };
144
158
  }
145
159
  catch {
@@ -0,0 +1,10 @@
1
+ import type { SidecarLockRetryOptions, SidecarLockStaleRecovery } from "./sidecar-lock.js";
2
+ export type FsSafeLockConfig = {
3
+ staleRecovery: SidecarLockStaleRecovery;
4
+ staleMs?: number;
5
+ timeoutMs?: number;
6
+ retry?: SidecarLockRetryOptions;
7
+ };
8
+ export declare function configureFsSafeLocks(config: Partial<FsSafeLockConfig>): void;
9
+ export declare function getFsSafeLockConfig(): FsSafeLockConfig;
10
+ //# sourceMappingURL=lock-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock-config.d.ts","sourceRoot":"","sources":["../src/lock-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAE3F,MAAM,MAAM,gBAAgB,GAAG;IAC7B,aAAa,EAAE,wBAAwB,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC,CAAC;AAQF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAI5E;AAED,wBAAgB,mBAAmB,IAAI,gBAAgB,CAEtD"}
@@ -0,0 +1,12 @@
1
+ const DEFAULT_LOCK_CONFIG = {
2
+ staleRecovery: "fail-closed",
3
+ };
4
+ let lockConfig = { ...DEFAULT_LOCK_CONFIG };
5
+ export function configureFsSafeLocks(config) {
6
+ // Process defaults only fill lock options after a caller explicitly enables
7
+ // locking for a resource; this must never turn sidecar locks on globally.
8
+ lockConfig = { ...lockConfig, ...config };
9
+ }
10
+ export function getFsSafeLockConfig() {
11
+ return { ...lockConfig, retry: lockConfig.retry ? { ...lockConfig.retry } : undefined };
12
+ }
@@ -1,5 +1,6 @@
1
1
  export type MovePathWithCopyFallbackOptions = {
2
2
  from: string;
3
+ sourceHardlinks?: "allow" | "reject";
3
4
  to: string;
4
5
  };
5
6
  export declare function movePathWithCopyFallback(options: MovePathWithCopyFallbackOptions): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,+BAA+B,GAAG;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAef"}
1
+ {"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,+BAA+B,GAAG;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAqPF,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAuBf"}