@timber-js/app 0.2.0-alpha.157 → 0.2.0-alpha.159
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/{actions-CqE7SW7E.js → actions-CBbP1hHO.js} +22 -6
- package/dist/_chunks/actions-CBbP1hHO.js.map +1 -0
- package/dist/_chunks/{build-output-helper-C8q4Zpf5.js → build-output-helper-DF5uScqS.js} +4 -1
- package/dist/_chunks/{build-output-helper-C8q4Zpf5.js.map → build-output-helper-DF5uScqS.js.map} +1 -1
- package/dist/_chunks/cache-api-DsWEYwVZ.js +1120 -0
- package/dist/_chunks/cache-api-DsWEYwVZ.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-DcmQrzJG.js → cli-schema-sync-Bivx4Rn7.js} +4 -3
- package/dist/_chunks/{cli-schema-sync-DcmQrzJG.js.map → cli-schema-sync-Bivx4Rn7.js.map} +1 -1
- package/dist/_chunks/dist-BA3u1z3W.js +423 -0
- package/dist/_chunks/dist-BA3u1z3W.js.map +1 -0
- package/dist/_chunks/{logger-C5v1IMuh.js → logger-CrRmZMlo.js} +81 -56
- package/dist/_chunks/logger-CrRmZMlo.js.map +1 -0
- package/dist/_chunks/{plugin-context-DUl3dnCF.js → plugin-context-DeAxFRMq.js} +3 -36
- package/dist/_chunks/plugin-context-DeAxFRMq.js.map +1 -0
- package/dist/_chunks/sizeof-lXffQ1Vr.js +35 -0
- package/dist/_chunks/sizeof-lXffQ1Vr.js.map +1 -0
- package/dist/_chunks/{walkers-D5jCGPum.js → walkers-DXbJkeCY.js} +217 -19
- package/dist/_chunks/walkers-DXbJkeCY.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -1
- package/dist/adapters/cloudflare.d.ts +28 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +143 -9
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/nitro.js +1 -1
- package/dist/cache/cache-api.d.ts +2 -0
- package/dist/cache/cache-api.d.ts.map +1 -1
- package/dist/cache/handler-store.d.ts +2 -0
- package/dist/cache/handler-store.d.ts.map +1 -1
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +3 -186
- package/dist/cache/redis-handler.d.ts +17 -5
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts +1 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/store.d.ts +53 -0
- package/dist/cache/store.d.ts.map +1 -0
- package/dist/cache/stores/cloudflare-kv.d.ts +38 -0
- package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -0
- package/dist/cache/stores/cloudflare-kv.js +29 -0
- package/dist/cache/stores/cloudflare-kv.js.map +1 -0
- package/dist/cache/stores/memory.d.ts +42 -0
- package/dist/cache/stores/memory.d.ts.map +1 -0
- package/dist/cache/stores/memory.js +90 -0
- package/dist/cache/stores/memory.js.map +1 -0
- package/dist/cache/stores/redis.d.ts +69 -0
- package/dist/cache/stores/redis.d.ts.map +1 -0
- package/dist/cache/stores/redis.js +35 -0
- package/dist/cache/stores/redis.js.map +1 -0
- package/dist/cache/stores/vercel.d.ts +36 -0
- package/dist/cache/stores/vercel.d.ts.map +1 -0
- package/dist/cache/stores/vercel.js +33 -0
- package/dist/cache/stores/vercel.js.map +1 -0
- package/dist/cache/tag-aware-handler.d.ts +51 -0
- package/dist/cache/tag-aware-handler.d.ts.map +1 -0
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cdn/cloudflare-purge.d.ts +23 -0
- package/dist/cdn/cloudflare-purge.d.ts.map +1 -0
- package/dist/cdn/fastly-purge.d.ts +25 -0
- package/dist/cdn/fastly-purge.d.ts.map +1 -0
- package/dist/cdn/headers.d.ts +82 -0
- package/dist/cdn/headers.d.ts.map +1 -0
- package/dist/cdn/index.d.ts +5 -0
- package/dist/cdn/index.d.ts.map +1 -0
- package/dist/cdn/purge-store.d.ts +23 -0
- package/dist/cdn/purge-store.d.ts.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/history.d.ts +0 -3
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.js +0 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +11 -86
- package/dist/client/internal.js.map +1 -1
- package/dist/client/navigation-api.d.ts +1 -3
- package/dist/client/navigation-api.d.ts.map +1 -1
- package/dist/client/router.d.ts +1 -4
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +1 -11
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +1 -3
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/config-types.d.ts +3 -1
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/cookies/index.js +15 -11
- package/dist/cookies/index.js.map +1 -1
- package/dist/dev-tools/browser-logs.d.ts +28 -72
- package/dist/dev-tools/browser-logs.d.ts.map +1 -1
- package/dist/dev-tools/index.d.ts +1 -1
- package/dist/dev-tools/index.d.ts.map +1 -1
- package/dist/dev-tools/logger.d.ts.map +1 -1
- package/dist/dev-tools/terminal.d.ts.map +1 -1
- package/dist/fonts/transform.d.ts +4 -0
- package/dist/fonts/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1269 -359
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +36 -0
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +21 -0
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/cache.d.ts +5 -14
- package/dist/plugins/cache.d.ts.map +1 -1
- package/dist/plugins/callsite-ast.d.ts +95 -0
- package/dist/plugins/callsite-ast.d.ts.map +1 -0
- package/dist/plugins/callsite-jsx.d.ts +35 -0
- package/dist/plugins/callsite-jsx.d.ts.map +1 -0
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/prebuilt-capture.d.ts +60 -0
- package/dist/plugins/prebuilt-capture.d.ts.map +1 -0
- package/dist/plugins/prebuilt.d.ts +82 -0
- package/dist/plugins/prebuilt.d.ts.map +1 -0
- package/dist/plugins/prerender-sugar.d.ts +42 -0
- package/dist/plugins/prerender-sugar.d.ts.map +1 -0
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/export-detect.d.ts +56 -0
- package/dist/routing/export-detect.d.ts.map +1 -0
- package/dist/routing/index.js +2 -2
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/rsc-runtime/rsc.d.ts +1 -1
- package/dist/rsc-runtime/rsc.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +0 -3
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +7 -0
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/cookie-parsing.d.ts +15 -1
- package/dist/server/cookie-parsing.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/form-data.d.ts +12 -0
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +1 -0
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/internal.d.ts +6 -1
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +563 -74
- package/dist/server/internal.js.map +1 -1
- package/dist/server/prebuilt/cache-key.d.ts +58 -0
- package/dist/server/prebuilt/cache-key.d.ts.map +1 -0
- package/dist/server/prebuilt/capture-state.d.ts +57 -0
- package/dist/server/prebuilt/capture-state.d.ts.map +1 -0
- package/dist/server/prebuilt/manifest-tag-index.d.ts +27 -0
- package/dist/server/prebuilt/manifest-tag-index.d.ts.map +1 -0
- package/dist/server/prebuilt/overlay.d.ts +100 -0
- package/dist/server/prebuilt/overlay.d.ts.map +1 -0
- package/dist/server/prebuilt/payload-source.d.ts +75 -0
- package/dist/server/prebuilt/payload-source.d.ts.map +1 -0
- package/dist/server/prebuilt/synthetic-store.d.ts +64 -0
- package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -0
- package/dist/server/prebuilt-builder.d.ts +110 -0
- package/dist/server/prebuilt-builder.d.ts.map +1 -0
- package/dist/server/prebuilt-runtime.d.ts +101 -0
- package/dist/server/prebuilt-runtime.d.ts.map +1 -0
- package/dist/server/route-element-builder.d.ts +10 -8
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +0 -5
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +10 -0
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +3 -4
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/sitemap-generator.d.ts +6 -6
- package/dist/server/sitemap-generator.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +37 -3
- package/dist/server/tracing.d.ts.map +1 -1
- package/docs/api/32-api-cache.mdx +10 -4
- package/docs/learn/00-introduction.mdx +8 -13
- package/docs/learn/02-pages-and-layouts.mdx +9 -0
- package/docs/learn/06-forms-and-actions.mdx +0 -4
- package/docs/learn/07-typed-routes.mdx +127 -0
- package/docs/learn/09-caching.mdx +12 -1
- package/docs/learn/10-middleware.mdx +1 -0
- package/docs/learn/11-error-handling.mdx +9 -0
- package/docs/learn/13-configuration.mdx +28 -0
- package/docs/learn/14-deploying.mdx +52 -0
- package/docs/more/06-instrumentation.mdx +27 -1
- package/docs/more/07-security.mdx +8 -2
- package/docs/more/08-developer-experience.mdx +134 -0
- package/package.json +31 -2
- package/src/adapters/build-output-helper.ts +10 -0
- package/src/adapters/cloudflare.ts +178 -4
- package/src/cache/cache-api.ts +60 -0
- package/src/cache/handler-store.ts +8 -0
- package/src/cache/index.ts +4 -0
- package/src/cache/redis-handler.ts +25 -13
- package/src/cache/singleflight.ts +11 -11
- package/src/cache/store.ts +71 -0
- package/src/cache/stores/cloudflare-kv.ts +52 -0
- package/src/cache/stores/memory.ts +125 -0
- package/src/cache/stores/redis.ts +97 -0
- package/src/cache/stores/vercel.ts +59 -0
- package/src/cache/tag-aware-handler.ts +233 -0
- package/src/cache/timber-cache.ts +17 -7
- package/src/cdn/cloudflare-purge.ts +50 -0
- package/src/cdn/fastly-purge.ts +63 -0
- package/src/cdn/headers.ts +127 -0
- package/src/cdn/index.ts +16 -0
- package/src/cdn/purge-store.ts +34 -0
- package/src/client/browser-entry/action-dispatch.ts +1 -10
- package/src/client/browser-entry/post-hydration.ts +0 -1
- package/src/client/browser-entry/router-init.ts +0 -5
- package/src/client/history.ts +0 -4
- package/src/client/navigation-api.ts +5 -11
- package/src/client/router.ts +9 -43
- package/src/client/rsc-fetch.ts +5 -27
- package/src/client/segment-cache.ts +1 -4
- package/src/client/use-cookie.ts +18 -12
- package/src/config-types.ts +3 -1
- package/src/config-validation.ts +1 -0
- package/src/dev-tools/browser-logs.ts +37 -273
- package/src/dev-tools/index.ts +2 -2
- package/src/dev-tools/instrumentation.ts +1 -1
- package/src/dev-tools/logger.ts +33 -51
- package/src/dev-tools/terminal.ts +21 -22
- package/src/fonts/transform.ts +4 -43
- package/src/index.ts +16 -3
- package/src/plugin-context.ts +36 -0
- package/src/plugins/build-report.ts +133 -1
- package/src/plugins/cache.ts +32 -131
- package/src/plugins/callsite-ast.ts +167 -0
- package/src/plugins/callsite-jsx.ts +243 -0
- package/src/plugins/entries.ts +23 -4
- package/src/plugins/fonts.ts +16 -2
- package/src/plugins/prebuilt-capture.ts +208 -0
- package/src/plugins/prebuilt.ts +673 -0
- package/src/plugins/prerender-sugar.ts +329 -0
- package/src/plugins/server-bundle.ts +12 -0
- package/src/plugins/shims.ts +9 -0
- package/src/plugins/static-build.ts +0 -1
- package/src/routing/codegen.ts +1 -21
- package/src/routing/convention-lint.ts +85 -49
- package/src/routing/export-detect.ts +304 -0
- package/src/routing/manifest-codegen.ts +2 -11
- package/src/rsc-runtime/rsc.ts +7 -0
- package/src/server/action-handler.ts +11 -27
- package/src/server/actions.ts +3 -4
- package/src/server/als-registry.ts +7 -0
- package/src/server/cookie-parsing.ts +75 -59
- package/src/server/deny-renderer.ts +7 -15
- package/src/server/form-data.ts +23 -7
- package/src/server/html-injectors.ts +11 -3
- package/src/server/internal.ts +17 -1
- package/src/server/prebuilt/cache-key.ts +224 -0
- package/src/server/prebuilt/capture-state.ts +89 -0
- package/src/server/prebuilt/manifest-tag-index.ts +46 -0
- package/src/server/prebuilt/overlay.ts +256 -0
- package/src/server/prebuilt/payload-source.ts +192 -0
- package/src/server/prebuilt/synthetic-store.ts +108 -0
- package/src/server/prebuilt-builder.ts +640 -0
- package/src/server/prebuilt-runtime.ts +706 -0
- package/src/server/route-element-builder.ts +40 -12
- package/src/server/rsc-entry/helpers.ts +0 -17
- package/src/server/rsc-entry/index.ts +83 -0
- package/src/server/rsc-entry/render-route.ts +6 -33
- package/src/server/rsc-entry/revalidate-renderer.ts +16 -11
- package/src/server/rsc-entry/rsc-payload.ts +5 -11
- package/src/server/sitemap-generator.ts +16 -10
- package/src/server/tracing.ts +88 -3
- package/dist/_chunks/actions-CqE7SW7E.js.map +0 -1
- package/dist/_chunks/cache-api-Cbyh3RW4.js +0 -401
- package/dist/_chunks/cache-api-Cbyh3RW4.js.map +0 -1
- package/dist/_chunks/logger-C5v1IMuh.js.map +0 -1
- package/dist/_chunks/plugin-context-DUl3dnCF.js.map +0 -1
- package/dist/_chunks/validation-CnKhGJsp.js +0 -113
- package/dist/_chunks/validation-CnKhGJsp.js.map +0 -1
- package/dist/_chunks/walkers-D5jCGPum.js.map +0 -1
- package/dist/cache/index.js.map +0 -1
- package/dist/client/head.d.ts +0 -16
- package/dist/client/head.d.ts.map +0 -1
- package/dist/client/navigation-api-types.d.ts +0 -90
- package/dist/client/navigation-api-types.d.ts.map +0 -1
- package/dist/server/client-module-map.d.ts +0 -47
- package/dist/server/client-module-map.d.ts.map +0 -1
- package/dist/shared/head-header.d.ts +0 -54
- package/dist/shared/head-header.d.ts.map +0 -1
- package/src/client/head.ts +0 -61
- package/src/client/navigation-api-types.ts +0 -112
- package/src/server/client-module-map.ts +0 -58
- package/src/shared/head-header.ts +0 -69
package/dist/server/internal.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { a as classifyMetadataRoute, i as METADATA_ROUTE_CONVENTIONS, o as getMetadataRouteAutoLink, r as canonicalize, s as getMetadataRouteServePath } from "../_chunks/canonicalize-P41GR6tY.js";
|
|
2
2
|
import { n as classifyUrlSegment } from "../_chunks/segment-classify-CDDRVKs7.js";
|
|
3
|
-
import { A as runWithRequestContext, D as getSearchParams, G as getTraceId, H as generateTraceId, I as getSetCookieHeaders, J as setSpanAttribute, K as replaceTraceId, M as setMutableCookieContext, N as setSegmentParams, O as getSegmentParams, P as getCookie, Q as requestContextAls, R as isDebug, T as getHeader, U as getOtelTraceId, X as earlyHintsSenderAls, Y as withSpan, _ as RedirectSignal, a as logProxyError, c as logRequestReceived, d as logSwrRefetchFailed, et as timingAls, f as logWaitUntilRejected, g as DenySignal, h as swallow, i as logMiddlewareShortCircuit, j as setMatchedSegmentPath, k as markResponseFlushed, l as logRouteError, m as setLogger, n as logCacheMiss, o as logRenderError, p as logWaitUntilUnsupported, q as runWithTraceId, r as logMiddlewareError, s as logRequestCompleted, t as getLogger, u as logSlowRequest, v as RenderError, w as applyRequestHeaderOverlay } from "../_chunks/logger-
|
|
4
|
-
import { i as coerce, t as executeAction } from "../_chunks/actions-
|
|
3
|
+
import { A as runWithRequestContext, D as getSearchParams, G as getTraceId, H as generateTraceId, I as getSetCookieHeaders, J as setSpanAttribute, K as replaceTraceId, L as getWaitUntil, M as setMutableCookieContext, N as setSegmentParams, O as getSegmentParams, P as getCookie, Q as requestContextAls, R as isDebug, T as getHeader, U as getOtelTraceId, X as earlyHintsSenderAls, Y as withSpan, _ as RedirectSignal, a as logProxyError, c as logRequestReceived, d as logSwrRefetchFailed, et as timingAls, f as logWaitUntilRejected, g as DenySignal, h as swallow, i as logMiddlewareShortCircuit, j as setMatchedSegmentPath, k as markResponseFlushed, l as logRouteError, m as setLogger, n as logCacheMiss, o as logRenderError, p as logWaitUntilUnsupported, q as runWithTraceId, r as logMiddlewareError, s as logRequestCompleted, t as getLogger, u as logSlowRequest, v as RenderError, w as applyRequestHeaderOverlay } from "../_chunks/logger-CrRmZMlo.js";
|
|
4
|
+
import { i as coerce, t as executeAction } from "../_chunks/actions-CBbP1hHO.js";
|
|
5
|
+
import { _ as currentInvalidationEpoch, a as getManifestEntry, b as createSingleflight, c as setPrebuiltPayloadSource, i as storeOverlayEntry, l as applyReloadHeaders, n as lookupOverlay, o as hasPrebuiltPayloadSource, r as overlayKey, s as lookupPrebuiltPayload, u as checkVersionSkew, v as lastInvalidationEpochFor, y as wasInvalidatedSince } from "../_chunks/cache-api-DsWEYwVZ.js";
|
|
5
6
|
import "../client/error-boundary.js";
|
|
6
7
|
import { n as toBracketKey } from "../_chunks/resolve-schema-3iUvBV5T.js";
|
|
7
8
|
import "../_chunks/segment-context-ZDnXDkbz.js";
|
|
8
9
|
import { readFile } from "node:fs/promises";
|
|
9
|
-
import "node:crypto";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
10
11
|
import React, { createElement } from "react";
|
|
11
12
|
//#region src/server/server-timing.ts
|
|
12
13
|
/**
|
|
@@ -1681,76 +1682,6 @@ var ParamCoercionError = class extends Error {
|
|
|
1681
1682
|
}
|
|
1682
1683
|
};
|
|
1683
1684
|
//#endregion
|
|
1684
|
-
//#region src/server/version-skew.ts
|
|
1685
|
-
/**
|
|
1686
|
-
* Version Skew Detection — graceful recovery when stale clients hit new deployments.
|
|
1687
|
-
*
|
|
1688
|
-
* When a new version of the app is deployed, clients with open tabs still have
|
|
1689
|
-
* the old JavaScript bundle. Without version skew handling, these stale clients
|
|
1690
|
-
* will experience:
|
|
1691
|
-
*
|
|
1692
|
-
* 1. Server action calls that crash (action IDs are content-hashed)
|
|
1693
|
-
* 2. Chunk load failures (old filenames gone from CDN)
|
|
1694
|
-
* 3. RSC payload mismatches (component references differ between builds)
|
|
1695
|
-
*
|
|
1696
|
-
* This module implements deployment ID comparison:
|
|
1697
|
-
* - A per-build deployment ID is generated at build time (see build-manifest.ts)
|
|
1698
|
-
* - The client sends it via `X-Timber-Deployment-Id` header on every RSC/action request
|
|
1699
|
-
* - The server compares it against the current build's ID
|
|
1700
|
-
* - On mismatch: signal the client to reload (not crash)
|
|
1701
|
-
*
|
|
1702
|
-
* The deployment ID is always-on in production. Dev mode skips the check
|
|
1703
|
-
* (HMR handles code updates without full reloads).
|
|
1704
|
-
*
|
|
1705
|
-
* See design/25-production-deployments.md, TIM-446
|
|
1706
|
-
*/
|
|
1707
|
-
/** Header sent by the client with every RSC/action request. */
|
|
1708
|
-
var DEPLOYMENT_ID_HEADER = "X-Timber-Deployment-Id";
|
|
1709
|
-
/** Response header that signals the client to do a full page reload. */
|
|
1710
|
-
var RELOAD_HEADER = "X-Timber-Reload";
|
|
1711
|
-
/**
|
|
1712
|
-
* The current build's deployment ID. Set at startup from the manifest init
|
|
1713
|
-
* module (globalThis.__TIMBER_DEPLOYMENT_ID__). Null in dev mode.
|
|
1714
|
-
*/
|
|
1715
|
-
var currentDeploymentId = null;
|
|
1716
|
-
/**
|
|
1717
|
-
* Check if a request's deployment ID matches the current build.
|
|
1718
|
-
*
|
|
1719
|
-
* Returns `{ ok: true }` when:
|
|
1720
|
-
* - Dev mode (no deployment ID set — HMR handles updates)
|
|
1721
|
-
* - No deployment ID header (initial page load, non-RSC request)
|
|
1722
|
-
* - Deployment IDs match
|
|
1723
|
-
*
|
|
1724
|
-
* Returns `{ ok: false }` when:
|
|
1725
|
-
* - Client sends a deployment ID that differs from the current build
|
|
1726
|
-
*/
|
|
1727
|
-
function checkVersionSkew(req) {
|
|
1728
|
-
if (!currentDeploymentId) return {
|
|
1729
|
-
ok: true,
|
|
1730
|
-
clientId: null
|
|
1731
|
-
};
|
|
1732
|
-
const clientId = req.headers.get(DEPLOYMENT_ID_HEADER);
|
|
1733
|
-
if (!clientId) return {
|
|
1734
|
-
ok: true,
|
|
1735
|
-
clientId: null
|
|
1736
|
-
};
|
|
1737
|
-
if (clientId === currentDeploymentId) return {
|
|
1738
|
-
ok: true,
|
|
1739
|
-
clientId
|
|
1740
|
-
};
|
|
1741
|
-
return {
|
|
1742
|
-
ok: false,
|
|
1743
|
-
clientId
|
|
1744
|
-
};
|
|
1745
|
-
}
|
|
1746
|
-
/**
|
|
1747
|
-
* Apply version skew reload headers to a response.
|
|
1748
|
-
* Sets X-Timber-Reload: 1 to signal the client to do a full page reload.
|
|
1749
|
-
*/
|
|
1750
|
-
function applyReloadHeaders(headers) {
|
|
1751
|
-
headers.set(RELOAD_HEADER, "1");
|
|
1752
|
-
}
|
|
1753
|
-
//#endregion
|
|
1754
1685
|
//#region src/server/pipeline-metadata.ts
|
|
1755
1686
|
/**
|
|
1756
1687
|
* Metadata route helpers for the request pipeline.
|
|
@@ -3083,6 +3014,564 @@ var RenderTimeoutError = class extends Error {
|
|
|
3083
3014
|
}
|
|
3084
3015
|
};
|
|
3085
3016
|
//#endregion
|
|
3086
|
-
|
|
3017
|
+
//#region src/server/prebuilt/cache-key.ts
|
|
3018
|
+
/**
|
|
3019
|
+
* Component cache-key hashing for prebuilt (cache.component) entries.
|
|
3020
|
+
*
|
|
3021
|
+
* Key: `sha256(canonicalJson({ props, params })).slice(0, 16)` — see
|
|
3022
|
+
* design/44-render-at-build-time.md §Cache Key. Both the request-time
|
|
3023
|
+
* lookup (prebuilt-runtime.ts) and the post-build capture pass (TIM-1119)
|
|
3024
|
+
* MUST use this exact function, or build artifacts become unaddressable.
|
|
3025
|
+
*
|
|
3026
|
+
* THE IDENTITY CONTRACT (design/44 §Cache Key): cache identity is **JSON
|
|
3027
|
+
* value semantics** — two inputs are the same entry iff they are the same
|
|
3028
|
+
* JSON value. This is an ALLOWLIST, not a denylist: a value participates
|
|
3029
|
+
* in caching iff it is plain JSON data — null, boolean, finite number,
|
|
3030
|
+
* string, dense array, or a plain `Object.prototype` object with only
|
|
3031
|
+
* enumerable string-keyed data properties — plus `undefined` and `-0`,
|
|
3032
|
+
* which hash as distinct sentinels because flight preserves them. Every
|
|
3033
|
+
* value outside that domain (functions, symbols, bigints, accessors,
|
|
3034
|
+
* hidden or symbol-keyed properties, non-`Object.prototype` prototypes
|
|
3035
|
+
* including null, aliased references, sparse arrays, cycles, non-finite
|
|
3036
|
+
* numbers) is UNKEYABLE by definition: `computeComponentCacheKey` returns
|
|
3037
|
+
* null and the caller renders live — slower, never wrong.
|
|
3038
|
+
*
|
|
3039
|
+
* The line between rejected and declared-out-of-domain: values are
|
|
3040
|
+
* REJECTED when the JSON projection itself would be ambiguous or lossy
|
|
3041
|
+
* (hidden/symbol/non-enumerable properties, accessors, exotic
|
|
3042
|
+
* prototypes). Observable state that leaves the projection intact — key
|
|
3043
|
+
* order, reference identity, descriptor metadata (writable/configurable,
|
|
3044
|
+
* frozen/sealed/extensible), proxy behavior — is DECLARED outside
|
|
3045
|
+
* cache.component's supported input domain rather than rejected:
|
|
3046
|
+
* key-order insensitivity is required for determinism, and rejecting
|
|
3047
|
+
* frozen objects would unkey ubiquitous legitimate inputs
|
|
3048
|
+
* (Object.freeze'd config props) to defend against components that
|
|
3049
|
+
* render differently based on property attributes.
|
|
3050
|
+
*
|
|
3051
|
+
* Two hard rules for this walk itself: it must be TOTAL (never throw
|
|
3052
|
+
* anything but UnkeyableValueError) and SIDE-EFFECT-FREE (descriptor
|
|
3053
|
+
* reads only; never invoke user code such as getters).
|
|
3054
|
+
*
|
|
3055
|
+
* Object keys sort recursively so `{a, b}` and `{b, a}` hash identically
|
|
3056
|
+
* (design/13-security.md checklist #14 — cache key determinism; key order
|
|
3057
|
+
* is deliberately NOT part of identity).
|
|
3058
|
+
*/
|
|
3059
|
+
var UnkeyableValueError = class extends Error {};
|
|
3060
|
+
function canonicalize$1(value, seen) {
|
|
3061
|
+
if (value === null) return "null";
|
|
3062
|
+
switch (typeof value) {
|
|
3063
|
+
case "string": return JSON.stringify(value);
|
|
3064
|
+
case "boolean": return value ? "true" : "false";
|
|
3065
|
+
case "number":
|
|
3066
|
+
if (!Number.isFinite(value)) throw new UnkeyableValueError("non-finite number");
|
|
3067
|
+
return Object.is(value, -0) ? "-0" : String(value);
|
|
3068
|
+
case "object": break;
|
|
3069
|
+
case "undefined": return "undefined";
|
|
3070
|
+
default: throw new UnkeyableValueError(`unsupported type: ${typeof value}`);
|
|
3071
|
+
}
|
|
3072
|
+
const obj = value;
|
|
3073
|
+
if (seen.has(obj)) throw new UnkeyableValueError("repeated object reference (cycle or alias)");
|
|
3074
|
+
seen.add(obj);
|
|
3075
|
+
if (Object.getOwnPropertySymbols(obj).length > 0) throw new UnkeyableValueError("symbol-keyed property");
|
|
3076
|
+
{
|
|
3077
|
+
if (Array.isArray(obj)) {
|
|
3078
|
+
if (Object.getOwnPropertyNames(obj).length !== obj.length + 1) throw new UnkeyableValueError("non-index array property");
|
|
3079
|
+
const parts = [];
|
|
3080
|
+
for (let i = 0; i < obj.length; i++) {
|
|
3081
|
+
const desc = Object.getOwnPropertyDescriptor(obj, i);
|
|
3082
|
+
if (desc === void 0) throw new UnkeyableValueError("sparse array hole");
|
|
3083
|
+
if (desc.get !== void 0 || desc.set !== void 0) throw new UnkeyableValueError("accessor property");
|
|
3084
|
+
if (!desc.enumerable) throw new UnkeyableValueError("non-enumerable array element");
|
|
3085
|
+
parts.push(canonicalize$1(desc.value, seen));
|
|
3086
|
+
}
|
|
3087
|
+
return "[" + parts.join(",") + "]";
|
|
3088
|
+
}
|
|
3089
|
+
if (Object.getPrototypeOf(obj) !== Object.prototype) throw new UnkeyableValueError("non-plain object");
|
|
3090
|
+
const keys = Object.keys(obj).sort();
|
|
3091
|
+
if (keys.length !== Object.getOwnPropertyNames(obj).length) throw new UnkeyableValueError("non-enumerable property");
|
|
3092
|
+
const parts = [];
|
|
3093
|
+
for (const key of keys) {
|
|
3094
|
+
const desc = Object.getOwnPropertyDescriptor(obj, key);
|
|
3095
|
+
if (desc.get !== void 0 || desc.set !== void 0) throw new UnkeyableValueError("accessor property");
|
|
3096
|
+
parts.push(JSON.stringify(key) + ":" + canonicalize$1(desc.value, seen));
|
|
3097
|
+
}
|
|
3098
|
+
return "{" + parts.join(",") + "}";
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
/**
|
|
3102
|
+
* Canonicalize a single value, or null if it is unkeyable. The ONE
|
|
3103
|
+
* definition of "faithfully comparable" — every prebuilt code path that
|
|
3104
|
+
* needs value equality (cache keys, slot-param divergence) goes through
|
|
3105
|
+
* this, so the unkeyable rules (accessors, hidden props, aliases,
|
|
3106
|
+
* bigints, …) live in exactly one place.
|
|
3107
|
+
*/
|
|
3108
|
+
function tryCanonicalize(value) {
|
|
3109
|
+
try {
|
|
3110
|
+
return canonicalize$1(value, /* @__PURE__ */ new Set());
|
|
3111
|
+
} catch (error) {
|
|
3112
|
+
return toUnkeyable(error);
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
/**
|
|
3116
|
+
* Totality by construction: ANY throw from the walk means the input is
|
|
3117
|
+
* outside the keyable domain. The walk touches only user-controlled
|
|
3118
|
+
* values through reflection ops — and a Proxy trap can throw arbitrary
|
|
3119
|
+
* errors from any of them (codex review on PR #835) — so converting only
|
|
3120
|
+
* UnkeyableValueError would let hostile/exotic inputs abort a render
|
|
3121
|
+
* that plain live rendering would have served fine. Unexpected error
|
|
3122
|
+
* types are logged for visibility (they would indicate either a proxy
|
|
3123
|
+
* trap or a canonicalizer bug) but still degrade to unkeyable.
|
|
3124
|
+
*/
|
|
3125
|
+
function toUnkeyable(error) {
|
|
3126
|
+
if (!(error instanceof UnkeyableValueError)) console.error("[timber] cache key canonicalization threw — treating input as unkeyable:", error);
|
|
3127
|
+
return null;
|
|
3128
|
+
}
|
|
3129
|
+
/**
|
|
3130
|
+
* Compute the cache-entry hash for a component render:
|
|
3131
|
+
* `sha256({props, params}).slice(0, 16)` over canonical JSON.
|
|
3132
|
+
*
|
|
3133
|
+
* Returns null when props (or params) contain unkeyable values — the
|
|
3134
|
+
* caller must fall back to dynamic rendering.
|
|
3135
|
+
*/
|
|
3136
|
+
function computeComponentCacheKey(props, params) {
|
|
3137
|
+
let canonical;
|
|
3138
|
+
try {
|
|
3139
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3140
|
+
canonical = "{\"params\":" + canonicalize$1({ ...params }, seen) + ",\"props\":" + canonicalize$1(props, seen) + "}";
|
|
3141
|
+
} catch (error) {
|
|
3142
|
+
return toUnkeyable(error);
|
|
3143
|
+
}
|
|
3144
|
+
return createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
3145
|
+
}
|
|
3146
|
+
//#endregion
|
|
3147
|
+
//#region src/server/prebuilt/capture-state.ts
|
|
3148
|
+
/** Thrown by the wrapper when a capture renders its own component. */
|
|
3149
|
+
var PrebuiltCaptureCycleError = class extends Error {
|
|
3150
|
+
constructor(componentId) {
|
|
3151
|
+
super(`[timber] cache.component "${componentId}" renders itself (directly or through other components) — prerendering it would recurse forever. Remove the cycle or drop { prerender: true } on this component.`);
|
|
3152
|
+
this.name = "PrebuiltCaptureCycleError";
|
|
3153
|
+
}
|
|
3154
|
+
};
|
|
3155
|
+
var active = null;
|
|
3156
|
+
/**
|
|
3157
|
+
* Mark a capture as active. The builder calls this immediately before
|
|
3158
|
+
* rendering an entry and MUST pair it with `endCapture()` in a finally.
|
|
3159
|
+
*/
|
|
3160
|
+
function beginCapture(outerId) {
|
|
3161
|
+
active = {
|
|
3162
|
+
outerId,
|
|
3163
|
+
nested: /* @__PURE__ */ new Map()
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3166
|
+
/**
|
|
3167
|
+
* End the active capture and return the wrapped components that rendered
|
|
3168
|
+
* inside it (first options object wins per inner ID).
|
|
3169
|
+
*/
|
|
3170
|
+
function endCapture() {
|
|
3171
|
+
const nested = active?.nested;
|
|
3172
|
+
active = null;
|
|
3173
|
+
if (!nested) return [];
|
|
3174
|
+
return Array.from(nested, ([innerId, options]) => ({
|
|
3175
|
+
innerId,
|
|
3176
|
+
options
|
|
3177
|
+
}));
|
|
3178
|
+
}
|
|
3179
|
+
/** Whether a build-time capture is currently rendering. */
|
|
3180
|
+
function isCaptureActive() {
|
|
3181
|
+
return active !== null;
|
|
3182
|
+
}
|
|
3183
|
+
/**
|
|
3184
|
+
* Called by the `__prebuilt` wrapper on every render. No-op unless a
|
|
3185
|
+
* capture is active. Throws `PrebuiltCaptureCycleError` when the wrapper
|
|
3186
|
+
* belongs to the component currently being captured.
|
|
3187
|
+
*/
|
|
3188
|
+
function noteWrapperRender(innerId, options) {
|
|
3189
|
+
if (active === null) return;
|
|
3190
|
+
if (innerId === active.outerId) throw new PrebuiltCaptureCycleError(innerId);
|
|
3191
|
+
if (!active.nested.has(innerId)) active.nested.set(innerId, options);
|
|
3192
|
+
}
|
|
3193
|
+
//#endregion
|
|
3194
|
+
//#region src/server/prebuilt-runtime.ts
|
|
3195
|
+
/**
|
|
3196
|
+
* Prebuilt component runtime — registration target for the timber-prebuilt
|
|
3197
|
+
* transform (plugins/prebuilt.ts) and the request-time half of
|
|
3198
|
+
* cache.component (TIM-1140).
|
|
3199
|
+
*
|
|
3200
|
+
* `cache.component(fn, opts)` call sites are rewritten at build time to
|
|
3201
|
+
* `__prebuilt(stableId, fn, opts)` (production) or `__prebuiltDev(name, fn)`
|
|
3202
|
+
* (dev). Both are exported via #server-internal — they are framework
|
|
3203
|
+
* plumbing, never called by user code directly.
|
|
3204
|
+
*
|
|
3205
|
+
* Cache hits are served by FLIGHT REVIVAL: the cached `.flight` payload is
|
|
3206
|
+
* decoded back into React elements inside the RSC environment with
|
|
3207
|
+
* `createFromReadableStream` (the flight client running in the flight
|
|
3208
|
+
* server — plugin-rsc wires the server-decode manifest so client
|
|
3209
|
+
* references revive as re-serializable reference proxies). The revived
|
|
3210
|
+
* element is returned from the wrapper and the outer render re-serializes
|
|
3211
|
+
* it like any other subtree. This is the same mechanism as Next.js
|
|
3212
|
+
* `"use cache"` (next/src/server/use-cache/use-cache-wrapper.ts).
|
|
3213
|
+
*
|
|
3214
|
+
* What a hit skips: all user work — data fetching, MDX compilation, the
|
|
3215
|
+
* component function itself never runs. What it re-pays: React
|
|
3216
|
+
* re-serializes the already-resolved tree (cost proportional to payload
|
|
3217
|
+
* size, not to data latency). The alternative — splicing raw payload bytes
|
|
3218
|
+
* into the outer stream — avoids re-serialization but requires byte-level
|
|
3219
|
+
* coupling to the flight wire format (row framing, ID rebasing, marker
|
|
3220
|
+
* detection); it was implemented and reviewed on PR #835 and deliberately
|
|
3221
|
+
* replaced by revival. See design/44-render-at-build-time.md
|
|
3222
|
+
* §Request-Time Injection.
|
|
3223
|
+
*
|
|
3224
|
+
* Misses render the original component live via the wrapper's own closure,
|
|
3225
|
+
* inside the React pass — segment params from ALS, deny()/redirect()
|
|
3226
|
+
* signals, Suspense, and error boundaries behave exactly as an unwrapped
|
|
3227
|
+
* component. `fallbackOnMissingSegmentParams: false` throws instead.
|
|
3228
|
+
*/
|
|
3229
|
+
/** Default timeout for component re-renders (overridden by renderTimeoutMs). */
|
|
3230
|
+
var DEFAULT_COMPONENT_TIMEOUT_MS = 3e4;
|
|
3231
|
+
/** Singleflight for component re-renders (inline and background). */
|
|
3232
|
+
var componentSingleflight = createSingleflight({ timeoutMs: DEFAULT_COMPONENT_TIMEOUT_MS });
|
|
3233
|
+
var componentTimeoutMs = DEFAULT_COMPONENT_TIMEOUT_MS;
|
|
3234
|
+
var warnedNoWaitUntil = false;
|
|
3235
|
+
/**
|
|
3236
|
+
* Server-side component registry, keyed by stable component ID. Populated at
|
|
3237
|
+
* module load time by `__prebuilt` calls; consumed by the post-build capture
|
|
3238
|
+
* pass (TIM-1119). Never serialized — component functions are not
|
|
3239
|
+
* Flight-serializable.
|
|
3240
|
+
*/
|
|
3241
|
+
var registry = /* @__PURE__ */ new Map();
|
|
3242
|
+
/**
|
|
3243
|
+
* Resolve the params the cache key may faithfully hash, or null if this
|
|
3244
|
+
* render is unkeyable.
|
|
3245
|
+
*
|
|
3246
|
+
* A wrapper rendered inside a parallel slot reads params merged with that
|
|
3247
|
+
* slot's overrides (`getSegmentParams`: `{...segmentParams, ...slotParams}`)
|
|
3248
|
+
* — but the cache key hashes only the route-level params, and the wrapper
|
|
3249
|
+
* cannot know which slot it renders under. The route params are usable
|
|
3250
|
+
* only when every slot's merged view canonicalizes identically to them;
|
|
3251
|
+
* otherwise two renders with the same key could see different effective
|
|
3252
|
+
* inputs and a hit could serve the WRONG cached subtree — so the render
|
|
3253
|
+
* goes live: slower, never wrong. (Build-time capture doesn't model slot
|
|
3254
|
+
* params either, so a divergent request could never have a faithful
|
|
3255
|
+
* entry. Codex review on PR #835; revisit with segment-path-scoped keys
|
|
3256
|
+
* if slot-scoped prebuilt is ever needed.)
|
|
3257
|
+
*
|
|
3258
|
+
* Comparison happens through `tryCanonicalize` — the same single
|
|
3259
|
+
* definition of value equality the cache key uses — so anything it
|
|
3260
|
+
* cannot represent faithfully (bigint-coerced codec params, accessors,
|
|
3261
|
+
* aliases, …) is unkeyable here too, and this guard can never throw.
|
|
3262
|
+
*/
|
|
3263
|
+
function resolveKeyParams(store) {
|
|
3264
|
+
const main = store.segmentParams ?? {};
|
|
3265
|
+
const map = store.slotParamsMap;
|
|
3266
|
+
if (!map || map.size === 0) return main;
|
|
3267
|
+
const mainCanonical = tryCanonicalize({ ...main });
|
|
3268
|
+
if (mainCanonical === null) return null;
|
|
3269
|
+
for (const slotParams of map.values()) {
|
|
3270
|
+
const merged = tryCanonicalize({
|
|
3271
|
+
...main,
|
|
3272
|
+
...slotParams
|
|
3273
|
+
});
|
|
3274
|
+
if (merged === null || merged !== mainCanonical) return null;
|
|
3275
|
+
}
|
|
3276
|
+
return main;
|
|
3277
|
+
}
|
|
3278
|
+
/** Wrap payload bytes as a single-chunk ReadableStream for the decoder. */
|
|
3279
|
+
function bytesToStream(bytes) {
|
|
3280
|
+
return new ReadableStream({ start(controller) {
|
|
3281
|
+
controller.enqueue(bytes);
|
|
3282
|
+
controller.close();
|
|
3283
|
+
} });
|
|
3284
|
+
}
|
|
3285
|
+
/** Whether the component has a runtime lifetime (ttl or tags). */
|
|
3286
|
+
function hasRuntimeLifetime(options) {
|
|
3287
|
+
return options.ttl !== void 0 || options.tags !== void 0 && options.tags !== null;
|
|
3288
|
+
}
|
|
3289
|
+
/** Resolve tags from options (may be static array or function of props). */
|
|
3290
|
+
function resolveTags(options, props) {
|
|
3291
|
+
if (!options.tags) return [];
|
|
3292
|
+
if (Array.isArray(options.tags)) return options.tags;
|
|
3293
|
+
return options.tags(props);
|
|
3294
|
+
}
|
|
3295
|
+
var rscFns = null;
|
|
3296
|
+
function requireRscFunctions() {
|
|
3297
|
+
if (!rscFns) throw new Error("[timber] RSC functions not wired — setRscFunctions() must be called at boot");
|
|
3298
|
+
return rscFns;
|
|
3299
|
+
}
|
|
3300
|
+
/**
|
|
3301
|
+
* Revive flight payload bytes into a React element.
|
|
3302
|
+
* Returns null on any failure (corrupt payload, missing decoder).
|
|
3303
|
+
*/
|
|
3304
|
+
async function revivePayload(id, payload, outcome) {
|
|
3305
|
+
try {
|
|
3306
|
+
const { createFromReadableStream } = requireRscFunctions();
|
|
3307
|
+
return { element: await createFromReadableStream(bytesToStream(payload)) };
|
|
3308
|
+
} catch (error) {
|
|
3309
|
+
console.error(`[timber] failed to revive prebuilt payload for "${id}" — ${outcome}:`, error);
|
|
3310
|
+
return null;
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
/** Ceiling on component re-render capture (design/13 A2). Uses configured renderTimeoutMs. */
|
|
3314
|
+
function getCaptureTimeoutMs() {
|
|
3315
|
+
return componentTimeoutMs;
|
|
3316
|
+
}
|
|
3317
|
+
var keyDisciplineWarned = /* @__PURE__ */ new Set();
|
|
3318
|
+
var REQUEST_CONTEXT_FIELDS = {
|
|
3319
|
+
headers: "headers()",
|
|
3320
|
+
parsedCookies: "cookies()",
|
|
3321
|
+
searchParams: "searchParams"
|
|
3322
|
+
};
|
|
3323
|
+
/**
|
|
3324
|
+
* Wrap a store with a proxy that records per-request field access.
|
|
3325
|
+
* The proxy forwards all reads/writes transparently — it only observes.
|
|
3326
|
+
*/
|
|
3327
|
+
function createKeyDisciplineProxy(store) {
|
|
3328
|
+
const accessedApis = /* @__PURE__ */ new Set();
|
|
3329
|
+
return {
|
|
3330
|
+
proxy: new Proxy(store, { get(target, prop, receiver) {
|
|
3331
|
+
const api = REQUEST_CONTEXT_FIELDS[prop];
|
|
3332
|
+
if (api) accessedApis.add(api);
|
|
3333
|
+
return Reflect.get(target, prop, receiver);
|
|
3334
|
+
} }),
|
|
3335
|
+
accessedApis
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
function emitKeyDisciplineWarning(id, apis) {
|
|
3339
|
+
if (keyDisciplineWarned.has(id)) return;
|
|
3340
|
+
keyDisciplineWarned.add(id);
|
|
3341
|
+
const apiList = Array.from(apis).join(", ");
|
|
3342
|
+
console.warn(`[timber] cache.component key-discipline warning: "${id}" reads ${apiList} but the cache key is computed from props and segment params only. If the output varies by ${apiList}, different users may see each other's cached content. Either pass request-derived values as props (included in the cache key) or remove the ${apiList} call.`);
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* Render a component in isolation and capture its flight payload bytes.
|
|
3346
|
+
* Used for inline re-renders (tombstone/miss) and SWR background re-renders.
|
|
3347
|
+
*
|
|
3348
|
+
* The stream read is timeout-guarded and the reader is cancelled on
|
|
3349
|
+
* expiry (design/13 A2, A3).
|
|
3350
|
+
*/
|
|
3351
|
+
async function captureComponentPayload(id, component, props) {
|
|
3352
|
+
const { renderToReadableStream } = requireRscFunctions();
|
|
3353
|
+
const element = createElement(component, props);
|
|
3354
|
+
let renderErrored = false;
|
|
3355
|
+
const store = requestContextAls.getStore();
|
|
3356
|
+
const discipline = store && !keyDisciplineWarned.has(id) ? createKeyDisciplineProxy(store) : null;
|
|
3357
|
+
const doCapture = async () => {
|
|
3358
|
+
beginCapture(id);
|
|
3359
|
+
try {
|
|
3360
|
+
const reader = renderToReadableStream(element, { onError() {
|
|
3361
|
+
renderErrored = true;
|
|
3362
|
+
return "";
|
|
3363
|
+
} }).getReader();
|
|
3364
|
+
const chunks = [];
|
|
3365
|
+
let totalLength = 0;
|
|
3366
|
+
const deadline = Date.now() + getCaptureTimeoutMs();
|
|
3367
|
+
try {
|
|
3368
|
+
for (;;) {
|
|
3369
|
+
const remaining = deadline - Date.now();
|
|
3370
|
+
if (remaining <= 0) throw new Error(`[timber] component re-render capture exceeded ${getCaptureTimeoutMs()}ms`);
|
|
3371
|
+
let timer;
|
|
3372
|
+
const { done, value } = await Promise.race([reader.read(), new Promise((_, reject) => {
|
|
3373
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`[timber] component re-render stream read timed out`)), remaining);
|
|
3374
|
+
})]);
|
|
3375
|
+
clearTimeout(timer);
|
|
3376
|
+
if (done) break;
|
|
3377
|
+
chunks.push(value);
|
|
3378
|
+
totalLength += value.byteLength;
|
|
3379
|
+
}
|
|
3380
|
+
} finally {
|
|
3381
|
+
reader.cancel().catch(() => {});
|
|
3382
|
+
}
|
|
3383
|
+
if (renderErrored) throw new Error("[timber] component re-render produced error rows — not caching");
|
|
3384
|
+
const result = new Uint8Array(totalLength);
|
|
3385
|
+
let offset = 0;
|
|
3386
|
+
for (const chunk of chunks) {
|
|
3387
|
+
result.set(chunk, offset);
|
|
3388
|
+
offset += chunk.byteLength;
|
|
3389
|
+
}
|
|
3390
|
+
return result;
|
|
3391
|
+
} finally {
|
|
3392
|
+
endCapture();
|
|
3393
|
+
}
|
|
3394
|
+
};
|
|
3395
|
+
const bytes = discipline ? await requestContextAls.run(discipline.proxy, doCapture) : await doCapture();
|
|
3396
|
+
if (discipline && discipline.accessedApis.size > 0) emitKeyDisciplineWarning(id, discipline.accessedApis);
|
|
3397
|
+
return bytes;
|
|
3398
|
+
}
|
|
3399
|
+
/**
|
|
3400
|
+
* Inline re-render: capture flight bytes, store in overlay, revive for
|
|
3401
|
+
* the current request. Singleflight-coalesced by overlay key.
|
|
3402
|
+
*/
|
|
3403
|
+
async function inlineRerender(id, component, props, cacheKey, options, hasSeed, outcome) {
|
|
3404
|
+
const tags = resolveTags(options, props);
|
|
3405
|
+
const oKey = overlayKey(id, cacheKey);
|
|
3406
|
+
const sfKey = `prebuilt:${id}:${cacheKey}\0${lastInvalidationEpochFor(oKey, tags)}`;
|
|
3407
|
+
try {
|
|
3408
|
+
return await revivePayload(id, await componentSingleflight.do(sfKey, async (signal) => {
|
|
3409
|
+
const startEpoch = currentInvalidationEpoch();
|
|
3410
|
+
const bytes = await captureComponentPayload(id, component, props);
|
|
3411
|
+
if (!signal.aborted && hasRuntimeLifetime(options) && !wasInvalidatedSince(startEpoch, oKey, tags)) await storeOverlayEntry(id, cacheKey, bytes, {
|
|
3412
|
+
ttl: options.ttl,
|
|
3413
|
+
tags,
|
|
3414
|
+
hasSeed
|
|
3415
|
+
});
|
|
3416
|
+
return bytes;
|
|
3417
|
+
}), outcome);
|
|
3418
|
+
} catch (error) {
|
|
3419
|
+
console.error(`[timber] cache.component re-render failed for "${id}" — ${outcome}:`, error);
|
|
3420
|
+
return null;
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
/**
|
|
3424
|
+
* Snapshot the request context values needed for a background re-render.
|
|
3425
|
+
* Creates a FRESH store with copied values — the original ALS store is
|
|
3426
|
+
* not referenced after capture.
|
|
3427
|
+
*/
|
|
3428
|
+
function snapshotRequestContext(store) {
|
|
3429
|
+
return {
|
|
3430
|
+
headers: new Headers(store.headers),
|
|
3431
|
+
originalHeaders: new Headers(store.originalHeaders),
|
|
3432
|
+
cookieHeader: store.cookieHeader,
|
|
3433
|
+
parsedCookies: store.parsedCookies ? new Map(store.parsedCookies) : void 0,
|
|
3434
|
+
searchParams: new URLSearchParams(store.searchParams),
|
|
3435
|
+
searchString: store.searchString,
|
|
3436
|
+
segmentParams: store.segmentParams ? { ...store.segmentParams } : void 0,
|
|
3437
|
+
cookieJar: /* @__PURE__ */ new Map(),
|
|
3438
|
+
flushed: false,
|
|
3439
|
+
mutableContext: false
|
|
3440
|
+
};
|
|
3441
|
+
}
|
|
3442
|
+
/**
|
|
3443
|
+
* Kick off a background re-render via waitUntil. The re-render runs inside
|
|
3444
|
+
* a fresh ALS scope with snapshotted request context.
|
|
3445
|
+
*/
|
|
3446
|
+
function backgroundRerender(id, component, props, cacheKey, options, hasSeed, snapshot) {
|
|
3447
|
+
const waitUntil = getWaitUntil();
|
|
3448
|
+
const tags = resolveTags(options, props);
|
|
3449
|
+
const oKey = overlayKey(id, cacheKey);
|
|
3450
|
+
const startEpoch = currentInvalidationEpoch();
|
|
3451
|
+
const sfKey = `prebuilt-bg:${id}:${cacheKey}\0${lastInvalidationEpochFor(oKey, tags)}`;
|
|
3452
|
+
const work = requestContextAls.run(snapshot, () => componentSingleflight.do(sfKey, async (signal) => {
|
|
3453
|
+
const bytes = await captureComponentPayload(id, component, props);
|
|
3454
|
+
if (!signal.aborted && !wasInvalidatedSince(startEpoch, oKey, tags)) await storeOverlayEntry(id, cacheKey, bytes, {
|
|
3455
|
+
ttl: options.ttl,
|
|
3456
|
+
tags,
|
|
3457
|
+
hasSeed
|
|
3458
|
+
});
|
|
3459
|
+
}).catch((error) => {
|
|
3460
|
+
console.error(`[timber] cache.component background re-render failed for "${id}":`, error);
|
|
3461
|
+
}));
|
|
3462
|
+
if (waitUntil) waitUntil(work);
|
|
3463
|
+
else if (!warnedNoWaitUntil) {
|
|
3464
|
+
warnedNoWaitUntil = true;
|
|
3465
|
+
console.warn("[timber] cache.component SWR background re-render has no waitUntil — the re-render may be killed when the handler returns on serverless platforms. Ensure the adapter wires runWithWaitUntil().");
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
/**
|
|
3469
|
+
* Try to serve this render from the prebuilt cache: overlay → artifact →
|
|
3470
|
+
* live render. Handles ISR (SWR, tombstones, re-renders) per design/45.
|
|
3471
|
+
*
|
|
3472
|
+
* Null on any miss — the caller renders live.
|
|
3473
|
+
*/
|
|
3474
|
+
async function tryRevivePayload(id, options, props) {
|
|
3475
|
+
const hasSource = hasPrebuiltPayloadSource();
|
|
3476
|
+
const hasRuntime = hasRuntimeLifetime(options);
|
|
3477
|
+
if (!hasSource && !hasRuntime) return null;
|
|
3478
|
+
const store = requestContextAls.getStore();
|
|
3479
|
+
if (!store) return null;
|
|
3480
|
+
const keyParams = resolveKeyParams(store);
|
|
3481
|
+
if (keyParams === null) return null;
|
|
3482
|
+
const cacheKey = computeComponentCacheKey(props ?? {}, keyParams);
|
|
3483
|
+
if (cacheKey === null) return null;
|
|
3484
|
+
const outcome = options.fallbackOnMissingSegmentParams === false ? "failing the render (fallbackOnMissingSegmentParams: false)" : "rendering dynamically";
|
|
3485
|
+
const component = registry.get(id)?.component;
|
|
3486
|
+
if (hasRuntime) {
|
|
3487
|
+
const overlayResult = await lookupOverlay(id, cacheKey);
|
|
3488
|
+
if (overlayResult) {
|
|
3489
|
+
const overlayHasSeed = overlayResult.record?.s ?? false;
|
|
3490
|
+
if (overlayResult.kind === "tombstone") {
|
|
3491
|
+
if (component) return await inlineRerender(id, component, props, cacheKey, options, true, outcome);
|
|
3492
|
+
return null;
|
|
3493
|
+
}
|
|
3494
|
+
if (overlayResult.kind === "fresh" && overlayResult.payload) return await revivePayload(id, overlayResult.payload, outcome);
|
|
3495
|
+
if (overlayResult.kind === "stale" && overlayResult.payload) {
|
|
3496
|
+
if (options.staleWhileRevalidate && component) {
|
|
3497
|
+
backgroundRerender(id, component, props, cacheKey, options, overlayHasSeed, snapshotRequestContext(store));
|
|
3498
|
+
return await revivePayload(id, overlayResult.payload, outcome);
|
|
3499
|
+
}
|
|
3500
|
+
if (component) return await inlineRerender(id, component, props, cacheKey, options, overlayHasSeed, outcome);
|
|
3501
|
+
return null;
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
if (hasSource) {
|
|
3506
|
+
const artifactPayload = await lookupPrebuiltPayload(id, cacheKey);
|
|
3507
|
+
if (artifactPayload !== null) {
|
|
3508
|
+
if (options.ttl !== void 0) {
|
|
3509
|
+
const buildTs = (await getManifestEntry(id, cacheKey))?.buildTimestamp ?? 0;
|
|
3510
|
+
if ((Date.now() - buildTs) / 1e3 > options.ttl) {
|
|
3511
|
+
if (options.staleWhileRevalidate && component) {
|
|
3512
|
+
backgroundRerender(id, component, props, cacheKey, options, true, snapshotRequestContext(store));
|
|
3513
|
+
return await revivePayload(id, artifactPayload, outcome);
|
|
3514
|
+
}
|
|
3515
|
+
if (component) return await inlineRerender(id, component, props, cacheKey, options, true, outcome);
|
|
3516
|
+
return null;
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
return await revivePayload(id, artifactPayload, outcome);
|
|
3520
|
+
}
|
|
3521
|
+
if (options.prerender) console.warn(`[timber] cache.component cache miss: "${id}" with params ${JSON.stringify(store.segmentParams ?? {})} — ${outcome}`);
|
|
3522
|
+
}
|
|
3523
|
+
if (hasRuntime && component && options.fallbackOnMissingSegmentParams !== false) return await inlineRerender(id, component, props, cacheKey, options, false, outcome);
|
|
3524
|
+
return null;
|
|
3525
|
+
}
|
|
3526
|
+
/**
|
|
3527
|
+
* Production wrapper injected by the timber-prebuilt transform. Registers
|
|
3528
|
+
* the component under its stable ID and returns a wrapper component that
|
|
3529
|
+
* either revives the cached payload (hit) or renders the original
|
|
3530
|
+
* component live (miss).
|
|
3531
|
+
*/
|
|
3532
|
+
function __prebuilt(id, component, options = {}) {
|
|
3533
|
+
registry.set(id, {
|
|
3534
|
+
id,
|
|
3535
|
+
component,
|
|
3536
|
+
options
|
|
3537
|
+
});
|
|
3538
|
+
const wrapper = async (props) => {
|
|
3539
|
+
noteWrapperRender(id, options);
|
|
3540
|
+
if (isCaptureActive()) return createElement(component, props);
|
|
3541
|
+
const revived = await tryRevivePayload(id, options, props);
|
|
3542
|
+
if (revived !== null) return revived.element;
|
|
3543
|
+
if (options.fallbackOnMissingSegmentParams === false) throw new Error(`[timber] cache.component "${id}": no prebuilt payload for the current params and fallbackOnMissingSegmentParams is false`);
|
|
3544
|
+
return createElement(component, props);
|
|
3545
|
+
};
|
|
3546
|
+
Object.defineProperty(wrapper, "name", { value: `Prebuilt(${id})` });
|
|
3547
|
+
return wrapper;
|
|
3548
|
+
}
|
|
3549
|
+
/** Look up a registration by stable component ID. */
|
|
3550
|
+
function getPrebuiltRegistration(id) {
|
|
3551
|
+
return registry.get(id);
|
|
3552
|
+
}
|
|
3553
|
+
/** All registrations, for the post-build capture pass (TIM-1119). */
|
|
3554
|
+
function getPrebuiltRegistry() {
|
|
3555
|
+
return registry;
|
|
3556
|
+
}
|
|
3557
|
+
/**
|
|
3558
|
+
* Dev-mode wrapper injected by the timber-prebuilt transform: a no-op that
|
|
3559
|
+
* renders the component dynamically on every request, logging once per
|
|
3560
|
+
* component on first render so the production behavior difference is visible.
|
|
3561
|
+
* See design/44 §Dev Mode.
|
|
3562
|
+
*/
|
|
3563
|
+
function __prebuiltDev(name, component, _options) {
|
|
3564
|
+
let logged = false;
|
|
3565
|
+
const wrapper = (props) => {
|
|
3566
|
+
if (!logged) {
|
|
3567
|
+
logged = true;
|
|
3568
|
+
console.log(`[timber] cache.component: "${name}" — rendering dynamically (dev mode)`);
|
|
3569
|
+
}
|
|
3570
|
+
return createElement(component, props);
|
|
3571
|
+
};
|
|
3572
|
+
return wrapper;
|
|
3573
|
+
}
|
|
3574
|
+
//#endregion
|
|
3575
|
+
export { AccessGate, DEFAULT_LIMITS, DenySignal, METADATA_ROUTE_CONVENTIONS, RedirectSignal, RenderError, RenderTimeoutError, SlotAccessGate, __prebuilt, __prebuiltDev, buildElementTree, callOnRequestError, canonicalize, classifyMetadataRoute, coerce, collectEarlyHintHeaders, computeComponentCacheKey, createPipeline, enforceBodyLimits, executeAction, formatLinkHeader, generateTraceId, getCookie, getHeader, getLogger, getMetadataRouteAutoLink, getMetadataRouteServePath, getPrebuiltRegistration, getPrebuiltRegistry, getSearchParams, getSegmentParams, getSetCookieHeaders, handleRouteRequest, hasOnRequestError, hasPrebuiltPayloadSource, loadInstrumentation, logCacheMiss, logMiddlewareError, logMiddlewareShortCircuit, logProxyError, logRenderError, logRequestCompleted, logRequestReceived, logSlowRequest, logSwrRefetchFailed, logWaitUntilRejected, logWaitUntilUnsupported, markResponseFlushed, parseBodySize, renderMetadataToElements, replaceTraceId, resolveAllowedMethods, resolveMetadata, resolveMetadataUrls, resolveSlotDenied, resolveStatusFile, resolveTitle, runMiddleware, runProxy, runWithEarlyHintsSender, runWithRequestContext, runWithTraceId, sendEarlyHints103, setLogger, setMutableCookieContext, setPrebuiltPayloadSource, setSegmentParams, validateCsrf };
|
|
3087
3576
|
|
|
3088
3577
|
//# sourceMappingURL=internal.js.map
|