@openclaw/fs-safe 0.16.0 → 0.17.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 (180) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +8 -1
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +2 -8
  5. package/dist/advanced.d.ts +2 -0
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +2 -0
  8. package/dist/archive-deadline.d.ts.map +1 -1
  9. package/dist/archive-deadline.js +22 -23
  10. package/dist/archive-merge.d.ts +1 -0
  11. package/dist/archive-merge.d.ts.map +1 -1
  12. package/dist/archive-merge.js +4 -4
  13. package/dist/archive-native.d.ts +1 -0
  14. package/dist/archive-native.d.ts.map +1 -1
  15. package/dist/archive-native.js +1 -0
  16. package/dist/archive-options.d.ts +2 -0
  17. package/dist/archive-options.d.ts.map +1 -1
  18. package/dist/archive-parser.wasm +0 -0
  19. package/dist/archive-zip-count.d.ts.map +1 -1
  20. package/dist/archive-zip-count.js +21 -1
  21. package/dist/archive-zip-directory.d.ts.map +1 -1
  22. package/dist/archive-zip-directory.js +23 -1
  23. package/dist/archive-zip-loader.d.ts +2 -0
  24. package/dist/archive-zip-loader.d.ts.map +1 -1
  25. package/dist/archive-zip-loader.js +7 -0
  26. package/dist/archive-zip-names.d.ts.map +1 -1
  27. package/dist/archive-zip-names.js +7 -2
  28. package/dist/archive.d.ts.map +1 -1
  29. package/dist/archive.js +9 -3
  30. package/dist/byte-view.d.ts +3 -0
  31. package/dist/byte-view.d.ts.map +1 -0
  32. package/dist/byte-view.js +13 -0
  33. package/dist/creation-darwin.d.ts +0 -1
  34. package/dist/creation-darwin.d.ts.map +1 -1
  35. package/dist/creation-darwin.js +0 -9
  36. package/dist/directory-durability.d.ts +6 -6
  37. package/dist/directory-durability.d.ts.map +1 -1
  38. package/dist/directory-receipt.d.ts +2 -2
  39. package/dist/directory-receipt.d.ts.map +1 -1
  40. package/dist/directory-receipt.js +15 -19
  41. package/dist/file-cleanup.d.ts +1 -0
  42. package/dist/file-cleanup.d.ts.map +1 -1
  43. package/dist/file-cleanup.js +7 -4
  44. package/dist/file-contents.d.ts +6 -0
  45. package/dist/file-contents.d.ts.map +1 -0
  46. package/dist/file-contents.js +40 -0
  47. package/dist/file-hash.d.ts.map +1 -1
  48. package/dist/file-hash.js +16 -4
  49. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -1
  50. package/dist/file-lock-sync-root-acquire.js +3 -0
  51. package/dist/file-lock-sync-root-held.d.ts +1 -2
  52. package/dist/file-lock-sync-root-held.d.ts.map +1 -1
  53. package/dist/file-lock-sync-root-held.js +7 -5
  54. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -1
  55. package/dist/file-lock-sync-stale-admission.js +3 -0
  56. package/dist/file-lock-sync.d.ts.map +1 -1
  57. package/dist/file-lock-sync.js +8 -11
  58. package/dist/file-store.js +3 -3
  59. package/dist/guarded-mkdir.d.ts.map +1 -1
  60. package/dist/guarded-mkdir.js +6 -27
  61. package/dist/install-path.d.ts.map +1 -1
  62. package/dist/install-path.js +2 -5
  63. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  64. package/dist/json-durable-queue-ownership.js +2 -6
  65. package/dist/json-durable-queue-paths.d.ts.map +1 -1
  66. package/dist/json-durable-queue-paths.js +2 -24
  67. package/dist/json-durable-queue.d.ts.map +1 -1
  68. package/dist/json-durable-queue.js +10 -9
  69. package/dist/json.d.ts.map +1 -1
  70. package/dist/json.js +32 -75
  71. package/dist/local-roots.d.ts.map +1 -1
  72. package/dist/local-roots.js +19 -21
  73. package/dist/move-path-cleanup.d.ts +5 -19
  74. package/dist/move-path-cleanup.d.ts.map +1 -1
  75. package/dist/move-path-cleanup.js +57 -21
  76. package/dist/move-path.d.ts.map +1 -1
  77. package/dist/move-path.js +62 -39
  78. package/dist/native-staged-file.d.ts +2 -1
  79. package/dist/native-staged-file.d.ts.map +1 -1
  80. package/dist/native-staged-file.js +8 -5
  81. package/dist/native.js +2 -2
  82. package/dist/opened-realpath.d.ts.map +1 -1
  83. package/dist/opened-realpath.js +11 -2
  84. package/dist/path.d.ts.map +1 -1
  85. package/dist/path.js +2 -1
  86. package/dist/permissions.d.ts.map +1 -1
  87. package/dist/permissions.js +3 -17
  88. package/dist/pinned-write-input.d.ts.map +1 -1
  89. package/dist/pinned-write-input.js +11 -1
  90. package/dist/pinned-write-mode.d.ts +3 -3
  91. package/dist/pinned-write-mode.d.ts.map +1 -1
  92. package/dist/pinned-write-mode.js +15 -8
  93. package/dist/pinned-write-staged.d.ts.map +1 -1
  94. package/dist/pinned-write-staged.js +10 -11
  95. package/dist/pinned-write.d.ts.map +1 -1
  96. package/dist/pinned-write.js +17 -13
  97. package/dist/publish-file.d.ts +2 -2
  98. package/dist/publish-file.d.ts.map +1 -1
  99. package/dist/publish-file.js +56 -96
  100. package/dist/regular-file.d.ts.map +1 -1
  101. package/dist/regular-file.js +35 -44
  102. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  103. package/dist/replace-file-copy-fallback.js +28 -26
  104. package/dist/replace-file-copy-source.d.ts.map +1 -1
  105. package/dist/replace-file-copy-source.js +13 -22
  106. package/dist/replace-file-descriptor.d.ts.map +1 -1
  107. package/dist/replace-file-descriptor.js +10 -16
  108. package/dist/replace-file-temp-owner.js +6 -6
  109. package/dist/replace-file.d.ts.map +1 -1
  110. package/dist/replace-file.js +9 -13
  111. package/dist/root-directory-list.d.ts.map +1 -1
  112. package/dist/root-directory-list.js +20 -3
  113. package/dist/root-file-final-admission.d.ts +1 -1
  114. package/dist/root-file-final-admission.d.ts.map +1 -1
  115. package/dist/root-file-final-admission.js +5 -2
  116. package/dist/root-file.d.ts.map +1 -1
  117. package/dist/root-file.js +3 -2
  118. package/dist/root-impl.d.ts.map +1 -1
  119. package/dist/root-impl.js +50 -20
  120. package/dist/root-move-noreplace.d.ts +2 -0
  121. package/dist/root-move-noreplace.d.ts.map +1 -1
  122. package/dist/root-move-noreplace.js +2 -2
  123. package/dist/root-read-admission.d.ts.map +1 -1
  124. package/dist/root-read-admission.js +7 -2
  125. package/dist/root-remove.d.ts.map +1 -1
  126. package/dist/root-remove.js +15 -1
  127. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  128. package/dist/sidecar-lock-acquire.js +4 -6
  129. package/dist/sidecar-lock-handle.d.ts +3 -0
  130. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  131. package/dist/sidecar-lock-handle.js +6 -0
  132. package/dist/sidecar-lock-reclaim.d.ts +1 -1
  133. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  134. package/dist/sidecar-lock-reclaim.js +11 -8
  135. package/dist/sidecar-lock.d.ts.map +1 -1
  136. package/dist/sidecar-lock.js +3 -5
  137. package/dist/staged-directory.d.ts +2 -2
  138. package/dist/staged-directory.d.ts.map +1 -1
  139. package/dist/strict-file-identity.d.ts +1 -1
  140. package/dist/strict-file-identity.d.ts.map +1 -1
  141. package/dist/strict-file-identity.js +9 -9
  142. package/dist/symlink-parents.d.ts.map +1 -1
  143. package/dist/symlink-parents.js +2 -27
  144. package/dist/temp-workspace-owner.js +4 -4
  145. package/dist/unicode-path.d.ts.map +1 -1
  146. package/dist/unicode-path.js +3 -0
  147. package/dist/walk.d.ts.map +1 -1
  148. package/dist/walk.js +4 -2
  149. package/dist/write-file-handle.d.ts +7 -0
  150. package/dist/write-file-handle.d.ts.map +1 -1
  151. package/dist/write-file-handle.js +23 -0
  152. package/dist/write-open-flags.d.ts.map +1 -1
  153. package/dist/write-open-flags.js +1 -8
  154. package/dist/write-queue.d.ts.map +1 -1
  155. package/dist/write-queue.js +1 -4
  156. package/docs/advanced.md +68 -1
  157. package/docs/archive.md +41 -2
  158. package/docs/atomic.md +29 -5
  159. package/docs/contributing.md +4 -0
  160. package/docs/creation.md +8 -4
  161. package/docs/durability.md +35 -0
  162. package/docs/file-contents.md +68 -0
  163. package/docs/json.md +5 -4
  164. package/docs/local-roots.md +2 -0
  165. package/docs/mutation-policy-proof.md +5 -3
  166. package/docs/native.md +9 -8
  167. package/docs/path.md +4 -4
  168. package/docs/public-api.md +5 -0
  169. package/docs/quickstart.md +1 -1
  170. package/docs/reading.md +2 -2
  171. package/docs/regular-file.md +3 -0
  172. package/docs/root.md +9 -0
  173. package/docs/sidecar-lock.md +9 -1
  174. package/docs/staged-file.md +4 -3
  175. package/docs/store.md +3 -1
  176. package/docs/temp.md +4 -1
  177. package/docs/types.md +18 -2
  178. package/docs/walk.md +7 -0
  179. package/docs/writing.md +9 -2
  180. package/package.json +8 -8
@@ -158,14 +158,10 @@ export async function completeDeliveredQueueEntry(paths) {
158
158
  return await withQueueEntryLock(validatedPaths, async () => {
159
159
  if (!(await regularQueueFileIdentity(validatedPaths.deliveredPath)))
160
160
  return false;
161
- if (await regularQueueFileIdentity(validatedPaths.processingPath)) {
162
- await fs.unlink(validatedPaths.deliveredPath);
163
- await syncDirectory(path.dirname(validatedPaths.deliveredPath));
164
- return false;
165
- }
161
+ const processing = await regularQueueFileIdentity(validatedPaths.processingPath);
166
162
  await fs.unlink(validatedPaths.deliveredPath);
167
163
  await syncDirectory(path.dirname(validatedPaths.deliveredPath));
168
- return true;
164
+ return !processing;
169
165
  });
170
166
  }
171
167
  export async function acknowledgeDurableQueueEntry(paths) {
@@ -1 +1 @@
1
- {"version":3,"file":"json-durable-queue-paths.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-paths.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhE;AAqBD,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,SAAS,EAAE,mBAAmB,CAAC;IAAC,UAAU,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAU9E;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAKpE"}
1
+ {"version":3,"file":"json-durable-queue-paths.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-paths.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhE;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,SAAS,EAAE,mBAAmB,CAAC;IAAC,UAAU,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAM9E;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAKpE"}
@@ -5,33 +5,11 @@ export function resolveQueueFilesystemPath(value) {
5
5
  assertNoWindowsPathAlias(resolved);
6
6
  return resolved;
7
7
  }
8
- function commonPathAncestor(paths) {
9
- const resolved = paths.map(resolveQueueFilesystemPath);
10
- const root = path.parse(resolved[0] ?? process.cwd()).root;
11
- const parts = resolved.map((entry) => path.relative(root, entry).split(path.sep));
12
- const common = [];
13
- for (let index = 0; parts.every((part) => index < part.length); index++) {
14
- const segment = parts[0]?.[index];
15
- if (!segment || !parts.every((part) => part[index] === segment))
16
- break;
17
- common.push(segment);
18
- }
19
- return path.join(root, ...common);
20
- }
21
- function samePathRoot(paths) {
22
- const roots = paths.map((entry) => path.parse(resolveQueueFilesystemPath(entry)).root);
23
- const first = process.platform === "win32" ? roots[0]?.toLowerCase() : roots[0];
24
- return roots.every((root) => (process.platform === "win32" ? root.toLowerCase() : root) === first);
25
- }
26
8
  export async function queueValidationRoots(queueDir, failedDir) {
27
- if (samePathRoot([queueDir, failedDir])) {
28
- const common = commonPathAncestor([queueDir, failedDir]);
29
- const root = queueValidationRoot(common);
30
- return { failedRoot: root, queueRoot: root };
31
- }
9
+ const queueRoot = queueValidationRoot(queueDir);
32
10
  return {
11
+ queueRoot,
33
12
  failedRoot: queueValidationRoot(failedDir),
34
- queueRoot: queueValidationRoot(queueDir),
35
13
  };
36
14
  }
37
15
  export function queueValidationRoot(dir) {
@@ -1 +1 @@
1
- {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAE,0CAA0C,EAAE,MAAM,8BAA8B,CAAC;AAE1F,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;AAMF,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE;AAED,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWpF;AAmBD,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,0BAA0B,CAQ5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AAwID,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,CAEhB;AAmBD,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CAEZ;AAgBD,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,CAwBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CA2Ed;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,CAehB"}
1
+ {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAE,0CAA0C,EAAE,MAAM,8BAA8B,CAAC;AAE1F,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;AAMF,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE;AAED,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWpF;AAmBD,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,0BAA0B,CAQ5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AAwID,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,CAEhB;AAmBD,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CAEZ;AAgBD,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,CAyBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CA2Ed;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,CAehB"}
@@ -213,15 +213,16 @@ export async function ackJsonDurableQueueEntry(paths) {
213
213
  await acknowledgeDurableQueueEntry(paths);
214
214
  }
215
215
  export async function loadJsonDurableQueueEntry(params) {
216
+ const pathsInput = params.paths;
217
+ const paths = validateDurableQueueEntryPaths(pathsInput);
218
+ const claimedPath = await claimDurableQueueEntry(paths);
219
+ if (!claimedPath)
220
+ return null;
221
+ const readOptions = { maxBytes: params.maxBytes };
222
+ let processingStarted = false;
216
223
  try {
217
- const pathsInput = params.paths;
218
- const paths = validateDurableQueueEntryPaths(pathsInput);
219
- const claimedPath = await claimDurableQueueEntry(paths);
220
- if (!claimedPath)
221
- return null;
222
- return await withJsonDurableQueueEntry(claimedPath, {
223
- maxBytes: params.maxBytes,
224
- }, async (raw, identity, releaseReadPin) => {
224
+ return await withJsonDurableQueueEntry(claimedPath, readOptions, async (raw, identity, releaseReadPin) => {
225
+ processingStarted = true;
225
226
  const read = params.read;
226
227
  const result = read ? await Reflect.apply(read, params, [raw, paths.jsonPath]) : { entry: raw };
227
228
  if (result.migrated) {
@@ -233,7 +234,7 @@ export async function loadJsonDurableQueueEntry(params) {
233
234
  });
234
235
  }
235
236
  catch (error) {
236
- if (getErrorCode(error) === "ENOENT") {
237
+ if (!processingStarted && getErrorCode(error) === "ENOENT") {
237
238
  return null;
238
239
  }
239
240
  throw error;
@@ -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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"}
package/dist/json.js CHANGED
@@ -3,12 +3,12 @@ import fsSync from "node:fs";
3
3
  import path from "node:path";
4
4
  import { readFileDescriptorBoundedSync } from "./bounded-read.js";
5
5
  import { FsSafeError } from "./errors.js";
6
- import { sameFileIdentityForCleanup } from "./file-identity.js";
7
6
  import { stringifyJsonDocument } from "./json-stringify.js";
8
7
  import { readRegularFile, readRegularFileSync, statRegularFile } from "./regular-file.js";
9
8
  import { openRootFileSync } from "./root-file.js";
10
- import { resolveReadOpenFlags } from "./read-open-flags.js";
11
9
  import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
10
+ import { writeTempFileSync } from "./replace-file-descriptor.js";
11
+ import { SyncAtomicTempOwner } from "./replace-file-temp-owner.js";
12
12
  import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
13
13
  import { writeTextAtomic } from "./text-atomic.js";
14
14
  import { sleep } from "./timing.js";
@@ -56,42 +56,12 @@ const JSON_DIR_MODE = 0o700;
56
56
  function getErrorCode(err) {
57
57
  return err instanceof Error ? err.code : undefined;
58
58
  }
59
- function trySetSecureMode(pathname, expectedIdentity) {
60
- let fd;
59
+ function trySetSecureMode(fd) {
61
60
  try {
62
- const before = fsSync.lstatSync(pathname, { bigint: true });
63
- if (before.isSymbolicLink() ||
64
- !before.isFile() ||
65
- before.nlink !== 1n ||
66
- !sameFileIdentityForCleanup(before, expectedIdentity)) {
67
- return;
68
- }
69
- fd = fsSync.openSync(pathname, resolveReadOpenFlags());
70
- const opened = fsSync.fstatSync(fd, { bigint: true });
71
- const current = fsSync.lstatSync(pathname, { bigint: true });
72
- if (!opened.isFile() ||
73
- opened.nlink !== 1n ||
74
- current.isSymbolicLink() ||
75
- !current.isFile() ||
76
- current.nlink !== 1n ||
77
- !sameFileIdentityForCleanup(opened, expectedIdentity) ||
78
- !sameFileIdentityForCleanup(current, opened)) {
79
- return;
80
- }
81
61
  fsSync.fchmodSync(fd, JSON_FILE_MODE);
82
62
  }
83
63
  catch {
84
- // Best-effort mode application never falls back to a mutable pathname.
85
- }
86
- finally {
87
- if (fd !== undefined) {
88
- try {
89
- fsSync.closeSync(fd);
90
- }
91
- catch {
92
- // best-effort cleanup
93
- }
94
- }
64
+ // Best-effort mode application stays on the retained staging descriptor.
95
65
  }
96
66
  }
97
67
  function trySyncDirectory(pathname) {
@@ -114,48 +84,24 @@ function trySyncDirectory(pathname) {
114
84
  }
115
85
  }
116
86
  }
117
- function renameJsonFileWithFallback(tmpPath, pathname) {
87
+ function renameJsonFileWithFallback(owner, pathname) {
88
+ owner.assertCurrent(fsSync);
118
89
  try {
119
- fsSync.renameSync(tmpPath, pathname);
90
+ fsSync.renameSync(owner.pathname, pathname);
120
91
  return;
121
92
  }
122
93
  catch (error) {
123
- const code = error.code;
94
+ const code = error?.code;
124
95
  if (code === "EPERM" || code === "EEXIST") {
125
- const existing = (() => {
126
- try {
127
- return fsSync.lstatSync(pathname);
128
- }
129
- catch (lstatError) {
130
- if (lstatError.code === "ENOENT") {
131
- return null;
132
- }
133
- throw lstatError;
134
- }
135
- })();
136
- if (existing?.isSymbolicLink()) {
137
- fsSync.rmSync(pathname, { force: true });
138
- fsSync.renameSync(tmpPath, pathname);
139
- return;
140
- }
96
+ owner.assertCurrent(fsSync);
141
97
  fsSync.rmSync(pathname, { force: true });
142
- fsSync.renameSync(tmpPath, pathname);
98
+ owner.assertCurrent(fsSync);
99
+ fsSync.renameSync(owner.pathname, pathname);
143
100
  return;
144
101
  }
145
102
  throw error;
146
103
  }
147
104
  }
148
- function writeTempJsonFile(pathname, payload) {
149
- const fd = fsSync.openSync(pathname, "wx", JSON_FILE_MODE);
150
- try {
151
- fsSync.writeFileSync(fd, payload, "utf8");
152
- fsSync.fsyncSync(fd);
153
- return fsSync.fstatSync(fd, { bigint: true });
154
- }
155
- finally {
156
- fsSync.closeSync(fd);
157
- }
158
- }
159
105
  export function tryReadJsonSync(pathname, options = {}) {
160
106
  try {
161
107
  const raw = readRegularFileSync({
@@ -174,20 +120,31 @@ export function writeJsonSync(pathname, data) {
174
120
  const tmpPath = path.format({ ...path.parse(filePath), base: `.fs-safe-${randomUUID()}.tmp` });
175
121
  const payload = `${stringifyJsonDocument(data, null, 2)}\n`;
176
122
  fsSync.mkdirSync(recursiveMkdirPath(path.dirname(filePath)), { recursive: true, mode: JSON_DIR_MODE });
123
+ const owner = new SyncAtomicTempOwner(tmpPath);
124
+ let originalFailure;
177
125
  try {
178
- const tempIdentity = writeTempJsonFile(tmpPath, payload);
179
- trySetSecureMode(tmpPath, tempIdentity);
180
- renameJsonFileWithFallback(tmpPath, filePath);
181
- trySetSecureMode(filePath, tempIdentity);
126
+ owner.start();
127
+ const temp = writeTempFileSync({
128
+ fsModule: fsSync, tempPath: tmpPath, content: payload, mode: JSON_FILE_MODE,
129
+ sync: false, onIdentity: owner.onIdentity,
130
+ });
131
+ owner.adopt(temp);
132
+ owner.assertCurrent(fsSync);
133
+ trySetSecureMode(temp.fd);
134
+ fsSync.fsyncSync(temp.fd);
135
+ renameJsonFileWithFallback(owner, filePath);
136
+ owner.markRenamed();
137
+ owner.assertPublished(fsSync, filePath);
138
+ trySetSecureMode(temp.fd);
182
139
  trySyncDirectory(filePath);
140
+ owner.assertPublished(fsSync, filePath);
141
+ }
142
+ catch (error) {
143
+ originalFailure = { error };
144
+ throw error;
183
145
  }
184
146
  finally {
185
- try {
186
- fsSync.rmSync(tmpPath, { force: true });
187
- }
188
- catch {
189
- // best-effort cleanup when rename does not happen
190
- }
147
+ owner.finish({ fsModule: fsSync, originalFailure, throwOnCleanupError: false });
191
148
  }
192
149
  }
193
150
  export class JsonFileReadError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAQA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAQ3F,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;AAiEF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA2C7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAuCtC"}
1
+ {"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAQA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAQ3F,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;AA+EF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CAoC7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA6BtC"}
@@ -71,7 +71,7 @@ function resolveRootRealSync(rootDir) {
71
71
  assertNoWindowsPathAlias(realPath, "filesystem", "local root uses a Windows filesystem namespace alias");
72
72
  return realPath;
73
73
  }
74
- export function resolveLocalPathFromRootsSync(options) {
74
+ function prepareLocalRootsInput(options) {
75
75
  const label = options.label ?? "local roots";
76
76
  const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
77
77
  let requestedPath;
@@ -83,6 +83,13 @@ export function resolveLocalPathFromRootsSync(options) {
83
83
  return null;
84
84
  throw error;
85
85
  }
86
+ return { label, rootDirs, requestedPath };
87
+ }
88
+ export function resolveLocalPathFromRootsSync(options) {
89
+ const input = prepareLocalRootsInput(options);
90
+ if (!input)
91
+ return null;
92
+ const { label, rootDirs, requestedPath } = input;
86
93
  for (const rootDir of rootDirs) {
87
94
  const rootReal = resolveRootRealSync(rootDir);
88
95
  if (!rootReal) {
@@ -122,17 +129,17 @@ export function resolveLocalPathFromRootsSync(options) {
122
129
  }
123
130
  export async function readLocalFileFromRoots(options) {
124
131
  const maxBytes = normalizeMaxBytes(options.maxBytes);
125
- const label = options.label ?? "local roots";
126
- const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
127
- let requestedPath;
128
- try {
129
- requestedPath = resolveLocalPathInput(options.filePath, "file path");
130
- }
131
- catch (error) {
132
- if (isWindowsPathAliasError(error))
133
- return null;
134
- throw error;
135
- }
132
+ const input = prepareLocalRootsInput(options);
133
+ if (!input || input.rootDirs.length === 0)
134
+ return null;
135
+ const { rootDirs, requestedPath } = input;
136
+ const readOptions = {
137
+ hardlinks: options.hardlinks,
138
+ symlinks: options.symlinks,
139
+ };
140
+ // Omission keeps Root's default cap; one policy snapshot serves every root.
141
+ if (maxBytes !== undefined)
142
+ readOptions.maxBytes = maxBytes;
136
143
  for (const rootDir of rootDirs) {
137
144
  let scopedRoot;
138
145
  try {
@@ -141,15 +148,6 @@ export async function readLocalFileFromRoots(options) {
141
148
  catch {
142
149
  continue;
143
150
  }
144
- const readOptions = {
145
- hardlinks: options.hardlinks,
146
- symlinks: options.symlinks,
147
- };
148
- // Leave maxBytes absent when the caller omits it so Root's own default
149
- // cap remains in force instead of being overwritten by undefined.
150
- if (maxBytes !== undefined) {
151
- readOptions.maxBytes = maxBytes;
152
- }
153
151
  try {
154
152
  const result = await scopedRoot.readAbsolute(requestedPath, readOptions);
155
153
  return { ...result, root: scopedRoot.rootReal };
@@ -1,13 +1,5 @@
1
1
  import fsSync from "node:fs";
2
- export type EntryIdentity = {
3
- ctimeMs: number;
4
- dev: number;
5
- ino: number;
6
- mode: number;
7
- mtimeMs: number;
8
- nlink: number;
9
- size: number;
10
- };
2
+ export type EntryIdentity = Readonly<Pick<fsSync.BigIntStats, "ctimeNs" | "dev" | "ino" | "mode" | "mtimeNs" | "nlink" | "size">>;
11
3
  export type CopiedEntryManifest = (EntryIdentity & {
12
4
  children: Array<{
13
5
  name: string;
@@ -27,19 +19,13 @@ type CleanupAliasGroup = {
27
19
  export type CleanupCopiedEntryState = {
28
20
  aliasGroups: Map<string, CleanupAliasGroup>;
29
21
  };
30
- export declare function entryIdentity(stat: {
31
- ctimeMs: number;
32
- dev: number;
33
- ino: number;
34
- mode: number;
35
- mtimeMs: number;
36
- nlink: number;
37
- size: number;
38
- }): EntryIdentity;
22
+ export declare function entryIdentity(stat: EntryIdentity): EntryIdentity;
39
23
  export declare function sameIdentity(a: EntryIdentity, b: EntryIdentity): boolean;
40
24
  export declare function sourceChangedError(sourcePath: string): Error;
25
+ export declare function inspectSourceEntry(sourcePath: string, observe: () => fsSync.BigIntStats): fsSync.BigIntStats;
26
+ export declare function inspectSourceDirectory(sourcePath: string, expected?: Pick<fsSync.BigIntStats, "dev" | "ino">): fsSync.BigIntStats;
41
27
  export declare function assertSourceStillMatches(sourcePath: string, identity: EntryIdentity): Promise<void>;
42
28
  export declare function createCleanupCopiedEntryState(sourcePath: string, manifest: CopiedEntryManifest): CleanupCopiedEntryState;
43
- export declare function cleanupCopiedEntry(sourcePath: string, manifest: CopiedEntryManifest, state: CleanupCopiedEntryState, assertBeforeMutation: () => void): Promise<CleanupCopiedEntryResult>;
29
+ export declare function cleanupCopiedEntry(sourcePath: string, manifest: CopiedEntryManifest, state: CleanupCopiedEntryState, assertBeforeMutation: (() => void) | undefined): Promise<CleanupCopiedEntryResult>;
44
30
  export {};
45
31
  //# sourceMappingURL=move-path-cleanup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"move-path-cleanup.d.ts","sourceRoot":"","sources":["../src/move-path-cleanup.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAM7B,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B,CAAC,aAAa,GAAG;IACf,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,mBAAmB,CAAA;KAAE,CAAC,CAAC;IACjE,iBAAiB,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;IACrE,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC,GACF,CAAC,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEvC,KAAK,wBAAwB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpD,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE,aAAa,CAAC;IACxB,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CAC7C,CAAC;AAEF,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,aAAa,CAUhB;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,OAAO,CAUxE;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,CAI5D;AAED,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CAIf;AA0BD,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,mBAAmB,GAC5B,uBAAuB,CAuBzB;AAsDD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,uBAAuB,EAC9B,oBAAoB,EAAE,MAAM,IAAI,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CAkFnC"}
1
+ {"version":3,"file":"move-path-cleanup.d.ts","sourceRoot":"","sources":["../src/move-path-cleanup.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAO7B,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAC1D,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAClE,CAAC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAC3B,CAAC,aAAa,GAAG;IACf,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,mBAAmB,CAAA;KAAE,CAAC,CAAC;IACjE,iBAAiB,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;IACrE,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC,GACF,CAAC,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEvC,KAAK,wBAAwB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpD,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE,aAAa,CAAC;IACxB,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CAC7C,CAAC;AAEF,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAUhE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,OAAO,CAUxE;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,CAI5D;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAE5G;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GACjD,MAAM,CAAC,WAAW,CAWpB;AAED,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CAIf;AA0BD,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,mBAAmB,GAC5B,uBAAuB,CAuBzB;AAgED,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,uBAAuB,EAC9B,oBAAoB,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,GAC7C,OAAO,CAAC,wBAAwB,CAAC,CA0FnC"}
@@ -2,14 +2,15 @@ import fsSync from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { FsSafeError } from "./errors.js";
5
+ import { inspectDirectoryIdentitySync } from "./directory-guard.js";
5
6
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
6
7
  export function entryIdentity(stat) {
7
8
  return {
8
- ctimeMs: stat.ctimeMs,
9
+ ctimeNs: stat.ctimeNs,
9
10
  dev: stat.dev,
10
11
  ino: stat.ino,
11
12
  mode: stat.mode,
12
- mtimeMs: stat.mtimeMs,
13
+ mtimeNs: stat.mtimeNs,
13
14
  nlink: stat.nlink,
14
15
  size: stat.size,
15
16
  };
@@ -20,16 +21,32 @@ export function sameIdentity(a, b) {
20
21
  a.mode === b.mode &&
21
22
  a.nlink === b.nlink &&
22
23
  a.size === b.size &&
23
- a.mtimeMs === b.mtimeMs &&
24
- a.ctimeMs === b.ctimeMs);
24
+ a.mtimeNs === b.mtimeNs &&
25
+ a.ctimeNs === b.ctimeNs);
25
26
  }
26
27
  export function sourceChangedError(sourcePath) {
27
28
  return Object.assign(new Error(`Source changed during move fallback: ${sourcePath}`), {
28
29
  code: "ESTALE",
29
30
  });
30
31
  }
32
+ export function inspectSourceEntry(sourcePath, observe) {
33
+ return inspectFileIdentitySync(observe, undefined, process.platform, () => sourceChangedError(sourcePath));
34
+ }
35
+ export function inspectSourceDirectory(sourcePath, expected) {
36
+ try {
37
+ return inspectDirectoryIdentitySync(sourcePath, expected);
38
+ }
39
+ catch (error) {
40
+ const code = error?.code;
41
+ if (code === "ENOENT" || code === "ENOTDIR" ||
42
+ (error instanceof FsSafeError && (error.code === "path-mismatch" || error.code === "not-file"))) {
43
+ throw sourceChangedError(sourcePath);
44
+ }
45
+ throw error;
46
+ }
47
+ }
31
48
  export async function assertSourceStillMatches(sourcePath, identity) {
32
- if (!sameIdentity(identity, entryIdentity(fsSync.lstatSync(sourcePath)))) {
49
+ if (!sameIdentity(identity, inspectSourceEntry(sourcePath, () => fsSync.lstatSync(sourcePath, { bigint: true })))) {
33
50
  throw sourceChangedError(sourcePath);
34
51
  }
35
52
  }
@@ -43,7 +60,7 @@ function collectAliasCandidates(sourcePath, manifest, candidates) {
43
60
  }
44
61
  return;
45
62
  }
46
- if (manifest.nlink <= 1) {
63
+ if (manifest.nlink <= 1n) {
47
64
  return;
48
65
  }
49
66
  const key = identityKey(manifest);
@@ -76,15 +93,26 @@ function sameOwnedUnlinkTransition(before, after) {
76
93
  before.ino === after.ino &&
77
94
  before.mode === after.mode &&
78
95
  before.size === after.size &&
79
- before.mtimeMs === after.mtimeMs &&
80
- before.nlink > 0 &&
81
- after.nlink === before.nlink - 1 &&
82
- after.ctimeMs >= before.ctimeMs);
96
+ before.mtimeNs === after.mtimeNs &&
97
+ before.nlink > 0n &&
98
+ after.nlink === before.nlink - 1n &&
99
+ after.ctimeNs >= before.ctimeNs);
83
100
  }
84
101
  function poisonAliasGroup(group) {
85
102
  group.stale = true;
86
103
  return "stale";
87
104
  }
105
+ function inspectCleanupLeaf(sourcePath, expected) {
106
+ let mismatch;
107
+ try {
108
+ return inspectFileIdentitySync(() => fsSync.lstatSync(sourcePath, { bigint: true }), expected, process.platform, () => mismatch = sourceChangedError(sourcePath));
109
+ }
110
+ catch (error) {
111
+ if (mismatch !== undefined && error === mismatch)
112
+ return undefined;
113
+ throw error;
114
+ }
115
+ }
88
116
  async function observeOwnedAliasUnlink(sourcePath, group) {
89
117
  group.remainingPaths.delete(sourcePath);
90
118
  const remainingPath = group.remainingPaths.values().next().value;
@@ -93,7 +121,7 @@ async function observeOwnedAliasUnlink(sourcePath, group) {
93
121
  }
94
122
  let observed;
95
123
  try {
96
- observed = fsSync.lstatSync(remainingPath);
124
+ observed = inspectCleanupLeaf(remainingPath, group.expected);
97
125
  }
98
126
  catch (error) {
99
127
  if (error?.code === "ENOENT") {
@@ -101,11 +129,10 @@ async function observeOwnedAliasUnlink(sourcePath, group) {
101
129
  }
102
130
  throw error;
103
131
  }
104
- const observedIdentity = entryIdentity(observed);
105
- if (!sameOwnedUnlinkTransition(group.expected, observedIdentity)) {
132
+ if (!observed || !sameOwnedUnlinkTransition(group.expected, observed)) {
106
133
  return poisonAliasGroup(group);
107
134
  }
108
- group.expected = observedIdentity;
135
+ group.expected = entryIdentity(observed);
109
136
  return "removed";
110
137
  }
111
138
  function mergeCleanupResults(a, b) {
@@ -130,12 +157,16 @@ export async function cleanupCopiedEntry(sourcePath, manifest, state, assertBefo
130
157
  // A same-inode directory can gain unrelated children after commit. Still
131
158
  // clean manifest children so the fallback does not duplicate copied files.
132
159
  let result = "removed";
160
+ const assertBeforeChildMutation = assertBeforeMutation ? () => {
161
+ assertBeforeMutation();
162
+ inspectSourceDirectory(sourcePath, manifest.directoryIdentity);
163
+ } : undefined;
133
164
  for (const child of manifest.children) {
134
- result = mergeCleanupResults(result, await cleanupCopiedEntry(path.join(sourcePath, child.name), child.manifest, state, assertBeforeMutation));
165
+ result = mergeCleanupResults(result, await cleanupCopiedEntry(path.join(sourcePath, child.name), child.manifest, state, assertBeforeChildMutation));
135
166
  }
136
167
  // Child cleanup and the caller's authority check can replace the directory.
137
168
  // Keep the final exact observation after both, immediately before removal.
138
- assertBeforeMutation();
169
+ assertBeforeMutation?.();
139
170
  try {
140
171
  currentStat = inspectFileIdentitySync(() => fsSync.lstatSync(sourcePath, { bigint: true }), manifest.directoryIdentity);
141
172
  }
@@ -166,9 +197,10 @@ export async function cleanupCopiedEntry(sourcePath, manifest, state, assertBefo
166
197
  if (aliasGroup?.stale) {
167
198
  return "stale";
168
199
  }
200
+ const expected = aliasGroup?.expected ?? manifest;
169
201
  let currentStat;
170
202
  try {
171
- currentStat = fsSync.lstatSync(sourcePath);
203
+ currentStat = inspectCleanupLeaf(sourcePath, expected);
172
204
  }
173
205
  catch (error) {
174
206
  if (error?.code === "ENOENT") {
@@ -176,11 +208,15 @@ export async function cleanupCopiedEntry(sourcePath, manifest, state, assertBefo
176
208
  }
177
209
  throw error;
178
210
  }
179
- const expected = aliasGroup?.expected ?? manifest;
180
- if (!sameIdentity(expected, entryIdentity(currentStat))) {
181
- return aliasGroup ? poisonAliasGroup(aliasGroup) : "stale";
211
+ for (let observation = 0;; observation++) {
212
+ if (!currentStat || !sameIdentity(expected, currentStat)) {
213
+ return aliasGroup ? poisonAliasGroup(aliasGroup) : "stale";
214
+ }
215
+ if (observation || !assertBeforeMutation)
216
+ break;
217
+ assertBeforeMutation();
218
+ currentStat = inspectCleanupLeaf(sourcePath, expected);
182
219
  }
183
- assertBeforeMutation();
184
220
  await fs.unlink(sourcePath);
185
221
  return aliasGroup
186
222
  ? await observeOwnedAliasUnlink(sourcePath, aliasGroup)
@@ -1 +1 @@
1
- {"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,qFAAqF;IACrF,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,KAAK,sBAAsB,GAAG,cAAc,GAAG,uBAAuB,CAAC;AAEvE,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,sBAAsB,GAAG,SAAS,CASpC;AAoSD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAoHf"}
1
+ {"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AA2BA,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,qFAAqF;IACrF,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,KAAK,sBAAsB,GAAG,cAAc,GAAG,uBAAuB,CAAC;AAGvE,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,sBAAsB,GAAG,SAAS,CASpC;AA4RD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CA2If"}