@solidjs/router 2.0.0-next.16 → 2.0.0-next.18
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 +33 -1
- package/dist/data/action.js +4 -2
- package/dist/data/index.d.ts +1 -0
- package/dist/data/index.js +1 -0
- package/dist/data/liveQuery.d.ts +44 -0
- package/dist/data/liveQuery.js +377 -0
- package/dist/data/query.d.ts +6 -0
- package/dist/data/query.js +21 -1
- package/dist/fs.js +3 -1
- package/dist/index.js +496 -41
- package/dist/paths.d.ts +8 -0
- package/dist/paths.js +13 -1
- package/dist/routers/components.jsx +12 -8
- package/dist/routers/factory.jsx +4 -1
- package/dist/routing.d.ts +15 -3
- package/dist/routing.js +89 -17
- package/dist/types.d.ts +6 -0
- package/package.json +7 -7
package/dist/paths.d.ts
CHANGED
|
@@ -108,6 +108,14 @@ type MultiPathContrib<Ps extends readonly string[], Def, PAcc extends Params> =
|
|
|
108
108
|
export type RoutePaths<R extends readonly RouteDefinition[]> = number extends R["length"] ? any : PathEnd<DefaultSearchTypes, {}> & TuplePaths<R, {}>;
|
|
109
109
|
/** Extracts the params record a paths node binds, as runtime (string-valued) params. */
|
|
110
110
|
export type PathParamsOf<N> = N extends TypedPath<infer P> ? Flat<P> : Params;
|
|
111
|
+
/**
|
|
112
|
+
* The global Href brand (`@solidjs/web` reads the same registry symbol).
|
|
113
|
+
* On a paths node it holds the logical path — the routable pathname before
|
|
114
|
+
* `renderPath` decorates it for display (eg. hash mode's `#` prefix) — so
|
|
115
|
+
* `navigate()` and `redirect()` can route the node while `toString()` keeps
|
|
116
|
+
* yielding the display href for the DOM.
|
|
117
|
+
*/
|
|
118
|
+
export declare const HREF: unique symbol;
|
|
111
119
|
/**
|
|
112
120
|
* Creates the runtime path proxy. It is instance-scoped: `renderPath` comes
|
|
113
121
|
* from the router's history adapter (eg. hash routing prefixes `#`), and
|
package/dist/paths.js
CHANGED
|
@@ -5,6 +5,14 @@ export const int = ((s) => /^-?\d+$/.test(s));
|
|
|
5
5
|
// Runtime
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
const encodeParam = (value) => String(value).split("/").map(encodeURIComponent).join("/");
|
|
8
|
+
/**
|
|
9
|
+
* The global Href brand (`@solidjs/web` reads the same registry symbol).
|
|
10
|
+
* On a paths node it holds the logical path — the routable pathname before
|
|
11
|
+
* `renderPath` decorates it for display (eg. hash mode's `#` prefix) — so
|
|
12
|
+
* `navigate()` and `redirect()` can route the node while `toString()` keeps
|
|
13
|
+
* yielding the display href for the DOM.
|
|
14
|
+
*/
|
|
15
|
+
export const HREF = Symbol.for("solid.Href");
|
|
8
16
|
/**
|
|
9
17
|
* Creates the runtime path proxy. It is instance-scoped: `renderPath` comes
|
|
10
18
|
* from the router's history adapter (eg. hash routing prefixes `#`), and
|
|
@@ -32,7 +40,11 @@ export function createPathsProxy(renderPath = p => p, base = "") {
|
|
|
32
40
|
if (prop === "toString")
|
|
33
41
|
return () => toHref(pathname);
|
|
34
42
|
if (typeof prop === "symbol")
|
|
35
|
-
return prop === Symbol.toPrimitive
|
|
43
|
+
return prop === Symbol.toPrimitive
|
|
44
|
+
? () => toHref(pathname)
|
|
45
|
+
: prop === HREF
|
|
46
|
+
? pathname || "/"
|
|
47
|
+
: undefined;
|
|
36
48
|
return node(`${pathname}/${prop}`);
|
|
37
49
|
}
|
|
38
50
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*@refresh skip*/
|
|
2
|
-
import { createMemo, createRoot, getOwner, onCleanup, runWithOwner, untrack } from "solid-js";
|
|
2
|
+
import { createMemo, createRoot, getOwner, onCleanup, runWithOwner, untrack, Show } from "solid-js";
|
|
3
3
|
import { getRequestEvent, isServer } from "@solidjs/web";
|
|
4
4
|
import { createRouteContext, getIntent, getRouteMatches, resolveLazySubtree, RouteContextObj, setInPreloadFn, unresolvedLazyMatches } from "../routing.js";
|
|
5
5
|
export function Root(props) {
|
|
@@ -104,13 +104,17 @@ export function Routes(props) {
|
|
|
104
104
|
return <>{outlet()}</>;
|
|
105
105
|
}
|
|
106
106
|
const createOutlet = (child) => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
// Keyed on the context's identity (#588). `when` compiles to a lazy prop
|
|
108
|
+
// getter, so child() is tracked inside Show's memo — the outlet function
|
|
109
|
+
// reads nothing reactive and Show owns the subtree. Levels whose context is
|
|
110
|
+
// reference-reused (parent layouts) keep their subtree across navigations;
|
|
111
|
+
// a changed context must re-create the provider, not just re-render under
|
|
112
|
+
// it: a context value is registered once at provider creation
|
|
113
|
+
// (setContext(provider, props.value)), so non-keyed reuse would leave new
|
|
114
|
+
// route content reading the previous level's context.
|
|
115
|
+
return () => (<Show when={child()} keyed>
|
|
116
|
+
{c => <RouteContextObj value={c}>{c.outlet()}</RouteContextObj>}
|
|
117
|
+
</Show>);
|
|
114
118
|
};
|
|
115
119
|
// for data only mode with single flight mutations
|
|
116
120
|
function dataOnly(event, routerState, branches) {
|
package/dist/routers/factory.jsx
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/*@refresh skip*/
|
|
2
2
|
import { createSignal, getOwner, onCleanup, sharedConfig, untrack } from "solid-js";
|
|
3
|
+
// standalone import: `DEV` is undefined in solid's production build, so app
|
|
4
|
+
// bundlers fold `DEV &&` diagnostics out of shipped bundles
|
|
5
|
+
import { DEV } from "solid-js";
|
|
3
6
|
import { getRequestEvent, isServer } from "@solidjs/web";
|
|
4
7
|
import { setupLinkClaims } from "../claims.js";
|
|
5
8
|
import { setupNativeEvents } from "../data/events.js";
|
|
@@ -89,7 +92,7 @@ export function createRouter(config) {
|
|
|
89
92
|
// fight it — stale content on click navigations, conflicting link
|
|
90
93
|
// attributes. Compose route trees instead; lazy subtrees are the planned
|
|
91
94
|
// answer for definitions unknown at build time.
|
|
92
|
-
if (useOptionalContext(RouterContextObj)) {
|
|
95
|
+
if (DEV && useOptionalContext(RouterContextObj)) {
|
|
93
96
|
console.warn("Mounting a router inside another router is not supported. " +
|
|
94
97
|
"Compose route trees in one createRouter config instead.");
|
|
95
98
|
}
|
package/dist/routing.d.ts
CHANGED
|
@@ -189,9 +189,21 @@ export declare function trackLazySubtrees(): number;
|
|
|
189
189
|
export declare function peekLazySubtrees(): number;
|
|
190
190
|
/**
|
|
191
191
|
* Kicks (or joins) a boundary's resolution. Returns the resolved routes
|
|
192
|
-
* synchronously once available, the in-flight promise otherwise.
|
|
193
|
-
*
|
|
194
|
-
*
|
|
192
|
+
* synchronously once available, the in-flight promise otherwise.
|
|
193
|
+
*
|
|
194
|
+
* Failure contract (same as solid's lazy(), 2.0.0-rc.1: the platform
|
|
195
|
+
* re-fetches a failed dynamic import): a client rejection is held through its
|
|
196
|
+
* settlement flush. The recomputes the settled promise triggers — the parked
|
|
197
|
+
* transition's and the mainline commit's — both consume it here as a
|
|
198
|
+
* synchronous throw, becoming cached error status that reaches the nearest
|
|
199
|
+
* error boundary like a failed lazy() component. The hold clears a microtask
|
|
200
|
+
* after the first delivery, so any later recompute — boundary reset(), a new
|
|
201
|
+
* navigation — finds a clean record and retries the import. Holding through
|
|
202
|
+
* the flush is what keeps the erroring computation from refiring the import
|
|
203
|
+
* in a tight loop while it fails. Server records are shared across requests
|
|
204
|
+
* and hold nothing — each request retries. Commit is always async — even for
|
|
205
|
+
* thunks returning arrays — so the version bump never writes a signal from
|
|
206
|
+
* inside a render computation.
|
|
195
207
|
*/
|
|
196
208
|
export declare function resolveLazySubtree(record: LazyBoundary): readonly RouteDefinition[] | Promise<readonly RouteDefinition[]>;
|
|
197
209
|
/**
|
package/dist/routing.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { flush, runWithOwner } from "solid-js";
|
|
2
|
+
// standalone import: `DEV` is undefined in solid's production build, so app
|
|
3
|
+
// bundlers fold `DEV &&` diagnostics out of shipped bundles
|
|
4
|
+
import { DEV } from "solid-js";
|
|
2
5
|
import { createComponent, createContext, createMemo, createSignal, isPending, NotReadyError, untrack, useContext } from "solid-js";
|
|
3
6
|
import { clearFlashCookie, getRequestEvent, hasFlashCookie, isServer } from "@solidjs/web";
|
|
4
7
|
import { mockBase, comparablePath, createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils.js";
|
|
8
|
+
import { HREF } from "./paths.js";
|
|
5
9
|
const MAX_REDIRECTS = 100;
|
|
6
10
|
/** Consider this API opaque and internal. It is likely to change in the future. */
|
|
7
11
|
export const RouterContextObj = createContext();
|
|
@@ -259,15 +263,20 @@ const encodeSegment = (s) => encodeURIComponent(s).replace(/%(2B|40|3A|24|26|2C|
|
|
|
259
263
|
const lazyBoundaries = new WeakMap();
|
|
260
264
|
// Module scope: boundary resolution is global, deterministic state (same
|
|
261
265
|
// thunk -> same routes), shared by every factory instance and the server's
|
|
262
|
-
// flight collector.
|
|
266
|
+
// flight collector. The counter is the source of truth; the client mirrors
|
|
267
|
+
// it into a signal for subscription. Server render is pure (signal writes
|
|
268
|
+
// are inert as of solid 2.0.0-rc.3): there the plain counter serves reads,
|
|
269
|
+
// and re-runs come from the parked computation's promise retry, not from
|
|
270
|
+
// reactivity.
|
|
271
|
+
let lazyTreeCounter = 0;
|
|
263
272
|
const [lazyTreeVersion, setLazyTreeVersion] = createSignal(0);
|
|
264
273
|
/** Reactive read of the lazy-subtree version — recompile compiled branches when it changes. */
|
|
265
274
|
export function trackLazySubtrees() {
|
|
266
|
-
return lazyTreeVersion();
|
|
275
|
+
return isServer ? lazyTreeCounter : lazyTreeVersion();
|
|
267
276
|
}
|
|
268
277
|
/** Non-reactive read, for cache-busting outside the reactive graph (server collectors). */
|
|
269
278
|
export function peekLazySubtrees() {
|
|
270
|
-
return
|
|
279
|
+
return lazyTreeCounter;
|
|
271
280
|
}
|
|
272
281
|
function getLazyBoundary(thunk) {
|
|
273
282
|
let record = lazyBoundaries.get(thunk);
|
|
@@ -277,13 +286,32 @@ function getLazyBoundary(thunk) {
|
|
|
277
286
|
}
|
|
278
287
|
/**
|
|
279
288
|
* Kicks (or joins) a boundary's resolution. Returns the resolved routes
|
|
280
|
-
* synchronously once available, the in-flight promise otherwise.
|
|
281
|
-
*
|
|
282
|
-
*
|
|
289
|
+
* synchronously once available, the in-flight promise otherwise.
|
|
290
|
+
*
|
|
291
|
+
* Failure contract (same as solid's lazy(), 2.0.0-rc.1: the platform
|
|
292
|
+
* re-fetches a failed dynamic import): a client rejection is held through its
|
|
293
|
+
* settlement flush. The recomputes the settled promise triggers — the parked
|
|
294
|
+
* transition's and the mainline commit's — both consume it here as a
|
|
295
|
+
* synchronous throw, becoming cached error status that reaches the nearest
|
|
296
|
+
* error boundary like a failed lazy() component. The hold clears a microtask
|
|
297
|
+
* after the first delivery, so any later recompute — boundary reset(), a new
|
|
298
|
+
* navigation — finds a clean record and retries the import. Holding through
|
|
299
|
+
* the flush is what keeps the erroring computation from refiring the import
|
|
300
|
+
* in a tight loop while it fails. Server records are shared across requests
|
|
301
|
+
* and hold nothing — each request retries. Commit is always async — even for
|
|
302
|
+
* thunks returning arrays — so the version bump never writes a signal from
|
|
303
|
+
* inside a render computation.
|
|
283
304
|
*/
|
|
284
305
|
export function resolveLazySubtree(record) {
|
|
285
306
|
if (record.resolved)
|
|
286
307
|
return record.resolved;
|
|
308
|
+
if (record.error !== undefined) {
|
|
309
|
+
if (!record.sweep) {
|
|
310
|
+
record.sweep = true;
|
|
311
|
+
queueMicrotask(() => (record.error = record.sweep = undefined));
|
|
312
|
+
}
|
|
313
|
+
throw record.error;
|
|
314
|
+
}
|
|
287
315
|
return (record.promise ||= Promise.resolve(record.thunk()).then(m => {
|
|
288
316
|
const routes = Array.isArray(m)
|
|
289
317
|
? m
|
|
@@ -292,8 +320,15 @@ export function resolveLazySubtree(record) {
|
|
|
292
320
|
m.routes ||
|
|
293
321
|
[];
|
|
294
322
|
record.resolved = routes;
|
|
295
|
-
|
|
323
|
+
lazyTreeCounter++;
|
|
324
|
+
isServer || setLazyTreeVersion(lazyTreeCounter);
|
|
296
325
|
return record.resolved;
|
|
326
|
+
}, e => {
|
|
327
|
+
// ?? Error(): a held undefined would read as "no failure" and refire
|
|
328
|
+
if (!isServer)
|
|
329
|
+
record.error = e ?? new Error();
|
|
330
|
+
record.promise = undefined;
|
|
331
|
+
throw e;
|
|
297
332
|
}));
|
|
298
333
|
}
|
|
299
334
|
/**
|
|
@@ -443,7 +478,7 @@ function createLocation(path, state, queryWrapper) {
|
|
|
443
478
|
return new URL(path_[0] === "/" ? mockBase + path_ : path_, origin);
|
|
444
479
|
}
|
|
445
480
|
catch (err) {
|
|
446
|
-
console.error(`Invalid path ${path_}`);
|
|
481
|
+
DEV && console.error(`Invalid path ${path_}`);
|
|
447
482
|
return prev;
|
|
448
483
|
}
|
|
449
484
|
}, {
|
|
@@ -586,8 +621,14 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
586
621
|
// from the carried promise's retry on the server; a boundary nested
|
|
587
622
|
// inside a boundary just parks the recomputed chain again.
|
|
588
623
|
const pending = unresolvedLazyMatches(m);
|
|
589
|
-
if (pending.length)
|
|
590
|
-
|
|
624
|
+
if (pending.length) {
|
|
625
|
+
const all = Promise.all(pending.map(resolveLazySubtree));
|
|
626
|
+
// pre-handle rejections: the failure reaches the app through this
|
|
627
|
+
// computation's error status, so the raw chain must not also surface
|
|
628
|
+
// as an unhandled rejection
|
|
629
|
+
all.catch(() => { });
|
|
630
|
+
throw new NotReadyError(all);
|
|
631
|
+
}
|
|
591
632
|
return m;
|
|
592
633
|
});
|
|
593
634
|
// Every write is a transition in Solid 2, so a native history pop forks the
|
|
@@ -596,7 +637,21 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
596
637
|
// window, and isPending over the location/matches read reports any
|
|
597
638
|
// in-flight fork — including popstate traversals and the lazy-subtree
|
|
598
639
|
// resolution matches() parks on.
|
|
599
|
-
const isRouting = createMemo(() => isNavigating() ||
|
|
640
|
+
const isRouting = createMemo(() => isNavigating() ||
|
|
641
|
+
isPending(() => {
|
|
642
|
+
// A real error means the navigation settled (failed) — it surfaces
|
|
643
|
+
// to the app through render reads, not through this derivation.
|
|
644
|
+
// Not-ready must keep propagating so isPending sees the parking.
|
|
645
|
+
try {
|
|
646
|
+
matches();
|
|
647
|
+
}
|
|
648
|
+
catch (e) {
|
|
649
|
+
if (e instanceof NotReadyError)
|
|
650
|
+
throw e;
|
|
651
|
+
}
|
|
652
|
+
location.search;
|
|
653
|
+
location.hash;
|
|
654
|
+
}));
|
|
600
655
|
const buildParams = () => mergeParams(matches());
|
|
601
656
|
const wrapParams = utils.paramsWrapper
|
|
602
657
|
? (getParams) => utils.paramsWrapper(getParams, branches)
|
|
@@ -642,13 +697,24 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
642
697
|
utils.go(to);
|
|
643
698
|
}
|
|
644
699
|
else {
|
|
645
|
-
console.warn("Router integration does not support relative routing");
|
|
700
|
+
DEV && console.warn("Router integration does not support relative routing");
|
|
646
701
|
}
|
|
647
702
|
return;
|
|
648
703
|
}
|
|
649
|
-
//
|
|
704
|
+
// A paths node carries its logical path under the Href brand — read
|
|
705
|
+
// that rather than coercing: toString() renders the *display* href
|
|
706
|
+
// (eg. hash mode's `#` prefix), which is for the DOM, not for routing.
|
|
707
|
+
// Foreign Href-branded values without the slot still coerce.
|
|
650
708
|
if (typeof to !== "string")
|
|
651
|
-
to = to.toString();
|
|
709
|
+
to = to[HREF] || to.toString();
|
|
710
|
+
// Display hrefs can still arrive as plain strings: terminating paths
|
|
711
|
+
// calls type as `string`, and redirect Location headers round-trip
|
|
712
|
+
// through here. Under hash mode those start with `#` — a spelling no
|
|
713
|
+
// logical path uses — so map them back through the integration's
|
|
714
|
+
// parser, exactly like the anchor click handler does. Elsewhere
|
|
715
|
+
// parsePath is identity and `#...` keeps its URL meaning below.
|
|
716
|
+
if (to[0] === "#")
|
|
717
|
+
to = parsePath(to);
|
|
652
718
|
const { replace, resolve, scroll, state: nextState } = {
|
|
653
719
|
replace: false,
|
|
654
720
|
resolve: true,
|
|
@@ -734,10 +800,16 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
734
800
|
const matches = getRouteMatches(branches(), url.pathname);
|
|
735
801
|
// An unresolved lazy subtree in the chain: the placeholder's
|
|
736
802
|
// component.preload (below) kicks the table load; once it lands,
|
|
737
|
-
// preload again so the real inner routes warm too.
|
|
803
|
+
// preload again so the real inner routes warm too. Preloads are
|
|
804
|
+
// speculative: a failed load (held sync throw or rejection) is ignored
|
|
805
|
+
// here — the real navigation surfaces and retries it.
|
|
738
806
|
const boundary = matches.find(m => m.route.lazy && !m.route.lazy.resolved);
|
|
739
|
-
boundary
|
|
740
|
-
|
|
807
|
+
if (boundary) {
|
|
808
|
+
try {
|
|
809
|
+
resolveLazySubtree(boundary.route.lazy).then(() => preloadRoute(url, preloadData), () => { });
|
|
810
|
+
}
|
|
811
|
+
catch { }
|
|
812
|
+
}
|
|
741
813
|
const prevIntent = intent;
|
|
742
814
|
intent = "preload";
|
|
743
815
|
for (let match in matches) {
|
package/dist/types.d.ts
CHANGED
|
@@ -231,6 +231,12 @@ export interface LazyBoundary {
|
|
|
231
231
|
thunk: LazyRouteChildren;
|
|
232
232
|
promise?: Promise<readonly RouteDefinition[]>;
|
|
233
233
|
resolved?: readonly RouteDefinition[];
|
|
234
|
+
/** A client load failure, held through its settlement flush so every
|
|
235
|
+
* recompute it triggers delivers the same error instead of refiring the
|
|
236
|
+
* import; cleared a microtask after first delivery so retries can run. */
|
|
237
|
+
error?: unknown;
|
|
238
|
+
/** The pending clear of `error` has been scheduled. */
|
|
239
|
+
sweep?: boolean;
|
|
234
240
|
}
|
|
235
241
|
export interface Branch {
|
|
236
242
|
routes: RouteDescription[];
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"Ryan Turnquist"
|
|
7
7
|
],
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "2.0.0-next.
|
|
9
|
+
"version": "2.0.0-next.18",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -31,28 +31,28 @@
|
|
|
31
31
|
],
|
|
32
32
|
"sideEffects": false,
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@solidjs/vite-plugin": "3.0.0-next.28",
|
|
35
|
-
"@solidjs/web": "^2.0.0-rc.0",
|
|
36
34
|
"@babel/core": "^7.26.0",
|
|
37
35
|
"@babel/preset-typescript": "^7.26.0",
|
|
38
36
|
"@changesets/cli": "^2.27.10",
|
|
39
37
|
"@rollup/plugin-babel": "6.0.4",
|
|
40
38
|
"@rollup/plugin-node-resolve": "15.3.0",
|
|
41
39
|
"@rollup/plugin-terser": "0.4.4",
|
|
40
|
+
"@solidjs/vite-plugin": "3.0.0-next.28",
|
|
41
|
+
"@solidjs/web": "^2.0.0-rc.3",
|
|
42
42
|
"@types/jest": "^29.5.14",
|
|
43
43
|
"@types/node": "^22.10.0",
|
|
44
|
-
"babel-preset-solid": "^2.0.0-rc.
|
|
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.
|
|
48
|
+
"solid-js": "^2.0.0-rc.3",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vite": "^6.0.0",
|
|
51
51
|
"vitest": "^2.1.6"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@solidjs/web": "^2.0.0-rc.
|
|
55
|
-
"solid-js": "^2.0.0-rc.
|
|
54
|
+
"@solidjs/web": "^2.0.0-rc.3",
|
|
55
|
+
"solid-js": "^2.0.0-rc.3"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "rm -rf dist && tsc && rollup -c",
|