@solidjs/router 0.17.0-next.5 → 1.0.0-next.7

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.
Files changed (49) hide show
  1. package/README.md +526 -716
  2. package/dist/claims.d.ts +21 -0
  3. package/dist/claims.js +108 -0
  4. package/dist/data/action.d.ts +28 -1
  5. package/dist/data/action.js +184 -43
  6. package/dist/data/events.d.ts +8 -0
  7. package/dist/data/events.js +23 -22
  8. package/dist/data/flash.d.ts +21 -0
  9. package/dist/data/flash.js +78 -0
  10. package/dist/data/flashCookie.d.ts +7 -0
  11. package/dist/data/flashCookie.js +20 -0
  12. package/dist/data/query.js +11 -4
  13. package/dist/data/serverForms.d.ts +1 -0
  14. package/dist/data/serverForms.js +5 -0
  15. package/dist/index.d.ts +5 -3
  16. package/dist/index.js +1381 -686
  17. package/dist/index.jsx +2 -2
  18. package/dist/lifecycle.d.ts +29 -4
  19. package/dist/lifecycle.js +40 -37
  20. package/dist/paths.d.ts +117 -0
  21. package/dist/paths.js +41 -0
  22. package/dist/routers/components.d.ts +10 -21
  23. package/dist/routers/components.jsx +29 -44
  24. package/dist/routers/factory.d.ts +45 -0
  25. package/dist/routers/factory.jsx +143 -0
  26. package/dist/routers/history.d.ts +24 -0
  27. package/dist/routers/history.js +180 -0
  28. package/dist/routers/index.d.ts +4 -11
  29. package/dist/routers/index.js +2 -6
  30. package/dist/routing.d.ts +80 -44
  31. package/dist/routing.js +303 -107
  32. package/dist/server.d.ts +75 -0
  33. package/dist/server.js +264 -0
  34. package/dist/types.d.ts +75 -6
  35. package/dist/utils.d.ts +2 -0
  36. package/dist/utils.js +2 -0
  37. package/package.json +10 -8
  38. package/dist/components.d.ts +0 -31
  39. package/dist/components.jsx +0 -46
  40. package/dist/routers/HashRouter.d.ts +0 -9
  41. package/dist/routers/HashRouter.js +0 -41
  42. package/dist/routers/MemoryRouter.d.ts +0 -24
  43. package/dist/routers/MemoryRouter.js +0 -57
  44. package/dist/routers/Router.d.ts +0 -9
  45. package/dist/routers/Router.js +0 -45
  46. package/dist/routers/StaticRouter.d.ts +0 -6
  47. package/dist/routers/StaticRouter.js +0 -15
  48. package/dist/routers/createRouter.d.ts +0 -10
  49. package/dist/routers/createRouter.js +0 -40
package/dist/index.jsx CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./routers/index.js";
2
- export * from "./components.jsx";
3
2
  export * from "./lifecycle.js";
4
- export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute, RouterContextObj as RouterContext } from "./routing.js";
3
+ export { useHref, useIsRouting, useLinkState, useLocation, useMatch, useNavigate, usePreloadRoute, useParams, useResolvedPath, useRouteMatches, useSearchParams, RouterContextObj as RouterContext } from "./routing.js";
5
4
  export { mergeSearchString as _mergeSearchString } from "./utils.js";
5
+ export { int } from "./paths.js";
6
6
  export * from "./data/index.js";
@@ -1,5 +1,30 @@
1
- import { BeforeLeaveLifecycle, LocationChange } from "./types.js";
1
+ import type { BeforeLeaveEventArgs, BeforeLeaveLifecycle } from "./types.js";
2
2
  export declare function createBeforeLeave(): BeforeLeaveLifecycle;
3
- export declare function saveCurrentDepth(): void;
4
- export declare function keepDepth(state: any): any;
5
- export declare function notifyIfNotBlocked(notify: (value?: string | LocationChange) => void, block: (delta: number | null) => boolean): () => void;
3
+ /**
4
+ * useBeforeLeave takes a function that will be called prior to leaving a route.
5
+ * The function will be called with:
6
+ *
7
+ * - from (*Location*): current location (before change).
8
+ * - to (*string | number*): path passed to `navigate`.
9
+ * - options (*NavigateOptions*): options passed to navigate.
10
+ * - preventDefault (*function*): call to block the route change.
11
+ * - defaultPrevented (*readonly boolean*): `true` if any previously called leave handlers called `preventDefault`.
12
+ * - retry (*function*, force?: boolean ): call to retry the same navigation, perhaps after confirming with the user. Pass `true` to skip running the leave handlers again (i.e. force navigate without confirming).
13
+ *
14
+ * @example
15
+ * ```js
16
+ * useBeforeLeave((e: BeforeLeaveEventArgs) => {
17
+ * if (form.isDirty && !e.defaultPrevented) {
18
+ * // preventDefault to block immediately and prompt user async
19
+ * e.preventDefault();
20
+ * setTimeout(() => {
21
+ * if (window.confirm("Discard unsaved changes - are you sure?")) {
22
+ * // user wants to proceed anyway so retry with force=true
23
+ * e.retry(true);
24
+ * }
25
+ * }, 100);
26
+ * }
27
+ * });
28
+ * ```
29
+ */
30
+ export declare const useBeforeLeave: (listener: (e: BeforeLeaveEventArgs) => void) => void;
package/dist/lifecycle.js CHANGED
@@ -1,4 +1,5 @@
1
- import { isServer } from "@solidjs/web";
1
+ import { onCleanup } from "solid-js";
2
+ import { useLocation, useNavigate, useRouter } from "./routing.js";
2
3
  export function createBeforeLeave() {
3
4
  let listeners = new Set();
4
5
  function subscribe(listener) {
@@ -38,39 +39,41 @@ export function createBeforeLeave() {
38
39
  confirm
39
40
  };
40
41
  }
41
- // The following supports browser initiated blocking (eg back/forward)
42
- let depth;
43
- export function saveCurrentDepth() {
44
- if (!window.history.state || window.history.state._depth == null) {
45
- window.history.replaceState({ ...window.history.state, _depth: window.history.length - 1 }, "");
46
- }
47
- depth = window.history.state._depth;
48
- }
49
- if (!isServer) {
50
- saveCurrentDepth();
51
- }
52
- export function keepDepth(state) {
53
- return {
54
- ...state,
55
- _depth: window.history.state && window.history.state._depth
56
- };
57
- }
58
- export function notifyIfNotBlocked(notify, block) {
59
- let ignore = false;
60
- return () => {
61
- const prevDepth = depth;
62
- saveCurrentDepth();
63
- const delta = prevDepth == null ? null : depth - prevDepth;
64
- if (ignore) {
65
- ignore = false;
66
- return;
67
- }
68
- if (delta && block(delta)) {
69
- ignore = true;
70
- window.history.go(-delta);
71
- }
72
- else {
73
- notify();
74
- }
75
- };
76
- }
42
+ /**
43
+ * useBeforeLeave takes a function that will be called prior to leaving a route.
44
+ * The function will be called with:
45
+ *
46
+ * - from (*Location*): current location (before change).
47
+ * - to (*string | number*): path passed to `navigate`.
48
+ * - options (*NavigateOptions*): options passed to navigate.
49
+ * - preventDefault (*function*): call to block the route change.
50
+ * - defaultPrevented (*readonly boolean*): `true` if any previously called leave handlers called `preventDefault`.
51
+ * - retry (*function*, force?: boolean ): call to retry the same navigation, perhaps after confirming with the user. Pass `true` to skip running the leave handlers again (i.e. force navigate without confirming).
52
+ *
53
+ * @example
54
+ * ```js
55
+ * useBeforeLeave((e: BeforeLeaveEventArgs) => {
56
+ * if (form.isDirty && !e.defaultPrevented) {
57
+ * // preventDefault to block immediately and prompt user async
58
+ * e.preventDefault();
59
+ * setTimeout(() => {
60
+ * if (window.confirm("Discard unsaved changes - are you sure?")) {
61
+ * // user wants to proceed anyway so retry with force=true
62
+ * e.retry(true);
63
+ * }
64
+ * }, 100);
65
+ * }
66
+ * });
67
+ * ```
68
+ */
69
+ export const useBeforeLeave = (listener) => {
70
+ // Installing the guard here (instead of eagerly in the router/history) keeps
71
+ // all of createBeforeLeave tree-shakeable for apps that never block leaves.
72
+ const slot = useRouter().beforeLeave;
73
+ const s = (slot.current || (slot.current = createBeforeLeave())).subscribe({
74
+ listener,
75
+ location: useLocation(),
76
+ navigate: useNavigate()
77
+ });
78
+ onCleanup(s);
79
+ };
@@ -0,0 +1,117 @@
1
+ import type { Params, RouteDefinition, SearchParams, SetSearchParams, StandardSchemaV1, TypedPath, TypedSearchPath } from "./types.js";
2
+ declare const FILTER_TYPE: unique symbol;
3
+ /**
4
+ * A match filter that also declares the TypeScript type accepted for the
5
+ * param at `paths` callsites (the runtime param remains a string).
6
+ */
7
+ export interface TypedMatchFilter<T> {
8
+ (s: string): boolean;
9
+ readonly [FILTER_TYPE]: T;
10
+ }
11
+ /** Matches integer segments; types the param as `number` in the path proxy. */
12
+ export declare const int: TypedMatchFilter<number>;
13
+ type Flat<T> = {
14
+ [K in keyof T]: T[K];
15
+ } & {};
16
+ type SegmentsOf<P extends string> = P extends `${infer A}/${infer B}` ? [...SegmentsOf<A>, ...SegmentsOf<B>] : P extends "" ? [] : [P];
17
+ type FilterArg<F> = F extends TypedMatchFilter<infer T> ? T : F extends readonly (infer S)[] ? S : string | number;
18
+ type ParamArg<Name extends string, F> = Name extends keyof F ? FilterArg<F[Name]> : string | number;
19
+ /** Collects a maximal run of required params (and a trailing splat) into one call's argument tuple. */
20
+ type ParamRun<Segs extends readonly string[], F, Args extends readonly unknown[] = [], PAcc extends Params = {}> = Segs extends readonly [infer H extends string, ...infer R extends readonly string[]] ? H extends `:${string}?` ? {
21
+ args: Args;
22
+ params: PAcc;
23
+ rest: Segs;
24
+ } : H extends `:${infer N}` ? ParamRun<R, F, [...Args, ParamArg<N, F>], PAcc & {
25
+ [K in N]: string;
26
+ }> : H extends `*${infer N}` ? {
27
+ args: [...Args, string | number];
28
+ params: PAcc & {
29
+ [K in N]: string;
30
+ };
31
+ rest: R;
32
+ } : {
33
+ args: Args;
34
+ params: PAcc;
35
+ rest: Segs;
36
+ } : {
37
+ args: Args;
38
+ params: PAcc;
39
+ rest: Segs;
40
+ };
41
+ /** The search param types a route end carries: input builds URLs, output is what parsing returns. */
42
+ export interface SearchTypes {
43
+ input: any;
44
+ output: any;
45
+ }
46
+ export interface DefaultSearchTypes {
47
+ input: SetSearchParams;
48
+ output: SearchParams;
49
+ }
50
+ /** Terminating calls available on every route end: zero-arg, or search object plus optional hash. */
51
+ export interface PathEnd<Sch extends SearchTypes = DefaultSearchTypes, P extends Params = Params> extends TypedPath<P>, TypedSearchPath<Sch["input"], Sch["output"]> {
52
+ (): string;
53
+ (search: Sch["input"], hash?: string): string;
54
+ }
55
+ type SearchTypesOf<Def> = Def extends {
56
+ search: infer S;
57
+ } ? S extends StandardSchemaV1<any, any> ? {
58
+ input: NonNullable<S["~standard"]["types"]>["input"];
59
+ output: NonNullable<S["~standard"]["types"]>["output"];
60
+ } : DefaultSearchTypes : DefaultSearchTypes;
61
+ type FiltersOf<Def> = Def extends {
62
+ matchFilters: infer F;
63
+ } ? F : {};
64
+ type ChildrenOf<Def> = Def extends {
65
+ children: infer C;
66
+ } ? C : undefined;
67
+ /**
68
+ * Sees through a lazy `children` thunk: the routes the import's promise
69
+ * resolves to (its `default` or `routes` export, matching the runtime) type
70
+ * exactly like inline children. Only tables genuinely built at runtime —
71
+ * where the thunk's return type is a plain `RouteDefinition[]` — degrade to
72
+ * untyped, definitionally.
73
+ */
74
+ type ResolvedChildren<C> = C extends () => infer R ? Awaited<R> extends infer M ? M extends {
75
+ default: infer D;
76
+ } ? D : M extends {
77
+ routes: infer D;
78
+ } ? D : M : never : C;
79
+ type ChildPaths<C, PAcc extends Params> = [C] extends [undefined] ? {} : ResolvedChildren<C> extends infer RC ? RC extends readonly unknown[] ? TuplePaths<RC, PAcc> : RouteContrib<RC, PAcc> : never;
80
+ type TuplePaths<R extends readonly unknown[], PAcc extends Params> = R extends readonly [
81
+ infer H,
82
+ ...infer T extends readonly unknown[]
83
+ ] ? RouteContrib<H, PAcc> & TuplePaths<T, PAcc> : {};
84
+ type PathLeaf<Sch extends SearchTypes, C, PAcc extends Params> = PathEnd<Sch, Flat<PAcc>> & ChildPaths<C, PAcc>;
85
+ type PathNode<Segs extends readonly string[], F, Sch extends SearchTypes, C, PAcc extends Params> = Segs extends readonly [infer H extends string, ...infer R extends readonly string[]] ? H extends `:${infer N}?` ? ((arg: ParamArg<N, F>) => PathNode<R, F, Sch, C, PAcc & {
86
+ [K in N]?: string;
87
+ }>) & PathNode<R, F, Sch, C, PAcc & {
88
+ [K in N]?: string;
89
+ }> : H extends `:${string}` | `*${string}` ? ParamCallNode<Segs, F, Sch, C, PAcc> : {
90
+ [K in H]: PathNode<R, F, Sch, C, PAcc>;
91
+ } : PathLeaf<Sch, C, PAcc>;
92
+ type ParamCallNode<Segs extends readonly string[], F, Sch extends SearchTypes, C, PAcc extends Params> = ParamRun<Segs, F> extends {
93
+ args: infer A extends readonly unknown[];
94
+ params: infer P2 extends Params;
95
+ rest: infer R2 extends readonly string[];
96
+ } ? ((...args: A) => PathNode<R2, F, Sch, C, PAcc & P2>) & (R2 extends readonly [] ? {
97
+ (...args: [...A, Sch["input"], string?]): string;
98
+ } : {}) & TypedPath<Flat<PAcc & P2>> : never;
99
+ type RouteContrib<Def, PAcc extends Params> = Def extends {
100
+ path: infer P;
101
+ } ? [P] extends [undefined] ? ChildPaths<ChildrenOf<Def>, PAcc> : P extends readonly string[] ? MultiPathContrib<P, Def, PAcc> : P extends string ? string extends P ? any : PathNode<SegmentsOf<P>, FiltersOf<Def>, SearchTypesOf<Def>, ChildrenOf<Def>, PAcc> : any : ChildPaths<ChildrenOf<Def>, PAcc>;
102
+ type MultiPathContrib<Ps extends readonly string[], Def, PAcc extends Params> = Ps extends readonly [infer H extends string, ...infer T extends readonly string[]] ? PathNode<SegmentsOf<H>, FiltersOf<Def>, SearchTypesOf<Def>, ChildrenOf<Def>, PAcc> & MultiPathContrib<T, Def, PAcc> : {};
103
+ /**
104
+ * The type of a router instance's `paths` proxy for a given route tree.
105
+ * Requires the tree to be a literal tuple (`as const` or a `const` type
106
+ * param); non-literal trees fall back to an untyped proxy.
107
+ */
108
+ export type RoutePaths<R extends readonly RouteDefinition[]> = number extends R["length"] ? any : PathEnd<DefaultSearchTypes, {}> & TuplePaths<R, {}>;
109
+ /** Extracts the params record a paths node binds, as runtime (string-valued) params. */
110
+ export type PathParamsOf<N> = N extends TypedPath<infer P> ? Flat<P> : Params;
111
+ /**
112
+ * Creates the runtime path proxy. It is instance-scoped: `renderPath` comes
113
+ * from the router's history adapter (eg. hash routing prefixes `#`), and
114
+ * `base` is baked into every produced path.
115
+ */
116
+ export declare function createPathsProxy(renderPath?: (path: string) => string, base?: string): any;
117
+ export {};
package/dist/paths.js ADDED
@@ -0,0 +1,41 @@
1
+ import { mergeSearchString, normalizePath } from "./utils.js";
2
+ /** Matches integer segments; types the param as `number` in the path proxy. */
3
+ export const int = ((s) => /^-?\d+$/.test(s));
4
+ // ---------------------------------------------------------------------------
5
+ // Runtime
6
+ // ---------------------------------------------------------------------------
7
+ const encodeParam = (value) => String(value).split("/").map(encodeURIComponent).join("/");
8
+ /**
9
+ * Creates the runtime path proxy. It is instance-scoped: `renderPath` comes
10
+ * from the router's history adapter (eg. hash routing prefixes `#`), and
11
+ * `base` is baked into every produced path.
12
+ */
13
+ export function createPathsProxy(renderPath = p => p, base = "") {
14
+ const toHref = (pathname, suffix = "") => renderPath(pathname || "/") + suffix;
15
+ function node(pathname) {
16
+ const build = (...args) => {
17
+ let path = pathname;
18
+ for (let i = 0; i < args.length; i++) {
19
+ const arg = args[i];
20
+ if (typeof arg === "object" && arg !== null) {
21
+ // a search object terminates; an optional hash string may follow
22
+ const hash = typeof args[i + 1] === "string" ? `#${args[i + 1]}` : "";
23
+ return toHref(path, mergeSearchString("", arg) + hash);
24
+ }
25
+ path += `/${encodeParam(arg)}`;
26
+ }
27
+ // zero-arg calls terminate; param-only calls stay chainable
28
+ return args.length ? node(path) : toHref(path);
29
+ };
30
+ return new Proxy(build, {
31
+ get(_, prop) {
32
+ if (prop === "toString")
33
+ return () => toHref(pathname);
34
+ if (typeof prop === "symbol")
35
+ return prop === Symbol.toPrimitive ? () => toHref(pathname) : undefined;
36
+ return node(`${pathname}/${prop}`);
37
+ }
38
+ });
39
+ }
40
+ return node(normalizePath(base));
41
+ }
@@ -1,24 +1,13 @@
1
1
  import type { Component } from "solid-js";
2
2
  import type { JSX } from "@solidjs/web";
3
- import type { MatchFilters, RouteDefinition, RoutePreloadFunc, RouterIntegration, RouteSectionComponent, RouteSectionProps } from "../types.js";
4
- export type BaseRouterProps = {
5
- base?: string;
6
- /**
7
- * A component that wraps the content of every route.
8
- */
3
+ import type { Branch, RoutePreloadFunc, RouterContext, RouteSectionProps } from "../types.js";
4
+ export declare function Root(props: {
5
+ routerState: RouterContext;
9
6
  root?: Component<RouteSectionProps>;
10
- rootPreload?: RoutePreloadFunc;
11
- singleFlight?: boolean;
12
- children?: JSX.Element | RouteDefinition | RouteDefinition[];
13
- transformUrl?: (url: string) => string;
14
- };
15
- export declare const createRouterComponent: (router: RouterIntegration) => (props: BaseRouterProps) => JSX.Element;
16
- export type RouteProps<S extends string, T = unknown> = {
17
- path?: S | S[];
18
- children?: JSX.Element;
19
- preload?: RoutePreloadFunc<T>;
20
- matchFilters?: MatchFilters<S>;
21
- component?: RouteSectionComponent<T>;
22
- info?: Record<string, any>;
23
- };
24
- export declare const Route: <S extends string, T = unknown>(props: RouteProps<S, T>) => JSX.Element;
7
+ preload?: RoutePreloadFunc;
8
+ children: JSX.Element;
9
+ }): JSX.Element;
10
+ export declare function Routes(props: {
11
+ routerState: RouterContext;
12
+ branches: () => Branch[];
13
+ }): JSX.Element;
@@ -1,40 +1,19 @@
1
1
  /*@refresh skip*/
2
- import { children, createMemo, createRoot, getOwner, merge, onCleanup, runWithOwner, untrack } from "solid-js";
2
+ import { createMemo, createRoot, getOwner, onCleanup, runWithOwner, untrack } from "solid-js";
3
3
  import { getRequestEvent, isServer } from "@solidjs/web";
4
- import { createBranches, createRouteContext, createRouterContext, getIntent, getRouteMatches, RouteContextObj, RouterContextObj, setInPreloadFn } from "../routing.js";
5
- export const createRouterComponent = (router) => function IntegratedRouter(props) {
6
- const { base, singleFlight, transformUrl, root, rootPreload, routeChildren } = untrack(() => ({
7
- base: props.base,
8
- singleFlight: props.singleFlight,
9
- transformUrl: props.transformUrl,
10
- root: props.root,
11
- rootPreload: props.rootPreload,
12
- routeChildren: props.children
13
- }));
14
- const routeDefs = children(() => routeChildren);
15
- const branches = createMemo(() => createBranches(routeDefs(), base || ""));
16
- let context;
17
- const routerState = createRouterContext(router, branches, () => context, {
18
- base,
19
- singleFlight,
20
- transformUrl,
21
- });
22
- router.create && router.create(routerState);
23
- return (<RouterContextObj value={routerState}>
24
- <Root routerState={routerState} root={root} preload={rootPreload}>
25
- {(context = getOwner()) && null}
26
- <Routes routerState={routerState} branches={branches()}/>
27
- </Root>
28
- </RouterContextObj>);
29
- };
30
- function Root(props) {
4
+ import { createRouteContext, getIntent, getRouteMatches, resolveLazySubtree, RouteContextObj, setInPreloadFn, unresolvedLazyMatches } from "../routing.js";
5
+ export function Root(props) {
31
6
  const location = props.routerState.location;
32
7
  const params = props.routerState.params;
33
8
  const data = createMemo(() => props.preload &&
34
9
  untrack(() => {
35
10
  setInPreloadFn(true);
36
- props.preload({ params, location, intent: getIntent() || "initial" });
37
- setInPreloadFn(false);
11
+ try {
12
+ return props.preload({ params, location, intent: getIntent() || "initial" });
13
+ }
14
+ finally {
15
+ setInPreloadFn(false);
16
+ }
38
17
  }));
39
18
  const RootComp = props.root;
40
19
  if (RootComp) {
@@ -44,22 +23,29 @@ function Root(props) {
44
23
  }
45
24
  return props.children;
46
25
  }
47
- function Routes(props) {
26
+ export function Routes(props) {
48
27
  if (isServer) {
49
28
  const e = getRequestEvent();
50
29
  if (e && e.router && e.router.dataOnly) {
51
- dataOnly(e, props.routerState, props.branches);
30
+ dataOnly(e, props.routerState, props.branches());
52
31
  return;
53
32
  }
54
- e &&
55
- ((e.router || (e.router = {})).matches ||
56
- (e.router.matches = props.routerState.matches().map(({ route, path, params }) => ({
33
+ if (e && !(e.router && e.router.matches)) {
34
+ // A lazy getter rather than a snapshot: unresolved lazy subtrees mean
35
+ // the match chain can still improve while the render streams, and
36
+ // frameworks read this after the render settles.
37
+ Object.defineProperty((e.router || (e.router = {})), "matches", {
38
+ configurable: true,
39
+ enumerable: true,
40
+ get: () => props.routerState.matches().map(({ route, path, params }) => ({
57
41
  path: route.originalPath,
58
42
  pattern: route.pattern,
59
43
  match: path,
60
44
  params,
61
45
  info: route.info
62
- }))));
46
+ }))
47
+ });
48
+ }
63
49
  }
64
50
  const disposers = [];
65
51
  let root;
@@ -72,6 +58,9 @@ function Routes(props) {
72
58
  // computation (which disposes its children every time it re-runs).
73
59
  const owner = getOwner();
74
60
  const routeStates = createMemo((prev) => {
61
+ // While a lazy subtree resolves, `matches()` is not ready and this
62
+ // computation parks with it — no route contexts are created against
63
+ // placeholder matches.
75
64
  const nextMatches = props.routerState.matches();
76
65
  const previousMatches = prevMatches;
77
66
  let equal = previousMatches && nextMatches.length === previousMatches.length;
@@ -123,19 +112,15 @@ const createOutlet = (child) => {
123
112
  return undefined;
124
113
  };
125
114
  };
126
- export const Route = (props) => {
127
- const childRoutes = children(() => props.children);
128
- return merge(props, {
129
- get children() {
130
- return childRoutes();
131
- }
132
- });
133
- };
134
115
  // for data only mode with single flight mutations
135
116
  function dataOnly(event, routerState, branches) {
136
117
  const url = new URL(event.request.url);
137
118
  const prevMatches = getRouteMatches(branches, new URL(event.router.previousUrl || event.request.url).pathname);
138
119
  const matches = getRouteMatches(branches, url.pathname);
120
+ // This pass is synchronous — an unresolved lazy subtree can only be
121
+ // kicked, not awaited (the flight collector's own runner awaits; see
122
+ // src/server.ts). Best effort so a subsequent pass sees the table.
123
+ unresolvedLazyMatches([...prevMatches, ...matches]).forEach(resolveLazySubtree);
139
124
  for (let match = 0; match < matches.length; match++) {
140
125
  if (!prevMatches[match] || matches[match].route !== prevMatches[match].route)
141
126
  event.router.dataOnly = true;
@@ -0,0 +1,45 @@
1
+ import type { JSX } from "@solidjs/web";
2
+ import type { RoutePaths } from "../paths.js";
3
+ import type { OutputMatch, RouteDefinition, RoutePreloadFunc, RouteSectionProps } from "../types.js";
4
+ import type { RouterHistory } from "./history.js";
5
+ /**
6
+ * Identity helper that preserves literal types when the route tree is
7
+ * declared as a separate variable. `createRouter` infers literally from
8
+ * inline arrays, but an extracted `const routes = [...]` widens paths to
9
+ * `string` and silently degrades `paths` and the typed hooks unless it is
10
+ * declared `as const` — `defineRoutes` makes that impossible to forget.
11
+ */
12
+ export declare function defineRoutes<const R extends readonly RouteDefinition[]>(routes: R): R;
13
+ export interface RouterConfig<R extends readonly RouteDefinition[] = RouteDefinition[]> {
14
+ /** The route tree. Immutable per instance — it is the source of truth for matching *and* types. */
15
+ routes: R;
16
+ base?: string;
17
+ /**
18
+ * Runs once per mount/request (and on single-flight collection passes) to
19
+ * warm app-wide data. Its result is passed to the render-prop child as
20
+ * `props.data`.
21
+ */
22
+ preload?: RoutePreloadFunc;
23
+ /** History adapter; defaults to browser history on the client and the request URL on the server. */
24
+ history?: RouterHistory;
25
+ singleFlight?: boolean;
26
+ actionBase?: string;
27
+ explicitLinks?: boolean;
28
+ /** Preload route code/data on link hover and focus. Defaults to `true`. */
29
+ preloadLinks?: boolean;
30
+ transformUrl?: (url: string) => string;
31
+ }
32
+ export interface RouterInstance<R extends readonly RouteDefinition[] = RouteDefinition[]> {
33
+ /** The instance is the provider component; the render-prop child receives the matched content as `props.children`. */
34
+ (props: {
35
+ children?: (props: RouteSectionProps) => JSX.Element;
36
+ }): JSX.Element;
37
+ /** Typed path proxy — builds URLs through property access and calls. */
38
+ readonly paths: RoutePaths<R>;
39
+ readonly routes: R;
40
+ /** The config the instance was created with — lets server integrations (flight collector, no-JS handler) consume the instance directly. */
41
+ readonly config: RouterConfig<R>;
42
+ /** Pure matching against an arbitrary URL — no rendering or request context involved. Root→leaf; `[]` when nothing matches. */
43
+ match(url: string): OutputMatch[];
44
+ }
45
+ export declare function createRouter<const R extends readonly RouteDefinition[]>(config: RouterConfig<R>): RouterInstance<R>;
@@ -0,0 +1,143 @@
1
+ /*@refresh skip*/
2
+ import { createSignal, getOwner, onCleanup, sharedConfig, untrack } from "solid-js";
3
+ import { getRequestEvent, isServer } from "@solidjs/web";
4
+ import { setupLinkClaims } from "../claims.js";
5
+ import { setupNativeEvents } from "../data/events.js";
6
+ import { createPathsProxy } from "../paths.js";
7
+ import { createBranches, createRouterContext, getRouteMatches, registerFlightRouter, RouterContextObj, trackLazySubtrees, useOptionalContext } from "../routing.js";
8
+ import { mockBase } from "../utils.js";
9
+ import { Root, Routes } from "./components.jsx";
10
+ import { browserHistory } from "./history.js";
11
+ /**
12
+ * Identity helper that preserves literal types when the route tree is
13
+ * declared as a separate variable. `createRouter` infers literally from
14
+ * inline arrays, but an extracted `const routes = [...]` widens paths to
15
+ * `string` and silently degrades `paths` and the typed hooks unless it is
16
+ * declared `as const` — `defineRoutes` makes that impossible to forget.
17
+ */
18
+ export function defineRoutes(routes) {
19
+ return routes;
20
+ }
21
+ /** Wraps a history adapter in the integration signal the router core consumes. Must run under a reactive owner. */
22
+ function createIntegration(history) {
23
+ let ignore = false;
24
+ const wrap = (value) => (typeof value === "string" ? { value } : value);
25
+ const [read, write] = createSignal(wrap(history.get()), {
26
+ equals: (a, b) => a.value === b.value && a.state === b.state,
27
+ ownedWrite: true
28
+ });
29
+ const signal = [
30
+ read,
31
+ (next) => {
32
+ !ignore && history.set(next);
33
+ if (sharedConfig.registry && !sharedConfig.done)
34
+ sharedConfig.done = true;
35
+ write(next);
36
+ }
37
+ ];
38
+ history.init &&
39
+ onCleanup(history.init((value = history.get()) => {
40
+ ignore = true;
41
+ signal[1](wrap(value));
42
+ ignore = false;
43
+ }));
44
+ return { signal, utils: history.utils };
45
+ }
46
+ /**
47
+ * Server default: a static view of the request URL — no signal machinery, a
48
+ * server render never navigates. Without a request event (SSG scripts,
49
+ * server-side tests) the configured history adapter provides the location,
50
+ * so e.g. `memoryHistory("/page")` works isomorphically.
51
+ */
52
+ function staticIntegration(history) {
53
+ const e = getRequestEvent();
54
+ let value = "";
55
+ if (e) {
56
+ const url = new URL(e.request.url);
57
+ value = url.pathname + url.search;
58
+ }
59
+ else if (history) {
60
+ value = history.get();
61
+ }
62
+ const obj = typeof value === "string" ? { value } : { ...value };
63
+ return { signal: [() => obj, next => Object.assign(obj, next)], utils: history && history.utils };
64
+ }
65
+ export function createRouter(config) {
66
+ const basePath = config.base || "";
67
+ // Routes are immutable per instance, so compilation is shared by every
68
+ // mount, request, and `match()` call — recompiled only when a lazy subtree
69
+ // resolves (append-only: resolution, not mutation). Reading the version
70
+ // inside a computation subscribes it; plain calls just see current state.
71
+ let compiled;
72
+ let compiledVersion = -1;
73
+ const branches = () => {
74
+ const version = trackLazySubtrees();
75
+ if (!compiled || compiledVersion !== version) {
76
+ compiled = createBranches(config.routes, basePath);
77
+ compiledVersion = version;
78
+ }
79
+ return compiled;
80
+ };
81
+ const renderPath = (config.history && config.history.utils && config.history.utils.renderPath) || undefined;
82
+ function RouterComponent(props) {
83
+ // One router per app: the session (location, history, delegation, link
84
+ // claims, preloading) has a single owner, and a second instance would
85
+ // fight it — stale content on click navigations, conflicting link
86
+ // attributes. Compose route trees instead; lazy subtrees are the planned
87
+ // answer for definitions unknown at build time.
88
+ if (useOptionalContext(RouterContextObj)) {
89
+ console.warn("Mounting a router inside another router is not supported. " +
90
+ "Compose route trees in one createRouter config instead.");
91
+ }
92
+ const root = untrack(() => props.children);
93
+ const integration = isServer
94
+ ? staticIntegration(config.history)
95
+ : createIntegration(config.history || browserHistory());
96
+ let context;
97
+ const routerState = createRouterContext(integration, branches, () => context, {
98
+ base: basePath,
99
+ singleFlight: config.singleFlight,
100
+ transformUrl: config.transformUrl
101
+ });
102
+ if (!isServer) {
103
+ setupNativeEvents({
104
+ preload: config.preloadLinks,
105
+ explicitLinks: config.explicitLinks,
106
+ actionBase: config.actionBase,
107
+ transformUrl: config.transformUrl
108
+ })(routerState);
109
+ setupLinkClaims(routerState, config.explicitLinks);
110
+ if (routerState.singleFlight)
111
+ onCleanup(registerFlightRouter(routerState));
112
+ }
113
+ return (<RouterContextObj value={routerState}>
114
+ <Root routerState={routerState} root={root} preload={config.preload}>
115
+ {(context = getOwner()) && null}
116
+ <Routes routerState={routerState} branches={branches}/>
117
+ </Root>
118
+ </RouterContextObj>);
119
+ }
120
+ const instance = Object.assign(RouterComponent, {
121
+ routes: config.routes,
122
+ config,
123
+ match(url) {
124
+ const u = new URL(url, mockBase);
125
+ const pathname = config.transformUrl ? config.transformUrl(u.pathname) : u.pathname;
126
+ return getRouteMatches(branches(), pathname).map(({ route, path, params }) => ({
127
+ path: route.originalPath,
128
+ pattern: route.pattern,
129
+ match: path,
130
+ params,
131
+ info: route.info
132
+ }));
133
+ }
134
+ });
135
+ // Built on first access (a getter via Object.assign would run during the
136
+ // copy) so runtimes without Proxy — some older TVs — can still route as
137
+ // long as they never touch typed paths.
138
+ let paths;
139
+ Object.defineProperty(instance, "paths", {
140
+ get: () => paths || (paths = createPathsProxy(renderPath, basePath))
141
+ });
142
+ return instance;
143
+ }
@@ -0,0 +1,24 @@
1
+ import type { LocationChange, RouterUtils } from "../types.js";
2
+ /**
3
+ * A history adapter: the source of truth for the current URL and how
4
+ * navigations write back to it. Adapters are plain imported values so
5
+ * unused ones never enter the bundle — `createRouter` defaults to browser
6
+ * history on the client and the request URL on the server.
7
+ */
8
+ export interface RouterHistory {
9
+ get: () => string | LocationChange;
10
+ set: (next: LocationChange) => void;
11
+ init?: (notify: (value?: string | LocationChange) => void) => () => void;
12
+ utils?: Partial<RouterUtils>;
13
+ }
14
+ export declare function browserHistory(): RouterHistory;
15
+ export declare function hashParser(str: string): string;
16
+ export declare function hashHistory(): RouterHistory;
17
+ export interface MemoryHistoryAdapter extends RouterHistory {
18
+ get: () => string;
19
+ go: (delta: number) => void;
20
+ back: () => void;
21
+ forward: () => void;
22
+ listen: (listener: (value: string) => void) => () => void;
23
+ }
24
+ export declare function memoryHistory(initial?: string): MemoryHistoryAdapter;