@openclaw/fs-safe 0.9.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 (326) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/LICENSE +1 -0
  3. package/README.md +52 -4
  4. package/dist/absolute-path.d.ts.map +1 -1
  5. package/dist/absolute-path.js +3 -2
  6. package/dist/advanced.d.ts +5 -0
  7. package/dist/advanced.d.ts.map +1 -1
  8. package/dist/advanced.js +5 -0
  9. package/dist/archive-crc32.d.ts.map +1 -1
  10. package/dist/archive-crc32.js +6 -1
  11. package/dist/archive-deadline.d.ts.map +1 -1
  12. package/dist/archive-deadline.js +3 -4
  13. package/dist/archive-durability.d.ts +6 -6
  14. package/dist/archive-durability.d.ts.map +1 -1
  15. package/dist/archive-durability.js +1 -1
  16. package/dist/archive-entry.d.ts.map +1 -1
  17. package/dist/archive-entry.js +4 -5
  18. package/dist/archive-gzip-tail.d.ts +3 -0
  19. package/dist/archive-gzip-tail.d.ts.map +1 -1
  20. package/dist/archive-gzip-tail.js +23 -3
  21. package/dist/archive-input.d.ts.map +1 -1
  22. package/dist/archive-input.js +4 -2
  23. package/dist/archive-merge.d.ts +5 -1
  24. package/dist/archive-merge.d.ts.map +1 -1
  25. package/dist/archive-merge.js +16 -13
  26. package/dist/archive-native.d.ts.map +1 -1
  27. package/dist/archive-native.js +7 -6
  28. package/dist/archive-parser.wasm +0 -0
  29. package/dist/archive-read.d.ts.map +1 -1
  30. package/dist/archive-read.js +83 -74
  31. package/dist/archive-staging.d.ts +6 -3
  32. package/dist/archive-staging.d.ts.map +1 -1
  33. package/dist/archive-staging.js +42 -22
  34. package/dist/archive-tar-stream.d.ts +11 -4
  35. package/dist/archive-tar-stream.d.ts.map +1 -1
  36. package/dist/archive-tar-stream.js +23 -10
  37. package/dist/archive-tar-wasm.d.ts.map +1 -1
  38. package/dist/archive-tar-wasm.js +18 -14
  39. package/dist/archive-zip-admission.d.ts +1 -1
  40. package/dist/archive-zip-admission.d.ts.map +1 -1
  41. package/dist/archive-zip-admission.js +48 -12
  42. package/dist/archive-zip-loader.d.ts +6 -0
  43. package/dist/archive-zip-loader.d.ts.map +1 -0
  44. package/dist/archive-zip-loader.js +38 -0
  45. package/dist/archive-zip-names.d.ts.map +1 -1
  46. package/dist/archive-zip-names.js +13 -8
  47. package/dist/archive-zip-preflight.d.ts +2 -3
  48. package/dist/archive-zip-preflight.d.ts.map +1 -1
  49. package/dist/archive-zip-preflight.js +2 -34
  50. package/dist/archive.d.ts.map +1 -1
  51. package/dist/archive.js +12 -10
  52. package/dist/bounded-read.d.ts +12 -0
  53. package/dist/bounded-read.d.ts.map +1 -1
  54. package/dist/bounded-read.js +82 -45
  55. package/dist/clone-metadata.d.ts +19 -0
  56. package/dist/clone-metadata.d.ts.map +1 -0
  57. package/dist/clone-metadata.js +32 -0
  58. package/dist/copy-file-input.d.ts +22 -0
  59. package/dist/copy-file-input.d.ts.map +1 -0
  60. package/dist/copy-file-input.js +69 -0
  61. package/dist/copy-policy.d.ts +3 -0
  62. package/dist/copy-policy.d.ts.map +1 -0
  63. package/dist/copy-policy.js +8 -0
  64. package/dist/copy-publication.d.ts +10 -1
  65. package/dist/copy-publication.d.ts.map +1 -1
  66. package/dist/copy-publication.js +27 -0
  67. package/dist/copy-tree-portable.d.ts +9 -0
  68. package/dist/copy-tree-portable.d.ts.map +1 -0
  69. package/dist/copy-tree-portable.js +222 -0
  70. package/dist/copy.d.ts +16 -0
  71. package/dist/copy.d.ts.map +1 -0
  72. package/dist/copy.js +125 -0
  73. package/dist/directory-durability.d.ts.map +1 -1
  74. package/dist/directory-durability.js +5 -4
  75. package/dist/directory-guard.d.ts +11 -1
  76. package/dist/directory-guard.d.ts.map +1 -1
  77. package/dist/directory-guard.js +53 -11
  78. package/dist/durability.d.ts +1 -1
  79. package/dist/durability.d.ts.map +1 -1
  80. package/dist/durability.js +1 -1
  81. package/dist/error-detail.d.ts.map +1 -1
  82. package/dist/error-detail.js +4 -1
  83. package/dist/file-handle-transfer.d.ts +14 -0
  84. package/dist/file-handle-transfer.d.ts.map +1 -0
  85. package/dist/file-handle-transfer.js +64 -0
  86. package/dist/file-hash.d.ts +9 -2
  87. package/dist/file-hash.d.ts.map +1 -1
  88. package/dist/file-hash.js +135 -39
  89. package/dist/file-lock-sync.d.ts.map +1 -1
  90. package/dist/file-lock-sync.js +8 -4
  91. package/dist/file-store-boundary.d.ts.map +1 -1
  92. package/dist/file-store-boundary.js +7 -5
  93. package/dist/file-store-path.d.ts +3 -0
  94. package/dist/file-store-path.d.ts.map +1 -0
  95. package/dist/file-store-path.js +27 -0
  96. package/dist/file-store-prune.d.ts.map +1 -1
  97. package/dist/file-store-prune.js +6 -4
  98. package/dist/file-store-sync-write.d.ts.map +1 -1
  99. package/dist/file-store-sync-write.js +56 -44
  100. package/dist/file-store.d.ts.map +1 -1
  101. package/dist/file-store.js +2 -18
  102. package/dist/filename.d.ts.map +1 -1
  103. package/dist/filename.js +4 -13
  104. package/dist/guarded-mkdir.d.ts +1 -0
  105. package/dist/guarded-mkdir.d.ts.map +1 -1
  106. package/dist/guarded-mkdir.js +4 -2
  107. package/dist/guarded-mutation.d.ts +2 -0
  108. package/dist/guarded-mutation.d.ts.map +1 -1
  109. package/dist/guarded-mutation.js +8 -4
  110. package/dist/guest-dispatch-python.d.ts +2 -0
  111. package/dist/guest-dispatch-python.d.ts.map +1 -0
  112. package/dist/guest-dispatch-python.js +117 -0
  113. package/dist/guest-native-python.d.ts +4 -0
  114. package/dist/guest-native-python.d.ts.map +1 -0
  115. package/dist/guest-native-python.js +135 -0
  116. package/dist/guest.d.ts +9 -0
  117. package/dist/guest.d.ts.map +1 -0
  118. package/dist/guest.js +413 -0
  119. package/dist/home-dir.d.ts.map +1 -1
  120. package/dist/home-dir.js +9 -7
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/install-path.d.ts.map +1 -1
  124. package/dist/install-path.js +5 -8
  125. package/dist/json-durable-queue-directory.js +3 -3
  126. package/dist/json-durable-queue.d.ts.map +1 -1
  127. package/dist/json-durable-queue.js +19 -18
  128. package/dist/json.d.ts.map +1 -1
  129. package/dist/json.js +2 -1
  130. package/dist/local-roots.d.ts.map +1 -1
  131. package/dist/local-roots.js +24 -25
  132. package/dist/move-path-stage.d.ts +8 -0
  133. package/dist/move-path-stage.d.ts.map +1 -0
  134. package/dist/move-path-stage.js +56 -0
  135. package/dist/move-path.d.ts.map +1 -1
  136. package/dist/move-path.js +30 -34
  137. package/dist/mutation-authority.d.ts +9 -0
  138. package/dist/mutation-authority.d.ts.map +1 -0
  139. package/dist/mutation-authority.js +36 -0
  140. package/dist/native-binding.d.ts +29 -1
  141. package/dist/native-binding.d.ts.map +1 -1
  142. package/dist/native-operations.d.ts +1 -1
  143. package/dist/native-operations.d.ts.map +1 -1
  144. package/dist/native-operations.js +11 -5
  145. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  146. package/dist/native-pinned-write-windows.js +19 -7
  147. package/dist/native-pinned-write.d.ts.map +1 -1
  148. package/dist/native-pinned-write.js +3 -1
  149. package/dist/native-staged-file.d.ts +3 -3
  150. package/dist/native-staged-file.d.ts.map +1 -1
  151. package/dist/native-staged-file.js +35 -8
  152. package/dist/opened-realpath.d.ts.map +1 -1
  153. package/dist/opened-realpath.js +5 -4
  154. package/dist/output.d.ts +2 -0
  155. package/dist/output.d.ts.map +1 -1
  156. package/dist/output.js +2 -0
  157. package/dist/overwrite-file-handle.d.ts +8 -0
  158. package/dist/overwrite-file-handle.d.ts.map +1 -0
  159. package/dist/overwrite-file-handle.js +42 -0
  160. package/dist/path-case.d.ts +7 -0
  161. package/dist/path-case.d.ts.map +1 -0
  162. package/dist/path-case.js +136 -0
  163. package/dist/path.d.ts.map +1 -1
  164. package/dist/path.js +5 -2
  165. package/dist/permissions-windows.d.ts +1 -1
  166. package/dist/permissions-windows.d.ts.map +1 -1
  167. package/dist/permissions-windows.js +85 -53
  168. package/dist/pinned-open.d.ts.map +1 -1
  169. package/dist/pinned-open.js +3 -1
  170. package/dist/pinned-operation.js +1 -1
  171. package/dist/pinned-write.d.ts +7 -3
  172. package/dist/pinned-write.d.ts.map +1 -1
  173. package/dist/pinned-write.js +51 -30
  174. package/dist/positional-read.d.ts +9 -0
  175. package/dist/positional-read.d.ts.map +1 -0
  176. package/dist/positional-read.js +36 -0
  177. package/dist/private-temp-workspace.d.ts.map +1 -1
  178. package/dist/private-temp-workspace.js +6 -4
  179. package/dist/publish-copy-stage.d.ts +13 -0
  180. package/dist/publish-copy-stage.d.ts.map +1 -0
  181. package/dist/publish-copy-stage.js +47 -0
  182. package/dist/publish-file.d.ts.map +1 -1
  183. package/dist/publish-file.js +6 -5
  184. package/dist/realpath.d.ts +4 -0
  185. package/dist/realpath.d.ts.map +1 -0
  186. package/dist/realpath.js +43 -0
  187. package/dist/recursive-mkdir-path.d.ts +3 -0
  188. package/dist/recursive-mkdir-path.d.ts.map +1 -0
  189. package/dist/recursive-mkdir-path.js +8 -0
  190. package/dist/replace-directory.d.ts.map +1 -1
  191. package/dist/replace-directory.js +2 -1
  192. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  193. package/dist/replace-file-copy-fallback.js +23 -31
  194. package/dist/replace-file-copy-source.d.ts.map +1 -1
  195. package/dist/replace-file-copy-source.js +7 -12
  196. package/dist/replace-file-mode.d.ts +3 -0
  197. package/dist/replace-file-mode.d.ts.map +1 -0
  198. package/dist/replace-file-mode.js +10 -0
  199. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  200. package/dist/replace-file-temp-owner.js +6 -2
  201. package/dist/replace-file.d.ts +1 -0
  202. package/dist/replace-file.d.ts.map +1 -1
  203. package/dist/replace-file.js +14 -8
  204. package/dist/root-context.d.ts +3 -0
  205. package/dist/root-context.d.ts.map +1 -1
  206. package/dist/root-context.js +53 -28
  207. package/dist/root-create-input.d.ts +10 -0
  208. package/dist/root-create-input.d.ts.map +1 -0
  209. package/dist/root-create-input.js +80 -0
  210. package/dist/root-directory-list.d.ts +26 -0
  211. package/dist/root-directory-list.d.ts.map +1 -0
  212. package/dist/root-directory-list.js +219 -0
  213. package/dist/root-entries.d.ts +11 -0
  214. package/dist/root-entries.d.ts.map +1 -0
  215. package/dist/root-entries.js +61 -0
  216. package/dist/root-errors.d.ts +6 -5
  217. package/dist/root-errors.d.ts.map +1 -1
  218. package/dist/root-errors.js +16 -12
  219. package/dist/root-file.d.ts +2 -0
  220. package/dist/root-file.d.ts.map +1 -1
  221. package/dist/root-file.js +12 -4
  222. package/dist/root-impl.d.ts +17 -51
  223. package/dist/root-impl.d.ts.map +1 -1
  224. package/dist/root-impl.js +358 -234
  225. package/dist/root-options.d.ts +77 -0
  226. package/dist/root-options.d.ts.map +1 -0
  227. package/dist/root-options.js +18 -0
  228. package/dist/root-path-existing.d.ts +5 -0
  229. package/dist/root-path-existing.d.ts.map +1 -1
  230. package/dist/root-path-existing.js +59 -3
  231. package/dist/root-path-symlink.d.ts.map +1 -1
  232. package/dist/root-path-symlink.js +3 -2
  233. package/dist/root-path.d.ts +1 -0
  234. package/dist/root-path.d.ts.map +1 -1
  235. package/dist/root-path.js +74 -162
  236. package/dist/root-paths.d.ts.map +1 -1
  237. package/dist/root-paths.js +13 -9
  238. package/dist/root-remove.d.ts +5 -0
  239. package/dist/root-remove.d.ts.map +1 -0
  240. package/dist/root-remove.js +286 -0
  241. package/dist/root-symlink-policy.d.ts +14 -0
  242. package/dist/root-symlink-policy.d.ts.map +1 -0
  243. package/dist/root-symlink-policy.js +34 -0
  244. package/dist/root-walk.d.ts +5 -4
  245. package/dist/root-walk.d.ts.map +1 -1
  246. package/dist/root-walk.js +99 -50
  247. package/dist/root-write-mode.d.ts.map +1 -1
  248. package/dist/root-write-mode.js +3 -5
  249. package/dist/root.d.ts +4 -1
  250. package/dist/root.d.ts.map +1 -1
  251. package/dist/secret-file.d.ts.map +1 -1
  252. package/dist/secret-file.js +2 -1
  253. package/dist/secret-read-async.d.ts.map +1 -1
  254. package/dist/secret-read-async.js +2 -1
  255. package/dist/secure-file.d.ts.map +1 -1
  256. package/dist/secure-file.js +25 -8
  257. package/dist/secure-temp-dir.d.ts.map +1 -1
  258. package/dist/secure-temp-dir.js +2 -1
  259. package/dist/sibling-staged-file.d.ts +1 -0
  260. package/dist/sibling-staged-file.d.ts.map +1 -1
  261. package/dist/sibling-staged-file.js +42 -8
  262. package/dist/sibling-temp.d.ts +2 -0
  263. package/dist/sibling-temp.d.ts.map +1 -1
  264. package/dist/sibling-temp.js +6 -4
  265. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  266. package/dist/sidecar-lock-acquire.js +17 -5
  267. package/dist/sidecar-lock-policy.d.ts +2 -0
  268. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  269. package/dist/sidecar-lock-policy.js +20 -1
  270. package/dist/staged-directory.d.ts.map +1 -1
  271. package/dist/staged-directory.js +4 -3
  272. package/dist/temp-cleanup.d.ts.map +1 -1
  273. package/dist/temp-cleanup.js +3 -2
  274. package/dist/temp-target.d.ts +14 -12
  275. package/dist/temp-target.d.ts.map +1 -1
  276. package/dist/temp-target.js +12 -6
  277. package/dist/timing.d.ts +1 -0
  278. package/dist/timing.d.ts.map +1 -1
  279. package/dist/timing.js +25 -6
  280. package/dist/trash.d.ts.map +1 -1
  281. package/dist/trash.js +9 -7
  282. package/dist/unicode-path.d.ts +3 -0
  283. package/dist/unicode-path.d.ts.map +1 -0
  284. package/dist/unicode-path.js +13 -0
  285. package/dist/walk.d.ts.map +1 -1
  286. package/dist/walk.js +9 -28
  287. package/dist/windows-owner.d.ts +9 -12
  288. package/dist/windows-owner.d.ts.map +1 -1
  289. package/dist/windows-owner.js +24 -58
  290. package/dist/write-file-handle.d.ts +7 -0
  291. package/dist/write-file-handle.d.ts.map +1 -0
  292. package/dist/write-file-handle.js +26 -0
  293. package/docs/advanced.md +22 -4
  294. package/docs/archive.md +44 -9
  295. package/docs/atomic.md +24 -1
  296. package/docs/config.md +1 -0
  297. package/docs/contributing.md +36 -1
  298. package/docs/copy.md +155 -0
  299. package/docs/directory-identity.md +85 -0
  300. package/docs/durability.md +53 -3
  301. package/docs/entries.md +109 -0
  302. package/docs/errors.md +4 -4
  303. package/docs/file-store.md +15 -0
  304. package/docs/guest.md +141 -0
  305. package/docs/in-place-write.md +81 -0
  306. package/docs/index.md +2 -0
  307. package/docs/install.md +31 -0
  308. package/docs/local-roots.md +8 -1
  309. package/docs/native-helper.md +10 -3
  310. package/docs/native.md +18 -1
  311. package/docs/output.md +32 -6
  312. package/docs/path-case.md +64 -0
  313. package/docs/path-scope.md +1 -1
  314. package/docs/permissions.md +29 -10
  315. package/docs/positional-read.md +63 -0
  316. package/docs/public-api.md +31 -2
  317. package/docs/root.md +196 -6
  318. package/docs/secure-file.md +2 -0
  319. package/docs/security-model.md +14 -0
  320. package/docs/sidecar-lock.md +12 -3
  321. package/docs/temp.md +35 -6
  322. package/docs/timing.md +2 -0
  323. package/docs/types.md +19 -12
  324. package/docs/walk.md +54 -5
  325. package/docs/writing.md +173 -5
  326. package/package.json +19 -8
@@ -1,26 +1,35 @@
1
1
  import fs from "node:fs";
2
- import { Writable } from "node:stream";
2
+ import { Readable, Writable } from "node:stream";
3
3
  import { pipeline } from "node:stream/promises";
4
4
  import { createGunzip } from "node:zlib";
5
- import { GzipInput, validateGzipContainerTail } from "./archive-gzip-tail.js";
5
+ import { GzipInput, isGzipBuffer, validateGzipBufferTail, validateGzipContainerTail } from "./archive-gzip-tail.js";
6
6
  import { ArchiveFormatError } from "./archive-errors.js";
7
7
  import { TarParserStream } from "./archive-tar-wasm.js";
8
+ import { readFileWindowFully } from "./positional-read.js";
9
+ function* bufferChunks(buffer) {
10
+ for (let offset = 0; offset < buffer.length; offset += 65536)
11
+ yield buffer.subarray(offset, offset + 65536);
12
+ }
8
13
  async function gzipFile(filePath) {
9
14
  const handle = await fs.promises.open(filePath, "r");
10
15
  try {
11
16
  const magic = Buffer.alloc(2);
12
- const { bytesRead } = await handle.read(magic, 0, 2, 0);
13
- return bytesRead === 2 && magic[0] === 31 && magic[1] === 139;
17
+ const bytesRead = await readFileWindowFully(handle, magic, 0);
18
+ return bytesRead === 2 && isGzipBuffer(magic);
14
19
  }
15
20
  finally {
16
21
  await handle.close();
17
22
  }
18
23
  }
19
24
  async function withTarStream(params, consume) {
20
- const gzip = await gzipFile(params.archivePath);
25
+ const buffer = params.archiveBuffer;
26
+ const gzip = buffer !== undefined ? isGzipBuffer(buffer) : await gzipFile(params.archivePath);
21
27
  const parser = new TarParserStream(params.limits, params.onMember);
22
- const input = fs.createReadStream(params.archivePath, { highWaterMark: 65536 });
23
- const decoder = gzip ? createGunzip() : undefined;
28
+ const input = buffer !== undefined
29
+ ? Readable.from(bufferChunks(buffer), { objectMode: false, highWaterMark: 65536 })
30
+ : fs.createReadStream(params.archivePath, { highWaterMark: 65536 });
31
+ // Match the WASM input window for both staged files and buffered reads.
32
+ const decoder = gzip ? createGunzip({ chunkSize: 65536 }) : undefined;
24
33
  const gzipInput = decoder ? new GzipInput(decoder) : undefined;
25
34
  const destroy = (error) => {
26
35
  input.destroy(error);
@@ -42,8 +51,12 @@ async function withTarStream(params, consume) {
42
51
  const error = await settled;
43
52
  if (error)
44
53
  throw error;
45
- if (gzipInput)
46
- await validateGzipContainerTail(params.archivePath, gzipInput.tailOffset, params.signal);
54
+ if (gzipInput) {
55
+ if (buffer !== undefined)
56
+ await validateGzipBufferTail(buffer, gzipInput.tailOffset, params.signal);
57
+ else
58
+ await validateGzipContainerTail(params.archivePath, gzipInput.tailOffset, params.signal);
59
+ }
47
60
  return result;
48
61
  }
49
62
  finally {
@@ -57,7 +70,7 @@ export async function inspectTar(params) {
57
70
  });
58
71
  }
59
72
  /** Replay in physical order, retaining at most one decoded chunk. Every range
60
- * comes from complete admission of the immutable staged input. */
73
+ * comes from complete admission of the immutable input. */
61
74
  export async function replayTar(params) {
62
75
  await withTarStream(params, async (parser) => {
63
76
  const iterator = parser[Symbol.asyncIterator]();
@@ -1 +1 @@
1
- {"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAuClE,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,SAAS;IAER,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAD9D,OAAO,CAAC,GAAG,CAAkB;IAC7B,YAAY,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,aAAA,EAQjG;IACD,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,IAAI;IAIH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAkB/F;IACQ,MAAM,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAKjD;IACQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAKpF;CACF"}
1
+ {"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAyClE,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,SAAS;IAER,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAD9D,OAAO,CAAC,GAAG,CAAkB;IAC7B,YAAY,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,aAAA,EAQjG;IACD,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,IAAI;IAIH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAqB/F;IACQ,MAAM,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAKjD;IACQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAKpF;CACF"}
@@ -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,26 +48,29 @@ 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 {
54
55
  const abi = this.abi;
55
- for (let offset = 0; offset < chunk.length;) {
56
+ for (let offset = 0; offset < chunk.length; offset += 65536) {
56
57
  const length = Math.min(65536, chunk.length - offset);
57
58
  this.bytes(abi.input_ptr(), length).set(chunk.subarray(offset, offset + length));
58
- const used = abi.push(length);
59
- if (used < 0)
60
- throw parserError(this.text());
61
- if (used === 0 || used > length)
62
- throw new ArchiveFormatError("TAR WASM made no progress");
63
- offset += used;
64
- const type = abi.member_type();
65
- if (type >= 0)
66
- this.onMember?.({
67
- path: this.text(), type: types.get(type) ?? "Unsupported", size: abi.member_size(),
68
- mode: abi.member_mode(), offset: abi.member_offset(),
69
- });
59
+ // A member event can stop before the end of this already-copied inbox.
60
+ for (let consumed = 0; consumed < length;) {
61
+ const used = abi.push(consumed, length - consumed);
62
+ if (used < 0)
63
+ throw parserError(this.text());
64
+ if (used === 0 || used > length - consumed)
65
+ throw new ArchiveFormatError("TAR WASM made no progress");
66
+ consumed += used;
67
+ const type = abi.member_type();
68
+ if (type >= 0)
69
+ this.onMember?.({
70
+ path: this.text(), type: types.get(type) ?? "Unsupported", size: abi.member_size(),
71
+ mode: abi.member_mode(), offset: abi.member_offset(),
72
+ });
73
+ }
70
74
  }
71
75
  callback(null, chunk);
72
76
  }
@@ -3,6 +3,6 @@ import { type ResolvedArchiveExtractLimits } from "./archive-limits.js";
3
3
  import { type ZipDirectoryEntry } from "./archive-zip-directory.js";
4
4
  /** Admit physical records before a decoder can normalize or collapse names. */
5
5
  export declare function admitZipBuffer(input: Uint8Array, limits: ResolvedArchiveExtractLimits): number;
6
- /** The extraction input is already private and staged; read only bounded metadata. */
6
+ /** The extraction input is private and immutable; read bounded metadata windows. */
7
7
  export declare function admitZipFile(archivePath: string, limits: ResolvedArchiveExtractLimits, deadline: ExtractionDeadline, onEntry?: (entry: ZipDirectoryEntry) => void): Promise<number>;
8
8
  //# sourceMappingURL=archive-zip-admission.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-admission.d.ts","sourceRoot":"","sources":["../src/archive-zip-admission.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAA+C,KAAK,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAErH,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAUtF,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,4BAA4B,GAAG,MAAM,CAU9F;AAED,sFAAsF;AACtF,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,QAAQ,EAAE,kBAAkB,EACvF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC3C,OAAO,CAAC,MAAM,CAAC,CA+BjB"}
1
+ {"version":3,"file":"archive-zip-admission.d.ts","sourceRoot":"","sources":["../src/archive-zip-admission.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAA+C,KAAK,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAErH,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAUtF,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,4BAA4B,GAAG,MAAM,CAU9F;AAMD,oFAAoF;AACpF,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,QAAQ,EAAE,kBAAkB,EACvF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC3C,OAAO,CAAC,MAAM,CAAC,CA8DjB"}
@@ -23,7 +23,9 @@ export function admitZipBuffer(input, limits) {
23
23
  }
24
24
  return step.value;
25
25
  }
26
- /** The extraction input is already private and staged; read only bounded metadata. */
26
+ const METADATA_WINDOW_BYTES = 4096;
27
+ const CACHED_READS_PER_YIELD = 32;
28
+ /** The extraction input is private and immutable; read bounded metadata windows. */
27
29
  export async function admitZipFile(archivePath, limits, deadline, onEntry) {
28
30
  deadline.check();
29
31
  const opened = await openRootFile({
@@ -35,20 +37,54 @@ export async function admitZipFile(archivePath, limits, deadline, onEntry) {
35
37
  try {
36
38
  checkSize(opened.stat.size, limits);
37
39
  const scan = scanZipDirectory(opened.stat.size, limits, onEntry);
40
+ // Separate windows keep alternating central and local records hot. Never
41
+ // refill a buffer: the scanner can retain its views after cache eviction.
42
+ const windows = [];
43
+ let cachedReads = 0;
38
44
  let step = scan.next();
39
45
  while (!step.done) {
46
+ deadline.check();
40
47
  const { offset, length } = step.value;
41
- const bytes = Buffer.alloc(length);
42
- let received = 0;
43
- while (received < length) {
44
- deadline.check();
45
- const count = await new Promise((resolve, reject) => {
46
- fs.read(opened.fd, bytes, received, length - received, offset + received, (error, count) => error ? reject(error) : resolve(count));
47
- });
48
- deadline.check();
49
- if (!count)
50
- zipFormat("truncated staged record");
51
- received += count;
48
+ const cached = windows.findIndex(window => offset >= window.offset &&
49
+ length <= window.bytes.length - (offset - window.offset));
50
+ let bytes;
51
+ if (cached !== -1) {
52
+ if (++cachedReads >= CACHED_READS_PER_YIELD) {
53
+ cachedReads = 0;
54
+ await new Promise(resolve => setImmediate(resolve));
55
+ deadline.check();
56
+ }
57
+ const window = windows[cached];
58
+ if (cached === 1)
59
+ windows.reverse();
60
+ bytes = window.bytes.subarray(offset - window.offset, offset - window.offset + length);
61
+ }
62
+ else {
63
+ const cacheable = length > 0 && length <= METADATA_WINDOW_BYTES;
64
+ const capacity = cacheable ? Math.min(METADATA_WINDOW_BYTES, opened.stat.size - offset) : length;
65
+ const buffer = Buffer.alloc(capacity);
66
+ let received = 0;
67
+ while (received < capacity) {
68
+ deadline.check();
69
+ const count = await new Promise((resolve, reject) => {
70
+ fs.read(opened.fd, buffer, received, capacity - received, offset + received, (error, count) => error ? reject(error) : resolve(count));
71
+ });
72
+ cachedReads = 0;
73
+ deadline.check();
74
+ if (!count) {
75
+ if (received < length)
76
+ zipFormat("truncated staged record");
77
+ break;
78
+ }
79
+ received += count;
80
+ }
81
+ const filled = buffer.subarray(0, received);
82
+ bytes = filled.subarray(0, length);
83
+ if (cacheable) {
84
+ windows.unshift({ offset, bytes: filled });
85
+ if (windows.length > 2)
86
+ windows.pop();
87
+ }
52
88
  }
53
89
  step = scan.next(bytes);
54
90
  }
@@ -0,0 +1,6 @@
1
+ export type ZipArchiveWithFiles = {
2
+ files: Record<string, unknown>;
3
+ };
4
+ /** Internal: the caller has admitted these unchanged bytes and their physical count. */
5
+ export declare function loadAdmittedZipArchive(buffer: Buffer | Uint8Array, entryCount: number): Promise<ZipArchiveWithFiles>;
6
+ //# sourceMappingURL=archive-zip-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-zip-loader.d.ts","sourceRoot":"","sources":["../src/archive-zip-loader.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAMF,wFAAwF;AACxF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAoB9B"}
@@ -0,0 +1,38 @@
1
+ import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
2
+ import { validateArchiveEntryPath } from "./archive-entry.js";
3
+ /** Internal: the caller has admitted these unchanged bytes and their physical count. */
4
+ export async function loadAdmittedZipArchive(buffer, entryCount) {
5
+ const JSZip = await importOptionalJsZip();
6
+ let archive;
7
+ try {
8
+ archive = await JSZip.loadAsync(buffer);
9
+ }
10
+ catch (error) {
11
+ throw new ArchiveFormatError(`invalid ZIP archive: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined });
12
+ }
13
+ const names = Object.keys(archive.files);
14
+ if (names.length !== entryCount) {
15
+ throw new ArchiveSecurityError("entry-path", "zip archive contains duplicate or colliding entry names");
16
+ }
17
+ for (const name of names)
18
+ validateArchiveEntryPath(name);
19
+ return archive;
20
+ }
21
+ async function importOptionalJsZip() {
22
+ try {
23
+ const module = await import("jszip");
24
+ const candidate = typeof module === "function" ? module : module.default;
25
+ if ((typeof candidate !== "object" && typeof candidate !== "function") ||
26
+ candidate === null ||
27
+ typeof candidate.loadAsync !== "function") {
28
+ throw new Error('Optional archive dependency "jszip" does not expose loadAsync().');
29
+ }
30
+ return candidate;
31
+ }
32
+ catch (err) {
33
+ throw missingOptionalArchiveDependencyError("jszip", err);
34
+ }
35
+ }
36
+ function missingOptionalArchiveDependencyError(packageName, cause) {
37
+ return new Error(`Optional archive dependency "${packageName}" is not installed. Install it to use ZIP archive helpers from @openclaw/fs-safe/archive.`, { cause });
38
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-names.d.ts","sourceRoot":"","sources":["../src/archive-zip-names.ts"],"names":[],"mappings":"AAIA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcjE;AA6BD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,GAAG,MAAM,GAAG,SAAS,CA8BrB"}
1
+ {"version":3,"file":"archive-zip-names.d.ts","sourceRoot":"","sources":["../src/archive-zip-names.ts"],"names":[],"mappings":"AAIA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcjE;AA+BD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,GAAG,MAAM,GAAG,SAAS,CAiCrB"}
@@ -31,9 +31,10 @@ export function zipExtraFields(bytes) {
31
31
  }
32
32
  return critical;
33
33
  }
34
+ const utf8Decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
34
35
  function utf8(bytes) {
35
36
  try {
36
- return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
37
+ return utf8Decoder.decode(bytes);
37
38
  }
38
39
  catch {
39
40
  return zipFormat("invalid UTF-8 name");
@@ -42,10 +43,10 @@ function utf8(bytes) {
42
43
  function key(name) {
43
44
  return stripArchivePath(name, 0) ?? "";
44
45
  }
45
- function originalName(name, flags) {
46
+ function originalName(name, rawName, flags) {
46
47
  // ASCII path syntax is encoding-independent. Do not reinterpret legacy bytes
47
48
  // as UTF-8 (native ZIP uses CP437); the selected decoder still validates its name.
48
- validateArchiveEntryPath(Array.from(name, (byte) => byte < 128 ? String.fromCharCode(byte) : "_").join(""));
49
+ validateArchiveEntryPath(rawName.replace(/[\x80-\xff]/g, "_"));
49
50
  if (!(flags & 0x800))
50
51
  return undefined;
51
52
  const decoded = utf8(name);
@@ -68,15 +69,19 @@ export function admitZipNames(params) {
68
69
  const { central, local, flags, centralExtra, localExtra, seen } = params;
69
70
  if (!central.length || !local.length)
70
71
  zipFormat("empty entry name");
71
- const centralUtf8 = originalName(central, flags);
72
- const localUtf8 = originalName(local, flags);
72
+ const centralRaw = central.toString("latin1");
73
+ const localRaw = local.toString("latin1");
74
+ const centralUtf8 = originalName(central, centralRaw, flags);
75
+ const localUtf8 = originalName(local, localRaw, flags);
73
76
  const centralUnicode = unicodeName(central, centralExtra);
74
77
  const localUnicode = unicodeName(local, localExtra);
75
- if (key(central.toString("latin1")) !== key(local.toString("latin1"))) {
78
+ const centralKey = key(centralRaw);
79
+ if (centralKey !== key(localRaw)) {
76
80
  zipFormat("central and local names disagree");
77
81
  }
78
82
  const interpretations = [centralUtf8, localUtf8, centralUnicode, localUnicode].filter((value) => value !== undefined);
79
- if (interpretations.some((value) => key(value) !== key(interpretations[0]))) {
83
+ const interpretationKey = interpretations.length ? key(interpretations[0]) : undefined;
84
+ if (interpretations.some((value) => key(value) !== interpretationKey)) {
80
85
  zipFormat("conflicting Unicode name interpretations");
81
86
  }
82
87
  // JSZip checks the central Unicode field against the local name. A slash-only
@@ -88,7 +93,7 @@ export function admitZipNames(params) {
88
93
  if (!centralUnicode && localUnicode && key(localUnicode) !== key(local.toString("utf8"))) {
89
94
  zipFormat("local-only Unicode override changes the name");
90
95
  }
91
- const identities = new Set([key(central.toString("latin1"))]);
96
+ const identities = new Set([centralKey]);
92
97
  if (centralUnicode !== undefined)
93
98
  identities.add(key(Buffer.from(centralUnicode).toString("latin1")));
94
99
  if ([...identities].some((identity) => seen.has(identity))) {
@@ -1,7 +1,6 @@
1
1
  import { type ArchiveExtractLimits } from "./archive-limits.js";
2
+ import { type ZipArchiveWithFiles } from "./archive-zip-loader.js";
2
3
  export { readZipCentralDirectoryEntryCount } from "./archive-zip-count.js";
3
- export type ZipArchiveWithFiles = {
4
- files: Record<string, unknown>;
5
- };
4
+ export type { ZipArchiveWithFiles } from "./archive-zip-loader.js";
6
5
  export declare function loadZipArchiveWithPreflight(buffer: Buffer | Uint8Array, limits?: ArchiveExtractLimits): Promise<ZipArchiveWithFiles>;
7
6
  //# sourceMappingURL=archive-zip-preflight.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-preflight.d.ts","sourceRoot":"","sources":["../src/archive-zip-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAE,iCAAiC,EAAE,MAAM,wBAAwB,CAAC;AAE3E,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAMF,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,MAAM,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAwB9B"}
1
+ {"version":3,"file":"archive-zip-preflight.d.ts","sourceRoot":"","sources":["../src/archive-zip-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC3F,OAAO,EAAE,iCAAiC,EAAE,MAAM,wBAAwB,CAAC;AAC3E,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,MAAM,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAO9B"}
@@ -1,7 +1,6 @@
1
1
  import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, resolveExtractLimits, } from "./archive-limits.js";
2
- import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
3
2
  import { admitZipBuffer } from "./archive-zip-admission.js";
4
- import { validateArchiveEntryPath } from "./archive-entry.js";
3
+ import { loadAdmittedZipArchive } from "./archive-zip-loader.js";
5
4
  export { readZipCentralDirectoryEntryCount } from "./archive-zip-count.js";
6
5
  export async function loadZipArchiveWithPreflight(buffer, limits) {
7
6
  const resolvedLimits = resolveExtractLimits(limits);
@@ -9,36 +8,5 @@ export async function loadZipArchiveWithPreflight(buffer, limits) {
9
8
  throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
10
9
  }
11
10
  const entryCount = admitZipBuffer(buffer, resolvedLimits);
12
- const JSZip = await importOptionalJsZip();
13
- let archive;
14
- try {
15
- archive = await JSZip.loadAsync(buffer);
16
- }
17
- catch (error) {
18
- throw new ArchiveFormatError(`invalid ZIP archive: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined });
19
- }
20
- if (Object.keys(archive.files).length !== entryCount) {
21
- throw new ArchiveSecurityError("entry-path", "zip archive contains duplicate or colliding entry names");
22
- }
23
- for (const name of Object.keys(archive.files))
24
- validateArchiveEntryPath(name);
25
- return archive;
26
- }
27
- async function importOptionalJsZip() {
28
- try {
29
- const module = await import("jszip");
30
- const candidate = typeof module === "function" ? module : module.default;
31
- if ((typeof candidate !== "object" && typeof candidate !== "function") ||
32
- candidate === null ||
33
- typeof candidate.loadAsync !== "function") {
34
- throw new Error('Optional archive dependency "jszip" does not expose loadAsync().');
35
- }
36
- return candidate;
37
- }
38
- catch (err) {
39
- throw missingOptionalArchiveDependencyError("jszip", err);
40
- }
41
- }
42
- function missingOptionalArchiveDependencyError(packageName, cause) {
43
- return new Error(`Optional archive dependency "${packageName}" is not installed. Install it to use ZIP archive helpers from @openclaw/fs-safe/archive.`, { cause });
11
+ return await loadAdmittedZipArchive(buffer, entryCount);
44
12
  }
@@ -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;AAiNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CjF"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGlE,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 fsSync, { constants as fsConstants } from "node:fs";
4
+ import { 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";
@@ -10,7 +10,7 @@ import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchiveP
10
10
  import { createPipelineTimeoutError, waitForDeadline, withExtractionDeadline, } from "./archive-deadline.js";
11
11
  import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, resolveTarMeterLimits, } from "./archive-limits.js";
12
12
  import { assertPortableArchiveKind, resolveArchiveKind } from "./archive-kind.js";
13
- import { prepareArchiveDestinationDir, preparePrivateArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
13
+ import { prepareArchiveDestinationGuard, preparePrivateArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
14
14
  import { mergePlannedArchiveIntoDestination } from "./archive-merge.js";
15
15
  import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
16
16
  import { isZipSymlinkEntry, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
@@ -22,6 +22,7 @@ import { stageArchiveFileForExtraction } from "./archive-input.js";
22
22
  import { getNativeBinding } from "./native.js";
23
23
  import { resolveArchiveFilteredEntryPolicy, shouldExtractArchiveEntry, } from "./archive-policy.js";
24
24
  import { writeSiblingTempFile } from "./sibling-temp.js";
25
+ import { realpathSync } from "./realpath.js";
25
26
  export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
26
27
  export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
27
28
  export { readArchiveEntry } from "./archive-read.js";
@@ -122,7 +123,8 @@ async function extractZip(params) {
122
123
  deadline: params.deadline,
123
124
  });
124
125
  try {
125
- const destinationRealDir = await prepareArchiveDestinationDir(params.destDir);
126
+ const destinationGuard = await prepareArchiveDestinationGuard(params.destDir);
127
+ const destinationRealDir = destinationGuard.realPath;
126
128
  params.deadline.check();
127
129
  const buffer = await fs.readFile(stagedArchive.path, { signal: params.deadline.signal });
128
130
  params.deadline.check();
@@ -136,7 +138,7 @@ async function extractZip(params) {
136
138
  await withStagedArchiveDestination({
137
139
  destinationRealDir,
138
140
  run: async (stagingDir) => {
139
- const stagingRealDir = fsSync.realpathSync.native(stagingDir);
141
+ const stagingRealDir = realpathSync.native(stagingDir);
140
142
  const acceptedEntries = [];
141
143
  for (const entry of entries) {
142
144
  params.deadline.check();
@@ -189,8 +191,7 @@ async function extractZip(params) {
189
191
  entries: acceptedEntries,
190
192
  durable: params.durable,
191
193
  sourceDir: stagingRealDir,
192
- destinationDir: params.destDir,
193
- destinationRealDir,
194
+ destinationGuard,
194
195
  deadline: params.deadline,
195
196
  });
196
197
  params.deadline.check();
@@ -248,9 +249,10 @@ async function extractWasmTar(params) {
248
249
  await inspectTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal,
249
250
  onMember: (entry) => { manifest.push(entry); } });
250
251
  deadline.check();
251
- const destinationRealDir = await prepareArchiveDestinationDir(options.destDir);
252
+ const destinationGuard = await prepareArchiveDestinationGuard(options.destDir);
253
+ const destinationRealDir = destinationGuard.realPath;
252
254
  await withStagedArchiveDestination({ destinationRealDir, run: async (stagingPath) => {
253
- const stagingDir = fsSync.realpathSync.native(stagingPath);
255
+ const stagingDir = realpathSync.native(stagingPath);
254
256
  const planEntry = createTarEntryPlanner({ ...options, rootDir: destinationRealDir, limits: params.limits });
255
257
  const accepted = manifest.flatMap((entry) => {
256
258
  deadline.check();
@@ -273,8 +275,8 @@ async function extractWasmTar(params) {
273
275
  },
274
276
  });
275
277
  deadline.check();
276
- await mergePlannedArchiveIntoDestination({ entries: accepted, sourceDir: stagingDir,
277
- destinationDir: options.destDir, destinationRealDir, deadline, durable: options.durable });
278
+ await mergePlannedArchiveIntoDestination({ entries: accepted, sourceDir: stagingDir, destinationGuard,
279
+ deadline, durable: options.durable });
278
280
  deadline.check();
279
281
  } });
280
282
  }
@@ -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
@@ -8,6 +15,11 @@ type ReadableFileHandle = Pick<FileHandle, "read">;
8
15
  export declare function readFileHandleBounded(handle: ReadableFileHandle, maxBytes: number): Promise<Buffer>;
9
16
  /** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
10
17
  export declare function readFileDescriptorBounded(fd: number, maxBytes: number): Promise<Buffer>;
18
+ export declare function readBoundedSync(maxBytes: number, readChunk: (scratch: Buffer, length: number) => number, options?: {
19
+ observeRegularFileSize?: () => number | undefined;
20
+ initialSize?: number;
21
+ createLimitError?: () => Error;
22
+ }): Buffer;
11
23
  /** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
12
24
  export declare function readFileDescriptorBoundedSync(fd: number, maxBytes: number): Buffer;
13
25
  export {};
@@ -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,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,EACtD,OAAO,GAAE;IACP,sBAAsB,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,KAAK,CAAC;CAC3B,GACL,MAAM,CAoBR;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMlF"}