@jk2908/solas 0.4.2 → 0.4.4
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/CHANGELOG.md +13 -0
- package/README.md +111 -17
- package/dist/internal/browser-router/link.js +2 -0
- package/dist/internal/{prefetcher.d.ts → browser-router/response-cache.d.ts} +12 -5
- package/dist/internal/{prefetcher.js → browser-router/response-cache.js} +11 -4
- package/dist/internal/browser-router/router.d.ts +1 -0
- package/dist/internal/browser-router/router.js +45 -13
- package/dist/internal/browser-router/shared.js +1 -0
- package/dist/internal/browser-router/use-router.d.ts +1 -0
- package/dist/internal/codegen/maps.js +7 -5
- package/dist/utils/export-reader.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.4 - 2026-05-29
|
|
4
|
+
|
|
5
|
+
- Added `router.refresh()` to the browser router, and made it clear that it clears the current route cache before fetching a fresh RSC payload.
|
|
6
|
+
- Reworked browser-router response caching so prefetched RSC responses can be reused by later navigations without a second fetch.
|
|
7
|
+
- Documented client routing and generated route typing in the README, including `useRouter()`, `router.go()`, `router.prefetch()`, `router.refresh()`, `Link` prefetch behaviour, and typed `Route.Metadata`/`Route.StaticParams` usage.
|
|
8
|
+
- Added a refresh demo route to the basic example app for manual regression testing.
|
|
9
|
+
|
|
10
|
+
## 0.4.3 - 2026-05-27
|
|
11
|
+
|
|
12
|
+
- Updated README docs to show that `dynamic()` must be awaited in request-time deferred `ppr` usage examples.
|
|
13
|
+
- Clarified route docs for `+endpoint.ts`, including that endpoint files can be placed anywhere in `app/` and how GET requests are resolved when `+page.tsx` and `+endpoint.ts` share a route.
|
|
14
|
+
- Tightened README language around experimental status, `url`, and `trustedOrigins`/CSRF guidance.
|
|
15
|
+
|
|
3
16
|
## 0.4.2 - 2026-05-22
|
|
4
17
|
|
|
5
18
|
- Changed `precompress` to default to `false`, so Solas no longer emits precompressed build output unless you opt in.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Solas is a minimal React meta-framework powered by Vite, created for experimenting with routing, streaming, and prerendering with React Server Components.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Solas is experimental and currently has no automated test suite, so expect rough edges.
|
|
6
6
|
|
|
7
7
|
Solas currently requires Bun 1.2+ on your `PATH`. You can still manage dependencies with `npm`, `pnpm`, or `yarn`, but the Solas CLI and Vite plugin runtime use Bun APIs directly.
|
|
8
8
|
|
|
@@ -56,7 +56,7 @@ Use these filename conventions:
|
|
|
56
56
|
|
|
57
57
|
- `+layout.tsx`: shared layout for a route branch.
|
|
58
58
|
- `+page.tsx`: page component for a route.
|
|
59
|
-
- `+endpoint.ts`: request handler for
|
|
59
|
+
- `+endpoint.ts`: request handler. Can be placed in any folder and responds to all HTTP methods for its route path.
|
|
60
60
|
- `+middleware.ts`: middleware that runs for the current route branch and is inherited by child routes. Parent and child middleware stack together.
|
|
61
61
|
- `+loading.tsx`: loading fallback inherited by child routes.
|
|
62
62
|
- `+401.tsx`: boundary for unauthorised responses in the current route branch and its children.
|
|
@@ -68,6 +68,107 @@ Nested folders create nested routes. Dynamic segments use `[param]`, and catch-a
|
|
|
68
68
|
|
|
69
69
|
Status boundaries follow the same override pattern as layouts: a child route uses the nearest matching boundary file above it, and a more specific boundary replaces a parent one.
|
|
70
70
|
|
|
71
|
+
If a route has both `+page.tsx` and `+endpoint.ts`, Solas selects the GET handler by `Accept` header:
|
|
72
|
+
|
|
73
|
+
- `Accept: text/html` or `text/x-component`: render `+page.tsx`
|
|
74
|
+
- other GET requests (for example `application/json`): run `+endpoint.ts` `GET`
|
|
75
|
+
|
|
76
|
+
Non-GET methods (`POST`, `PUT`, `PATCH`, `DELETE`) always run `+endpoint.ts`.
|
|
77
|
+
|
|
78
|
+
## Client Routing
|
|
79
|
+
|
|
80
|
+
Import client navigation helpers from `@jk2908/solas/router`.
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { Link, useRouter } from '@jk2908/solas/router'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Solas generates route types for your app. `Link` and `router.go(...)` use those generated route types for autocomplete and type checking:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<Link href="/posts" />
|
|
90
|
+
<Link href="/p/:id" params={{ id: 'post-1' }} />
|
|
91
|
+
|
|
92
|
+
await router.go('/posts')
|
|
93
|
+
await router.go('/p/:id', { params: { id: 'post-1' } })
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
That gives you:
|
|
97
|
+
|
|
98
|
+
- autocomplete for known route paths
|
|
99
|
+
- required params for dynamic routes like `/p/:id`
|
|
100
|
+
- rejected params for static routes that do not accept them
|
|
101
|
+
- typed query and navigation options on `router.go(...)`
|
|
102
|
+
|
|
103
|
+
Use `Link` for same-origin app navigation. Prefetching is opt-in:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<Link href="/posts">Posts</Link>
|
|
107
|
+
<Link href="/posts" prefetch="intent">Prefetch on focus or touch</Link>
|
|
108
|
+
<Link href="/posts" prefetch="hover">Prefetch on hover</Link>
|
|
109
|
+
<Link href="/p/:id" params={{ id: 'post-1' }}>Typed params</Link>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`prefetch="none"` is the default. Solas does not automatically prefetch routes unless you opt in with `Link` or call `router.prefetch(...)` yourself.
|
|
113
|
+
|
|
114
|
+
Use `useRouter()` inside client components for programmatic navigation, prefetching, and refreshing the current route:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
'use client'
|
|
118
|
+
|
|
119
|
+
import { useRouter } from '@jk2908/solas/router'
|
|
120
|
+
|
|
121
|
+
export function Controls() {
|
|
122
|
+
const router = useRouter()
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<>
|
|
126
|
+
<button type="button" onClick={() => void router.go('/posts')}>
|
|
127
|
+
Go to posts
|
|
128
|
+
</button>
|
|
129
|
+
|
|
130
|
+
<button type="button" onMouseEnter={() => router.prefetch('/posts')}>
|
|
131
|
+
Prefetch posts
|
|
132
|
+
</button>
|
|
133
|
+
|
|
134
|
+
<button type="button" onClick={() => void router.refresh()}>
|
|
135
|
+
Refresh current route
|
|
136
|
+
</button>
|
|
137
|
+
</>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`router.go(...)` accepts route params and query values using the same typed route rules as `Link`:
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
await router.go('/p/:id', {
|
|
146
|
+
params: { id: 'post-2' },
|
|
147
|
+
query: { draft: true },
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Those same generated route types can also be reused outside navigation helpers when you want route params to stay typed in page exports:
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import type { Route, Solas } from '@jk2908/solas'
|
|
155
|
+
|
|
156
|
+
export const metadata: Route.Metadata<Solas.Routes['/writing/:slug']> = ({ params }) => {
|
|
157
|
+
const post = allPosts?.find(p => p.__mdsrc.slug === params?.slug)
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
title: post?.title ?? 'Post not found',
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const params: Route.StaticParams<Solas.Routes['/writing/:slug']> = () =>
|
|
165
|
+
allPosts?.map(p => ({ slug: p.__mdsrc.slug })) ?? []
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
That keeps your route params aligned across links, imperative navigation, metadata, and static params.
|
|
169
|
+
|
|
170
|
+
`router.refresh()` clears the cached RSC response for the current path and fetches a fresh payload, so it is most useful for routes that render request-time data. `router.isNavigating` exposes pending client-side navigation state.
|
|
171
|
+
|
|
71
172
|
## Config
|
|
72
173
|
|
|
73
174
|
All Solas options are passed to `solas()` inside `defineConfig`.
|
|
@@ -81,14 +182,9 @@ Solas resolves it in this order:
|
|
|
81
182
|
- the `url` option passed to `solas()`
|
|
82
183
|
- `VITE_APP_URL`
|
|
83
184
|
|
|
84
|
-
|
|
185
|
+
Solas exposes the resolved value as `import.meta.env.VITE_APP_URL`. If `url` is set, prerender also uses it as the request origin for build-time renders.
|
|
85
186
|
|
|
86
|
-
|
|
87
|
-
- Solas exposes the resolved value as `import.meta.env.VITE_APP_URL`.
|
|
88
|
-
- If `url` is set, prerender uses it as the request origin for build-time renders.
|
|
89
|
-
- The runtime router does not otherwise require `config.url` for routing to work.
|
|
90
|
-
|
|
91
|
-
In practice, you only need `url` if your app code wants to read the public origin from `import.meta.env.VITE_APP_URL`, or if your prerendered output needs a real public origin.
|
|
187
|
+
In practice, you only need `url` if your app reads `import.meta.env.VITE_APP_URL` or your prerendered output needs a real public origin.
|
|
92
188
|
|
|
93
189
|
If you do want to set it explicitly, this is the shape:
|
|
94
190
|
|
|
@@ -126,9 +222,9 @@ export default defineConfig({
|
|
|
126
222
|
|
|
127
223
|
### `precompress`
|
|
128
224
|
|
|
129
|
-
Use `precompress` to control whether Solas writes compressed build assets.
|
|
225
|
+
Use `precompress` to control whether Solas writes compressed browser-served build assets (like `.js`, `.css`, etc.).
|
|
130
226
|
|
|
131
|
-
Default: `
|
|
227
|
+
Default: `false`
|
|
132
228
|
|
|
133
229
|
```ts
|
|
134
230
|
export default defineConfig({
|
|
@@ -208,7 +304,7 @@ export default function Page() {
|
|
|
208
304
|
}
|
|
209
305
|
|
|
210
306
|
async function Ts() {
|
|
211
|
-
dynamic()
|
|
307
|
+
await dynamic()
|
|
212
308
|
return <div>{Date.now()}</div>
|
|
213
309
|
}
|
|
214
310
|
```
|
|
@@ -294,12 +390,10 @@ Use `trustedOrigins` to allow specific origins to make cross-origin browser subm
|
|
|
294
390
|
|
|
295
391
|
Default: `[]`
|
|
296
392
|
|
|
297
|
-
Solas protects server actions and `+endpoint` handlers against CSRF.
|
|
298
|
-
|
|
299
|
-
Server actions are always `POST` requests. `+endpoint` handlers are protected on `POST`, `PUT`, `PATCH`, and `DELETE` requests.
|
|
300
|
-
|
|
301
393
|
By default, only same-origin browser requests are allowed. Add a trusted origin when a third-party service needs to submit through the user's browser, such as a payment gateway or identity provider.
|
|
302
394
|
|
|
395
|
+
This setting controls which cross-origin browser sources are allowed for unsafe requests. See Security > CSRF for enforcement details.
|
|
396
|
+
|
|
303
397
|
Each value must be a complete origin including protocol:
|
|
304
398
|
|
|
305
399
|
```ts
|
|
@@ -312,7 +406,7 @@ export default defineConfig({
|
|
|
312
406
|
})
|
|
313
407
|
```
|
|
314
408
|
|
|
315
|
-
Only add origins you completely trust.
|
|
409
|
+
Only add origins you completely trust.
|
|
316
410
|
|
|
317
411
|
### `sitemap`
|
|
318
412
|
|
|
@@ -16,6 +16,8 @@ function guard(path, prefetcher) {
|
|
|
16
16
|
export function Link({ children, href, params, prefetch = 'none', query, ...rest }) {
|
|
17
17
|
const { go, prefetch: prefetcher } = useRouter();
|
|
18
18
|
const timer = useRef(null);
|
|
19
|
+
// track whether the link is meant to be handled by the router, to avoid
|
|
20
|
+
// unnecessary prefetching and event handling for external links
|
|
19
21
|
const handled = useRef(false);
|
|
20
22
|
const target = BrowserRouter.toTarget(href, params, query);
|
|
21
23
|
// clear any pending hover-prefetch timer on unmount
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
export declare namespace
|
|
1
|
+
export declare namespace ResponseCache {
|
|
2
2
|
type Entry = {
|
|
3
3
|
promise: Promise<Response>;
|
|
4
4
|
timeoutId: ReturnType<typeof setTimeout>;
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* A simple in-memory cache for RSC response promises used by the BrowserRouter.
|
|
9
|
+
* It lets a later navigation reuse a prefetched response for the same path,
|
|
10
|
+
* and helps avoid issuing a second fetch when navigation follows shortly
|
|
11
|
+
* after prefetch. Entries are stored by normalised path with TTL and
|
|
12
|
+
* max size eviction
|
|
13
|
+
*/
|
|
14
|
+
export declare class ResponseCache {
|
|
8
15
|
#private;
|
|
9
16
|
ttl: number;
|
|
10
17
|
maxSize: number;
|
|
@@ -16,7 +23,7 @@ export declare class Prefetcher {
|
|
|
16
23
|
* Converts a url path to a cache key by normalising it
|
|
17
24
|
* against a base url
|
|
18
25
|
*/
|
|
19
|
-
static
|
|
26
|
+
static toCacheKey(path: string, base: string): string | null;
|
|
20
27
|
/**
|
|
21
28
|
* Evicts the oldest entry from the cache
|
|
22
29
|
*/
|
|
@@ -26,8 +33,8 @@ export declare class Prefetcher {
|
|
|
26
33
|
*/
|
|
27
34
|
has(path: string): boolean;
|
|
28
35
|
/**
|
|
29
|
-
* Retrieves a fresh response promise for the given path if it exists
|
|
30
|
-
*
|
|
36
|
+
* Retrieves a fresh response promise for the given path if it exists by
|
|
37
|
+
* cloning the cached response so each consumer gets an unread stream
|
|
31
38
|
*/
|
|
32
39
|
get(path: string): Promise<Response> | undefined;
|
|
33
40
|
/**
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A simple in-memory cache for RSC response promises used by the BrowserRouter.
|
|
3
|
+
* It lets a later navigation reuse a prefetched response for the same path,
|
|
4
|
+
* and helps avoid issuing a second fetch when navigation follows shortly
|
|
5
|
+
* after prefetch. Entries are stored by normalised path with TTL and
|
|
6
|
+
* max size eviction
|
|
7
|
+
*/
|
|
8
|
+
export class ResponseCache {
|
|
2
9
|
#cache = new Map();
|
|
3
10
|
ttl = 60_000;
|
|
4
11
|
maxSize = 32;
|
|
@@ -10,7 +17,7 @@ export class Prefetcher {
|
|
|
10
17
|
* Converts a url path to a cache key by normalising it
|
|
11
18
|
* against a base url
|
|
12
19
|
*/
|
|
13
|
-
static
|
|
20
|
+
static toCacheKey(path, base) {
|
|
14
21
|
try {
|
|
15
22
|
const url = new URL(path, base);
|
|
16
23
|
// hash is client-only and never sent to the server, so exclude it
|
|
@@ -40,8 +47,8 @@ export class Prefetcher {
|
|
|
40
47
|
return this.#cache.has(path);
|
|
41
48
|
}
|
|
42
49
|
/**
|
|
43
|
-
* Retrieves a fresh response promise for the given path if it exists
|
|
44
|
-
*
|
|
50
|
+
* Retrieves a fresh response promise for the given path if it exists by
|
|
51
|
+
* cloning the cached response so each consumer gets an unread stream
|
|
45
52
|
*/
|
|
46
53
|
get(path) {
|
|
47
54
|
const promise = this.#cache.get(path)?.promise;
|
|
@@ -4,12 +4,13 @@ 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
6
|
import { Solas } from '../../solas.js';
|
|
7
|
-
import {
|
|
7
|
+
import { ResponseCache } from './response-cache.js';
|
|
8
8
|
import { BrowserRouter } from './shared.js';
|
|
9
9
|
export { BrowserRouter } from './shared.js';
|
|
10
10
|
export const BrowserRouterContext = createContext({
|
|
11
11
|
go: async () => '',
|
|
12
12
|
prefetch: () => { },
|
|
13
|
+
refresh: () => { },
|
|
13
14
|
isNavigating: false,
|
|
14
15
|
url: {},
|
|
15
16
|
});
|
|
@@ -17,10 +18,18 @@ const DEFAULT_GO_CONFIG = {
|
|
|
17
18
|
replace: false,
|
|
18
19
|
};
|
|
19
20
|
const logger = new Logger();
|
|
20
|
-
const
|
|
21
|
+
const responseCache = new ResponseCache();
|
|
21
22
|
export function BrowserRouterProvider({ children, setPayload, isNavigating = false, url, }) {
|
|
22
23
|
const id = useRef(0);
|
|
23
24
|
const controller = useRef(null);
|
|
25
|
+
/**
|
|
26
|
+
* Navigates to a given path
|
|
27
|
+
*
|
|
28
|
+
* @param to - the target path to navigate to, which can be a route pattern with params or an external URL
|
|
29
|
+
* @param opts - options for navigation, including whether to replace the current history entry and pass query
|
|
30
|
+
* and route params
|
|
31
|
+
* @returns the final path navigated to after any redirects, or the original path if navigation failed
|
|
32
|
+
*/
|
|
24
33
|
const go = useCallback(async (to, opts = {}) => {
|
|
25
34
|
id.current += 1;
|
|
26
35
|
const navigationId = id.current;
|
|
@@ -36,7 +45,7 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
36
45
|
throw new Error('[router.go]: external URLs are not supported. Use <a> instead');
|
|
37
46
|
}
|
|
38
47
|
const url = new URL(target, window.location.origin);
|
|
39
|
-
const key =
|
|
48
|
+
const key = ResponseCache.toCacheKey(url.toString(), window.location.origin);
|
|
40
49
|
if (!key)
|
|
41
50
|
throw new Error('Invalid navigation url');
|
|
42
51
|
path = key;
|
|
@@ -48,7 +57,7 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
48
57
|
window.history.pushState(null, '', path);
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
|
-
let promise =
|
|
60
|
+
let promise = responseCache.get(path);
|
|
52
61
|
existing = promise !== undefined;
|
|
53
62
|
if (!promise) {
|
|
54
63
|
const ctrl = new AbortController();
|
|
@@ -57,7 +66,7 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
57
66
|
headers: { accept: 'text/x-component' },
|
|
58
67
|
signal: ctrl.signal,
|
|
59
68
|
});
|
|
60
|
-
|
|
69
|
+
responseCache.set(path, promise);
|
|
61
70
|
}
|
|
62
71
|
if (navigationId !== id.current)
|
|
63
72
|
return path;
|
|
@@ -65,7 +74,7 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
65
74
|
promise,
|
|
66
75
|
createFromFetch(promise),
|
|
67
76
|
]);
|
|
68
|
-
const resolvedPath =
|
|
77
|
+
const resolvedPath = ResponseCache.toCacheKey(res.url, window.location.origin) ?? path;
|
|
69
78
|
if (navigationId !== id.current)
|
|
70
79
|
return resolvedPath;
|
|
71
80
|
if (resolvedPath !== path) {
|
|
@@ -92,20 +101,42 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
92
101
|
finally {
|
|
93
102
|
if (navigationId === id.current)
|
|
94
103
|
controller.current = null;
|
|
95
|
-
if (!existing)
|
|
96
|
-
|
|
97
|
-
}
|
|
104
|
+
if (!existing)
|
|
105
|
+
responseCache.remove(path);
|
|
98
106
|
}
|
|
99
107
|
return path;
|
|
100
108
|
}, [setPayload]);
|
|
109
|
+
/**
|
|
110
|
+
* Prefetches the RSC response for a given path and caches it for later navigation.
|
|
111
|
+
* Does nothing if a cached response already exists for the path
|
|
112
|
+
*
|
|
113
|
+
* @param path - the target path to prefetch
|
|
114
|
+
* @returns void
|
|
115
|
+
*/
|
|
101
116
|
const prefetch = useCallback((path) => {
|
|
102
|
-
const key =
|
|
117
|
+
const key = ResponseCache.toCacheKey(path, window.location.origin);
|
|
103
118
|
if (!key)
|
|
104
119
|
return;
|
|
105
|
-
if (
|
|
120
|
+
if (responseCache.has(key))
|
|
106
121
|
return;
|
|
107
|
-
|
|
122
|
+
responseCache.set(key, fetch(key, { headers: { Accept: 'text/x-component' } }));
|
|
108
123
|
}, []);
|
|
124
|
+
/**
|
|
125
|
+
* Refreshes the current page by re-fetching the RSC response for the current path and updating the
|
|
126
|
+
* payload. It also clears any cached response for the current path to ensure that the latest
|
|
127
|
+
* version is fetched
|
|
128
|
+
*/
|
|
129
|
+
const refresh = useCallback(() => {
|
|
130
|
+
const currentPath = window.location.pathname + window.location.search;
|
|
131
|
+
const key = ResponseCache.toCacheKey(currentPath, window.location.origin);
|
|
132
|
+
if (!key)
|
|
133
|
+
return;
|
|
134
|
+
if (responseCache.has(key))
|
|
135
|
+
responseCache.remove(key);
|
|
136
|
+
return go(currentPath, {
|
|
137
|
+
replace: true,
|
|
138
|
+
});
|
|
139
|
+
}, [go]);
|
|
109
140
|
useEffect(() => {
|
|
110
141
|
const handler = () => go(BrowserRouter.toTarget(window.location.pathname + window.location.search), {
|
|
111
142
|
replace: true,
|
|
@@ -120,11 +151,12 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
120
151
|
const value = useMemo(() => ({
|
|
121
152
|
go,
|
|
122
153
|
prefetch,
|
|
154
|
+
refresh,
|
|
123
155
|
isNavigating,
|
|
124
156
|
url: {
|
|
125
157
|
pathname: url?.pathname,
|
|
126
158
|
search: url?.search,
|
|
127
159
|
},
|
|
128
|
-
}), [go, prefetch, isNavigating, url]);
|
|
160
|
+
}), [go, prefetch, refresh, isNavigating, url]);
|
|
129
161
|
return _jsx(BrowserRouterContext, { value: value, children: children });
|
|
130
162
|
}
|
|
@@ -24,6 +24,7 @@ var BrowserRouter;
|
|
|
24
24
|
*/
|
|
25
25
|
function toTarget(path, params, query) {
|
|
26
26
|
const used = new Set();
|
|
27
|
+
// first, replace all the :param parts with the corresponding params
|
|
27
28
|
let to = path.replaceAll(/:([A-Za-z0-9_]+)/g, (_, key) => {
|
|
28
29
|
const value = params?.[key];
|
|
29
30
|
if (value == null) {
|
|
@@ -37,8 +37,8 @@ export function writeMaps(imports, modules) {
|
|
|
37
37
|
parts.push(`endpoint: ${toIdentifier(m.endpointId, `endpoint id for ${moduleId}`)}`);
|
|
38
38
|
}
|
|
39
39
|
if (m['401Ids']?.length) {
|
|
40
|
-
const
|
|
41
|
-
parts.push(`'401s': [${
|
|
40
|
+
const unauthorised = toIdentifierList(m['401Ids'], `401s for ${moduleId}`);
|
|
41
|
+
parts.push(`'401s': [${unauthorised}]`);
|
|
42
42
|
}
|
|
43
43
|
if (m['403Ids']?.length) {
|
|
44
44
|
const forbidden = toIdentifierList(m['403Ids'], `403s for ${moduleId}`);
|
|
@@ -70,13 +70,15 @@ export function writeMaps(imports, modules) {
|
|
|
70
70
|
${AUTOGEN_MSG}
|
|
71
71
|
|
|
72
72
|
import type { ImportMap } from '${Solas.Config.PKG_NAME}'
|
|
73
|
-
|
|
73
|
+
|
|
74
|
+
${importLines
|
|
74
75
|
? `
|
|
75
|
-
${importLines}
|
|
76
|
+
${importLines}
|
|
77
|
+
`
|
|
76
78
|
: ''}
|
|
77
79
|
|
|
78
80
|
export const importMap = {
|
|
79
|
-
${entries}
|
|
81
|
+
${entries}
|
|
80
82
|
} as const satisfies ImportMap
|
|
81
83
|
`;
|
|
82
84
|
}
|
|
@@ -99,7 +99,8 @@ export class ExportReader {
|
|
|
99
99
|
// capture one supported literal value (string, number, boolean, null)
|
|
100
100
|
'\\s*=\\s*(?<value>(?:"(?:[^"\\\\]|\\\\.)*"|\'(?:[^\'\\\\]|\\\\.)*\'|\\x60(?:[^\\x60\\\\]|\\\\.)*\\x60|true|false|null|-?\\d+(?:\\.\\d+)?))(?=\\s|;|$)';
|
|
101
101
|
// multiline mode lets ^ match the start of each transpiled line, so the
|
|
102
|
-
// export regex stays anchored to a real statement boundary instead of
|
|
102
|
+
// export regex stays anchored to a real statement boundary instead of
|
|
103
|
+
// the file start
|
|
103
104
|
const text = code.match(new RegExp(source, 'm'))?.groups?.value;
|
|
104
105
|
if (!text)
|
|
105
106
|
return;
|
package/package.json
CHANGED