@openclaw/fs-safe 0.10.0 → 0.11.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 (259) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +1 -0
  3. package/README.md +36 -5
  4. package/dist/absolute-path.d.ts.map +1 -1
  5. package/dist/absolute-path.js +3 -2
  6. package/dist/advanced.d.ts +4 -0
  7. package/dist/advanced.d.ts.map +1 -1
  8. package/dist/advanced.js +4 -0
  9. package/dist/archive-durability.d.ts +6 -6
  10. package/dist/archive-durability.d.ts.map +1 -1
  11. package/dist/archive-durability.js +1 -1
  12. package/dist/archive-entry.d.ts.map +1 -1
  13. package/dist/archive-entry.js +4 -5
  14. package/dist/archive-gzip-tail.d.ts +1 -0
  15. package/dist/archive-gzip-tail.d.ts.map +1 -1
  16. package/dist/archive-gzip-tail.js +3 -0
  17. package/dist/archive-input.d.ts.map +1 -1
  18. package/dist/archive-input.js +4 -2
  19. package/dist/archive-merge.d.ts +5 -1
  20. package/dist/archive-merge.d.ts.map +1 -1
  21. package/dist/archive-merge.js +15 -12
  22. package/dist/archive-native.js +4 -4
  23. package/dist/archive-parser.wasm +0 -0
  24. package/dist/archive-read.d.ts.map +1 -1
  25. package/dist/archive-read.js +17 -9
  26. package/dist/archive-staging.d.ts +6 -3
  27. package/dist/archive-staging.d.ts.map +1 -1
  28. package/dist/archive-staging.js +42 -22
  29. package/dist/archive-tar-stream.d.ts.map +1 -1
  30. package/dist/archive-tar-stream.js +7 -6
  31. package/dist/archive-tar-wasm.d.ts.map +1 -1
  32. package/dist/archive-tar-wasm.js +16 -13
  33. package/dist/archive-zip-admission.d.ts +1 -1
  34. package/dist/archive-zip-admission.d.ts.map +1 -1
  35. package/dist/archive-zip-admission.js +48 -12
  36. package/dist/archive-zip-loader.d.ts +6 -0
  37. package/dist/archive-zip-loader.d.ts.map +1 -0
  38. package/dist/archive-zip-loader.js +38 -0
  39. package/dist/archive-zip-names.d.ts.map +1 -1
  40. package/dist/archive-zip-names.js +13 -8
  41. package/dist/archive-zip-preflight.d.ts +2 -3
  42. package/dist/archive-zip-preflight.d.ts.map +1 -1
  43. package/dist/archive-zip-preflight.js +2 -34
  44. package/dist/archive.d.ts.map +1 -1
  45. package/dist/archive.js +12 -10
  46. package/dist/bounded-read.d.ts +5 -0
  47. package/dist/bounded-read.d.ts.map +1 -1
  48. package/dist/bounded-read.js +16 -9
  49. package/dist/copy-file-input.d.ts +0 -1
  50. package/dist/copy-file-input.d.ts.map +1 -1
  51. package/dist/copy-file-input.js +4 -26
  52. package/dist/copy-tree-portable.d.ts.map +1 -1
  53. package/dist/copy-tree-portable.js +57 -26
  54. package/dist/copy.d.ts +1 -1
  55. package/dist/copy.d.ts.map +1 -1
  56. package/dist/copy.js +3 -1
  57. package/dist/directory-durability.d.ts.map +1 -1
  58. package/dist/directory-durability.js +5 -4
  59. package/dist/directory-guard.d.ts +11 -1
  60. package/dist/directory-guard.d.ts.map +1 -1
  61. package/dist/directory-guard.js +53 -11
  62. package/dist/durability.d.ts +1 -1
  63. package/dist/durability.d.ts.map +1 -1
  64. package/dist/durability.js +1 -1
  65. package/dist/file-handle-transfer.d.ts +14 -0
  66. package/dist/file-handle-transfer.d.ts.map +1 -0
  67. package/dist/file-handle-transfer.js +64 -0
  68. package/dist/file-hash.d.ts +3 -0
  69. package/dist/file-hash.d.ts.map +1 -1
  70. package/dist/file-hash.js +91 -31
  71. package/dist/file-lock-sync.d.ts.map +1 -1
  72. package/dist/file-lock-sync.js +8 -4
  73. package/dist/file-store-boundary.d.ts.map +1 -1
  74. package/dist/file-store-boundary.js +7 -5
  75. package/dist/file-store-path.d.ts +3 -0
  76. package/dist/file-store-path.d.ts.map +1 -0
  77. package/dist/file-store-path.js +27 -0
  78. package/dist/file-store-prune.d.ts.map +1 -1
  79. package/dist/file-store-prune.js +6 -4
  80. package/dist/file-store-sync-write.d.ts.map +1 -1
  81. package/dist/file-store-sync-write.js +56 -44
  82. package/dist/file-store.d.ts.map +1 -1
  83. package/dist/file-store.js +2 -18
  84. package/dist/filename.d.ts.map +1 -1
  85. package/dist/filename.js +2 -1
  86. package/dist/guarded-mkdir.d.ts.map +1 -1
  87. package/dist/guarded-mkdir.js +3 -2
  88. package/dist/guest-dispatch-python.d.ts +2 -0
  89. package/dist/guest-dispatch-python.d.ts.map +1 -0
  90. package/dist/guest-dispatch-python.js +117 -0
  91. package/dist/guest-native-python.d.ts +4 -0
  92. package/dist/guest-native-python.d.ts.map +1 -0
  93. package/dist/guest-native-python.js +135 -0
  94. package/dist/guest.d.ts +9 -0
  95. package/dist/guest.d.ts.map +1 -0
  96. package/dist/guest.js +413 -0
  97. package/dist/index.d.ts +1 -1
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/install-path.d.ts.map +1 -1
  100. package/dist/install-path.js +3 -2
  101. package/dist/json-durable-queue-directory.js +3 -3
  102. package/dist/json-durable-queue.d.ts.map +1 -1
  103. package/dist/json-durable-queue.js +6 -4
  104. package/dist/json.d.ts.map +1 -1
  105. package/dist/json.js +2 -1
  106. package/dist/local-roots.d.ts.map +1 -1
  107. package/dist/local-roots.js +2 -1
  108. package/dist/move-path-stage.d.ts.map +1 -1
  109. package/dist/move-path-stage.js +2 -1
  110. package/dist/move-path.d.ts.map +1 -1
  111. package/dist/move-path.js +4 -3
  112. package/dist/mutation-authority.d.ts +1 -0
  113. package/dist/mutation-authority.d.ts.map +1 -1
  114. package/dist/mutation-authority.js +4 -4
  115. package/dist/native-binding.d.ts +7 -2
  116. package/dist/native-binding.d.ts.map +1 -1
  117. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  118. package/dist/native-pinned-write-windows.js +3 -2
  119. package/dist/native-pinned-write.d.ts.map +1 -1
  120. package/dist/native-pinned-write.js +2 -1
  121. package/dist/opened-realpath.d.ts.map +1 -1
  122. package/dist/opened-realpath.js +5 -4
  123. package/dist/output.d.ts +2 -0
  124. package/dist/output.d.ts.map +1 -1
  125. package/dist/output.js +2 -0
  126. package/dist/overwrite-file-handle.d.ts +8 -0
  127. package/dist/overwrite-file-handle.d.ts.map +1 -0
  128. package/dist/overwrite-file-handle.js +42 -0
  129. package/dist/path-case.d.ts +7 -0
  130. package/dist/path-case.d.ts.map +1 -0
  131. package/dist/path-case.js +136 -0
  132. package/dist/path.d.ts.map +1 -1
  133. package/dist/path.js +2 -1
  134. package/dist/permissions-windows.d.ts +1 -1
  135. package/dist/permissions-windows.d.ts.map +1 -1
  136. package/dist/permissions-windows.js +48 -6
  137. package/dist/pinned-open.d.ts.map +1 -1
  138. package/dist/pinned-open.js +3 -1
  139. package/dist/pinned-write.d.ts +2 -2
  140. package/dist/pinned-write.d.ts.map +1 -1
  141. package/dist/pinned-write.js +2 -1
  142. package/dist/private-temp-workspace.d.ts.map +1 -1
  143. package/dist/private-temp-workspace.js +6 -4
  144. package/dist/realpath.d.ts +4 -0
  145. package/dist/realpath.d.ts.map +1 -0
  146. package/dist/realpath.js +43 -0
  147. package/dist/recursive-mkdir-path.d.ts +3 -0
  148. package/dist/recursive-mkdir-path.d.ts.map +1 -0
  149. package/dist/recursive-mkdir-path.js +8 -0
  150. package/dist/replace-directory.d.ts.map +1 -1
  151. package/dist/replace-directory.js +2 -1
  152. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  153. package/dist/replace-file-copy-fallback.js +23 -31
  154. package/dist/replace-file-copy-source.d.ts.map +1 -1
  155. package/dist/replace-file-copy-source.js +7 -12
  156. package/dist/replace-file-mode.d.ts +3 -0
  157. package/dist/replace-file-mode.d.ts.map +1 -0
  158. package/dist/replace-file-mode.js +10 -0
  159. package/dist/replace-file.d.ts +1 -0
  160. package/dist/replace-file.d.ts.map +1 -1
  161. package/dist/replace-file.js +14 -8
  162. package/dist/root-context.d.ts.map +1 -1
  163. package/dist/root-context.js +8 -8
  164. package/dist/root-create-input.d.ts +10 -0
  165. package/dist/root-create-input.d.ts.map +1 -0
  166. package/dist/root-create-input.js +80 -0
  167. package/dist/root-directory-list.d.ts +3 -1
  168. package/dist/root-directory-list.d.ts.map +1 -1
  169. package/dist/root-directory-list.js +21 -3
  170. package/dist/root-entries.d.ts +11 -0
  171. package/dist/root-entries.d.ts.map +1 -0
  172. package/dist/root-entries.js +61 -0
  173. package/dist/root-errors.d.ts +5 -5
  174. package/dist/root-errors.d.ts.map +1 -1
  175. package/dist/root-errors.js +13 -12
  176. package/dist/root-impl.d.ts +13 -3
  177. package/dist/root-impl.d.ts.map +1 -1
  178. package/dist/root-impl.js +40 -36
  179. package/dist/root-options.d.ts +12 -1
  180. package/dist/root-options.d.ts.map +1 -1
  181. package/dist/root-path-existing.d.ts.map +1 -1
  182. package/dist/root-path-existing.js +4 -3
  183. package/dist/root-path-symlink.d.ts.map +1 -1
  184. package/dist/root-path-symlink.js +3 -2
  185. package/dist/root-paths.d.ts.map +1 -1
  186. package/dist/root-paths.js +13 -9
  187. package/dist/root-remove.d.ts +5 -0
  188. package/dist/root-remove.d.ts.map +1 -0
  189. package/dist/root-remove.js +286 -0
  190. package/dist/root-symlink-policy.d.ts +2 -1
  191. package/dist/root-symlink-policy.d.ts.map +1 -1
  192. package/dist/root-symlink-policy.js +2 -2
  193. package/dist/root-write-mode.d.ts.map +1 -1
  194. package/dist/root-write-mode.js +2 -1
  195. package/dist/root.d.ts +2 -1
  196. package/dist/root.d.ts.map +1 -1
  197. package/dist/secret-file.d.ts.map +1 -1
  198. package/dist/secret-file.js +2 -1
  199. package/dist/secret-read-async.d.ts.map +1 -1
  200. package/dist/secret-read-async.js +2 -1
  201. package/dist/secure-file.d.ts.map +1 -1
  202. package/dist/secure-file.js +21 -4
  203. package/dist/secure-temp-dir.d.ts.map +1 -1
  204. package/dist/secure-temp-dir.js +2 -1
  205. package/dist/sibling-staged-file.d.ts +1 -0
  206. package/dist/sibling-staged-file.d.ts.map +1 -1
  207. package/dist/sibling-staged-file.js +42 -8
  208. package/dist/sibling-temp.d.ts +2 -0
  209. package/dist/sibling-temp.d.ts.map +1 -1
  210. package/dist/sibling-temp.js +6 -4
  211. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  212. package/dist/sidecar-lock-acquire.js +15 -4
  213. package/dist/sidecar-lock-policy.d.ts +2 -0
  214. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  215. package/dist/sidecar-lock-policy.js +17 -0
  216. package/dist/staged-directory.d.ts.map +1 -1
  217. package/dist/staged-directory.js +4 -3
  218. package/dist/temp-target.d.ts +14 -12
  219. package/dist/temp-target.d.ts.map +1 -1
  220. package/dist/temp-target.js +12 -6
  221. package/dist/trash.d.ts.map +1 -1
  222. package/dist/trash.js +7 -5
  223. package/dist/unicode-path.d.ts +3 -0
  224. package/dist/unicode-path.d.ts.map +1 -0
  225. package/dist/unicode-path.js +13 -0
  226. package/dist/walk.d.ts.map +1 -1
  227. package/dist/walk.js +3 -2
  228. package/dist/write-file-handle.d.ts +1 -0
  229. package/dist/write-file-handle.d.ts.map +1 -1
  230. package/dist/write-file-handle.js +3 -2
  231. package/docs/advanced.md +4 -0
  232. package/docs/archive.md +25 -5
  233. package/docs/atomic.md +17 -1
  234. package/docs/config.md +1 -0
  235. package/docs/contributing.md +29 -1
  236. package/docs/copy.md +75 -6
  237. package/docs/directory-identity.md +85 -0
  238. package/docs/durability.md +36 -1
  239. package/docs/entries.md +109 -0
  240. package/docs/errors.md +3 -3
  241. package/docs/file-store.md +15 -0
  242. package/docs/guest.md +141 -0
  243. package/docs/in-place-write.md +81 -0
  244. package/docs/index.md +2 -0
  245. package/docs/install.md +31 -0
  246. package/docs/native-helper.md +10 -3
  247. package/docs/native.md +4 -0
  248. package/docs/output.md +32 -6
  249. package/docs/path-case.md +64 -0
  250. package/docs/path-scope.md +1 -1
  251. package/docs/permissions.md +11 -2
  252. package/docs/public-api.md +31 -2
  253. package/docs/root.md +30 -2
  254. package/docs/secure-file.md +2 -0
  255. package/docs/sidecar-lock.md +12 -3
  256. package/docs/temp.md +35 -6
  257. package/docs/types.md +1 -1
  258. package/docs/writing.md +153 -3
  259. package/package.json +14 -8
@@ -1 +1 @@
1
- {"version":3,"file":"sibling-staged-file.d.ts","sourceRoot":"","sources":["../src/sibling-staged-file.ts"],"names":[],"mappings":"AAmCA,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CAyG3C"}
1
+ {"version":3,"file":"sibling-staged-file.d.ts","sourceRoot":"","sources":["../src/sibling-staged-file.ts"],"names":[],"mappings":"AAuEA,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CA+G3C"}
@@ -2,12 +2,14 @@ import { syncFileBestEffort } from "./file-sync.js";
2
2
  import fsSync, {} from "node:fs";
3
3
  import fs, {} from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
5
+ import { assertAsyncDirectoryGuard, assertDirectoryIdentitySync, createAsyncDirectoryGuard, } from "./directory-guard.js";
6
6
  import { syncDirectoryBestEffort } from "./directory-durability.js";
7
7
  import { FsSafeError } from "./errors.js";
8
8
  import { resolveReadOpenFlags } from "./read-open-flags.js";
9
+ import { root } from "./root.js";
9
10
  import { inspectFileIdentity } from "./strict-file-identity.js";
10
11
  import { registerTempPathForExit } from "./temp-cleanup.js";
12
+ import { createOwnedTempFile } from "./temp-target.js";
11
13
  import { serializePathWrite } from "./write-queue.js";
12
14
  function assertRegularFile(stat) {
13
15
  if (stat.isSymbolicLink()) {
@@ -27,17 +29,39 @@ async function inspectStage(inspect, expected) {
27
29
  return stat;
28
30
  }, expected);
29
31
  }
32
+ // Own the workspace before the producer can leave partial output. The finished
33
+ // file still enters the ordinary sibling admission and publication lifecycle.
34
+ async function writeIsolatedProducer(params) {
35
+ const targetRoot = await root(path.dirname(params.tempPath));
36
+ const { target, identity } = await createOwnedTempFile({
37
+ rootDir: targetRoot.rootReal,
38
+ prefix: "fs-safe-output",
39
+ fileName: path.basename(params.tempPath),
40
+ });
41
+ const assertCurrent = () => {
42
+ params.assertParent();
43
+ assertDirectoryIdentitySync(target.dir, { ...identity, realPath: target.dir });
44
+ };
45
+ try {
46
+ assertCurrent();
47
+ const result = await params.write(target.path);
48
+ assertCurrent();
49
+ await targetRoot.move(path.relative(targetRoot.rootReal, target.path), path.basename(params.tempPath), {
50
+ assertBeforeMutation: assertCurrent,
51
+ });
52
+ return result;
53
+ }
54
+ finally {
55
+ await target.cleanup();
56
+ }
57
+ }
30
58
  // Callback paths are not owned until all three admission observations agree.
31
59
  // Keep one descriptor and one exact identity through mode, sync, rename and cleanup.
32
60
  // Read/write access is needed only when the caller requests file synchronization.
33
61
  export async function writeCallbackSibling(params) {
34
62
  const parent = path.dirname(params.tempPath);
35
- const guard = await createAsyncDirectoryGuard(parent);
36
- const parentIdentity = await inspectFileIdentity(() => fsSync.lstatSync(parent, { bigint: true }));
37
- const assertParent = async () => {
38
- await assertAsyncDirectoryGuard(guard);
39
- await inspectFileIdentity(() => fsSync.lstatSync(parent, { bigint: true }), parentIdentity);
40
- };
63
+ const guard = await createAsyncDirectoryGuard(parent, { bigint: true });
64
+ const assertParent = () => assertAsyncDirectoryGuard(guard);
41
65
  let handle;
42
66
  let identity;
43
67
  let unregister;
@@ -54,7 +78,17 @@ export async function writeCallbackSibling(params) {
54
78
  }
55
79
  };
56
80
  try {
57
- const result = await params.write(params.tempPath);
81
+ const result = params.producerIsolation === "private-directory"
82
+ ? await writeIsolatedProducer({
83
+ tempPath: params.tempPath,
84
+ write: params.write,
85
+ assertParent: () => assertDirectoryIdentitySync(parent, {
86
+ dev: guard.stat.dev,
87
+ ino: guard.stat.ino,
88
+ realPath: guard.realPath,
89
+ }),
90
+ })
91
+ : await params.write(params.tempPath);
58
92
  await assertParent();
59
93
  const before = await inspectPath(params.tempPath);
60
94
  try {
@@ -1,6 +1,8 @@
1
1
  export type WriteSiblingTempFileOptions<T> = {
2
2
  dir: string;
3
3
  writeTemp: (tempPath: string) => Promise<T>;
4
+ /** Own a private sibling workspace before invoking the producer. */
5
+ producerIsolation?: "private-directory";
4
6
  resolveFinalPath: (result: T) => string;
5
7
  tempPrefix?: string;
6
8
  dirMode?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAoBxC;AAwBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6ChB"}
1
+ {"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAqBxC;AAwBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6ChB"}
@@ -1,10 +1,11 @@
1
1
  import crypto, { randomUUID } from "node:crypto";
2
- import fsSync from "node:fs";
3
2
  import fs from "node:fs/promises";
4
3
  import path from "node:path";
5
4
  import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
6
5
  import { fitFileNameToPortableComponent, sanitizeUntrustedFileName } from "./filename.js";
7
6
  import { applyDirectoryMode } from "./replace-file-descriptor.js";
7
+ import { realpathSync } from "./realpath.js";
8
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
8
9
  import { root } from "./root.js";
9
10
  import { assertSafePathPrefix } from "./safe-path-segment.js";
10
11
  import { resolveSecureTempRoot } from "./secure-temp-dir.js";
@@ -19,7 +20,7 @@ function buildTempPath(dir, tempPrefix) {
19
20
  }
20
21
  export async function writeSiblingTempFile(options) {
21
22
  const dir = path.resolve(options.dir);
22
- await fs.mkdir(dir, { recursive: true, mode: options.dirMode ?? 0o700 });
23
+ await fs.mkdir(recursiveMkdirPath(dir), { recursive: true, mode: options.dirMode ?? 0o700 });
23
24
  if (options.chmodDir !== false) {
24
25
  await applyDirectoryMode({
25
26
  fsModule: fs,
@@ -31,6 +32,7 @@ export async function writeSiblingTempFile(options) {
31
32
  return await writeCallbackSibling({
32
33
  tempPath: buildTempPath(dir, options.tempPrefix),
33
34
  write: options.writeTemp,
35
+ producerIsolation: options.producerIsolation,
34
36
  resolveFinalPath: options.resolveFinalPath,
35
37
  mode: options.mode,
36
38
  ignoreModeError: true,
@@ -55,7 +57,7 @@ function buildSiblingTempPath(params) {
55
57
  export async function writeViaSiblingTempPath(params) {
56
58
  let rootDir;
57
59
  try {
58
- rootDir = fsSync.realpathSync.native(path.resolve(params.rootDir));
60
+ rootDir = realpathSync.native(path.resolve(params.rootDir));
59
61
  }
60
62
  catch {
61
63
  rootDir = path.resolve(params.rootDir);
@@ -63,7 +65,7 @@ export async function writeViaSiblingTempPath(params) {
63
65
  const requestedTargetPath = path.resolve(params.targetPath);
64
66
  let targetPath;
65
67
  try {
66
- const realDir = fsSync.realpathSync.native(path.dirname(requestedTargetPath));
68
+ const realDir = realpathSync.native(path.dirname(requestedTargetPath));
67
69
  targetPath = path.join(realDir, path.basename(requestedTargetPath));
68
70
  }
69
71
  catch {
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAQA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAS3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAI5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAqBF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CAqT5B"}
1
+ {"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAUA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAW3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAI5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAqBF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CA6T5B"}
@@ -5,9 +5,11 @@ import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { fileObservation } from "./file-observation.js";
7
7
  import { readFileHandleBounded } from "./bounded-read.js";
8
+ import { realpathSync } from "./realpath.js";
9
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
8
10
  import { openSidecarRoot } from "./sidecar-lock-root.js";
9
11
  import { createNativeExclusiveFile } from "./native-operations.js";
10
- import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransientLockFileDenial, maxTransientLockDenials, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
12
+ import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransientLockFileDenial, maxTransientLockDenials, validateSidecarLockStaleMs, validateSidecarLockRetryOptions, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
11
13
  import { readSidecarLockRawSnapshot, parseSidecarLockSnapshot, relativeSidecarLockPath, releaseSidecarReclaimGuard, removeSidecarLockIfUnchanged, removeStaleSidecarLockIfAllowed, serializeSidecarLockPayload, sidecarLockSnapshotStillPresent, sidecarReclaimGuardExists, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
12
14
  import { createSuppressedError } from "./suppressed-error.js";
13
15
  import { sleep } from "./timing.js";
@@ -22,9 +24,9 @@ async function resolveNormalizedTargetPath(targetPath, lockRoot) {
22
24
  await lockRoot.resolve(".");
23
25
  return path.join(parent, path.basename(resolved));
24
26
  }
25
- await fs.mkdir(dir, { recursive: true });
27
+ await fs.mkdir(recursiveMkdirPath(dir), { recursive: true });
26
28
  try {
27
- return path.join(fsSync.realpathSync.native(dir), path.basename(resolved));
29
+ return path.join(realpathSync.native(dir), path.basename(resolved));
28
30
  }
29
31
  catch {
30
32
  return resolved;
@@ -34,6 +36,8 @@ export async function acquireSidecarLock(options, context) {
34
36
  const retry = options.retry ?? {};
35
37
  validateSidecarLockRetryOptions(retry);
36
38
  validateSidecarLockTimeoutMs(options.timeoutMs);
39
+ validateSidecarLockStaleMs(options.staleMs);
40
+ validateSidecarLockCompromiseCheckIntervalMs(options.compromiseCheckIntervalMs);
37
41
  context.ensureExitCleanupRegistered();
38
42
  const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath, options.lockRoot);
39
43
  const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
@@ -153,7 +157,7 @@ export async function acquireSidecarLock(options, context) {
153
157
  try {
154
158
  handle =
155
159
  (await createNativeExclusiveFile(lockPath, 0o600)) ??
156
- (await fs.open(lockPath, "wx"));
160
+ (await fs.open(lockPath, "wx", 0o600));
157
161
  }
158
162
  catch (createError) {
159
163
  lockFileCreateDenied = isTransientLockFileDenial(createError, lockPath);
@@ -194,7 +198,11 @@ export async function acquireSidecarLock(options, context) {
194
198
  const returnedHandle = context.handleForHeldLock(normalizedTargetPath, createdHeld);
195
199
  const interval = options.compromiseCheckIntervalMs;
196
200
  if (options.onCompromised && interval !== undefined && interval > 0) {
201
+ let compromiseCheckInFlight = false;
197
202
  createdHeld.compromiseTimer = setInterval(() => {
203
+ if (compromiseCheckInFlight)
204
+ return;
205
+ compromiseCheckInFlight = true;
198
206
  void returnedHandle
199
207
  .verifyStillHeld()
200
208
  .catch(() => false)
@@ -204,6 +212,9 @@ export async function acquireSidecarLock(options, context) {
204
212
  createdHeld.compromiseTimer = undefined;
205
213
  options.onCompromised?.({ lockPath, normalizedTargetPath });
206
214
  }
215
+ })
216
+ .finally(() => {
217
+ compromiseCheckInFlight = false;
207
218
  });
208
219
  }, interval);
209
220
  createdHeld.compromiseTimer.unref();
@@ -1,6 +1,8 @@
1
1
  import type { SidecarLockRetryOptions } from "./sidecar-lock-types.js";
2
2
  export declare function validateSidecarLockRetryOptions(retry: SidecarLockRetryOptions): void;
3
3
  export declare function validateSidecarLockTimeoutMs(timeoutMs: number | undefined): void;
4
+ export declare function validateSidecarLockStaleMs(staleMs: number | undefined): void;
5
+ export declare function validateSidecarLockCompromiseCheckIntervalMs(intervalMs: number | undefined): void;
4
6
  export declare function computeSidecarLockDelayMs(retry: SidecarLockRetryOptions, attempt: number): number;
5
7
  export declare const maxTransientLockDenials = 8;
6
8
  export declare function isTransientLockFileDenial(error: unknown, lockPath: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock-policy.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAQvE,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAkBpF;AAED,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAGhF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAUjG;AASD,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGnF;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,CAQnB"}
1
+ {"version":3,"file":"sidecar-lock-policy.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAUvE,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAkBpF;AAED,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAGhF;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAO5E;AAED,wBAAgB,4CAA4C,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAOjG;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAUjG;AASD,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGnF;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,CAQnB"}
@@ -1,4 +1,5 @@
1
1
  import fsSync from "node:fs";
2
+ const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
2
3
  function assertFiniteNonNegative(value, label) {
3
4
  if (!Number.isFinite(value) || value < 0) {
4
5
  throw new RangeError(`${label} must be a finite non-negative number`);
@@ -27,6 +28,22 @@ export function validateSidecarLockTimeoutMs(timeoutMs) {
27
28
  return;
28
29
  assertFiniteNonNegative(timeoutMs, "lock timeoutMs");
29
30
  }
31
+ export function validateSidecarLockStaleMs(staleMs) {
32
+ // The lower-level sidecar manager historically treats an omitted threshold
33
+ // as disabling age-based reclamation. Its public type requires a number, but
34
+ // retain that fail-closed runtime behavior for JavaScript callers and older
35
+ // consumers.
36
+ if (staleMs === undefined || staleMs === Number.POSITIVE_INFINITY)
37
+ return;
38
+ assertFiniteNonNegative(staleMs, "lock staleMs");
39
+ }
40
+ export function validateSidecarLockCompromiseCheckIntervalMs(intervalMs) {
41
+ if (intervalMs === undefined || intervalMs === 0)
42
+ return;
43
+ if (!Number.isFinite(intervalMs) || intervalMs < 1 || intervalMs > MAX_TIMER_DELAY_MS) {
44
+ throw new RangeError(`lock compromiseCheckIntervalMs must be 0 or a finite number between 1 and ${MAX_TIMER_DELAY_MS}`);
45
+ }
46
+ }
30
47
  export function computeSidecarLockDelayMs(retry, attempt) {
31
48
  validateSidecarLockRetryOptions(retry);
32
49
  const minTimeout = retry.minTimeout ?? 50;
@@ -1 +1 @@
1
- {"version":3,"file":"staged-directory.d.ts","sourceRoot":"","sources":["../src/staged-directory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,KAAK,iBAAiB,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAExD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,OAAO,CAKT;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAYvF;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAQ7E;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,iBAAiB,CAAC;CAC5B,CA+BA"}
1
+ {"version":3,"file":"staged-directory.d.ts","sourceRoot":"","sources":["../src/staged-directory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,KAAK,iBAAiB,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAExD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,OAAO,CAKT;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAYvF;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAQ7E;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,iBAAiB,CAAC;CAC5B,CA+BA"}
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
+ import { realpathSync } from "./realpath.js";
4
5
  export function exactIdentityMatches(expected, actual) {
5
6
  return ["dev", "ino"].every((key) => {
6
7
  const value = expected[key];
@@ -14,7 +15,7 @@ export function describeStagedDirectory(fd, pathname) {
14
15
  }
15
16
  const receipt = Object.freeze({
16
17
  path: path.resolve(pathname),
17
- realPath: fs.realpathSync(pathname),
18
+ realPath: realpathSync(pathname),
18
19
  identity: Object.freeze({ dev: identity.dev, ino: identity.ino }),
19
20
  });
20
21
  assertStagedDirectoryCurrent(receipt);
@@ -23,7 +24,7 @@ export function describeStagedDirectory(fd, pathname) {
23
24
  export function assertStagedDirectoryCurrent(receipt) {
24
25
  const current = fs.lstatSync(receipt.path, { bigint: true });
25
26
  if (!current.isDirectory() || !exactIdentityMatches(receipt.identity, current) ||
26
- fs.realpathSync(receipt.path) !== receipt.realPath) {
27
+ realpathSync(receipt.path) !== receipt.realPath) {
27
28
  throw new FsSafeError("path-mismatch", "staging directory pathname changed");
28
29
  }
29
30
  }
@@ -37,7 +38,7 @@ export function openStagedDirectory(directory) {
37
38
  if (!before.isDirectory()) {
38
39
  throw new FsSafeError("not-file", "staging parent must be a real directory");
39
40
  }
40
- if (expected && (!exactIdentityMatches(expected, before) || fs.realpathSync(pathname) !== expected.realPath)) {
41
+ if (expected && (!exactIdentityMatches(expected, before) || realpathSync(pathname) !== expected.realPath)) {
41
42
  throw new FsSafeError("path-mismatch", "stale staging directory receipt");
42
43
  }
43
44
  const fd = fs.openSync(pathname, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
@@ -1,3 +1,4 @@
1
+ import fsSync from "node:fs";
1
2
  export type TempFile = {
2
3
  dir: string;
3
4
  path: string;
@@ -5,6 +6,12 @@ export type TempFile = {
5
6
  cleanup: () => Promise<void>;
6
7
  [Symbol.asyncDispose](): Promise<void>;
7
8
  };
9
+ type TempFileOptions = {
10
+ rootDir?: string;
11
+ prefix: string;
12
+ fileName?: string;
13
+ onCleanupError?: (error: unknown) => void;
14
+ };
8
15
  export declare function sanitizeTempFileName(fileName: string): string;
9
16
  export declare function buildRandomTempFilePath(params: {
10
17
  rootDir?: string;
@@ -13,16 +20,11 @@ export declare function buildRandomTempFilePath(params: {
13
20
  now?: number;
14
21
  uuid?: string;
15
22
  }): string;
16
- export declare function tempFile(params: {
17
- rootDir?: string;
18
- prefix: string;
19
- fileName?: string;
20
- onCleanupError?: (error: unknown) => void;
21
- }): Promise<TempFile>;
22
- export declare function withTempFile<T>(params: {
23
- rootDir?: string;
24
- prefix: string;
25
- fileName?: string;
26
- onCleanupError?: (error: unknown) => void;
27
- }, fn: (tmpPath: string) => Promise<T>): Promise<T>;
23
+ export declare function createOwnedTempFile(params: TempFileOptions): Promise<{
24
+ target: TempFile;
25
+ identity: Readonly<Pick<fsSync.BigIntStats, "dev" | "ino">>;
26
+ }>;
27
+ export declare function tempFile(params: TempFileOptions): Promise<TempFile>;
28
+ export declare function withTempFile<T>(params: TempFileOptions, fn: (tmpPath: string) => Promise<T>): Promise<T>;
29
+ export {};
28
30
  //# sourceMappingURL=temp-target.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"temp-target.d.ts","sourceRoot":"","sources":["../src/temp-target.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AA8DF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAaT;AAyCD,wBAAsB,QAAQ,CAAC,MAAM,EAAE;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwBpB;AAED,wBAAsB,YAAY,CAAC,CAAC,EAClC,MAAM,EAAE;IACN,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C,EACD,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
1
+ {"version":3,"file":"temp-target.d.ts","sourceRoot":"","sources":["../src/temp-target.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAQ7B,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C,CAAC;AA8DF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAaT;AAyCD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC;IAC1E,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;CAC7D,CAAC,CA2BD;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEzE;AAED,wBAAsB,YAAY,CAAC,CAAC,EAClC,MAAM,EAAE,eAAe,EACvB,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
@@ -106,7 +106,7 @@ async function cleanupTempDir(dir, identity, onCleanupError) {
106
106
  function resolveTempRoot(rootDir) {
107
107
  return path.resolve(rootDir ?? resolveSecureTempRoot({ fallbackPrefix: "fs-safe" }));
108
108
  }
109
- export async function tempFile(params) {
109
+ export async function createOwnedTempFile(params) {
110
110
  const rootDir = resolveTempRoot(params.rootDir);
111
111
  const prefix = `${sanitizePrefix(params.prefix)}-`;
112
112
  const dir = await fs.mkdtemp(path.join(rootDir, prefix));
@@ -124,13 +124,19 @@ export async function tempFile(params) {
124
124
  }
125
125
  };
126
126
  return {
127
- dir,
128
- path: file(),
129
- file,
130
- cleanup,
131
- [Symbol.asyncDispose]: cleanup,
127
+ target: {
128
+ dir,
129
+ path: file(),
130
+ file,
131
+ cleanup,
132
+ [Symbol.asyncDispose]: cleanup,
133
+ },
134
+ identity: Object.freeze({ dev: identity.dev, ino: identity.ino }),
132
135
  };
133
136
  }
137
+ export async function tempFile(params) {
138
+ return (await createOwnedTempFile(params)).target;
139
+ }
134
140
  export async function withTempFile(params, fn) {
135
141
  const target = await tempFile(params);
136
142
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"trash.d.ts","sourceRoot":"","sources":["../src/trash.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AAkLF,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,CAAC,CAcjB"}
1
+ {"version":3,"file":"trash.d.ts","sourceRoot":"","sources":["../src/trash.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AAkLF,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,CAAC,CAcjB"}
package/dist/trash.js CHANGED
@@ -3,6 +3,8 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { sameFileIdentity } from "./file-identity.js";
5
5
  import { guardedRenameSync, guardedRmSync } from "./guarded-mutation.js";
6
+ import { realpathSync } from "./realpath.js";
7
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
6
8
  import { getFsSafeTestHooks } from "./test-hooks.js";
7
9
  const TRASH_DESTINATION_COLLISION_CODES = new Set(["EEXIST", "ENOTEMPTY", "ERR_FS_CP_EEXIST"]);
8
10
  const TRASH_DESTINATION_RETRY_LIMIT = 4;
@@ -26,7 +28,7 @@ function resolveAllowedTrashRoots(allowedRoots) {
26
28
  try {
27
29
  // Keep both spellings: broken symlink targets cannot be realpathed and
28
30
  // may only compare equal to the caller's lexical allowed root.
29
- return [path.resolve(fs.realpathSync.native(root)), lexicalRoot];
31
+ return [path.resolve(realpathSync.native(root)), lexicalRoot];
30
32
  }
31
33
  catch {
32
34
  return [lexicalRoot];
@@ -36,7 +38,7 @@ function resolveAllowedTrashRoots(allowedRoots) {
36
38
  }
37
39
  function resolveTrashTargetPath(targetPath) {
38
40
  try {
39
- return { path: path.resolve(fs.realpathSync.native(targetPath)), resolved: true };
41
+ return { path: path.resolve(realpathSync.native(targetPath)), resolved: true };
40
42
  }
41
43
  catch {
42
44
  // Broken symlinks are valid trash targets. Fall back to the lexical path,
@@ -75,13 +77,13 @@ function assertTrashTargetGuard(guard) {
75
77
  function resolveTrashDir() {
76
78
  const homeDir = os.homedir();
77
79
  const trashDir = path.join(homeDir, ".Trash");
78
- fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
80
+ fs.mkdirSync(recursiveMkdirPath(trashDir), { recursive: true, mode: 0o700 });
79
81
  const trashDirStat = fs.lstatSync(trashDir);
80
82
  if (!trashDirStat.isDirectory() || trashDirStat.isSymbolicLink()) {
81
83
  throw new Error(`Refusing to use non-directory/symlink trash directory: ${trashDir}`);
82
84
  }
83
- const realHome = path.resolve(fs.realpathSync.native(homeDir));
84
- const resolvedTrashDir = path.resolve(fs.realpathSync.native(trashDir));
85
+ const realHome = path.resolve(realpathSync.native(homeDir));
86
+ const resolvedTrashDir = path.resolve(realpathSync.native(trashDir));
85
87
  if (resolvedTrashDir === realHome || !isSameOrChildPath(resolvedTrashDir, realHome)) {
86
88
  throw new Error(`Trash directory escaped home directory: ${trashDir}`);
87
89
  }
@@ -0,0 +1,3 @@
1
+ export declare function lowerCaseNfc(value: string): string;
2
+ export declare function maxNormalizedUtf8Bytes(value: string, includeRaw?: boolean): number;
3
+ //# sourceMappingURL=unicode-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unicode-path.d.ts","sourceRoot":"","sources":["../src/unicode-path.ts"],"names":[],"mappings":"AAEA,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,UAAQ,GAAG,MAAM,CAUhF"}
@@ -0,0 +1,13 @@
1
+ const NON_ASCII = /[^\x00-\x7f]/;
2
+ export function lowerCaseNfc(value) {
3
+ return NON_ASCII.test(value) ? value.normalize("NFC").toLowerCase().normalize("NFC") : value.toLowerCase();
4
+ }
5
+ export function maxNormalizedUtf8Bytes(value, includeRaw = false) {
6
+ const nfc = value.normalize("NFC");
7
+ const bytes = Buffer.byteLength(nfc, "utf8");
8
+ const maximum = includeRaw && nfc !== value ? Math.max(bytes, Buffer.byteLength(value, "utf8")) : bytes;
9
+ // ASCII NFC output also has identical NFD and one UTF-8 byte per code unit.
10
+ if (bytes === nfc.length)
11
+ return maximum;
12
+ return Math.max(maximum, Buffer.byteLength(value.normalize("NFD"), "utf8"));
13
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAI7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;AAgFF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,+BAA+B,CAyDjC;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,+BAA+B,CAAC,CAyD1C"}
1
+ {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAK7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;AAgFF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,+BAA+B,CAyDjC;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,+BAA+B,CAAC,CAyD1C"}
package/dist/walk.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fsSync from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { realpathSync } from "./realpath.js";
4
5
  function validateWalkBudget(name, value) {
5
6
  if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
6
7
  throw new RangeError(`${name} must be a non-negative safe integer`);
@@ -83,7 +84,7 @@ export function walkDirectorySync(rootDir, options = {}) {
83
84
  return;
84
85
  let realDir;
85
86
  try {
86
- realDir = fsSync.realpathSync(dir);
87
+ realDir = realpathSync(dir);
87
88
  }
88
89
  catch (error) {
89
90
  recordFailedDir(result, root, dir, depth, error);
@@ -142,7 +143,7 @@ export async function walkDirectory(rootDir, options = {}) {
142
143
  return;
143
144
  let realDir;
144
145
  try {
145
- realDir = fsSync.realpathSync.native(dir);
146
+ realDir = realpathSync.native(dir);
146
147
  }
147
148
  catch (error) {
148
149
  recordFailedDir(result, root, dir, depth, error);
@@ -1,6 +1,7 @@
1
1
  import type { FileHandle } from "node:fs/promises";
2
2
  export declare function writeAllToFile(target: FileHandle | number, data: string | Uint8Array, options?: {
3
3
  encoding?: BufferEncoding;
4
+ position?: number;
4
5
  assertBeforeMutation?: () => void;
5
6
  }): Promise<void>;
6
7
  //# sourceMappingURL=write-file-handle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"write-file-handle.d.ts","sourceRoot":"","sources":["../src/write-file-handle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,GAC7E,OAAO,CAAC,IAAI,CAAC,CAmBf"}
1
+ {"version":3,"file":"write-file-handle.d.ts","sourceRoot":"","sources":["../src/write-file-handle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,GAChG,OAAO,CAAC,IAAI,CAAC,CAoBf"}
@@ -6,17 +6,18 @@ export async function writeAllToFile(target, data, options = {}) {
6
6
  let offset = 0;
7
7
  while (offset < buffer.byteLength) {
8
8
  const length = Math.min(WRITE_CHUNK_BYTES, buffer.byteLength - offset);
9
+ const position = options.position === undefined ? null : options.position + offset;
9
10
  options.assertBeforeMutation?.();
10
11
  const written = typeof target === "number"
11
12
  ? await new Promise((resolve, reject) => {
12
- fs.write(target, buffer, offset, length, null, (error, bytesWritten) => {
13
+ fs.write(target, buffer, offset, length, position, (error, bytesWritten) => {
13
14
  if (error)
14
15
  reject(error);
15
16
  else
16
17
  resolve(bytesWritten);
17
18
  });
18
19
  })
19
- : (await target.write(buffer, offset, length, null)).bytesWritten;
20
+ : (await target.write(buffer, offset, length, position)).bytesWritten;
20
21
  if (written <= 0) {
21
22
  throw new FsSafeError("helper-failed", "file write made no progress");
22
23
  }
package/docs/advanced.md CHANGED
@@ -32,6 +32,7 @@ The exports group into a handful of themes. Each documented helper has its own p
32
32
  | `resolveWritablePathWithinRoot` | – | Resolve a write target inside a root. |
33
33
  | `resolveRootPath`, `resolveRootPathSync`, `ResolvedRootPath`, `ROOT_PATH_ALIAS_POLICIES`, `RootPathAliasPolicy` | – | Resolve a root directory honoring alias policy. |
34
34
  | `resolvePathViaExistingAncestorSync` | – | Walk to an existing ancestor for paths whose tail does not yet exist. |
35
+ | `probePathCaseInsensitiveSync`, `ProbePathCaseOptions` | [path-case.md](path-case.md) | Observe local ASCII-case behavior with explicit read-only mode and owned temporary-probe cleanup. |
35
36
 
36
37
  `ensureDirectoryWithinRoot({ rootDir, requestedPath, scopeLabel, defaultDirName?, mode? })`
37
38
  returns `{ ok: true, path }` or `{ ok: false, error: string, diagnostic?: FsSafeError }`.
@@ -66,9 +67,12 @@ Operational filesystem failures such as permissions or I/O errors are rethrown.
66
67
  |---|---|---|
67
68
  | `readFileDescriptorBounded`, `readFileDescriptorBoundedSync`, `readFileHandleBounded` | – | Incremental whole-file reads for already-open descriptors/handles. They consume at most `maxBytes + 1`, do not close the input, and throw `FsSafeError("too-large")` on overflow. |
68
69
  | `readFileWindowFully`, `readFileWindowFullySync`, `ReadFileWindowOptions` | [positional-read.md](positional-read.md) | Fill a caller-owned buffer at an explicit file position, completing short reads and returning the EOF count without moving or closing the descriptor. |
70
+ | `copyFileHandle`, `CopyFileHandleOptions` | [copy.md](copy.md#borrowed-filehandle-transfers) | Copy caller-owned regular-file handles from position zero with byte limits, settled cancellation, and a synchronous source observer; preserves cursors and leaves publication and cleanup to the caller. |
71
+ | `overwriteFileHandle`, `OverwriteFileHandleOptions` | [in-place-write.md](in-place-write.md) | Overwrite a borrowed read/write handle with prefix-only preparation and best-effort rollback; preserves its inode, cursor, and caller-owned lifetime. |
69
72
  | `openRootFile`, `openRootFileSync`, `canUseRootFileOpen`, `matchRootFileOpenFailure`, related types | – | Low-level root-bounded open; rejects every symlink component by default, with `symlinks: "follow-parents-within-root"` for contained parent aliases or `"follow-within-root"` for final links too. |
70
73
  | `appendRegularFile`, `appendRegularFileSync`, `readRegularFile`, `readRegularFileSync`, `statRegularFile`, `statRegularFileSync`, `resolveRegularFileAppendFlags`, `AppendRegularFileOptions`, `RegularFileStatResult` | [regular-file.md](regular-file.md) | Type-checked regular-file I/O. |
71
74
  | `sameFileIdentity`, `FileIdentityStat` | – | Compare two stats for same-inode equality. |
75
+ | `readDirectoryIdentity`, `assertDirectoryIdentitySync`, `DirectoryIdentity` | [directory-identity.md](directory-identity.md) | Observe exact bigint directory identity and synchronously check a caller-selected path, optionally retaining its canonical path. |
72
76
  | `pathExists`, `pathExistsSync` | – | Boolean existence check that does not throw on `ENOENT`. |
73
77
  | `assertNoSymlinkParents`, `assertNoSymlinkParentsSync`, `AssertNoSymlinkParentsOptions` | – | Reject paths whose ancestor chain contains symlinks. |
74
78
  | `assertNoHardlinkedFinalPath`, `assertNoPathAliasEscape`, `PATH_ALIAS_POLICIES`, `PathAliasPolicy` | – | Hardlink/alias defense building blocks. |