@blamejs/core 0.8.43 → 0.8.49

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 (222) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/README.md +10 -10
  3. package/index.js +52 -0
  4. package/lib/a2a.js +159 -34
  5. package/lib/acme.js +762 -0
  6. package/lib/ai-pref.js +166 -43
  7. package/lib/api-key.js +108 -47
  8. package/lib/api-snapshot.js +157 -40
  9. package/lib/app-shutdown.js +113 -77
  10. package/lib/archive.js +337 -40
  11. package/lib/arg-parser.js +697 -0
  12. package/lib/asyncapi.js +99 -55
  13. package/lib/atomic-file.js +465 -104
  14. package/lib/audit-chain.js +123 -34
  15. package/lib/audit-daily-review.js +389 -0
  16. package/lib/audit-sign.js +302 -56
  17. package/lib/audit-tools.js +412 -63
  18. package/lib/audit.js +656 -35
  19. package/lib/auth/jwt-external.js +17 -0
  20. package/lib/auth/oauth.js +7 -0
  21. package/lib/auth-bot-challenge.js +505 -0
  22. package/lib/auth-header.js +92 -25
  23. package/lib/backup/bundle.js +26 -0
  24. package/lib/backup/index.js +512 -89
  25. package/lib/backup/manifest.js +168 -7
  26. package/lib/break-glass.js +415 -39
  27. package/lib/budr.js +103 -30
  28. package/lib/bundler.js +86 -66
  29. package/lib/cache.js +192 -72
  30. package/lib/chain-writer.js +65 -40
  31. package/lib/circuit-breaker.js +56 -33
  32. package/lib/cli-helpers.js +106 -75
  33. package/lib/cli.js +6 -30
  34. package/lib/cloud-events.js +99 -32
  35. package/lib/cluster-storage.js +162 -37
  36. package/lib/cluster.js +340 -49
  37. package/lib/codepoint-class.js +66 -0
  38. package/lib/compliance.js +424 -24
  39. package/lib/config-drift.js +111 -46
  40. package/lib/config.js +94 -40
  41. package/lib/consent.js +165 -18
  42. package/lib/constants.js +1 -0
  43. package/lib/content-credentials.js +153 -48
  44. package/lib/cookies.js +154 -62
  45. package/lib/credential-hash.js +133 -61
  46. package/lib/crypto-field.js +702 -18
  47. package/lib/crypto-hpke.js +256 -0
  48. package/lib/crypto.js +744 -22
  49. package/lib/csv.js +178 -35
  50. package/lib/daemon.js +456 -0
  51. package/lib/dark-patterns.js +186 -55
  52. package/lib/db-query.js +79 -2
  53. package/lib/db.js +1431 -60
  54. package/lib/ddl-change-control.js +523 -0
  55. package/lib/deprecate.js +195 -40
  56. package/lib/dev.js +82 -39
  57. package/lib/dora.js +67 -48
  58. package/lib/dr-runbook.js +368 -0
  59. package/lib/dsr.js +142 -11
  60. package/lib/dual-control.js +91 -56
  61. package/lib/events.js +120 -41
  62. package/lib/external-db-migrate.js +192 -2
  63. package/lib/external-db.js +795 -50
  64. package/lib/fapi2.js +122 -1
  65. package/lib/fda-21cfr11.js +395 -0
  66. package/lib/fdx.js +132 -2
  67. package/lib/file-type.js +87 -0
  68. package/lib/file-upload.js +93 -0
  69. package/lib/flag.js +82 -20
  70. package/lib/forms.js +132 -29
  71. package/lib/framework-error.js +169 -0
  72. package/lib/framework-schema.js +163 -35
  73. package/lib/gate-contract.js +849 -175
  74. package/lib/graphql-federation.js +68 -7
  75. package/lib/guard-all.js +172 -55
  76. package/lib/guard-archive.js +286 -124
  77. package/lib/guard-auth.js +194 -21
  78. package/lib/guard-cidr.js +190 -28
  79. package/lib/guard-csv.js +397 -51
  80. package/lib/guard-domain.js +213 -91
  81. package/lib/guard-email.js +236 -29
  82. package/lib/guard-filename.js +307 -75
  83. package/lib/guard-graphql.js +263 -30
  84. package/lib/guard-html.js +310 -116
  85. package/lib/guard-image.js +243 -30
  86. package/lib/guard-json.js +260 -54
  87. package/lib/guard-jsonpath.js +235 -23
  88. package/lib/guard-jwt.js +284 -30
  89. package/lib/guard-markdown.js +204 -22
  90. package/lib/guard-mime.js +190 -26
  91. package/lib/guard-oauth.js +277 -28
  92. package/lib/guard-pdf.js +251 -27
  93. package/lib/guard-regex.js +226 -18
  94. package/lib/guard-shell.js +229 -26
  95. package/lib/guard-svg.js +177 -10
  96. package/lib/guard-template.js +232 -21
  97. package/lib/guard-time.js +195 -29
  98. package/lib/guard-uuid.js +189 -30
  99. package/lib/guard-xml.js +259 -36
  100. package/lib/guard-yaml.js +241 -44
  101. package/lib/honeytoken.js +63 -27
  102. package/lib/html-balance.js +83 -0
  103. package/lib/http-client.js +486 -59
  104. package/lib/http-message-signature.js +582 -0
  105. package/lib/i18n.js +102 -49
  106. package/lib/iab-mspa.js +112 -32
  107. package/lib/iab-tcf.js +107 -2
  108. package/lib/inbox.js +90 -52
  109. package/lib/keychain.js +865 -0
  110. package/lib/legal-hold.js +374 -0
  111. package/lib/local-db-thin.js +320 -0
  112. package/lib/log-stream.js +281 -51
  113. package/lib/log.js +184 -86
  114. package/lib/mail-bounce.js +107 -62
  115. package/lib/mail.js +295 -58
  116. package/lib/mcp.js +108 -27
  117. package/lib/metrics.js +98 -89
  118. package/lib/middleware/age-gate.js +36 -0
  119. package/lib/middleware/ai-act-disclosure.js +37 -0
  120. package/lib/middleware/api-encrypt.js +45 -0
  121. package/lib/middleware/assetlinks.js +40 -0
  122. package/lib/middleware/asyncapi-serve.js +35 -0
  123. package/lib/middleware/attach-user.js +40 -0
  124. package/lib/middleware/bearer-auth.js +40 -0
  125. package/lib/middleware/body-parser.js +230 -0
  126. package/lib/middleware/bot-disclose.js +34 -0
  127. package/lib/middleware/bot-guard.js +39 -0
  128. package/lib/middleware/compression.js +37 -0
  129. package/lib/middleware/cookies.js +32 -0
  130. package/lib/middleware/cors.js +40 -0
  131. package/lib/middleware/csp-nonce.js +40 -0
  132. package/lib/middleware/csp-report.js +34 -0
  133. package/lib/middleware/csrf-protect.js +43 -0
  134. package/lib/middleware/daily-byte-quota.js +53 -85
  135. package/lib/middleware/db-role-for.js +40 -0
  136. package/lib/middleware/dpop.js +40 -0
  137. package/lib/middleware/error-handler.js +37 -14
  138. package/lib/middleware/fetch-metadata.js +39 -0
  139. package/lib/middleware/flag-context.js +34 -0
  140. package/lib/middleware/gpc.js +33 -0
  141. package/lib/middleware/headers.js +35 -0
  142. package/lib/middleware/health.js +46 -0
  143. package/lib/middleware/host-allowlist.js +30 -0
  144. package/lib/middleware/network-allowlist.js +38 -0
  145. package/lib/middleware/openapi-serve.js +34 -0
  146. package/lib/middleware/rate-limit.js +160 -18
  147. package/lib/middleware/request-id.js +36 -18
  148. package/lib/middleware/request-log.js +37 -0
  149. package/lib/middleware/require-aal.js +29 -0
  150. package/lib/middleware/require-auth.js +32 -0
  151. package/lib/middleware/require-bound-key.js +41 -0
  152. package/lib/middleware/require-content-type.js +32 -0
  153. package/lib/middleware/require-methods.js +27 -0
  154. package/lib/middleware/require-mtls.js +33 -0
  155. package/lib/middleware/require-step-up.js +37 -0
  156. package/lib/middleware/security-headers.js +44 -0
  157. package/lib/middleware/security-txt.js +38 -0
  158. package/lib/middleware/span-http-server.js +37 -0
  159. package/lib/middleware/sse.js +36 -0
  160. package/lib/middleware/trace-log-correlation.js +33 -0
  161. package/lib/middleware/trace-propagate.js +32 -0
  162. package/lib/middleware/tus-upload.js +90 -0
  163. package/lib/middleware/web-app-manifest.js +53 -0
  164. package/lib/mtls-ca.js +100 -70
  165. package/lib/network-byte-quota.js +308 -0
  166. package/lib/network-heartbeat.js +135 -0
  167. package/lib/network-tls.js +534 -4
  168. package/lib/network.js +103 -0
  169. package/lib/notify.js +114 -43
  170. package/lib/ntp-check.js +192 -51
  171. package/lib/observability.js +145 -47
  172. package/lib/openapi.js +90 -44
  173. package/lib/outbox.js +99 -1
  174. package/lib/pagination.js +168 -86
  175. package/lib/parsers/index.js +16 -5
  176. package/lib/permissions.js +93 -40
  177. package/lib/pqc-agent.js +84 -8
  178. package/lib/pqc-software.js +94 -60
  179. package/lib/process-spawn.js +95 -21
  180. package/lib/pubsub.js +96 -66
  181. package/lib/queue.js +375 -54
  182. package/lib/redact.js +793 -21
  183. package/lib/render.js +139 -47
  184. package/lib/request-helpers.js +485 -121
  185. package/lib/restore-bundle.js +142 -39
  186. package/lib/restore-rollback.js +136 -45
  187. package/lib/retention.js +178 -50
  188. package/lib/retry.js +116 -33
  189. package/lib/router.js +475 -23
  190. package/lib/safe-async.js +543 -94
  191. package/lib/safe-buffer.js +337 -41
  192. package/lib/safe-json.js +467 -62
  193. package/lib/safe-jsonpath.js +285 -0
  194. package/lib/safe-schema.js +631 -87
  195. package/lib/safe-sql.js +221 -59
  196. package/lib/safe-url.js +278 -46
  197. package/lib/sandbox-worker.js +135 -0
  198. package/lib/sandbox.js +358 -0
  199. package/lib/scheduler.js +135 -70
  200. package/lib/self-update.js +647 -0
  201. package/lib/session-device-binding.js +431 -0
  202. package/lib/session.js +259 -49
  203. package/lib/slug.js +138 -26
  204. package/lib/ssrf-guard.js +316 -56
  205. package/lib/storage.js +433 -70
  206. package/lib/subject.js +405 -23
  207. package/lib/template.js +148 -8
  208. package/lib/tenant-quota.js +545 -0
  209. package/lib/testing.js +440 -53
  210. package/lib/time.js +291 -23
  211. package/lib/tls-exporter.js +239 -0
  212. package/lib/tracing.js +90 -74
  213. package/lib/uuid.js +97 -22
  214. package/lib/vault/index.js +284 -22
  215. package/lib/vault/seal-pem-file.js +66 -0
  216. package/lib/watcher.js +368 -0
  217. package/lib/webhook.js +196 -63
  218. package/lib/websocket.js +393 -68
  219. package/lib/wiki-concepts.js +338 -0
  220. package/lib/worker-pool.js +464 -0
  221. package/package.json +3 -3
  222. package/sbom.cyclonedx.json +7 -7
package/lib/archive.js CHANGED
@@ -1,41 +1,57 @@
1
1
  "use strict";
2
2
  /**
3
- * archive — ZIP creation. Operator-data-export shape ("download my
4
- * data as a zip"), log archives, plain-zip exports for users.
3
+ * @module b.archive
4
+ * @nav Tools
5
+ * @title Archive
5
6
  *
6
- * var archive = b.archive.zip();
7
- * archive.addFile("readme.txt", "Hello\n");
8
- * archive.addFile("data/users.csv", csvBytes, { method: "deflate" });
9
- * archive.addFile("avatars/me.png", pngBuf, { method: "store" }); // already-compressed
10
- * var zipBytes = archive.toBuffer();
7
+ * @intro
8
+ * ZIP archive creation primitive. Operator-data-export shape
9
+ * ("download my data as a zip"), log bundling, plain-zip exports for
10
+ * end users.
11
+ *
12
+ * Two output paths:
13
+ * - `toBuffer()` builds the whole archive in memory — good for
14
+ * small-to-medium exports that fit comfortably in process RSS.
15
+ * - `toStream(writable)` deflates each entry through a piped
16
+ * zlib transform and writes the central directory only after
17
+ * every entry finalizes, so multi-GB exports never need to
18
+ * fit in memory. If any source errors mid-pipe, the destination
19
+ * is destroyed with `archive/aborted` — consumers see a broken
20
+ * stream rather than a half-archive that pretends to be complete.
21
+ *
22
+ * Compression:
23
+ * - `deflate` (default) via node:zlib's `deflateRawSync` —
24
+ * falls back to STORE when deflate didn't shrink the input.
25
+ * - `store` — no compression, for already-compressed bytes
26
+ * (PNG / JPEG / mp4).
11
27
  *
12
- * // OR write directly to disk:
13
- * archive.writeTo("/tmp/export.zip");
28
+ * Format guarantees:
29
+ * - Deterministic insertion order (entries appear in the order
30
+ * `addFile` is called; central directory matches).
31
+ * - UTF-8 file names with the APPNOTE 6.3.4 EFS bit set.
32
+ * - Path-traversal refused at `addFile`: leading `/`, backslashes,
33
+ * null bytes, and `..` segments throw `archive/bad-name`.
34
+ * - No symlink emission — only regular file entries are produced.
35
+ * - SHA3-512 fingerprint via `digest()` for operator integrity logs.
14
36
  *
15
- * Format support:
16
- * - Stored (no compression for already-compressed inputs like
17
- * PNG / JPEG / mp4)
18
- * - Deflate via node:zlib's deflateRawSync (default for everything else)
19
- * - File names with / are honored — directory entries are implicit;
20
- * extractors create the directory structure on demand
21
- * - UTF-8 file names (sets the EFS bit per APPNOTE 6.3.4)
22
- * - Modification time defaults to "now"; operators override per file
37
+ * Out of scope (v1):
38
+ * - ZIP64 (>4 GiB archives, >65535 files) `toBuffer` and
39
+ * `toStream` throw `archive/too-many-entries` past the limit;
40
+ * operators at that scale bring their own toolset.
41
+ * - ZIP-native password encryption (broken-by-design); operators
42
+ * wrap the produced bytes via `b.crypto.encryptPacked` for
43
+ * encryption-at-rest.
44
+ * - Reading / extraction write-only; operators use yauzl or
45
+ * `unzip` for read paths.
23
46
  *
24
- * Out of scope:
25
- * - ZIP64 (>4 GiB archives, >65535 files) — operators at that scale
26
- * bring their own toolset
27
- * - Encryption — `b.crypto.encryptPacked` produces a sealed bundle
28
- * for the operator's encryption-at-rest needs; ZIP-native
29
- * password encryption is broken-by-design
30
- * - Streaming write (toStream) — toBuffer() covers the "download my
31
- * data" shape; operators streaming gigabytes use the operator-side
32
- * toolset
33
- * - Reading / extraction — write-only; operators use node:zlib +
34
- * yauzl (or unzip in shell) for read paths
47
+ * @card
48
+ * ZIP archive creation primitive.
35
49
  */
36
50
  var zlib = require("node:zlib");
37
51
  var fs = require("node:fs");
38
52
  var nodeCrypto = require("node:crypto");
53
+ var stream = require("node:stream");
54
+ var streamPromises = require("node:stream/promises");
39
55
  var C = require("./constants");
40
56
  var { defineClass } = require("./framework-error");
41
57
 
@@ -85,10 +101,43 @@ function _msdosDateTime(date) {
85
101
  return { time: dosTime, date: dosDate };
86
102
  }
87
103
 
104
+ /**
105
+ * @primitive b.archive.zip
106
+ * @signature b.archive.zip()
107
+ * @since 0.4.0
108
+ * @status stable
109
+ * @related b.crypto.encryptPacked
110
+ *
111
+ * Create a new ZIP archive builder. The returned object exposes
112
+ * `addFile(name, content, opts?)`, `toBuffer()`, `toStream(writable?)`,
113
+ * `writeTo(path)`, `digest()`, and `entryCount`. Entries appear in the
114
+ * archive's central directory in insertion order — same byte output
115
+ * given the same input sequence and mtimes.
116
+ *
117
+ * `content` may be a `Buffer`, a UTF-8 `string`, or a `Readable`; only
118
+ * `toStream()` can finalize archives containing `Readable` sources
119
+ * (`toBuffer()` throws `archive/streaming-entry`).
120
+ *
121
+ * @example
122
+ * var archive = b.archive.zip();
123
+ * archive.addFile("readme.txt", "Hello\n");
124
+ * archive.addFile("data/users.csv", Buffer.from("name,age\nAda,36\n"));
125
+ * archive.addFile("avatars/me.png", Buffer.from([0x89, 0x50, 0x4e, 0x47]),
126
+ * { method: "store" }); // already-compressed
127
+ * var zipBytes = archive.toBuffer();
128
+ * archive.entryCount; // → 3
129
+ * typeof archive.digest(); // → "string" (sha3-512 hex)
130
+ *
131
+ * // Stream a multi-GB export directly to an HTTP response.
132
+ * var fs = require("node:fs");
133
+ * var big = b.archive.zip();
134
+ * big.addFile("logs/2026-q1.ndjson", fs.createReadStream("/var/log/q1.ndjson"));
135
+ * // await big.toStream(res);
136
+ */
88
137
  function zip() {
89
138
  var entries = [];
90
139
 
91
- function addFile(name, content, opts) {
140
+ function _normalizeName(name) {
92
141
  if (typeof name !== "string" || name.length === 0) {
93
142
  throw new ArchiveError("archive/bad-name", "addFile: name must be a non-empty string");
94
143
  }
@@ -103,16 +152,42 @@ function zip() {
103
152
  throw new ArchiveError("archive/bad-name", "addFile: name contains '..' segment");
104
153
  }
105
154
  }
106
- var bodyBuf;
107
- if (Buffer.isBuffer(content)) bodyBuf = content;
108
- else if (typeof content === "string") bodyBuf = Buffer.from(content, "utf8");
109
- else throw new ArchiveError("archive/bad-content",
110
- "addFile: content must be a Buffer or string, got " + typeof content);
155
+ return normalized;
156
+ }
111
157
 
158
+ function _isReadableStream(o) {
159
+ // Duck-type: Readable instance or any object exposing .pipe + .on +
160
+ // a `readable` flag / readableState. Avoids importing every consumer's
161
+ // stream class; matches Node's own stream-detection pattern.
162
+ return !!o && (o instanceof stream.Readable ||
163
+ (typeof o.pipe === "function" && typeof o.on === "function"));
164
+ }
165
+
166
+ function addFile(name, content, opts) {
167
+ var normalized = _normalizeName(name);
112
168
  opts = opts || {};
113
169
  var method = opts.method === "store" ? METHOD_STORE_ID : METHOD_DEFLATE_ID;
114
170
  var mtime = opts.mtime instanceof Date ? opts.mtime : new Date();
115
171
 
172
+ if (_isReadableStream(content)) {
173
+ // Streaming entry — content is finalized at toStream() time. CRC,
174
+ // sizes, and method-fallback-to-STORE are computed during the pipe.
175
+ entries.push({
176
+ name: normalized,
177
+ method: method,
178
+ mtime: mtime,
179
+ source: content,
180
+ kind: "stream",
181
+ });
182
+ return;
183
+ }
184
+
185
+ var bodyBuf;
186
+ if (Buffer.isBuffer(content)) bodyBuf = content;
187
+ else if (typeof content === "string") bodyBuf = Buffer.from(content, "utf8");
188
+ else throw new ArchiveError("archive/bad-content",
189
+ "addFile: content must be a Buffer, string, or Readable, got " + typeof content);
190
+
116
191
  var crc = _crc32(bodyBuf);
117
192
  var stored = bodyBuf;
118
193
  if (method === METHOD_DEFLATE_ID) {
@@ -132,12 +207,22 @@ function zip() {
132
207
  crc: crc,
133
208
  stored: stored,
134
209
  uncompressedSize: bodyBuf.length,
210
+ kind: "buffer",
135
211
  });
136
212
  }
137
213
 
138
- function _buildLocalFileHeader(entry) {
214
+ // FLAG_DATA_DESCRIPTOR — APPNOTE 4.4.4 bit 3. Set when crc / csize /
215
+ // usize are not known at LFH-write time and a 16-byte descriptor
216
+ // follows the compressed data.
217
+ var FLAG_UTF8_NAME = 0x0800;
218
+ var FLAG_DATA_DESCRIPTOR = 0x0008;
219
+ var SIG_DATA_DESCRIPTOR = 0x08074b50; // optional but conventional
220
+
221
+ function _buildLocalFileHeader(entry, opts) {
222
+ var streaming = !!(opts && opts.streaming);
139
223
  var nameBuf = Buffer.from(entry.name, "utf8");
140
224
  var dt = _msdosDateTime(entry.mtime);
225
+ var flags = FLAG_UTF8_NAME | (streaming ? FLAG_DATA_DESCRIPTOR : 0);
141
226
  // APPNOTE 4.3.7 — local file header. Offsets are byte positions
142
227
  // within the 30-byte fixed header; each route through C.BYTES.bytes
143
228
  // so the framework's byte-math discipline applies even to format-
@@ -145,27 +230,38 @@ function zip() {
145
230
  var hdr = Buffer.alloc(C.BYTES.bytes(30));
146
231
  hdr.writeUInt32LE(SIG_LFH, C.BYTES.bytes(0));
147
232
  hdr.writeUInt16LE(20, C.BYTES.bytes(4)); // version needed
148
- hdr.writeUInt16LE(0x0800, C.BYTES.bytes(6)); // flags: bit 11 = UTF-8 name
233
+ hdr.writeUInt16LE(flags, C.BYTES.bytes(6)); // flags: bit 11 UTF-8, bit 3 data-descriptor
149
234
  hdr.writeUInt16LE(entry.method, C.BYTES.bytes(0x08));
150
235
  hdr.writeUInt16LE(dt.time, C.BYTES.bytes(10));
151
236
  hdr.writeUInt16LE(dt.date, C.BYTES.bytes(12));
152
- hdr.writeUInt32LE(entry.crc, C.BYTES.bytes(14));
153
- hdr.writeUInt32LE(entry.stored.length, C.BYTES.bytes(18));
154
- hdr.writeUInt32LE(entry.uncompressedSize, C.BYTES.bytes(22));
237
+ hdr.writeUInt32LE(streaming ? 0 : entry.crc, C.BYTES.bytes(14));
238
+ hdr.writeUInt32LE(streaming ? 0 : entry.stored.length, C.BYTES.bytes(18));
239
+ hdr.writeUInt32LE(streaming ? 0 : entry.uncompressedSize, C.BYTES.bytes(22));
155
240
  hdr.writeUInt16LE(nameBuf.length, C.BYTES.bytes(26));
156
241
  hdr.writeUInt16LE(0, C.BYTES.bytes(28)); // extra field length
157
242
  return Buffer.concat([hdr, nameBuf]);
158
243
  }
159
244
 
245
+ function _buildDataDescriptor(crc, csize, usize) {
246
+ // APPNOTE 4.3.9 — 16-byte data descriptor (with optional sig dword).
247
+ var dd = Buffer.alloc(C.BYTES.bytes(16));
248
+ dd.writeUInt32LE(SIG_DATA_DESCRIPTOR, C.BYTES.bytes(0));
249
+ dd.writeUInt32LE(crc, C.BYTES.bytes(4));
250
+ dd.writeUInt32LE(csize, C.BYTES.bytes(0x08));
251
+ dd.writeUInt32LE(usize, C.BYTES.bytes(12));
252
+ return dd;
253
+ }
254
+
160
255
  function _buildCentralDirectoryEntry(entry, lfhOffset) {
161
256
  var nameBuf = Buffer.from(entry.name, "utf8");
162
257
  var dt = _msdosDateTime(entry.mtime);
258
+ var flags = FLAG_UTF8_NAME | (entry.kind === "stream" ? FLAG_DATA_DESCRIPTOR : 0);
163
259
  // APPNOTE 4.3.12 — central directory file header (46-byte fixed prefix).
164
260
  var hdr = Buffer.alloc(C.BYTES.bytes(46));
165
261
  hdr.writeUInt32LE(SIG_CFH, C.BYTES.bytes(0));
166
262
  hdr.writeUInt16LE(0x033f, C.BYTES.bytes(4)); // version made by (UNIX | 6.3)
167
263
  hdr.writeUInt16LE(20, C.BYTES.bytes(6)); // version needed
168
- hdr.writeUInt16LE(0x0800, C.BYTES.bytes(0x08)); // flags: bit 11 = UTF-8
264
+ hdr.writeUInt16LE(flags, C.BYTES.bytes(0x08)); // flags: bit 11 UTF-8, bit 3 data-descriptor (stream)
169
265
  hdr.writeUInt16LE(entry.method, C.BYTES.bytes(10));
170
266
  hdr.writeUInt16LE(dt.time, C.BYTES.bytes(12));
171
267
  hdr.writeUInt16LE(dt.date, C.BYTES.bytes(14));
@@ -187,6 +283,13 @@ function zip() {
187
283
  throw new ArchiveError("archive/too-many-entries",
188
284
  "ZIP archive cannot contain more than 65535 entries (ZIP64 unsupported in v1)");
189
285
  }
286
+ for (var k = 0; k < entries.length; k++) {
287
+ if (entries[k].kind === "stream") {
288
+ throw new ArchiveError("archive/streaming-entry",
289
+ "toBuffer cannot finalize streaming entry " + JSON.stringify(entries[k].name) +
290
+ "; use archive.toStream(writable) for archives containing Readable sources");
291
+ }
292
+ }
190
293
  var pieces = [];
191
294
  var offsets = [];
192
295
  var totalLocalBytes = 0;
@@ -224,6 +327,199 @@ function zip() {
224
327
  return buf.length;
225
328
  }
226
329
 
330
+ function _emitAudit(opts, action, outcome, metadata) {
331
+ if (!opts || !opts.audit || typeof opts.audit.safeEmit !== "function") return;
332
+ try {
333
+ opts.audit.safeEmit({
334
+ action: action,
335
+ outcome: outcome,
336
+ metadata: metadata,
337
+ });
338
+ } catch (_e) { /* drop-silent — audit sinks must never crash the producer */ }
339
+ }
340
+
341
+ function _writeChunk(writable, chunk) {
342
+ return new Promise(function (resolve, reject) {
343
+ function onError(e) {
344
+ writable.removeListener("drain", onDrain);
345
+ reject(e);
346
+ }
347
+ function onDrain() {
348
+ writable.removeListener("error", onError);
349
+ resolve();
350
+ }
351
+ var ok = writable.write(chunk);
352
+ if (ok) {
353
+ // Already flushed — no need to wait for drain.
354
+ resolve();
355
+ return;
356
+ }
357
+ writable.once("drain", onDrain);
358
+ writable.once("error", onError);
359
+ });
360
+ }
361
+
362
+ async function _streamEntry(entry, writable) {
363
+ var lfh = _buildLocalFileHeader(entry, { streaming: true });
364
+ await _writeChunk(writable, lfh);
365
+
366
+ var crc = 0xffffffff;
367
+ var usize = 0;
368
+ var csize = 0;
369
+ var method = entry.method;
370
+
371
+ // Per-byte CRC table walk reused from _crc32 (kept inline here so
372
+ // we operate on Buffer chunks rather than allocating a full body).
373
+ function _crcChunk(chunk) {
374
+ for (var i = 0; i < chunk.length; i++) {
375
+ crc = CRC32_TABLE[(crc ^ chunk[i]) & 0xff] ^ (crc >>> 8);
376
+ }
377
+ }
378
+
379
+ // CRC tap — a passthrough Transform that observes uncompressed bytes
380
+ // and updates usize / CRC before forwarding to the next stage. Avoids
381
+ // consuming the source twice via parallel listeners.
382
+ var crcTap = new stream.Transform({
383
+ transform: function (chunk, enc, cb) {
384
+ usize += chunk.length;
385
+ _crcChunk(chunk);
386
+ cb(null, chunk);
387
+ },
388
+ });
389
+
390
+ if (method === METHOD_DEFLATE_ID) {
391
+ var deflater = zlib.createDeflateRaw();
392
+ // Pipe source -> crcTap -> deflater -> writable directly. CRC is
393
+ // computed on uncompressed bytes via the tap. Atomicity is held by
394
+ // the central-directory-after-all-entries pattern: partial bytes
395
+ // may leak to dest on failure, but no EOCD is ever written, so
396
+ // consumers see a broken stream rather than a half-archive that
397
+ // pretends to be complete.
398
+ var sinkWritable = new stream.Writable({
399
+ write: function (chunk, enc, cb) {
400
+ csize += chunk.length;
401
+ var ok = writable.write(chunk);
402
+ if (ok) cb();
403
+ else writable.once("drain", function () { cb(); });
404
+ },
405
+ });
406
+ try {
407
+ await streamPromises.pipeline(entry.source, crcTap, deflater, sinkWritable);
408
+ } catch (e) {
409
+ throw new ArchiveError("archive/source-error",
410
+ "stream entry " + JSON.stringify(entry.name) + " failed: " + (e && e.message));
411
+ }
412
+ } else {
413
+ // STORE: pipe source -> crcTap -> writable. csize === usize.
414
+ var storeCollect = new stream.Writable({
415
+ write: function (chunk, enc, cb) {
416
+ csize += chunk.length;
417
+ var ok = writable.write(chunk);
418
+ if (ok) cb();
419
+ else writable.once("drain", function () { cb(); });
420
+ },
421
+ });
422
+ try {
423
+ await streamPromises.pipeline(entry.source, crcTap, storeCollect);
424
+ } catch (e) {
425
+ throw new ArchiveError("archive/source-error",
426
+ "stream entry " + JSON.stringify(entry.name) + " failed: " + (e && e.message));
427
+ }
428
+ }
429
+
430
+ crc = (crc ^ 0xffffffff) >>> 0;
431
+ var dd = _buildDataDescriptor(crc, csize, usize);
432
+ await _writeChunk(writable, dd);
433
+
434
+ // Mutate entry with finalized values so the central-directory
435
+ // build pass writes correct sizes/crc.
436
+ entry.crc = crc;
437
+ entry.stored = { length: csize }; // CDH only reads .length
438
+ entry.uncompressedSize = usize;
439
+
440
+ return lfh.length + csize + dd.length;
441
+ }
442
+
443
+ async function toStream(writable, opts) {
444
+ opts = opts || {};
445
+ var returnReadable = !writable;
446
+ var dest = writable;
447
+ if (returnReadable) {
448
+ dest = new stream.PassThrough();
449
+ } else if (typeof writable.write !== "function") {
450
+ throw new ArchiveError("archive/bad-writable",
451
+ "toStream: writable must be a Writable (or omit to receive a Readable)");
452
+ }
453
+
454
+ if (entries.length > 65535) {
455
+ throw new ArchiveError("archive/too-many-entries",
456
+ "ZIP archive cannot contain more than 65535 entries (ZIP64 unsupported in v1)");
457
+ }
458
+
459
+ var run = (async function () {
460
+ var offsets = [];
461
+ var totalLocalBytes = 0;
462
+ try {
463
+ for (var i = 0; i < entries.length; i++) {
464
+ offsets.push(totalLocalBytes);
465
+ var entry = entries[i];
466
+ if (entry.kind === "stream") {
467
+ totalLocalBytes += await _streamEntry(entry, dest);
468
+ } else {
469
+ var lfh = _buildLocalFileHeader(entry);
470
+ await _writeChunk(dest, lfh);
471
+ await _writeChunk(dest, entry.stored);
472
+ totalLocalBytes += lfh.length + entry.stored.length;
473
+ }
474
+ }
475
+ // Atomic finalize — central directory only after every entry succeeded.
476
+ var cdStart = totalLocalBytes;
477
+ var cdSize = 0;
478
+ for (var j = 0; j < entries.length; j++) {
479
+ var cdh = _buildCentralDirectoryEntry(entries[j], offsets[j]);
480
+ await _writeChunk(dest, cdh);
481
+ cdSize += cdh.length;
482
+ }
483
+ var eocd = Buffer.alloc(C.BYTES.bytes(22));
484
+ eocd.writeUInt32LE(SIG_EOCD, C.BYTES.bytes(0));
485
+ eocd.writeUInt16LE(0, C.BYTES.bytes(4));
486
+ eocd.writeUInt16LE(0, C.BYTES.bytes(6));
487
+ eocd.writeUInt16LE(entries.length, C.BYTES.bytes(0x08));
488
+ eocd.writeUInt16LE(entries.length, C.BYTES.bytes(10));
489
+ eocd.writeUInt32LE(cdSize, C.BYTES.bytes(12));
490
+ eocd.writeUInt32LE(cdStart, C.BYTES.bytes(0x10));
491
+ eocd.writeUInt16LE(0, C.BYTES.bytes(20));
492
+ await _writeChunk(dest, eocd);
493
+ if (typeof dest.end === "function") dest.end();
494
+ _emitAudit(opts, "archive.zip.streamed.completed", "success", {
495
+ entries: entries.length,
496
+ bytes: totalLocalBytes + cdSize + eocd.length,
497
+ });
498
+ } catch (e) {
499
+ // Aborted — destroy the destination so partial output is not
500
+ // mistaken for a complete archive (no central directory written).
501
+ _emitAudit(opts, "archive.zip.streamed.aborted", "failure", {
502
+ entries: entries.length,
503
+ error: e && (e.code || e.message) || String(e),
504
+ });
505
+ if (typeof dest.destroy === "function") {
506
+ dest.destroy(e instanceof ArchiveError ? e : new ArchiveError(
507
+ "archive/aborted", "archive stream aborted: " + (e && e.message || e)));
508
+ }
509
+ if (!returnReadable) throw e;
510
+ }
511
+ })();
512
+
513
+ if (returnReadable) {
514
+ // Operator gets a Readable; surface stream errors via the
515
+ // PassThrough's 'error' event (run() does not throw in that path).
516
+ run.catch(function () { /* already routed via dest.destroy */ });
517
+ return dest;
518
+ }
519
+ await run;
520
+ return undefined;
521
+ }
522
+
227
523
  function digest() {
228
524
  // SHA3-512 of the produced archive bytes — useful for operator-side
229
525
  // integrity logging on exported bundles. Matches the framework's
@@ -235,6 +531,7 @@ function zip() {
235
531
  return {
236
532
  addFile: addFile,
237
533
  toBuffer: toBuffer,
534
+ toStream: toStream,
238
535
  writeTo: writeTo,
239
536
  digest: digest,
240
537
  get entryCount() { return entries.length; },