@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
@@ -17,6 +17,7 @@ const types = new Map([
17
17
  [51, "CharacterDevice"], [52, "BlockDevice"], [53, "Directory"],
18
18
  [54, "FIFO"], [55, "ContiguousFile"], [68, "GNUDumpDir"],
19
19
  ]);
20
+ const utf8Decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
20
21
  function parserError(message) {
21
22
  const mapped = classifyArchiveParserError(message);
22
23
  if (mapped)
@@ -47,7 +48,7 @@ export class TarParserStream extends Transform {
47
48
  }
48
49
  text() {
49
50
  const abi = this.abi;
50
- return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(this.bytes(abi.text_ptr(), abi.text_len()));
51
+ return utf8Decoder.decode(this.bytes(abi.text_ptr(), abi.text_len()));
51
52
  }
52
53
  _transform(chunk, _encoding, callback) {
53
54
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AA6MpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CjF"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAiNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CjF"}
package/dist/archive.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createTarEntryPlanner } from "./archive-tar.js";
2
2
  import { inspectTar, replayTar } from "./archive-tar-stream.js";
3
3
  import { runPinnedWriteHelper } from "./pinned-write.js";
4
- import { constants as fsConstants } from "node:fs";
4
+ import fsSync, { constants as fsConstants } from "node:fs";
5
5
  import fs from "node:fs/promises";
6
6
  import path from "node:path";
7
7
  import { Readable } from "node:stream";
@@ -77,6 +77,8 @@ async function writeZipFileEntry(params) {
77
77
  dir: path.dirname(destinationPath),
78
78
  chmodDir: false,
79
79
  mode: 0o600,
80
+ syncTempFile: false,
81
+ syncParentDir: false,
80
82
  writeTemp: async (tempPath) => {
81
83
  tempHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o600);
82
84
  const writable = tempHandle.createWriteStream();
@@ -134,7 +136,7 @@ async function extractZip(params) {
134
136
  await withStagedArchiveDestination({
135
137
  destinationRealDir,
136
138
  run: async (stagingDir) => {
137
- const stagingRealDir = await fs.realpath(stagingDir);
139
+ const stagingRealDir = fsSync.realpathSync.native(stagingDir);
138
140
  const acceptedEntries = [];
139
141
  for (const entry of entries) {
140
142
  params.deadline.check();
@@ -185,6 +187,7 @@ async function extractZip(params) {
185
187
  params.deadline.check();
186
188
  await mergePlannedArchiveIntoDestination({
187
189
  entries: acceptedEntries,
190
+ durable: params.durable,
188
191
  sourceDir: stagingRealDir,
189
192
  destinationDir: params.destDir,
190
193
  destinationRealDir,
@@ -212,6 +215,7 @@ export async function extractArchive(params) {
212
215
  // getters and inherited policy must survive identically on every backend.
213
216
  const options = {
214
217
  archivePath: params.archivePath, destDir: params.destDir,
218
+ durable: params.durable,
215
219
  stripComponents: params.stripComponents, limits,
216
220
  entryModes: params.entryModes, entryFilter: params.entryFilter, onFiltered,
217
221
  };
@@ -246,7 +250,7 @@ async function extractWasmTar(params) {
246
250
  deadline.check();
247
251
  const destinationRealDir = await prepareArchiveDestinationDir(options.destDir);
248
252
  await withStagedArchiveDestination({ destinationRealDir, run: async (stagingPath) => {
249
- const stagingDir = await fs.realpath(stagingPath);
253
+ const stagingDir = fsSync.realpathSync.native(stagingPath);
250
254
  const planEntry = createTarEntryPlanner({ ...options, rootDir: destinationRealDir, limits: params.limits });
251
255
  const accepted = manifest.flatMap((entry) => {
252
256
  deadline.check();
@@ -262,6 +266,7 @@ async function extractWasmTar(params) {
262
266
  if (member.kind === "file") {
263
267
  await runPinnedWriteHelper({ rootPath: stagingDir, relativeParentPath: path.posix.dirname(member.path),
264
268
  basename: path.posix.basename(member.path), mkdir: false, mode: 0o600, overwrite: false,
269
+ sync: false,
265
270
  maxBytes: member.size, input: { kind: "stream", stream: Readable.from(payload) } });
266
271
  }
267
272
  deadline.check();
@@ -269,7 +274,7 @@ async function extractWasmTar(params) {
269
274
  });
270
275
  deadline.check();
271
276
  await mergePlannedArchiveIntoDestination({ entries: accepted, sourceDir: stagingDir,
272
- destinationDir: options.destDir, destinationRealDir, deadline });
277
+ destinationDir: options.destDir, destinationRealDir, deadline, durable: options.durable });
273
278
  deadline.check();
274
279
  } });
275
280
  }
@@ -1,5 +1,12 @@
1
1
  import type { FileHandle } from "node:fs/promises";
2
2
  type ReadableFileHandle = Pick<FileHandle, "read">;
3
+ export declare function readBoundedAsync(maxBytes: number, readChunk: (scratch: Buffer, length: number) => Promise<number>, options?: {
4
+ observeRegularFileSize?: () => number | undefined;
5
+ initialSize?: number;
6
+ createLimitError?: () => Error;
7
+ /** Keep the result outside Node's shared small-buffer allocation pool. */
8
+ unpooled?: boolean;
9
+ }): Promise<Buffer>;
3
10
  /**
4
11
  * Reads from the handle's current offset without closing it. A bounded read
5
12
  * consumes at most maxBytes + 1 bytes so growth after an earlier stat cannot
@@ -1 +1 @@
1
- {"version":3,"file":"bounded-read.d.ts","sourceRoot":"","sources":["../src/bounded-read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AA+DnD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAMjB;AAyBD,oFAAoF;AACpF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7F;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAalF"}
1
+ {"version":3,"file":"bounded-read.d.ts","sourceRoot":"","sources":["../src/bounded-read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQnD,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AA8CnD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EAC/D,OAAO,GAAE;IACP,sBAAsB,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,KAAK,CAAC;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACf,GACL,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAMjB;AAyBD,oFAAoF;AACpF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7F;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoBlF"}
@@ -2,50 +2,69 @@ import fs from "node:fs";
2
2
  import { normalizeMaxBytes } from "./byte-budget.js";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  const READ_CHUNK_BYTES = 64 * 1024;
5
- function createScratchBuffer(maxBytes) {
6
- const initialReadBytes = Number.isFinite(maxBytes)
7
- ? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
8
- : READ_CHUNK_BYTES;
9
- return Buffer.allocUnsafe(Math.max(1, initialReadBytes));
5
+ // Preserve one-read performance through the default Root byte budget.
6
+ const MAX_INITIAL_READ_BYTES = 16 * 1024 * 1024;
7
+ function createInitialBuffer(maxBytes, size, allocate = Buffer.allocUnsafe) {
8
+ // A size hint can describe a huge sparse file or an already exhausted fd.
9
+ return allocate(Math.min(maxBytes, size, MAX_INITIAL_READ_BYTES) + 1);
10
10
  }
11
- function nextReadLength(total, maxBytes, capacity) {
12
- return Number.isFinite(maxBytes)
13
- ? Math.min(capacity, maxBytes - total + 1)
14
- : capacity;
11
+ function createScratchBuffer(maxBytes, allocate = Buffer.allocUnsafe) {
12
+ return allocate(Math.min(READ_CHUNK_BYTES, maxBytes + 1));
15
13
  }
16
- function appendChunk(params) {
17
- const total = params.total + params.bytesRead;
18
- if (total > params.maxBytes) {
19
- throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got at least ${total})`);
14
+ function growReadBuffer(buffer, maxBytes, allocate = Buffer.allocUnsafe) {
15
+ // Grow only after consuming the whole buffer, never from an unchecked size hint.
16
+ const capacity = Math.min(maxBytes + 1, Math.max(READ_CHUNK_BYTES, buffer.length * 2));
17
+ const grown = allocate(capacity);
18
+ buffer.copy(grown);
19
+ return grown;
20
+ }
21
+ function finishReadBuffer(buffer, total, allocate) {
22
+ if (total === 0)
23
+ return Buffer.alloc(0);
24
+ const result = buffer.subarray(0, total);
25
+ if (total >= buffer.length / 2)
26
+ return result;
27
+ if (!allocate)
28
+ return Buffer.from(result);
29
+ const compact = allocate(total);
30
+ result.copy(compact);
31
+ return compact;
32
+ }
33
+ function addReadBytes(total, bytesRead, maxBytes, createLimitError) {
34
+ const next = total + bytesRead;
35
+ if (next > maxBytes) {
36
+ if (createLimitError)
37
+ throw createLimitError();
38
+ throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${next})`);
20
39
  }
21
- params.chunks.push(Buffer.from(params.scratch.subarray(0, params.bytesRead)));
22
- return total;
40
+ return next;
23
41
  }
24
- async function readBoundedAsync(maxBytes, readChunk, observeRegularFileSize) {
42
+ export async function readBoundedAsync(maxBytes, readChunk, options = {}) {
25
43
  normalizeMaxBytes(maxBytes);
26
- const chunks = [];
27
44
  let total = 0;
28
- const size = observeRegularFileSize?.();
45
+ const { observeRegularFileSize, createLimitError } = options;
46
+ const allocate = options.unpooled ? Buffer.allocUnsafeSlow : undefined;
47
+ const size = options.initialSize ?? observeRegularFileSize?.();
48
+ let buffer = size === undefined ? createScratchBuffer(maxBytes, allocate) : createInitialBuffer(maxBytes, size, allocate);
29
49
  if (size !== undefined) {
30
- const first = Buffer.allocUnsafe(Math.min(maxBytes, size) + 1);
31
- const bytesRead = await readChunk(first, first.length);
50
+ const bytesRead = await readChunk(buffer, buffer.length);
32
51
  if (bytesRead === 0)
33
- return first.subarray(0, 0);
34
- // Short reads can occur before EOF. Only accept one when a fresh fd size
35
- // observation proves we consumed at least the entire current file size.
36
- const currentSize = bytesRead < first.length ? observeRegularFileSize?.() : undefined;
52
+ return finishReadBuffer(buffer, 0, allocate);
53
+ // Short reads can occur before EOF. Only use the size shortcut on this
54
+ // initial read; virtual files can report zero or stale sizes thereafter.
55
+ const currentSize = bytesRead < buffer.length ? observeRegularFileSize?.() : undefined;
56
+ total = addReadBytes(total, bytesRead, maxBytes, createLimitError);
37
57
  if (currentSize !== undefined && bytesRead >= currentSize)
38
- return first.subarray(0, bytesRead);
39
- total = appendChunk({ chunks, scratch: first, bytesRead, total, maxBytes });
58
+ return finishReadBuffer(buffer, total, allocate);
40
59
  }
41
- const scratch = createScratchBuffer(maxBytes);
42
60
  while (true) {
43
- const length = nextReadLength(total, maxBytes, scratch.length);
44
- const bytesRead = await readChunk(scratch, length);
45
- if (bytesRead === 0) {
46
- return Buffer.concat(chunks, total);
47
- }
48
- total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
61
+ if (total === buffer.length)
62
+ buffer = growReadBuffer(buffer, maxBytes, allocate);
63
+ const remaining = buffer.subarray(total);
64
+ const bytesRead = await readChunk(remaining, remaining.length);
65
+ if (bytesRead === 0)
66
+ return finishReadBuffer(buffer, total, allocate);
67
+ total = addReadBytes(total, bytesRead, maxBytes, createLimitError);
49
68
  }
50
69
  }
51
70
  /**
@@ -58,7 +77,7 @@ export async function readFileHandleBounded(handle, maxBytes) {
58
77
  const fd = "fd" in handle && typeof handle.fd === "number" ? handle.fd : undefined;
59
78
  return await readBoundedAsync(maxBytes, async (scratch, length) => {
60
79
  return (await handle.read(scratch, 0, length, null)).bytesRead;
61
- }, fd === undefined ? undefined : () => regularFileSize(fd));
80
+ }, { observeRegularFileSize: fd === undefined ? undefined : () => regularFileSize(fd) });
62
81
  }
63
82
  function regularFileSize(fd) {
64
83
  try {
@@ -87,20 +106,31 @@ export async function readFileDescriptorBounded(fd, maxBytes) {
87
106
  normalizeMaxBytes(maxBytes);
88
107
  return await readBoundedAsync(maxBytes, async (scratch, length) => {
89
108
  return await readDescriptorChunk(fd, scratch, length);
90
- }, () => regularFileSize(fd));
109
+ }, { observeRegularFileSize: () => regularFileSize(fd) });
91
110
  }
92
111
  /** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
93
112
  export function readFileDescriptorBoundedSync(fd, maxBytes) {
94
113
  normalizeMaxBytes(maxBytes);
95
- const chunks = [];
96
- const scratch = createScratchBuffer(maxBytes);
97
114
  let total = 0;
115
+ // Small budgets already bound the first allocation without a size lookup.
116
+ const size = maxBytes <= READ_CHUNK_BYTES ? maxBytes : regularFileSize(fd);
117
+ let buffer = size === undefined ? createScratchBuffer(maxBytes) : createInitialBuffer(maxBytes, size);
118
+ if (size !== undefined) {
119
+ const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null);
120
+ if (bytesRead === 0)
121
+ return finishReadBuffer(buffer, 0);
122
+ const currentSize = bytesRead < buffer.length ? regularFileSize(fd) : undefined;
123
+ total = addReadBytes(total, bytesRead, maxBytes);
124
+ if (currentSize !== undefined && bytesRead >= currentSize)
125
+ return finishReadBuffer(buffer, total);
126
+ }
98
127
  while (true) {
99
- const length = nextReadLength(total, maxBytes, scratch.length);
100
- const bytesRead = fs.readSync(fd, scratch, 0, length, null);
101
- if (bytesRead === 0) {
102
- return Buffer.concat(chunks, total);
103
- }
104
- total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
128
+ if (total === buffer.length)
129
+ buffer = growReadBuffer(buffer, maxBytes);
130
+ const remaining = buffer.subarray(total);
131
+ const bytesRead = fs.readSync(fd, remaining, 0, remaining.length, null);
132
+ if (bytesRead === 0)
133
+ return finishReadBuffer(buffer, total);
134
+ total = addReadBytes(total, bytesRead, maxBytes);
105
135
  }
106
136
  }
@@ -0,0 +1,19 @@
1
+ export type CloneFileMetadata = {
2
+ dev: number;
3
+ type: number;
4
+ mtimeSec: number;
5
+ mtimeNs: number;
6
+ ctimeSec: number;
7
+ ctimeNs: number;
8
+ uid: number;
9
+ gid: number;
10
+ mode: number;
11
+ ino: bigint;
12
+ size: bigint;
13
+ cloneId: bigint;
14
+ };
15
+ /** Batched APFS metadata snapshots; absent on unsupported files or filesystems.
16
+ * These are point-in-time facts, not authorization or a guarantee against later edits.
17
+ */
18
+ export declare function readCloneFileMetadata(files: readonly string[]): Promise<(CloneFileMetadata | undefined)[]>;
19
+ //# sourceMappingURL=clone-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clone-metadata.d.ts","sourceRoot":"","sources":["../src/clone-metadata.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC,CAAC,iBAAiB,GAAG,SAAS,CAAC,EAAE,CAAC,CA4B5C"}
@@ -0,0 +1,32 @@
1
+ import { assertAbsolutePathInput } from "./absolute-path.js";
2
+ import { requireNativeBinding } from "./native.js";
3
+ /** Batched APFS metadata snapshots; absent on unsupported files or filesystems.
4
+ * These are point-in-time facts, not authorization or a guarantee against later edits.
5
+ */
6
+ export async function readCloneFileMetadata(files) {
7
+ const paths = files.map(assertAbsolutePathInput);
8
+ const results = await requireNativeBinding().readCloneFileMetadata(paths);
9
+ return results.map((result) => {
10
+ if (!result ||
11
+ result.length !== 100 ||
12
+ result.readUInt32LE(0) !== 100 ||
13
+ result.readUInt32LE(4) !== 0x82038c0a ||
14
+ result.readUInt32LE(16) !== 0x200 ||
15
+ result.readUInt32LE(20) !== 0x100)
16
+ return undefined;
17
+ return {
18
+ dev: result.readUInt32LE(24),
19
+ type: result.readUInt32LE(28),
20
+ mtimeSec: Number(result.readBigInt64LE(32)),
21
+ mtimeNs: Number(result.readBigInt64LE(40)),
22
+ ctimeSec: Number(result.readBigInt64LE(48)),
23
+ ctimeNs: Number(result.readBigInt64LE(56)),
24
+ uid: result.readUInt32LE(64),
25
+ gid: result.readUInt32LE(68),
26
+ mode: result.readUInt32LE(72),
27
+ ino: result.readBigUInt64LE(76),
28
+ size: result.readBigUInt64LE(84),
29
+ cloneId: result.readBigUInt64LE(92),
30
+ };
31
+ });
32
+ }
@@ -0,0 +1,23 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ import type { FileHandle } from "node:fs/promises";
3
+ import { type CopyCloneMode } from "./copy-policy.js";
4
+ import { type NativeBinding } from "./native.js";
5
+ import type { NativeFileCopyResult } from "./native-binding.js";
6
+ export type CopyFileInput = {
7
+ kind: "file";
8
+ handle: FileHandle;
9
+ size: number;
10
+ clone: CopyCloneMode;
11
+ signal?: AbortSignal;
12
+ verifySource(): Promise<void>;
13
+ };
14
+ export declare function resolveFileCopyCloneMode(mode?: CopyCloneMode): CopyCloneMode;
15
+ export declare function assertCopySourceCurrent(source: {
16
+ handle: FileHandle;
17
+ realPath: string;
18
+ }, identity: BigIntStats): Promise<void>;
19
+ export declare function copyFileChunks(input: CopyFileInput, maxBytes?: number): AsyncGenerator<Buffer>;
20
+ export declare function writeCopyFileToFd(fd: number, input: CopyFileInput, maxBytes?: number, assertBeforeMutation?: () => void): Promise<void>;
21
+ export declare function createNativeCopyFile(native: NativeBinding, input: CopyFileInput, parentFd: number, basename: string, maxBytes: number | undefined, sync: boolean): Promise<NativeFileCopyResult | undefined>;
22
+ export declare function assertNativeCopyCompleted(input: CopyFileInput, copied?: NativeFileCopyResult): void;
23
+ //# sourceMappingURL=copy-file-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-file-input.d.ts","sourceRoot":"","sources":["../src/copy-file-input.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE5E,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAIhE,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,IAAI,CAAC,EAAE,aAAa,GAAG,aAAa,CAM5E;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAChD,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAuB,cAAc,CACnC,KAAK,EAAE,aAAa,EACpB,QAAQ,SAAW,GAClB,cAAc,CAAC,MAAM,CAAC,CAiBxB;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,aAAa,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CA8B3C;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAWnG"}
@@ -0,0 +1,91 @@
1
+ import fs, {} from "node:fs";
2
+ import { resolveCopyCloneMode } from "./copy-policy.js";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { getNativeBinding } from "./native.js";
5
+ import { inspectFileIdentity } from "./strict-file-identity.js";
6
+ import { writeAllToFile } from "./write-file-handle.js";
7
+ export function resolveFileCopyCloneMode(mode) {
8
+ const clone = resolveCopyCloneMode(mode, "never");
9
+ if (clone === "always" && !getNativeBinding()?.copyFileExclusive) {
10
+ throw new FsSafeError("helper-unavailable", "native file cloning is unavailable");
11
+ }
12
+ return clone;
13
+ }
14
+ export async function assertCopySourceCurrent(source, identity) {
15
+ await inspectFileIdentity(() => fs.fstatSync(source.handle.fd, { bigint: true }), identity);
16
+ await inspectFileIdentity(() => {
17
+ const current = fs.lstatSync(source.realPath, { bigint: true });
18
+ if (current.isSymbolicLink() || !current.isFile()) {
19
+ throw new FsSafeError("path-mismatch", "copy source path changed");
20
+ }
21
+ return current;
22
+ }, identity);
23
+ }
24
+ export async function* copyFileChunks(input, maxBytes = Infinity) {
25
+ const buffer = Buffer.allocUnsafe(Math.min(256 * 1024, Math.max(64 * 1024, input.size)));
26
+ let position = 0;
27
+ while (true) {
28
+ input.signal?.throwIfAborted();
29
+ const { bytesRead } = await input.handle.read(buffer, 0, Math.min(buffer.length, maxBytes - position + 1), position);
30
+ input.signal?.throwIfAborted();
31
+ if (bytesRead > maxBytes - position) {
32
+ throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes`);
33
+ }
34
+ if (bytesRead === 0)
35
+ return;
36
+ position += bytesRead;
37
+ // The consumer finishes writing before the next iteration reuses this buffer.
38
+ yield buffer.subarray(0, bytesRead);
39
+ }
40
+ }
41
+ export async function writeCopyFileToFd(fd, input, maxBytes, assertBeforeMutation) {
42
+ for await (const chunk of copyFileChunks(input, maxBytes)) {
43
+ await writeAllToFile(fd, chunk, {
44
+ assertBeforeMutation: () => {
45
+ input.signal?.throwIfAborted();
46
+ assertBeforeMutation?.();
47
+ },
48
+ });
49
+ }
50
+ }
51
+ export async function createNativeCopyFile(native, input, parentFd, basename, maxBytes, sync) {
52
+ input.signal?.throwIfAborted();
53
+ if (!native.copyFileExclusive) {
54
+ if (input.clone === "always") {
55
+ throw new FsSafeError("helper-unavailable", "native file cloning is unavailable");
56
+ }
57
+ return undefined;
58
+ }
59
+ const nativeSignal = input.signal ? AbortSignal.any([input.signal]) : undefined;
60
+ try {
61
+ // The caller adopts this descriptor before observing a later cancellation.
62
+ return await native.copyFileExclusive(input.handle.fd, parentFd, basename, input.clone, maxBytes !== undefined && Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal, sync);
63
+ }
64
+ catch (error) {
65
+ const code = error?.code;
66
+ if (code === "Cancelled" || code === "ABORT_ERR")
67
+ input.signal?.throwIfAborted();
68
+ if (code === "too-large") {
69
+ throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes`, { cause: error });
70
+ }
71
+ if (code === "ENOTSUP" && input.clone !== "always")
72
+ return undefined;
73
+ if (code === "ENOTSUP") {
74
+ throw new FsSafeError("unsupported-platform", "native file cloning is unsupported", { cause: error });
75
+ }
76
+ throw new FsSafeError("helper-failed", "native file copy failed", { cause: error });
77
+ }
78
+ finally {
79
+ if (nativeSignal)
80
+ nativeSignal.onabort = null;
81
+ }
82
+ }
83
+ export function assertNativeCopyCompleted(input, copied) {
84
+ if (copied?.errorCode) {
85
+ if (copied.errorCode === "Cancelled" || copied.errorCode === "ABORT_ERR")
86
+ input.signal?.throwIfAborted();
87
+ const unsupported = ["ENOTSUP", "EOPNOTSUPP", "ENOSYS", "EXDEV", "EINVAL"].includes(copied.errorCode);
88
+ throw new FsSafeError(copied.errorCode === "too-large" ? "too-large" : unsupported ? "unsupported-platform" : "helper-failed", copied.errorMessage ?? "native file copy failed", { cause: Object.assign(new Error(copied.errorMessage ?? "native file copy failed"), { code: copied.errorCode }) });
89
+ }
90
+ input.signal?.throwIfAborted();
91
+ }
@@ -0,0 +1,3 @@
1
+ export type CopyCloneMode = "auto" | "always" | "never";
2
+ export declare function resolveCopyCloneMode(mode: CopyCloneMode | undefined, defaultMode: CopyCloneMode): CopyCloneMode;
3
+ //# sourceMappingURL=copy-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-policy.d.ts","sourceRoot":"","sources":["../src/copy-policy.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAExD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,aAAa,GAAG,SAAS,EAC/B,WAAW,EAAE,aAAa,GACzB,aAAa,CAMf"}
@@ -0,0 +1,8 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ export function resolveCopyCloneMode(mode, defaultMode) {
3
+ const policy = mode === undefined ? defaultMode : mode;
4
+ if (policy !== "auto" && policy !== "always" && policy !== "never") {
5
+ throw new FsSafeError("invalid-path", "copy clone policy must be auto, always, or never");
6
+ }
7
+ return policy;
8
+ }
@@ -0,0 +1,15 @@
1
+ import type { PinnedWriteParams, PublishedWriteIdentity } from "./pinned-write.js";
2
+ export type RootCopyPublicationReceipt = Readonly<{
3
+ path: string;
4
+ dev: bigint;
5
+ ino: bigint;
6
+ }>;
7
+ export declare function createCopyPublicationObserver(path: string, notify?: (receipt: RootCopyPublicationReceipt) => void): {
8
+ onPublished(identity: PublishedWriteIdentity): void;
9
+ rethrowObserverFailure(error: unknown): void;
10
+ };
11
+ export declare const onCopyPublication: unique symbol;
12
+ export type CopyPublicationOptions = {
13
+ [onCopyPublication]?: PinnedWriteParams["verifyPublished"];
14
+ };
15
+ //# sourceMappingURL=copy-publication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-publication.d.ts","sourceRoot":"","sources":["../src/copy-publication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAGnF,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,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI;IAIpD,WAAW,WAAW,sBAAsB,GAAG,IAAI;IAcnD,sBAAsB,QAAQ,OAAO,GAAG,IAAI;EAO/C;AAID,eAAO,MAAM,iBAAiB,eAA8B,CAAC;AAC7D,MAAM,MAAM,sBAAsB,GAAG;IACnC,CAAC,iBAAiB,CAAC,CAAC,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;CAC5D,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ export function createCopyPublicationObserver(path, notify) {
3
+ let rejected;
4
+ return {
5
+ onPublished(identity) {
6
+ const receipt = Object.freeze({ path, dev: identity.dev, ino: identity.ino });
7
+ try {
8
+ const result = notify?.(receipt);
9
+ if (result !== null && (typeof result === "object" || typeof result === "function") &&
10
+ "then" in result && typeof result.then === "function") {
11
+ void Promise.resolve(result).catch(() => undefined);
12
+ throw new TypeError("onDestinationPublished must be synchronous");
13
+ }
14
+ }
15
+ catch (error) {
16
+ rejected = { error };
17
+ throw error;
18
+ }
19
+ },
20
+ rethrowObserverFailure(error) {
21
+ if (rejected && (error === rejected.error ||
22
+ (error instanceof FsSafeError && error.cause === rejected.error && error.details?.phase === "publish"))) {
23
+ throw rejected.error;
24
+ }
25
+ },
26
+ };
27
+ }
28
+ // Internal composition hook; the descriptor is borrowed until the callback returns.
29
+ // Kept off RootCopyOptions and all public package exports.
30
+ export const onCopyPublication = Symbol("onCopyPublication");
@@ -0,0 +1,9 @@
1
+ import type { NativeBinding } from "./native-binding.js";
2
+ import { openStagedDirectory } from "./staged-directory.js";
3
+ /** Byte-copy adapter for the shared immutable-source, caller-owned namespace contract. */
4
+ export declare function copyOwnedTree(source: ReturnType<typeof openStagedDirectory>, destination: string, options: {
5
+ signal?: AbortSignal;
6
+ concurrency: number;
7
+ copyFileContents?: NativeBinding["copyFileContents"];
8
+ }): Promise<void>;
9
+ //# sourceMappingURL=copy-tree-portable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-tree-portable.d.ts","sourceRoot":"","sources":["../src/copy-tree-portable.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAGL,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAE/B,0FAA0F;AAC1F,wBAAsB,aAAa,CACjC,MAAM,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,EAC9C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IACP,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACtD,GACA,OAAO,CAAC,IAAI,CAAC,CAkLf"}