@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.
- package/dist/types/cache/cf/cf-cache-store.d.ts +30 -16
- package/dist/types/cache/shell-snapshot.d.ts +2 -2
- package/dist/types/cache/types.d.ts +26 -7
- package/dist/types/client-urls/server-projection.d.ts +10 -1
- package/dist/types/client-urls/types.d.ts +5 -2
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -9
- package/dist/types/rsc/shell-capture.d.ts +5 -4
- package/dist/types/urls/path-helper-types.d.ts +4 -1
- package/dist/types/vite/plugins/client-ref-dedup.d.ts +0 -11
- package/dist/vite/index.js +360 -108
- package/package.json +25 -25
- package/skills/client-urls/SKILL.md +9 -9
- package/skills/cloudflare/SKILL.md +5 -3
- package/skills/ppr/SKILL.md +11 -7
- package/src/cache/cf/cf-cache-store.ts +118 -62
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/types.ts +27 -7
- package/src/client-urls/client-urls.ts +78 -0
- package/src/client-urls/server-projection.ts +95 -4
- package/src/client-urls/types.ts +8 -2
- package/src/router/segment-resolution/fresh.ts +8 -2
- package/src/router/segment-resolution/loader-cache.ts +34 -13
- package/src/router/segment-resolution/loader-mask.ts +9 -9
- package/src/router/segment-resolution/revalidation.ts +3 -1
- package/src/rsc/shell-build-manifest.ts +13 -6
- package/src/rsc/shell-capture.ts +22 -5
- package/src/urls/path-helper-types.ts +4 -1
- package/src/vite/plugins/client-ref-dedup.ts +281 -19
- package/src/vite/plugins/expose-action-id.ts +45 -23
|
@@ -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
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
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
|
|
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
|
|
282
|
-
* 60-second floor, KV. The shared write is
|
|
283
|
-
* awaited so invalidation rejection can be
|
|
284
|
-
* scheduler. Short-lived shells remain useful
|
|
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
|
-
*
|
|
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,
|
|
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 (
|
|
170
|
-
* captures whose only write target is inert —
|
|
171
|
-
* dead work that still occupies the
|
|
172
|
-
* (a promise-heavy route bakes for
|
|
173
|
-
* Absent/false means the family,
|
|
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
|
-
*
|
|
32
|
-
* docs/design/loader-container-bake.md)
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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
|
|
130
|
-
*
|
|
131
|
-
* background render was not
|
|
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;
|