manifest 6.0.0 → 7.0.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.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,19 +17,30 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
31
  var index_exports = {};
22
32
  __export(index_exports, {
23
33
  VERSION: () => VERSION,
24
- flush: () => flush,
25
34
  manifest: () => manifest
26
35
  });
27
36
  module.exports = __toCommonJS(index_exports);
28
37
 
29
- // src/runtime.ts
30
- var import_node_crypto = require("crypto");
38
+ // src/http.ts
39
+ var import_node_http = __toESM(require("http"), 1);
40
+ var import_node_https = __toESM(require("https"), 1);
41
+ var import_node_module = require("module");
42
+ var import_node_stream = require("stream");
43
+ var import_node_zlib = require("zlib");
31
44
 
32
45
  // src/wire.ts
33
46
  var secretNames = /* @__PURE__ */ new Set([
@@ -101,6 +114,118 @@ function parseJson(bytes) {
101
114
  return null;
102
115
  }
103
116
  }
117
+ var unsafeFormKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
118
+ var arrayIndex = (value) => /^(0|[1-9]\d*)$/.test(value) && Number(value) <= 1e5;
119
+ var formEncoded = (contentType) => contentType?.split(";", 1)[0]?.trim().toLowerCase() === "application/x-www-form-urlencoded";
120
+ function formPath(key) {
121
+ const bracket = key.indexOf("[");
122
+ const root = bracket === -1 ? key : key.slice(0, bracket);
123
+ if (!root || root.includes("]")) return null;
124
+ const path = [root];
125
+ let offset = root.length;
126
+ while (offset < key.length) {
127
+ if (key[offset] !== "[") return null;
128
+ const end = key.indexOf("]", offset + 1);
129
+ if (end === -1) return null;
130
+ const part = key.slice(offset + 1, end);
131
+ if (part.includes("[")) return null;
132
+ path.push(part);
133
+ offset = end + 1;
134
+ }
135
+ return path.length <= 64 && path.every((part) => !unsafeFormKeys.has(part)) ? path : null;
136
+ }
137
+ function putFormValue(root, path, value) {
138
+ let current = root;
139
+ for (let index = 0; index < path.length; index++) {
140
+ const part = path[index];
141
+ const last = index === path.length - 1;
142
+ if (Array.isArray(current)) {
143
+ if (part !== "" && !arrayIndex(part)) return false;
144
+ const position = part === "" ? current.length : Number(part);
145
+ if (last) {
146
+ const existing3 = Object.hasOwn(current, position) ? current[position] : void 0;
147
+ if (existing3 === void 0) current[position] = value;
148
+ else if (typeof existing3 === "string") current[position] = [existing3, value];
149
+ else if (Array.isArray(existing3) && existing3.every((item) => typeof item === "string")) existing3.push(value);
150
+ else return false;
151
+ continue;
152
+ }
153
+ const next2 = path[index + 1];
154
+ const needsArray2 = next2 === "" || arrayIndex(next2);
155
+ const existing2 = Object.hasOwn(current, position) ? current[position] : void 0;
156
+ if (existing2 === void 0) current[position] = needsArray2 ? [] : {};
157
+ else if (typeof existing2 !== "object" || existing2 === null || Array.isArray(existing2) !== needsArray2) return false;
158
+ current = current[position];
159
+ continue;
160
+ }
161
+ if (!part) return false;
162
+ if (last) {
163
+ const existing2 = Object.hasOwn(current, part) ? current[part] : void 0;
164
+ if (existing2 === void 0) current[part] = value;
165
+ else if (typeof existing2 === "string") current[part] = [existing2, value];
166
+ else if (Array.isArray(existing2) && existing2.every((item) => typeof item === "string")) existing2.push(value);
167
+ else return false;
168
+ continue;
169
+ }
170
+ const next = path[index + 1];
171
+ const needsArray = next === "" || arrayIndex(next);
172
+ const existing = Object.hasOwn(current, part) ? current[part] : void 0;
173
+ if (existing === void 0) current[part] = needsArray ? [] : {};
174
+ else if (typeof existing !== "object" || existing === null || Array.isArray(existing) !== needsArray) return false;
175
+ current = current[part];
176
+ }
177
+ return true;
178
+ }
179
+ function parseForm(bytes) {
180
+ try {
181
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
182
+ const body = {};
183
+ let fields = 0;
184
+ for (const pair of text.split("&")) {
185
+ if (!pair) continue;
186
+ if (++fields > 1e5) return { body: null, valid: false };
187
+ const separator = pair.indexOf("=");
188
+ const rawKey = separator === -1 ? pair : pair.slice(0, separator);
189
+ const rawValue = separator === -1 ? "" : pair.slice(separator + 1);
190
+ const key = decodeURIComponent(rawKey.replaceAll("+", " "));
191
+ const value = decodeURIComponent(rawValue.replaceAll("+", " "));
192
+ const path = formPath(key);
193
+ if (!path || !putFormValue(body, path, value)) return { body: null, valid: false };
194
+ }
195
+ return boundedJson(body) ? { body, valid: true } : { body: null, valid: false };
196
+ } catch {
197
+ return { body: null, valid: false };
198
+ }
199
+ }
200
+ function parseRequestBody(bytes, contentType) {
201
+ return formEncoded(contentType) ? parseForm(bytes) : { body: parseJson(bytes), valid: true };
202
+ }
203
+ function serializeRequestBody(body, contentType) {
204
+ if (!formEncoded(contentType)) return JSON.stringify(body);
205
+ if (!isObject(body)) throw new TypeError("Form body must be an object");
206
+ const params = new URLSearchParams();
207
+ let fields = 0;
208
+ const append = (value, key, depth) => {
209
+ if (++fields > 1e5 || depth > 64) throw new TypeError("Form body exceeds structural limits");
210
+ if (Array.isArray(value)) {
211
+ for (let index = 0; index < value.length; index++) {
212
+ if (Object.hasOwn(value, index)) append(value[index], `${key}[${index}]`, depth + 1);
213
+ }
214
+ } else if (isObject(value)) {
215
+ for (const [name, child] of Object.entries(value)) {
216
+ if (unsafeFormKeys.has(name)) throw new TypeError("Form body contains an unsafe key");
217
+ append(child, key ? `${key}[${name}]` : name, depth + 1);
218
+ }
219
+ } else if (value === null) params.append(key, "");
220
+ else if (typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) params.append(key, String(value));
221
+ else throw new TypeError("Form body contains an unsupported value");
222
+ };
223
+ for (const [key, value] of Object.entries(body)) {
224
+ if (unsafeFormKeys.has(key)) throw new TypeError("Form body contains an unsafe key");
225
+ append(value, key, 1);
226
+ }
227
+ return params.toString();
228
+ }
104
229
  function errorBody(bytes) {
105
230
  const parsed = parseJson(bytes);
106
231
  const text = Buffer.from(bytes).toString("utf8");
@@ -146,7 +271,9 @@ async function captureRequest(request) {
146
271
  const reader = clone.body.getReader();
147
272
  try {
148
273
  const result = await prefix(reader, REQUEST_LIMIT, CAPTURE_MS);
149
- return { body: result.complete ? parseJson(result.bytes) : null, complete: result.complete };
274
+ if (!result.complete) return { body: null, complete: false };
275
+ const parsed = parseRequestBody(result.bytes, request.headers.get("content-type"));
276
+ return { body: parsed.body, complete: parsed.valid };
150
277
  } finally {
151
278
  void reader.cancel().catch(() => {
152
279
  });
@@ -197,8 +324,11 @@ async function captureResponse(original, limit = RESPONSE_LIMIT, timeoutMs = CAP
197
324
  return { response, body: errorBody(captured.bytes), truncated: !captured.complete || Buffer.byteLength(Buffer.from(captured.bytes).toString("utf8")) > limit, complete: captured.complete };
198
325
  }
199
326
 
327
+ // src/runtime.ts
328
+ var import_node_crypto = require("crypto");
329
+
200
330
  // src/api.ts
201
- var VERSION = "6.0.0";
331
+ var VERSION = "7.0.0";
202
332
  var warn = (message) => process.emitWarning(message, { code: "MNFST" });
203
333
  var HealApi = class {
204
334
  constructor(rawFetch, key, url, timeoutMs = 6e4, reportTimeoutMs = 5e3) {
@@ -215,6 +345,7 @@ var HealApi = class {
215
345
  reportTimeoutMs;
216
346
  disabledUntil = 0;
217
347
  inFlight = 0;
348
+ /** @internal In-flight outcome reports; exposed so tests can await delivery. */
218
349
  pending = /* @__PURE__ */ new Set();
219
350
  enabled() {
220
351
  return performance.now() >= this.disabledUntil;
@@ -296,23 +427,11 @@ var HealApi = class {
296
427
  clearTimeout(timer);
297
428
  }
298
429
  }
299
- async flush(timeoutMs = 5e3) {
300
- let timer;
301
- try {
302
- await Promise.race([
303
- Promise.allSettled([...this.pending]),
304
- new Promise((resolve) => {
305
- timer = setTimeout(resolve, Math.max(0, timeoutMs));
306
- })
307
- ]);
308
- } finally {
309
- clearTimeout(timer);
310
- }
311
- }
312
430
  };
313
431
 
314
432
  // src/runtime.ts
315
- var eligible = /* @__PURE__ */ new Set([400, 404, 422]);
433
+ var forbidden = /* @__PURE__ */ new Set([401, 402, 403, 429]);
434
+ var eligible = (status) => status >= 400 && status < 500 && !forbidden.has(status);
316
435
  var Runtime = class {
317
436
  constructor(options, original, api) {
318
437
  this.options = options;
@@ -331,9 +450,13 @@ var Runtime = class {
331
450
  const started = performance.now();
332
451
  const response = await this.original(request, extras);
333
452
  const responseTimeMs = performance.now() - started;
334
- if (!eligible.has(response.status) || response.redirected || !this.api.enabled()) return response;
335
- return this.repair(request, extras, response, await bodyPromise, responseTimeMs);
453
+ if (!eligible(response.status) || response.redirected || !this.api.enabled()) return response;
454
+ return this.handleResponse(request, response, await bodyPromise, responseTimeMs, extras);
336
455
  };
456
+ async handleResponse(request, response, body, responseTimeMs, extras = {}) {
457
+ if (!eligible(response.status) || response.redirected || !this.api.enabled()) return response;
458
+ return this.repair(request, extras, response, body, responseTimeMs);
459
+ }
337
460
  async repair(request, extras, original, body, responseTimeMs) {
338
461
  let response = original;
339
462
  let result = null;
@@ -407,6 +530,7 @@ function buildRetry(request, originalBody, result) {
407
530
  const url = new URL(healed.url ?? request.url);
408
531
  if (url.origin !== new URL(request.url).origin || url.username || url.password) return null;
409
532
  const headers = new Headers(request.headers);
533
+ const contentType = headers.get("content-type");
410
534
  headers.delete("content-length");
411
535
  if (healed.headers !== void 0 && !isObject(healed.headers)) return null;
412
536
  for (const [name, value] of Object.entries(healed.headers ?? {})) {
@@ -419,7 +543,7 @@ function buildRetry(request, originalBody, result) {
419
543
  return new Request(url, {
420
544
  method: request.method,
421
545
  headers,
422
- body: ["GET", "HEAD"].includes(request.method) ? void 0 : JSON.stringify(body),
546
+ body: ["GET", "HEAD"].includes(request.method) ? void 0 : serializeRequestBody(body, contentType),
423
547
  signal: request.signal,
424
548
  redirect: request.redirect,
425
549
  credentials: request.credentials,
@@ -435,6 +559,170 @@ function buildRetry(request, originalBody, result) {
435
559
  }
436
560
  }
437
561
 
562
+ // src/http.ts
563
+ function installHttp(runtime) {
564
+ const httpRequest = wrapRequest(import_node_http.default.request, "http:", runtime);
565
+ const httpsRequest = wrapRequest(import_node_https.default.request, "https:", runtime);
566
+ import_node_http.default.request = httpRequest;
567
+ import_node_https.default.request = httpsRequest;
568
+ import_node_http.default.get = wrapGet(httpRequest);
569
+ import_node_https.default.get = wrapGet(httpsRequest);
570
+ (0, import_node_module.syncBuiltinESMExports)();
571
+ }
572
+ function wrapGet(request) {
573
+ return ((...args) => {
574
+ const result = request(...args);
575
+ result.end();
576
+ return result;
577
+ });
578
+ }
579
+ function wrapRequest(original, protocol, runtime) {
580
+ return ((...received) => {
581
+ const args = [...received];
582
+ const callback = typeof args.at(-1) === "function" ? args.pop() : void 0;
583
+ const started = performance.now();
584
+ const request = original(...args);
585
+ const capture = captureBody(request);
586
+ const signal = cancellation(request, requestSignal(args));
587
+ const emit = request.emit.bind(request);
588
+ request.emit = ((event, ...values) => {
589
+ if (event !== "response") return emit(event, ...values);
590
+ const response = values[0];
591
+ if (!eligible(response.statusCode ?? 0) || !runtime.api.enabled()) {
592
+ return emit(event, ...values);
593
+ }
594
+ void handleResponse(runtime, request, response, protocol, capture.body(), signal, started).then((healed) => emit("response", healed)).catch((error) => {
595
+ response.destroy();
596
+ if (!request.destroyed) request.destroy(error instanceof Error ? error : void 0);
597
+ });
598
+ return true;
599
+ });
600
+ if (callback) request.once("response", callback);
601
+ return request;
602
+ });
603
+ }
604
+ async function handleResponse(runtime, clientRequest, incoming, protocol, body, signal, started) {
605
+ const request = webRequest(clientRequest, protocol, body, signal);
606
+ const response = webResponse(incoming, request.url);
607
+ const healed = await runtime.handleResponse(request, response, body, performance.now() - started);
608
+ return incomingResponse(healed, clientRequest);
609
+ }
610
+ function webRequest(request, protocol, captured, signal) {
611
+ const headers = new Headers();
612
+ for (const name of request.getHeaderNames()) {
613
+ const value = request.getHeader(name);
614
+ for (const item of Array.isArray(value) ? value : [value]) {
615
+ if (item !== void 0) headers.append(name, String(item));
616
+ }
617
+ }
618
+ const authority = String(request.getHeader("host") ?? request.host);
619
+ const url = new URL(request.path, `${protocol}//${authority}`);
620
+ const method = request.method;
621
+ return new Request(url, {
622
+ method,
623
+ headers,
624
+ signal,
625
+ body: ["GET", "HEAD"].includes(method) || !captured.complete || captured.body === null ? void 0 : serializeRequestBody(captured.body, headers.get("content-type"))
626
+ });
627
+ }
628
+ function webResponse(response, url) {
629
+ const headers = new Headers();
630
+ for (const [name, value] of Object.entries(response.headers)) {
631
+ for (const item of Array.isArray(value) ? value : [value]) {
632
+ if (item !== void 0) headers.append(name, String(item));
633
+ }
634
+ }
635
+ const encoding = headers.get("content-encoding")?.toLowerCase();
636
+ let body = response;
637
+ if (encoding === "gzip" || encoding === "x-gzip" || encoding === "deflate") {
638
+ body = response.pipe((0, import_node_zlib.createUnzip)());
639
+ headers.delete("content-encoding");
640
+ headers.delete("content-length");
641
+ } else if (encoding === "br") {
642
+ body = response.pipe((0, import_node_zlib.createBrotliDecompress)());
643
+ headers.delete("content-encoding");
644
+ headers.delete("content-length");
645
+ }
646
+ const converted = new Response(
647
+ import_node_stream.Readable.toWeb(body),
648
+ { status: response.statusCode, statusText: response.statusMessage, headers }
649
+ );
650
+ Object.defineProperty(converted, "url", { value: url });
651
+ return converted;
652
+ }
653
+ function incomingResponse(response, request) {
654
+ const stream = response.body ? import_node_stream.Readable.fromWeb(response.body) : import_node_stream.Readable.from([]);
655
+ const headers = {};
656
+ for (const [name, value] of response.headers) headers[name] = value;
657
+ if (["gzip", "x-gzip", "deflate", "br"].includes(String(headers["content-encoding"]).toLowerCase())) {
658
+ delete headers["content-encoding"];
659
+ delete headers["content-length"];
660
+ }
661
+ const cookies = response.headers.getSetCookie();
662
+ if (cookies.length) headers["set-cookie"] = cookies;
663
+ const rawHeaders = Object.entries(headers).flatMap(([name, value]) => (Array.isArray(value) ? value : [value]).flatMap((item) => item === void 0 ? [] : [name, String(item)]));
664
+ return Object.assign(stream, {
665
+ statusCode: response.status,
666
+ statusMessage: response.statusText,
667
+ headers,
668
+ rawHeaders,
669
+ trailers: {},
670
+ rawTrailers: [],
671
+ httpVersion: "1.1",
672
+ httpVersionMajor: 1,
673
+ httpVersionMinor: 1,
674
+ complete: true,
675
+ req: request
676
+ });
677
+ }
678
+ function captureBody(request) {
679
+ let chunks = [];
680
+ let size = 0;
681
+ let complete = true;
682
+ const record = (chunk, encoding) => {
683
+ if (chunk === void 0 || chunk === null || typeof chunk === "function" || !complete) return;
684
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk, encoding) : Buffer.from(chunk);
685
+ size += bytes.byteLength;
686
+ if (size > REQUEST_LIMIT) {
687
+ complete = false;
688
+ chunks = [];
689
+ return;
690
+ }
691
+ chunks.push(bytes);
692
+ };
693
+ const write = request.write;
694
+ request.write = ((...args) => {
695
+ record(args[0], typeof args[1] === "string" ? args[1] : void 0);
696
+ return Reflect.apply(write, request, args);
697
+ });
698
+ const end = request.end;
699
+ request.end = ((...args) => {
700
+ record(args[0], typeof args[1] === "string" ? args[1] : void 0);
701
+ return Reflect.apply(end, request, args);
702
+ });
703
+ return { body: () => {
704
+ if (!complete) return { body: null, complete: false };
705
+ const header = request.getHeader("content-type");
706
+ const contentType = Array.isArray(header) ? header[0] : header === void 0 ? void 0 : String(header);
707
+ const parsed = parseRequestBody(Buffer.concat(chunks, size), contentType);
708
+ return { body: parsed.body, complete: parsed.valid };
709
+ } };
710
+ }
711
+ function requestSignal(args) {
712
+ for (const value of args.slice(0, 2)) {
713
+ if (value && typeof value === "object" && "signal" in value && value.signal instanceof AbortSignal) return value.signal;
714
+ }
715
+ }
716
+ function cancellation(request, external) {
717
+ const controller = new AbortController();
718
+ const destroy = request.destroy.bind(request);
719
+ request.destroy = ((error) => {
720
+ if (!controller.signal.aborted) controller.abort(error);
721
+ return destroy(error);
722
+ });
723
+ return external ? AbortSignal.any([external, controller.signal]) : controller.signal;
724
+ }
725
+
438
726
  // src/index.ts
439
727
  var STATE = /* @__PURE__ */ Symbol.for("mnfst.node.runtime.v1");
440
728
  var globals = globalThis;
@@ -459,14 +747,11 @@ function manifest(options = {}) {
459
747
  }
460
748
  const runtime = new Runtime(resolved, globalThis.fetch.bind(globalThis));
461
749
  globalThis.fetch = runtime.fetch;
750
+ installHttp(runtime);
462
751
  globals[STATE] = runtime;
463
752
  }
464
- async function flush(options = {}) {
465
- await globals[STATE]?.api.flush(options.timeoutMs ?? 5e3);
466
- }
467
753
  // Annotate the CommonJS export names for ESM import in node:
468
754
  0 && (module.exports = {
469
755
  VERSION,
470
- flush,
471
756
  manifest
472
757
  });
package/dist/index.d.cts CHANGED
@@ -12,13 +12,9 @@ interface HealEvent {
12
12
  operations?: unknown[];
13
13
  }
14
14
 
15
- declare const VERSION = "6.0.0";
15
+ declare const VERSION = "7.0.0";
16
16
 
17
17
  /** Install once, before libraries capture their own reference to global fetch. */
18
18
  declare function manifest(options?: ManifestOptions): void;
19
- /** Wait for queued outcome reports within one total deadline. Does not uninstall. */
20
- declare function flush(options?: {
21
- timeoutMs?: number;
22
- }): Promise<void>;
23
19
 
24
- export { type HealEvent, type ManifestOptions, VERSION, flush, manifest };
20
+ export { type HealEvent, type ManifestOptions, VERSION, manifest };
package/dist/index.d.ts CHANGED
@@ -12,13 +12,9 @@ interface HealEvent {
12
12
  operations?: unknown[];
13
13
  }
14
14
 
15
- declare const VERSION = "6.0.0";
15
+ declare const VERSION = "7.0.0";
16
16
 
17
17
  /** Install once, before libraries capture their own reference to global fetch. */
18
18
  declare function manifest(options?: ManifestOptions): void;
19
- /** Wait for queued outcome reports within one total deadline. Does not uninstall. */
20
- declare function flush(options?: {
21
- timeoutMs?: number;
22
- }): Promise<void>;
23
19
 
24
- export { type HealEvent, type ManifestOptions, VERSION, flush, manifest };
20
+ export { type HealEvent, type ManifestOptions, VERSION, manifest };