@openclaw/fs-safe 0.8.6 → 0.10.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 (246) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +17 -0
  3. package/dist/advanced.d.ts +1 -0
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-crc32.d.ts.map +1 -1
  7. package/dist/archive-crc32.js +6 -1
  8. package/dist/archive-deadline.d.ts.map +1 -1
  9. package/dist/archive-deadline.js +3 -4
  10. package/dist/archive-durability.d.ts +24 -0
  11. package/dist/archive-durability.d.ts.map +1 -0
  12. package/dist/archive-durability.js +180 -0
  13. package/dist/archive-gzip-tail.d.ts +2 -0
  14. package/dist/archive-gzip-tail.d.ts.map +1 -1
  15. package/dist/archive-gzip-tail.js +22 -4
  16. package/dist/archive-input.js +4 -4
  17. package/dist/archive-kind.d.ts.map +1 -1
  18. package/dist/archive-kind.js +3 -2
  19. package/dist/archive-merge.d.ts +1 -0
  20. package/dist/archive-merge.d.ts.map +1 -1
  21. package/dist/archive-merge.js +42 -9
  22. package/dist/archive-native.d.ts +1 -0
  23. package/dist/archive-native.d.ts.map +1 -1
  24. package/dist/archive-native.js +4 -2
  25. package/dist/archive-options.d.ts +2 -0
  26. package/dist/archive-options.d.ts.map +1 -1
  27. package/dist/archive-parser.wasm +0 -0
  28. package/dist/archive-read.d.ts.map +1 -1
  29. package/dist/archive-read.js +72 -70
  30. package/dist/archive-staging.d.ts +1 -1
  31. package/dist/archive-staging.d.ts.map +1 -1
  32. package/dist/archive-staging.js +24 -19
  33. package/dist/archive-tar-stream.d.ts +11 -4
  34. package/dist/archive-tar-stream.d.ts.map +1 -1
  35. package/dist/archive-tar-stream.js +20 -8
  36. package/dist/archive-tar-wasm.d.ts.map +1 -1
  37. package/dist/archive-tar-wasm.js +2 -1
  38. package/dist/archive.d.ts.map +1 -1
  39. package/dist/archive.js +9 -4
  40. package/dist/bounded-read.d.ts +7 -0
  41. package/dist/bounded-read.d.ts.map +1 -1
  42. package/dist/bounded-read.js +73 -43
  43. package/dist/clone-metadata.d.ts +19 -0
  44. package/dist/clone-metadata.d.ts.map +1 -0
  45. package/dist/clone-metadata.js +32 -0
  46. package/dist/copy-file-input.d.ts +23 -0
  47. package/dist/copy-file-input.d.ts.map +1 -0
  48. package/dist/copy-file-input.js +91 -0
  49. package/dist/copy-policy.d.ts +3 -0
  50. package/dist/copy-policy.d.ts.map +1 -0
  51. package/dist/copy-policy.js +8 -0
  52. package/dist/copy-publication.d.ts +15 -0
  53. package/dist/copy-publication.d.ts.map +1 -0
  54. package/dist/copy-publication.js +30 -0
  55. package/dist/copy-tree-portable.d.ts +9 -0
  56. package/dist/copy-tree-portable.d.ts.map +1 -0
  57. package/dist/copy-tree-portable.js +191 -0
  58. package/dist/copy.d.ts +16 -0
  59. package/dist/copy.d.ts.map +1 -0
  60. package/dist/copy.js +123 -0
  61. package/dist/directory-mode-node.d.ts.map +1 -1
  62. package/dist/directory-mode-node.js +4 -4
  63. package/dist/durability.d.ts +1 -1
  64. package/dist/durability.d.ts.map +1 -1
  65. package/dist/error-detail.d.ts.map +1 -1
  66. package/dist/error-detail.js +4 -1
  67. package/dist/file-hash.d.ts +6 -2
  68. package/dist/file-hash.d.ts.map +1 -1
  69. package/dist/file-hash.js +49 -12
  70. package/dist/file-store-sync-write.d.ts +1 -0
  71. package/dist/file-store-sync-write.d.ts.map +1 -1
  72. package/dist/file-store-sync-write.js +9 -6
  73. package/dist/file-store.d.ts +4 -0
  74. package/dist/file-store.d.ts.map +1 -1
  75. package/dist/file-store.js +10 -1
  76. package/dist/filename.d.ts.map +1 -1
  77. package/dist/filename.js +2 -12
  78. package/dist/fs.d.ts.map +1 -1
  79. package/dist/fs.js +1 -2
  80. package/dist/guarded-mkdir.d.ts +1 -0
  81. package/dist/guarded-mkdir.d.ts.map +1 -1
  82. package/dist/guarded-mkdir.js +1 -0
  83. package/dist/guarded-mutation.d.ts +2 -0
  84. package/dist/guarded-mutation.d.ts.map +1 -1
  85. package/dist/guarded-mutation.js +9 -5
  86. package/dist/home-dir.d.ts.map +1 -1
  87. package/dist/home-dir.js +9 -7
  88. package/dist/index.d.ts +1 -1
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/install-path.d.ts.map +1 -1
  91. package/dist/install-path.js +9 -13
  92. package/dist/json-document-store.d.ts +3 -0
  93. package/dist/json-document-store.d.ts.map +1 -1
  94. package/dist/json-document-store.js +1 -0
  95. package/dist/json-durable-queue-directory.js +3 -3
  96. package/dist/json-durable-queue-ownership.js +3 -2
  97. package/dist/json-durable-queue.d.ts.map +1 -1
  98. package/dist/json-durable-queue.js +31 -27
  99. package/dist/local-roots.d.ts.map +1 -1
  100. package/dist/local-roots.js +22 -24
  101. package/dist/move-path-cleanup.d.ts.map +1 -1
  102. package/dist/move-path-cleanup.js +4 -3
  103. package/dist/move-path-stage.d.ts +8 -0
  104. package/dist/move-path-stage.d.ts.map +1 -0
  105. package/dist/move-path-stage.js +55 -0
  106. package/dist/move-path.d.ts.map +1 -1
  107. package/dist/move-path.js +55 -47
  108. package/dist/mutation-authority.d.ts +8 -0
  109. package/dist/mutation-authority.d.ts.map +1 -0
  110. package/dist/mutation-authority.js +36 -0
  111. package/dist/native-binding.d.ts +24 -1
  112. package/dist/native-binding.d.ts.map +1 -1
  113. package/dist/native-operations.d.ts +1 -1
  114. package/dist/native-operations.d.ts.map +1 -1
  115. package/dist/native-operations.js +11 -5
  116. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  117. package/dist/native-pinned-write-windows.js +18 -7
  118. package/dist/native-pinned-write.d.ts.map +1 -1
  119. package/dist/native-pinned-write.js +1 -0
  120. package/dist/native-staged-file.d.ts +3 -3
  121. package/dist/native-staged-file.d.ts.map +1 -1
  122. package/dist/native-staged-file.js +35 -8
  123. package/dist/opened-file-failure.d.ts +1 -1
  124. package/dist/opened-file-failure.d.ts.map +1 -1
  125. package/dist/opened-file-failure.js +3 -2
  126. package/dist/path.d.ts.map +1 -1
  127. package/dist/path.js +3 -1
  128. package/dist/permissions-windows.d.ts.map +1 -1
  129. package/dist/permissions-windows.js +38 -48
  130. package/dist/permissions.js +3 -3
  131. package/dist/pinned-operation.js +1 -1
  132. package/dist/pinned-write.d.ts +5 -1
  133. package/dist/pinned-write.d.ts.map +1 -1
  134. package/dist/pinned-write.js +50 -30
  135. package/dist/positional-read.d.ts +9 -0
  136. package/dist/positional-read.d.ts.map +1 -0
  137. package/dist/positional-read.js +36 -0
  138. package/dist/private-temp-workspace.d.ts.map +1 -1
  139. package/dist/private-temp-workspace.js +9 -3
  140. package/dist/publish-copy-stage.d.ts +13 -0
  141. package/dist/publish-copy-stage.d.ts.map +1 -0
  142. package/dist/publish-copy-stage.js +47 -0
  143. package/dist/publish-file.d.ts.map +1 -1
  144. package/dist/publish-file.js +26 -25
  145. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  146. package/dist/replace-file-copy-fallback.js +30 -17
  147. package/dist/replace-file-copy-source.d.ts.map +1 -1
  148. package/dist/replace-file-copy-source.js +10 -5
  149. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  150. package/dist/replace-file-temp-owner.js +6 -2
  151. package/dist/replace-file.d.ts.map +1 -1
  152. package/dist/replace-file.js +8 -3
  153. package/dist/root-context.d.ts +3 -0
  154. package/dist/root-context.d.ts.map +1 -1
  155. package/dist/root-context.js +51 -26
  156. package/dist/root-directory-list.d.ts +24 -0
  157. package/dist/root-directory-list.d.ts.map +1 -0
  158. package/dist/root-directory-list.js +201 -0
  159. package/dist/root-errors.d.ts +1 -0
  160. package/dist/root-errors.d.ts.map +1 -1
  161. package/dist/root-errors.js +3 -0
  162. package/dist/root-file.d.ts +2 -0
  163. package/dist/root-file.d.ts.map +1 -1
  164. package/dist/root-file.js +12 -4
  165. package/dist/root-impl.d.ts +6 -50
  166. package/dist/root-impl.d.ts.map +1 -1
  167. package/dist/root-impl.js +342 -208
  168. package/dist/root-options.d.ts +66 -0
  169. package/dist/root-options.d.ts.map +1 -0
  170. package/dist/root-options.js +18 -0
  171. package/dist/root-path-existing.d.ts +5 -0
  172. package/dist/root-path-existing.d.ts.map +1 -1
  173. package/dist/root-path-existing.js +56 -1
  174. package/dist/root-path.d.ts +1 -0
  175. package/dist/root-path.d.ts.map +1 -1
  176. package/dist/root-path.js +74 -162
  177. package/dist/root-symlink-policy.d.ts +13 -0
  178. package/dist/root-symlink-policy.d.ts.map +1 -0
  179. package/dist/root-symlink-policy.js +34 -0
  180. package/dist/root-walk.d.ts +5 -4
  181. package/dist/root-walk.d.ts.map +1 -1
  182. package/dist/root-walk.js +99 -50
  183. package/dist/root-write-mode.d.ts.map +1 -1
  184. package/dist/root-write-mode.js +1 -4
  185. package/dist/root.d.ts +3 -1
  186. package/dist/root.d.ts.map +1 -1
  187. package/dist/secret-file.d.ts +1 -0
  188. package/dist/secret-file.d.ts.map +1 -1
  189. package/dist/secret-file.js +1 -0
  190. package/dist/secret-read-async.js +5 -5
  191. package/dist/secure-file.d.ts +1 -1
  192. package/dist/secure-file.d.ts.map +1 -1
  193. package/dist/secure-file.js +23 -13
  194. package/dist/sibling-staged-file.js +7 -7
  195. package/dist/sibling-temp.d.ts.map +1 -1
  196. package/dist/sibling-temp.js +16 -7
  197. package/dist/sidecar-lock-acquire.d.ts +1 -1
  198. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  199. package/dist/sidecar-lock-acquire.js +6 -4
  200. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  201. package/dist/sidecar-lock-policy.js +5 -3
  202. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  203. package/dist/sidecar-lock-reclaim.js +16 -4
  204. package/dist/sidecar-lock.d.ts.map +1 -1
  205. package/dist/sidecar-lock.js +2 -1
  206. package/dist/temp-cleanup.d.ts.map +1 -1
  207. package/dist/temp-cleanup.js +3 -2
  208. package/dist/temp-target.d.ts.map +1 -1
  209. package/dist/temp-target.js +9 -4
  210. package/dist/timing.d.ts +1 -0
  211. package/dist/timing.d.ts.map +1 -1
  212. package/dist/timing.js +25 -6
  213. package/dist/trash.js +2 -2
  214. package/dist/walk.d.ts.map +1 -1
  215. package/dist/walk.js +7 -27
  216. package/dist/windows-owner.d.ts +9 -12
  217. package/dist/windows-owner.d.ts.map +1 -1
  218. package/dist/windows-owner.js +24 -58
  219. package/dist/write-file-handle.d.ts +6 -0
  220. package/dist/write-file-handle.d.ts.map +1 -0
  221. package/dist/write-file-handle.js +25 -0
  222. package/dist/write-open-flags.d.ts.map +1 -1
  223. package/dist/write-open-flags.js +1 -2
  224. package/docs/advanced.md +18 -4
  225. package/docs/archive.md +57 -9
  226. package/docs/atomic.md +7 -0
  227. package/docs/contributing.md +7 -0
  228. package/docs/copy.md +86 -0
  229. package/docs/durability.md +17 -2
  230. package/docs/errors.md +1 -1
  231. package/docs/file-store.md +48 -3
  232. package/docs/json-store.md +10 -0
  233. package/docs/local-roots.md +8 -1
  234. package/docs/native.md +14 -1
  235. package/docs/permissions.md +20 -10
  236. package/docs/positional-read.md +63 -0
  237. package/docs/reading.md +6 -0
  238. package/docs/root.md +168 -6
  239. package/docs/secret-file.md +6 -0
  240. package/docs/security-model.md +14 -0
  241. package/docs/sidecar-lock.md +2 -0
  242. package/docs/timing.md +2 -0
  243. package/docs/types.md +18 -11
  244. package/docs/walk.md +54 -5
  245. package/docs/writing.md +25 -5
  246. package/package.json +16 -11
@@ -2,10 +2,9 @@ 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, resolveHomeRelativePath } from "./home-dir.js";
5
+ import { expandHomePrefix, resolveRequiredHomeDir } from "./home-dir.js";
6
6
  import { isFileUrl, safeFileURLToPath } from "./local-file-access.js";
7
- import { isPathInside } from "./path.js";
8
- import { resolveRootPathSync } from "./root-path.js";
7
+ import { ROOT_PATH_ALIAS_POLICIES, resolveRootPathSync } from "./root-path.js";
9
8
  import { root } from "./root.js";
10
9
  function resolveLocalPathInput(input, label) {
11
10
  if (isFileUrl(input)) {
@@ -20,7 +19,14 @@ function resolveLocalPathInput(input, label) {
20
19
  if (input.includes("\0")) {
21
20
  throw new FsSafeError("invalid-path", `${label} must not contain NUL bytes`);
22
21
  }
23
- return resolveHomeRelativePath(input);
22
+ const homePrefix = input === "~" || input.startsWith("~/") ||
23
+ (path.sep === "\\" && input.startsWith("~\\"));
24
+ const expanded = homePrefix ? `${resolveRequiredHomeDir()}${path.sep}${input.slice(2)}` : input;
25
+ if (path.isAbsolute(expanded))
26
+ return expanded;
27
+ const drive = path.parse(expanded).root;
28
+ const base = drive ? path.resolve(drive) : process.cwd();
29
+ return `${base}${path.sep}${expanded.slice(drive.length)}`;
24
30
  }
25
31
  function resolveLocalRootInput(input, label) {
26
32
  const trimmed = input.trim();
@@ -54,7 +60,7 @@ function resolveRootRealSync(rootDir) {
54
60
  }
55
61
  export function resolveLocalPathFromRootsSync(options) {
56
62
  const label = options.label ?? "local roots";
57
- const requestedPath = path.resolve(resolveLocalPathInput(options.filePath, "file path"));
63
+ const requestedPath = resolveLocalPathInput(options.filePath, "file path");
58
64
  const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
59
65
  for (const rootDir of rootDirs) {
60
66
  const rootReal = resolveRootRealSync(rootDir);
@@ -69,6 +75,7 @@ export function resolveLocalPathFromRootsSync(options) {
69
75
  rootCanonicalPath: rootReal,
70
76
  boundaryLabel: label,
71
77
  rejectUnresolvedSymlinks: true,
78
+ policy: options.requireFile ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
72
79
  });
73
80
  }
74
81
  catch {
@@ -77,11 +84,12 @@ export function resolveLocalPathFromRootsSync(options) {
77
84
  if (!candidate.exists && options.allowMissing !== true) {
78
85
  continue;
79
86
  }
87
+ if (candidate.exists && options.requireFile === true && candidate.kind !== "file")
88
+ continue;
80
89
  if (candidate.exists && options.requireFile === true) {
81
90
  try {
82
- if (!fsSync.lstatSync(requestedPath).isFile()) {
91
+ if (!fsSync.lstatSync(requestedPath).isFile())
83
92
  continue;
84
- }
85
93
  }
86
94
  catch {
87
95
  continue;
@@ -94,7 +102,7 @@ export function resolveLocalPathFromRootsSync(options) {
94
102
  export async function readLocalFileFromRoots(options) {
95
103
  const maxBytes = normalizeMaxBytes(options.maxBytes);
96
104
  const label = options.label ?? "local roots";
97
- const requestedPath = path.resolve(resolveLocalPathInput(options.filePath, "file path"));
105
+ const requestedPath = resolveLocalPathInput(options.filePath, "file path");
98
106
  const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
99
107
  for (const rootDir of rootDirs) {
100
108
  let scopedRoot;
@@ -113,22 +121,12 @@ export async function readLocalFileFromRoots(options) {
113
121
  if (maxBytes !== undefined) {
114
122
  readOptions.maxBytes = maxBytes;
115
123
  }
116
- // A trusted root symlink has two valid spellings. Preserve the caller's
117
- // lexical spelling when possible so Root.read() still enforces its
118
- // symlink policy, while also accepting a path expressed below rootReal.
119
- const relativePaths = [scopedRoot.rootDir, scopedRoot.rootReal]
120
- .filter((rootPath, index, roots) => roots.indexOf(rootPath) === index)
121
- .filter((rootPath) => isPathInside(rootPath, requestedPath))
122
- .map((rootPath) => path.relative(rootPath, requestedPath))
123
- .filter(Boolean);
124
- for (const relativePath of relativePaths) {
125
- try {
126
- const result = await scopedRoot.read(relativePath, readOptions);
127
- return { ...result, root: scopedRoot.rootReal };
128
- }
129
- catch {
130
- // Try the canonical spelling before moving to the next configured root.
131
- }
124
+ try {
125
+ const result = await scopedRoot.readAbsolute(requestedPath, readOptions);
126
+ return { ...result, root: scopedRoot.rootReal };
127
+ }
128
+ catch {
129
+ // Root handles both trusted spellings; try the next configured root.
132
130
  }
133
131
  }
134
132
  return null;
@@ -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,EAC9B,oBAAoB,EAAE,MAAM,IAAI,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CAyDnC"}
1
+ {"version":3,"file":"move-path-cleanup.d.ts","sourceRoot":"","sources":["../src/move-path-cleanup.ts"],"names":[],"mappings":"AAIA,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"}
@@ -1,3 +1,4 @@
1
+ import fsSync from "node:fs";
1
2
  import fs from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  export function entryIdentity(stat) {
@@ -29,7 +30,7 @@ export function sourceChangedError(sourcePath) {
29
30
  });
30
31
  }
31
32
  export async function assertSourceStillMatches(sourcePath, identity) {
32
- if (!sameIdentity(identity, entryIdentity(await fs.lstat(sourcePath)))) {
33
+ if (!sameIdentity(identity, entryIdentity(fsSync.lstatSync(sourcePath)))) {
33
34
  throw sourceChangedError(sourcePath);
34
35
  }
35
36
  }
@@ -93,7 +94,7 @@ async function observeOwnedAliasUnlink(sourcePath, group) {
93
94
  }
94
95
  let observed;
95
96
  try {
96
- observed = await fs.lstat(remainingPath);
97
+ observed = fsSync.lstatSync(remainingPath);
97
98
  }
98
99
  catch (error) {
99
100
  if (error?.code === "ENOENT") {
@@ -118,7 +119,7 @@ export async function cleanupCopiedEntry(sourcePath, manifest, state, assertBefo
118
119
  }
119
120
  let currentStat;
120
121
  try {
121
- currentStat = await fs.lstat(sourcePath);
122
+ currentStat = fsSync.lstatSync(sourcePath);
122
123
  }
123
124
  catch (error) {
124
125
  if (error?.code === "ENOENT") {
@@ -0,0 +1,8 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ export declare function createMoveStageOwner(staged: string): Promise<{
3
+ record(created: BigIntStats): void;
4
+ assertCurrent: () => void;
5
+ published(): void;
6
+ cleanup(): Promise<void>;
7
+ }>;
8
+ //# sourceMappingURL=move-path-stage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"move-path-stage.d.ts","sourceRoot":"","sources":["../src/move-path-stage.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAQnD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM;oBAkBrC,WAAW,GAAG,IAAI;yBAPV,IAAI;iBAkBf,IAAI;eACA,OAAO,CAAC,IAAI,CAAC;GAajC"}
@@ -0,0 +1,55 @@
1
+ import fsSync, {} from "node:fs";
2
+ import path from "node:path";
3
+ import { createAsyncDirectoryGuard } from "./directory-guard.js";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { guardedRm, guardedRmSync } from "./guarded-mutation.js";
6
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
7
+ import { registerTempPathForExit } from "./temp-cleanup.js";
8
+ export async function createMoveStageOwner(staged) {
9
+ const parent = await createAsyncDirectoryGuard(path.dirname(staged), { bigint: true });
10
+ let identity;
11
+ let unregister;
12
+ const changed = () => new FsSafeError("path-mismatch", "move staging path changed before publication");
13
+ const assertParent = () => {
14
+ const current = fsSync.lstatSync(parent.dir, { bigint: true });
15
+ if (!current.isDirectory() || current.isSymbolicLink() ||
16
+ !sameFileIdentityForCleanup(current, parent.stat) ||
17
+ fsSync.realpathSync.native(parent.dir) !== parent.realPath)
18
+ throw changed();
19
+ };
20
+ const assertCurrent = () => {
21
+ assertParent();
22
+ if (!identity || !sameFileIdentityForCleanup(fsSync.lstatSync(staged, { bigint: true }), identity)) {
23
+ throw changed();
24
+ }
25
+ };
26
+ return {
27
+ record(created) {
28
+ if (!sameFileIdentityForCleanup(created, created))
29
+ throw changed();
30
+ identity = created;
31
+ unregister = registerTempPathForExit(staged, {
32
+ identity,
33
+ cleanupSync: () => {
34
+ guardedRmSync({ target: staged, recursive: created.isDirectory(), assertBeforeMutation: assertCurrent });
35
+ },
36
+ });
37
+ },
38
+ assertCurrent,
39
+ published() { unregister?.(); },
40
+ async cleanup() {
41
+ if (!identity)
42
+ return;
43
+ try {
44
+ await guardedRm({ target: staged, recursive: identity.isDirectory(), assertBeforeMutation: assertCurrent });
45
+ unregister?.();
46
+ }
47
+ catch (error) {
48
+ if (error.code === "ENOENT" || error instanceof FsSafeError) {
49
+ unregister?.();
50
+ }
51
+ // Retry only the original receipt after transient cleanup I/O failures.
52
+ }
53
+ },
54
+ };
55
+ }
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAsBA,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;AAiRD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAwGf"}
package/dist/move-path.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { constants as fsConstants } from "node:fs";
2
+ import fsSync, { constants as fsConstants } from "node:fs";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
+ import { createAsyncDirectoryGuard } from "./directory-guard.js";
5
6
  import { FsSafeError } from "./errors.js";
6
7
  import { guardedRename } from "./guarded-mutation.js";
7
8
  import { assertSourceStillMatches, cleanupCopiedEntry, createCleanupCopiedEntryState, entryIdentity, sameIdentity, sourceChangedError, } from "./move-path-cleanup.js";
8
9
  import { resolveReadOpenFlags } from "./read-open-flags.js";
9
- import { registerTempPathForExit } from "./temp-cleanup.js";
10
+ import { cleanupPinnedFilePath } from "./replace-file-temp-owner.js";
11
+ import { createMoveStageOwner } from "./move-path-stage.js";
10
12
  export function moveCopyFallbackReasonForRenameError(error, platform = process.platform) {
11
13
  const code = error?.code;
12
14
  if (code === "EXDEV") {
@@ -29,7 +31,7 @@ async function preflightSourceHardlinks(sourcePath) {
29
31
  let discovered = 1;
30
32
  while (pending.length > 0) {
31
33
  const current = pending.pop();
32
- const stat = await fs.lstat(current);
34
+ const stat = fsSync.lstatSync(current);
33
35
  if (stat.isFile() && stat.nlink > 1) {
34
36
  throw hardlinkedSourceError(current);
35
37
  }
@@ -52,21 +54,19 @@ function isSameOrDescendant(parentPath, candidatePath) {
52
54
  (!path.isAbsolute(relative) && !relative.startsWith(`..${path.sep}`) && relative !== ".."));
53
55
  }
54
56
  async function assertCopyDestinationOutsideSource(sourcePath, targetPath, expectedIdentity) {
55
- const sourceStat = await fs.lstat(sourcePath);
57
+ const sourceStat = fsSync.lstatSync(sourcePath);
56
58
  const sourceIdentity = entryIdentity(sourceStat);
57
59
  if (expectedIdentity && !sameIdentity(expectedIdentity, sourceIdentity)) {
58
60
  throw sourceChangedError(sourcePath);
59
61
  }
60
62
  const normalizedSource = path.resolve(sourcePath);
61
63
  const normalizedTarget = path.resolve(targetPath);
62
- const [sourceParentReal, targetParentReal] = await Promise.all([
63
- fs.realpath(path.dirname(normalizedSource)),
64
- fs.realpath(path.dirname(normalizedTarget)),
65
- ]);
64
+ const sourceParentReal = fsSync.realpathSync.native(path.dirname(normalizedSource));
65
+ const targetParentReal = fsSync.realpathSync.native(path.dirname(normalizedTarget));
66
66
  const sourceCandidate = path.join(sourceParentReal, path.basename(normalizedSource));
67
67
  const targetCandidate = path.join(targetParentReal, path.basename(normalizedTarget));
68
68
  const sourceBoundary = sourceStat.isDirectory()
69
- ? await fs.realpath(sourcePath)
69
+ ? fsSync.realpathSync.native(sourcePath)
70
70
  : sourceCandidate;
71
71
  const unsafeTarget = sourceStat.isDirectory()
72
72
  ? isSameOrDescendant(sourceBoundary, targetCandidate)
@@ -98,11 +98,13 @@ async function writeAll(handle, buffer, bytesRead) {
98
98
  let offset = 0;
99
99
  while (offset < bytesRead) {
100
100
  const { bytesWritten } = await handle.write(buffer, offset, bytesRead - offset);
101
+ if (bytesWritten <= 0)
102
+ throw new FsSafeError("helper-failed", "move copy made no progress");
101
103
  offset += bytesWritten;
102
104
  }
103
105
  }
104
106
  async function copyRegularFilePinned(params) {
105
- let destinationCreated = false;
107
+ let openedIdentity;
106
108
  let sourceHandle;
107
109
  try {
108
110
  sourceHandle = await fs.open(params.from, resolveReadOpenFlags());
@@ -115,16 +117,30 @@ async function copyRegularFilePinned(params) {
115
117
  throw error;
116
118
  }
117
119
  try {
118
- const openedStat = await sourceHandle.stat();
120
+ const openedStat = fsSync.fstatSync(sourceHandle.fd);
121
+ openedIdentity = entryIdentity(openedStat);
119
122
  if (params.rejectHardlinks && openedStat.nlink > 1) {
120
123
  throw hardlinkedSourceError(params.from);
121
124
  }
122
- if (!openedStat.isFile() || !sameIdentity(params.identity, entryIdentity(openedStat))) {
125
+ const openAdvancedWindowsCtime = process.platform === "win32" &&
126
+ params.identity.dev === openedIdentity.dev &&
127
+ params.identity.ino === openedIdentity.ino &&
128
+ params.identity.mode === openedIdentity.mode &&
129
+ params.identity.nlink === openedIdentity.nlink &&
130
+ params.identity.size === openedIdentity.size &&
131
+ params.identity.mtimeMs === openedIdentity.mtimeMs &&
132
+ openedIdentity.ctimeMs >= params.identity.ctimeMs;
133
+ if (!openedStat.isFile() ||
134
+ (!sameIdentity(params.identity, openedIdentity) && !openAdvancedWindowsCtime)) {
123
135
  throw sourceChangedError(params.from);
124
136
  }
137
+ await assertSourceStillMatches(params.from, openedIdentity);
138
+ const parentGuard = await createAsyncDirectoryGuard(path.dirname(params.to), { bigint: true });
125
139
  const destinationHandle = await fs.open(params.to, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, modeBits(params.mode) || 0o666);
126
- destinationCreated = true;
140
+ let destinationIdentity;
127
141
  try {
142
+ destinationIdentity = fsSync.fstatSync(destinationHandle.fd, { bigint: true });
143
+ params.onCreated?.(destinationIdentity);
128
144
  const scratch = Buffer.allocUnsafe(64 * 1024);
129
145
  while (true) {
130
146
  const { bytesRead } = await sourceHandle.read(scratch, 0, scratch.length, null);
@@ -135,37 +151,41 @@ async function copyRegularFilePinned(params) {
135
151
  }
136
152
  // Re-check the opened source before the staged tree can be committed. If
137
153
  // it changed while we copied, the caller should retry the move.
138
- const finalSourceStat = await sourceHandle.stat();
154
+ const finalSourceStat = fsSync.fstatSync(sourceHandle.fd);
139
155
  if (params.rejectHardlinks && finalSourceStat.nlink > 1) {
140
156
  throw hardlinkedSourceError(params.from);
141
157
  }
142
- if (!sameIdentity(params.identity, entryIdentity(finalSourceStat))) {
158
+ if (!sameIdentity(openedIdentity, entryIdentity(finalSourceStat))) {
143
159
  throw sourceChangedError(params.from);
144
160
  }
145
161
  await destinationHandle.chmod(modeBits(params.mode));
146
162
  }
163
+ catch (error) {
164
+ await cleanupPinnedFilePath({
165
+ pathname: params.to, handle: destinationHandle, identity: destinationIdentity, parentGuard,
166
+ });
167
+ throw error;
168
+ }
147
169
  finally {
148
170
  await destinationHandle.close();
149
171
  }
150
172
  }
151
- catch (error) {
152
- if (destinationCreated) {
153
- await fs.rm(params.to, { force: true }).catch(() => undefined);
154
- }
155
- throw error;
156
- }
157
173
  finally {
158
174
  await sourceHandle.close();
159
175
  }
176
+ return openedIdentity;
160
177
  }
161
178
  async function copyEntryWithManifest(from, to, options, expectedIdentity) {
162
- const sourceStat = await fs.lstat(from);
179
+ const sourceStat = fsSync.lstatSync(from);
163
180
  const identity = entryIdentity(sourceStat);
164
181
  if (expectedIdentity && !sameIdentity(expectedIdentity, identity)) {
165
182
  throw sourceChangedError(from);
166
183
  }
167
184
  if (sourceStat.isSymbolicLink()) {
168
- await fs.symlink(await fs.readlink(from), to);
185
+ const target = await fs.readlink(from);
186
+ const targetType = process.platform === "win32" && fsSync.statSync(from).isDirectory() ? "junction" : undefined;
187
+ await fs.symlink(target, to, targetType);
188
+ options.onCreated?.(fsSync.lstatSync(to, { bigint: true }));
169
189
  // readlink() is path-based; verify the symlink we copied is still the one
170
190
  // we inspected before letting the staged destination become visible.
171
191
  await assertSourceStillMatches(from, identity);
@@ -173,6 +193,7 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
173
193
  }
174
194
  if (sourceStat.isDirectory()) {
175
195
  await fs.mkdir(to, { mode: modeBits(sourceStat.mode) || 0o755 });
196
+ options.onCreated?.(fsSync.lstatSync(to, { bigint: true }));
176
197
  const children = [];
177
198
  const childNames = [];
178
199
  const directory = await fs.opendir(from);
@@ -185,7 +206,9 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
185
206
  for (const child of childNames) {
186
207
  children.push({
187
208
  name: child,
188
- manifest: await copyEntryWithManifest(path.join(from, child), path.join(to, child), options),
209
+ manifest: await copyEntryWithManifest(path.join(from, child), path.join(to, child), {
210
+ sourceHardlinks: options.sourceHardlinks, budget: options.budget,
211
+ }),
189
212
  });
190
213
  }
191
214
  // Directory traversal is path-based in Node. Treat a changed parent as a
@@ -202,14 +225,15 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
202
225
  if (options.sourceHardlinks === "reject" && sourceStat.nlink > 1) {
203
226
  throw hardlinkedSourceError(from);
204
227
  }
205
- await copyRegularFilePinned({
228
+ const copiedIdentity = await copyRegularFilePinned({
206
229
  from,
207
230
  identity,
208
231
  mode: sourceStat.mode,
209
232
  rejectHardlinks: options.sourceHardlinks === "reject",
210
233
  to,
234
+ onCreated: options.onCreated,
211
235
  });
212
- return { ...identity, kind: "leaf" };
236
+ return { ...copiedIdentity, kind: "leaf" };
213
237
  }
214
238
  function assertSynchronousResult(returned, name) {
215
239
  if (returned !== undefined) {
@@ -286,22 +310,22 @@ export async function movePathWithCopyFallback(options) {
286
310
  const sourceIdentity = await assertCopyDestinationOutsideSource(sourcePath, targetPath);
287
311
  const targetDir = path.dirname(targetPath);
288
312
  const staged = path.join(targetDir, `.fs-safe-move-${process.pid}-${randomUUID()}.tmp`);
289
- const unregisterStaged = registerTempPathForExit(staged, { recursive: true });
313
+ const stage = await createMoveStageOwner(staged);
290
314
  try {
291
315
  const manifest = await copyEntryWithManifest(sourcePath, staged, {
292
316
  sourceHardlinks: rejectHardlinks ? "reject" : "allow",
317
+ onCreated: stage.record,
293
318
  ...(rejectHardlinks ? { budget: { discovered: 1 } } : {}),
294
319
  }, sourceIdentity);
295
320
  const cleanupState = createCleanupCopiedEntryState(sourcePath, manifest);
296
- unregisterStaged.setIdentity(await fs.lstat(staged, { bigint: true }));
297
321
  await assertCopyDestinationOutsideSource(sourcePath, targetPath, manifest);
298
322
  await guardedRename({
299
323
  from: staged,
300
324
  to: targetPath,
301
- assertBeforeRename,
325
+ assertBeforeRename: () => { assertBeforeRename(); stage.assertCurrent(); },
302
326
  onSourceInspected,
303
327
  onRenamed: () => {
304
- unregisterStaged();
328
+ stage.published();
305
329
  onRenamed();
306
330
  },
307
331
  });
@@ -312,23 +336,7 @@ export async function movePathWithCopyFallback(options) {
312
336
  }
313
337
  finally {
314
338
  if (!destinationPublished) {
315
- try {
316
- const stagedIdentity = await fs.lstat(staged, { bigint: true });
317
- if (!stagedIdentity.isSymbolicLink())
318
- unregisterStaged.setIdentity(stagedIdentity);
319
- }
320
- catch (error) {
321
- if (error.code === "ENOENT") {
322
- unregisterStaged();
323
- }
324
- }
325
- try {
326
- await fs.rm(staged, { recursive: true, force: true });
327
- unregisterStaged();
328
- }
329
- catch {
330
- // Keep the identity-bound exit cleanup registered for a later retry.
331
- }
339
+ await stage.cleanup();
332
340
  }
333
341
  }
334
342
  }
@@ -0,0 +1,8 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ export declare class MutationAuthorityError extends FsSafeError {
3
+ readonly rejection: unknown;
4
+ constructor(rejection: unknown);
5
+ }
6
+ export declare function composeMutationAssertions(defaultAssertion?: () => void, callAssertion?: () => void): (() => void) | undefined;
7
+ export declare function rethrowMutationAuthorityError(error: unknown): never;
8
+ //# sourceMappingURL=mutation-authority.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation-authority.d.ts","sourceRoot":"","sources":["../src/mutation-authority.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,qBAAa,sBAAuB,SAAQ,WAAW;IACzC,QAAQ,CAAC,SAAS,EAAE,OAAO;IAAvC,YAAqB,SAAS,EAAE,OAAO,EAEtC;CACF;AAcD,wBAAgB,yBAAyB,CACvC,gBAAgB,CAAC,EAAE,MAAM,IAAI,EAC7B,aAAa,CAAC,EAAE,MAAM,IAAI,GACzB,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAU1B;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAInE"}
@@ -0,0 +1,36 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ export class MutationAuthorityError extends FsSafeError {
3
+ rejection;
4
+ constructor(rejection) {
5
+ super("denied-path", "mutation authority rejected the operation", { cause: rejection });
6
+ this.rejection = rejection;
7
+ }
8
+ }
9
+ function assertSynchronous(returned) {
10
+ if (returned !== null &&
11
+ (typeof returned === "object" || typeof returned === "function") &&
12
+ typeof returned.then === "function") {
13
+ // TypeScript permits async functions for () => void. Do not admit their work.
14
+ void Promise.resolve(returned).catch(() => undefined);
15
+ throw new TypeError("assertBeforeMutation must be synchronous");
16
+ }
17
+ }
18
+ export function composeMutationAssertions(defaultAssertion, callAssertion) {
19
+ if (!defaultAssertion && !callAssertion)
20
+ return undefined;
21
+ return () => {
22
+ try {
23
+ assertSynchronous(defaultAssertion?.());
24
+ assertSynchronous(callAssertion?.());
25
+ }
26
+ catch (error) {
27
+ throw new MutationAuthorityError(error);
28
+ }
29
+ };
30
+ }
31
+ export function rethrowMutationAuthorityError(error) {
32
+ if (error instanceof MutationAuthorityError)
33
+ throw error.rejection;
34
+ // Cleanup failures and indeterminate publication receipts must retain their context.
35
+ throw error;
36
+ }
@@ -1,5 +1,6 @@
1
1
  import type { TarMeterLimits } from "./archive-limits.js";
2
2
  import type { ArchiveMemberKind } from "./archive-plan.js";
3
+ import type { CopyCloneMode } from "./copy-policy.js";
3
4
  export interface NativeFileHash {
4
5
  bytes: number;
5
6
  digest: string;
@@ -29,6 +30,12 @@ export interface NativeCopyResult {
29
30
  errorCode?: string;
30
31
  errorMessage?: string;
31
32
  }
33
+ export interface NativeFileCopyResult {
34
+ fd: number;
35
+ method: "clone" | "copy-file-range" | "copy";
36
+ errorCode?: string;
37
+ errorMessage?: string;
38
+ }
32
39
  export interface NativeOpenBeneathResult {
33
40
  fd: number;
34
41
  containment: "kernel-atomic" | "best-effort";
@@ -69,9 +76,25 @@ export interface NativeWindowsSecurityFacts {
69
76
  aces: NativeWindowsAccessControlEntry[];
70
77
  }
71
78
  export interface NativeBinding {
79
+ /** Windows-only byte transfer; callers retain both admitted descriptors until settlement. */
80
+ copyFileContents?(sourceFd: number, targetFd: number, signal?: AbortSignal): Promise<void>;
81
+ /** Internal: same private, immutable input ownership as the ZIP buffer reader. */
82
+ openTarBufferNative(buffer: Buffer, kind: string, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
83
+ readonly entries: NativeArchiveEntry[];
84
+ readEntry(index: number, maxBytes: number, signal?: AbortSignal): Promise<Buffer>;
85
+ }>;
86
+ /** Internal: input remains private, unpooled and immutable until the reader is released. */
87
+ openZipBufferNative(buffer: Buffer, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
88
+ readonly entries: NativeArchiveEntry[];
89
+ readEntry(index: number, maxBytes: number, signal?: AbortSignal): Promise<Buffer>;
90
+ }>;
91
+ readCloneFileMetadata(paths: string[]): Promise<(Buffer | null)[]>;
92
+ probeTreeClone(parentFd: number): "apfs" | "btrfs" | "refs" | "xfs" | null;
93
+ cloneTree(sourceFd: number | null, parentFd: number, basename: string, concurrency: number, signal?: AbortSignal): Promise<void>;
72
94
  createStagedFile?(parentFd: number, basename: string): number;
73
95
  stagedFileMatches?(parentFd: number, basename: string, fileFd: number): boolean;
74
96
  removeStagedFile?(parentFd: number, basename: string, fileFd: number): "removed" | "name-absent" | "preserved";
97
+ copyFileExclusive?(sourceFd: number, parentFd: number, basename: string, clone: CopyCloneMode, maxBytes: number | undefined, signal: AbortSignal | undefined, sync: boolean): Promise<NativeFileCopyResult>;
75
98
  cloneFileExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): number;
76
99
  copyFileRangeExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): Promise<NativeCopyResult>;
77
100
  createPrivateDirectory(path: string): void;
@@ -88,6 +111,6 @@ export interface NativeBinding {
88
111
  removeOwnedTreeSync?(parentFd: number, basename: string, directoryFd: number): NativeOwnedTreeRemovalResult;
89
112
  renameNoReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
90
113
  renameReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
91
- sha256File(fd: number): Promise<NativeFileHash>;
114
+ sha256File(fd: number, maxBytes?: number, signal?: AbortSignal): Promise<NativeFileHash>;
92
115
  }
93
116
  //# sourceMappingURL=native-binding.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@ export type NativeFileHandle = {
10
10
  };
11
11
  export declare function nativeOpenFlags(flags: number): number;
12
12
  export declare function writeNativeFd(fd: number, data: Buffer): void;
13
- export declare function writeNativeInput(fd: number, input: PinnedWriteInput, maxBytes?: number): Promise<void>;
13
+ export declare function writeNativeInput(fd: number, input: PinnedWriteInput, maxBytes?: number, assertBeforeMutation?: () => void): Promise<void>;
14
14
  export declare function removeNativeCreatedFileIfStillPinned(params: {
15
15
  parentPath: string;
16
16
  parentFd: number;
@@ -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"}
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;AAI1D,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,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAoBf;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"}
@@ -4,6 +4,8 @@ import path from "node:path";
4
4
  import { FsSafeError } from "./errors.js";
5
5
  import { sameFileIdentityForCleanup } from "./file-identity.js";
6
6
  import { getNativeBinding } from "./native.js";
7
+ import { writeAllToFile } from "./write-file-handle.js";
8
+ import { writeCopyFileToFd } from "./copy-file-input.js";
7
9
  export function nativeOpenFlags(flags) {
8
10
  const closeOnExec = fsSync.constants.O_CLOEXEC;
9
11
  return (flags |
@@ -20,21 +22,25 @@ export function writeNativeFd(fd, data) {
20
22
  offset += written;
21
23
  }
22
24
  }
23
- export async function writeNativeInput(fd, input, maxBytes) {
25
+ export async function writeNativeInput(fd, input, maxBytes, assertBeforeMutation) {
26
+ if (input.kind === "file") {
27
+ await writeCopyFileToFd(fd, input, maxBytes, assertBeforeMutation);
28
+ return;
29
+ }
24
30
  let bytes = 0;
25
- const write = (data) => {
31
+ const write = async (data) => {
26
32
  bytes += data.byteLength;
27
33
  if (maxBytes !== undefined && bytes > maxBytes) {
28
34
  throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
29
35
  }
30
- writeNativeFd(fd, data);
36
+ await writeAllToFile(fd, data, { assertBeforeMutation });
31
37
  };
32
38
  if (input.kind === "buffer") {
33
- write(typeof input.data === "string" ? Buffer.from(input.data, input.encoding ?? "utf8") : input.data);
39
+ await write(typeof input.data === "string" ? Buffer.from(input.data, input.encoding ?? "utf8") : input.data);
34
40
  }
35
41
  else {
36
42
  for await (const chunk of input.stream) {
37
- write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
43
+ await write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
38
44
  }
39
45
  }
40
46
  }