@rangojs/router 0.5.0 → 0.5.2

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.
Files changed (65) hide show
  1. package/README.md +5 -1
  2. package/dist/types/browser/event-controller.d.ts +6 -0
  3. package/dist/types/browser/prefetch/cache.d.ts +31 -2
  4. package/dist/types/cache/cf/cf-cache-constants.d.ts +8 -1
  5. package/dist/types/cache/cf/cf-cache-store.d.ts +15 -1
  6. package/dist/types/cache/cf/cf-cache-types.d.ts +1 -1
  7. package/dist/types/cache/cf/cf-kv-utils.d.ts +21 -0
  8. package/dist/types/handles/is-thenable.d.ts +2 -4
  9. package/dist/types/route-definition/helpers-types.d.ts +5 -4
  10. package/dist/types/rsc/helpers.d.ts +3 -0
  11. package/dist/types/rsc/render-pipeline.d.ts +9 -0
  12. package/dist/types/rsc/routine-plan.d.ts +124 -0
  13. package/dist/types/rsc/shell-capture-constants.d.ts +17 -0
  14. package/dist/types/server/request-context.d.ts +4 -1
  15. package/dist/types/static-handler.d.ts +15 -1
  16. package/dist/types/urls/path-helper-types.d.ts +6 -7
  17. package/dist/types/vite/encryption-key.d.ts +2 -0
  18. package/dist/types/vite/plugins/expose-internal-ids.d.ts +10 -0
  19. package/dist/types/vite/plugins/server-ref-hashing.d.ts +24 -0
  20. package/dist/types/vite/plugins/server-reference-pattern.d.ts +1 -0
  21. package/dist/types/vite/utils/shared-utils.d.ts +12 -0
  22. package/dist/vite/index.js +154 -57
  23. package/package.json +3 -3
  24. package/skills/caching/SKILL.md +1 -1
  25. package/skills/mime-routes/SKILL.md +3 -1
  26. package/skills/parallel/SKILL.md +1 -1
  27. package/skills/response-routes/SKILL.md +4 -2
  28. package/skills/typesafety/generated-files-and-cli.md +16 -9
  29. package/skills/typesafety/route-types.md +5 -1
  30. package/skills/use-cache/SKILL.md +47 -0
  31. package/src/browser/event-controller.ts +40 -15
  32. package/src/browser/partial-update.ts +26 -11
  33. package/src/browser/prefetch/cache.ts +53 -9
  34. package/src/browser/prefetch/fetch.ts +83 -2
  35. package/src/browser/react/NavigationProvider.tsx +7 -0
  36. package/src/cache/cache-runtime.ts +89 -15
  37. package/src/cache/cf/cf-cache-constants.ts +8 -1
  38. package/src/cache/cf/cf-cache-store.ts +41 -64
  39. package/src/cache/cf/cf-cache-types.ts +1 -1
  40. package/src/cache/cf/cf-kv-utils.ts +38 -0
  41. package/src/cache/segment-codec.ts +21 -5
  42. package/src/handles/is-thenable.ts +2 -4
  43. package/src/route-definition/helpers-types.ts +5 -3
  44. package/src/router/match-middleware/cache-lookup.ts +24 -15
  45. package/src/rsc/handler.ts +26 -5
  46. package/src/rsc/helpers.ts +13 -0
  47. package/src/rsc/progressive-enhancement.ts +247 -70
  48. package/src/rsc/render-pipeline.ts +68 -24
  49. package/src/rsc/routine-plan.ts +359 -0
  50. package/src/rsc/rsc-rendering.ts +555 -302
  51. package/src/rsc/server-action.ts +180 -71
  52. package/src/rsc/shell-capture-constants.ts +18 -0
  53. package/src/rsc/shell-capture.ts +92 -21
  54. package/src/server/request-context.ts +6 -0
  55. package/src/ssr/ssr-root.tsx +1 -0
  56. package/src/static-handler.ts +18 -2
  57. package/src/urls/path-helper-types.ts +9 -10
  58. package/src/vite/encryption-key.ts +29 -0
  59. package/src/vite/plugins/expose-action-id.ts +2 -2
  60. package/src/vite/plugins/expose-internal-ids.ts +46 -0
  61. package/src/vite/plugins/server-ref-hashing.ts +74 -0
  62. package/src/vite/plugins/server-reference-pattern.ts +10 -0
  63. package/src/vite/rango.ts +9 -0
  64. package/src/vite/router-discovery.ts +21 -2
  65. package/src/vite/utils/shared-utils.ts +12 -7
package/README.md CHANGED
@@ -439,10 +439,14 @@ For response-aware and path-based utilities (`href()`, `Rango.Path`,
439
439
  // router.tsx
440
440
  const router = createRouter<AppBindings>({}).routes(urlpatterns);
441
441
 
442
+ // The alias is required: an interface heritage clause cannot take a `typeof`
443
+ // type query directly (TS1109), so extend through a named alias.
444
+ type AppRoutes = typeof router.routeMap;
445
+
442
446
  declare global {
443
447
  namespace Rango {
444
448
  interface Env extends AppEnv {}
445
- interface RegisteredRoutes extends typeof router.routeMap {}
449
+ interface RegisteredRoutes extends AppRoutes {}
446
450
  }
447
451
  }
448
452
  ```
@@ -180,6 +180,12 @@ export interface EventController {
180
180
  subscribe(listener: StateListener): () => void;
181
181
  subscribeToAction(actionId: string, listener: ActionStateListener): () => void;
182
182
  subscribeToHandles(listener: HandleListener): () => void;
183
+ /**
184
+ * Deliver queued location, params, navigation, and handle notifications now.
185
+ * NavigationProvider calls this inside the payload update so React assigns
186
+ * every route-state hook update to the same normal or transition lane.
187
+ */
188
+ flushRouteState(): void;
183
189
  setHandleData(data: HandleData, matched?: string[], isPartial?: boolean,
184
190
  /**
185
191
  * Segment ids that were re-resolved on the server this request (the
@@ -67,8 +67,31 @@ export interface DecodedPrefetch {
67
67
  * tell a FULLY warmed prefetch (payload commits without suspending → safe to
68
68
  * commit in a startTransition, no fallback flash) from a partially-warmed one
69
69
  * (still streaming → stream its fallbacks like a cold load). Starts false.
70
+ *
71
+ * On a respawned entry (see `respawn`) this starts true: the buffered bytes
72
+ * are a complete stream by construction.
70
73
  */
71
74
  complete: boolean;
75
+ /**
76
+ * Re-create this entry from its buffered raw Flight bytes. Attached (by
77
+ * `executePrefetchFetch`) only after the stream ended cleanly AND the eager
78
+ * decode succeeded — a truncated or failed prefetch must stay one-shot.
79
+ *
80
+ * Why bytes and not the decoded payload: the revived payload is a live graph
81
+ * with one-shot stream edges (the handle stream is an async generator drained
82
+ * during commit; userland payloads may embed serialized ReadableStreams).
83
+ * Re-decoding from the wire bytes manufactures fresh instances of every
84
+ * stream, which no amount of cloning the revived graph can do. Each call tees
85
+ * the reserve branch, so the replacement entry carries its own `respawn` —
86
+ * one prefetch serves unlimited adoptions within TTL/state validity.
87
+ */
88
+ respawn?: () => DecodedPrefetch;
89
+ /**
90
+ * Release the buffered reserve bytes. Called when the entry is evicted
91
+ * without being consumed (expiry, FIFO, sweep, cache clear) so the tee
92
+ * buffer is dropped promptly instead of waiting for GC.
93
+ */
94
+ dispose?: () => void;
72
95
  }
73
96
  /**
74
97
  * Initialize the prefetch cache with the configured TTL and max size.
@@ -123,8 +146,14 @@ export declare function buildSourceKey(rangoState: string, sourceHref: string, t
123
146
  export declare function hasPrefetch(key: string): boolean;
124
147
  /**
125
148
  * Consume a cached, eagerly-decoded prefetch. Returns null if not found or
126
- * expired. One-time consumption: the entry is deleted after retrieval.
127
- * Returns null when caching is disabled (TTL <= 0).
149
+ * expired. Returns null when caching is disabled (TTL <= 0).
150
+ *
151
+ * A decoded payload is single-render (its handle stream is drained during the
152
+ * commit), so the returned entry is never served twice. When the entry carries
153
+ * `respawn` (clean-EOF prefetch with buffered bytes), the slot is re-armed in
154
+ * place with a fresh decode of those bytes — the ORIGINAL timestamp is kept so
155
+ * TTL bounds the age of the data, not of the latest adoption. Without
156
+ * `respawn` the entry is deleted (legacy one-shot behavior).
128
157
  *
129
158
  * Does NOT check in-flight prefetches — use consumeInflightPrefetch()
130
159
  * for that (returns a Promise instead of a resolved entry).
@@ -65,8 +65,15 @@ export declare const MAX_REVALIDATION_INTERVAL = 30;
65
65
  *
66
66
  * This is the default; override per store via
67
67
  * `CFCacheStoreOptions.edgeLookupTimeoutMs` (<= 0 disables the budget).
68
+ *
69
+ * 25ms, raised from 10: production Workers logs (autobarn pilot) showed the
70
+ * 10ms budget firing frequently on cold colos where the first Cache API touch
71
+ * is slow but healthy — each false positive downgrades a warm L1 HIT to an
72
+ * L2/render round trip that costs far more than the 15ms of extra patience.
73
+ * A genuinely degraded colo still gets cut off; tune per store for
74
+ * shell-critical routes.
68
75
  */
69
- export declare const EDGE_LOOKUP_TIMEOUT_MS = 10;
76
+ export declare const EDGE_LOOKUP_TIMEOUT_MS = 25;
70
77
  /**
71
78
  * Maximum time (ms) to wait for the BODY of a matched L1 entry to be read
72
79
  * (response.json()) before treating the read as a miss.
@@ -302,6 +302,19 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
302
302
  /**
303
303
  * Convert string key to KV key string.
304
304
  * Uses same version prefix as Cache API for consistent invalidation.
305
+ *
306
+ * Single chokepoint for EVERY KV family (segments, items, shells, documents
307
+ * via toDocKVKey, tag markers via tagMarkerKey): a composed key over
308
+ * Cloudflare KV's 512-byte limit is normalized to a preserved readable
309
+ * prefix plus a SHA-256-derived 128-bit digest of the FULL key. Without
310
+ * this, kv.put/get reject with `414 ... exceeds key length limit of 512`
311
+ * and the entry silently never reaches L2 (observed in production for
312
+ * "use cache" items whose serialized args — e.g. a CMS query object — blow
313
+ * the cap; autobarn pilot). Keys are opaque storage identifiers, so
314
+ * normalization is semantics-preserving as long as distinct logical keys
315
+ * stay distinct: colliding requires an identical 400-byte prefix AND a
316
+ * 128-bit SHA-256 collision. Deterministic, so every family's read, write,
317
+ * and delete paths agree on the stored key.
305
318
  * @internal
306
319
  */
307
320
  private toKVKey;
@@ -546,7 +559,8 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
546
559
  *
547
560
  * Durable-write integrity: the in-memory write-through (memo + L1) for a tag
548
561
  * runs ONLY after that tag's KV marker write is confirmed. If any KV write
549
- * fails (transient error, or an over-512-byte key), this rejects with the
562
+ * fails (transient error; over-limit keys are normalized by toKVKey rather
563
+ * than rejected), this rejects with the
550
564
  * failed tags so an awaiting updateTag() surfaces the failure instead of
551
565
  * silently reporting success while other requests/colos serve stale data. The
552
566
  * eager purge still fires for the whole batch first (it is additive).
@@ -288,7 +288,7 @@ export interface CFCacheStoreOptions<TEnv = unknown> {
288
288
  * cannot stall the request; the read then falls through to its normal miss
289
289
  * path (L2/KV or render).
290
290
  *
291
- * Defaults to {@link EDGE_LOOKUP_TIMEOUT_MS} (10). Set to 0 (or any value
291
+ * Defaults to {@link EDGE_LOOKUP_TIMEOUT_MS} (25). Set to 0 (or any value
292
292
  * <= 0) to disable the budget and always await `match`.
293
293
  */
294
294
  edgeLookupTimeoutMs?: number;
@@ -9,6 +9,27 @@ export declare const KV_MAX_KEY_BYTES = 512;
9
9
  export declare const KV_MIN_EXPIRATION_TTL = 60;
10
10
  /** UTF-8 byte length of a KV key (multibyte tags can exceed the char count). */
11
11
  export declare function kvKeyByteLength(key: string): number;
12
+ /**
13
+ * Bytes of a composed key preserved verbatim when an over-limit key is
14
+ * normalized (toKVKey). 400 leaves room for the `~` separator and the 32-hex
15
+ * digest inside KV_MAX_KEY_BYTES while keeping the version prefix and most of
16
+ * the logical key readable (and prefix-listable) in the KV dashboard.
17
+ */
18
+ export declare const KV_KEY_PRESERVED_PREFIX_BYTES = 400;
19
+ /**
20
+ * Truncate to at most `maxBytes` of UTF-8. A multibyte sequence split at the
21
+ * boundary decodes to U+FFFD, which is stripped — the result is deterministic
22
+ * for a given input, which is all key normalization needs.
23
+ */
24
+ export declare function truncateToBytes(value: string, maxBytes: number): string;
25
+ /**
26
+ * 128-bit hex digest (SHA-256 truncated) of a composed KV key. WebCrypto is
27
+ * available on workerd and Node alike; SHA-256 (not a fast non-crypto hash)
28
+ * because cache keys embed user-influenced serialized args — an engineered
29
+ * collision between two normalized keys would serve one entry's payload for
30
+ * the other's.
31
+ */
32
+ export declare function kvKeyDigest(value: string): Promise<string>;
12
33
  /**
13
34
  * Compute the Cache-Control directive for a stale-path REVALIDATING re-put from
14
35
  * the entry's stored hard-expiry deadline (CACHE_EXPIRES_AT_HEADER). Returns the
@@ -1,8 +1,6 @@
1
1
  /**
2
- * Single thenable predicate for the resolve-by-default handle machinery
3
- * (`handles/deferred-resolution.ts`, its sole owner) it decides which top-level
4
- * handle entries are deferred (`Promise`) values to await before any consumer
5
- * sees them, and which pass through verbatim.
2
+ * Shared thenable predicate for framework orchestration. It decides which values
3
+ * participate in promise lifecycles and which pass through synchronously.
6
4
  *
7
5
  * Requires a CALLABLE `then` (`typeof obj.then === "function"`), not merely a
8
6
  * `"then" in obj` membership check, so a non-promise carrying a `then` field
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { ExtractRouteParams, Handler, PartialCacheOptions, ErrorBoundaryHandler, LoaderDefinition, MiddlewareFn, NotFoundBoundaryHandler, ResolvedRouteMap, RouteDefinition, ShouldRevalidateFn, TransitionConfig } from "../types.js";
3
3
  import type { AllUseItems, LayoutItem, RouteItem, ParallelItem, InterceptItem, MiddlewareItem, RevalidateItem, LoaderItem, LoadingItem, ErrorBoundaryItem, NotFoundBoundaryItem, LayoutUseItem, RouteUseItem, ParallelUseItem, InterceptUseItem, LoaderUseItem, CacheItem, TransitionItem, UseItems } from "../route-types.js";
4
+ import type { StaticHandlerRef } from "../static-handler.js";
4
5
  export type { AllUseItems, LayoutItem, RouteItem, ParallelItem, InterceptItem, MiddlewareItem, RevalidateItem, LoaderItem, ErrorBoundaryItem, NotFoundBoundaryItem, LayoutUseItem, RouteUseItem, ParallelUseItem, InterceptUseItem, CacheItem, } from "../route-types.js";
5
6
  export type { InterceptConfig, InterceptSelectorContext, InterceptSegmentsState, InterceptWhenFn, } from "../server/context";
6
7
  /**
@@ -75,8 +76,8 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
75
76
  * },
76
77
  * })
77
78
  * ```
78
- * @param slots - Object with slot names (prefixed with @) mapped to handlers
79
- * or `{ handler, use? }` slot descriptors.
79
+ * @param slots - Object with slot names (prefixed with @) mapped to handlers,
80
+ * Static() definitions, or `{ handler, use? }` descriptors.
80
81
  * @param use - Optional callback for loaders, loading, revalidate, etc.
81
82
  * Items here apply to every slot in the call (broadcast).
82
83
  * For per-slot single-assignment items, use the slot descriptor's
@@ -84,8 +85,8 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
84
85
  * so they take precedence on `loading()` and other last-write-wins
85
86
  * fields.
86
87
  */
87
- parallel: (slots: Record<`@${string}`, Handler<any, any, TEnv> | ReactNode | {
88
- handler: Handler<any, any, TEnv> | ReactNode;
88
+ parallel: (slots: Record<`@${string}`, Handler<any, any, TEnv> | ReactNode | StaticHandlerRef | {
89
+ handler: Handler<any, any, TEnv> | ReactNode | StaticHandlerRef;
89
90
  use?: () => UseItems<ParallelUseItem>;
90
91
  }>, use?: () => UseItems<ParallelUseItem>) => ParallelItem;
91
92
  /**
@@ -6,6 +6,7 @@
6
6
  import type { RequestContext } from "../server/request-context.js";
7
7
  import type { MiddlewareEntry, MiddlewareFn } from "../router/middleware.js";
8
8
  import type { RscPayload } from "./types.js";
9
+ import type { HandlerContext } from "./handler-context.js";
9
10
  /**
10
11
  * Drain ctx._onResponseCallbacks onto a response. Swapping the array before
11
12
  * iteration prevents re-entrant registrations from double-firing and matches
@@ -122,3 +123,5 @@ export declare function mergeStubHeadersAndFinalize(response: Response): Respons
122
123
  * but still need ctx.onResponse() callbacks to fire.
123
124
  */
124
125
  export declare function finalizeResponse(response: Response): Response;
126
+ /** Match and stamp params/routeName on the request context as one operation. */
127
+ export declare function matchAndRecordParams<TEnv>(ctx: HandlerContext<TEnv>, request: Request, env: TEnv): Promise<Awaited<ReturnType<HandlerContext<TEnv>["router"]["match"]>>>;
@@ -2,6 +2,7 @@ import type { TraceSpan } from "../router/tracing.js";
2
2
  import type { RenderMode, RenderPhase } from "../router/timeout.js";
3
3
  import type { HandlerContext } from "./handler-context.js";
4
4
  import type { RscPayload } from "./types.js";
5
+ import type { RoutineTrace } from "./routine-plan.js";
5
6
  export type RscRenderMode = RenderMode;
6
7
  export type RscRenderPhase = RenderPhase;
7
8
  export interface RscRenderStageProgress {
@@ -135,5 +136,13 @@ export declare function renderRscResponse<TEnv>(input: RscRenderInput<TEnv>, opt
135
136
  * Standard responses use renderRscResponse so the driver owns every phase.
136
137
  */
137
138
  export declare function renderRscFlightStage<TEnv>(input: RscFlightStageInput<TEnv>, init?: ResponseInit): RscFlightStage;
139
+ /**
140
+ * Feed this driver's stage events (flight/html/response) into a routine flow
141
+ * trace as child entries of the currently running plan step. Stages are
142
+ * strictly sequential, so one `current` slot suffices. Depth is captured at
143
+ * creation: the bridge is built while the caller's render step executes, so
144
+ * children sit one level under it.
145
+ */
146
+ export declare function createRenderStageTraceBridge(trace: RoutineTrace): (event: RscRenderStageEvent) => void;
138
147
  export declare function createRscStageDebugSink(log?: (message: string, details?: Record<string, unknown>) => void): (event: RscRenderStageEvent) => void;
139
148
  export {};
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Routine plans — the request-level extension of the render stage driver
3
+ * (docs/design/routine-plans.md; protocol lineage in render-stage-driver.md).
4
+ * A plan is a synchronous generator that EMITS instructions before their work
5
+ * runs; the runner is the only code that executes them. Two effect shapes:
6
+ *
7
+ * step(name, fn) — sequential work; the plan suspends until its result.
8
+ * handoff(name, fn) — background registration (waitUntil-shaped); completes
9
+ * at scheduling and returns nothing.
10
+ *
11
+ * Plans compose by level: `yield* scope(name, plan)` flattens a nested plan
12
+ * into the same instruction stream while the trace records the scope. Tracking
13
+ * is derived, not declared — "what is running" is the instruction in hand.
14
+ *
15
+ * Deliberate boundaries (see the design doc's "Not built yet, on purpose"):
16
+ * the runner opens no observability spans — effects own their own
17
+ * instrumentation, and the render driver keeps PHASES.ssr — and there is no
18
+ * plan.return abort/cleanup path; the render driver's cleanup rules are the
19
+ * template if a caller ever needs one.
20
+ */
21
+ export type RoutineEffectKind = "step" | "handoff";
22
+ export type RoutineCommand = {
23
+ kind: "step";
24
+ name: string;
25
+ execute: () => unknown;
26
+ } | {
27
+ kind: "handoff";
28
+ name: string;
29
+ execute: () => unknown;
30
+ } | {
31
+ kind: "enter";
32
+ name: string;
33
+ } | {
34
+ kind: "exit";
35
+ outcome: "done" | "failed";
36
+ error?: unknown;
37
+ };
38
+ export type RoutineCommandResult = {
39
+ kind: "step";
40
+ value: unknown;
41
+ } | {
42
+ kind: "handoff";
43
+ } | {
44
+ kind: "enter";
45
+ } | {
46
+ kind: "exit";
47
+ };
48
+ /** One imperative step: yields a command, returns its typed result. */
49
+ export type RoutineStep<T> = Generator<RoutineCommand, T, RoutineCommandResult>;
50
+ /** A full plan is shaped like a step; the alias marks intent at call sites. */
51
+ export type RoutinePlan<TReturn> = RoutineStep<TReturn>;
52
+ /**
53
+ * Sequential work. The `as Awaited<T>` narrow is the trust boundary with the
54
+ * runner, which resumes with the exact value `execute` produced.
55
+ */
56
+ export declare function step<T>(name: string, execute: () => T | Promise<T>): RoutineStep<Awaited<T>>;
57
+ /**
58
+ * Background handoff: successful registration completes the foreground step.
59
+ * A synchronous registration failure is thrown back into the plan, matching a
60
+ * direct waitUntil/scheduler call. A returned promise settles in the background;
61
+ * its rejection marks the trace but never fails the foreground response.
62
+ *
63
+ * Settlement tracking follows the RETURNED value: a returned promise is
64
+ * observed until it settles; a scheduler that fires-and-forgets internally
65
+ * (e.g. scheduleShellCapture via runBackground) returns void, so its trace
66
+ * entry settles at the scheduling call, not at background completion. That is
67
+ * deliberate — the plan's contract ends at the handoff.
68
+ */
69
+ export declare function handoff(name: string, execute: () => unknown): RoutineStep<void>;
70
+ /**
71
+ * Run a nested plan as a named scope. `yield*` flattens its instructions into
72
+ * the parent stream; enter/exit only inform the trace. The scope reports its
73
+ * terminal outcome after internal recovery has either succeeded or escaped.
74
+ */
75
+ export declare function scope<TReturn>(name: string, plan: RoutinePlan<TReturn>): RoutineStep<TReturn>;
76
+ export interface RoutineTraceEntry {
77
+ name: string;
78
+ kind: RoutineEffectKind | "scope";
79
+ depth: number;
80
+ /** step/scope: running -> done|failed. handoff: pending -> settled|failed. */
81
+ state: "running" | "pending" | "done" | "settled" | "failed";
82
+ startedAt: number;
83
+ endedAt?: number;
84
+ settledAt?: number;
85
+ error?: unknown;
86
+ }
87
+ export interface RoutineTrace {
88
+ readonly name: string;
89
+ readonly entries: readonly RoutineTraceEntry[];
90
+ enterScope(name: string): void;
91
+ exitScope(outcome: "done" | "failed", error?: unknown): void;
92
+ /** Scope depth entries currently record at. Bridges (e.g. render stage
93
+ * events feeding child entries under a running step) capture this once and
94
+ * pass `depth + 1` explicitly. */
95
+ currentDepth(): number;
96
+ begin(name: string, kind: RoutineEffectKind, depth?: number): RoutineTraceEntry;
97
+ end(entry: RoutineTraceEntry): void;
98
+ fail(entry: RoutineTraceEntry, error: unknown): void;
99
+ settle(entry: RoutineTraceEntry, outcome: {
100
+ status: "fulfilled";
101
+ } | {
102
+ status: "rejected";
103
+ error: unknown;
104
+ }): void;
105
+ /** Running scopes plus the foreground instruction currently in hand. */
106
+ active(): RoutineTraceEntry[];
107
+ format(): string;
108
+ formatActive(entries?: readonly RoutineTraceEntry[], activeAt?: number): string;
109
+ }
110
+ export declare function createRoutineTrace(name: string, now?: () => number): RoutineTrace;
111
+ export interface RoutineTraceOwner {
112
+ _activeRoutine?: RoutineTrace;
113
+ }
114
+ export interface RoutineRunnerOptions {
115
+ trace?: RoutineTrace;
116
+ owner?: RoutineTraceOwner;
117
+ }
118
+ /**
119
+ * Interpret a plan. Invariants shared with driveRscRenderPlan: a command is
120
+ * observable before its work runs, `execute` runs exactly once, results and
121
+ * errors resume the plan with exact identity, and a plan-level try/catch
122
+ * around a step is the recovery mechanism.
123
+ */
124
+ export declare function runRoutine<TReturn>(plan: RoutinePlan<TReturn>, options?: RoutineRunnerOptions): Promise<TReturn>;
@@ -25,3 +25,20 @@
25
25
  * docs/design/ppr-shell-resume.md (Cost model).
26
26
  */
27
27
  export declare const SHELL_CAPTURE_MAX_WAIT_MS = 15000;
28
+ /**
29
+ * Hard cap on one capture TASK — runShellCapture end to end (both attempts +
30
+ * the in-place retry delay). SHELL_CAPTURE_MAX_WAIT_MS arms only inside
31
+ * captureShellHTML, AFTER the capture's router.match(); a handler wedged on a
32
+ * never-settling upstream await (autobarn pilot: a 30s+ tarpitting fetch)
33
+ * wedges the task with no deadline in force. The task's settle path releases
34
+ * the per-key stampede guard and the serialized capture-queue slot, so an
35
+ * unbounded task strands BOTH for the isolate's lifetime.
36
+ *
37
+ * 25s: above one full-budget attempt plus overhead (cutting a second attempt
38
+ * short is an acceptable loss — every terminal path backs the key off anyway),
39
+ * and below workerd's ~30s waitUntil grace so the cap timer still fires in a
40
+ * live context. When workerd kills the context BEFORE the cap fires, the timer
41
+ * dies with it — that stranding is healed by scheduleShellCapture's staleness
42
+ * check on the stampede guard, keyed off the same constant.
43
+ */
44
+ export declare const SHELL_CAPTURE_TASK_HARD_CAP_MS = 25000;
@@ -26,6 +26,7 @@ import type { TransitionWhenFn } from "../types/segments.js";
26
26
  import type { ResolvedTracing } from "../router/tracing.js";
27
27
  import type { RenderMode, RenderPhase } from "../router/timeout.js";
28
28
  import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
29
+ import type { RoutineTrace } from "../rsc/routine-plan.js";
29
30
  export interface RenderForegroundCursor {
30
31
  mode: RenderMode;
31
32
  phase: RenderPhase;
@@ -542,6 +543,8 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
542
543
  * loaders and post-commit stream work have separate lifetimes.
543
544
  */
544
545
  _renderForeground?: RenderForegroundCursor;
546
+ /** @internal Request routine currently executing, used by timeout diagnostics. */
547
+ _activeRoutine?: RoutineTrace;
545
548
  /** @internal Keep the render cursor active for timeout support. */
546
549
  _renderDiagnosticsEnabled?: boolean;
547
550
  /**
@@ -585,7 +588,7 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
585
588
  * This is the type exported to library consumers. Internal code should
586
589
  * use the full RequestContext interface directly.
587
590
  */
588
- export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhen" | "_pprTransitionDecisions" | "_pprReplayPostMatchReason" | "_cacheStore" | "_searchParamsFilter" | "_shellCaptureRun" | "_shellImplicitCache" | "_shellLoaderSeed" | "_shellCaptureLoaderRecords" | "_shellCaptureHandleLiveness" | "_shellCaptureLoaderHandleValues" | "_shellFragmentPayload" | "_shellCaptureGuardTripped" | "_shellCaptureGuardTrippedLoaderId" | "_explicitTaggedStores" | "_requestTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_locationState" | "_routeName" | "_prevRouteKey" | "_gateCurrentUrl" | "_gateCurrentParams" | "_gateActionId" | "_gateActionUrl" | "_gateActionResult" | "_gateFormData" | "_inActionRevalidation" | "_reportedErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_tracing" | "_basename" | "_routerId" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
591
+ export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhen" | "_pprTransitionDecisions" | "_pprReplayPostMatchReason" | "_cacheStore" | "_searchParamsFilter" | "_shellCaptureRun" | "_shellImplicitCache" | "_shellLoaderSeed" | "_shellCaptureLoaderRecords" | "_shellCaptureHandleLiveness" | "_shellCaptureLoaderHandleValues" | "_shellFragmentPayload" | "_shellCaptureGuardTripped" | "_shellCaptureGuardTrippedLoaderId" | "_explicitTaggedStores" | "_requestTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_locationState" | "_routeName" | "_prevRouteKey" | "_gateCurrentUrl" | "_gateCurrentParams" | "_gateActionId" | "_gateActionUrl" | "_gateActionResult" | "_gateFormData" | "_inActionRevalidation" | "_reportedErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_activeRoutine" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_tracing" | "_basename" | "_routerId" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
589
592
  /**
590
593
  * Marker for a waitUntil-scheduled fn whose task promise must NOT enter
591
594
  * _pendingBackgroundTasks. Used by the PPR shell capture for its own task:
@@ -42,7 +42,20 @@ export interface StaticHandlerOptions {
42
42
  */
43
43
  passthrough?: boolean;
44
44
  }
45
- export interface StaticHandlerDefinition<TParams extends Record<string, any> = any> {
45
+ declare const STATIC_HANDLER_REF: unique symbol;
46
+ /**
47
+ * Opaque identity used by DSL type seams that accept Static() values.
48
+ *
49
+ * The brand is required, never optional: an optional brand makes this a weak
50
+ * type, and weak-type checking lets `{}` through — `parallel({ "@x": {} })`
51
+ * would typecheck and then die at render as an invalid React child. The
52
+ * symbol never exists at runtime; Static() casts its return instead.
53
+ */
54
+ export interface StaticHandlerRef {
55
+ /** @internal Type-only brand; see interface doc. */
56
+ readonly [STATIC_HANDLER_REF]: true;
57
+ }
58
+ export interface StaticHandlerDefinition<TParams extends Record<string, any> = any> extends StaticHandlerRef {
46
59
  readonly __brand: "staticHandler";
47
60
  /** Auto-generated unique ID (injected by Vite plugin). */
48
61
  $$id: string;
@@ -55,3 +68,4 @@ export interface StaticHandlerDefinition<TParams extends Record<string, any> = a
55
68
  }
56
69
  export declare function Static<TParams extends Record<string, any> = {}>(handler: (ctx: StaticBuildContext) => ReactNode | Promise<ReactNode>, options?: StaticHandlerOptions, __injectedId?: string): StaticHandlerDefinition<TParams>;
57
70
  export declare function isStaticHandler(value: unknown): value is StaticHandlerDefinition;
71
+ export {};
@@ -3,7 +3,7 @@ import type { ErrorBoundaryHandler, ExtractParams, Handler, HandlerContext, Load
3
3
  import type { AllUseItems, TypedLayoutItem, ParallelItem, InterceptItem, MiddlewareItem, RevalidateItem, LoaderItem, LoadingItem, ErrorBoundaryItem, NotFoundBoundaryItem, LayoutUseItem, RouteUseItem, ResponseRouteUseItem, ParallelUseItem, InterceptUseItem, LoaderUseItem, TypedCacheItem, TransitionItem, TypedTransitionItem, TypedRouteItem, TypedIncludeItem, UseItems } from "../route-types.js";
4
4
  import type { SearchSchema } from "../search-params.js";
5
5
  import type { PrerenderHandlerDefinition, PassthroughHandlerDefinition } from "../prerender.js";
6
- import type { StaticHandlerDefinition } from "../static-handler.js";
6
+ import type { StaticHandlerDefinition, StaticHandlerRef } from "../static-handler.js";
7
7
  import type { ResponseHandler, ResponseHandlerContext, TextResponseHandler } from "./response-types.js";
8
8
  import type { UnnamedRoute, LocalOnlyInclude, PathOptions, UrlPatterns, IncludeOptions } from "./pattern-types.js";
9
9
  import type { ExtractRoutes, ExtractResponses } from "./type-extraction.js";
@@ -118,13 +118,12 @@ export type PathHelpers<TEnv> = {
118
118
  *
119
119
  * Not generic over the slots record: an inferred type parameter makes the
120
120
  * object literal an inference site, which suppresses contextual typing of
121
- * arrow slot handlers (`(ctx) => ...` was implicit any). Bare handlers infer
122
- * now; a descriptor's `handler:` arrow still needs an explicit ctx annotation
123
- * because StaticHandlerDefinition's own `.handler` joins the contextual union
124
- * (two callables — see parallel-slot-handler-types.test.ts).
121
+ * arrow slot handlers (`(ctx) => ...` was implicit any). Static() values use
122
+ * an opaque handler-less reference here so their own `.handler` cannot
123
+ * contribute a second call signature or expose internal fields in completion.
125
124
  */
126
- parallel: (slots: Record<`@${string}`, Handler<any, any, TEnv> | ReactNode | StaticHandlerDefinition | {
127
- handler: Handler<any, any, TEnv> | ReactNode | StaticHandlerDefinition;
125
+ parallel: (slots: Record<`@${string}`, Handler<any, any, TEnv> | ReactNode | StaticHandlerRef | {
126
+ handler: Handler<any, any, TEnv> | ReactNode | StaticHandlerRef;
128
127
  use?: () => ParallelUseItem[];
129
128
  }>, use?: () => ParallelUseItem[]) => ParallelItem;
130
129
  /**
@@ -0,0 +1,2 @@
1
+ export declare function buildEncryptionKey(): string;
2
+ export declare function defineEncryptionKeyExpr(): string;
@@ -1,6 +1,16 @@
1
1
  import type { Plugin } from "vite";
2
2
  export { exposeRouterId } from "./expose-ids/router-transform.js";
3
3
  export type { ExposeInternalIdsApi } from "./expose-ids/types.js";
4
+ /**
5
+ * Stub export-only loader modules before plugin-rsc reduces scan modules to
6
+ * imports. The regular post transform remains authoritative outside scan builds.
7
+ *
8
+ * Handles and location-state definitions are intentionally excluded: handle
9
+ * collect functions run in the browser, and location-state definitions are
10
+ * callable browser objects. Their dependencies must remain visible to non-RSC
11
+ * validation rather than being hidden behind an opaque scan stub.
12
+ */
13
+ export declare function createLoaderScanStubPlugin(): Plugin;
4
14
  export declare function exposeInternalIds(options?: {
5
15
  forceBuild?: boolean;
6
16
  }): Plugin;
@@ -0,0 +1,24 @@
1
+ import type { Plugin } from "vite";
2
+ /**
3
+ * Replace dev-style server-reference ids with their production hashes. The temp
4
+ * server renders Static/Prerender in a dev/serve Vite server, so plugin-rsc mints
5
+ * a dev-style id (e.g. "/src/urls/prerender.tsx"); rewriting only the id (group 2)
6
+ * leaves the hoisted function and its trailing encrypted .bind(...) untouched.
7
+ * Exported for testing; used by hashServerRefs. Returns null if nothing changed.
8
+ */
9
+ export declare function transformServerRefs(code: string, projectRoot: string): string | null;
10
+ /**
11
+ * Server-side analog of hashClientRefs. Runs in the build-discovery temp server
12
+ * (a dev/serve Vite server, where plugin-rsc mints dev-style ids), rewriting
13
+ * registerServerReference ids to production hashes AFTER plugin-rsc's
14
+ * rsc:use-server transform (enforce:"post"), so the Flight serializer bakes the
15
+ * production hash into the stored prerender/static payloads.
16
+ *
17
+ * Without this, a server-created action embedded in a prerendered/static Flight
18
+ * is stored with a dev-style id that the production hash-keyed server-references
19
+ * manifest cannot resolve -> "server reference not found" on a build-time-cache
20
+ * hit. Mirrors hashClientRefs, which already does exactly this for the client
21
+ * half of the same payload (which is why client refs in stored Flight are
22
+ * already production hashes while server refs were not).
23
+ */
24
+ export declare function hashServerRefs(projectRoot: string): Plugin;
@@ -0,0 +1 @@
1
+ export declare function registerServerReferenceRegex(): RegExp;
@@ -52,4 +52,16 @@ export declare function createVirtualEntriesPlugin(entries: {
52
52
  * asset collisions from @vitejs/plugin-rsc copying RSC-env assets to the client bundle.
53
53
  */
54
54
  export declare function onwarn(warning: Vite.Rollup.RollupLog, defaultHandler: (warning: Vite.Rollup.RollupLog) => void): void;
55
+ /**
56
+ * All of `browser/prefetch/` belongs to the eager "router" chunk — including
57
+ * the modules reachable only via loader.ts's dynamic import. #766 split those
58
+ * into a separate lazy chunk to trim the eager runtime (~2.3 KB gzip), but
59
+ * production's `defaultPrefetch: "viewport"` loads them on almost every page,
60
+ * so the split became a document -> router -> runtime request waterfall
61
+ * (measured 502 ms critical-path latency in a deployed worker). Same-chunk
62
+ * placement resolves the dynamic import without a network fetch
63
+ * (`Promise.resolve().then(...)` in the emitted chunk), and the merged chunk
64
+ * gzips smaller than the two chunks did apart. Do not re-split without new
65
+ * information (AGENTS.md, Bundle hygiene, rejected optimizations).
66
+ */
55
67
  export declare function getManualChunks(id: string): string | undefined;