@openclaw/fs-safe 0.6.0 → 0.7.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 (183) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +7 -0
  3. package/dist/archive-deadline.d.ts +3 -0
  4. package/dist/archive-deadline.d.ts.map +1 -1
  5. package/dist/archive-deadline.js +44 -8
  6. package/dist/archive-entry.d.ts.map +1 -1
  7. package/dist/archive-entry.js +1 -0
  8. package/dist/archive-errors.d.ts +1 -0
  9. package/dist/archive-errors.d.ts.map +1 -1
  10. package/dist/archive-errors.js +3 -0
  11. package/dist/archive-input.d.ts.map +1 -1
  12. package/dist/archive-input.js +26 -16
  13. package/dist/archive-limits.d.ts +11 -3
  14. package/dist/archive-limits.d.ts.map +1 -1
  15. package/dist/archive-limits.js +24 -0
  16. package/dist/archive-native.d.ts +3 -2
  17. package/dist/archive-native.d.ts.map +1 -1
  18. package/dist/archive-native.js +24 -9
  19. package/dist/archive-policy.d.ts +2 -0
  20. package/dist/archive-policy.d.ts.map +1 -1
  21. package/dist/archive-policy.js +9 -1
  22. package/dist/archive-read.d.ts.map +1 -1
  23. package/dist/archive-read.js +69 -41
  24. package/dist/archive-staging.d.ts +3 -0
  25. package/dist/archive-staging.d.ts.map +1 -1
  26. package/dist/archive-staging.js +91 -43
  27. package/dist/archive-tar-admission.d.ts +7 -0
  28. package/dist/archive-tar-admission.d.ts.map +1 -0
  29. package/dist/archive-tar-admission.js +43 -0
  30. package/dist/archive-tar-gnu.d.ts +2 -0
  31. package/dist/archive-tar-gnu.d.ts.map +1 -0
  32. package/dist/archive-tar-gnu.js +20 -0
  33. package/dist/archive-tar-header.d.ts +8 -0
  34. package/dist/archive-tar-header.d.ts.map +1 -0
  35. package/dist/archive-tar-header.js +47 -0
  36. package/dist/archive-tar-meta.d.ts +29 -1
  37. package/dist/archive-tar-meta.d.ts.map +1 -1
  38. package/dist/archive-tar-meta.js +104 -35
  39. package/dist/archive-tar-runtime.d.ts +3 -0
  40. package/dist/archive-tar-runtime.d.ts.map +1 -1
  41. package/dist/archive-tar-runtime.js +3 -0
  42. package/dist/archive-tar.d.ts.map +1 -1
  43. package/dist/archive-tar.js +8 -2
  44. package/dist/archive-zip-names.js +2 -2
  45. package/dist/archive.d.ts.map +1 -1
  46. package/dist/archive.js +38 -26
  47. package/dist/atomic.d.ts +1 -1
  48. package/dist/atomic.d.ts.map +1 -1
  49. package/dist/bounded-read-stream.d.ts.map +1 -1
  50. package/dist/bounded-read-stream.js +2 -4
  51. package/dist/bounded-read.d.ts.map +1 -1
  52. package/dist/bounded-read.js +3 -10
  53. package/dist/byte-budget.d.ts +5 -0
  54. package/dist/byte-budget.d.ts.map +1 -0
  55. package/dist/byte-budget.js +9 -0
  56. package/dist/file-lock-sync.d.ts.map +1 -1
  57. package/dist/file-lock-sync.js +10 -5
  58. package/dist/file-store-boundary.d.ts.map +1 -1
  59. package/dist/file-store-boundary.js +9 -5
  60. package/dist/file-store-limit.d.ts +2 -0
  61. package/dist/file-store-limit.d.ts.map +1 -0
  62. package/dist/file-store-limit.js +8 -0
  63. package/dist/file-store-sync-write.d.ts.map +1 -1
  64. package/dist/file-store-sync-write.js +38 -8
  65. package/dist/file-store.d.ts.map +1 -1
  66. package/dist/file-store.js +26 -26
  67. package/dist/json-durable-queue-directory.d.ts +2 -0
  68. package/dist/json-durable-queue-directory.d.ts.map +1 -0
  69. package/dist/json-durable-queue-directory.js +20 -0
  70. package/dist/json-durable-queue-ownership.d.ts +14 -0
  71. package/dist/json-durable-queue-ownership.d.ts.map +1 -0
  72. package/dist/json-durable-queue-ownership.js +168 -0
  73. package/dist/json-durable-queue-retirement.d.ts +9 -0
  74. package/dist/json-durable-queue-retirement.d.ts.map +1 -0
  75. package/dist/json-durable-queue-retirement.js +126 -0
  76. package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
  77. package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
  78. package/dist/json-durable-queue-transfer-lock.js +19 -0
  79. package/dist/json-durable-queue.d.ts +1 -0
  80. package/dist/json-durable-queue.d.ts.map +1 -1
  81. package/dist/json-durable-queue.js +90 -57
  82. package/dist/json.d.ts.map +1 -1
  83. package/dist/json.js +27 -8
  84. package/dist/local-roots.d.ts.map +1 -1
  85. package/dist/local-roots.js +4 -2
  86. package/dist/native-binding.d.ts +12 -3
  87. package/dist/native-binding.d.ts.map +1 -1
  88. package/dist/output-sibling.d.ts.map +1 -1
  89. package/dist/output-sibling.js +11 -110
  90. package/dist/output.d.ts.map +1 -1
  91. package/dist/output.js +4 -2
  92. package/dist/pinned-open.d.ts.map +1 -1
  93. package/dist/pinned-open.js +27 -48
  94. package/dist/pinned-write.d.ts.map +1 -1
  95. package/dist/pinned-write.js +8 -9
  96. package/dist/private-temp-workspace.d.ts +3 -1
  97. package/dist/private-temp-workspace.d.ts.map +1 -1
  98. package/dist/private-temp-workspace.js +81 -56
  99. package/dist/publish-file.d.ts.map +1 -1
  100. package/dist/publish-file.js +2 -4
  101. package/dist/read-opened-file.d.ts.map +1 -1
  102. package/dist/read-opened-file.js +6 -4
  103. package/dist/regular-file.d.ts.map +1 -1
  104. package/dist/regular-file.js +136 -82
  105. package/dist/replace-file-copy-fallback.d.ts +3 -1
  106. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  107. package/dist/replace-file-copy-fallback.js +21 -36
  108. package/dist/replace-file-copy-source.d.ts +21 -0
  109. package/dist/replace-file-copy-source.d.ts.map +1 -0
  110. package/dist/replace-file-copy-source.js +112 -0
  111. package/dist/replace-file-descriptor.d.ts +13 -3
  112. package/dist/replace-file-descriptor.d.ts.map +1 -1
  113. package/dist/replace-file-descriptor.js +32 -7
  114. package/dist/replace-file-rename-policy.d.ts +7 -0
  115. package/dist/replace-file-rename-policy.d.ts.map +1 -0
  116. package/dist/replace-file-rename-policy.js +30 -0
  117. package/dist/replace-file-temp-owner.d.ts +46 -0
  118. package/dist/replace-file-temp-owner.d.ts.map +1 -0
  119. package/dist/replace-file-temp-owner.js +346 -0
  120. package/dist/replace-file.d.ts +6 -1
  121. package/dist/replace-file.d.ts.map +1 -1
  122. package/dist/replace-file.js +72 -58
  123. package/dist/root-impl.d.ts.map +1 -1
  124. package/dist/root-impl.js +40 -26
  125. package/dist/secret-file.d.ts.map +1 -1
  126. package/dist/secret-file.js +6 -3
  127. package/dist/secret-read-async.d.ts.map +1 -1
  128. package/dist/secret-read-async.js +6 -5
  129. package/dist/secure-file.d.ts.map +1 -1
  130. package/dist/secure-file.js +17 -13
  131. package/dist/sibling-staged-file.d.ts +15 -0
  132. package/dist/sibling-staged-file.d.ts.map +1 -0
  133. package/dist/sibling-staged-file.js +148 -0
  134. package/dist/sibling-temp.d.ts +3 -0
  135. package/dist/sibling-temp.d.ts.map +1 -1
  136. package/dist/sibling-temp.js +30 -74
  137. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  138. package/dist/sidecar-lock-acquire.js +48 -27
  139. package/dist/sidecar-lock-handle.d.ts +6 -2
  140. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  141. package/dist/sidecar-lock-handle.js +17 -3
  142. package/dist/sidecar-lock-policy.d.ts +2 -0
  143. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  144. package/dist/sidecar-lock-policy.js +29 -0
  145. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  146. package/dist/sidecar-lock-reclaim.js +29 -7
  147. package/dist/sidecar-lock.d.ts.map +1 -1
  148. package/dist/sidecar-lock.js +28 -16
  149. package/dist/suppressed-error.d.ts +6 -0
  150. package/dist/suppressed-error.d.ts.map +1 -0
  151. package/dist/suppressed-error.js +15 -0
  152. package/dist/temp-cleanup.d.ts +2 -0
  153. package/dist/temp-cleanup.d.ts.map +1 -1
  154. package/dist/temp-cleanup.js +25 -10
  155. package/dist/temp-workspace-owner.d.ts +23 -0
  156. package/dist/temp-workspace-owner.d.ts.map +1 -0
  157. package/dist/temp-workspace-owner.js +320 -0
  158. package/dist/temp.d.ts +1 -1
  159. package/dist/temp.d.ts.map +1 -1
  160. package/dist/test-hooks.d.ts +5 -0
  161. package/dist/test-hooks.d.ts.map +1 -1
  162. package/docs/advanced.md +8 -1
  163. package/docs/archive.md +194 -26
  164. package/docs/atomic.md +11 -2
  165. package/docs/durability.md +1 -1
  166. package/docs/file-store.md +2 -0
  167. package/docs/install.md +8 -4
  168. package/docs/json.md +8 -4
  169. package/docs/native-helper.md +15 -3
  170. package/docs/native.md +33 -6
  171. package/docs/output.md +10 -0
  172. package/docs/public-api.md +6 -3
  173. package/docs/quickstart.md +1 -1
  174. package/docs/reading.md +1 -1
  175. package/docs/regular-file.md +9 -2
  176. package/docs/root.md +2 -0
  177. package/docs/secret-file.md +5 -1
  178. package/docs/secure-file.md +4 -2
  179. package/docs/security-model.md +18 -0
  180. package/docs/sidecar-lock.md +4 -0
  181. package/docs/store.md +13 -0
  182. package/docs/temp.md +128 -14
  183. package/package.json +10 -10
@@ -0,0 +1,168 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { syncDirectory } from "./directory-durability.js";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
6
+ import { recoverDurableQueueRetirement, retireDurableQueueSource, } from "./json-durable-queue-retirement.js";
7
+ import { withQueueTransferLock } from "./json-durable-queue-transfer-lock.js";
8
+ import { serializePathWrite } from "./write-queue.js";
9
+ function getErrorCode(error) {
10
+ return error && typeof error === "object" && "code" in error
11
+ ? String(error.code)
12
+ : null;
13
+ }
14
+ export function durableQueueProcessingPath(paths) {
15
+ if (paths.processingPath)
16
+ return paths.processingPath;
17
+ return paths.jsonPath.endsWith(".json")
18
+ ? `${paths.jsonPath.slice(0, -".json".length)}.processing`
19
+ : `${paths.jsonPath}.processing`;
20
+ }
21
+ async function lstatOrNull(filePath) {
22
+ try {
23
+ return await fs.lstat(filePath, { bigint: true });
24
+ }
25
+ catch (error) {
26
+ if (getErrorCode(error) === "ENOENT")
27
+ return null;
28
+ throw error;
29
+ }
30
+ }
31
+ async function regularQueueFileIdentity(filePath) {
32
+ const identity = await lstatOrNull(filePath);
33
+ if (!identity)
34
+ return null;
35
+ if (identity.isSymbolicLink() || !identity.isFile()) {
36
+ throw new Error(`queue entry is not a regular file: ${filePath}`);
37
+ }
38
+ return identity;
39
+ }
40
+ async function withQueueEntryLock(paths, run) {
41
+ return await serializePathWrite(paths.jsonPath, async () => await withQueueTransferLock(path.resolve(paths.jsonPath), run));
42
+ }
43
+ async function claimDurableQueueEntryUnlocked(paths) {
44
+ const processingPath = durableQueueProcessingPath(paths);
45
+ await recoverDurableQueueRetirement({ jsonPath: paths.jsonPath, processingPath });
46
+ const existingProcessing = await regularQueueFileIdentity(processingPath);
47
+ if (existingProcessing) {
48
+ const pending = await lstatOrNull(paths.jsonPath);
49
+ if (pending &&
50
+ !pending.isSymbolicLink() &&
51
+ pending.isFile() &&
52
+ sameFileIdentityForCleanup(pending, existingProcessing)) {
53
+ await retireDurableQueueSource({ jsonPath: paths.jsonPath, processingPath });
54
+ }
55
+ return processingPath;
56
+ }
57
+ const pending = await lstatOrNull(paths.jsonPath);
58
+ if (!pending || pending.isSymbolicLink() || !pending.isFile())
59
+ return null;
60
+ if (pending.nlink > 1n || !sameFileIdentityForCleanup(pending, pending)) {
61
+ throw new FsSafeError("path-mismatch", "queue entry is not exclusively owned");
62
+ }
63
+ try {
64
+ await fs.link(paths.jsonPath, processingPath);
65
+ }
66
+ catch (error) {
67
+ if (getErrorCode(error) === "EEXIST") {
68
+ return (await regularQueueFileIdentity(processingPath)) ? processingPath : null;
69
+ }
70
+ if (getErrorCode(error) === "ENOENT")
71
+ return null;
72
+ throw error;
73
+ }
74
+ await syncDirectory(path.dirname(processingPath));
75
+ const claimed = await regularQueueFileIdentity(processingPath);
76
+ if (!claimed || claimed.nlink > 2n) {
77
+ throw new FsSafeError("path-mismatch", "queue claim is not an owned regular file");
78
+ }
79
+ await retireDurableQueueSource({ jsonPath: paths.jsonPath, processingPath });
80
+ return processingPath;
81
+ }
82
+ export async function claimDurableQueueEntry(paths) {
83
+ if (!(await lstatOrNull(path.dirname(paths.jsonPath))))
84
+ return null;
85
+ return await withQueueEntryLock(paths, async () => await claimDurableQueueEntryUnlocked(paths));
86
+ }
87
+ export async function completeDeliveredQueueEntry(paths) {
88
+ if (!(await lstatOrNull(path.dirname(paths.deliveredPath))))
89
+ return false;
90
+ return await withQueueEntryLock(paths, async () => {
91
+ if (!(await regularQueueFileIdentity(paths.deliveredPath)))
92
+ return false;
93
+ if (await regularQueueFileIdentity(durableQueueProcessingPath(paths))) {
94
+ await fs.unlink(paths.deliveredPath);
95
+ await syncDirectory(path.dirname(paths.deliveredPath));
96
+ return false;
97
+ }
98
+ await fs.unlink(paths.deliveredPath);
99
+ await syncDirectory(path.dirname(paths.deliveredPath));
100
+ return true;
101
+ });
102
+ }
103
+ export async function acknowledgeDurableQueueEntry(paths) {
104
+ if (!(await lstatOrNull(path.dirname(paths.jsonPath))))
105
+ return;
106
+ await withQueueEntryLock(paths, async () => {
107
+ const processingPath = durableQueueProcessingPath(paths);
108
+ const processing = await regularQueueFileIdentity(processingPath);
109
+ const delivered = await regularQueueFileIdentity(paths.deliveredPath);
110
+ if (!processing) {
111
+ if (delivered) {
112
+ await fs.unlink(paths.deliveredPath);
113
+ await syncDirectory(path.dirname(paths.deliveredPath));
114
+ }
115
+ if (await regularQueueFileIdentity(paths.jsonPath)) {
116
+ throw new FsSafeError("path-mismatch", "queue acknowledgement requires a processing claim");
117
+ }
118
+ return;
119
+ }
120
+ if (delivered)
121
+ await fs.unlink(paths.deliveredPath);
122
+ await fs.rename(processingPath, paths.deliveredPath);
123
+ await syncDirectory(path.dirname(paths.deliveredPath));
124
+ await fs.unlink(paths.deliveredPath);
125
+ await syncDirectory(path.dirname(paths.deliveredPath));
126
+ });
127
+ }
128
+ export async function moveDurableQueueEntryToFailed(params) {
129
+ await withQueueEntryLock(params.paths, async () => {
130
+ const processingPath = durableQueueProcessingPath(params.paths);
131
+ const processing = await regularQueueFileIdentity(processingPath);
132
+ const existingFailed = await regularQueueFileIdentity(params.failedPath);
133
+ if (!processing && existingFailed) {
134
+ await syncDirectory(path.dirname(params.failedPath));
135
+ await syncDirectory(path.dirname(processingPath));
136
+ const recoveredFailed = await regularQueueFileIdentity(params.failedPath);
137
+ if (!recoveredFailed ||
138
+ !sameFileIdentityForCleanup(existingFailed, recoveredFailed)) {
139
+ throw new FsSafeError("path-mismatch", "failed queue destination changed during recovery");
140
+ }
141
+ if (await regularQueueFileIdentity(params.paths.jsonPath)) {
142
+ throw new FsSafeError("already-exists", "failed queue destination already exists");
143
+ }
144
+ return;
145
+ }
146
+ const sourcePath = processing
147
+ ? processingPath
148
+ : await claimDurableQueueEntryUnlocked(params.paths);
149
+ if (!sourcePath) {
150
+ throw Object.assign(new Error("queue entry does not exist"), { code: "ENOENT" });
151
+ }
152
+ const source = await regularQueueFileIdentity(sourcePath);
153
+ const failed = await regularQueueFileIdentity(params.failedPath);
154
+ if (failed) {
155
+ if (source && sameFileIdentityForCleanup(source, failed)) {
156
+ await syncDirectory(path.dirname(params.failedPath));
157
+ await fs.unlink(sourcePath);
158
+ await syncDirectory(path.dirname(sourcePath));
159
+ return;
160
+ }
161
+ throw new FsSafeError("already-exists", "failed queue destination already exists");
162
+ }
163
+ await fs.link(sourcePath, params.failedPath);
164
+ await syncDirectory(path.dirname(params.failedPath));
165
+ await fs.unlink(sourcePath);
166
+ await syncDirectory(path.dirname(sourcePath));
167
+ });
168
+ }
@@ -0,0 +1,9 @@
1
+ export declare function recoverDurableQueueRetirement(params: {
2
+ jsonPath: string;
3
+ processingPath: string;
4
+ }): Promise<void>;
5
+ export declare function retireDurableQueueSource(params: {
6
+ jsonPath: string;
7
+ processingPath: string;
8
+ }): Promise<void>;
9
+ //# sourceMappingURL=json-durable-queue-retirement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-durable-queue-retirement.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-retirement.ts"],"names":[],"mappings":"AAgFA,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmChB;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBhB"}
@@ -0,0 +1,126 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { syncDirectory } from "./directory-durability.js";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
6
+ const RETIREMENT_ROOT_NAME = ".fs-safe-retirements";
7
+ const RETIREMENT_ENTRY_NAME = "entry";
8
+ function getErrorCode(error) {
9
+ return error && typeof error === "object" && "code" in error
10
+ ? String(error.code)
11
+ : null;
12
+ }
13
+ async function lstatOrNull(filePath) {
14
+ try {
15
+ return await fs.lstat(filePath, { bigint: true });
16
+ }
17
+ catch (error) {
18
+ if (getErrorCode(error) === "ENOENT")
19
+ return null;
20
+ throw error;
21
+ }
22
+ }
23
+ function assertDirectory(identity, label) {
24
+ if (identity.isSymbolicLink() || !identity.isDirectory()) {
25
+ throw new FsSafeError("path-mismatch", `${label} is not an owned directory`);
26
+ }
27
+ }
28
+ async function regularFileIdentity(filePath) {
29
+ const identity = await lstatOrNull(filePath);
30
+ if (!identity)
31
+ return null;
32
+ if (identity.isSymbolicLink() || !identity.isFile()) {
33
+ throw new FsSafeError("path-mismatch", "queue retirement path is not a regular file");
34
+ }
35
+ return identity;
36
+ }
37
+ function retirementPaths(jsonPath) {
38
+ const rootPath = path.join(path.dirname(jsonPath), RETIREMENT_ROOT_NAME);
39
+ const dirPath = path.join(rootPath, path.basename(jsonPath));
40
+ return { dirPath, entryPath: path.join(dirPath, RETIREMENT_ENTRY_NAME), rootPath };
41
+ }
42
+ async function inspectRetirementRoot(jsonPath) {
43
+ const rootPath = retirementPaths(jsonPath).rootPath;
44
+ const root = await lstatOrNull(rootPath);
45
+ if (!root)
46
+ return null;
47
+ assertDirectory(root, "queue retirement root");
48
+ return rootPath;
49
+ }
50
+ async function ensureRetirementRoot(jsonPath) {
51
+ const parentPath = path.dirname(jsonPath);
52
+ const rootPath = retirementPaths(jsonPath).rootPath;
53
+ await fs.mkdir(rootPath, { mode: 0o700 }).catch((error) => {
54
+ if (getErrorCode(error) !== "EEXIST")
55
+ throw error;
56
+ });
57
+ const root = await fs.lstat(rootPath, { bigint: true });
58
+ assertDirectory(root, "queue retirement root");
59
+ await syncDirectory(parentPath);
60
+ return rootPath;
61
+ }
62
+ async function removeRetirementRecord(params) {
63
+ await fs.rmdir(params.dirPath).catch((error) => {
64
+ if (getErrorCode(error) !== "ENOENT")
65
+ throw error;
66
+ });
67
+ await syncDirectory(params.rootPath);
68
+ }
69
+ export async function recoverDurableQueueRetirement(params) {
70
+ const rootPath = await inspectRetirementRoot(params.jsonPath);
71
+ if (!rootPath)
72
+ return;
73
+ const { dirPath, entryPath } = retirementPaths(params.jsonPath);
74
+ const dir = await lstatOrNull(dirPath);
75
+ if (!dir)
76
+ return;
77
+ assertDirectory(dir, "queue retirement record");
78
+ const names = await fs.readdir(dirPath);
79
+ if (names.length === 0) {
80
+ await removeRetirementRecord({ dirPath, rootPath });
81
+ return;
82
+ }
83
+ if (names.length !== 1 || names[0] !== RETIREMENT_ENTRY_NAME) {
84
+ throw new FsSafeError("path-mismatch", "queue retirement record is ambiguous");
85
+ }
86
+ const entry = await regularFileIdentity(entryPath);
87
+ const processing = await regularFileIdentity(params.processingPath);
88
+ if (!entry || !processing) {
89
+ throw new FsSafeError("path-mismatch", "queue retirement ownership is incomplete");
90
+ }
91
+ if (!sameFileIdentityForCleanup(entry, processing)) {
92
+ const pending = await regularFileIdentity(params.jsonPath);
93
+ if (!pending) {
94
+ await fs.link(entryPath, params.jsonPath);
95
+ await syncDirectory(path.dirname(params.jsonPath));
96
+ }
97
+ else if (sameFileIdentityForCleanup(pending, entry)) {
98
+ await syncDirectory(path.dirname(params.jsonPath));
99
+ }
100
+ else if (pending.nlink > 1n) {
101
+ throw new FsSafeError("path-mismatch", "queue replacement identity is ambiguous");
102
+ }
103
+ }
104
+ await fs.unlink(entryPath);
105
+ await syncDirectory(dirPath);
106
+ await removeRetirementRecord({ dirPath, rootPath });
107
+ }
108
+ export async function retireDurableQueueSource(params) {
109
+ await recoverDurableQueueRetirement(params);
110
+ const rootPath = await ensureRetirementRoot(params.jsonPath);
111
+ const { dirPath, entryPath } = retirementPaths(params.jsonPath);
112
+ await fs.mkdir(dirPath, { mode: 0o700 });
113
+ await syncDirectory(rootPath);
114
+ try {
115
+ await fs.rename(params.jsonPath, entryPath);
116
+ }
117
+ catch (error) {
118
+ if (getErrorCode(error) !== "ENOENT")
119
+ throw error;
120
+ await removeRetirementRecord({ dirPath, rootPath });
121
+ return;
122
+ }
123
+ await syncDirectory(dirPath);
124
+ await syncDirectory(path.dirname(params.jsonPath));
125
+ await recoverDurableQueueRetirement(params);
126
+ }
@@ -0,0 +1,2 @@
1
+ export declare function withQueueTransferLock<T>(filePath: string, run: () => Promise<T>): Promise<T>;
2
+ //# sourceMappingURL=json-durable-queue-transfer-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-durable-queue-transfer-lock.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-transfer-lock.ts"],"names":[],"mappings":"AAKA,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAqBZ"}
@@ -0,0 +1,19 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import path from "node:path";
3
+ import { sha256Hex } from "./file-identity.js";
4
+ import { withSidecarLock } from "./sidecar-lock.js";
5
+ export async function withQueueTransferLock(filePath, run) {
6
+ const lockPath = path.join(path.dirname(filePath), `.fs-safe-transfer-${sha256Hex(path.basename(filePath)).slice(0, 32)}.lock`);
7
+ return await withSidecarLock(filePath, {
8
+ managerKey: "fs-safe.queue-transfer",
9
+ lockPath,
10
+ staleMs: 30_000,
11
+ staleRecovery: "fail-closed",
12
+ timeoutMs: 45_000,
13
+ payload: () => ({
14
+ ownerToken: randomUUID(),
15
+ createdAt: new Date().toISOString(),
16
+ }),
17
+ retry: { retries: 180, minTimeout: 25, maxTimeout: 250, factor: 1.1 },
18
+ }, run);
19
+ }
@@ -1,6 +1,7 @@
1
1
  export type JsonDurableQueueEntryPaths = {
2
2
  jsonPath: string;
3
3
  deliveredPath: string;
4
+ processingPath?: string;
4
5
  };
5
6
  export type JsonDurableQueueReadResult<T> = {
6
7
  entry: T;
@@ -1 +1 @@
1
- {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAQA,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;AAOF,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,CAIhB;AA+KD,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;AAsDD,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,CAShB"}
1
+ {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,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;AAOF,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,CAO5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAgLD,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,CAShB;AA4ED,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CASZ;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/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,CAsBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CA4Dd;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,CAShB"}
@@ -1,9 +1,13 @@
1
- import fs from "node:fs";
1
+ import fs, {} from "node:fs";
2
2
  import path from "node:path";
3
- import { sameFileIdentity } from "./file-identity.js";
3
+ import { normalizeMaxBytes } from "./byte-budget.js";
4
+ import { syncQueueDirectoryCreation } from "./json-durable-queue-directory.js";
5
+ import { acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, moveDurableQueueEntryToFailed, } from "./json-durable-queue-ownership.js";
4
6
  import { stringifyJsonDocument } from "./json-stringify.js";
7
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
5
8
  import { replaceFileAtomic } from "./replace-file.js";
6
9
  import { assertSafePathSegment } from "./safe-path-segment.js";
10
+ import { inspectFileIdentity } from "./strict-file-identity.js";
7
11
  export const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES = 16 * 1024 * 1024;
8
12
  function getErrnoCode(error) {
9
13
  return error && typeof error === "object" && "code" in error
@@ -46,6 +50,7 @@ export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
46
50
  return {
47
51
  jsonPath: path.join(queueDir, `${id}.json`),
48
52
  deliveredPath: path.join(queueDir, `${id}.delivered`),
53
+ processingPath: path.join(queueDir, `${id}.processing`),
49
54
  };
50
55
  }
51
56
  export async function ensureJsonDurableQueueDirs(params) {
@@ -61,6 +66,7 @@ async function ensureJsonDurableQueueDir(dir, validationRoot) {
61
66
  await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
62
67
  await assertNoSymlinkDirectorySegments(root, dir, false);
63
68
  await chmodQueueDirectory(dir);
69
+ await syncQueueDirectoryCreation(dir, root.path);
64
70
  }
65
71
  async function assertJsonDurableQueueDir(dir, validationRoot) {
66
72
  const root = validationRoot
@@ -208,35 +214,55 @@ export async function writeJsonDurableQueueEntry(params) {
208
214
  content: stringifyJsonDocument(params.entry, null, 2),
209
215
  mode: 0o600,
210
216
  tempPrefix: params.tempPrefix,
217
+ syncTempFile: true,
218
+ syncParentDir: true,
211
219
  });
212
220
  }
213
- async function readBoundedUtf8File(params) {
214
- const initialStat = await fs.promises.lstat(params.filePath);
215
- if (initialStat.isSymbolicLink() || !initialStat.isFile()) {
216
- throw new Error("queue entry is not a regular file");
217
- }
218
- if (initialStat.nlink > 1) {
219
- throw new Error("queue entry hardlinks are not allowed");
221
+ async function replaceJsonDurableQueueEntry(params) {
222
+ await replaceFileAtomic({
223
+ filePath: params.filePath,
224
+ content: stringifyJsonDocument(params.entry, null, 2),
225
+ mode: 0o600,
226
+ tempPrefix: params.tempPrefix,
227
+ syncTempFile: true,
228
+ syncParentDir: true,
229
+ });
230
+ }
231
+ async function inspectQueueEntry(inspect, maxBytes, expected) {
232
+ let inspectionFailed = false;
233
+ try {
234
+ return await inspectFileIdentity(async () => {
235
+ try {
236
+ const stat = await inspect();
237
+ if (stat.isSymbolicLink() || !stat.isFile()) {
238
+ throw new Error("queue entry is not a regular file");
239
+ }
240
+ if (stat.nlink > 1n)
241
+ throw new Error("queue entry hardlinks are not allowed");
242
+ if (stat.size > maxBytes)
243
+ throw new Error(`queue entry exceeds ${maxBytes} bytes`);
244
+ return stat;
245
+ }
246
+ catch (error) {
247
+ inspectionFailed = true;
248
+ throw error;
249
+ }
250
+ }, expected);
220
251
  }
221
- if (initialStat.size > params.maxBytes) {
222
- throw new Error(`queue entry exceeds ${params.maxBytes} bytes`);
252
+ catch (error) {
253
+ // Translate only the identity helper's rejection, never inspection errors.
254
+ if (inspectionFailed)
255
+ throw error;
256
+ throw new Error("queue entry changed during read", { cause: error });
223
257
  }
224
- const noFollow = typeof fs.constants.O_NOFOLLOW === "number" && process.platform !== "win32"
225
- ? fs.constants.O_NOFOLLOW
226
- : 0;
227
- const handle = await fs.promises.open(params.filePath, fs.constants.O_RDONLY | noFollow);
258
+ }
259
+ async function readBoundedUtf8File(params) {
260
+ const inspectPath = () => fs.promises.lstat(params.filePath, { bigint: true });
261
+ const initialStat = await inspectQueueEntry(inspectPath, params.maxBytes);
262
+ const handle = await fs.promises.open(params.filePath, resolveReadOpenFlags());
228
263
  try {
229
- const openedStat = await handle.stat();
230
- const pathStat = await fs.promises.lstat(params.filePath);
231
- if (!openedStat.isFile() ||
232
- pathStat.isSymbolicLink() ||
233
- !pathStat.isFile() ||
234
- openedStat.nlink > 1 ||
235
- pathStat.nlink > 1 ||
236
- !sameFileIdentity(initialStat, openedStat) ||
237
- !sameFileIdentity(pathStat, openedStat)) {
238
- throw new Error("queue entry changed during read");
239
- }
264
+ const openedStat = await inspectQueueEntry(() => handle.stat({ bigint: true }), params.maxBytes, initialStat);
265
+ await inspectQueueEntry(inspectPath, params.maxBytes, openedStat);
240
266
  const chunks = [];
241
267
  const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, params.maxBytes + 1));
242
268
  let total = 0;
@@ -259,35 +285,26 @@ async function readBoundedUtf8File(params) {
259
285
  export async function readJsonDurableQueueEntry(filePath, options = {}) {
260
286
  return JSON.parse(await readBoundedUtf8File({
261
287
  filePath,
262
- maxBytes: options.maxBytes ?? DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES,
288
+ maxBytes: normalizeMaxBytes(options.maxBytes, {
289
+ defaultValue: DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES,
290
+ }),
263
291
  }));
264
292
  }
265
293
  export async function ackJsonDurableQueueEntry(paths) {
266
- try {
267
- await fs.promises.rename(paths.jsonPath, paths.deliveredPath);
268
- }
269
- catch (error) {
270
- if (getErrnoCode(error) === "ENOENT") {
271
- await unlinkBestEffort(paths.deliveredPath);
272
- return;
273
- }
274
- throw error;
275
- }
276
- await unlinkBestEffort(paths.deliveredPath);
294
+ await acknowledgeDurableQueueEntry(paths);
277
295
  }
278
296
  export async function loadJsonDurableQueueEntry(params) {
279
297
  try {
280
- const stat = await fs.promises.lstat(params.paths.jsonPath);
281
- if (!stat.isFile()) {
298
+ const claimedPath = await claimDurableQueueEntry(params.paths);
299
+ if (!claimedPath)
282
300
  return null;
283
- }
284
- const raw = await readJsonDurableQueueEntry(params.paths.jsonPath, {
301
+ const raw = await readJsonDurableQueueEntry(claimedPath, {
285
302
  maxBytes: params.maxBytes,
286
303
  });
287
304
  const result = params.read ? await params.read(raw, params.paths.jsonPath) : { entry: raw };
288
305
  if (result.migrated) {
289
- await writeJsonDurableQueueEntry({
290
- filePath: params.paths.jsonPath,
306
+ await replaceJsonDurableQueueEntry({
307
+ filePath: claimedPath,
291
308
  entry: result.entry,
292
309
  tempPrefix: params.tempPrefix,
293
310
  });
@@ -315,28 +332,41 @@ export async function loadPendingJsonDurableQueueEntries(options) {
315
332
  const now = Date.now();
316
333
  for (const file of files) {
317
334
  if (file.endsWith(".delivered")) {
318
- await unlinkBestEffort(path.join(options.queueDir, file));
335
+ const id = file.slice(0, -".delivered".length);
336
+ await completeDeliveredQueueEntry(resolveJsonDurableQueueEntryPaths(options.queueDir, id));
319
337
  }
320
338
  else if (options.cleanupTmpMaxAgeMs !== undefined && file.endsWith(".tmp")) {
321
339
  await unlinkStaleTmpBestEffort(path.join(options.queueDir, file), now, options.cleanupTmpMaxAgeMs);
322
340
  }
323
341
  }
324
- const entries = [];
342
+ const ids = [];
343
+ const seenIds = new Set();
325
344
  for (const file of files) {
326
- if (!file.endsWith(".json")) {
345
+ const suffix = file.endsWith(".processing")
346
+ ? ".processing"
347
+ : file.endsWith(".json")
348
+ ? ".json"
349
+ : null;
350
+ if (!suffix)
327
351
  continue;
328
- }
329
- const filePath = path.join(options.queueDir, file);
352
+ const id = file.slice(0, -suffix.length);
353
+ if (seenIds.has(id))
354
+ continue;
355
+ seenIds.add(id);
356
+ ids.push(id);
357
+ }
358
+ const entries = [];
359
+ for (const id of ids) {
330
360
  try {
331
- const stat = await fs.promises.lstat(filePath);
332
- if (!stat.isFile()) {
361
+ const paths = resolveJsonDurableQueueEntryPaths(options.queueDir, id);
362
+ const claimedPath = await claimDurableQueueEntry(paths);
363
+ if (!claimedPath)
333
364
  continue;
334
- }
335
- const raw = await readJsonDurableQueueEntry(filePath, { maxBytes: options.maxBytes });
336
- const result = options.read ? await options.read(raw, filePath) : { entry: raw };
365
+ const raw = await readJsonDurableQueueEntry(claimedPath, { maxBytes: options.maxBytes });
366
+ const result = options.read ? await options.read(raw, paths.jsonPath) : { entry: raw };
337
367
  if (result.migrated) {
338
- await writeJsonDurableQueueEntry({
339
- filePath,
368
+ await replaceJsonDurableQueueEntry({
369
+ filePath: claimedPath,
340
370
  entry: result.entry,
341
371
  tempPrefix: options.tempPrefix,
342
372
  });
@@ -354,5 +384,8 @@ export async function moveJsonDurableQueueEntryToFailed(params) {
354
384
  const roots = await queueValidationRoots(params.queueDir, params.failedDir);
355
385
  await assertJsonDurableQueueDir(params.queueDir, roots.queueRoot);
356
386
  await ensureJsonDurableQueueDir(params.failedDir, roots.failedRoot);
357
- await fs.promises.rename(path.join(params.queueDir, `${params.id}.json`), path.join(params.failedDir, `${params.id}.json`));
387
+ await moveDurableQueueEntryToFailed({
388
+ paths: resolveJsonDurableQueueEntryPaths(params.queueDir, params.id),
389
+ failedPath: path.join(params.failedDir, `${params.id}.json`),
390
+ });
358
391
  }
@@ -1 +1 @@
1
- {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}