@jk2908/solas 0.5.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +34 -94
  3. package/dist/config.d.ts +24 -0
  4. package/dist/config.js +143 -0
  5. package/dist/events.d.ts +4 -0
  6. package/dist/events.js +5 -0
  7. package/dist/index.d.ts +1 -2
  8. package/dist/index.js +59 -41
  9. package/dist/internal/boundary-error.d.ts +4 -0
  10. package/dist/internal/boundary-error.js +8 -0
  11. package/dist/internal/browser-router/history.d.ts +1 -1
  12. package/dist/internal/browser-router/link.d.ts +1 -1
  13. package/dist/internal/browser-router/link.js +1 -1
  14. package/dist/internal/browser-router/router.d.ts +2 -2
  15. package/dist/internal/browser-router/router.js +5 -5
  16. package/dist/internal/browser-router/shared.d.ts +182 -184
  17. package/dist/internal/browser-router/shared.js +49 -56
  18. package/dist/internal/browser-router/use-router.d.ts +1 -1
  19. package/dist/internal/browser-router/use-search-params.js +3 -3
  20. package/dist/internal/build.d.ts +108 -110
  21. package/dist/internal/build.js +569 -581
  22. package/dist/internal/codegen/config.js +4 -5
  23. package/dist/internal/codegen/environments.d.ts +1 -1
  24. package/dist/internal/codegen/environments.js +6 -9
  25. package/dist/internal/codegen/format.d.ts +6 -0
  26. package/dist/internal/codegen/format.js +24 -0
  27. package/dist/internal/codegen/manifest.js +2 -2
  28. package/dist/internal/codegen/maps.d.ts +1 -1
  29. package/dist/internal/codegen/maps.js +2 -2
  30. package/dist/internal/codegen/types.js +4 -4
  31. package/dist/internal/codegen/utils.js +2 -2
  32. package/dist/internal/env/request-context.js +1 -1
  33. package/dist/internal/env/rsc.d.ts +3 -3
  34. package/dist/internal/env/rsc.js +26 -26
  35. package/dist/internal/env/ssr.js +4 -4
  36. package/dist/internal/http-router/router.js +8 -8
  37. package/dist/internal/metadata.d.ts +1 -1
  38. package/dist/internal/metadata.js +1 -1
  39. package/dist/internal/navigation/http-exception-boundary.d.ts +1 -1
  40. package/dist/internal/navigation/redirect-boundary.d.ts +1 -1
  41. package/dist/internal/navigation/redirect.js +6 -6
  42. package/dist/internal/postbuild.js +18 -17
  43. package/dist/internal/prerender.d.ts +130 -138
  44. package/dist/internal/prerender.js +326 -354
  45. package/dist/internal/public-files.js +2 -4
  46. package/dist/internal/render/head.js +2 -2
  47. package/dist/internal/resolver.js +1 -1
  48. package/dist/internal/runtimes/runtime.d.ts +7 -21
  49. package/dist/internal/runtimes/runtime.js +24 -19
  50. package/dist/internal/server/actions.js +2 -2
  51. package/dist/internal/ui/error-boundary.d.ts +1 -1
  52. package/dist/manifest.d.ts +7 -0
  53. package/dist/manifest.js +85 -0
  54. package/dist/prerender.d.ts +1 -1
  55. package/dist/prerender.js +1 -1
  56. package/dist/solas.d.ts +2 -46
  57. package/dist/solas.js +6 -266
  58. package/dist/types.d.ts +14 -25
  59. package/dist/types.js +1 -1
  60. package/dist/utils/base-path.d.ts +12 -14
  61. package/dist/utils/base-path.js +75 -82
  62. package/dist/utils/compress.d.ts +9 -11
  63. package/dist/utils/compress.js +59 -64
  64. package/dist/utils/context.d.ts +4 -6
  65. package/dist/utils/context.js +17 -22
  66. package/dist/utils/logger.js +2 -2
  67. package/dist/utils/time.d.ts +2 -4
  68. package/dist/utils/time.js +23 -29
  69. package/package.json +18 -8
  70. package/dist/internal/runtimes/bun.d.ts +0 -9
  71. package/dist/internal/runtimes/bun.js +0 -33
  72. package/dist/internal/runtimes/create.d.ts +0 -4
  73. package/dist/internal/runtimes/create.js +0 -9
  74. package/dist/internal/runtimes/node.d.ts +0 -9
  75. package/dist/internal/runtimes/node.js +0 -31
@@ -0,0 +1,8 @@
1
+ export class BoundaryError extends Error {
2
+ digest;
3
+ constructor(digest) {
4
+ super();
5
+ if (digest)
6
+ this.digest = digest;
7
+ }
8
+ }
@@ -1,4 +1,4 @@
1
- import type { BrowserRouter } from './shared.js';
1
+ import * as BrowserRouter from './shared.js';
2
2
  /**
3
3
  * A small in-memory mirror of browser history used by BrowserRouter.
4
4
  * It keeps local entries/index in sync with push/replace/go and
@@ -1,4 +1,4 @@
1
- import { BrowserRouter } from './shared.js';
1
+ import * as BrowserRouter from './shared.js';
2
2
  type AnchorProps = React.ComponentPropsWithRef<'a'> & {
3
3
  href: string;
4
4
  };
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useEffect, useRef } from 'react';
4
- import { BrowserRouter } from './shared.js';
4
+ import * as BrowserRouter from './shared.js';
5
5
  import { useRouter } from './use-router.js';
6
6
  function guard(path, prefetcher) {
7
7
  const connection = window.navigator.connection;
@@ -1,6 +1,6 @@
1
1
  import type { RscPayload } from '../env/rsc.js';
2
- import { BrowserRouter } from './shared.js';
3
- export { BrowserRouter } from './shared.js';
2
+ import * as BrowserRouter from './shared.js';
3
+ export * as BrowserRouter from './shared.js';
4
4
  export declare const BrowserRouterContext: import("react").Context<BrowserRouter.Context>;
5
5
  export declare function BrowserRouterProvider({ children, setPayload, isNavigating, url }: {
6
6
  children: React.ReactNode;
@@ -3,11 +3,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useCallback, useEffect, useMemo, useRef } from 'react';
4
4
  import { createFromFetch } from '@vitejs/plugin-rsc/browser';
5
5
  import { Logger } from '../../utils/logger.js';
6
- import { Solas } from '../../solas.js';
6
+ import * as Events from '../../events.js';
7
7
  import { BrowserRouterHistory } from './history.js';
8
8
  import { ResponseCache } from './response-cache.js';
9
- import { BrowserRouter } from './shared.js';
10
- export { BrowserRouter } from './shared.js';
9
+ import * as BrowserRouter from './shared.js';
10
+ export * as BrowserRouter from './shared.js';
11
11
  export const BrowserRouterContext = createContext({
12
12
  go: async () => '',
13
13
  prefetch: () => { },
@@ -98,7 +98,7 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
98
98
  history.current.replaceState(resolvedPath);
99
99
  }
100
100
  setPayload?.(payload);
101
- window.dispatchEvent(new CustomEvent(Solas.Events.names.NAVIGATION, {
101
+ window.dispatchEvent(new CustomEvent(Events.names.NAVIGATION, {
102
102
  detail: { path: resolvedPath },
103
103
  }));
104
104
  return resolvedPath;
@@ -107,7 +107,7 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
107
107
  if (err instanceof Error && err.name === 'AbortError') {
108
108
  return path;
109
109
  }
110
- window.dispatchEvent(new CustomEvent(Solas.Events.names.NAVIGATION_ERROR, {
110
+ window.dispatchEvent(new CustomEvent(Events.names.NAVIGATION_ERROR, {
111
111
  detail: {
112
112
  path,
113
113
  error: err instanceof Error ? err.message : Logger.print(err),
@@ -1,191 +1,189 @@
1
- import { Solas } from '../../solas.js';
2
- export declare namespace BrowserRouter {
3
- export type Params = Record<string, string>;
4
- export type Query = Record<string, string | number | boolean>;
5
- export type Path = keyof Solas.Routes & string;
6
- type Replace = {
7
- replace?: boolean;
8
- };
9
- export type GoOptions = {
10
- replace?: boolean;
11
- query?: Query;
12
- params?: Params;
13
- };
14
- export type Context = {
15
- go: BrowserRouter.Go;
16
- prefetch: (path: string) => void;
17
- refresh: BrowserRouter.Refresh;
18
- isNavigating: boolean;
19
- url: {
20
- pathname: string;
21
- search?: string;
22
- hash?: string;
23
- };
24
- history: {
25
- entries: HistoryEntry[];
26
- index: number;
27
- };
28
- };
29
- export type HistoryEntry = {
1
+ import * as Solas from '../../solas.js';
2
+ export type Params = Record<string, string>;
3
+ export type Query = Record<string, string | number | boolean>;
4
+ export type Path = keyof Solas.Routes & string;
5
+ type Replace = {
6
+ replace?: boolean;
7
+ };
8
+ export type GoOptions = {
9
+ replace?: boolean;
10
+ query?: Query;
11
+ params?: Params;
12
+ };
13
+ export type Context = {
14
+ go: Go;
15
+ prefetch: (path: string) => void;
16
+ refresh: Refresh;
17
+ isNavigating: boolean;
18
+ url: {
30
19
  pathname: string;
31
- search: string;
32
- hash: string;
20
+ search?: string;
21
+ hash?: string;
33
22
  };
34
- /**
35
- * These targets are used as-is. They are not matched against the route table,
36
- * so this covers normal external URLs and hash-only links
37
- */
38
- export type ExternalTarget = `${string}:${string}` | `//${string}` | `#${string}`;
39
- export function isHashOnlyTarget(target: string): boolean;
40
- export function isExternalTarget(target: string, origin: string): boolean;
41
- /**
42
- * Turn a route pattern into the real path shape a caller can use. In practice,
43
- * every ':param' or '*' part becomes a plain string slot
44
- *
45
- * @example
46
- * ```ts
47
- * // '/p/:id' becomes '/p/${string}'
48
- * // '/test/*' becomes '/test/${string}'
49
- * // '/posts' stays '/posts'
50
- * ```
51
- *
52
- * @example
53
- * ```ts
54
- * type A = ResolvedPath<'/posts/:id'>
55
- * // '/posts/${string}'
56
- * ```
57
- *
58
- * @example
59
- * ```ts
60
- * type B = ResolvedPath<'/docs/*'>
61
- * // '/docs/${string}'
62
- * ```
63
- */
64
- export type ResolvedPath<TPath extends string> = TPath extends `${infer Start}:${string}/${infer Rest}` ? `${Start}${string}/${ResolvedPath<Rest>}` : TPath extends `${infer Start}:${string}` ? `${Start}${string}` : TPath extends `${infer Start}*${infer Rest}` ? `${Start}${string}${ResolvedPath<Rest>}` : TPath;
65
- /**
66
- * Once we have a real path, also allow the usual query-string and hash forms
67
- *
68
- * @example
69
- * ```ts
70
- * type A = TargetSuffix<'/posts/123'>
71
- * // '/posts/123' | '/posts/123?${string}' | '/posts/123#${string}' | '/posts/123?${string}#${string}'
72
- * ```
73
- */
74
- export type TargetSuffix<TPath extends string> = TPath | `${TPath}?${string}` | `${TPath}#${string}` | `${TPath}?${string}#${string}`;
75
- /**
76
- * This is the final string form a caller can navigate to. It can be an external
77
- * URL, or a concrete URL that matches one of the known routes
78
- *
79
- * @example
80
- * ```ts
81
- * type A = Target
82
- * // 'https://example.com'
83
- * // '#intro'
84
- * // '/posts/123'
85
- * // '/posts/123?draft=true'
86
- * ```
87
- */
88
- export type Target = ExternalTarget | TargetSuffix<ResolvedPath<Path>>;
89
- /**
90
- * Extra options for callers who already have a finished target string
91
- *
92
- * @example
93
- * ```ts
94
- * const a: TargetConfig = { query: { page: 2 } }
95
- * // params is rejected here because the path is already complete
96
- * ```
97
- */
98
- type TargetConfig = {
99
- params?: never;
100
- query?: Query;
23
+ history: {
24
+ entries: HistoryEntry[];
25
+ index: number;
101
26
  };
102
- /**
103
- * Extra options for callers who pass a route pattern and params separately.
104
- * If the route definition says that route needs params, this type makes
105
- * those params required. If the route has no params, it rejects them
106
- *
107
- * @example
108
- * ```ts
109
- * // if Solas.Routes['/posts/:id'] is { params: { id: string } }
110
- * type A = PatternConfig<'/posts/:id'>
111
- * // { query?: Query } & { params: { id: string } }
112
- * ```
113
- *
114
- * @example
115
- * ```ts
116
- * // if Solas.Routes['/about'] has no params field
117
- * type B = PatternConfig<'/about'>
118
- * // { query?: Query } & { params?: never }
119
- * ```
120
- */
121
- type PatternConfig<TPath extends Path> = {
122
- query?: Query;
123
- } & (Solas.Routes[TPath] extends {
124
- params: infer TParams extends Params;
125
- } ? {
126
- params: TParams;
127
- } : {
128
- params?: never;
129
- });
130
- /**
131
- * Typed <Link /> props, using `href` instead of the internal `to` name
132
- *
133
- * `query` is always allowed
134
- * `params` are only allowed when `href` is a known route pattern
135
- *
136
- * @example
137
- * ```ts
138
- * const a: LinkProps = { href: '/posts/:id', params: { id: '123' } }
139
- * const b: LinkProps = { href: '/posts/123?draft=true' }
140
- * ```
141
- */
142
- export type LinkProps = ({
143
- href: Target;
144
- } & TargetConfig) | (keyof Solas.Routes extends never ? never : {
145
- [TPath in Path]: {
146
- href: TPath;
147
- } & PatternConfig<TPath>;
148
- }[Path]);
149
- /**
150
- * Typed input for router.go(), using the same route rules as <Link />
151
- *
152
- * @example
153
- * ```ts
154
- * go('/p/post-2')
155
- * go('/?foo=bar', { replace: true })
156
- * ```
157
- *
158
- * @example
159
- * ```ts
160
- * go('/p/:id', { params: { id: 'post-2' }, replace: true })
161
- * ```
162
- *
163
- * The last overload is the fallback for plain `string` values. The
164
- * `string extends TTo` check stops that fallback from taking over
165
- * when TypeScript already knows the caller passed a more specific
166
- * string literal
167
- *
168
- * @example
169
- * ```ts
170
- * declare const dynamicPath: string
171
- * go(dynamicPath, { replace: true })
172
- * ```
173
- */
174
- export type Go = {
175
- (to: number): Promise<string>;
176
- <TTo extends Path>(to: TTo, opts?: PatternConfig<TTo> & Replace): Promise<string>;
177
- <TTo extends Target>(to: TTo, opts?: TargetConfig & Replace): Promise<string>;
178
- <TTo extends string>(to: string extends TTo ? TTo : never, opts?: GoOptions): Promise<string>;
179
- };
180
- export type Refresh = () => Promise<string>;
181
- /**
182
- * Convert a route pattern and params into a real path string. This is used internally
183
- * to implement <Link /> and router.go
184
- */
185
- export function toTarget(path: string, params?: Record<string, string>, query?: Query): string;
186
- export {};
187
- }
27
+ };
28
+ export type HistoryEntry = {
29
+ pathname: string;
30
+ search: string;
31
+ hash: string;
32
+ };
33
+ /**
34
+ * These targets are used as-is. They are not matched against the route table,
35
+ * so this covers normal external URLs and hash-only links
36
+ */
37
+ export type ExternalTarget = `${string}:${string}` | `//${string}` | `#${string}`;
38
+ export declare function isHashOnlyTarget(target: string): boolean;
39
+ export declare function isExternalTarget(target: string, origin: string): boolean;
40
+ /**
41
+ * Turn a route pattern into the real path shape a caller can use. In practice,
42
+ * every ':param' or '*' part becomes a plain string slot
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // '/p/:id' becomes '/p/${string}'
47
+ * // '/test/*' becomes '/test/${string}'
48
+ * // '/posts' stays '/posts'
49
+ * ```
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * type A = ResolvedPath<'/posts/:id'>
54
+ * // '/posts/${string}'
55
+ * ```
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * type B = ResolvedPath<'/docs/*'>
60
+ * // '/docs/${string}'
61
+ * ```
62
+ */
63
+ export type ResolvedPath<TPath extends string> = TPath extends `${infer Start}:${string}/${infer Rest}` ? `${Start}${string}/${ResolvedPath<Rest>}` : TPath extends `${infer Start}:${string}` ? `${Start}${string}` : TPath extends `${infer Start}*${infer Rest}` ? `${Start}${string}${ResolvedPath<Rest>}` : TPath;
64
+ /**
65
+ * Once we have a real path, also allow the usual query-string and hash forms
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * type A = TargetSuffix<'/posts/123'>
70
+ * // '/posts/123' | '/posts/123?${string}' | '/posts/123#${string}' | '/posts/123?${string}#${string}'
71
+ * ```
72
+ */
73
+ export type TargetSuffix<TPath extends string> = TPath | `${TPath}?${string}` | `${TPath}#${string}` | `${TPath}?${string}#${string}`;
74
+ /**
75
+ * This is the final string form a caller can navigate to. It can be an external
76
+ * URL, or a concrete URL that matches one of the known routes
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * type A = Target
81
+ * // 'https://example.com'
82
+ * // '#intro'
83
+ * // '/posts/123'
84
+ * // '/posts/123?draft=true'
85
+ * ```
86
+ */
87
+ export type Target = ExternalTarget | TargetSuffix<ResolvedPath<Path>>;
88
+ /**
89
+ * Extra options for callers who already have a finished target string
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * const a: TargetConfig = { query: { page: 2 } }
94
+ * // params is rejected here because the path is already complete
95
+ * ```
96
+ */
97
+ type TargetConfig = {
98
+ params?: never;
99
+ query?: Query;
100
+ };
101
+ /**
102
+ * Extra options for callers who pass a route pattern and params separately.
103
+ * If the route definition says that route needs params, this type makes
104
+ * those params required. If the route has no params, it rejects them
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * // if Solas.Routes['/posts/:id'] is { params: { id: string } }
109
+ * type A = PatternConfig<'/posts/:id'>
110
+ * // { query?: Query } & { params: { id: string } }
111
+ * ```
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * // if Solas.Routes['/about'] has no params field
116
+ * type B = PatternConfig<'/about'>
117
+ * // { query?: Query } & { params?: never }
118
+ * ```
119
+ */
120
+ type PatternConfig<TPath extends Path> = {
121
+ query?: Query;
122
+ } & (Solas.Routes[TPath] extends {
123
+ params: infer TParams extends Params;
124
+ } ? {
125
+ params: TParams;
126
+ } : {
127
+ params?: never;
128
+ });
129
+ /**
130
+ * Typed <Link /> props, using `href` instead of the internal `to` name
131
+ *
132
+ * `query` is always allowed
133
+ * `params` are only allowed when `href` is a known route pattern
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * const a: LinkProps = { href: '/posts/:id', params: { id: '123' } }
138
+ * const b: LinkProps = { href: '/posts/123?draft=true' }
139
+ * ```
140
+ */
141
+ export type LinkProps = ({
142
+ href: Target;
143
+ } & TargetConfig) | (keyof Solas.Routes extends never ? never : {
144
+ [TPath in Path]: {
145
+ href: TPath;
146
+ } & PatternConfig<TPath>;
147
+ }[Path]);
148
+ /**
149
+ * Typed input for router.go(), using the same route rules as <Link />
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * go('/p/post-2')
154
+ * go('/?foo=bar', { replace: true })
155
+ * ```
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * go('/p/:id', { params: { id: 'post-2' }, replace: true })
160
+ * ```
161
+ *
162
+ * The last overload is the fallback for plain `string` values. The
163
+ * `string extends TTo` check stops that fallback from taking over
164
+ * when TypeScript already knows the caller passed a more specific
165
+ * string literal
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * declare const dynamicPath: string
170
+ * go(dynamicPath, { replace: true })
171
+ * ```
172
+ */
173
+ export type Go = {
174
+ (to: number): Promise<string>;
175
+ <TTo extends Path>(to: TTo, opts?: PatternConfig<TTo> & Replace): Promise<string>;
176
+ <TTo extends Target>(to: TTo, opts?: TargetConfig & Replace): Promise<string>;
177
+ <TTo extends string>(to: string extends TTo ? TTo : never, opts?: GoOptions): Promise<string>;
178
+ };
179
+ export type Refresh = () => Promise<string>;
180
+ /**
181
+ * Convert a route pattern and params into a real path string. This is used internally
182
+ * to implement <Link /> and router.go
183
+ */
184
+ export declare function toTarget(path: string, params?: Record<string, string>, query?: Query): string;
188
185
  /**
189
186
  * Apply the base path to a target string when needed
190
187
  */
191
188
  export declare function withBase(target: string): string;
189
+ export {};
@@ -1,72 +1,65 @@
1
- import { BasePath } from '../../utils/base-path.js';
2
- const BASE_PATH = BasePath.normalise(import.meta.env.BASE_URL);
3
- export { BrowserRouter };
4
- var BrowserRouter;
5
- (function (BrowserRouter) {
6
- function isHashOnlyTarget(target) {
7
- return target.startsWith('#');
1
+ import { applyBasePath, normaliseBasePath } from '../../utils/base-path.js';
2
+ const BASE_PATH = normaliseBasePath(import.meta.env.BASE_URL);
3
+ export function isHashOnlyTarget(target) {
4
+ return target.startsWith('#');
5
+ }
6
+ export function isExternalTarget(target, origin) {
7
+ if (isHashOnlyTarget(target))
8
+ return false;
9
+ try {
10
+ return new URL(target, origin).origin !== origin;
8
11
  }
9
- BrowserRouter.isHashOnlyTarget = isHashOnlyTarget;
10
- function isExternalTarget(target, origin) {
11
- if (isHashOnlyTarget(target))
12
- return false;
13
- try {
14
- return new URL(target, origin).origin !== origin;
15
- }
16
- catch {
17
- return false;
18
- }
12
+ catch {
13
+ return false;
19
14
  }
20
- BrowserRouter.isExternalTarget = isExternalTarget;
21
- /**
22
- * Convert a route pattern and params into a real path string. This is used internally
23
- * to implement <Link /> and router.go
24
- */
25
- function toTarget(path, params, query) {
26
- const used = new Set();
27
- // first, replace all the :param parts with the corresponding params
28
- let to = path.replaceAll(/:([A-Za-z0-9_]+)/g, (_, key) => {
29
- const value = params?.[key];
30
- if (value == null) {
31
- throw new Error(`[Link]: missing route param: ${key}`);
32
- }
33
- used.add(key);
34
- return encodeURIComponent(value);
35
- });
36
- if (to.includes('*')) {
37
- const remaining = Object.entries(params ?? {}).filter(([key]) => !used.has(key));
38
- if (remaining.length !== 1) {
39
- throw new Error('[Link]: wildcard routes require exactly one unmatched param');
40
- }
41
- to = to.replace('*', remaining[0][1].split('/').map(encodeURIComponent).join('/'));
15
+ }
16
+ /**
17
+ * Convert a route pattern and params into a real path string. This is used internally
18
+ * to implement <Link /> and router.go
19
+ */
20
+ export function toTarget(path, params, query) {
21
+ const used = new Set();
22
+ // first, replace all the :param parts with the corresponding params
23
+ let to = path.replaceAll(/:([A-Za-z0-9_]+)/g, (_, key) => {
24
+ const value = params?.[key];
25
+ if (value == null) {
26
+ throw new Error(`[Link]: missing route param: ${key}`);
42
27
  }
43
- if (!query)
44
- return withBase(to);
45
- const hashIndex = to.indexOf('#');
46
- const hash = hashIndex >= 0 ? to.slice(hashIndex) : '';
47
- const pathWithSearch = hashIndex >= 0 ? to.slice(0, hashIndex) : to;
48
- const searchIndex = pathWithSearch.indexOf('?');
49
- const pathname = searchIndex >= 0 ? pathWithSearch.slice(0, searchIndex) : pathWithSearch;
50
- const currentSearch = searchIndex >= 0 ? pathWithSearch.slice(searchIndex + 1) : '';
51
- const search = new URLSearchParams(currentSearch);
52
- for (const [key, value] of Object.entries(query)) {
53
- search.set(key, String(value));
28
+ used.add(key);
29
+ return encodeURIComponent(value);
30
+ });
31
+ if (to.includes('*')) {
32
+ const remaining = Object.entries(params ?? {}).filter(([key]) => !used.has(key));
33
+ if (remaining.length !== 1) {
34
+ throw new Error('[Link]: wildcard routes require exactly one unmatched param');
54
35
  }
55
- const value = search.toString();
56
- return withBase(`${pathname}${value.length > 0 ? `?${value}` : ''}${hash}`);
36
+ to = to.replace('*', remaining[0][1].split('/').map(encodeURIComponent).join('/'));
57
37
  }
58
- BrowserRouter.toTarget = toTarget;
59
- })(BrowserRouter || (BrowserRouter = {}));
38
+ if (!query)
39
+ return withBase(to);
40
+ const hashIndex = to.indexOf('#');
41
+ const hash = hashIndex >= 0 ? to.slice(hashIndex) : '';
42
+ const pathWithSearch = hashIndex >= 0 ? to.slice(0, hashIndex) : to;
43
+ const searchIndex = pathWithSearch.indexOf('?');
44
+ const pathname = searchIndex >= 0 ? pathWithSearch.slice(0, searchIndex) : pathWithSearch;
45
+ const currentSearch = searchIndex >= 0 ? pathWithSearch.slice(searchIndex + 1) : '';
46
+ const search = new URLSearchParams(currentSearch);
47
+ for (const [key, value] of Object.entries(query)) {
48
+ search.set(key, String(value));
49
+ }
50
+ const value = search.toString();
51
+ return withBase(`${pathname}${value.length > 0 ? `?${value}` : ''}${hash}`);
52
+ }
60
53
  /**
61
54
  * Apply the base path to a target string when needed
62
55
  */
63
56
  export function withBase(target) {
64
- if (BrowserRouter.isHashOnlyTarget(target))
57
+ if (isHashOnlyTarget(target))
65
58
  return target;
66
59
  if (target.startsWith('//') || /^[A-Za-z][A-Za-z\d+.-]*:/.test(target))
67
60
  return target;
68
61
  const suffixIndex = target.search(/[?#]/);
69
62
  const pathname = suffixIndex === -1 ? target : target.slice(0, suffixIndex);
70
63
  const suffix = suffixIndex === -1 ? '' : target.slice(suffixIndex);
71
- return `${BasePath.apply(pathname || '/', BASE_PATH)}${suffix}`;
64
+ return `${applyBasePath(pathname || '/', BASE_PATH)}${suffix}`;
72
65
  }
@@ -1 +1 @@
1
- export declare function useRouter(): import("./shared.js").BrowserRouter.Context;
1
+ export declare function useRouter(): import("./shared.js").Context;
@@ -1,14 +1,14 @@
1
1
  import { useMemo, useSyncExternalStore } from 'react';
2
- import { Solas } from '../../solas.js';
2
+ import * as Events from '../../events.js';
3
3
  import { useRouter } from '../browser-router/use-router.js';
4
4
  export function useSearchParams() {
5
5
  const { url } = useRouter();
6
6
  const search = useSyncExternalStore(fn => {
7
7
  window.addEventListener('popstate', fn);
8
- window.addEventListener(Solas.Events.names.NAVIGATION, fn);
8
+ window.addEventListener(Events.names.NAVIGATION, fn);
9
9
  return () => {
10
10
  window.removeEventListener('popstate', fn);
11
- window.removeEventListener(Solas.Events.names.NAVIGATION, fn);
11
+ window.removeEventListener(Events.names.NAVIGATION, fn);
12
12
  };
13
13
  }, () => window.location.search, () => url?.search);
14
14
  return useMemo(() => new URLSearchParams(search), [search]);