@timber-js/app 0.1.20 → 0.1.22
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/_chunks/als-registry-c0AGnbqS.js +39 -0
- package/dist/_chunks/als-registry-c0AGnbqS.js.map +1 -0
- package/dist/_chunks/{interception-c-a3uODY.js → interception-DGDIjDbR.js} +10 -3
- package/dist/_chunks/interception-DGDIjDbR.js.map +1 -0
- package/dist/_chunks/{metadata-routes-BDnswgRO.js → metadata-routes-CQCnF4VK.js} +14 -2
- package/dist/_chunks/metadata-routes-CQCnF4VK.js.map +1 -0
- package/dist/_chunks/{request-context-BzES06i1.js → request-context-C69VW4xS.js} +2 -4
- package/dist/_chunks/request-context-C69VW4xS.js.map +1 -0
- package/dist/_chunks/ssr-data-B2yikEEB.js +90 -0
- package/dist/_chunks/ssr-data-B2yikEEB.js.map +1 -0
- package/dist/_chunks/{tracing-BtOwb8O6.js → tracing-tIvqStk8.js} +2 -3
- package/dist/_chunks/tracing-tIvqStk8.js.map +1 -0
- package/dist/_chunks/{use-cookie-D2cZu0jK.js → use-cookie-D5aS4slY.js} +2 -2
- package/dist/_chunks/{use-cookie-D2cZu0jK.js.map → use-cookie-D5aS4slY.js.map} +1 -1
- package/dist/_chunks/{use-query-states-wEXY2JQB.js → use-query-states-DAhgj8Gx.js} +1 -1
- package/dist/_chunks/{use-query-states-wEXY2JQB.js.map → use-query-states-DAhgj8Gx.js.map} +1 -1
- package/dist/cache/index.js +2 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +40 -26
- package/dist/client/index.js.map +1 -1
- package/dist/client/router-ref.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/ssr-data.d.ts +3 -0
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/state.d.ts +47 -0
- package/dist/client/state.d.ts.map +1 -0
- package/dist/client/types.d.ts +10 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/unload-guard.d.ts +3 -0
- package/dist/client/unload-guard.d.ts.map +1 -1
- package/dist/client/use-params.d.ts +19 -6
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-search-params.d.ts +3 -0
- package/dist/client/use-search-params.d.ts.map +1 -1
- package/dist/cookies/index.js +4 -2
- package/dist/cookies/index.js.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/routing/index.js +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/rsc-runtime/browser.d.ts +13 -0
- package/dist/rsc-runtime/browser.d.ts.map +1 -0
- package/dist/rsc-runtime/rsc.d.ts +14 -0
- package/dist/rsc-runtime/rsc.d.ts.map +1 -0
- package/dist/rsc-runtime/ssr.d.ts +13 -0
- package/dist/rsc-runtime/ssr.d.ts.map +1 -0
- package/dist/search-params/builtin-codecs.d.ts +105 -0
- package/dist/search-params/builtin-codecs.d.ts.map +1 -0
- package/dist/search-params/index.d.ts +1 -0
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +167 -2
- package/dist/search-params/index.js.map +1 -1
- package/dist/server/actions.d.ts +2 -7
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +80 -0
- package/dist/server/als-registry.d.ts.map +1 -0
- package/dist/server/early-hints-sender.d.ts.map +1 -1
- package/dist/server/form-flash.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +242 -76
- package/dist/server/index.js.map +1 -1
- package/dist/server/metadata-routes.d.ts +27 -0
- package/dist/server/metadata-routes.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +7 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +14 -6
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +2 -32
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +5 -0
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +25 -0
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -0
- package/dist/server/rsc-entry/rsc-stream.d.ts +43 -0
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -0
- package/dist/server/rsc-entry/ssr-renderer.d.ts +52 -0
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -0
- package/dist/server/rsc-prop-warnings.d.ts +53 -0
- package/dist/server/rsc-prop-warnings.d.ts.map +1 -0
- package/dist/server/server-timing.d.ts +49 -0
- package/dist/server/server-timing.d.ts.map +1 -0
- package/dist/server/tracing.d.ts +2 -6
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/types.d.ts +11 -0
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/browser-entry.ts +1 -1
- package/src/client/index.ts +1 -1
- package/src/client/router-ref.ts +6 -12
- package/src/client/router.ts +14 -4
- package/src/client/ssr-data.ts +25 -9
- package/src/client/state.ts +83 -0
- package/src/client/types.ts +18 -1
- package/src/client/unload-guard.ts +6 -3
- package/src/client/use-params.ts +42 -32
- package/src/client/use-search-params.ts +9 -5
- package/src/plugins/shims.ts +26 -2
- package/src/routing/scanner.ts +18 -2
- package/src/rsc-runtime/browser.ts +18 -0
- package/src/rsc-runtime/rsc.ts +19 -0
- package/src/rsc-runtime/ssr.ts +13 -0
- package/src/search-params/builtin-codecs.ts +228 -0
- package/src/search-params/index.ts +11 -0
- package/src/server/action-handler.ts +1 -1
- package/src/server/actions.ts +4 -10
- package/src/server/als-registry.ts +116 -0
- package/src/server/deny-renderer.ts +1 -1
- package/src/server/early-hints-sender.ts +1 -3
- package/src/server/form-flash.ts +1 -5
- package/src/server/index.ts +1 -0
- package/src/server/metadata-routes.ts +61 -0
- package/src/server/pipeline.ts +164 -38
- package/src/server/primitives.ts +110 -6
- package/src/server/request-context.ts +8 -36
- package/src/server/route-matcher.ts +25 -2
- package/src/server/rsc-entry/error-renderer.ts +1 -1
- package/src/server/rsc-entry/index.ts +42 -380
- package/src/server/rsc-entry/rsc-payload.ts +126 -0
- package/src/server/rsc-entry/rsc-stream.ts +162 -0
- package/src/server/rsc-entry/ssr-renderer.ts +228 -0
- package/src/server/rsc-prop-warnings.ts +187 -0
- package/src/server/server-timing.ts +132 -0
- package/src/server/ssr-entry.ts +1 -1
- package/src/server/tracing.ts +3 -11
- package/src/server/types.ts +16 -0
- package/dist/_chunks/interception-c-a3uODY.js.map +0 -1
- package/dist/_chunks/metadata-routes-BDnswgRO.js.map +0 -1
- package/dist/_chunks/request-context-BzES06i1.js.map +0 -1
- package/dist/_chunks/ssr-data-BgSwMbN9.js +0 -38
- package/dist/_chunks/ssr-data-BgSwMbN9.js.map +0 -1
- package/dist/_chunks/tracing-BtOwb8O6.js.map +0 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized AsyncLocalStorage registry for server-side per-request state.
|
|
3
|
+
*
|
|
4
|
+
* ALL ALS instances used by the server framework live here. Individual
|
|
5
|
+
* modules (request-context.ts, tracing.ts, actions.ts, etc.) import from
|
|
6
|
+
* this registry and re-export public accessor functions.
|
|
7
|
+
*
|
|
8
|
+
* Why: ALS instances require singleton semantics — if two copies of the
|
|
9
|
+
* same ALS exist (one from a relative import, one from a barrel import),
|
|
10
|
+
* one module writes to its copy and another reads from an empty copy.
|
|
11
|
+
* Centralizing ALS creation in a single module eliminates this class of bug.
|
|
12
|
+
*
|
|
13
|
+
* The `timber-shims` plugin ensures `@timber-js/app/server` resolves to
|
|
14
|
+
* src/ in RSC and SSR environments, so all import paths converge here.
|
|
15
|
+
*
|
|
16
|
+
* DO NOT create ALS instances outside this file. If you need a new ALS,
|
|
17
|
+
* add it here and import from `./als-registry.js` in the consuming module.
|
|
18
|
+
*
|
|
19
|
+
* See design/18-build-system.md §"Module Singleton Strategy" and
|
|
20
|
+
* §"Singleton State Registry".
|
|
21
|
+
*/
|
|
22
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
23
|
+
/** @internal — import via request-context.ts public API */
|
|
24
|
+
export declare const requestContextAls: AsyncLocalStorage<RequestContextStore>;
|
|
25
|
+
export interface RequestContextStore {
|
|
26
|
+
/** Incoming request headers (read-only view). */
|
|
27
|
+
headers: Headers;
|
|
28
|
+
/** Raw cookie header string, parsed lazily into a Map on first access. */
|
|
29
|
+
cookieHeader: string;
|
|
30
|
+
/** Lazily-parsed cookie map (mutable — reflects write-overlay from set()). */
|
|
31
|
+
parsedCookies?: Map<string, string>;
|
|
32
|
+
/** Original (pre-overlay) frozen headers, kept for overlay merging. */
|
|
33
|
+
originalHeaders: Headers;
|
|
34
|
+
/**
|
|
35
|
+
* Promise resolving to the route's typed search params (when search-params.ts
|
|
36
|
+
* exists) or to the raw URLSearchParams. Stored as a Promise so the framework
|
|
37
|
+
* can later support partial pre-rendering where param resolution is deferred.
|
|
38
|
+
*/
|
|
39
|
+
searchParamsPromise: Promise<URLSearchParams | Record<string, unknown>>;
|
|
40
|
+
/** Outgoing Set-Cookie entries (name → serialized value + options). Last write wins. */
|
|
41
|
+
cookieJar: Map<string, CookieEntry>;
|
|
42
|
+
/** Whether the response has flushed (headers committed). */
|
|
43
|
+
flushed: boolean;
|
|
44
|
+
/** Whether the current context allows cookie mutation. */
|
|
45
|
+
mutableContext: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** A single outgoing cookie entry in the cookie jar. */
|
|
48
|
+
export interface CookieEntry {
|
|
49
|
+
name: string;
|
|
50
|
+
value: string;
|
|
51
|
+
options: import('./request-context.js').CookieOptions;
|
|
52
|
+
}
|
|
53
|
+
export interface TraceStore {
|
|
54
|
+
/** 32-char lowercase hex trace ID (OTEL or UUID fallback). */
|
|
55
|
+
traceId: string;
|
|
56
|
+
/** OTEL span ID if available, undefined otherwise. */
|
|
57
|
+
spanId?: string;
|
|
58
|
+
}
|
|
59
|
+
/** @internal — import via tracing.ts public API */
|
|
60
|
+
export declare const traceAls: AsyncLocalStorage<TraceStore>;
|
|
61
|
+
export interface TimingStore {
|
|
62
|
+
entries: import('./server-timing.js').TimingEntry[];
|
|
63
|
+
}
|
|
64
|
+
/** @internal — import via server-timing.ts public API */
|
|
65
|
+
export declare const timingAls: AsyncLocalStorage<TimingStore>;
|
|
66
|
+
export interface RevalidationState {
|
|
67
|
+
/** Paths to re-render (populated by revalidatePath calls). */
|
|
68
|
+
paths: string[];
|
|
69
|
+
/** Tags to invalidate (populated by revalidateTag calls). */
|
|
70
|
+
tags: string[];
|
|
71
|
+
}
|
|
72
|
+
/** @internal — import via actions.ts public API */
|
|
73
|
+
export declare const revalidationAls: AsyncLocalStorage<RevalidationState>;
|
|
74
|
+
/** @internal — import via form-flash.ts public API */
|
|
75
|
+
export declare const formFlashAls: AsyncLocalStorage<import("./form-flash.js").FormFlashData>;
|
|
76
|
+
/** Function that sends Link header values as a 103 Early Hints response. */
|
|
77
|
+
export type EarlyHintsSenderFn = (links: string[]) => void;
|
|
78
|
+
/** @internal — import via early-hints-sender.ts public API */
|
|
79
|
+
export declare const earlyHintsSenderAls: AsyncLocalStorage<EarlyHintsSenderFn>;
|
|
80
|
+
//# sourceMappingURL=als-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"als-registry.d.ts","sourceRoot":"","sources":["../../src/server/als-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAMrD,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,wCAA+C,CAAC;AAE9E,MAAM,WAAW,mBAAmB;IAClC,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,uEAAuE;IACvE,eAAe,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,mBAAmB,EAAE,OAAO,CAAC,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxE,wFAAwF;IACxF,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,4DAA4D;IAC5D,OAAO,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wDAAwD;AACxD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,sBAAsB,EAAE,aAAa,CAAC;CACvD;AAMD,MAAM,WAAW,UAAU;IACzB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,mDAAmD;AACnD,eAAO,MAAM,QAAQ,+BAAsC,CAAC;AAM5D,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,oBAAoB,EAAE,WAAW,EAAE,CAAC;CACrD;AAED,yDAAyD;AACzD,eAAO,MAAM,SAAS,gCAAuC,CAAC;AAM9D,MAAM,WAAW,iBAAiB;IAChC,8DAA8D;IAC9D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,mDAAmD;AACnD,eAAO,MAAM,eAAe,sCAA6C,CAAC;AAM1E,sDAAsD;AACtD,eAAO,MAAM,YAAY,4DAAmE,CAAC;AAM7F,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAE3D,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,uCAA8C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"early-hints-sender.d.ts","sourceRoot":"","sources":["../../src/server/early-hints-sender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"early-hints-sender.d.ts","sourceRoot":"","sources":["../../src/server/early-hints-sender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAE3D;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAErF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CASvD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-flash.d.ts","sourceRoot":"","sources":["../../src/server/form-flash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"form-flash.d.ts","sourceRoot":"","sources":["../../src/server/form-flash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK3D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,uDAAuD;IACvD,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CAChE;AAID;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,YAAY,IAAI,aAAa,GAAG,IAAI,CAEnD;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEvE"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { headers, cookies, searchParams, setParsedSearchParams, runWithRequestCo
|
|
|
6
6
|
export type { ReadonlyHeaders, RequestCookies, CookieOptions } from './request-context';
|
|
7
7
|
export { deny, notFound, redirect, permanentRedirect, redirectExternal, RedirectType, RenderError, waitUntil, DenySignal, RedirectSignal, } from './primitives';
|
|
8
8
|
export type { RenderErrorDigest, WaitUntilAdapter } from './primitives';
|
|
9
|
+
export type { JsonSerializable } from './types';
|
|
9
10
|
export { createPipeline } from './pipeline';
|
|
10
11
|
export type { PipelineConfig, RouteMatch, RouteMatcher, RouteRenderer, EarlyHintsEmitter, } from './pipeline';
|
|
11
12
|
export { collectEarlyHintHeaders, formatLinkHeader } from './early-hints';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAKvD,OAAO,EACL,OAAO,EACP,OAAO,EACP,YAAY,EACZ,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGxF,OAAO,EACL,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,SAAS,EACT,UAAU,EACV,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
package/dist/server/index.js
CHANGED
|
@@ -1,10 +1,60 @@
|
|
|
1
1
|
import { a as warnDenyAfterFlush, c as warnRedirectInAccess, d as warnSlowSlotWithoutSuspense, f as warnStaticRequestApi, i as warnCacheRequestProps, l as warnRedirectInSlotAccess, n as WarningId, o as warnDenyInSuspense, p as warnSuspenseWrappingChildren, r as setViteServer, s as warnDynamicApiInStaticBuild, t as formatSize, u as warnRedirectInSuspense } from "../_chunks/format-DNt20Kt8.js";
|
|
2
|
-
import { i as getMetadataRouteServePath, n as classifyMetadataRoute, r as getMetadataRouteAutoLink, t as METADATA_ROUTE_CONVENTIONS } from "../_chunks/metadata-routes-
|
|
3
|
-
import { a as
|
|
4
|
-
import { a as
|
|
5
|
-
import {
|
|
2
|
+
import { i as getMetadataRouteServePath, n as classifyMetadataRoute, r as getMetadataRouteAutoLink, t as METADATA_ROUTE_CONVENTIONS } from "../_chunks/metadata-routes-CQCnF4VK.js";
|
|
3
|
+
import { a as timingAls, i as revalidationAls, n as formFlashAls, t as earlyHintsSenderAls } from "../_chunks/als-registry-c0AGnbqS.js";
|
|
4
|
+
import { a as markResponseFlushed, c as setCookieSecrets, i as headers, l as setMutableCookieContext, n as cookies, o as runWithRequestContext, r as getSetCookieHeaders, s as searchParams, t as applyRequestHeaderOverlay, u as setParsedSearchParams } from "../_chunks/request-context-C69VW4xS.js";
|
|
5
|
+
import { a as replaceTraceId, c as spanId, i as getTraceStore, l as traceId, n as generateTraceId, o as runWithTraceId, r as getOtelTraceId, s as setSpanAttribute, t as addSpanEvent, u as withSpan } from "../_chunks/tracing-tIvqStk8.js";
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
6
7
|
//#region src/server/primitives.ts
|
|
7
8
|
/**
|
|
9
|
+
* Check if a value is JSON-serializable without data loss.
|
|
10
|
+
* Returns a description of the first non-serializable value found, or null if OK.
|
|
11
|
+
*
|
|
12
|
+
* @internal Exported for testing only.
|
|
13
|
+
*/
|
|
14
|
+
function findNonSerializable(value, path = "data") {
|
|
15
|
+
if (value === null || value === void 0) return null;
|
|
16
|
+
switch (typeof value) {
|
|
17
|
+
case "string":
|
|
18
|
+
case "number":
|
|
19
|
+
case "boolean": return null;
|
|
20
|
+
case "bigint": return `${path} contains a BigInt — BigInt throws in JSON.stringify`;
|
|
21
|
+
case "function": return `${path} is a function — functions are not JSON-serializable`;
|
|
22
|
+
case "symbol": return `${path} is a symbol — symbols are not JSON-serializable`;
|
|
23
|
+
case "object": break;
|
|
24
|
+
default: return `${path} has unsupported type "${typeof value}"`;
|
|
25
|
+
}
|
|
26
|
+
if (value instanceof Date) return `${path} is a Date — Dates silently coerce to strings in JSON.stringify`;
|
|
27
|
+
if (value instanceof Map) return `${path} is a Map — Maps serialize as {} in JSON.stringify (data loss)`;
|
|
28
|
+
if (value instanceof Set) return `${path} is a Set — Sets serialize as {} in JSON.stringify (data loss)`;
|
|
29
|
+
if (value instanceof RegExp) return `${path} is a RegExp — RegExps serialize as {} in JSON.stringify`;
|
|
30
|
+
if (value instanceof Error) return `${path} is an Error — Errors serialize as {} in JSON.stringify`;
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
for (let i = 0; i < value.length; i++) {
|
|
33
|
+
const result = findNonSerializable(value[i], `${path}[${i}]`);
|
|
34
|
+
if (result) return result;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const proto = Object.getPrototypeOf(value);
|
|
39
|
+
if (proto === null) return `${path} is a null-prototype object — React Flight rejects null prototypes`;
|
|
40
|
+
if (proto !== Object.prototype) return `${path} is a ${value.constructor?.name ?? "unknown"} instance — class instances may lose data in JSON.stringify`;
|
|
41
|
+
for (const key of Object.keys(value)) {
|
|
42
|
+
const result = findNonSerializable(value[key], `${path}.${key}`);
|
|
43
|
+
if (result) return result;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Emit a dev-mode warning if data is not JSON-serializable.
|
|
49
|
+
* No-op in production.
|
|
50
|
+
*/
|
|
51
|
+
function warnIfNotSerializable(data, callerName) {
|
|
52
|
+
if (process.env.NODE_ENV === "production") return;
|
|
53
|
+
if (data === void 0) return;
|
|
54
|
+
const issue = findNonSerializable(data);
|
|
55
|
+
if (issue) console.warn(`[timber] ${callerName}: ${issue}. Data passed to deny() or RenderError must be JSON-serializable because the post-flush path uses JSON.stringify, not React Flight.`);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
8
58
|
* Render-phase signal thrown by `deny()`. Caught by the framework to produce
|
|
9
59
|
* the correct HTTP status code (segment context) or graceful degradation (slot context).
|
|
10
60
|
*/
|
|
@@ -47,10 +97,11 @@ var DenySignal = class extends Error {
|
|
|
47
97
|
* - Inside Suspense (after flush): error boundary + noindex meta
|
|
48
98
|
*
|
|
49
99
|
* @param status - Any 4xx HTTP status code. Defaults to 403.
|
|
50
|
-
* @param data - Optional data passed as `dangerouslyPassData` prop to status-code files.
|
|
100
|
+
* @param data - Optional JSON-serializable data passed as `dangerouslyPassData` prop to status-code files.
|
|
51
101
|
*/
|
|
52
102
|
function deny(status = 403, data) {
|
|
53
103
|
if (status < 400 || status > 499) throw new Error(`deny() requires a 4xx status code, got ${status}. For 5xx errors, throw a RenderError instead.`);
|
|
104
|
+
warnIfNotSerializable(data, "deny()");
|
|
54
105
|
throw new DenySignal(status, data);
|
|
55
106
|
}
|
|
56
107
|
/**
|
|
@@ -157,6 +208,7 @@ var RenderError = class extends Error {
|
|
|
157
208
|
code,
|
|
158
209
|
data
|
|
159
210
|
};
|
|
211
|
+
warnIfNotSerializable(data, "RenderError");
|
|
160
212
|
const status = options?.status ?? 500;
|
|
161
213
|
if (status < 400 || status > 599) throw new Error(`RenderError status must be 4xx or 5xx, got ${status}.`);
|
|
162
214
|
this.status = status;
|
|
@@ -278,6 +330,82 @@ async function runMiddleware(middlewareFn, ctx) {
|
|
|
278
330
|
if (result instanceof Response) return result;
|
|
279
331
|
}
|
|
280
332
|
//#endregion
|
|
333
|
+
//#region src/server/server-timing.ts
|
|
334
|
+
/**
|
|
335
|
+
* Server-Timing header — dev-mode timing breakdowns for Chrome DevTools.
|
|
336
|
+
*
|
|
337
|
+
* Collects timing entries per request using ALS. Each pipeline phase
|
|
338
|
+
* (proxy, middleware, render, SSR, access, fetch) records an entry.
|
|
339
|
+
* Before response flush, entries are formatted into a Server-Timing header.
|
|
340
|
+
*
|
|
341
|
+
* Only active in dev mode — zero overhead in production.
|
|
342
|
+
*
|
|
343
|
+
* See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing
|
|
344
|
+
* Task: LOCAL-290
|
|
345
|
+
*/
|
|
346
|
+
/**
|
|
347
|
+
* Run a callback with a per-request timing collector.
|
|
348
|
+
* Must be called at the top of the request pipeline (wraps the full request).
|
|
349
|
+
*/
|
|
350
|
+
function runWithTimingCollector(fn) {
|
|
351
|
+
return timingAls.run({ entries: [] }, fn);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Run a function and automatically record its duration as a timing entry.
|
|
355
|
+
* Returns the function's result. No-ops the recording if outside a collector.
|
|
356
|
+
*/
|
|
357
|
+
async function withTiming(name, desc, fn) {
|
|
358
|
+
const store = timingAls.getStore();
|
|
359
|
+
if (!store) return fn();
|
|
360
|
+
const start = performance.now();
|
|
361
|
+
try {
|
|
362
|
+
return await fn();
|
|
363
|
+
} finally {
|
|
364
|
+
const dur = Math.round(performance.now() - start);
|
|
365
|
+
store.entries.push({
|
|
366
|
+
name,
|
|
367
|
+
dur,
|
|
368
|
+
desc
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Get the Server-Timing header value for the current request.
|
|
374
|
+
* Returns null if no entries exist or outside a collector.
|
|
375
|
+
*
|
|
376
|
+
* Format: `name;dur=123;desc="description", name2;dur=456`
|
|
377
|
+
* See RFC 6797 / Server-Timing spec for format details.
|
|
378
|
+
*/
|
|
379
|
+
function getServerTimingHeader() {
|
|
380
|
+
const store = timingAls.getStore();
|
|
381
|
+
if (!store || store.entries.length === 0) return null;
|
|
382
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
383
|
+
const parts = store.entries.map((entry) => {
|
|
384
|
+
const count = nameCounts.get(entry.name) ?? 0;
|
|
385
|
+
nameCounts.set(entry.name, count + 1);
|
|
386
|
+
const uniqueName = count > 0 ? `${entry.name}-${count}` : entry.name;
|
|
387
|
+
return {
|
|
388
|
+
...entry,
|
|
389
|
+
name: uniqueName
|
|
390
|
+
};
|
|
391
|
+
}).map((entry) => {
|
|
392
|
+
let part = `${entry.name};dur=${entry.dur}`;
|
|
393
|
+
if (entry.desc) {
|
|
394
|
+
const safeDesc = entry.desc.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
395
|
+
part += `;desc="${safeDesc}"`;
|
|
396
|
+
}
|
|
397
|
+
return part;
|
|
398
|
+
});
|
|
399
|
+
const MAX_HEADER_SIZE = 4096;
|
|
400
|
+
let result = "";
|
|
401
|
+
for (let i = 0; i < parts.length; i++) {
|
|
402
|
+
const candidate = result ? `${result}, ${parts[i]}` : parts[i];
|
|
403
|
+
if (candidate.length > MAX_HEADER_SIZE) break;
|
|
404
|
+
result = candidate;
|
|
405
|
+
}
|
|
406
|
+
return result || null;
|
|
407
|
+
}
|
|
408
|
+
//#endregion
|
|
281
409
|
//#region src/server/error-formatter.ts
|
|
282
410
|
/**
|
|
283
411
|
* Error Formatter — rewrites SSR/RSC error messages to surface user code.
|
|
@@ -554,7 +682,7 @@ function hasOnRequestError() {
|
|
|
554
682
|
* and produces a Response. This is the top-level entry point for the server.
|
|
555
683
|
*/
|
|
556
684
|
function createPipeline(config) {
|
|
557
|
-
const { proxy, matchRoute, render, earlyHints, stripTrailingSlash = true, slowRequestMs = 3e3, onPipelineError } = config;
|
|
685
|
+
const { proxy, matchRoute, render, earlyHints, stripTrailingSlash = true, slowRequestMs = 3e3, enableServerTiming = false, onPipelineError } = config;
|
|
558
686
|
return async (req) => {
|
|
559
687
|
const url = new URL(req.url);
|
|
560
688
|
const method = req.method;
|
|
@@ -562,37 +690,47 @@ function createPipeline(config) {
|
|
|
562
690
|
const startTime = performance.now();
|
|
563
691
|
return runWithTraceId(generateTraceId(), async () => {
|
|
564
692
|
return runWithRequestContext(req, async () => {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
"http.request
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
693
|
+
const runRequest = async () => {
|
|
694
|
+
logRequestReceived({
|
|
695
|
+
method,
|
|
696
|
+
path
|
|
697
|
+
});
|
|
698
|
+
const response = await withSpan("http.server.request", {
|
|
699
|
+
"http.request.method": method,
|
|
700
|
+
"url.path": path
|
|
701
|
+
}, async () => {
|
|
702
|
+
const otelIds = await getOtelTraceId();
|
|
703
|
+
if (otelIds) replaceTraceId(otelIds.traceId, otelIds.spanId);
|
|
704
|
+
let result;
|
|
705
|
+
if (proxy || config.proxyLoader) result = await runProxyPhase(req, method, path);
|
|
706
|
+
else result = await handleRequest(req, method, path);
|
|
707
|
+
await setSpanAttribute("http.response.status_code", result.status);
|
|
708
|
+
if (enableServerTiming) {
|
|
709
|
+
const serverTiming = getServerTimingHeader();
|
|
710
|
+
if (serverTiming) {
|
|
711
|
+
result = ensureMutableResponse(result);
|
|
712
|
+
result.headers.set("Server-Timing", serverTiming);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return result;
|
|
716
|
+
});
|
|
717
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
718
|
+
const status = response.status;
|
|
719
|
+
logRequestCompleted({
|
|
720
|
+
method,
|
|
721
|
+
path,
|
|
722
|
+
status,
|
|
723
|
+
durationMs
|
|
724
|
+
});
|
|
725
|
+
if (slowRequestMs > 0 && durationMs > slowRequestMs) logSlowRequest({
|
|
726
|
+
method,
|
|
727
|
+
path,
|
|
728
|
+
durationMs,
|
|
729
|
+
threshold: slowRequestMs
|
|
730
|
+
});
|
|
731
|
+
return response;
|
|
732
|
+
};
|
|
733
|
+
return enableServerTiming ? runWithTimingCollector(runRequest) : runRequest();
|
|
596
734
|
});
|
|
597
735
|
});
|
|
598
736
|
};
|
|
@@ -601,7 +739,8 @@ function createPipeline(config) {
|
|
|
601
739
|
let proxyExport;
|
|
602
740
|
if (config.proxyLoader) proxyExport = (await config.proxyLoader()).default;
|
|
603
741
|
else proxyExport = config.proxy;
|
|
604
|
-
|
|
742
|
+
const proxyFn = () => runProxy(proxyExport, req, () => handleRequest(req, method, path));
|
|
743
|
+
return await withSpan("timber.proxy", {}, () => enableServerTiming ? withTiming("proxy", "proxy.ts", proxyFn) : proxyFn());
|
|
605
744
|
} catch (error) {
|
|
606
745
|
logProxyError({ error });
|
|
607
746
|
await fireOnRequestError(error, req, "proxy");
|
|
@@ -616,6 +755,7 @@ function createPipeline(config) {
|
|
|
616
755
|
if (config.matchMetadataRoute) {
|
|
617
756
|
const metaMatch = config.matchMetadataRoute(canonicalPathname);
|
|
618
757
|
if (metaMatch) try {
|
|
758
|
+
if (metaMatch.isStatic) return await serveStaticMetadataFile(metaMatch);
|
|
619
759
|
const mod = await metaMatch.file.load();
|
|
620
760
|
if (typeof mod.default !== "function") return new Response("Metadata route must export a default function", { status: 500 });
|
|
621
761
|
const handlerResult = await mod.default();
|
|
@@ -684,16 +824,18 @@ function createPipeline(config) {
|
|
|
684
824
|
};
|
|
685
825
|
try {
|
|
686
826
|
setMutableCookieContext(true);
|
|
687
|
-
const
|
|
827
|
+
const middlewareFn = () => runMiddleware(match.middleware, ctx);
|
|
828
|
+
const middlewareResponse = await withSpan("timber.middleware", {}, () => enableServerTiming ? withTiming("mw", "middleware.ts", middlewareFn) : middlewareFn());
|
|
688
829
|
setMutableCookieContext(false);
|
|
689
830
|
if (middlewareResponse) {
|
|
690
|
-
|
|
831
|
+
const finalResponse = ensureMutableResponse(middlewareResponse);
|
|
832
|
+
applyCookieJar(finalResponse.headers);
|
|
691
833
|
logMiddlewareShortCircuit({
|
|
692
834
|
method,
|
|
693
835
|
path,
|
|
694
|
-
status:
|
|
836
|
+
status: finalResponse.status
|
|
695
837
|
});
|
|
696
|
-
return
|
|
838
|
+
return finalResponse;
|
|
697
839
|
}
|
|
698
840
|
applyRequestHeaderOverlay(requestHeaderOverlay);
|
|
699
841
|
} catch (error) {
|
|
@@ -726,7 +868,8 @@ function createPipeline(config) {
|
|
|
726
868
|
}
|
|
727
869
|
applyCookieJar(responseHeaders);
|
|
728
870
|
try {
|
|
729
|
-
const
|
|
871
|
+
const renderFn = () => render(req, match, responseHeaders, requestHeaderOverlay, interception);
|
|
872
|
+
const response = await withSpan("timber.render", { "http.route": canonicalPathname }, () => enableServerTiming ? withTiming("render", "RSC + SSR render", renderFn) : renderFn());
|
|
730
873
|
markResponseFlushed();
|
|
731
874
|
return response;
|
|
732
875
|
} catch (error) {
|
|
@@ -811,6 +954,29 @@ function applyCookieJar(headers) {
|
|
|
811
954
|
for (const value of getSetCookieHeaders()) headers.append("Set-Cookie", value);
|
|
812
955
|
}
|
|
813
956
|
/**
|
|
957
|
+
* Ensure a Response has mutable headers so the pipeline can safely append
|
|
958
|
+
* Set-Cookie and Server-Timing entries.
|
|
959
|
+
*
|
|
960
|
+
* `Response.redirect()` and some platform-level responses return objects
|
|
961
|
+
* with immutable headers. Calling `.set()` or `.append()` on them throws
|
|
962
|
+
* `TypeError: immutable`. This helper detects the immutable case by
|
|
963
|
+
* attempting a no-op write and, on failure, clones into a fresh Response
|
|
964
|
+
* with mutable headers.
|
|
965
|
+
*/
|
|
966
|
+
function ensureMutableResponse(response) {
|
|
967
|
+
try {
|
|
968
|
+
response.headers.set("X-Timber-Probe", "1");
|
|
969
|
+
response.headers.delete("X-Timber-Probe");
|
|
970
|
+
return response;
|
|
971
|
+
} catch {
|
|
972
|
+
return new Response(response.body, {
|
|
973
|
+
status: response.status,
|
|
974
|
+
statusText: response.statusText,
|
|
975
|
+
headers: new Headers(response.headers)
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
814
980
|
* Serialize a sitemap array to XML.
|
|
815
981
|
* Follows the sitemap.org protocol: https://www.sitemaps.org/protocol.html
|
|
816
982
|
*/
|
|
@@ -831,6 +997,39 @@ function serializeSitemap(entries) {
|
|
|
831
997
|
function escapeXml(str) {
|
|
832
998
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
833
999
|
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Content types that are text-based and should include charset=utf-8.
|
|
1002
|
+
* Binary formats (images) should not include charset.
|
|
1003
|
+
*/
|
|
1004
|
+
var TEXT_CONTENT_TYPES = new Set([
|
|
1005
|
+
"application/xml",
|
|
1006
|
+
"text/plain",
|
|
1007
|
+
"application/json",
|
|
1008
|
+
"application/manifest+json",
|
|
1009
|
+
"image/svg+xml"
|
|
1010
|
+
]);
|
|
1011
|
+
/**
|
|
1012
|
+
* Serve a static metadata file by reading it from disk.
|
|
1013
|
+
*
|
|
1014
|
+
* Static metadata route files (.xml, .txt, .json, .png, .ico, .svg, etc.)
|
|
1015
|
+
* are served as-is with the appropriate Content-Type header.
|
|
1016
|
+
* Text files include charset=utf-8; binary files do not.
|
|
1017
|
+
*
|
|
1018
|
+
* See design/16-metadata.md §"Metadata Routes"
|
|
1019
|
+
*/
|
|
1020
|
+
async function serveStaticMetadataFile(metaMatch) {
|
|
1021
|
+
const { contentType, file } = metaMatch;
|
|
1022
|
+
const isText = TEXT_CONTENT_TYPES.has(contentType);
|
|
1023
|
+
const body = await readFile(file.filePath);
|
|
1024
|
+
const headers = {
|
|
1025
|
+
"Content-Type": isText ? `${contentType}; charset=utf-8` : contentType,
|
|
1026
|
+
"Content-Length": String(body.byteLength)
|
|
1027
|
+
};
|
|
1028
|
+
return new Response(body, {
|
|
1029
|
+
status: 200,
|
|
1030
|
+
headers
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
834
1033
|
//#endregion
|
|
835
1034
|
//#region src/server/build-manifest.ts
|
|
836
1035
|
/**
|
|
@@ -996,7 +1195,6 @@ function collectEarlyHintHeaders(segments, manifest, options) {
|
|
|
996
1195
|
*
|
|
997
1196
|
* Design doc: 02-rendering-pipeline.md §"Early Hints (103)"
|
|
998
1197
|
*/
|
|
999
|
-
var earlyHintsSenderAls = new AsyncLocalStorage();
|
|
1000
1198
|
/**
|
|
1001
1199
|
* Run a function with a per-request early hints sender installed.
|
|
1002
1200
|
*
|
|
@@ -2655,22 +2853,6 @@ function stripFiles(value) {
|
|
|
2655
2853
|
//#endregion
|
|
2656
2854
|
//#region src/server/form-flash.ts
|
|
2657
2855
|
/**
|
|
2658
|
-
* Form Flash — ALS-based store for no-JS form action results.
|
|
2659
|
-
*
|
|
2660
|
-
* When a no-JS form action completes, the server re-renders the page with
|
|
2661
|
-
* the action result injected via AsyncLocalStorage instead of redirecting
|
|
2662
|
-
* (which would discard the result). Server components read the flash and
|
|
2663
|
-
* pass it to client form components as the initial `useActionState` value.
|
|
2664
|
-
*
|
|
2665
|
-
* This follows the Remix/Rails pattern — the form component becomes the
|
|
2666
|
-
* single source of truth for both with-JS (React state) and no-JS (flash).
|
|
2667
|
-
*
|
|
2668
|
-
* The flash data is server-side only — never serialized to cookies or headers.
|
|
2669
|
-
*
|
|
2670
|
-
* See design/08-forms-and-actions.md §"No-JS Error Round-Trip"
|
|
2671
|
-
*/
|
|
2672
|
-
var formFlashAls = new AsyncLocalStorage();
|
|
2673
|
-
/**
|
|
2674
2856
|
* Read the form flash data for the current request.
|
|
2675
2857
|
*
|
|
2676
2858
|
* Returns `null` if no flash data is present (i.e., this is a normal page
|
|
@@ -2701,22 +2883,6 @@ function getFormFlash() {
|
|
|
2701
2883
|
//#endregion
|
|
2702
2884
|
//#region src/server/actions.ts
|
|
2703
2885
|
/**
|
|
2704
|
-
* Server action primitives: revalidatePath, revalidateTag, and the action handler.
|
|
2705
|
-
*
|
|
2706
|
-
* - revalidatePath(path) re-renders the route at that path and returns the RSC
|
|
2707
|
-
* flight payload for inline reconciliation.
|
|
2708
|
-
* - revalidateTag(tag) invalidates cached shells and 'use cache' entries by tag.
|
|
2709
|
-
*
|
|
2710
|
-
* Both are callable from anywhere on the server — actions, API routes, handlers.
|
|
2711
|
-
*
|
|
2712
|
-
* The action handler processes incoming action requests, validates CSRF,
|
|
2713
|
-
* enforces body limits, executes the action, and returns the response
|
|
2714
|
-
* (with piggybacked RSC payload if revalidatePath was called).
|
|
2715
|
-
*
|
|
2716
|
-
* See design/08-forms-and-actions.md
|
|
2717
|
-
*/
|
|
2718
|
-
var revalidationAls = new AsyncLocalStorage();
|
|
2719
|
-
/**
|
|
2720
2886
|
* Get the current revalidation state. Throws if called outside an action context.
|
|
2721
2887
|
* @internal
|
|
2722
2888
|
*/
|