@openclaw/fs-safe 0.8.2 → 0.8.3

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 (159) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +1 -1
  3. package/dist/advanced.d.ts +2 -2
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +2 -2
  6. package/dist/archive-kind.d.ts +1 -0
  7. package/dist/archive-kind.d.ts.map +1 -1
  8. package/dist/archive-kind.js +6 -0
  9. package/dist/archive-limits.d.ts +0 -1
  10. package/dist/archive-limits.d.ts.map +1 -1
  11. package/dist/archive-limits.js +0 -3
  12. package/dist/archive-native.d.ts.map +1 -1
  13. package/dist/archive-native.js +5 -8
  14. package/dist/archive-parser-errors.d.ts +2 -0
  15. package/dist/archive-parser-errors.d.ts.map +1 -0
  16. package/dist/archive-parser-errors.js +12 -0
  17. package/dist/archive-read.d.ts.map +1 -1
  18. package/dist/archive-read.js +7 -13
  19. package/dist/archive-tar-wasm.d.ts +1 -1
  20. package/dist/archive-tar-wasm.d.ts.map +1 -1
  21. package/dist/archive-tar-wasm.js +5 -8
  22. package/dist/archive.d.ts.map +1 -1
  23. package/dist/archive.js +43 -6
  24. package/dist/atomic.d.ts +1 -1
  25. package/dist/atomic.d.ts.map +1 -1
  26. package/dist/atomic.js +1 -1
  27. package/dist/bounded-read-stream.d.ts +1 -5
  28. package/dist/bounded-read-stream.d.ts.map +1 -1
  29. package/dist/bounded-read-stream.js +5 -13
  30. package/dist/byte-budget.d.ts +0 -1
  31. package/dist/byte-budget.d.ts.map +1 -1
  32. package/dist/byte-budget.js +1 -1
  33. package/dist/directory-mode-owner.d.ts.map +1 -1
  34. package/dist/directory-mode-owner.js +2 -0
  35. package/dist/error-detail.d.ts +1 -0
  36. package/dist/error-detail.d.ts.map +1 -1
  37. package/dist/error-detail.js +6 -0
  38. package/dist/file-store-boundary.d.ts +1 -1
  39. package/dist/file-store-boundary.d.ts.map +1 -1
  40. package/dist/file-store-boundary.js +2 -13
  41. package/dist/file-store.js +1 -1
  42. package/dist/file-sync.d.ts +5 -0
  43. package/dist/file-sync.d.ts.map +1 -0
  44. package/dist/file-sync.js +19 -0
  45. package/dist/guarded-mutation.d.ts +3 -0
  46. package/dist/guarded-mutation.d.ts.map +1 -1
  47. package/dist/guarded-mutation.js +5 -1
  48. package/dist/home-dir.d.ts +0 -10
  49. package/dist/home-dir.d.ts.map +1 -1
  50. package/dist/home-dir.js +0 -28
  51. package/dist/json-durable-queue-ownership.d.ts +1 -0
  52. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  53. package/dist/json-durable-queue-ownership.js +139 -18
  54. package/dist/json-durable-queue.d.ts.map +1 -1
  55. package/dist/json-durable-queue.js +5 -10
  56. package/dist/json-store.d.ts.map +1 -1
  57. package/dist/json-store.js +1 -1
  58. package/dist/local-roots.d.ts.map +1 -1
  59. package/dist/local-roots.js +2 -3
  60. package/dist/move-path-cleanup.d.ts +1 -1
  61. package/dist/move-path-cleanup.d.ts.map +1 -1
  62. package/dist/move-path-cleanup.js +4 -2
  63. package/dist/move-path.d.ts +9 -0
  64. package/dist/move-path.d.ts.map +1 -1
  65. package/dist/move-path.js +53 -16
  66. package/dist/native-operations.d.ts +0 -1
  67. package/dist/native-operations.d.ts.map +1 -1
  68. package/dist/native-operations.js +0 -10
  69. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  70. package/dist/native-pinned-write-windows.js +5 -4
  71. package/dist/native-staged-file.d.ts +8 -1
  72. package/dist/native-staged-file.d.ts.map +1 -1
  73. package/dist/native-staged-file.js +30 -9
  74. package/dist/opened-realpath.d.ts +12 -1
  75. package/dist/opened-realpath.d.ts.map +1 -1
  76. package/dist/opened-realpath.js +8 -7
  77. package/dist/output.d.ts.map +1 -1
  78. package/dist/output.js +26 -2
  79. package/dist/path-policy.js +1 -1
  80. package/dist/permissions.d.ts +1 -0
  81. package/dist/permissions.d.ts.map +1 -1
  82. package/dist/permissions.js +3 -0
  83. package/dist/pinned-write.d.ts +1 -2
  84. package/dist/pinned-write.d.ts.map +1 -1
  85. package/dist/pinned-write.js +2 -11
  86. package/dist/private-temp-workspace.js +1 -1
  87. package/dist/publish-file.js +2 -2
  88. package/dist/replace-file-copy-source.d.ts.map +1 -1
  89. package/dist/replace-file-copy-source.js +2 -7
  90. package/dist/replace-file-descriptor.d.ts.map +1 -1
  91. package/dist/replace-file-descriptor.js +3 -16
  92. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  93. package/dist/replace-file-temp-owner.js +2 -7
  94. package/dist/replace-file.d.ts.map +1 -1
  95. package/dist/replace-file.js +21 -8
  96. package/dist/root-errors.d.ts +1 -0
  97. package/dist/root-errors.d.ts.map +1 -1
  98. package/dist/root-errors.js +9 -0
  99. package/dist/root-file.d.ts.map +1 -1
  100. package/dist/root-file.js +17 -28
  101. package/dist/root-impl.d.ts.map +1 -1
  102. package/dist/root-impl.js +12 -33
  103. package/dist/root-path.d.ts.map +1 -1
  104. package/dist/root-path.js +49 -94
  105. package/dist/root-write-mode.d.ts +6 -0
  106. package/dist/root-write-mode.d.ts.map +1 -0
  107. package/dist/root-write-mode.js +60 -0
  108. package/dist/root-write-verification.js +2 -2
  109. package/dist/safe-path-segment.d.ts +1 -0
  110. package/dist/safe-path-segment.d.ts.map +1 -1
  111. package/dist/safe-path-segment.js +1 -1
  112. package/dist/secret-file.d.ts.map +1 -1
  113. package/dist/secret-file.js +2 -9
  114. package/dist/secret-read-async.d.ts.map +1 -1
  115. package/dist/secret-read-async.js +2 -10
  116. package/dist/secret-read-policy.d.ts +4 -0
  117. package/dist/secret-read-policy.d.ts.map +1 -1
  118. package/dist/secret-read-policy.js +12 -0
  119. package/dist/sibling-staged-file.d.ts.map +1 -1
  120. package/dist/sibling-staged-file.js +2 -7
  121. package/dist/sidecar-lock-policy.d.ts +0 -1
  122. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  123. package/dist/sidecar-lock-policy.js +0 -4
  124. package/dist/string-coerce.d.ts +0 -9
  125. package/dist/string-coerce.d.ts.map +1 -1
  126. package/dist/string-coerce.js +0 -56
  127. package/dist/temp-target.d.ts.map +1 -1
  128. package/dist/temp-target.js +1 -12
  129. package/docs/atomic.md +53 -19
  130. package/docs/staged-file.md +6 -2
  131. package/docs/writing.md +5 -0
  132. package/package.json +8 -8
  133. package/dist/archive-tar-extract.d.ts +0 -11
  134. package/dist/archive-tar-extract.d.ts.map +0 -1
  135. package/dist/archive-tar-extract.js +0 -49
  136. package/dist/fsync.d.ts +0 -2
  137. package/dist/fsync.d.ts.map +0 -1
  138. package/dist/fsync.js +0 -1
  139. package/dist/json-durable-queue-retirement.d.ts +0 -9
  140. package/dist/json-durable-queue-retirement.d.ts.map +0 -1
  141. package/dist/json-durable-queue-retirement.js +0 -126
  142. package/dist/json-durable-queue-transfer-lock.d.ts +0 -2
  143. package/dist/json-durable-queue-transfer-lock.d.ts.map +0 -1
  144. package/dist/json-durable-queue-transfer-lock.js +0 -19
  145. package/dist/mode.d.ts +0 -2
  146. package/dist/mode.d.ts.map +0 -1
  147. package/dist/mode.js +0 -3
  148. package/dist/output-sibling.d.ts +0 -8
  149. package/dist/output-sibling.d.ts.map +0 -1
  150. package/dist/output-sibling.js +0 -30
  151. package/dist/read-error.d.ts +0 -2
  152. package/dist/read-error.d.ts.map +0 -1
  153. package/dist/read-error.js +0 -11
  154. package/dist/short-path.d.ts +0 -2
  155. package/dist/short-path.d.ts.map +0 -1
  156. package/dist/short-path.js +0 -7
  157. package/dist/staged-file.d.ts +0 -10
  158. package/dist/staged-file.d.ts.map +0 -1
  159. package/dist/staged-file.js +0 -15
@@ -1,3 +1,4 @@
1
+ import { type BigIntStats } from "node:fs";
1
2
  import { type AnyAsyncDirectoryGuard, type SyncDirectoryGuard } from "./directory-guard.js";
2
3
  export declare function withAsyncDirectoryGuards<T>(guards: readonly AnyAsyncDirectoryGuard[], mutate: () => Promise<T>, options?: {
3
4
  verifyAfter?: boolean;
@@ -8,6 +9,8 @@ export declare function withSyncDirectoryGuards<T>(guards: readonly SyncDirector
8
9
  }): T;
9
10
  export declare function guardedRename(params: {
10
11
  assertBeforeRename?: () => void;
12
+ onSourceInspected?: (identity: Pick<BigIntStats, "dev" | "ino">) => void;
13
+ onRenamed?: () => void;
11
14
  from: string;
12
15
  to: string;
13
16
  targetRoot?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"guarded-mutation.d.ts","sourceRoot":"","sources":["../src/guarded-mutation.ts"],"names":[],"mappings":"AAGA,OAAO,EAOL,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAE9B,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,MAAM,EAAE,SAAS,sBAAsB,EAAE,EACzC,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACxB,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACrE,GACL,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAED,wBAAgB,uBAAuB,CAAC,CAAC,EACvC,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,MAAM,EAAE,MAAM,CAAC,EACf,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,CAAC,CAWH;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAchB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,IAAI,CAUP;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhB;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,IAAI,CAWP"}
1
+ {"version":3,"file":"guarded-mutation.d.ts","sourceRoot":"","sources":["../src/guarded-mutation.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAGnD,OAAO,EAOL,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAE9B,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,MAAM,EAAE,SAAS,sBAAsB,EAAE,EACzC,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACxB,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACrE,GACL,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAED,wBAAgB,uBAAuB,CAAC,CAAC,EACvC,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,MAAM,EAAE,MAAM,CAAC,EACf,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,CAAC,CAWH;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,CAAC;IACzE,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBhB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,IAAI,CAUP;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhB;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,IAAI,CAWP"}
@@ -1,4 +1,4 @@
1
- import fsSync from "node:fs";
1
+ import fsSync, {} from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard, createNearestExistingDirectoryGuard, createNearestExistingSyncDirectoryGuard, createSyncDirectoryGuard, } from "./directory-guard.js";
@@ -48,9 +48,13 @@ export async function guardedRename(params) {
48
48
  ? await createNearestExistingDirectoryGuard(params.targetRoot, path.dirname(params.to))
49
49
  : await createAsyncDirectoryGuard(path.dirname(params.to));
50
50
  await withAsyncDirectoryGuards([sourceGuard, targetGuard], async () => {
51
+ if (params.onSourceInspected) {
52
+ params.onSourceInspected(await fs.lstat(params.from, { bigint: true }));
53
+ }
51
54
  // Authority must survive all awaited guards; do not yield before rename dispatch.
52
55
  params.assertBeforeRename?.();
53
56
  await fs.rename(params.from, params.to);
57
+ params.onRenamed?.();
54
58
  }, { verifyAfter: params.verifyAfter });
55
59
  }
56
60
  export function guardedRenameSync(params) {
@@ -1,7 +1,5 @@
1
1
  export declare function resolveEffectiveHomeDir(env?: NodeJS.ProcessEnv, homedir?: () => string): string | undefined;
2
- export declare function resolveOsHomeDir(env?: NodeJS.ProcessEnv, homedir?: () => string): string | undefined;
3
2
  export declare function resolveRequiredHomeDir(env?: NodeJS.ProcessEnv, homedir?: () => string): string;
4
- export declare function resolveRequiredOsHomeDir(env?: NodeJS.ProcessEnv, homedir?: () => string): string;
5
3
  export declare function expandHomePrefix(input: string, opts?: {
6
4
  home?: string;
7
5
  env?: NodeJS.ProcessEnv;
@@ -11,12 +9,4 @@ export declare function resolveHomeRelativePath(input: string, opts?: {
11
9
  env?: NodeJS.ProcessEnv;
12
10
  homedir?: () => string;
13
11
  }): string;
14
- export declare function resolveUserPath(input: string, optsOrEnv?: {
15
- env?: NodeJS.ProcessEnv;
16
- homedir?: () => string;
17
- } | NodeJS.ProcessEnv, homedir?: () => string): string;
18
- export declare function resolveOsHomeRelativePath(input: string, opts?: {
19
- env?: NodeJS.ProcessEnv;
20
- homedir?: () => string;
21
- }): string;
22
12
  //# sourceMappingURL=home-dir.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"AAeA,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,GAAG,SAAS,CAGpB;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,GAAG,SAAS,CAGpB;AAyCD,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,CAER;AAED,wBAAgB,wBAAwB,CACtC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,CAER;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAYR;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAcR;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EACN;IACE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACD,MAAM,CAAC,UAAU,EACrB,OAAO,CAAC,EAAE,MAAM,MAAM,GACrB,MAAM,CAMR;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAcR"}
1
+ {"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"AAeA,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,GAAG,SAAS,CAGpB;AAyCD,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,CAER;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAYR;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAcR"}
package/dist/home-dir.js CHANGED
@@ -15,10 +15,6 @@ export function resolveEffectiveHomeDir(env = process.env, homedir = os.homedir)
15
15
  const raw = resolveRawHomeDir(env, homedir);
16
16
  return raw ? path.resolve(raw) : undefined;
17
17
  }
18
- export function resolveOsHomeDir(env = process.env, homedir = os.homedir) {
19
- const raw = resolveRawOsHomeDir(env, homedir);
20
- return raw ? path.resolve(raw) : undefined;
21
- }
22
18
  function resolveRawHomeDir(env, homedir) {
23
19
  const explicitHome = normalize(env.OPENCLAW_HOME);
24
20
  if (!explicitHome) {
@@ -59,9 +55,6 @@ function normalizeSafe(homedir) {
59
55
  export function resolveRequiredHomeDir(env = process.env, homedir = os.homedir) {
60
56
  return resolveEffectiveHomeDir(env, homedir) ?? path.resolve(process.cwd());
61
57
  }
62
- export function resolveRequiredOsHomeDir(env = process.env, homedir = os.homedir) {
63
- return resolveOsHomeDir(env, homedir) ?? path.resolve(process.cwd());
64
- }
65
58
  export function expandHomePrefix(input, opts) {
66
59
  const segments = path.normalize(input).split(path.sep);
67
60
  if (segments[0] !== "~") {
@@ -89,24 +82,3 @@ export function resolveHomeRelativePath(input, opts) {
89
82
  });
90
83
  return path.resolve(expanded);
91
84
  }
92
- export function resolveUserPath(input, optsOrEnv, homedir) {
93
- const opts = optsOrEnv && ("env" in optsOrEnv || "homedir" in optsOrEnv)
94
- ? optsOrEnv
95
- : { env: optsOrEnv, homedir };
96
- return resolveHomeRelativePath(input, opts);
97
- }
98
- export function resolveOsHomeRelativePath(input, opts) {
99
- if (!input) {
100
- return input;
101
- }
102
- const segments = path.normalize(input).split(path.sep);
103
- if (segments[0] !== "~") {
104
- return path.resolve(input);
105
- }
106
- const expanded = expandHomePrefix(input, {
107
- home: resolveRequiredOsHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir),
108
- env: opts?.env,
109
- homedir: opts?.homedir,
110
- });
111
- return path.resolve(expanded);
112
- }
@@ -13,4 +13,5 @@ export declare function moveDurableQueueEntryToFailed(params: {
13
13
  paths: DurableQueueEntryPathsLike;
14
14
  failedPath: string;
15
15
  }): Promise<void>;
16
+ export declare function getErrorCode(error: unknown): string | null;
16
17
  //# sourceMappingURL=json-durable-queue-ownership.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"json-durable-queue-ownership.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-ownership.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAQF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAKpF;AAwED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,0BAA0B,EACjC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,OAAO,CAAC,CAalB;AAED,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDhB"}
1
+ {"version":3,"file":"json-durable-queue-ownership.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-ownership.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAKpF;AA+DD,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,0BAA0B,EACjC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,OAAO,CAAC,CAalB;AAED,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDhB;AAKD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI1D"}
@@ -1,16 +1,11 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { withSidecarLock } from "./sidecar-lock.js";
1
3
  import fs from "node:fs/promises";
2
4
  import path from "node:path";
3
5
  import { syncDirectory } from "./directory-durability.js";
4
6
  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";
7
+ import { sameFileIdentityForCleanup, sha256Hex } from "./file-identity.js";
8
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
9
  export function durableQueueProcessingPath(paths) {
15
10
  if (paths.processingPath)
16
11
  return paths.processingPath;
@@ -18,16 +13,6 @@ export function durableQueueProcessingPath(paths) {
18
13
  ? `${paths.jsonPath.slice(0, -".json".length)}.processing`
19
14
  : `${paths.jsonPath}.processing`;
20
15
  }
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
16
  async function regularQueueFileIdentity(filePath) {
32
17
  const identity = await lstatOrNull(filePath);
33
18
  if (!identity)
@@ -169,3 +154,139 @@ export async function moveDurableQueueEntryToFailed(params) {
169
154
  await syncDirectory(path.dirname(sourcePath));
170
155
  });
171
156
  }
157
+ const RETIREMENT_ROOT_NAME = ".fs-safe-retirements";
158
+ const RETIREMENT_ENTRY_NAME = "entry";
159
+ export function getErrorCode(error) {
160
+ return error && typeof error === "object" && "code" in error
161
+ ? String(error.code)
162
+ : null;
163
+ }
164
+ async function lstatOrNull(filePath) {
165
+ try {
166
+ return await fs.lstat(filePath, { bigint: true });
167
+ }
168
+ catch (error) {
169
+ if (getErrorCode(error) === "ENOENT")
170
+ return null;
171
+ throw error;
172
+ }
173
+ }
174
+ function assertDirectory(identity, label) {
175
+ if (identity.isSymbolicLink() || !identity.isDirectory()) {
176
+ throw new FsSafeError("path-mismatch", `${label} is not an owned directory`);
177
+ }
178
+ }
179
+ async function regularFileIdentity(filePath) {
180
+ const identity = await lstatOrNull(filePath);
181
+ if (!identity)
182
+ return null;
183
+ if (identity.isSymbolicLink() || !identity.isFile()) {
184
+ throw new FsSafeError("path-mismatch", "queue retirement path is not a regular file");
185
+ }
186
+ return identity;
187
+ }
188
+ function retirementPaths(jsonPath) {
189
+ const rootPath = path.join(path.dirname(jsonPath), RETIREMENT_ROOT_NAME);
190
+ const dirPath = path.join(rootPath, path.basename(jsonPath));
191
+ return { dirPath, entryPath: path.join(dirPath, RETIREMENT_ENTRY_NAME), rootPath };
192
+ }
193
+ async function inspectRetirementRoot(jsonPath) {
194
+ const rootPath = retirementPaths(jsonPath).rootPath;
195
+ const root = await lstatOrNull(rootPath);
196
+ if (!root)
197
+ return null;
198
+ assertDirectory(root, "queue retirement root");
199
+ return rootPath;
200
+ }
201
+ async function ensureRetirementRoot(jsonPath) {
202
+ const parentPath = path.dirname(jsonPath);
203
+ const rootPath = retirementPaths(jsonPath).rootPath;
204
+ await fs.mkdir(rootPath, { mode: 0o700 }).catch((error) => {
205
+ if (getErrorCode(error) !== "EEXIST")
206
+ throw error;
207
+ });
208
+ const root = await fs.lstat(rootPath, { bigint: true });
209
+ assertDirectory(root, "queue retirement root");
210
+ await syncDirectory(parentPath);
211
+ return rootPath;
212
+ }
213
+ async function removeRetirementRecord(params) {
214
+ await fs.rmdir(params.dirPath).catch((error) => {
215
+ if (getErrorCode(error) !== "ENOENT")
216
+ throw error;
217
+ });
218
+ await syncDirectory(params.rootPath);
219
+ }
220
+ async function recoverDurableQueueRetirement(params) {
221
+ const rootPath = await inspectRetirementRoot(params.jsonPath);
222
+ if (!rootPath)
223
+ return;
224
+ const { dirPath, entryPath } = retirementPaths(params.jsonPath);
225
+ const dir = await lstatOrNull(dirPath);
226
+ if (!dir)
227
+ return;
228
+ assertDirectory(dir, "queue retirement record");
229
+ const names = await fs.readdir(dirPath);
230
+ if (names.length === 0) {
231
+ await removeRetirementRecord({ dirPath, rootPath });
232
+ return;
233
+ }
234
+ if (names.length !== 1 || names[0] !== RETIREMENT_ENTRY_NAME) {
235
+ throw new FsSafeError("path-mismatch", "queue retirement record is ambiguous");
236
+ }
237
+ const entry = await regularFileIdentity(entryPath);
238
+ const processing = await regularFileIdentity(params.processingPath);
239
+ if (!entry || !processing) {
240
+ throw new FsSafeError("path-mismatch", "queue retirement ownership is incomplete");
241
+ }
242
+ if (!sameFileIdentityForCleanup(entry, processing)) {
243
+ const pending = await regularFileIdentity(params.jsonPath);
244
+ if (!pending) {
245
+ await fs.link(entryPath, params.jsonPath);
246
+ await syncDirectory(path.dirname(params.jsonPath));
247
+ }
248
+ else if (sameFileIdentityForCleanup(pending, entry)) {
249
+ await syncDirectory(path.dirname(params.jsonPath));
250
+ }
251
+ else if (pending.nlink > 1n) {
252
+ throw new FsSafeError("path-mismatch", "queue replacement identity is ambiguous");
253
+ }
254
+ }
255
+ await fs.unlink(entryPath);
256
+ await syncDirectory(dirPath);
257
+ await removeRetirementRecord({ dirPath, rootPath });
258
+ }
259
+ async function retireDurableQueueSource(params) {
260
+ await recoverDurableQueueRetirement(params);
261
+ const rootPath = await ensureRetirementRoot(params.jsonPath);
262
+ const { dirPath, entryPath } = retirementPaths(params.jsonPath);
263
+ await fs.mkdir(dirPath, { mode: 0o700 });
264
+ await syncDirectory(rootPath);
265
+ try {
266
+ await fs.rename(params.jsonPath, entryPath);
267
+ }
268
+ catch (error) {
269
+ if (getErrorCode(error) !== "ENOENT")
270
+ throw error;
271
+ await removeRetirementRecord({ dirPath, rootPath });
272
+ return;
273
+ }
274
+ await syncDirectory(dirPath);
275
+ await syncDirectory(path.dirname(params.jsonPath));
276
+ await recoverDurableQueueRetirement(params);
277
+ }
278
+ async function withQueueTransferLock(filePath, run) {
279
+ const lockPath = path.join(path.dirname(filePath), `.fs-safe-transfer-${sha256Hex(path.basename(filePath)).slice(0, 32)}.lock`);
280
+ return await withSidecarLock(filePath, {
281
+ managerKey: "fs-safe.queue-transfer",
282
+ lockPath,
283
+ staleMs: 30_000,
284
+ staleRecovery: "fail-closed",
285
+ timeoutMs: 45_000,
286
+ payload: () => ({
287
+ ownerToken: randomUUID(),
288
+ createdAt: new Date().toISOString(),
289
+ }),
290
+ retry: { retries: 180, minTimeout: 25, maxTimeout: 250, factor: 1.1 },
291
+ }, run);
292
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAkBA,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,CAQhB;AA6DD,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,CA8Dd;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":"AAmBA,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;AAM3E,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,CAQhB;AA6DD,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,CA8Dd;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"}
@@ -3,18 +3,13 @@ import path from "node:path";
3
3
  import { normalizeMaxBytes } from "./byte-budget.js";
4
4
  import { syncDirectory } from "./directory-durability.js";
5
5
  import { syncQueueDirectoryCreation } from "./json-durable-queue-directory.js";
6
- import { acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, moveDurableQueueEntryToFailed, } from "./json-durable-queue-ownership.js";
6
+ import { getErrorCode, acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, moveDurableQueueEntryToFailed, } from "./json-durable-queue-ownership.js";
7
7
  import { stringifyJsonDocument } from "./json-stringify.js";
8
8
  import { resolveReadOpenFlags } from "./read-open-flags.js";
9
9
  import { replaceFileAtomicWithDirectorySync } from "./replace-file.js";
10
10
  import { assertSafePathSegment } from "./safe-path-segment.js";
11
11
  import { inspectFileIdentity } from "./strict-file-identity.js";
12
12
  export const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES = 16 * 1024 * 1024;
13
- function getErrnoCode(error) {
14
- return error && typeof error === "object" && "code" in error
15
- ? String(error.code)
16
- : null;
17
- }
18
13
  function assertSafeQueueEntryId(id) {
19
14
  assertSafePathSegment(id, { label: "queue entry id" });
20
15
  }
@@ -27,7 +22,7 @@ export async function jsonDurableQueueEntryExists(filePath) {
27
22
  return stat.isFile();
28
23
  }
29
24
  catch (error) {
30
- if (getErrnoCode(error) === "ENOENT") {
25
+ if (getErrorCode(error) === "ENOENT") {
31
26
  return false;
32
27
  }
33
28
  throw error;
@@ -41,7 +36,7 @@ async function unlinkStaleTmpBestEffort(filePath, now, maxAgeMs) {
41
36
  }
42
37
  }
43
38
  catch (error) {
44
- if (getErrnoCode(error) !== "ENOENT") {
39
+ if (getErrorCode(error) !== "ENOENT") {
45
40
  throw error;
46
41
  }
47
42
  }
@@ -302,7 +297,7 @@ export async function loadJsonDurableQueueEntry(params) {
302
297
  return result.entry;
303
298
  }
304
299
  catch (error) {
305
- if (getErrnoCode(error) === "ENOENT") {
300
+ if (getErrorCode(error) === "ENOENT") {
306
301
  return null;
307
302
  }
308
303
  throw error;
@@ -314,7 +309,7 @@ export async function loadPendingJsonDurableQueueEntries(options) {
314
309
  files = await fs.promises.readdir(options.queueDir);
315
310
  }
316
311
  catch (error) {
317
- if (getErrnoCode(error) === "ENOENT") {
312
+ if (getErrorCode(error) === "ENOENT") {
318
313
  return [];
319
314
  }
320
315
  throw error;
@@ -1 +1 @@
1
- {"version":3,"file":"json-store.d.ts","sourceRoot":"","sources":["../src/json-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACd,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,oBAAoB,GAAG;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,YAAY,EAAE,oBAAoB,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAC;AAEtE,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAQvE"}
1
+ {"version":3,"file":"json-store.d.ts","sourceRoot":"","sources":["../src/json-store.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACd,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,oBAAoB,GAAG;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,YAAY,EAAE,oBAAoB,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAC;AAEtE,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAQvE"}
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { fileStore } from "./file-store.js";
3
- import { createJsonStore, } from "./json-document-store.js";
3
+ import {} from "./json-document-store.js";
4
4
  export function jsonStore(options) {
5
5
  const filePath = path.resolve(options.filePath);
6
6
  return fileStore({
@@ -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;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;AAgDF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CAuC7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA4CtC"}
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;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;AAgDF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CAuC7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA2CtC"}
@@ -2,7 +2,7 @@ import fsSync from "node:fs";
2
2
  import path from "node:path";
3
3
  import { normalizeMaxBytes } from "./byte-budget.js";
4
4
  import { FsSafeError } from "./errors.js";
5
- import { expandHomePrefix, resolveUserPath } from "./home-dir.js";
5
+ import { expandHomePrefix, resolveHomeRelativePath } from "./home-dir.js";
6
6
  import { isFileUrl, safeFileURLToPath } from "./local-file-access.js";
7
7
  import { isPathInside } from "./path.js";
8
8
  import { resolveRootPathSync } from "./root-path.js";
@@ -20,7 +20,7 @@ function resolveLocalPathInput(input, label) {
20
20
  if (input.includes("\0")) {
21
21
  throw new FsSafeError("invalid-path", `${label} must not contain NUL bytes`);
22
22
  }
23
- return resolveUserPath(input);
23
+ return resolveHomeRelativePath(input);
24
24
  }
25
25
  function resolveLocalRootInput(input, label) {
26
26
  const trimmed = input.trim();
@@ -106,7 +106,6 @@ export async function readLocalFileFromRoots(options) {
106
106
  }
107
107
  const readOptions = {
108
108
  hardlinks: options.hardlinks,
109
- nonBlockingRead: options.nonBlockingRead,
110
109
  symlinks: options.symlinks,
111
110
  };
112
111
  // Leave maxBytes absent when the caller omits it so Root's own default
@@ -38,6 +38,6 @@ export declare function sameIdentity(a: EntryIdentity, b: EntryIdentity): boolea
38
38
  export declare function sourceChangedError(sourcePath: string): Error;
39
39
  export declare function assertSourceStillMatches(sourcePath: string, identity: EntryIdentity): Promise<void>;
40
40
  export declare function createCleanupCopiedEntryState(sourcePath: string, manifest: CopiedEntryManifest): CleanupCopiedEntryState;
41
- export declare function cleanupCopiedEntry(sourcePath: string, manifest: CopiedEntryManifest, state: CleanupCopiedEntryState): Promise<CleanupCopiedEntryResult>;
41
+ export declare function cleanupCopiedEntry(sourcePath: string, manifest: CopiedEntryManifest, state: CleanupCopiedEntryState, assertBeforeMutation: () => void): Promise<CleanupCopiedEntryResult>;
42
42
  export {};
43
43
  //# 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":"AAGA,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,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;AAMD,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,GAC7B,OAAO,CAAC,wBAAwB,CAAC,CAkDnC"}
1
+ {"version":3,"file":"move-path-cleanup.d.ts","sourceRoot":"","sources":["../src/move-path-cleanup.ts"],"names":[],"mappings":"AAGA,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,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;AAMD,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,CAyDnC"}
@@ -111,7 +111,7 @@ async function observeOwnedAliasUnlink(sourcePath, group) {
111
111
  function mergeCleanupResults(a, b) {
112
112
  return a === "stale" || b === "stale" ? "stale" : "removed";
113
113
  }
114
- export async function cleanupCopiedEntry(sourcePath, manifest, state) {
114
+ export async function cleanupCopiedEntry(sourcePath, manifest, state, assertBeforeMutation) {
115
115
  const aliasGroup = manifest.kind === "leaf" ? state.aliasGroups.get(identityKey(manifest)) : undefined;
116
116
  if (aliasGroup?.stale) {
117
117
  return "stale";
@@ -134,8 +134,9 @@ export async function cleanupCopiedEntry(sourcePath, manifest, state) {
134
134
  // clean manifest children so the fallback does not duplicate copied files.
135
135
  let result = "removed";
136
136
  for (const child of manifest.children) {
137
- result = mergeCleanupResults(result, await cleanupCopiedEntry(path.join(sourcePath, child.name), child.manifest, state));
137
+ result = mergeCleanupResults(result, await cleanupCopiedEntry(path.join(sourcePath, child.name), child.manifest, state, assertBeforeMutation));
138
138
  }
139
+ assertBeforeMutation();
139
140
  try {
140
141
  await fs.rmdir(sourcePath);
141
142
  }
@@ -152,6 +153,7 @@ export async function cleanupCopiedEntry(sourcePath, manifest, state) {
152
153
  if (!sameIdentity(expected, entryIdentity(currentStat))) {
153
154
  return aliasGroup ? poisonAliasGroup(aliasGroup) : "stale";
154
155
  }
156
+ assertBeforeMutation();
155
157
  await fs.unlink(sourcePath);
156
158
  return aliasGroup
157
159
  ? await observeOwnedAliasUnlink(sourcePath, aliasGroup)
@@ -1,6 +1,15 @@
1
+ export type MovePathPublicationReceipt = Readonly<{
2
+ path: string;
3
+ dev: bigint;
4
+ ino: bigint;
5
+ }>;
1
6
  export type MovePathWithCopyFallbackOptions = {
7
+ /** Rechecks authority before each rename and copied-source unlink/rmdir dispatch. */
8
+ assertBeforeMutation?: () => void;
2
9
  /** Rechecks caller authority synchronously immediately before each rename dispatch. */
3
10
  assertBeforeRename?: () => void;
11
+ /** Reports the committed destination before post-rename checks or source cleanup. */
12
+ onDestinationPublished?: (receipt: MovePathPublicationReceipt) => void;
4
13
  from: string;
5
14
  sourceHardlinks?: "allow" | "reject";
6
15
  to: string;
@@ -1 +1 @@
1
- {"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,+BAA+B,GAAG;IAC5C,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,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;AA8OD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAkFf"}
1
+ {"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAoBA,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;AAsPD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAqHf"}
package/dist/move-path.js CHANGED
@@ -211,24 +211,49 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
211
211
  });
212
212
  return { ...identity, kind: "leaf" };
213
213
  }
214
+ function assertSynchronousResult(returned, name) {
215
+ if (returned !== undefined) {
216
+ // TypeScript permits async functions for () => void; consume their rejection.
217
+ void Promise.resolve(returned).catch(() => { });
218
+ throw new TypeError(`${name} must return undefined synchronously`);
219
+ }
220
+ }
214
221
  export async function movePathWithCopyFallback(options) {
215
- // Keep the initiating owner's callback across preparation and copy fallback.
216
- const callerAssert = options.assertBeforeRename;
222
+ // Keep the initiating owner's callbacks across preparation and copy fallback.
223
+ const callerRenameAssert = options.assertBeforeRename;
224
+ const callerMutationAssert = options.assertBeforeMutation;
225
+ const callerPublished = options.onDestinationPublished;
217
226
  let assertionRejected = false;
227
+ let destinationPublished = false;
228
+ const assertBeforeMutation = () => {
229
+ assertSynchronousResult(callerMutationAssert?.(), "assertBeforeMutation");
230
+ };
218
231
  const assertBeforeRename = () => {
219
232
  try {
220
- const returned = callerAssert?.();
221
- if (returned !== undefined) {
222
- // TypeScript permits async functions for () => void; they cannot authorize a rename.
223
- void Promise.resolve(returned).catch(() => { });
224
- throw new TypeError("assertBeforeRename must return undefined synchronously");
225
- }
233
+ assertSynchronousResult(callerRenameAssert?.(), "assertBeforeRename");
234
+ assertBeforeMutation();
226
235
  }
227
236
  catch (error) {
228
237
  assertionRejected = true;
229
238
  throw error;
230
239
  }
231
240
  };
241
+ let publicationReceipt;
242
+ const onSourceInspected = callerPublished
243
+ ? (identity) => {
244
+ publicationReceipt = Object.freeze({
245
+ path: targetPath,
246
+ dev: identity.dev,
247
+ ino: identity.ino,
248
+ });
249
+ }
250
+ : undefined;
251
+ const onRenamed = () => {
252
+ destinationPublished = true;
253
+ if (publicationReceipt) {
254
+ assertSynchronousResult(callerPublished?.(publicationReceipt), "onDestinationPublished");
255
+ }
256
+ };
232
257
  const sourcePath = path.resolve(options.from);
233
258
  const targetPath = path.resolve(options.to);
234
259
  const rejectHardlinks = options.sourceHardlinks === "reject";
@@ -237,12 +262,18 @@ export async function movePathWithCopyFallback(options) {
237
262
  }
238
263
  if (!rejectHardlinks) {
239
264
  try {
240
- await guardedRename({ from: sourcePath, to: targetPath, assertBeforeRename });
265
+ await guardedRename({
266
+ from: sourcePath,
267
+ to: targetPath,
268
+ assertBeforeRename,
269
+ onSourceInspected,
270
+ onRenamed,
271
+ });
241
272
  return;
242
273
  }
243
274
  catch (error) {
244
275
  // An owner's EXDEV/EPERM refusal is not permission to copy instead.
245
- if (assertionRejected || !moveCopyFallbackReasonForRenameError(error)) {
276
+ if (assertionRejected || destinationPublished || !moveCopyFallbackReasonForRenameError(error)) {
246
277
  throw error;
247
278
  }
248
279
  }
@@ -256,7 +287,6 @@ export async function movePathWithCopyFallback(options) {
256
287
  const targetDir = path.dirname(targetPath);
257
288
  const staged = path.join(targetDir, `.fs-safe-move-${process.pid}-${randomUUID()}.tmp`);
258
289
  const unregisterStaged = registerTempPathForExit(staged, { recursive: true });
259
- let stagedCommitted = false;
260
290
  try {
261
291
  const manifest = await copyEntryWithManifest(sourcePath, staged, {
262
292
  sourceHardlinks: options.sourceHardlinks ?? "allow",
@@ -265,16 +295,23 @@ export async function movePathWithCopyFallback(options) {
265
295
  const cleanupState = createCleanupCopiedEntryState(sourcePath, manifest);
266
296
  unregisterStaged.setIdentity(await fs.lstat(staged, { bigint: true }));
267
297
  await assertCopyDestinationOutsideSource(sourcePath, targetPath, manifest);
268
- await guardedRename({ from: staged, to: targetPath, assertBeforeRename });
269
- stagedCommitted = true;
270
- unregisterStaged();
271
- const cleanupResult = await cleanupCopiedEntry(sourcePath, manifest, cleanupState);
298
+ await guardedRename({
299
+ from: staged,
300
+ to: targetPath,
301
+ assertBeforeRename,
302
+ onSourceInspected,
303
+ onRenamed: () => {
304
+ unregisterStaged();
305
+ onRenamed();
306
+ },
307
+ });
308
+ const cleanupResult = await cleanupCopiedEntry(sourcePath, manifest, cleanupState, assertBeforeMutation);
272
309
  if (cleanupResult === "stale") {
273
310
  throw sourceChangedError(sourcePath);
274
311
  }
275
312
  }
276
313
  finally {
277
- if (!stagedCommitted) {
314
+ if (!destinationPublished) {
278
315
  try {
279
316
  const stagedIdentity = await fs.lstat(staged, { bigint: true });
280
317
  if (!stagedIdentity.isSymbolicLink())
@@ -18,5 +18,4 @@ export declare function removeNativeCreatedFileIfStillPinned(params: {
18
18
  created?: BigIntStats;
19
19
  }): void;
20
20
  export declare function createNativeExclusiveFile(targetPath: string, mode: number): Promise<NativeFileHandle | undefined>;
21
- export declare function syncNativeFileBestEffort(fd: number): void;
22
21
  //# sourceMappingURL=native-operations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAS5D;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAgBf;AAsBD,wBAAgB,oCAAoC,CAAC,MAAM,EAAE;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB,GAAG,IAAI,CAqBP;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAwDvC;AAED,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAQzD"}
1
+ {"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAS5D;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAgBf;AAsBD,wBAAgB,oCAAoC,CAAC,MAAM,EAAE;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB,GAAG,IAAI,CAqBP;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAwDvC"}
@@ -130,13 +130,3 @@ export async function createNativeExclusiveFile(targetPath, mode) {
130
130
  await parent.close().catch(() => undefined);
131
131
  }
132
132
  }
133
- export function syncNativeFileBestEffort(fd) {
134
- try {
135
- fsSync.fsyncSync(fd);
136
- }
137
- catch (error) {
138
- if (error.code !== "EPERM") {
139
- throw error;
140
- }
141
- }
142
- }