@rangojs/router 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,8 +30,6 @@ import type { KVNamespace, CFCacheReadDebugEvent, CFCacheDebug, CFCacheStoreOpti
30
30
  export type { KVNamespace, CFCacheReadDebugEvent, CFCacheDebug, CFCacheStoreOptions, };
31
31
  export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
32
32
  readonly supportsPassiveShellReads: true;
33
- /** True when constructed without KV: the shell family no-ops (see ctor). */
34
- readonly shellFamilyInert?: boolean;
35
33
  readonly defaults?: CacheDefaults;
36
34
  readonly keyGenerator?: (ctx: RequestContext<TEnv>, defaultKey: string) => string | Promise<string>;
37
35
  private readonly namespace?;
@@ -43,6 +41,8 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
43
41
  private readonly kvReadTimeoutMs;
44
42
  private readonly debug?;
45
43
  private readonly kv?;
44
+ /** True when constructed without KV: no durable tag history (see ctor). */
45
+ readonly tagHistoryInert?: boolean;
46
46
  private readonly onRevalidateTag?;
47
47
  private readonly tagPurge?;
48
48
  private readonly tagInvalidationTtl?;
@@ -257,15 +257,26 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
257
257
  */
258
258
  setItem(key: string, value: string, options?: CacheItemOptions): Promise<void>;
259
259
  /**
260
- * Warn once per isolate that the shell family is inert: getShell/putShell
261
- * are ONLY called for routes that declared the `ppr` path option, so firing
262
- * here (not in the constructor) scopes the warning to apps that actually
263
- * use PPR a KV-less CFCacheStore is a perfectly fine config otherwise.
264
- * Without it, the correctness-first fail-open (issue #651) is invisible:
265
- * every ppr route is a permanent MISS with zero diagnostics.
260
+ * Warn once per isolate that a TAGGED shell landed on a store with no
261
+ * eviction path for it: no KV (markers) and no tagPurge (purge-by-tag).
262
+ * The shell still caches and expires by ttl+swr, but updateTag()/
263
+ * revalidateTag() cannot reach it silent staleness a consumer who tagged
264
+ * the route clearly did not intend. Fired from putShell (not the
265
+ * constructor) so an untagged edge-only ppr config stays warning-free.
266
266
  * @internal
267
267
  */
268
- private warnShellFamilyInertOnce;
268
+ private warnShellTagsNoEvictionOnce;
269
+ /**
270
+ * Generation gate for shell writes and the capture scheduler
271
+ * (isTagsInvalidatedSince). With KV it is the durable marker cascade.
272
+ * Without KV there are no markers: in purge mode the per-request memo is
273
+ * the only signal — a capture racing THIS request's updateTag() is still
274
+ * rejected (read-your-own-writes), while cross-request races are bounded
275
+ * by ttl+swr exactly like the data families' purge-mode writes. Without
276
+ * either, fail open (ttl/swr-only semantics, warned at putShell).
277
+ * @internal
278
+ */
279
+ private isShellGenerationInvalidated;
269
280
  /**
270
281
  * Get a cached PPR shell entry from Cache API, falling through to KV and
271
282
  * promoting a valid KV hit. Both tiers store one envelope so the prelude,
@@ -278,13 +289,14 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
278
289
  shouldRevalidate?: boolean;
279
290
  } | null>;
280
291
  /**
281
- * Store a PPR shell envelope in Cache API and, when its retention meets KV's
282
- * 60-second floor, KV. The shared write is registered with waitUntil and
283
- * awaited so invalidation rejection can be acknowledged to the capture
284
- * scheduler. Short-lived shells remain useful in L1 even though KV rejects
285
- * them.
292
+ * Store a PPR shell envelope in Cache API and, when KV is configured and
293
+ * the retention meets its 60-second floor, KV. The shared write is
294
+ * registered with waitUntil and awaited so invalidation rejection can be
295
+ * acknowledged to the capture scheduler. Short-lived shells remain useful
296
+ * in L1 even though KV rejects them; a KV-less store is L1-only by design
297
+ * (edge-only ppr — see the section comment).
286
298
  */
287
- putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | void>;
299
+ putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | "uncacheable" | void>;
288
300
  /** Rebuild the public shell entry from its validated storage envelope. */
289
301
  private shellEnvelopeToEntry;
290
302
  /** Build the Cache API representation of the coupled shell envelope. */
@@ -569,7 +581,9 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
569
581
  * Shell tag-generation gate (SegmentCacheStore.isTagsInvalidatedSince): the
570
582
  * SAME KV markers used by runtime envelopes also evict immutable build shells
571
583
  * and captures whose write races updateTag(). Thin public wrapper over the
572
- * private envelope check (marker >= since, fail open).
584
+ * shell generation check (marker >= since, fail open); KV-less it degrades
585
+ * to the per-request memo in purge mode and to false otherwise — see
586
+ * isShellGenerationInvalidated.
573
587
  */
574
588
  isTagsInvalidatedSince(tags: string[], sinceMs: number): Promise<boolean>;
575
589
  invalidateTags(tags: string[]): Promise<void>;
@@ -90,7 +90,7 @@ export declare class RecordingShellStore<TEnv = unknown> implements SegmentCache
90
90
  entry: ShellCacheEntry;
91
91
  shouldRevalidate?: boolean;
92
92
  } | null>;
93
- putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | void>;
93
+ putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | "uncacheable" | void>;
94
94
  invalidateTags(tags: string[]): Promise<void>;
95
95
  }
96
96
  /** True iff `store` is a RecordingShellStore (duck-typed across module copies). */
@@ -181,6 +181,6 @@ export declare class SeededShellStore<TEnv = unknown> implements SegmentCacheSto
181
181
  entry: ShellCacheEntry;
182
182
  shouldRevalidate?: boolean;
183
183
  } | null>;
184
- putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | void>;
184
+ putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | "uncacheable" | void>;
185
185
  invalidateTags(tags: string[]): Promise<void>;
186
186
  }
@@ -161,18 +161,37 @@ export interface SegmentCacheStore<TEnv = unknown> {
161
161
  * @param tags - Optional cache tags for invalidation (participates in
162
162
  * invalidateTags via the same tag machinery as the item family)
163
163
  * @returns `invalidated` when a generation marker rejected the write,
164
- * `stored` when acknowledged, or void for stores without acknowledgements.
164
+ * `stored` when acknowledged, `uncacheable` when the entry can NEVER be
165
+ * stored under the current configuration (every retry would refuse
166
+ * identically — the capture scheduler backs the key off instead of
167
+ * recapturing per MISS; CFCacheStore returns it for a tag set whose
168
+ * Cache-Tag header overflows in KV-less purge mode), or void for stores
169
+ * without acknowledgements.
165
170
  */
166
- putShell?(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | void>;
171
+ putShell?(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | "uncacheable" | void>;
167
172
  /**
168
173
  * Declares the shell family present-but-inert: getShell/putShell exist but
169
- * no-op (CFCacheStore without a KV namespace). scheduleShellCapture skips
170
- * captures whose only write target is inert — the background render would be
171
- * dead work that still occupies the per-isolate serialized capture queue
172
- * (a promise-heavy route bakes for seconds per MISS with nothing stored).
173
- * Absent/false means the family, when present, actually stores.
174
+ * no-op (a custom store whose backing tier is conditionally unavailable).
175
+ * scheduleShellCapture skips captures whose only write target is inert —
176
+ * the background render would be dead work that still occupies the
177
+ * per-isolate serialized capture queue (a promise-heavy route bakes for
178
+ * seconds per MISS with nothing stored). Absent/false means the family,
179
+ * when present, actually stores. The built-in stores never declare it:
180
+ * CFCacheStore is L1-only without KV (edge-only ppr), not inert.
174
181
  */
175
182
  shellFamilyInert?: boolean;
183
+ /**
184
+ * Declares isTagsInvalidatedSince present-but-inert: the store implements
185
+ * the method but has no DURABLE invalidation history behind it (a KV-less
186
+ * CFCacheStore answers from the per-request memo at best). Runtime shells
187
+ * tolerate that — purge eviction plus ttl/swr bound their staleness — but
188
+ * a TAGGED build-manifest shell is immutable with no ttl of its own, so
189
+ * serving it on such a store would make updateTag() a permanent no-op for
190
+ * it. The build-shell read-through declines tagged entries on this flag
191
+ * (same declared-intent-cannot-be-honored doctrine as a store missing the
192
+ * method entirely). Absent/false means answers are durably backed.
193
+ */
194
+ tagHistoryInert?: boolean;
176
195
  /**
177
196
  * Get a cached function result by key.
178
197
  * Returns the serialized value, optional handle data, and staleness flag.
@@ -1,6 +1,6 @@
1
1
  import type { SearchSchemaValue } from "../search-params.js";
2
2
  import type { TrailingSlashMode } from "../types.js";
3
- import type { UrlPatterns } from "../urls/pattern-types.js";
3
+ import type { PartialPrerenderProps, UrlPatterns } from "../urls/pattern-types.js";
4
4
  import type { ClientTransitionConfig, ClientUrlPatterns } from "./types.js";
5
5
  export interface ClientUrlReference {
6
6
  readonly $$typeof: symbol;
@@ -10,6 +10,15 @@ export type ClientUrlDefinitionSource = ClientUrlReference | ClientUrlPatterns;
10
10
  export interface ClientUrlProjectionOptions {
11
11
  readonly search?: Readonly<Record<string, SearchSchemaValue>>;
12
12
  readonly trailingSlash?: TrailingSlashMode;
13
+ /**
14
+ * The `ppr` path option, JSON-projected. Materialization passes it onto
15
+ * the group route's server `path()` verbatim, so the manifest entry
16
+ * carries it and the runtime shell capture/serve lanes engage exactly as
17
+ * for a hand-written server route (search is part of shell identity —
18
+ * the capture render seeds the key's own search, so group static parts
19
+ * may read useSearchParams).
20
+ */
21
+ readonly ppr?: true | Readonly<PartialPrerenderProps>;
13
22
  }
14
23
  export interface ClientUrlProjectionRoute {
15
24
  readonly id: string;
@@ -15,7 +15,7 @@ export interface ClientUrlItem {
15
15
  export type ClientUrlItemInput = ClientUrlItem | readonly ClientUrlItemInput[];
16
16
  export type ClientUrlItems = readonly ClientUrlItemInput[];
17
17
  export type ClientUrlUse = () => ClientUrlItems;
18
- export type ClientPathOptions<TName extends string = string, TSearch extends SearchSchema = SearchSchema> = Pick<PathOptions<TName, TSearch>, "name" | "search" | "trailingSlash">;
18
+ export type ClientPathOptions<TName extends string = string, TSearch extends SearchSchema = SearchSchema> = Pick<PathOptions<TName, TSearch>, "name" | "search" | "trailingSlash" | "ppr">;
19
19
  export type ClientPathFn = <const TPattern extends string, const TName extends string = UnnamedRoute, const TSearch extends SearchSchema = {}>(pattern: TPattern, component: ComponentType, optionsOrUse?: ClientPathOptions<TName, TSearch> | ClientUrlUse, use?: ClientUrlUse) => ClientUrlItem & TypedRouteItem<TName, TPattern, unknown, TSearch>;
20
20
  export type ClientLayoutFn = <const TItems extends ClientUrlItems>(component: ComponentType, children: () => TItems) => ClientUrlItem & TypedLayoutItem<ExtractRoutes<TItems>>;
21
21
  /**
@@ -104,7 +104,10 @@ export interface ClientUrlHelpers {
104
104
  * Pass `{ stream: "navigation" }` to await this loader before first flush
105
105
  * on DOCUMENT requests (see {@link LoaderOptions}) — the opt-in for loaders
106
106
  * whose data, handle pushes, or thrown notFound()/redirect() must be in the
107
- * SSR'd HTML. Per-loader: a dynamic sibling keeps streaming.
107
+ * SSR'd HTML. Per-loader: a dynamic sibling keeps streaming. Under a
108
+ * `ppr` group route the flag BAKES: the loader executes at shell capture
109
+ * and its settled return freezes into the shell (nested promises stay
110
+ * live holes).
108
111
  */
109
112
  readonly loader: <TData>(definition: LoaderDefinition<TData>, optionsOrUse?: LoaderOptions | ClientUrlUse, use?: ClientUrlUse) => ClientUrlItem;
110
113
  readonly loading: (component: ReactNode) => ClientUrlItem;
@@ -28,15 +28,15 @@ import { type RequestContext } from "../../server/request-context.js";
28
28
  export declare function isShellCaptureActive(reqCtx?: RequestContext<any> | undefined): boolean;
29
29
  export { createMaskedLoaderPromise } from "./mask-nested.js";
30
30
  /**
31
- * Lane decision for an entry's loaders under PPR (the loading() value decides;
32
- * docs/design/loader-container-bake.md):
33
- *
34
- * - RENDERABLE loading() (the LoaderBoundary Suspense fallback) — the LIVE
35
- * lane: masked at capture, guaranteed fresh on every serve. Returns true.
36
- * - No loading() (absent, or explicitly `false`, incl. `loading(x, { ssr:
37
- * false })` under the SSR manifest) the BAKE lane: the loader executes
38
- * during capture, its settled container bakes, nested pending promises hole
39
- * at the consumer's own Suspense. Returns false.
31
+ * Entry-level lane input for an entry's loaders under PPR (the loading()
32
+ * value; docs/design/loader-container-bake.md). The CAPTURE decision itself
33
+ * is per LOADER in loader-cache.ts: a `stream: "navigation"`
34
+ * (awaitBeforeFlush) loader BAKES at capture regardless of this value — the
35
+ * flag's document promise ("data in the HTML before first flush") maps to
36
+ * the frozen prelude while every other loader is LIVE (masked at capture,
37
+ * fresh on every serve), postponing at loading() or an inline Suspense.
38
+ * This helper still gates which entries pass a bake segment key for the
39
+ * legacy loading()-less shape and the HIT-tail seed overlay.
40
40
  *
41
41
  * Mirrors segment-system's isRenderableLoading so the mask decision and the
42
42
  * tree's boundary placement can never disagree.
@@ -126,9 +126,10 @@ export interface ShellCaptureDebugEvent {
126
126
  * the capture was not attempted
127
127
  * - skip-capacity: the isolate capture queue is full; a later request may retry
128
128
  * - skip-inert-store: the resolved store's shell family is missing or
129
- * declared inert (SegmentCacheStore.shellFamilyInert — a CFCacheStore
130
- * without a KV namespace); nothing could store the result, so the
131
- * background render was not scheduled at all
129
+ * declared inert (SegmentCacheStore.shellFamilyInert — a custom store
130
+ * whose backing tier is unavailable; built-in stores never declare it);
131
+ * nothing could store the result, so the background render was not
132
+ * scheduled at all
132
133
  * - skip-queue-timeout: the capture waited past CAPTURE_QUEUE_WAIT_BUDGET_MS
133
134
  * behind other captures and was dropped unrun (no backoff — the route is
134
135
  * not doomed, the isolate was busy; a later request re-probes). Carries
@@ -160,7 +161,7 @@ export interface ShellCaptureDebugEvent {
160
161
  /** A bake-lane loader settled into a shell that uses TTL/SWR-only invalidation. */
161
162
  untaggedBake?: true;
162
163
  /** Outcome reported by a store that supports shell-write acknowledgements. */
163
- storeWrite?: "stored" | "invalidated";
164
+ storeWrite?: "stored" | "invalidated" | "uncacheable";
164
165
  /** Consecutive failure count in the key's backoff entry, when one exists. */
165
166
  backoffFailures?: number;
166
167
  /** Ms remaining in the key's backoff window, when one exists. */
@@ -151,7 +151,10 @@ export type PathHelpers<TEnv> = {
151
151
  * Pass `{ stream: "navigation" }` to await this loader before first flush
152
152
  * on DOCUMENT requests (see {@link LoaderOptions}) — the opt-in for loaders
153
153
  * whose data, handle pushes, or thrown notFound()/redirect() must be in the
154
- * SSR'd HTML. Per-loader: a dynamic sibling keeps streaming.
154
+ * SSR'd HTML. Per-loader: a dynamic sibling keeps streaming. Under a
155
+ * `ppr` route the flag BAKES: the loader executes at shell capture and
156
+ * its settled return freezes into the shell (nested promises stay live
157
+ * holes) — the pre-flush promise applied to the prelude.
155
158
  */
156
159
  loader: <TData>(loaderDef: LoaderDefinition<TData>, optionsOrUse?: LoaderOptions | (() => LoaderUseItem[]), use?: () => LoaderUseItem[]) => LoaderItem;
157
160
  /**
@@ -4,17 +4,6 @@ import type { Plugin } from "vite";
4
4
  * Handles scoped packages (@org/name) and nested node_modules.
5
5
  * Returns null if the path doesn't contain a valid package reference.
6
6
  *
7
- * NOTE: This is a lossy transformation. It maps a specific submodule path
8
- * (e.g., pkg/internal/context.js) to the package root (pkg). The load()
9
- * hook then re-exports via the bare specifier, which resolves to the
10
- * package entry point. This works for packages that barrel-export their
11
- * "use client" symbols from the root, which covers the common case
12
- * (component libraries like @mantine/core, @chakra-ui/react, etc.).
13
- * Packages whose client symbols are only available from deep subpaths
14
- * (not re-exported from the root) would lose those symbols after the
15
- * rewrite. A more precise approach would resolve through the package's
16
- * exports map to find the correct entry point, but that adds significant
17
- * complexity for a rare edge case.
18
7
  * See: https://github.com/cloudflare/vinext/pull/413
19
8
  */
20
9
  export declare function extractPackageName(absolutePath: string): string | null;