@solidjs/router 2.0.0-next.29 → 2.0.0-next.31

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/README.md CHANGED
@@ -850,7 +850,7 @@ const isRouting = useIsRouting();
850
850
  return <div classList={{ "grey-out": isRouting() }}>...</div>;
851
851
  ```
852
852
 
853
- In Solid's dev and observe builds the router also declares every navigation to the attribution engine (`solid-js/attribution`): holds and re-runs caused by a navigation are named after the route pattern (`navigation to /users/:id`), timed from the user event that started it, and redirect hops fold onto the navigation they belong to. `attribution.navigations()` and `feedback().navigations` list them; nothing of this exists in production builds.
853
+ In Solid's dev and observe builds the router also declares every navigation to the attribution engine (`solid-js/attribution`): holds and re-runs caused by a navigation are named after the route pattern (`navigation to /users/:id`), timed from the user event that started it, and redirect hops fold onto the navigation they belong to. `attribution.history("navigation")` and `feedback().navigations` list them; nothing of this exists in production builds.
854
854
 
855
855
  ### useMatch
856
856
 
@@ -29,9 +29,10 @@ export const actions = /* #__PURE__ */ new Map();
29
29
  export function handleFormAction(evt, router, actionBase) {
30
30
  if (evt.defaultPrevented)
31
31
  return;
32
+ const form = evt.target;
32
33
  let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction")
33
34
  ? evt.submitter.getAttribute("formaction")
34
- : evt.target.getAttribute("action");
35
+ : form.getAttribute("action");
35
36
  if (!actionRef)
36
37
  return;
37
38
  const serverAction = !actionRef.startsWith("https://action/");
@@ -42,7 +43,7 @@ export function handleFormAction(evt, router, actionBase) {
42
43
  if (!actionRef.startsWith(actionBase))
43
44
  return;
44
45
  }
45
- if (evt.target.method.toUpperCase() !== "POST")
46
+ if (form.method.toUpperCase() !== "POST")
46
47
  throw new Error("Only POST forms are supported for Actions");
47
48
  // A registry miss on a server-action url is a direct bind whose module
48
49
  // never loaded client-side (server components): the url is self-describing
@@ -54,10 +55,8 @@ export function handleFormAction(evt, router, actionBase) {
54
55
  const handler = actions.get(actionRef) || (serverAction && createServerFormAction(actionRef));
55
56
  if (handler) {
56
57
  evt.preventDefault();
57
- const data = new FormData(evt.target, evt.submitter);
58
- handler.call({ r: router, f: evt.target }, evt.target.enctype === "multipart/form-data"
59
- ? data
60
- : new URLSearchParams(data));
58
+ const data = new FormData(form, evt.submitter);
59
+ handler.call({ r: router, f: form }, form.enctype === "multipart/form-data" ? data : new URLSearchParams(data));
61
60
  }
62
61
  }
63
62
  /**
@@ -159,18 +158,22 @@ function actionImpl(fn, options = {}) {
159
158
  const runMutation = () => fn(...variables);
160
159
  const run = createSolidAction(async function* (context) {
161
160
  context.optimistic?.();
161
+ let value;
162
+ let error = false;
162
163
  try {
163
- const value = await context.call();
164
- yield;
165
- return { error: false, value };
164
+ value = await context.call();
166
165
  }
167
- catch (error) {
168
- yield;
169
- return { error: true, value: error };
166
+ catch (e) {
167
+ value = e;
168
+ error = true;
170
169
  }
170
+ const read = await readResponse(value, error);
171
+ yield;
172
+ // Apply inside the transition so the default revalidation's refetch and
173
+ // the release of the caller's optimistic writes commit as one frame (#619).
174
+ return applyResponse(read, router.navigatorFactory(), flightApplications !== flightApplicationsBefore);
171
175
  });
172
176
  form && setFormBusy(form, 1);
173
- let settled;
174
177
  let response;
175
178
  // The transport consumer is awaited before a single-flight mutation
176
179
  // resolves, so a counter delta over the call tells whether this action's
@@ -180,7 +183,7 @@ function actionImpl(fn, options = {}) {
180
183
  // which misses every response the server returned without flight data.
181
184
  const flightApplicationsBefore = flightApplications;
182
185
  try {
183
- settled = await settleActionResult(run({
186
+ response = await settleActionResult(run({
184
187
  call: runMutation,
185
188
  optimistic: submitHooks.size
186
189
  ? () => {
@@ -189,7 +192,6 @@ function actionImpl(fn, options = {}) {
189
192
  }
190
193
  : undefined
191
194
  }));
192
- response = await handleResponse(settled.value, settled.error, router.navigatorFactory(), flightApplications !== flightApplicationsBefore);
193
195
  }
194
196
  finally {
195
197
  form && setFormBusy(form, -1);
@@ -198,8 +200,8 @@ function actionImpl(fn, options = {}) {
198
200
  submission = {
199
201
  input: variables,
200
202
  url,
201
- result: response && response.data,
202
- error: response && response.error,
203
+ result: response?.data,
204
+ error: response?.error,
203
205
  clear() {
204
206
  router.submissions[1](entries => entries.filter(entry => entry !== submission));
205
207
  },
@@ -244,8 +246,7 @@ function toAction(invoke, url, boundArgs = [], base = url, submitHooks = new Map
244
246
  fn.with = function (...args) {
245
247
  const uri = new URL(url, mockBase);
246
248
  uri.searchParams.set("args", hashKey(args));
247
- const next = toAction(invoke, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search, [...boundArgs, ...args], base, submitHooks, settledHooks);
248
- return next;
249
+ return toAction(invoke, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search, [...boundArgs, ...args], base, submitHooks, settledHooks);
249
250
  };
250
251
  fn.onSubmit = function (hook) {
251
252
  const id = Symbol("actionOnSubmitHook");
@@ -362,8 +363,10 @@ function applyResponseMetadata(metadata, navigate, flightData) {
362
363
  cacheKeyOp(keys, entry => (entry[0] = 0));
363
364
  // set cache — and fan the payload out to other keyed stores (live query
364
365
  // channels adopt delivered values instead of reconnecting)
365
- flightData && Object.keys(flightData).forEach(k => query.set(k, flightData[k]));
366
- flightData && deliverFlightData(flightData);
366
+ if (flightData) {
367
+ Object.keys(flightData).forEach(k => query.set(k, flightData[k]));
368
+ deliverFlightData(flightData);
369
+ }
367
370
  // trigger revalidation inside the same transition as the navigation, so
368
371
  // the redirect commits atomically with fresh data: surviving consumers
369
372
  // (shared layouts) the flight payload didn't seed refetch and hold the
@@ -371,7 +374,7 @@ function applyResponseMetadata(metadata, navigate, flightData) {
371
374
  // are fresh again by now, so the sweep re-reads them from cache.
372
375
  revalidate(keys, false);
373
376
  }
374
- async function handleResponse(response, error, navigate, metadataHandled) {
377
+ async function readResponse(response, error) {
375
378
  let data;
376
379
  let flightData;
377
380
  let metadata;
@@ -397,6 +400,12 @@ async function handleResponse(response, error, navigate, metadataHandled) {
397
400
  return { error: response };
398
401
  else
399
402
  data = response;
403
+ return { data, flightData, metadata };
404
+ }
405
+ function applyResponse(read, navigate, metadataHandled) {
406
+ if ("error" in read)
407
+ return read;
408
+ const { data, flightData, metadata } = read;
400
409
  // The transport consumer applies metadata before returning a server
401
410
  // function's unwrapped value. Do not treat that value as a second plain
402
411
  // action response and invalidate the freshly seeded query cache again.
@@ -32,7 +32,7 @@ export function setupNativeEvents({ preload = true, explicitLinks = false, actio
32
32
  if (target || (!href && !a.hasAttribute("state")))
33
33
  return;
34
34
  const rel = (a.getAttribute("rel") || "").split(/\s+/);
35
- if (a.hasAttribute("download") || (rel && rel.includes("external")))
35
+ if (a.hasAttribute("download") || rel.includes("external"))
36
36
  return;
37
37
  const url = svg ? new URL(href, document.baseURI) : new URL(href);
38
38
  // Skip non-http(s) schemes (blob:, mailto:, tel:, data:, ...). blob: URLs
@@ -156,10 +156,7 @@ export function query(fn, name) {
156
156
  }
157
157
  let res = cached[1];
158
158
  if (intent !== "preload") {
159
- res =
160
- "then" in cached[1]
161
- ? cached[1].then(handleResponse(false), handleResponse(true))
162
- : handleResponse(false)(cached[1]);
159
+ res = settle(res);
163
160
  !isServer && intent === "navigate" && cached[4][1](cached[0]); // update version
164
161
  }
165
162
  inPreloadFn && "then" in res && res.catch(() => { });
@@ -225,12 +222,8 @@ export function query(fn, name) {
225
222
  if (e && e.router.dataOnly)
226
223
  return (e.router.data[key] = res);
227
224
  }
228
- if (intent !== "preload") {
229
- res =
230
- "then" in res
231
- ? res.then(handleResponse(false), handleResponse(true))
232
- : handleResponse(false)(res);
233
- }
225
+ if (intent !== "preload")
226
+ res = settle(res);
234
227
  inPreloadFn && "then" in res && res.catch(() => { });
235
228
  // serialize on server
236
229
  if (isServer &&
@@ -241,6 +234,12 @@ export function query(fn, name) {
241
234
  (!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
242
235
  }
243
236
  return res;
237
+ // a settled read: the response handling applied to the value or the promise
238
+ function settle(v) {
239
+ return "then" in v
240
+ ? v.then(handleResponse(false), handleResponse(true))
241
+ : handleResponse(false)(v);
242
+ }
244
243
  function handleResponse(error) {
245
244
  return async (v) => {
246
245
  let enveloped;
package/dist/fs.d.ts CHANGED
@@ -35,7 +35,7 @@ export type FileRouteConfig<S extends string = string, T = unknown, F = undefine
35
35
  * `matchFilters` are validated against the pattern's params, and their
36
36
  * types (and any `search` schema) flow through the manifest into `paths`.
37
37
  */
38
- export declare function defineFileRoute<S extends string, T = unknown, const F = DefinedRouteFilters<S>, Sch extends StandardSchemaV1<any, any> | undefined = undefined>(path: S, config: {
38
+ export declare function defineFileRoute<S extends string, T = unknown, const F = DefinedRouteFilters<S>, Sch extends StandardSchemaV1<any, any> | undefined = undefined>(path: S, config?: {
39
39
  matchFilters?: (F & ValidFilters<F, S>) | undefined;
40
40
  preload?: ((args: RoutePreloadFuncArgs<RouteParams<S>>) => T) | undefined;
41
41
  /** Standard Schema validator for this route's search params; its input type flows into the typed path proxy. */
package/dist/fs.js CHANGED
@@ -20,7 +20,7 @@ import * as server from "./fsServer.js";
20
20
  * `matchFilters` are validated against the pattern's params, and their
21
21
  * types (and any `search` schema) flow through the manifest into `paths`.
22
22
  */
23
- export function defineFileRoute(path, config) {
23
+ export function defineFileRoute(path, config = {}) {
24
24
  return config;
25
25
  }
26
26
  /**
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getOwner, runWithOwner, createMemo, createRenderEffect, onCleanup, untrack, createContext, createSignal, useContext, NotReadyError, isPending, DEV, latest, createComponent, createRoot, Show, createEffect, sharedConfig, OBSERVE, onSettled, getObserver, $TRACK, action as action$1 } from 'solid-js';
1
+ import { getOwner, runWithOwner, createMemo, createRenderEffect, onCleanup, untrack, createContext, createSignal, useContext, NotReadyError, DEV, isPending, latest, createComponent, createRoot, Show, createEffect, sharedConfig, OBSERVE, onSettled, getObserver, $TRACK, action as action$1 } from 'solid-js';
2
2
  import { registerElementClaim, delegateEvents, isServer, getRequestEvent, hasFlashCookie, clearFlashCookie, createComponent as createComponent$1, memo, dynamic, isServerFunction, getServerFunctionMetadata, getServerFunctionRPC, isResponseEnvelope, REVALIDATE_HEADER } from '@solidjs/web';
3
3
  import { REDIRECT_HEADER, decodeRedirectHeaderValue, subscribeFlightData, decodeResponsePayload, parseServerFunctionActionUrl, createServerReference } from '@solidjs/web/server-functions';
4
4
 
@@ -72,13 +72,13 @@ function createMatcher(path, partial, matchFilters) {
72
72
  path: len ? "" : "/",
73
73
  params: {}
74
74
  };
75
- const matchFilter = s => matchFilters === undefined ? undefined : matchFilters[s];
75
+ const filters = matchFilters;
76
76
  for (let i = 0; i < len; i++) {
77
77
  const segment = segments[i];
78
78
  const dynamic = segment[0] === ":";
79
79
  const locSegment = dynamic ? locSegments[i] : locSegments[i].toLowerCase();
80
80
  const key = dynamic ? segment.slice(1) : segment.toLowerCase();
81
- if (dynamic && matchSegment(locSegment, matchFilter(key))) {
81
+ if (dynamic && matchSegment(locSegment, filters?.[key])) {
82
82
  match.params[key] = locSegment;
83
83
  } else if (dynamic || !matchSegment(locSegment, key)) {
84
84
  return null;
@@ -87,7 +87,7 @@ function createMatcher(path, partial, matchFilters) {
87
87
  }
88
88
  if (splat) {
89
89
  const remainder = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
90
- if (matchSegment(remainder, matchFilter(splat))) {
90
+ if (matchSegment(remainder, filters?.[splat])) {
91
91
  match.params[splat] = remainder;
92
92
  } else {
93
93
  return null;
@@ -96,21 +96,9 @@ function createMatcher(path, partial, matchFilters) {
96
96
  return match;
97
97
  };
98
98
  }
99
- function matchSegment(input, filter) {
100
- const isEqual = s => s === input;
101
- if (filter === undefined) {
102
- return true;
103
- } else if (typeof filter === "string") {
104
- return isEqual(filter);
105
- } else if (typeof filter === "function") {
106
- return filter(input);
107
- } else if (Array.isArray(filter)) {
108
- return filter.some(isEqual);
109
- } else if (filter instanceof RegExp) {
110
- return filter.test(input);
111
- }
112
- return false;
113
- }
99
+
100
+ // a string filter is the segment itself; anything else not listed never matches
101
+ const matchSegment = (input, filter) => filter === undefined || (typeof filter === "function" ? filter(input) : Array.isArray(filter) ? filter.includes(input) : filter instanceof RegExp ? filter.test(input) : filter === input);
114
102
  function scoreRoute(route) {
115
103
  const [pattern, splat] = route.pattern.split("/*", 2);
116
104
  const segments = pattern.split("/").filter(Boolean);
@@ -340,7 +328,7 @@ function setupNativeEvents({
340
328
  const target = svg ? a.target.baseVal : a.target;
341
329
  if (target || !href && !a.hasAttribute("state")) return;
342
330
  const rel = (a.getAttribute("rel") || "").split(/\s+/);
343
- if (a.hasAttribute("download") || rel && rel.includes("external")) return;
331
+ if (a.hasAttribute("download") || rel.includes("external")) return;
344
332
  const url = svg ? new URL(href, document.baseURI) : new URL(href);
345
333
  // Skip non-http(s) schemes (blob:, mailto:, tel:, data:, ...). blob: URLs
346
334
  // inherit the page origin, so the origin check below won't reject them. #382
@@ -514,8 +502,8 @@ function createPathsProxy(renderPath = p => p, base = "") {
514
502
  };
515
503
  return new Proxy(build, {
516
504
  get(_, prop) {
517
- if (prop === "toString") return () => toHref(pathname);
518
- if (typeof prop === "symbol") return prop === Symbol.toPrimitive ? () => toHref(pathname) : prop === HREF ? pathname || "/" : undefined;
505
+ if (prop === "toString" || prop === Symbol.toPrimitive) return () => toHref(pathname);
506
+ if (typeof prop === "symbol") return prop === HREF ? pathname || "/" : undefined;
519
507
  return node(`${pathname}/${prop}`);
520
508
  }
521
509
  });
@@ -553,7 +541,6 @@ function serverRouteOf(component) {
553
541
  */
554
542
  function serverRouteArgs(route, params, query) {
555
543
  const schema = route.key?.search;
556
- let search;
557
544
  if (schema) {
558
545
  const raw = {
559
546
  ...query
@@ -561,13 +548,22 @@ function serverRouteArgs(route, params, query) {
561
548
  const outcome = validateSearch(schema, raw);
562
549
  // Issues leave the raw values in place, matching useSearchParams: search
563
550
  // strings are user input, so defaults belong in the schema itself.
564
- search = outcome.issues ? raw : outcome.value;
551
+ return {
552
+ params: {
553
+ ...params
554
+ },
555
+ search: outcome.issues ? raw : outcome.value
556
+ };
565
557
  }
558
+ // No schema: leave the key out rather than carrying `search: undefined` —
559
+ // the args are sent as plain JSON by default and the runtime's JSON-safety
560
+ // check rejects explicit `undefined` (#615). Destructuring on the server
561
+ // reads the same either way, and the args-memo equality treats an absent
562
+ // key and undefined alike.
566
563
  return {
567
564
  params: {
568
565
  ...params
569
- },
570
- search
566
+ }
571
567
  };
572
568
  }
573
569
  const shallowEqual = (a, b) => a === b || !!a && !!b && typeof a === "object" && typeof b === "object" && Object.keys(a).length === Object.keys(b).length && Object.keys(a).every(k => a[k] === b[k]);
@@ -1015,9 +1011,7 @@ function createRoutes(routeDef, base = "") {
1015
1011
  for (const expandedPath of expandOptionals(originalPath)) {
1016
1012
  const path = joinPaths(base, expandedPath);
1017
1013
  let pattern = isLeaf ? path : path.split("/*", 1)[0];
1018
- pattern = pattern.split("/").map(s => {
1019
- return s.startsWith(":") || s.startsWith("*") ? s : encodeSegment(s);
1020
- }).join("/");
1014
+ pattern = pattern.split("/").map(s => /^[:*]/.test(s) ? s : encodeSegment(s)).join("/");
1021
1015
  acc.push({
1022
1016
  ...shared,
1023
1017
  originalPath,
@@ -1108,13 +1102,12 @@ function mergeParams(matches) {
1108
1102
  return params;
1109
1103
  }
1110
1104
  function createLocation(path, state, queryWrapper) {
1111
- const origin = new URL(mockBase);
1112
- const url = createMemo((prev = origin) => {
1105
+ const url = createMemo((prev = new URL(mockBase)) => {
1113
1106
  const path_ = path();
1114
1107
  try {
1115
1108
  // anchor rooted paths against the origin explicitly - a path with
1116
1109
  // doubled leading slashes would otherwise parse as protocol-relative
1117
- return new URL(path_[0] === "/" ? mockBase + path_ : path_, origin);
1110
+ return new URL(path_[0] === "/" ? mockBase + path_ : path_, mockBase);
1118
1111
  } catch (err) {
1119
1112
  DEV && console.error(`Invalid path ${path_}`);
1120
1113
  return prev;
@@ -1125,7 +1118,6 @@ function createLocation(path, state, queryWrapper) {
1125
1118
  const pathname = createMemo(() => url().pathname);
1126
1119
  const search = createMemo(() => url().search);
1127
1120
  const hash = createMemo(() => url().hash);
1128
- const key = () => "";
1129
1121
  const queryFn = createMemo(() => extractSearchParams(url()));
1130
1122
  return {
1131
1123
  get pathname() {
@@ -1141,7 +1133,7 @@ function createLocation(path, state, queryWrapper) {
1141
1133
  return state();
1142
1134
  },
1143
1135
  get key() {
1144
- return key();
1136
+ return "";
1145
1137
  },
1146
1138
  query: queryWrapper ? queryWrapper(queryFn) : createMemoObject(queryFn)
1147
1139
  };
@@ -1307,7 +1299,7 @@ function createRouterContext(integration, branches, getContext, options = {}) {
1307
1299
  read = runWithOwner(routerOwner, () => createMemo(() => {
1308
1300
  const result = resolveLazySubtree(record);
1309
1301
  return result instanceof Promise ? result.then(() => undefined) : undefined;
1310
- }, {
1302
+ }, DEV && {
1311
1303
  name: "lazyRoutes"
1312
1304
  }));
1313
1305
  lazyReaders.set(record, read);
@@ -1340,7 +1332,7 @@ function createRouterContext(integration, branches, getContext, options = {}) {
1340
1332
  }
1341
1333
  }
1342
1334
  return m;
1343
- }, {
1335
+ }, DEV && {
1344
1336
  name: "matches"
1345
1337
  });
1346
1338
  const routingPending = createMemo(() => isPending(() => {
@@ -1351,7 +1343,7 @@ function createRouterContext(integration, branches, getContext, options = {}) {
1351
1343
  }
1352
1344
  location.search;
1353
1345
  location.hash;
1354
- }), {
1346
+ }), DEV && {
1355
1347
  name: "routingPending"
1356
1348
  });
1357
1349
  const isRouting = () => routingPending() || isPending(source);
@@ -1557,11 +1549,11 @@ function createRouterContext(integration, branches, getContext, options = {}) {
1557
1549
  route,
1558
1550
  params
1559
1551
  } = next[match];
1560
- route.component && route.component.preload && route.component.preload();
1561
1552
  const {
1562
1553
  preload,
1563
1554
  component
1564
1555
  } = route;
1556
+ component?.preload?.();
1565
1557
  const now = current && current[match];
1566
1558
  const unchanged = now && now.route.key === route.key && serverRouteArgsEqual(inputs(params, query, url.search, route), inputs(now.params, location.query, location.search, route));
1567
1559
  inPreloadFn = true;
@@ -1625,7 +1617,7 @@ function createRouteContext(router, parent, outlet, match, matches = () => [matc
1625
1617
  // its last valid value while this route is being torn down, so outgoing
1626
1618
  // components and preloads never observe another route's params.
1627
1619
  const params = wrapParams(() => mergeParams(matches()));
1628
- component && component.preload && component.preload();
1620
+ component?.preload?.();
1629
1621
  inPreloadFn = true;
1630
1622
  const data = preload ? preload({
1631
1623
  params,
@@ -1752,9 +1744,7 @@ function Routes(props) {
1752
1744
  next[i] = prev[i];
1753
1745
  } else {
1754
1746
  equal = false;
1755
- if (disposers[i]) {
1756
- disposers[i]();
1757
- }
1747
+ disposers[i]?.();
1758
1748
  runWithOwner(owner, () => createRoot(dispose => {
1759
1749
  disposers[i] = dispose;
1760
1750
  const routeKey = nextMatch.route.key;
@@ -1843,13 +1833,14 @@ function bindEvent(target, type, handler) {
1843
1833
 
1844
1834
  let depth;
1845
1835
  function saveCurrentDepth() {
1846
- if (!window.history.state || window.history.state._depth == null) {
1847
- window.history.replaceState({
1848
- ...window.history.state,
1849
- _depth: window.history.length - 1
1836
+ const h = window.history;
1837
+ if (!h.state || h.state._depth == null) {
1838
+ h.replaceState({
1839
+ ...h.state,
1840
+ _depth: h.length - 1
1850
1841
  }, "");
1851
1842
  }
1852
- depth = window.history.state._depth;
1843
+ depth = h.state?._depth ?? h.length - 1;
1853
1844
  }
1854
1845
  function keepDepth(state) {
1855
1846
  return {
@@ -1893,11 +1884,10 @@ function scrollToHash(hash, fallbackTop) {
1893
1884
 
1894
1885
  function browserHistory() {
1895
1886
  const getSource = () => {
1896
- const url = window.location.pathname + window.location.search;
1897
- const state = window.history.state && window.history.state._depth && Object.keys(window.history.state).length === 1 ? undefined : window.history.state;
1887
+ const s = window.history.state;
1898
1888
  return {
1899
- value: url + window.location.hash,
1900
- state
1889
+ value: window.location.pathname + window.location.search + window.location.hash,
1890
+ state: s && s._depth && Object.keys(s).length === 1 ? undefined : s
1901
1891
  };
1902
1892
  };
1903
1893
  const beforeLeave = {};
@@ -2125,7 +2115,7 @@ function createScrollRestoration() {
2125
2115
  // reload/back_forward document loads land on an existing entry (a fresh
2126
2116
  // navigation starts a new one and belongs at the top); the effect's
2127
2117
  // initial run performs the restore after first render
2128
- const [nav] = performance.getEntriesByType && performance.getEntriesByType("navigation");
2118
+ const [nav] = performance.getEntriesByType?.("navigation");
2129
2119
  if (nav && nav.type !== "navigate") pending = depth();
2130
2120
  }
2131
2121
  };
@@ -2424,11 +2414,10 @@ function createRouter(config) {
2424
2414
  }));
2425
2415
  },
2426
2416
  keysFor(url) {
2427
- const pathname = typeof url === "string" ? new URL(url, mockBase).pathname : url[HREF];
2428
2417
  const keys = [];
2429
2418
  for (const {
2430
2419
  route
2431
- } of matchPath(pathname)) {
2420
+ } of matchPath(pathnameOf(url))) {
2432
2421
  const key = serverRouteOf(route.component)?.call?.key;
2433
2422
  typeof key === "string" && key && !keys.includes(key) && keys.push(key);
2434
2423
  }
@@ -2743,7 +2732,7 @@ function query(fn, name) {
2743
2732
  }
2744
2733
  let res = cached[1];
2745
2734
  if (intent !== "preload") {
2746
- res = "then" in cached[1] ? cached[1].then(handleResponse(false), handleResponse(true)) : handleResponse(false)(cached[1]);
2735
+ res = settle(res);
2747
2736
  !isServer && intent === "navigate" && cached[4][1](cached[0]); // update version
2748
2737
  }
2749
2738
  inPreloadFn && "then" in res && res.catch(() => {});
@@ -2803,9 +2792,7 @@ function query(fn, name) {
2803
2792
  const e = getRequestEvent();
2804
2793
  if (e && e.router.dataOnly) return e.router.data[key] = res;
2805
2794
  }
2806
- if (intent !== "preload") {
2807
- res = "then" in res ? res.then(handleResponse(false), handleResponse(true)) : handleResponse(false)(res);
2808
- }
2795
+ if (intent !== "preload") res = settle(res);
2809
2796
  inPreloadFn && "then" in res && res.catch(() => {});
2810
2797
  // serialize on server
2811
2798
  if (isServer && sharedConfig.context && sharedConfig.context.async && !sharedConfig.context.noHydrate) {
@@ -2813,6 +2800,11 @@ function query(fn, name) {
2813
2800
  (!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
2814
2801
  }
2815
2802
  return res;
2803
+
2804
+ // a settled read: the response handling applied to the value or the promise
2805
+ function settle(v) {
2806
+ return "then" in v ? v.then(handleResponse(false), handleResponse(true)) : handleResponse(false)(v);
2807
+ }
2816
2808
  function handleResponse(error) {
2817
2809
  return async v => {
2818
2810
  let enveloped;
@@ -2989,7 +2981,8 @@ const actions = /* #__PURE__ */new Map();
2989
2981
  */
2990
2982
  function handleFormAction(evt, router, actionBase) {
2991
2983
  if (evt.defaultPrevented) return;
2992
- let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction") ? evt.submitter.getAttribute("formaction") : evt.target.getAttribute("action");
2984
+ const form = evt.target;
2985
+ let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction") ? evt.submitter.getAttribute("formaction") : form.getAttribute("action");
2993
2986
  if (!actionRef) return;
2994
2987
  const serverAction = !actionRef.startsWith("https://action/");
2995
2988
  if (serverAction) {
@@ -2998,7 +2991,7 @@ function handleFormAction(evt, router, actionBase) {
2998
2991
  actionRef = router.parsePath(url.pathname + url.search);
2999
2992
  if (!actionRef.startsWith(actionBase)) return;
3000
2993
  }
3001
- if (evt.target.method.toUpperCase() !== "POST") throw new Error("Only POST forms are supported for Actions");
2994
+ if (form.method.toUpperCase() !== "POST") throw new Error("Only POST forms are supported for Actions");
3002
2995
  // A registry miss on a server-action url is a direct bind whose module
3003
2996
  // never loaded client-side (server components): the url is self-describing
3004
2997
  // (the id in the path, bound `?args` in the query), so a generic invocation
@@ -3009,11 +3002,11 @@ function handleFormAction(evt, router, actionBase) {
3009
3002
  const handler = actions.get(actionRef) || serverAction && createServerFormAction(actionRef);
3010
3003
  if (handler) {
3011
3004
  evt.preventDefault();
3012
- const data = new FormData(evt.target, evt.submitter);
3005
+ const data = new FormData(form, evt.submitter);
3013
3006
  handler.call({
3014
3007
  r: router,
3015
- f: evt.target
3016
- }, evt.target.enctype === "multipart/form-data" ? data : new URLSearchParams(data));
3008
+ f: form
3009
+ }, form.enctype === "multipart/form-data" ? data : new URLSearchParams(data));
3017
3010
  }
3018
3011
  }
3019
3012
 
@@ -3120,23 +3113,21 @@ function actionImpl(fn, options = {}) {
3120
3113
  const runMutation = () => fn(...variables);
3121
3114
  const run = action$1(async function* (context) {
3122
3115
  context.optimistic?.();
3116
+ let value;
3117
+ let error = false;
3123
3118
  try {
3124
- const value = await context.call();
3125
- yield;
3126
- return {
3127
- error: false,
3128
- value
3129
- };
3130
- } catch (error) {
3131
- yield;
3132
- return {
3133
- error: true,
3134
- value: error
3135
- };
3119
+ value = await context.call();
3120
+ } catch (e) {
3121
+ value = e;
3122
+ error = true;
3136
3123
  }
3124
+ const read = await readResponse(value, error);
3125
+ yield;
3126
+ // Apply inside the transition so the default revalidation's refetch and
3127
+ // the release of the caller's optimistic writes commit as one frame (#619).
3128
+ return applyResponse(read, router.navigatorFactory(), flightApplications !== flightApplicationsBefore);
3137
3129
  });
3138
3130
  form && setFormBusy(form, 1);
3139
- let settled;
3140
3131
  let response;
3141
3132
  // The transport consumer is awaited before a single-flight mutation
3142
3133
  // resolves, so a counter delta over the call tells whether this action's
@@ -3146,13 +3137,12 @@ function actionImpl(fn, options = {}) {
3146
3137
  // which misses every response the server returned without flight data.
3147
3138
  const flightApplicationsBefore = flightApplications;
3148
3139
  try {
3149
- settled = await settleActionResult(run({
3140
+ response = await settleActionResult(run({
3150
3141
  call: runMutation,
3151
3142
  optimistic: submitHooks.size ? () => {
3152
3143
  for (const hook of submitHooks.values()) hook(...variables);
3153
3144
  } : undefined
3154
3145
  }));
3155
- response = await handleResponse(settled.value, settled.error, router.navigatorFactory(), flightApplications !== flightApplicationsBefore);
3156
3146
  } finally {
3157
3147
  form && setFormBusy(form, -1);
3158
3148
  }
@@ -3160,8 +3150,8 @@ function actionImpl(fn, options = {}) {
3160
3150
  submission = {
3161
3151
  input: variables,
3162
3152
  url,
3163
- result: response && response.data,
3164
- error: response && response.error,
3153
+ result: response?.data,
3154
+ error: response?.error,
3165
3155
  clear() {
3166
3156
  router.submissions[1](entries => entries.filter(entry => entry !== submission));
3167
3157
  },
@@ -3207,8 +3197,7 @@ function toAction(invoke, url, boundArgs = [], base = url, submitHooks = new Map
3207
3197
  fn.with = function (...args) {
3208
3198
  const uri = new URL(url, mockBase);
3209
3199
  uri.searchParams.set("args", hashKey(args));
3210
- const next = toAction(invoke, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search, [...boundArgs, ...args], base, submitHooks, settledHooks);
3211
- return next;
3200
+ return toAction(invoke, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search, [...boundArgs, ...args], base, submitHooks, settledHooks);
3212
3201
  };
3213
3202
  fn.onSubmit = function (hook) {
3214
3203
  const id = Symbol("actionOnSubmitHook");
@@ -3327,8 +3316,10 @@ function applyResponseMetadata(metadata, navigate, flightData) {
3327
3316
  cacheKeyOp(keys, entry => entry[0] = 0);
3328
3317
  // set cache — and fan the payload out to other keyed stores (live query
3329
3318
  // channels adopt delivered values instead of reconnecting)
3330
- flightData && Object.keys(flightData).forEach(k => query.set(k, flightData[k]));
3331
- flightData && deliverFlightData(flightData);
3319
+ if (flightData) {
3320
+ Object.keys(flightData).forEach(k => query.set(k, flightData[k]));
3321
+ deliverFlightData(flightData);
3322
+ }
3332
3323
  // trigger revalidation inside the same transition as the navigation, so
3333
3324
  // the redirect commits atomically with fresh data: surviving consumers
3334
3325
  // (shared layouts) the flight payload didn't seed refetch and hold the
@@ -3336,7 +3327,7 @@ function applyResponseMetadata(metadata, navigate, flightData) {
3336
3327
  // are fresh again by now, so the sweep re-reads them from cache.
3337
3328
  revalidate(keys, false);
3338
3329
  }
3339
- async function handleResponse(response, error, navigate, metadataHandled) {
3330
+ async function readResponse(response, error) {
3340
3331
  let data;
3341
3332
  let flightData;
3342
3333
  let metadata;
@@ -3359,6 +3350,19 @@ async function handleResponse(response, error, navigate, metadataHandled) {
3359
3350
  } else if (error) return {
3360
3351
  error: response
3361
3352
  };else data = response;
3353
+ return {
3354
+ data,
3355
+ flightData,
3356
+ metadata
3357
+ };
3358
+ }
3359
+ function applyResponse(read, navigate, metadataHandled) {
3360
+ if ("error" in read) return read;
3361
+ const {
3362
+ data,
3363
+ flightData,
3364
+ metadata
3365
+ } = read;
3362
3366
  // The transport consumer applies metadata before returning a server
3363
3367
  // function's unwrapped value. Do not treat that value as a second plain
3364
3368
  // action response and invalidate the freshly seeded query cache again.
package/dist/paths.js CHANGED
@@ -37,14 +37,10 @@ export function createPathsProxy(renderPath = p => p, base = "") {
37
37
  };
38
38
  return new Proxy(build, {
39
39
  get(_, prop) {
40
- if (prop === "toString")
40
+ if (prop === "toString" || prop === Symbol.toPrimitive)
41
41
  return () => toHref(pathname);
42
42
  if (typeof prop === "symbol")
43
- return prop === Symbol.toPrimitive
44
- ? () => toHref(pathname)
45
- : prop === HREF
46
- ? pathname || "/"
47
- : undefined;
43
+ return prop === HREF ? pathname || "/" : undefined;
48
44
  return node(`${pathname}/${prop}`);
49
45
  }
50
46
  });
@@ -74,9 +74,7 @@ export function Routes(props) {
74
74
  }
75
75
  else {
76
76
  equal = false;
77
- if (disposers[i]) {
78
- disposers[i]();
79
- }
77
+ disposers[i]?.();
80
78
  runWithOwner(owner, () => createRoot(dispose => {
81
79
  disposers[i] = dispose;
82
80
  const routeKey = nextMatch.route.key;
@@ -247,9 +247,8 @@ export function createRouter(config) {
247
247
  }));
248
248
  },
249
249
  keysFor(url) {
250
- const pathname = typeof url === "string" ? new URL(url, mockBase).pathname : url[HREF];
251
250
  const keys = [];
252
- for (const { route } of matchPath(pathname)) {
251
+ for (const { route } of matchPath(pathnameOf(url))) {
253
252
  const key = serverRouteOf(route.component)?.call?.key;
254
253
  typeof key === "string" && key && !keys.includes(key) && keys.push(key);
255
254
  }
@@ -9,10 +9,11 @@ export function bindEvent(target, type, handler) {
9
9
  // guard machinery itself lives behind the lazy `beforeLeave` slot.
10
10
  let depth;
11
11
  export function saveCurrentDepth() {
12
- if (!window.history.state || window.history.state._depth == null) {
13
- window.history.replaceState({ ...window.history.state, _depth: window.history.length - 1 }, "");
12
+ const h = window.history;
13
+ if (!h.state || h.state._depth == null) {
14
+ h.replaceState({ ...h.state, _depth: h.length - 1 }, "");
14
15
  }
15
- depth = window.history.state._depth;
16
+ depth = h.state?._depth ?? h.length - 1;
16
17
  }
17
18
  function keepDepth(state) {
18
19
  return {
@@ -50,15 +51,10 @@ function scrollToHash(hash, fallbackTop) {
50
51
  }
51
52
  export function browserHistory() {
52
53
  const getSource = () => {
53
- const url = window.location.pathname + window.location.search;
54
- const state = window.history.state &&
55
- window.history.state._depth &&
56
- Object.keys(window.history.state).length === 1
57
- ? undefined
58
- : window.history.state;
54
+ const s = window.history.state;
59
55
  return {
60
- value: url + window.location.hash,
61
- state
56
+ value: window.location.pathname + window.location.search + window.location.hash,
57
+ state: s && s._depth && Object.keys(s).length === 1 ? undefined : s
62
58
  };
63
59
  };
64
60
  const beforeLeave = {};
@@ -105,8 +105,7 @@ export function createScrollRestoration() {
105
105
  // reload/back_forward document loads land on an existing entry (a fresh
106
106
  // navigation starts a new one and belongs at the top); the effect's
107
107
  // initial run performs the restore after first render
108
- const [nav] = (performance.getEntriesByType &&
109
- performance.getEntriesByType("navigation"));
108
+ const [nav] = performance.getEntriesByType?.("navigation");
110
109
  if (nav && nav.type !== "navigate")
111
110
  pending = depth();
112
111
  }
package/dist/routing.js CHANGED
@@ -391,9 +391,7 @@ export function createRoutes(routeDef, base = "") {
391
391
  let pattern = isLeaf ? path : path.split("/*", 1)[0];
392
392
  pattern = pattern
393
393
  .split("/")
394
- .map((s) => {
395
- return s.startsWith(":") || s.startsWith("*") ? s : encodeSegment(s);
396
- })
394
+ .map(s => (/^[:*]/.test(s) ? s : encodeSegment(s)))
397
395
  .join("/");
398
396
  acc.push({
399
397
  ...shared,
@@ -487,13 +485,12 @@ export function mergeParams(matches) {
487
485
  return params;
488
486
  }
489
487
  function createLocation(path, state, queryWrapper) {
490
- const origin = new URL(mockBase);
491
- const url = createMemo((prev = origin) => {
488
+ const url = createMemo((prev = new URL(mockBase)) => {
492
489
  const path_ = path();
493
490
  try {
494
491
  // anchor rooted paths against the origin explicitly - a path with
495
492
  // doubled leading slashes would otherwise parse as protocol-relative
496
- return new URL(path_[0] === "/" ? mockBase + path_ : path_, origin);
493
+ return new URL(path_[0] === "/" ? mockBase + path_ : path_, mockBase);
497
494
  }
498
495
  catch (err) {
499
496
  DEV && console.error(`Invalid path ${path_}`);
@@ -505,7 +502,6 @@ function createLocation(path, state, queryWrapper) {
505
502
  const pathname = createMemo(() => url().pathname);
506
503
  const search = createMemo(() => url().search);
507
504
  const hash = createMemo(() => url().hash);
508
- const key = () => "";
509
505
  const queryFn = createMemo(() => extractSearchParams(url()));
510
506
  return {
511
507
  get pathname() {
@@ -521,7 +517,7 @@ function createLocation(path, state, queryWrapper) {
521
517
  return state();
522
518
  },
523
519
  get key() {
524
- return key();
520
+ return "";
525
521
  },
526
522
  query: queryWrapper ? queryWrapper(queryFn) : createMemoObject(queryFn)
527
523
  };
@@ -660,7 +656,7 @@ export function createRouterContext(integration, branches, getContext, options =
660
656
  read = runWithOwner(routerOwner, () => createMemo(() => {
661
657
  const result = resolveLazySubtree(record);
662
658
  return result instanceof Promise ? result.then(() => undefined) : undefined;
663
- }, { name: "lazyRoutes" }));
659
+ }, DEV && { name: "lazyRoutes" }));
664
660
  lazyReaders.set(record, read);
665
661
  }
666
662
  return read();
@@ -695,7 +691,7 @@ export function createRouterContext(integration, branches, getContext, options =
695
691
  }
696
692
  }
697
693
  return m;
698
- }, { name: "matches" });
694
+ }, DEV && { name: "matches" });
699
695
  const routingPending = createMemo(() => isPending(() => {
700
696
  try {
701
697
  matches();
@@ -706,7 +702,7 @@ export function createRouterContext(integration, branches, getContext, options =
706
702
  }
707
703
  location.search;
708
704
  location.hash;
709
- }), { name: "routingPending" });
705
+ }), DEV && { name: "routingPending" });
710
706
  const isRouting = () => routingPending() || isPending(source);
711
707
  const transitionIntent = () => {
712
708
  if (!isPending(source))
@@ -912,10 +908,8 @@ export function createRouterContext(integration, branches, getContext, options =
912
908
  preloadIntent = "preload";
913
909
  for (let match in next) {
914
910
  const { route, params } = next[match];
915
- route.component &&
916
- route.component.preload &&
917
- route.component.preload();
918
911
  const { preload, component } = route;
912
+ component?.preload?.();
919
913
  const now = current && current[match];
920
914
  const unchanged = now &&
921
915
  now.route.key === route.key &&
@@ -978,9 +972,7 @@ export function createRouteContext(router, parent, outlet, match, matches = () =
978
972
  // its last valid value while this route is being torn down, so outgoing
979
973
  // components and preloads never observe another route's params.
980
974
  const params = wrapParams(() => mergeParams(matches()));
981
- component &&
982
- component.preload &&
983
- component.preload();
975
+ component?.preload?.();
984
976
  inPreloadFn = true;
985
977
  const data = preload
986
978
  ? preload({ params, location, intent: router.intent?.() || "initial" })
@@ -12,15 +12,19 @@ export function serverRouteOf(component) {
12
12
  */
13
13
  export function serverRouteArgs(route, params, query) {
14
14
  const schema = route.key?.search;
15
- let search;
16
15
  if (schema) {
17
16
  const raw = { ...query };
18
17
  const outcome = validateSearch(schema, raw);
19
18
  // Issues leave the raw values in place, matching useSearchParams: search
20
19
  // strings are user input, so defaults belong in the schema itself.
21
- search = outcome.issues ? raw : outcome.value;
20
+ return { params: { ...params }, search: outcome.issues ? raw : outcome.value };
22
21
  }
23
- return { params: { ...params }, search };
22
+ // No schema: leave the key out rather than carrying `search: undefined` —
23
+ // the args are sent as plain JSON by default and the runtime's JSON-safety
24
+ // check rejects explicit `undefined` (#615). Destructuring on the server
25
+ // reads the same either way, and the args-memo equality treats an absent
26
+ // key and undefined alike.
27
+ return { params: { ...params } };
24
28
  }
25
29
  const shallowEqual = (a, b) => a === b ||
26
30
  (!!a &&
package/dist/types.d.ts CHANGED
@@ -144,12 +144,21 @@ export type RouteSectionComponent<T = unknown, P extends Params = Params> = Comp
144
144
  /**
145
145
  * What a server component route is called with (experimental): the params
146
146
  * the route's pattern (and its ancestors') declares, plus — only when the
147
- * route declares a `search` schema — that schema's validated output.
147
+ * route declares a `search` schema — that schema's validated output. Without
148
+ * a schema the `search` key is ABSENT, not `undefined`: the args travel as
149
+ * plain JSON by default, and the runtime's JSON-safety check rejects
150
+ * explicit `undefined` (#615). The type mirrors that — optional exactly when
151
+ * the search type admits undefined, required when a schema narrows it.
148
152
  */
149
- export interface ServerRouteArgs<P extends Params | TypedRouteConfig = Params, S = undefined> {
153
+ export type ServerRouteArgs<P extends Params | TypedRouteConfig = Params, S = undefined> = {
150
154
  params: ServerRouteParams<P>;
151
- search: P extends TypedRouteConfig ? SearchOutputOf<P> : S;
152
- }
155
+ } & (undefined extends ServerRouteSearch<P, S> ? {
156
+ search?: ServerRouteSearch<P, S>;
157
+ } : {
158
+ search: ServerRouteSearch<P, S>;
159
+ });
160
+ /** The search half of {@link ServerRouteArgs}: schema output when declared. */
161
+ export type ServerRouteSearch<P, S> = P extends TypedRouteConfig ? SearchOutputOf<P> : S;
153
162
  /**
154
163
  * `ServerRouteArgs<typeof route>`: a `defineFileRoute` config is a witness —
155
164
  * params from its pattern, search from its schema — the way
package/dist/utils.js CHANGED
@@ -78,13 +78,13 @@ export function createMatcher(path, partial, matchFilters) {
78
78
  path: len ? "" : "/",
79
79
  params: {}
80
80
  };
81
- const matchFilter = (s) => matchFilters === undefined ? undefined : matchFilters[s];
81
+ const filters = matchFilters;
82
82
  for (let i = 0; i < len; i++) {
83
83
  const segment = segments[i];
84
84
  const dynamic = segment[0] === ":";
85
85
  const locSegment = dynamic ? locSegments[i] : locSegments[i].toLowerCase();
86
86
  const key = dynamic ? segment.slice(1) : segment.toLowerCase();
87
- if (dynamic && matchSegment(locSegment, matchFilter(key))) {
87
+ if (dynamic && matchSegment(locSegment, filters?.[key])) {
88
88
  match.params[key] = locSegment;
89
89
  }
90
90
  else if (dynamic || !matchSegment(locSegment, key)) {
@@ -94,7 +94,7 @@ export function createMatcher(path, partial, matchFilters) {
94
94
  }
95
95
  if (splat) {
96
96
  const remainder = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
97
- if (matchSegment(remainder, matchFilter(splat))) {
97
+ if (matchSegment(remainder, filters?.[splat])) {
98
98
  match.params[splat] = remainder;
99
99
  }
100
100
  else {
@@ -104,25 +104,15 @@ export function createMatcher(path, partial, matchFilters) {
104
104
  return match;
105
105
  };
106
106
  }
107
- function matchSegment(input, filter) {
108
- const isEqual = (s) => s === input;
109
- if (filter === undefined) {
110
- return true;
111
- }
112
- else if (typeof filter === "string") {
113
- return isEqual(filter);
114
- }
115
- else if (typeof filter === "function") {
116
- return filter(input);
117
- }
118
- else if (Array.isArray(filter)) {
119
- return filter.some(isEqual);
120
- }
121
- else if (filter instanceof RegExp) {
122
- return filter.test(input);
123
- }
124
- return false;
125
- }
107
+ // a string filter is the segment itself; anything else not listed never matches
108
+ const matchSegment = (input, filter) => filter === undefined ||
109
+ (typeof filter === "function"
110
+ ? filter(input)
111
+ : Array.isArray(filter)
112
+ ? filter.includes(input)
113
+ : filter instanceof RegExp
114
+ ? filter.test(input)
115
+ : filter === input);
126
116
  export function scoreRoute(route) {
127
117
  const [pattern, splat] = route.pattern.split("/*", 2);
128
118
  const segments = pattern.split("/").filter(Boolean);
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "2.0.0-next.29",
9
+ "version": "2.0.0-next.31",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",
@@ -38,22 +38,22 @@
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.9",
41
+ "@solidjs/web": "^2.0.0-rc.11",
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.9",
48
+ "solid-js": "^2.0.0-rc.11",
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.9",
54
+ "@solidjs/web": "^2.0.0-rc.10",
55
55
  "filesystem-routing": ">=0.4.0",
56
- "solid-js": "^2.0.0-rc.9"
56
+ "solid-js": "^2.0.0-rc.10"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "filesystem-routing": {