@solidjs/router 2.0.0-next.21 → 2.0.0-next.23

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.
@@ -118,6 +118,7 @@ function installRouterIntegrations() {
118
118
  if (isServer) {
119
119
  // Server-only: initSubmissions only decodes during SSR, so client builds
120
120
  // tree-shake the codec (which now lives behind the runtime's server entry).
121
+ // The codec is async from @solidjs/web 2.0.0-rc.7 (encrypted cookie).
121
122
  provideFlashDecoder(decodeFlashCookie);
122
123
  }
123
124
  else {
package/dist/fs.d.ts CHANGED
@@ -12,8 +12,8 @@ export type FileRouteConfig<S extends string = string, T = unknown, F = undefine
12
12
  }) & ([Sch] extends [undefined] ? {} : {
13
13
  search: Sch;
14
14
  }) & {
15
- preload?: RoutePreloadFunc<T>;
16
- info?: RouteInfo;
15
+ preload?: RoutePreloadFunc<T> | undefined;
16
+ info?: RouteInfo | undefined;
17
17
  };
18
18
  /**
19
19
  * Identity helper for a route file's `route` export that types `preload`'s
@@ -34,11 +34,11 @@ export type FileRouteConfig<S extends string = string, T = unknown, F = undefine
34
34
  * types (and any `search` schema) flow through the manifest into `paths`.
35
35
  */
36
36
  export declare function defineFileRoute<S extends string, T = unknown, const F = DefinedRouteFilters<S>, Sch extends StandardSchemaV1<any, any> | undefined = undefined>(path: S, config: {
37
- matchFilters?: F & ValidFilters<F, S>;
38
- preload?: (args: RoutePreloadFuncArgs<RouteParams<S>>) => T;
37
+ matchFilters?: (F & ValidFilters<F, S>) | undefined;
38
+ preload?: ((args: RoutePreloadFuncArgs<RouteParams<S>>) => T) | undefined;
39
39
  /** Standard Schema validator for this route's search params; its input type flows into the typed path proxy. */
40
40
  search?: Sch;
41
- info?: RouteInfo;
41
+ info?: RouteInfo | undefined;
42
42
  }): FileRouteConfig<S, T, F, Sch>;
43
43
  /** A code-split module ref: delivered as a dynamic import. */
44
44
  export interface FileRouteLazyRef<M = Record<string, unknown>> {
@@ -47,7 +47,7 @@ export interface FileRouteLazyRef<M = Record<string, unknown>> {
47
47
  }
48
48
  /** An eager module ref: its picked exports are imported statically. */
49
49
  export interface FileRouteEagerRef<M = Record<string, unknown>> {
50
- src?: string;
50
+ src?: string | undefined;
51
51
  require(): M;
52
52
  }
53
53
  /** The shape of a nested route-manifest entry the adapter consumes. */
package/dist/index.js CHANGED
@@ -971,7 +971,8 @@ function createBranches(routeDef, base = "", stack = [], branches = []) {
971
971
  for (let i = 0, len = routeDefs.length; i < len; i++) {
972
972
  const def = routeDefs[i];
973
973
  if (def && typeof def === "object") {
974
- if (!def.hasOwnProperty("path")) def.path = "";
974
+ // absent and explicitly `undefined` paths are the same pathless route
975
+ if (def.path === undefined) def.path = "";
975
976
  const routes = createRoutes(def, base);
976
977
  for (const route of routes) {
977
978
  stack.push(route);
@@ -1097,7 +1098,12 @@ function provideFlightConsumer(factory) {
1097
1098
  * decoder is always installed before useSubmission can read — and a
1098
1099
  * router-only app, where it never installs, has no actions that could have
1099
1100
  * produced a flash cookie in the first place.
1101
+ *
1102
+ * The decoder is async: the flash cookie is encrypted (solidjs/solid#3239),
1103
+ * so the runtime's `decodeFlashCookie` decrypts through WebCrypto and the
1104
+ * seeding read parks on the not-ready protocol until the decode settles.
1100
1105
  */
1106
+
1101
1107
  let flashDecoder;
1102
1108
  function provideFlashDecoder(decoder) {
1103
1109
  flashDecoder || (flashDecoder = decoder);
@@ -1153,6 +1159,54 @@ function createRouterContext(integration, branches, getContext, options = {}) {
1153
1159
  }
1154
1160
  }
1155
1161
  }
1162
+
1163
+ // The decode, at most once per request: the decoder may answer with a
1164
+ // Promise (the cookie is encrypted; the runtime's decodeFlashCookie is
1165
+ // async), and this cache is what keeps the parked read's rerun from
1166
+ // restarting it — resumption finds the settled outcome and just reads it.
1167
+ // A decoder that rejects reads as "no flash", matching the runtime's own
1168
+ // malformed-cookie semantics.
1169
+ let flashDecode;
1170
+
1171
+ // The seeding read, as a memo: NotReadyError must surface from a reactive
1172
+ // node the graph can park and retry — never from router setup, which no
1173
+ // boundary guards — and the memo bounds the recompute to this function;
1174
+ // a parked reader resumes into the settled cache above, never a second
1175
+ // decode. Created only when a flash cookie actually arrived (server-only
1176
+ // by construction: flashCookieHeader is only ever set there), and
1177
+ // - `lazy`: server memos compute eagerly by default — deferred to first
1178
+ // read, a request whose submissions are never read never decodes;
1179
+ // - `transparent`: the memo exists on the server only, so its owner
1180
+ // must not consume a hydration-id slot — the client, which seeds
1181
+ // submissions as [] without ever creating this memo, would miss it
1182
+ // and every sibling id would shift.
1183
+ const flashSubmission = flashCookieHeader !== undefined ? createMemo(() => {
1184
+ if (!flashDecoder) return undefined;
1185
+ if (!flashDecode) {
1186
+ const promise = flashDecoder(flashCookieHeader).then(value => {
1187
+ flashDecode = {
1188
+ done: true,
1189
+ value
1190
+ };
1191
+ }, () => {
1192
+ flashDecode = {
1193
+ done: true,
1194
+ value: undefined
1195
+ };
1196
+ });
1197
+ flashDecode = {
1198
+ done: false,
1199
+ promise
1200
+ };
1201
+ }
1202
+ // SSR carries the Promise through NotReadyError so the parked
1203
+ // reader can resume, exactly like the lazy matches above.
1204
+ if (!flashDecode.done) throw new NotReadyError(flashDecode.promise);
1205
+ return flashDecode.value;
1206
+ }, {
1207
+ lazy: true,
1208
+ transparent: true
1209
+ }) : undefined;
1156
1210
  let submissions;
1157
1211
 
1158
1212
  // NotReadyError's source must be a reactive async node, not the raw
@@ -1384,12 +1438,16 @@ function createRouterContext(integration, branches, getContext, options = {}) {
1384
1438
  // Seeds the initial submission from a no-JS form post: the server
1385
1439
  // function runtime redirected back with the outcome in a one-shot flash
1386
1440
  // cookie (its default no-JS convention), consumed eagerly above and
1387
- // decoded here — so the post-redirect SSR renders useSubmission() state
1388
- // exactly as a scripted submission would. An explicitly pre-seeded
1389
- // `event.router.submission` (framework integrations) takes precedence.
1441
+ // decoded through the flashSubmission memo — so the post-redirect SSR
1442
+ // renders useSubmission() state exactly as a scripted submission would.
1443
+ // The memo read may throw NotReadyError while the (encrypted) cookie
1444
+ // decodes; the assignment in the submissions getter never completed, so
1445
+ // the resumed rerun retries it against the settled decode. An explicitly
1446
+ // pre-seeded `event.router.submission` (framework integrations) takes
1447
+ // precedence.
1390
1448
  function initSubmissions() {
1391
1449
  const e = getRequestEvent();
1392
- const submission = e && e.router && e.router.submission || (flashDecoder && flashCookieHeader !== undefined ? flashDecoder(flashCookieHeader) : undefined);
1450
+ const submission = e && e.router && e.router.submission || flashSubmission && flashSubmission();
1393
1451
  if (!submission) return [];
1394
1452
  return [{
1395
1453
  ...submission,
@@ -2616,6 +2674,7 @@ function installRouterIntegrations() {
2616
2674
  if (isServer) {
2617
2675
  // Server-only: initSubmissions only decodes during SSR, so client builds
2618
2676
  // tree-shake the codec (which now lives behind the runtime's server entry).
2677
+ // The codec is async from @solidjs/web 2.0.0-rc.7 (encrypted cookie).
2619
2678
  provideFlashDecoder(decodeFlashCookie);
2620
2679
  } else {
2621
2680
  setRouterFormHandler(handleFormAction);
@@ -32,9 +32,9 @@ export type DefinedRoute<S = undefined, T = unknown, F = undefined, C = undefine
32
32
  }) & ([Sch] extends [undefined] ? {} : {
33
33
  search: Sch;
34
34
  }) & {
35
- component?: RouteSectionComponent<T>;
36
- preload?: RoutePreloadFunc<T>;
37
- info?: RouteInfo;
35
+ component?: RouteSectionComponent<T> | undefined;
36
+ preload?: RoutePreloadFunc<T> | undefined;
37
+ info?: RouteInfo | undefined;
38
38
  };
39
39
  /**
40
40
  * Identity helper that types a single route from its own `path` pattern:
@@ -49,20 +49,20 @@ export type DefinedRoute<S = undefined, T = unknown, F = undefined, C = undefine
49
49
  */
50
50
  export declare function defineRoute<const S extends string | readonly string[], T = unknown, const F = DefinedRouteFilters<S>, const C extends RouteChildren | undefined = RouteChildren | undefined, Sch extends StandardSchemaV1<any, any> | undefined = undefined>(route: {
51
51
  path: S;
52
- matchFilters?: F & ValidFilters<F, S>;
53
- preload?: (args: RoutePreloadFuncArgs<RouteParams<S>>) => T;
54
- component?: DefinedRouteComponent<T, RouteParams<S>>;
52
+ matchFilters?: (F & ValidFilters<F, S>) | undefined;
53
+ preload?: ((args: RoutePreloadFuncArgs<RouteParams<S>>) => T) | undefined;
54
+ component?: DefinedRouteComponent<T, RouteParams<S>> | undefined;
55
55
  children?: C;
56
56
  /** Standard Schema validator for this route's search params; its input type flows into the typed path proxy. */
57
57
  search?: Sch;
58
- info?: RouteInfo;
58
+ info?: RouteInfo | undefined;
59
59
  }): DefinedRoute<S, T, F, C, Sch>;
60
60
  export declare function defineRoute<T = unknown, const C extends RouteChildren | undefined = RouteChildren | undefined, Sch extends StandardSchemaV1<any, any> | undefined = undefined>(route: {
61
- preload?: (args: RoutePreloadFuncArgs) => T;
62
- component?: DefinedRouteComponent<T, Params>;
61
+ preload?: ((args: RoutePreloadFuncArgs) => T) | undefined;
62
+ component?: DefinedRouteComponent<T, Params> | undefined;
63
63
  children?: C;
64
64
  search?: Sch;
65
- info?: RouteInfo;
65
+ info?: RouteInfo | undefined;
66
66
  }): DefinedRoute<undefined, T, undefined, C, Sch>;
67
67
  export interface RouterConfig<R extends readonly RouteDefinition[] = RouteDefinition[]> {
68
68
  /** The route tree. Immutable per instance — it is the source of truth for matching *and* types. */
package/dist/routing.d.ts CHANGED
@@ -216,7 +216,22 @@ export declare function getRouteMatches(branches: Branch[], location: string): R
216
216
  export declare function mergeParams(matches: RouteMatch[]): Params;
217
217
  export declare function registerFlightRouter(router: RouterContext): () => void;
218
218
  export declare function provideFlightConsumer(factory: (router: RouterContext) => () => void): void;
219
- export declare function provideFlashDecoder(decoder: (cookieHeader: string | null) => FlashSubmission | undefined): void;
219
+ /**
220
+ * The flash-cookie codec, provided by the action side (data/action.ts) so
221
+ * the router core never carries it: the core consumes the cookie eagerly
222
+ * per request (detection + one-shot clear via the runtime's isomorphic half)
223
+ * but defers decoding to this slot, read when the submissions signal
224
+ * initializes. Actions are created at module scope, so on the server the
225
+ * decoder is always installed before useSubmission can read — and a
226
+ * router-only app, where it never installs, has no actions that could have
227
+ * produced a flash cookie in the first place.
228
+ *
229
+ * The decoder is async: the flash cookie is encrypted (solidjs/solid#3239),
230
+ * so the runtime's `decodeFlashCookie` decrypts through WebCrypto and the
231
+ * seeding read parks on the not-ready protocol until the decode settles.
232
+ */
233
+ type FlashDecoder = (cookieHeader: string | null) => Promise<FlashSubmission | undefined>;
234
+ export declare function provideFlashDecoder(decoder: FlashDecoder): void;
220
235
  export declare function getIntent(): Intent | undefined;
221
236
  export declare function getInPreloadFn(): boolean;
222
237
  export declare function setInPreloadFn(value: boolean): void;
@@ -226,3 +241,4 @@ export declare function createRouterContext(integration: RouterIntegration, bran
226
241
  transformUrl?: (url: string) => string;
227
242
  }): RouterContext;
228
243
  export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch, matches?: () => RouteMatch[]): RouteContext;
244
+ export {};
package/dist/routing.js CHANGED
@@ -421,7 +421,8 @@ export function createBranches(routeDef, base = "", stack = [], branches = []) {
421
421
  for (let i = 0, len = routeDefs.length; i < len; i++) {
422
422
  const def = routeDefs[i];
423
423
  if (def && typeof def === "object") {
424
- if (!def.hasOwnProperty("path"))
424
+ // absent and explicitly `undefined` paths are the same pathless route
425
+ if (def.path === undefined)
425
426
  def.path = "";
426
427
  const routes = createRoutes(def, base);
427
428
  for (const route of routes) {
@@ -541,16 +542,6 @@ export function provideFlightConsumer(factory) {
541
542
  flightRouters.set(router, factory(router));
542
543
  }
543
544
  }
544
- /**
545
- * The flash-cookie codec, provided by the action side (data/action.ts) so
546
- * the router core never carries it: the core consumes the cookie eagerly
547
- * per request (detection + one-shot clear via the runtime's isomorphic half)
548
- * but defers decoding to this slot, read when the submissions signal
549
- * initializes. Actions are created at module scope, so on the server the
550
- * decoder is always installed before useSubmission can read — and a
551
- * router-only app, where it never installs, has no actions that could have
552
- * produced a flash cookie in the first place.
553
- */
554
545
  let flashDecoder;
555
546
  export function provideFlashDecoder(decoder) {
556
547
  flashDecoder || (flashDecoder = decoder);
@@ -605,6 +596,44 @@ export function createRouterContext(integration, branches, getContext, options =
605
596
  }
606
597
  }
607
598
  }
599
+ // The decode, at most once per request: the decoder may answer with a
600
+ // Promise (the cookie is encrypted; the runtime's decodeFlashCookie is
601
+ // async), and this cache is what keeps the parked read's rerun from
602
+ // restarting it — resumption finds the settled outcome and just reads it.
603
+ // A decoder that rejects reads as "no flash", matching the runtime's own
604
+ // malformed-cookie semantics.
605
+ let flashDecode;
606
+ // The seeding read, as a memo: NotReadyError must surface from a reactive
607
+ // node the graph can park and retry — never from router setup, which no
608
+ // boundary guards — and the memo bounds the recompute to this function;
609
+ // a parked reader resumes into the settled cache above, never a second
610
+ // decode. Created only when a flash cookie actually arrived (server-only
611
+ // by construction: flashCookieHeader is only ever set there), and
612
+ // - `lazy`: server memos compute eagerly by default — deferred to first
613
+ // read, a request whose submissions are never read never decodes;
614
+ // - `transparent`: the memo exists on the server only, so its owner
615
+ // must not consume a hydration-id slot — the client, which seeds
616
+ // submissions as [] without ever creating this memo, would miss it
617
+ // and every sibling id would shift.
618
+ const flashSubmission = flashCookieHeader !== undefined
619
+ ? createMemo(() => {
620
+ if (!flashDecoder)
621
+ return undefined;
622
+ if (!flashDecode) {
623
+ const promise = flashDecoder(flashCookieHeader).then(value => {
624
+ flashDecode = { done: true, value };
625
+ }, () => {
626
+ flashDecode = { done: true, value: undefined };
627
+ });
628
+ flashDecode = { done: false, promise };
629
+ }
630
+ // SSR carries the Promise through NotReadyError so the parked
631
+ // reader can resume, exactly like the lazy matches above.
632
+ if (!flashDecode.done)
633
+ throw new NotReadyError(flashDecode.promise);
634
+ return flashDecode.value;
635
+ }, { lazy: true, transparent: true })
636
+ : undefined;
608
637
  let submissions;
609
638
  // NotReadyError's source must be a reactive async node, not the raw
610
639
  // Promise. Keep one reader per boundary for this router owner so rejection
@@ -843,15 +872,16 @@ export function createRouterContext(integration, branches, getContext, options =
843
872
  // Seeds the initial submission from a no-JS form post: the server
844
873
  // function runtime redirected back with the outcome in a one-shot flash
845
874
  // cookie (its default no-JS convention), consumed eagerly above and
846
- // decoded here — so the post-redirect SSR renders useSubmission() state
847
- // exactly as a scripted submission would. An explicitly pre-seeded
848
- // `event.router.submission` (framework integrations) takes precedence.
875
+ // decoded through the flashSubmission memo — so the post-redirect SSR
876
+ // renders useSubmission() state exactly as a scripted submission would.
877
+ // The memo read may throw NotReadyError while the (encrypted) cookie
878
+ // decodes; the assignment in the submissions getter never completed, so
879
+ // the resumed rerun retries it against the settled decode. An explicitly
880
+ // pre-seeded `event.router.submission` (framework integrations) takes
881
+ // precedence.
849
882
  function initSubmissions() {
850
883
  const e = getRequestEvent();
851
- const submission = (e && e.router && e.router.submission) ||
852
- (flashDecoder && flashCookieHeader !== undefined
853
- ? flashDecoder(flashCookieHeader)
854
- : undefined);
884
+ const submission = (e && e.router && e.router.submission) || (flashSubmission && flashSubmission());
855
885
  if (!submission)
856
886
  return [];
857
887
  return [
package/dist/types.d.ts CHANGED
@@ -162,14 +162,14 @@ export type LazyRouteChildren = () => readonly RouteDefinition[] | Promise<reado
162
162
  routes: readonly RouteDefinition[];
163
163
  }>;
164
164
  export type RouteDefinition<S extends string | string[] = any, T = any> = {
165
- path?: S;
166
- matchFilters?: MatchFilters<S>;
167
- preload?: RoutePreloadFunc<T>;
168
- children?: RouteDefinition | readonly RouteDefinition[] | LazyRouteChildren;
169
- component?: RouteSectionComponent<T>;
165
+ path?: S | undefined;
166
+ matchFilters?: MatchFilters<S> | undefined;
167
+ preload?: RoutePreloadFunc<T> | undefined;
168
+ children?: RouteDefinition | readonly RouteDefinition[] | LazyRouteChildren | undefined;
169
+ component?: RouteSectionComponent<T> | undefined;
170
170
  /** Standard Schema validator for this route's search params; its input type flows into the typed path proxy. */
171
- search?: StandardSchemaV1<any, any>;
172
- info?: RouteInfo;
171
+ search?: StandardSchemaV1<any, any> | undefined;
172
+ info?: RouteInfo | undefined;
173
173
  };
174
174
  import type { RouteInfo } from "./index.jsx";
175
175
  export type { RouteInfo };
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "2.0.0-next.21",
9
+ "version": "2.0.0-next.23",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",
@@ -38,21 +38,21 @@
38
38
  "@rollup/plugin-node-resolve": "15.3.0",
39
39
  "@rollup/plugin-terser": "0.4.4",
40
40
  "@solidjs/vite-plugin": "3.0.0-next.35",
41
- "@solidjs/web": "^2.0.0-rc.5",
41
+ "@solidjs/web": "^2.0.0-rc.7",
42
42
  "@types/jest": "^29.5.14",
43
43
  "@types/node": "^22.10.0",
44
44
  "babel-preset-solid": "^2.0.0-rc.2",
45
45
  "jsdom": "^25.0.1",
46
46
  "prettier": "^3.4.1",
47
47
  "rollup": "^4.27.4",
48
- "solid-js": "^2.0.0-rc.5",
48
+ "solid-js": "^2.0.0-rc.7",
49
49
  "typescript": "^5.7.2",
50
50
  "vite": "^8.2.2",
51
51
  "vitest": "^4.1.11"
52
52
  },
53
53
  "peerDependencies": {
54
- "@solidjs/web": "^2.0.0-rc.5",
55
- "solid-js": "^2.0.0-rc.5"
54
+ "@solidjs/web": "^2.0.0-rc.7",
55
+ "solid-js": "^2.0.0-rc.7"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "rm -rf dist && tsc && rollup -c",