@nifrajs/core 5.6.0 → 5.7.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/CHANGELOG.md +93 -0
- package/README.md +5 -0
- package/dist/binary.d.ts +15 -1
- package/dist/binary.d.ts.map +1 -1
- package/dist/binary.js +1 -0
- package/dist/binary.js.map +1 -1
- package/dist/edge-kit.d.ts +1 -0
- package/dist/edge-kit.d.ts.map +1 -1
- package/dist/edge-kit.js +1 -0
- package/dist/edge-kit.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/evidence.d.ts +2 -0
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +2 -0
- package/dist/evidence.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/hook-origins.d.ts +2 -0
- package/dist/internal/hook-origins.d.ts.map +1 -1
- package/dist/internal/hook-origins.js +12 -0
- package/dist/internal/hook-origins.js.map +1 -1
- package/dist/reflection.d.ts +5 -0
- package/dist/reflection.d.ts.map +1 -1
- package/dist/reflection.js +12 -0
- package/dist/reflection.js.map +1 -1
- package/dist/server/answer-step.d.ts +27 -0
- package/dist/server/answer-step.d.ts.map +1 -0
- package/dist/server/answer-step.js +33 -0
- package/dist/server/answer-step.js.map +1 -0
- package/dist/server/base64-answers.d.ts +5 -0
- package/dist/server/base64-answers.d.ts.map +1 -0
- package/dist/server/base64-answers.js +132 -0
- package/dist/server/base64-answers.js.map +1 -0
- package/dist/server/core-copies.d.ts.map +1 -1
- package/dist/server/core-copies.js +5 -2
- package/dist/server/core-copies.js.map +1 -1
- package/dist/server/install.d.ts +2 -0
- package/dist/server/install.d.ts.map +1 -1
- package/dist/server/install.js +2 -0
- package/dist/server/install.js.map +1 -1
- package/dist/server/json-response.d.ts +3 -0
- package/dist/server/json-response.d.ts.map +1 -0
- package/dist/server/json-response.js +14 -0
- package/dist/server/json-response.js.map +1 -0
- package/dist/server/json-text.d.ts +16 -0
- package/dist/server/json-text.d.ts.map +1 -0
- package/dist/server/json-text.js +34 -0
- package/dist/server/json-text.js.map +1 -0
- package/dist/server/node-outcome.d.ts +5 -0
- package/dist/server/node-outcome.d.ts.map +1 -1
- package/dist/server/node-outcome.js +3 -4
- package/dist/server/node-outcome.js.map +1 -1
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +4 -2
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/respond.d.ts +2 -1
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +27 -24
- package/dist/server/respond.js.map +1 -1
- package/dist/server/response-contract-lane.d.ts.map +1 -1
- package/dist/server/response-contract-lane.js +2 -1
- package/dist/server/response-contract-lane.js.map +1 -1
- package/dist/server/route-template.d.ts +10 -0
- package/dist/server/route-template.d.ts.map +1 -0
- package/dist/server/route-template.js +21 -0
- package/dist/server/route-template.js.map +1 -0
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js +2 -1
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +14 -6
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +21 -10
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +94 -37
- package/dist/server/server.js.map +1 -1
- package/dist/server/sse.d.ts.map +1 -1
- package/dist/server/sse.js +2 -1
- package/dist/server/sse.js.map +1 -1
- package/dist/server/text-slots.d.ts +8 -0
- package/dist/server/text-slots.d.ts.map +1 -0
- package/dist/server/text-slots.js +19 -0
- package/dist/server/text-slots.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -1
- package/dist/single-copy.d.ts +5 -0
- package/dist/single-copy.d.ts.map +1 -1
- package/dist/single-copy.js +26 -17
- package/dist/single-copy.js.map +1 -1
- package/dist/transport-codec.d.ts.map +1 -1
- package/dist/transport-codec.js +2 -1
- package/dist/transport-codec.js.map +1 -1
- package/package.json +1 -1
- package/src/binary.ts +17 -0
- package/src/edge-kit.ts +1 -0
- package/src/errors.ts +1 -0
- package/src/evidence.ts +4 -0
- package/src/index.ts +2 -1
- package/src/internal/hook-origins.ts +10 -0
- package/src/reflection.ts +18 -0
- package/src/server/answer-step.ts +68 -0
- package/src/server/base64-answers.ts +138 -0
- package/src/server/core-copies.ts +5 -2
- package/src/server/install.ts +3 -0
- package/src/server/json-response.ts +13 -0
- package/src/server/json-text.ts +34 -0
- package/src/server/node-outcome.ts +4 -5
- package/src/server/request-context.ts +4 -2
- package/src/server/respond.ts +27 -18
- package/src/server/response-contract-lane.ts +2 -1
- package/src/server/route-template.ts +24 -0
- package/src/server/runtime-core.ts +2 -1
- package/src/server/server-types.ts +15 -4
- package/src/server/server.ts +114 -54
- package/src/server/sse.ts +2 -1
- package/src/server/text-slots.ts +21 -0
- package/src/server.ts +1 -0
- package/src/single-copy.ts +31 -15
- package/src/transport-codec.ts +3 -1
package/dist/server/server.js
CHANGED
|
@@ -10,6 +10,8 @@ import { authenticateMount, compileRouteProgram, executeRouteProgram, } from "..
|
|
|
10
10
|
import { isSameOriginRequest } from "../internal/same-origin.js";
|
|
11
11
|
import { decodeRouteParams, expandOptionalParams, } from "../router/pattern.js";
|
|
12
12
|
import { EMPTY_PARAMS, METHODS, Router } from "../router/router.js";
|
|
13
|
+
import { withAnswerSteps } from "./answer-step.js";
|
|
14
|
+
import { isAuthenticatedRequest, markAuthenticatedRequest, } from "./auth.js";
|
|
13
15
|
import { emitRequestErrorLog, renderBareError } from "./bare-error-lane.js";
|
|
14
16
|
import { LAST_BEFORE_HANDLE } from "./before-handler.js";
|
|
15
17
|
import { assertByteLimit, clientBodyErrorCode, hasTrustedBodyFraming, markTransportCap, markTrustedBodyFraming, UNLIMITED_BODY_BYTES, } from "./body.js";
|
|
@@ -26,9 +28,10 @@ import { needsResolving, resolveDotSegments } from "./request-target.js";
|
|
|
26
28
|
import { applyStaticResponseHeaders, buildStaticResponseHeaders, claimHandedOver, fusedRespond, fusedRespondNoSet, handedBackResponse, recordResidentTexts, toResponse, writableResponse, } from "./respond.js";
|
|
27
29
|
import { CONTEXT_REQUEST, CONTEXT_SEARCH, CONTEXT_SET, EMPTY_RESPONSE_CONTROLS, getNeverAbortSignal, getUnboundedRequestBudget, headerOf, isResponseResult, requestOf, } from "./runtime-core.js";
|
|
28
30
|
import { normalizeStaticResponseHeaders } from "./static-headers.js";
|
|
31
|
+
import { withTextSlots } from "./text-slots.js";
|
|
29
32
|
import { plainValidationError } from "./validation.js";
|
|
30
33
|
import { NIFRA_BACKEND_WS_RUNTIME } from "../mount.js";
|
|
31
|
-
import { GET_WS_RUNTIME, INSTALL_EFFECT_LEDGER, INSTALL_IDEMPOTENCY, INSTALL_MCP, INSTALL_NODE_DIRECT, INSTALL_NOT_FOUND, INSTALL_RESPONSE_CONTRACT, INSTALL_RESPONSE_OBSERVER, INSTALL_SSE, INSTALL_WS, NODE_NATIVE_MOUNT, REPORT_ERROR, RESOLVE_NODE_MOUNT, } from "./install.js";
|
|
34
|
+
import { GET_WS_RUNTIME, INSTALL_EFFECT_LEDGER, INSTALL_IDEMPOTENCY, INSTALL_MCP, INSTALL_NODE_DIRECT, INSTALL_NOT_FOUND, INSTALL_RESPONSE_CONTRACT, INSTALL_RESPONSE_OBSERVER, INSTALL_SSE, INSTALL_WS, NODE_NATIVE_MOUNT, REPORT_ERROR, RESOLVE_NODE_MOUNT, SERVER_LIFECYCLE, } from "./install.js";
|
|
32
35
|
import { jsonLogger } from "./logger.js";
|
|
33
36
|
// CORS uses this marker to say its request hook only handles OPTIONS preflight. Bun can keep
|
|
34
37
|
// ordinary registered routes on its native table and send unmatched OPTIONS requests through the
|
|
@@ -303,13 +306,17 @@ function mountPriorityOf(priority) {
|
|
|
303
306
|
function compareMounts(a, b) {
|
|
304
307
|
return b.priority - a.priority || b.path.length - a.path.length || a.order - b.order;
|
|
305
308
|
}
|
|
309
|
+
/** Rebase a mounted request's path without losing the authentication mark used by child caches. */
|
|
306
310
|
function stripMountPrefix(request, prefix) {
|
|
307
311
|
if (prefix === "/")
|
|
308
312
|
return request;
|
|
309
313
|
const url = new URL(request.url);
|
|
310
314
|
const rest = url.pathname.slice(prefix.length);
|
|
311
315
|
url.pathname = rest === "" ? "/" : rest;
|
|
312
|
-
|
|
316
|
+
const childRequest = new Request(url.href, request);
|
|
317
|
+
if (isAuthenticatedRequest(request))
|
|
318
|
+
markAuthenticatedRequest(childRequest);
|
|
319
|
+
return childRequest;
|
|
313
320
|
}
|
|
314
321
|
function responseSet(ctx) {
|
|
315
322
|
return ctx[CONTEXT_SET]() ?? EMPTY_RESPONSE_CONTROLS;
|
|
@@ -659,6 +666,7 @@ export class Server {
|
|
|
659
666
|
deadlineAdmissionOptions;
|
|
660
667
|
gracefulSignals;
|
|
661
668
|
stopHooks;
|
|
669
|
+
stoppingHooks;
|
|
662
670
|
fetchMounts;
|
|
663
671
|
mountOrder;
|
|
664
672
|
/** Number of composed mounts that must run before the parent route table. Keeps legacy mounts off the hot path. */
|
|
@@ -742,7 +750,7 @@ export class Server {
|
|
|
742
750
|
? fusedRespondNoSet(result, this.responseBodyTag, this.staticResponseHeaders, true)
|
|
743
751
|
: toResponse(result, set, this.responseBodyTag, this.staticResponseHeaders,
|
|
744
752
|
// Response hooks write next, and they run for this same request object.
|
|
745
|
-
ctx !== undefined && this.onResponseHooks.length > 0 ? ctx[CONTEXT_REQUEST] : undefined);
|
|
753
|
+
ctx !== undefined && this.onResponseHooks.length > 0 ? ctx[CONTEXT_REQUEST] : undefined, this.onResponseHooks.length === 0);
|
|
746
754
|
responseRequests;
|
|
747
755
|
/** A request the WebSocket handshake check ran the onRequest hooks on and then passed to HTTP →
|
|
748
756
|
* what the hooks returned, so `fetch` routes that instead of running the hooks a second time. */
|
|
@@ -808,6 +816,7 @@ export class Server {
|
|
|
808
816
|
this.gracefulSignals =
|
|
809
817
|
options.gracefulSignals === true ? {} : options.gracefulSignals || undefined;
|
|
810
818
|
this.stopHooks = [];
|
|
819
|
+
this.stoppingHooks = [];
|
|
811
820
|
this.fetchMounts = [];
|
|
812
821
|
this.mountOrder = 0;
|
|
813
822
|
this.preRouteMountCount = 0;
|
|
@@ -878,6 +887,18 @@ export class Server {
|
|
|
878
887
|
this.stopHooks.push(fn);
|
|
879
888
|
return this;
|
|
880
889
|
}
|
|
890
|
+
/**
|
|
891
|
+
* Register a callback awaited as soon as `stop()` begins, while the server still serves: readiness
|
|
892
|
+
* should fail from here, and work that outlives a request (a poller, a queue consumer) should stop
|
|
893
|
+
* taking more. Hung callbacks are bounded.
|
|
894
|
+
*/
|
|
895
|
+
onStopping(fn) {
|
|
896
|
+
this.assertConfigurable("onStopping()");
|
|
897
|
+
if (typeof fn !== "function")
|
|
898
|
+
throw new TypeError("onStopping callback must be a function");
|
|
899
|
+
this.stoppingHooks.push(fn);
|
|
900
|
+
return this;
|
|
901
|
+
}
|
|
881
902
|
/**
|
|
882
903
|
* Mount a legacy fetch handler under a literal path prefix. Mounted handlers sit behind typed
|
|
883
904
|
* routes, so routes can move one at a time while the remaining legacy surface stays live.
|
|
@@ -1575,6 +1596,7 @@ export class Server {
|
|
|
1575
1596
|
throw new RouteConfigError("INVALID_PATH", `group(${JSON.stringify(prefix)}) route path must start with "/": ${JSON.stringify(path)}`);
|
|
1576
1597
|
}
|
|
1577
1598
|
prepareRoute(method, path, schema, handler) {
|
|
1599
|
+
schema = withTextSlots(schema);
|
|
1578
1600
|
// Route provenance for the CLI's capability walk, which sets NIFRA_ROUTE_SITES while it loads the
|
|
1579
1601
|
// app: the module that registered the route, read where a static scan cannot see the path.
|
|
1580
1602
|
if (hookAuditRuntime &&
|
|
@@ -1604,6 +1626,15 @@ export class Server {
|
|
|
1604
1626
|
defaultOnValidationError: this.defaultOnValidationError,
|
|
1605
1627
|
}, method, path, schema, handler);
|
|
1606
1628
|
const { pattern, bodyLimit, capabilities, routeDecorations, hasDecorations, idempotent, ledgered, responseContract, lanes, routeAssurance, } = compiled;
|
|
1629
|
+
// A schema that writes its answer its own way (`t.bytes()`) wraps every stage that can answer.
|
|
1630
|
+
const stages = withAnswerSteps(schema, method, path, {
|
|
1631
|
+
handler,
|
|
1632
|
+
beforeHandle: this.beforeHandleHooks,
|
|
1633
|
+
afterHandle: this.afterHandleHooks,
|
|
1634
|
+
onError: this.onErrorHooks,
|
|
1635
|
+
});
|
|
1636
|
+
handler = stages.handler;
|
|
1637
|
+
const { beforeHandle, afterHandle, onError } = stages;
|
|
1607
1638
|
if (idempotent !== undefined && this.idempotencyRuntime !== undefined) {
|
|
1608
1639
|
handler = this.idempotencyRuntime.wrapHandler(handler);
|
|
1609
1640
|
}
|
|
@@ -1622,11 +1653,11 @@ export class Server {
|
|
|
1622
1653
|
const fusedBodyRunner = fusedBody
|
|
1623
1654
|
? this.buildFusedBodyRunner(handler, schema?.body, hasDecorations ? routeDecorations : undefined, bodyLimit ?? UNLIMITED_BODY_BYTES)
|
|
1624
1655
|
: isFusedBodyLifecycle
|
|
1625
|
-
? this.buildFusedBodyDeriveBeforeAfterRunner(handler, this.derives[0],
|
|
1656
|
+
? this.buildFusedBodyDeriveBeforeAfterRunner(handler, this.derives[0], beforeHandle[0], afterHandle[0], schema?.body, bodyLimit ?? UNLIMITED_BODY_BYTES)
|
|
1626
1657
|
: isFusedLifecycle && lifecycleHookLane === "derive-before"
|
|
1627
|
-
? this.buildFusedDeriveBeforeRunner(handler, this.derives[0],
|
|
1658
|
+
? this.buildFusedDeriveBeforeRunner(handler, this.derives[0], beforeHandle[0], undefined, schema?.query, bodyLimit ?? UNLIMITED_BODY_BYTES)
|
|
1628
1659
|
: isFusedLifecycle && lifecycleHookLane === "derive-before-after"
|
|
1629
|
-
? this.buildFusedDeriveBeforeRunner(handler, this.derives[0],
|
|
1660
|
+
? this.buildFusedDeriveBeforeRunner(handler, this.derives[0], beforeHandle[0], afterHandle[0], schema?.query, bodyLimit ?? UNLIMITED_BODY_BYTES)
|
|
1630
1661
|
: undefined;
|
|
1631
1662
|
const fusedWeb = bare && this.aroundHooks.length === 0
|
|
1632
1663
|
? this.buildFusedWeb(handler, hasDecorations ? routeDecorations : undefined, isContextlessNoArgArrow(handler), bodyLimit ?? UNLIMITED_BODY_BYTES)
|
|
@@ -1635,20 +1666,20 @@ export class Server {
|
|
|
1635
1666
|
: fusedBody
|
|
1636
1667
|
? this.buildFusedBodyWeb(fusedBodyRunner)
|
|
1637
1668
|
: isFusedBodyLifecycle
|
|
1638
|
-
? this.buildFusedBodyDeriveBeforeAfter(handler, this.derives[0],
|
|
1669
|
+
? this.buildFusedBodyDeriveBeforeAfter(handler, this.derives[0], beforeHandle[0], afterHandle[0], schema?.body, bodyLimit ?? UNLIMITED_BODY_BYTES)
|
|
1639
1670
|
: isFusedLifecycle && lifecycleHookLane === "derive-before"
|
|
1640
|
-
? this.buildFusedDeriveBefore(handler, this.derives[0],
|
|
1671
|
+
? this.buildFusedDeriveBefore(handler, this.derives[0], beforeHandle[0], schema?.query, bodyLimit ?? UNLIMITED_BODY_BYTES)
|
|
1641
1672
|
: isFusedLifecycle && lifecycleHookLane === "derive-before-after"
|
|
1642
|
-
? this.buildFusedDeriveBeforeAfter(handler, this.derives[0],
|
|
1673
|
+
? this.buildFusedDeriveBeforeAfter(handler, this.derives[0], beforeHandle[0], afterHandle[0], schema?.query, bodyLimit ?? UNLIMITED_BODY_BYTES)
|
|
1643
1674
|
: undefined;
|
|
1644
1675
|
const program = compileRouteProgram({
|
|
1645
1676
|
schema,
|
|
1646
1677
|
handler: handler,
|
|
1647
1678
|
derives: this.derives,
|
|
1648
1679
|
authStages: compiled.authStages,
|
|
1649
|
-
beforeHandle
|
|
1650
|
-
afterHandle
|
|
1651
|
-
onError
|
|
1680
|
+
beforeHandle,
|
|
1681
|
+
afterHandle,
|
|
1682
|
+
onError,
|
|
1652
1683
|
decorations: routeDecorations,
|
|
1653
1684
|
hasDecorations,
|
|
1654
1685
|
bodySchema: schema?.body,
|
|
@@ -1676,9 +1707,9 @@ export class Server {
|
|
|
1676
1707
|
authStages: [...compiled.authStages],
|
|
1677
1708
|
decorations: routeDecorations,
|
|
1678
1709
|
hasDecorations,
|
|
1679
|
-
beforeHandle: [...
|
|
1680
|
-
afterHandle: [...
|
|
1681
|
-
onError: [...
|
|
1710
|
+
beforeHandle: [...beforeHandle],
|
|
1711
|
+
afterHandle: [...afterHandle],
|
|
1712
|
+
onError: [...onError],
|
|
1682
1713
|
lifecycleHookLane: lanes.lifecycleHookLane,
|
|
1683
1714
|
around: [...this.aroundHooks],
|
|
1684
1715
|
execution,
|
|
@@ -2064,6 +2095,7 @@ export class Server {
|
|
|
2064
2095
|
// Cleanup the source registered (a pool, a queue consumer) belongs to the server that now runs
|
|
2065
2096
|
// its routes; dropping it would leak the resource past `stop()`.
|
|
2066
2097
|
this.stopHooks.push(...source.stopHooks);
|
|
2098
|
+
this.stoppingHooks.push(...source.stoppingHooks);
|
|
2067
2099
|
this.mcpResourceList.push(...source.mcpResourceList);
|
|
2068
2100
|
this.mcpPromptList.push(...source.mcpPromptList);
|
|
2069
2101
|
}
|
|
@@ -2691,16 +2723,20 @@ export class Server {
|
|
|
2691
2723
|
return refuse(jsonError(500, "internal_error"));
|
|
2692
2724
|
}
|
|
2693
2725
|
};
|
|
2694
|
-
const child = mount.authStages.length === 0
|
|
2695
|
-
? upgrade()
|
|
2696
|
-
: thenMaybe(authenticateMount(mount.authStages, request, platform, this.logger), (refusal) => refusal === undefined ? upgrade() : refuse(refusal));
|
|
2697
2726
|
const settle = (outcome) => outcome.kind === "pass" ||
|
|
2698
2727
|
(outcome.kind === "reject" && outcome.response.status === 404 && selected.fallbackOn404)
|
|
2699
2728
|
? dispatch(selectedIndex + 1)
|
|
2700
2729
|
: outcome.kind === "reject"
|
|
2701
2730
|
? refuse(outcome.response)
|
|
2702
2731
|
: outcome;
|
|
2703
|
-
|
|
2732
|
+
const run = () => {
|
|
2733
|
+
const child = upgrade();
|
|
2734
|
+
return child instanceof Promise ? child.then(settle) : settle(child);
|
|
2735
|
+
};
|
|
2736
|
+
// Only a child's miss may fall through, never an authentication refusal.
|
|
2737
|
+
return mount.authStages.length === 0
|
|
2738
|
+
? run()
|
|
2739
|
+
: thenMaybe(authenticateMount(mount.authStages, request, platform, this.logger), (refusal) => (refusal === undefined ? run() : refuse(refusal)));
|
|
2704
2740
|
};
|
|
2705
2741
|
return dispatch(0);
|
|
2706
2742
|
};
|
|
@@ -3094,20 +3130,26 @@ export class Server {
|
|
|
3094
3130
|
return fallback ?? jsonError(404, "not_found");
|
|
3095
3131
|
const selected = mount;
|
|
3096
3132
|
const run = () => {
|
|
3133
|
+
const reused = selected.stripPrefix ? selected.wsPassed?.get(request) : undefined;
|
|
3097
3134
|
const childRequest = selected.stripPrefix
|
|
3098
|
-
? (
|
|
3135
|
+
? (reused ?? stripMountPrefix(request, selected.path))
|
|
3099
3136
|
: request;
|
|
3100
|
-
|
|
3137
|
+
// HTTP authentication may have marked the original since the WebSocket pass.
|
|
3138
|
+
if (reused !== undefined && isAuthenticatedRequest(request)) {
|
|
3139
|
+
markAuthenticatedRequest(reused);
|
|
3140
|
+
}
|
|
3141
|
+
const outcome = selected.handler(childRequest, platform);
|
|
3142
|
+
if (!replayable || !selected.fallbackOn404)
|
|
3143
|
+
return outcome;
|
|
3144
|
+
if (outcome instanceof Promise) {
|
|
3145
|
+
return outcome.then((response) => response.status === 404 ? dispatch(index + 1, response) : response);
|
|
3146
|
+
}
|
|
3147
|
+
return outcome.status === 404 ? dispatch(index + 1, outcome) : outcome;
|
|
3101
3148
|
};
|
|
3102
|
-
|
|
3149
|
+
// Keep gate refusals outside the child's 404 fallthrough path.
|
|
3150
|
+
return mount.authStages.length === 0
|
|
3103
3151
|
? run()
|
|
3104
3152
|
: thenMaybe(authenticateMount(mount.authStages, request, platform, this.logger), (refusal) => refusal ?? run());
|
|
3105
|
-
if (!replayable || !mount.fallbackOn404)
|
|
3106
|
-
return outcome;
|
|
3107
|
-
if (outcome instanceof Promise) {
|
|
3108
|
-
return outcome.then((response) => response.status === 404 ? dispatch(index + 1, response) : response);
|
|
3109
|
-
}
|
|
3110
|
-
return outcome.status === 404 ? dispatch(index + 1, outcome) : outcome;
|
|
3111
3153
|
};
|
|
3112
3154
|
return dispatch(first);
|
|
3113
3155
|
}
|
|
@@ -4874,11 +4916,12 @@ export class Server {
|
|
|
4874
4916
|
* The Bun semantics: poll `pendingRequests` (awaiting
|
|
4875
4917
|
* `stop()`'s promise drops in-flight requests), and decide graceful-vs-forced in
|
|
4876
4918
|
* ONE call (Bun can't escalate an already-graceful `stop()` to a forced close).
|
|
4877
|
-
* New connections may be accepted during the drain window;
|
|
4878
|
-
*
|
|
4879
|
-
*
|
|
4919
|
+
* New connections may be accepted during the drain window; `delayMs` is the time a load balancer
|
|
4920
|
+
* gets to stop routing here first. The `onStopping` hooks run before that delay, the `onStop`
|
|
4921
|
+
* hooks after the drain. Open WebSockets get a 1001 close when the drain starts and the same
|
|
4922
|
+
* window to finish it; a socket opened after `stop()` began is closed at once.
|
|
4880
4923
|
*/
|
|
4881
|
-
async stop({ drainMs = DEFAULT_DRAIN_MS } = {}) {
|
|
4924
|
+
async stop({ drainMs = DEFAULT_DRAIN_MS, delayMs = 0 } = {}) {
|
|
4882
4925
|
const onSignal = this.signalHandler;
|
|
4883
4926
|
if (onSignal !== undefined) {
|
|
4884
4927
|
this.signalHandler = undefined;
|
|
@@ -4888,6 +4931,9 @@ export class Server {
|
|
|
4888
4931
|
if (server === undefined)
|
|
4889
4932
|
return;
|
|
4890
4933
|
this.bunServer = undefined;
|
|
4934
|
+
await this[SERVER_LIFECYCLE]("stopping");
|
|
4935
|
+
if (delayMs > 0)
|
|
4936
|
+
await Bun.sleep(delayMs);
|
|
4891
4937
|
const sockets = this.bunSockets;
|
|
4892
4938
|
this.bunSockets = undefined;
|
|
4893
4939
|
if (sockets !== undefined)
|
|
@@ -4899,14 +4945,25 @@ export class Server {
|
|
|
4899
4945
|
await Bun.sleep(DRAIN_POLL_MS);
|
|
4900
4946
|
}
|
|
4901
4947
|
server.stop(busy()); // force-close iff stragglers remain past the deadline
|
|
4902
|
-
|
|
4948
|
+
await this[SERVER_LIFECYCLE]("stopped");
|
|
4949
|
+
}
|
|
4950
|
+
/**
|
|
4951
|
+
* @internal Run the `onStopping` or the `onStop` hooks, each bounded. A runtime adapter that drains
|
|
4952
|
+
* its own listener calls it on either side of the drain, as `stop()` does here.
|
|
4953
|
+
*/
|
|
4954
|
+
async [SERVER_LIFECYCLE](phase) {
|
|
4955
|
+
const hooks = phase === "stopping" ? this.stoppingHooks : this.stopHooks;
|
|
4956
|
+
if (hooks.length === 0)
|
|
4903
4957
|
return;
|
|
4904
|
-
|
|
4905
|
-
const settled = Promise.allSettled(callbacks);
|
|
4958
|
+
let timer;
|
|
4906
4959
|
await Promise.race([
|
|
4907
|
-
|
|
4908
|
-
new Promise((resolve) =>
|
|
4960
|
+
Promise.allSettled(hooks.map((hook) => Promise.resolve().then(hook))),
|
|
4961
|
+
new Promise((resolve) => {
|
|
4962
|
+
timer = setTimeout(resolve, STOP_HOOK_TIMEOUT_MS);
|
|
4963
|
+
}),
|
|
4909
4964
|
]);
|
|
4965
|
+
// A pending bound would hold the event loop open after every hook settled.
|
|
4966
|
+
clearTimeout(timer);
|
|
4910
4967
|
}
|
|
4911
4968
|
installSignalHandlers(drain) {
|
|
4912
4969
|
// Drain, then let the process exit naturally - the stopped server no longer
|