manifest 7.0.0 → 7.2.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/CONTRACT.md +25 -1
- package/LICENSE +21 -0
- package/README.md +64 -19
- package/dist/bin.cjs +410 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +383 -0
- package/dist/chunk-GC5H22R2.js +495 -0
- package/dist/{chunk-DP2C4E42.js → chunk-NHEF7GUP.js} +126 -348
- package/dist/index.cjs +264 -24
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -2
- package/dist/register.cjs +264 -24
- package/dist/register.js +2 -1
- package/docs/guide.md +70 -10
- package/docs/sdk-flow-diagram.png +0 -0
- package/package.json +8 -3
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
// src/http.ts
|
|
2
|
-
import http from "http";
|
|
3
|
-
import https from "https";
|
|
4
|
-
import { syncBuiltinESMExports } from "module";
|
|
5
|
-
import { Readable } from "stream";
|
|
6
|
-
import { createBrotliDecompress, createUnzip } from "zlib";
|
|
7
|
-
|
|
8
1
|
// src/wire.ts
|
|
9
2
|
var secretNames = /* @__PURE__ */ new Set([
|
|
10
3
|
"api_key",
|
|
@@ -44,6 +37,19 @@ function safeUrl(raw) {
|
|
|
44
37
|
url.search = new URLSearchParams(pairs).toString();
|
|
45
38
|
return url.toString();
|
|
46
39
|
}
|
|
40
|
+
function trackedUrl(raw) {
|
|
41
|
+
try {
|
|
42
|
+
const url = new URL(raw);
|
|
43
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
44
|
+
url.username = "";
|
|
45
|
+
url.password = "";
|
|
46
|
+
url.search = "";
|
|
47
|
+
url.hash = "";
|
|
48
|
+
return url.toString();
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
47
53
|
function safeHeaders(headers) {
|
|
48
54
|
return Object.fromEntries([...headers].map(([key, value]) => [
|
|
49
55
|
key,
|
|
@@ -287,11 +293,9 @@ async function captureResponse(original, limit = RESPONSE_LIMIT, timeoutMs = CAP
|
|
|
287
293
|
return { response, body: errorBody(captured.bytes), truncated: !captured.complete || Buffer.byteLength(Buffer.from(captured.bytes).toString("utf8")) > limit, complete: captured.complete };
|
|
288
294
|
}
|
|
289
295
|
|
|
290
|
-
// src/runtime.ts
|
|
291
|
-
import { randomUUID } from "crypto";
|
|
292
|
-
|
|
293
296
|
// src/api.ts
|
|
294
|
-
var VERSION = "7.
|
|
297
|
+
var VERSION = "7.2.0";
|
|
298
|
+
var MAX_HEALS_IN_FLIGHT = 8;
|
|
295
299
|
var warn = (message) => process.emitWarning(message, { code: "MNFST" });
|
|
296
300
|
var HealApi = class {
|
|
297
301
|
constructor(rawFetch, key, url, timeoutMs = 6e4, reportTimeoutMs = 5e3) {
|
|
@@ -313,16 +317,23 @@ var HealApi = class {
|
|
|
313
317
|
enabled() {
|
|
314
318
|
return performance.now() >= this.disabledUntil;
|
|
315
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Whether a heal call would be sent right now: the project is not disabled
|
|
322
|
+
* and a heal slot is free. A healable failure that cannot be sent is tracked
|
|
323
|
+
* instead, so it is never lost from both ledgers.
|
|
324
|
+
*/
|
|
325
|
+
canHeal() {
|
|
326
|
+
return this.enabled() && this.inFlight < MAX_HEALS_IN_FLIGHT;
|
|
327
|
+
}
|
|
316
328
|
headers() {
|
|
317
329
|
return {
|
|
318
330
|
authorization: `Bearer ${this.key}`,
|
|
319
331
|
"content-type": "application/json",
|
|
320
|
-
"user-agent": `mnfst-node/${VERSION}
|
|
321
|
-
"x-mnfst-source": "node-sdk"
|
|
332
|
+
"user-agent": `mnfst-node/${VERSION}`
|
|
322
333
|
};
|
|
323
334
|
}
|
|
324
335
|
async heal(capture, signal) {
|
|
325
|
-
if (!this.
|
|
336
|
+
if (!this.canHeal()) return null;
|
|
326
337
|
this.inFlight++;
|
|
327
338
|
const controller = new AbortController();
|
|
328
339
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
@@ -339,14 +350,20 @@ var HealApi = class {
|
|
|
339
350
|
});
|
|
340
351
|
return null;
|
|
341
352
|
}
|
|
342
|
-
const captured = await captureResponse(
|
|
353
|
+
const captured = await captureResponse(
|
|
354
|
+
response,
|
|
355
|
+
1048576,
|
|
356
|
+
this.timeoutMs
|
|
357
|
+
);
|
|
343
358
|
void captured.response.body?.cancel().catch(() => {
|
|
344
359
|
});
|
|
345
|
-
if (!captured.complete || !isObject(captured.body) || !boundedJson(captured.body))
|
|
360
|
+
if (!captured.complete || !isObject(captured.body) || !boundedJson(captured.body))
|
|
361
|
+
return null;
|
|
346
362
|
if (response.status === 403 && captured.body.error === "project_disabled") {
|
|
347
363
|
this.disabledUntil = performance.now() + 3e5;
|
|
348
364
|
}
|
|
349
|
-
if (response.status !== 200 || typeof captured.body.status !== "string")
|
|
365
|
+
if (response.status !== 200 || typeof captured.body.status !== "string")
|
|
366
|
+
return null;
|
|
350
367
|
return {
|
|
351
368
|
status: captured.body.status,
|
|
352
369
|
...typeof captured.body.healAttemptId === "string" ? { healAttemptId: captured.body.healAttemptId } : {},
|
|
@@ -360,6 +377,72 @@ var HealApi = class {
|
|
|
360
377
|
this.inFlight--;
|
|
361
378
|
}
|
|
362
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Announce this install: "I am installed."
|
|
382
|
+
*
|
|
383
|
+
* Without it, a broken install and a healthy app look identical from the
|
|
384
|
+
* dashboard — both are silence. Four causes hide behind that silence (key
|
|
385
|
+
* unset, key invalid, app not restarted, SDK never loaded), and an app with
|
|
386
|
+
* no failing calls produces exactly the same nothing.
|
|
387
|
+
*
|
|
388
|
+
* Deliberately fire-and-forget and deliberately quiet. It carries no
|
|
389
|
+
* application data: the name and version ride in the user-agent this class
|
|
390
|
+
* already sends, so the body is the runtime and nothing else. A failure is
|
|
391
|
+
* not warned about — the dashboard showing "not connected" IS the signal,
|
|
392
|
+
* and an app that cannot reach Manifest must not print on every boot.
|
|
393
|
+
*
|
|
394
|
+
* Uses `rawFetch`, so the handshake cannot be intercepted by our own patch.
|
|
395
|
+
*/
|
|
396
|
+
hello(runtime) {
|
|
397
|
+
const controller = new AbortController();
|
|
398
|
+
const timer = setTimeout(() => controller.abort(), this.reportTimeoutMs);
|
|
399
|
+
void this.rawFetch(new URL("v1/hello", this.url), {
|
|
400
|
+
method: "POST",
|
|
401
|
+
headers: this.headers(),
|
|
402
|
+
body: JSON.stringify({ runtime }),
|
|
403
|
+
signal: controller.signal,
|
|
404
|
+
redirect: "error"
|
|
405
|
+
}).then((response) => {
|
|
406
|
+
void response.body?.cancel().catch(() => {
|
|
407
|
+
});
|
|
408
|
+
}).catch(() => {
|
|
409
|
+
}).finally(() => clearTimeout(timer));
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Ship one batch of tracked calls to `POST /v1/requests`. Throws only when a
|
|
413
|
+
* retry could help (network error, timeout, 429, 5xx), so the buffer retries
|
|
414
|
+
* once; any other answer, including 404 from a backend that predates the
|
|
415
|
+
* route, drops the batch quietly. Uses `rawFetch`, so the send is never
|
|
416
|
+
* tracked or healed by our own patch.
|
|
417
|
+
*/
|
|
418
|
+
async sendRequests(calls, signal) {
|
|
419
|
+
if (!this.enabled() || calls.length === 0) return;
|
|
420
|
+
const controller = new AbortController();
|
|
421
|
+
const timer = setTimeout(() => controller.abort(), this.reportTimeoutMs);
|
|
422
|
+
try {
|
|
423
|
+
const response = await this.rawFetch(new URL("v1/requests", this.url), {
|
|
424
|
+
method: "POST",
|
|
425
|
+
headers: this.headers(),
|
|
426
|
+
body: JSON.stringify({ requests: calls }),
|
|
427
|
+
signal: signal ? AbortSignal.any([signal, controller.signal]) : controller.signal,
|
|
428
|
+
redirect: "error"
|
|
429
|
+
});
|
|
430
|
+
if (response.status === 403) {
|
|
431
|
+
const body = await response.json().catch(() => null);
|
|
432
|
+
if (isObject(body) && body.error === "project_disabled") {
|
|
433
|
+
this.disabledUntil = performance.now() + 3e5;
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
void response.body?.cancel().catch(() => {
|
|
438
|
+
});
|
|
439
|
+
if (response.status === 429 || response.status >= 500) {
|
|
440
|
+
throw new Error(`tracked calls refused (${response.status})`);
|
|
441
|
+
}
|
|
442
|
+
} finally {
|
|
443
|
+
clearTimeout(timer);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
363
446
|
report(id, outcome) {
|
|
364
447
|
if (!id) return;
|
|
365
448
|
if (this.pending.size >= 64) {
|
|
@@ -374,16 +457,20 @@ var HealApi = class {
|
|
|
374
457
|
const controller = new AbortController();
|
|
375
458
|
const timer = setTimeout(() => controller.abort(), this.reportTimeoutMs);
|
|
376
459
|
try {
|
|
377
|
-
const response = await this.rawFetch(
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
460
|
+
const response = await this.rawFetch(
|
|
461
|
+
new URL(`v1/heal-attempts/${encodeURIComponent(id)}`, this.url),
|
|
462
|
+
{
|
|
463
|
+
method: "PATCH",
|
|
464
|
+
headers: this.headers(),
|
|
465
|
+
body: JSON.stringify(outcome),
|
|
466
|
+
signal: controller.signal,
|
|
467
|
+
redirect: "error"
|
|
468
|
+
}
|
|
469
|
+
);
|
|
384
470
|
void response.body?.cancel().catch(() => {
|
|
385
471
|
});
|
|
386
|
-
if (!response.ok)
|
|
472
|
+
if (!response.ok)
|
|
473
|
+
warn("Outcome report rejected; attempt remains unconfirmed");
|
|
387
474
|
} catch {
|
|
388
475
|
warn("Outcome report failed; attempt remains unconfirmed");
|
|
389
476
|
} finally {
|
|
@@ -392,329 +479,20 @@ var HealApi = class {
|
|
|
392
479
|
}
|
|
393
480
|
};
|
|
394
481
|
|
|
395
|
-
// src/runtime.ts
|
|
396
|
-
var forbidden = /* @__PURE__ */ new Set([401, 402, 403, 429]);
|
|
397
|
-
var eligible = (status) => status >= 400 && status < 500 && !forbidden.has(status);
|
|
398
|
-
var Runtime = class {
|
|
399
|
-
constructor(options, original, api) {
|
|
400
|
-
this.options = options;
|
|
401
|
-
this.original = original;
|
|
402
|
-
this.api = api ?? new HealApi(original, options.key, options.url);
|
|
403
|
-
}
|
|
404
|
-
options;
|
|
405
|
-
original;
|
|
406
|
-
api;
|
|
407
|
-
fetch = async (input, init) => {
|
|
408
|
-
const request = new Request(input, init);
|
|
409
|
-
const extras = { ...init };
|
|
410
|
-
delete extras.body;
|
|
411
|
-
delete extras.headers;
|
|
412
|
-
const bodyPromise = captureRequest(request).catch(() => ({ body: null, complete: false }));
|
|
413
|
-
const started = performance.now();
|
|
414
|
-
const response = await this.original(request, extras);
|
|
415
|
-
const responseTimeMs = performance.now() - started;
|
|
416
|
-
if (!eligible(response.status) || response.redirected || !this.api.enabled()) return response;
|
|
417
|
-
return this.handleResponse(request, response, await bodyPromise, responseTimeMs, extras);
|
|
418
|
-
};
|
|
419
|
-
async handleResponse(request, response, body, responseTimeMs, extras = {}) {
|
|
420
|
-
if (!eligible(response.status) || response.redirected || !this.api.enabled()) return response;
|
|
421
|
-
return this.repair(request, extras, response, body, responseTimeMs);
|
|
422
|
-
}
|
|
423
|
-
async repair(request, extras, original, body, responseTimeMs) {
|
|
424
|
-
let response = original;
|
|
425
|
-
let result = null;
|
|
426
|
-
const started = performance.now();
|
|
427
|
-
let replayStatusCode = null;
|
|
428
|
-
let replayAttempted = false;
|
|
429
|
-
try {
|
|
430
|
-
const captured = await captureResponse(response);
|
|
431
|
-
response = captured.response;
|
|
432
|
-
const payload = {
|
|
433
|
-
traceId: randomUUID(),
|
|
434
|
-
request: { method: request.method, url: safeUrl(request.url), headers: safeHeaders(request.headers), body: travelingBody(body.body) },
|
|
435
|
-
response: { statusCode: response.status, body: captured.body, truncated: captured.truncated },
|
|
436
|
-
responseTimeMs: Math.round(responseTimeMs)
|
|
437
|
-
};
|
|
438
|
-
request.signal.throwIfAborted();
|
|
439
|
-
result = await this.api.heal(payload, request.signal);
|
|
440
|
-
request.signal.throwIfAborted();
|
|
441
|
-
const retry = body.complete && captured.complete ? buildRetry(request, body.body, result) : null;
|
|
442
|
-
if (!retry) {
|
|
443
|
-
this.api.report(result?.healAttemptId, { failure: { kind: "not_attempted", message: "replay_not_attempted" } });
|
|
444
|
-
return response;
|
|
445
|
-
}
|
|
446
|
-
replayAttempted = true;
|
|
447
|
-
let retried;
|
|
448
|
-
try {
|
|
449
|
-
retried = await this.original(retry, extras);
|
|
450
|
-
} catch {
|
|
451
|
-
this.api.report(result?.healAttemptId, { failure: { kind: "transport_error", message: TRANSPORT_ERROR } });
|
|
452
|
-
request.signal.throwIfAborted();
|
|
453
|
-
return response;
|
|
454
|
-
}
|
|
455
|
-
replayStatusCode = retried.status;
|
|
456
|
-
if (retried.status >= 400) {
|
|
457
|
-
const capturedRetry = await captureResponse(retried);
|
|
458
|
-
retried = capturedRetry.response;
|
|
459
|
-
this.api.report(result?.healAttemptId, { response: { statusCode: retried.status, body: capturedRetry.body, truncated: capturedRetry.truncated } });
|
|
460
|
-
} else {
|
|
461
|
-
this.api.report(result?.healAttemptId, { response: { statusCode: retried.status } });
|
|
462
|
-
}
|
|
463
|
-
void response.body?.cancel().catch(() => {
|
|
464
|
-
});
|
|
465
|
-
return retried;
|
|
466
|
-
} catch {
|
|
467
|
-
request.signal.throwIfAborted();
|
|
468
|
-
this.api.report(result?.healAttemptId, { failure: { kind: "not_attempted", message: "replay_not_attempted" } });
|
|
469
|
-
return response;
|
|
470
|
-
} finally {
|
|
471
|
-
if (this.options.onHeal) {
|
|
472
|
-
try {
|
|
473
|
-
void Promise.resolve(this.options.onHeal({
|
|
474
|
-
url: safeUrl(request.url),
|
|
475
|
-
statusCode: original.status,
|
|
476
|
-
healStatus: replayAttempted && replayStatusCode === null ? "replay_failed" : result?.status ?? "heal_unreachable",
|
|
477
|
-
replayStatusCode,
|
|
478
|
-
healMs: Math.round(performance.now() - started),
|
|
479
|
-
operations: result?.operations
|
|
480
|
-
})).catch(() => warn("onHeal callback failed"));
|
|
481
|
-
} catch {
|
|
482
|
-
warn("onHeal callback failed");
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
function buildRetry(request, originalBody, result) {
|
|
489
|
-
if (!result || !["patched", "unverified"].includes(result.status) || !isObject(result.healedRequest)) return null;
|
|
490
|
-
const healed = result.healedRequest;
|
|
491
|
-
if (!["url", "headers", "body"].some((key) => Object.hasOwn(healed, key))) return null;
|
|
492
|
-
try {
|
|
493
|
-
const url = new URL(healed.url ?? request.url);
|
|
494
|
-
if (url.origin !== new URL(request.url).origin || url.username || url.password) return null;
|
|
495
|
-
const headers = new Headers(request.headers);
|
|
496
|
-
const contentType = headers.get("content-type");
|
|
497
|
-
headers.delete("content-length");
|
|
498
|
-
if (healed.headers !== void 0 && !isObject(healed.headers)) return null;
|
|
499
|
-
for (const [name, value] of Object.entries(healed.headers ?? {})) {
|
|
500
|
-
if (value === null) headers.delete(name);
|
|
501
|
-
else if (typeof value === "string") headers.set(name, value);
|
|
502
|
-
else return null;
|
|
503
|
-
}
|
|
504
|
-
const body = Object.hasOwn(healed, "body") ? mergeBody(originalBody, healed.body) : originalBody;
|
|
505
|
-
if (body === null && !["GET", "HEAD"].includes(request.method)) return null;
|
|
506
|
-
return new Request(url, {
|
|
507
|
-
method: request.method,
|
|
508
|
-
headers,
|
|
509
|
-
body: ["GET", "HEAD"].includes(request.method) ? void 0 : serializeRequestBody(body, contentType),
|
|
510
|
-
signal: request.signal,
|
|
511
|
-
redirect: request.redirect,
|
|
512
|
-
credentials: request.credentials,
|
|
513
|
-
cache: request.cache,
|
|
514
|
-
integrity: request.integrity,
|
|
515
|
-
keepalive: request.keepalive,
|
|
516
|
-
mode: request.mode,
|
|
517
|
-
referrer: request.referrer,
|
|
518
|
-
referrerPolicy: request.referrerPolicy
|
|
519
|
-
});
|
|
520
|
-
} catch {
|
|
521
|
-
return null;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// src/http.ts
|
|
526
|
-
function installHttp(runtime) {
|
|
527
|
-
const httpRequest = wrapRequest(http.request, "http:", runtime);
|
|
528
|
-
const httpsRequest = wrapRequest(https.request, "https:", runtime);
|
|
529
|
-
http.request = httpRequest;
|
|
530
|
-
https.request = httpsRequest;
|
|
531
|
-
http.get = wrapGet(httpRequest);
|
|
532
|
-
https.get = wrapGet(httpsRequest);
|
|
533
|
-
syncBuiltinESMExports();
|
|
534
|
-
}
|
|
535
|
-
function wrapGet(request) {
|
|
536
|
-
return ((...args) => {
|
|
537
|
-
const result = request(...args);
|
|
538
|
-
result.end();
|
|
539
|
-
return result;
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
function wrapRequest(original, protocol, runtime) {
|
|
543
|
-
return ((...received) => {
|
|
544
|
-
const args = [...received];
|
|
545
|
-
const callback = typeof args.at(-1) === "function" ? args.pop() : void 0;
|
|
546
|
-
const started = performance.now();
|
|
547
|
-
const request = original(...args);
|
|
548
|
-
const capture = captureBody(request);
|
|
549
|
-
const signal = cancellation(request, requestSignal(args));
|
|
550
|
-
const emit = request.emit.bind(request);
|
|
551
|
-
request.emit = ((event, ...values) => {
|
|
552
|
-
if (event !== "response") return emit(event, ...values);
|
|
553
|
-
const response = values[0];
|
|
554
|
-
if (!eligible(response.statusCode ?? 0) || !runtime.api.enabled()) {
|
|
555
|
-
return emit(event, ...values);
|
|
556
|
-
}
|
|
557
|
-
void handleResponse(runtime, request, response, protocol, capture.body(), signal, started).then((healed) => emit("response", healed)).catch((error) => {
|
|
558
|
-
response.destroy();
|
|
559
|
-
if (!request.destroyed) request.destroy(error instanceof Error ? error : void 0);
|
|
560
|
-
});
|
|
561
|
-
return true;
|
|
562
|
-
});
|
|
563
|
-
if (callback) request.once("response", callback);
|
|
564
|
-
return request;
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
async function handleResponse(runtime, clientRequest, incoming, protocol, body, signal, started) {
|
|
568
|
-
const request = webRequest(clientRequest, protocol, body, signal);
|
|
569
|
-
const response = webResponse(incoming, request.url);
|
|
570
|
-
const healed = await runtime.handleResponse(request, response, body, performance.now() - started);
|
|
571
|
-
return incomingResponse(healed, clientRequest);
|
|
572
|
-
}
|
|
573
|
-
function webRequest(request, protocol, captured, signal) {
|
|
574
|
-
const headers = new Headers();
|
|
575
|
-
for (const name of request.getHeaderNames()) {
|
|
576
|
-
const value = request.getHeader(name);
|
|
577
|
-
for (const item of Array.isArray(value) ? value : [value]) {
|
|
578
|
-
if (item !== void 0) headers.append(name, String(item));
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
const authority = String(request.getHeader("host") ?? request.host);
|
|
582
|
-
const url = new URL(request.path, `${protocol}//${authority}`);
|
|
583
|
-
const method = request.method;
|
|
584
|
-
return new Request(url, {
|
|
585
|
-
method,
|
|
586
|
-
headers,
|
|
587
|
-
signal,
|
|
588
|
-
body: ["GET", "HEAD"].includes(method) || !captured.complete || captured.body === null ? void 0 : serializeRequestBody(captured.body, headers.get("content-type"))
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
function webResponse(response, url) {
|
|
592
|
-
const headers = new Headers();
|
|
593
|
-
for (const [name, value] of Object.entries(response.headers)) {
|
|
594
|
-
for (const item of Array.isArray(value) ? value : [value]) {
|
|
595
|
-
if (item !== void 0) headers.append(name, String(item));
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
const encoding = headers.get("content-encoding")?.toLowerCase();
|
|
599
|
-
let body = response;
|
|
600
|
-
if (encoding === "gzip" || encoding === "x-gzip" || encoding === "deflate") {
|
|
601
|
-
body = response.pipe(createUnzip());
|
|
602
|
-
headers.delete("content-encoding");
|
|
603
|
-
headers.delete("content-length");
|
|
604
|
-
} else if (encoding === "br") {
|
|
605
|
-
body = response.pipe(createBrotliDecompress());
|
|
606
|
-
headers.delete("content-encoding");
|
|
607
|
-
headers.delete("content-length");
|
|
608
|
-
}
|
|
609
|
-
const converted = new Response(
|
|
610
|
-
Readable.toWeb(body),
|
|
611
|
-
{ status: response.statusCode, statusText: response.statusMessage, headers }
|
|
612
|
-
);
|
|
613
|
-
Object.defineProperty(converted, "url", { value: url });
|
|
614
|
-
return converted;
|
|
615
|
-
}
|
|
616
|
-
function incomingResponse(response, request) {
|
|
617
|
-
const stream = response.body ? Readable.fromWeb(response.body) : Readable.from([]);
|
|
618
|
-
const headers = {};
|
|
619
|
-
for (const [name, value] of response.headers) headers[name] = value;
|
|
620
|
-
if (["gzip", "x-gzip", "deflate", "br"].includes(String(headers["content-encoding"]).toLowerCase())) {
|
|
621
|
-
delete headers["content-encoding"];
|
|
622
|
-
delete headers["content-length"];
|
|
623
|
-
}
|
|
624
|
-
const cookies = response.headers.getSetCookie();
|
|
625
|
-
if (cookies.length) headers["set-cookie"] = cookies;
|
|
626
|
-
const rawHeaders = Object.entries(headers).flatMap(([name, value]) => (Array.isArray(value) ? value : [value]).flatMap((item) => item === void 0 ? [] : [name, String(item)]));
|
|
627
|
-
return Object.assign(stream, {
|
|
628
|
-
statusCode: response.status,
|
|
629
|
-
statusMessage: response.statusText,
|
|
630
|
-
headers,
|
|
631
|
-
rawHeaders,
|
|
632
|
-
trailers: {},
|
|
633
|
-
rawTrailers: [],
|
|
634
|
-
httpVersion: "1.1",
|
|
635
|
-
httpVersionMajor: 1,
|
|
636
|
-
httpVersionMinor: 1,
|
|
637
|
-
complete: true,
|
|
638
|
-
req: request
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
function captureBody(request) {
|
|
642
|
-
let chunks = [];
|
|
643
|
-
let size = 0;
|
|
644
|
-
let complete = true;
|
|
645
|
-
const record = (chunk, encoding) => {
|
|
646
|
-
if (chunk === void 0 || chunk === null || typeof chunk === "function" || !complete) return;
|
|
647
|
-
const bytes = typeof chunk === "string" ? Buffer.from(chunk, encoding) : Buffer.from(chunk);
|
|
648
|
-
size += bytes.byteLength;
|
|
649
|
-
if (size > REQUEST_LIMIT) {
|
|
650
|
-
complete = false;
|
|
651
|
-
chunks = [];
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
chunks.push(bytes);
|
|
655
|
-
};
|
|
656
|
-
const write = request.write;
|
|
657
|
-
request.write = ((...args) => {
|
|
658
|
-
record(args[0], typeof args[1] === "string" ? args[1] : void 0);
|
|
659
|
-
return Reflect.apply(write, request, args);
|
|
660
|
-
});
|
|
661
|
-
const end = request.end;
|
|
662
|
-
request.end = ((...args) => {
|
|
663
|
-
record(args[0], typeof args[1] === "string" ? args[1] : void 0);
|
|
664
|
-
return Reflect.apply(end, request, args);
|
|
665
|
-
});
|
|
666
|
-
return { body: () => {
|
|
667
|
-
if (!complete) return { body: null, complete: false };
|
|
668
|
-
const header = request.getHeader("content-type");
|
|
669
|
-
const contentType = Array.isArray(header) ? header[0] : header === void 0 ? void 0 : String(header);
|
|
670
|
-
const parsed = parseRequestBody(Buffer.concat(chunks, size), contentType);
|
|
671
|
-
return { body: parsed.body, complete: parsed.valid };
|
|
672
|
-
} };
|
|
673
|
-
}
|
|
674
|
-
function requestSignal(args) {
|
|
675
|
-
for (const value of args.slice(0, 2)) {
|
|
676
|
-
if (value && typeof value === "object" && "signal" in value && value.signal instanceof AbortSignal) return value.signal;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
function cancellation(request, external) {
|
|
680
|
-
const controller = new AbortController();
|
|
681
|
-
const destroy = request.destroy.bind(request);
|
|
682
|
-
request.destroy = ((error) => {
|
|
683
|
-
if (!controller.signal.aborted) controller.abort(error);
|
|
684
|
-
return destroy(error);
|
|
685
|
-
});
|
|
686
|
-
return external ? AbortSignal.any([external, controller.signal]) : controller.signal;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// src/index.ts
|
|
690
|
-
var STATE = /* @__PURE__ */ Symbol.for("mnfst.node.runtime.v1");
|
|
691
|
-
var globals = globalThis;
|
|
692
|
-
function manifest(options = {}) {
|
|
693
|
-
const key = options.key || process.env.MNFST_KEY;
|
|
694
|
-
if (!key) {
|
|
695
|
-
warn("MNFST_KEY is not set; Manifest is disabled");
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
const url = new URL(options.url || process.env.MNFST_URL || "https://api.manifest.build");
|
|
699
|
-
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
700
|
-
throw new TypeError("Manifest URL must be an HTTP(S) base URL without credentials, query or fragment");
|
|
701
|
-
}
|
|
702
|
-
if (!url.pathname.endsWith("/")) url.pathname += "/";
|
|
703
|
-
const resolved = { ...options, key, url: url.toString() };
|
|
704
|
-
const existing = globals[STATE];
|
|
705
|
-
if (existing) {
|
|
706
|
-
if (existing.options.key !== key || existing.options.url !== resolved.url || existing.options.onHeal !== options.onHeal) {
|
|
707
|
-
warn("Manifest is already configured; changing configuration requires a process restart");
|
|
708
|
-
}
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
const runtime = new Runtime(resolved, globalThis.fetch.bind(globalThis));
|
|
712
|
-
globalThis.fetch = runtime.fetch;
|
|
713
|
-
installHttp(runtime);
|
|
714
|
-
globals[STATE] = runtime;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
482
|
export {
|
|
483
|
+
isObject,
|
|
484
|
+
safeUrl,
|
|
485
|
+
trackedUrl,
|
|
486
|
+
safeHeaders,
|
|
487
|
+
travelingBody,
|
|
488
|
+
mergeBody,
|
|
489
|
+
parseRequestBody,
|
|
490
|
+
serializeRequestBody,
|
|
491
|
+
TRANSPORT_ERROR,
|
|
492
|
+
REQUEST_LIMIT,
|
|
493
|
+
captureRequest,
|
|
494
|
+
captureResponse,
|
|
718
495
|
VERSION,
|
|
719
|
-
|
|
496
|
+
warn,
|
|
497
|
+
HealApi
|
|
720
498
|
};
|