@jk2908/solas 0.3.0 → 0.3.2
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 +12 -0
- package/dist/cli/build.d.ts +7 -0
- package/dist/cli/build.js +183 -0
- package/dist/cli/dev.d.ts +4 -0
- package/dist/cli/dev.js +13 -0
- package/dist/cli/preview.d.ts +1 -0
- package/dist/cli/preview.js +47 -0
- package/dist/cli.js +4 -238
- package/dist/index.js +2 -0
- package/dist/internal/browser-router/link.d.ts +17 -0
- package/dist/internal/{navigation → browser-router}/link.js +22 -16
- package/dist/internal/browser-router/router.d.ts +184 -0
- package/dist/internal/{router/router-provider.js → browser-router/router.js} +81 -12
- package/dist/internal/{router → browser-router}/use-router.d.ts +1 -1
- package/dist/internal/browser-router/use-router.js +5 -0
- package/dist/internal/{navigation → browser-router}/use-search-params.js +1 -1
- package/dist/internal/build.js +2 -2
- package/dist/internal/codegen/config.js +17 -8
- package/dist/internal/codegen/environments.js +7 -7
- package/dist/internal/codegen/manifest.js +3 -3
- package/dist/internal/codegen/maps.js +11 -15
- package/dist/internal/codegen/types.d.ts +5 -0
- package/dist/internal/codegen/types.js +48 -0
- package/dist/internal/codegen/utils.d.ts +10 -0
- package/dist/internal/codegen/utils.js +27 -2
- package/dist/internal/env/browser.js +6 -6
- package/dist/internal/env/flight.d.ts +29 -0
- package/dist/internal/env/flight.js +187 -0
- package/dist/internal/env/request-context.d.ts +1 -1
- package/dist/internal/env/rsc.d.ts +1 -1
- package/dist/internal/env/rsc.js +23 -28
- package/dist/internal/env/ssr.d.ts +2 -2
- package/dist/internal/env/ssr.js +27 -13
- package/dist/internal/env/utils.js +13 -1
- package/dist/internal/http-router/create-http-router.d.ts +6 -0
- package/dist/internal/{router/create-router.js → http-router/create-http-router.js} +5 -5
- package/dist/internal/{router → http-router}/router.d.ts +9 -9
- package/dist/internal/{router → http-router}/router.js +20 -19
- package/dist/internal/{router → http-router}/utils.d.ts +11 -3
- package/dist/internal/{router → http-router}/utils.js +9 -1
- package/dist/internal/metadata.js +10 -10
- package/dist/internal/prerender.d.ts +4 -9
- package/dist/internal/prerender.js +6 -23
- package/dist/internal/render/head.js +1 -1
- package/dist/internal/render/tree.d.ts +1 -1
- package/dist/internal/render/tree.js +17 -13
- package/dist/internal/{router/resolver.d.ts → resolver.d.ts} +41 -41
- package/dist/internal/{router/resolver.js → resolver.js} +7 -7
- package/dist/internal/server/actions.js +1 -1
- package/dist/internal/server/cookies.d.ts +3 -2
- package/dist/internal/server/cookies.js +4 -3
- package/dist/internal/server/dynamic.d.ts +1 -3
- package/dist/internal/server/dynamic.js +3 -11
- package/dist/internal/server/headers.d.ts +2 -2
- package/dist/internal/server/headers.js +3 -3
- package/dist/internal/server/url.d.ts +2 -2
- package/dist/internal/server/url.js +3 -3
- package/dist/navigation.d.ts +2 -4
- package/dist/navigation.js +2 -4
- package/dist/router.d.ts +3 -4
- package/dist/router.js +3 -4
- package/dist/solas.d.ts +3 -1
- package/dist/solas.js +1 -1
- package/dist/types.d.ts +15 -7
- package/dist/utils/logger.js +1 -1
- package/package.json +2 -7
- package/dist/internal/navigation/link.d.ts +0 -13
- package/dist/internal/router/create-router.d.ts +0 -6
- package/dist/internal/router/router-context.d.ts +0 -15
- package/dist/internal/router/router-context.js +0 -8
- package/dist/internal/router/router-provider.d.ts +0 -10
- package/dist/internal/router/use-router.js +0 -5
- /package/dist/internal/{navigation → browser-router}/use-search-params.d.ts +0 -0
- /package/dist/internal/{router/prefetcher.d.ts → prefetcher.d.ts} +0 -0
- /package/dist/internal/{router/prefetcher.js → prefetcher.js} +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { match as createMatch } from 'path-to-regexp';
|
|
3
3
|
import { Solas } from '../../solas.js';
|
|
4
|
-
import { getAlternatePathname, normalisePathname, toPathPattern } from './utils.js';
|
|
5
4
|
import { HttpException } from '../navigation/http-exception.js';
|
|
6
5
|
import { maybeAction } from '../server/actions.js';
|
|
6
|
+
import { getAlternatePathname, normalisePathname, toPathPattern } from './utils.js';
|
|
7
7
|
/**
|
|
8
8
|
* Handle routing and matching for server requests
|
|
9
9
|
*/
|
|
10
|
-
export class
|
|
10
|
+
export class HttpRouter {
|
|
11
11
|
opts;
|
|
12
12
|
static #matchers = new WeakMap();
|
|
13
13
|
#routes = {
|
|
@@ -54,7 +54,7 @@ export class Router {
|
|
|
54
54
|
const routePath = !path.includes(':') && !path.includes('*')
|
|
55
55
|
? normalisePathname(path, this.opts.trailingSlash ?? 'never')
|
|
56
56
|
: path;
|
|
57
|
-
const segments =
|
|
57
|
+
const segments = HttpRouter.#split(routePath);
|
|
58
58
|
const tokens = [];
|
|
59
59
|
let score = 0;
|
|
60
60
|
let wildcard = false;
|
|
@@ -122,7 +122,7 @@ export class Router {
|
|
|
122
122
|
* Match a path and method, returning params and route
|
|
123
123
|
*/
|
|
124
124
|
match(path, method) {
|
|
125
|
-
for (const candidate of
|
|
125
|
+
for (const candidate of HttpRouter.#candidates(path)) {
|
|
126
126
|
const direct = this.#routes.static.get(`${method}:${candidate}`);
|
|
127
127
|
if (direct)
|
|
128
128
|
return { route: direct, params: {} };
|
|
@@ -133,25 +133,25 @@ export class Router {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
// else dynamic/wildcard match
|
|
136
|
-
const segments =
|
|
136
|
+
const segments = HttpRouter.#split(path);
|
|
137
137
|
// try the leading-static prefix bucket first
|
|
138
138
|
const prefixed = this.#routes.dynamic.byPrefix.get(segments[0] ?? '');
|
|
139
|
-
const prefixedMatch = prefixed ?
|
|
139
|
+
const prefixedMatch = prefixed ? HttpRouter.#pick(prefixed, segments, method) : null;
|
|
140
140
|
if (prefixedMatch)
|
|
141
141
|
return prefixedMatch;
|
|
142
142
|
// if the prefix bucket has no winner, fall back to all dynamic
|
|
143
143
|
// routes with the same segment count
|
|
144
|
-
const dynamicMatch =
|
|
144
|
+
const dynamicMatch = HttpRouter.#pick(this.#routes.dynamic.byLength.get(segments.length) ?? [], segments, method);
|
|
145
145
|
if (dynamicMatch)
|
|
146
146
|
return dynamicMatch;
|
|
147
147
|
// finally check wildcard routes, prefixed first, then fully generic ones
|
|
148
148
|
const wildcardPrefixed = this.#routes.wildcard.byPrefix.get(segments[0] ?? '');
|
|
149
149
|
const wildcardMatch = wildcardPrefixed
|
|
150
|
-
?
|
|
150
|
+
? HttpRouter.#pick(wildcardPrefixed, segments, method)
|
|
151
151
|
: null;
|
|
152
152
|
if (wildcardMatch)
|
|
153
153
|
return wildcardMatch;
|
|
154
|
-
const wildcardFallbackMatch =
|
|
154
|
+
const wildcardFallbackMatch = HttpRouter.#pick(this.#routes.wildcard.fallback, segments, method);
|
|
155
155
|
if (wildcardFallbackMatch)
|
|
156
156
|
return wildcardFallbackMatch;
|
|
157
157
|
// no match
|
|
@@ -192,14 +192,14 @@ export class Router {
|
|
|
192
192
|
// unmatched requests still pass through the shared error hook with the
|
|
193
193
|
// same request metadata shape as matched requests
|
|
194
194
|
return (this.#onError?.(error, Object.assign(req, {
|
|
195
|
-
[Solas.Config.
|
|
195
|
+
[Solas.Config.REQUEST_META_KEY]: { match: null, error, action },
|
|
196
196
|
})) ?? new Response(error.message, { status: error.status }));
|
|
197
197
|
}
|
|
198
198
|
const matched = match;
|
|
199
199
|
// attach routing state to the request once so middleware and handlers can
|
|
200
200
|
// read the same per-request metadata
|
|
201
201
|
const request = Object.assign(req, {
|
|
202
|
-
[Solas.Config.
|
|
202
|
+
[Solas.Config.REQUEST_META_KEY]: { match: matched, action, parsedFormData },
|
|
203
203
|
});
|
|
204
204
|
// global middleware stays outside route middleware by preserving
|
|
205
205
|
// registration order here before composition in #run
|
|
@@ -210,7 +210,7 @@ export class Router {
|
|
|
210
210
|
// normalise unknown throwables so the error hook always receives an Error
|
|
211
211
|
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
|
|
212
212
|
const request = Object.assign(req, {
|
|
213
|
-
[Solas.Config.
|
|
213
|
+
[Solas.Config.REQUEST_META_KEY]: { match, error, action },
|
|
214
214
|
});
|
|
215
215
|
if (this.#onError)
|
|
216
216
|
return this.#onError(error, request);
|
|
@@ -270,7 +270,7 @@ export class Router {
|
|
|
270
270
|
return new Response('Forbidden', { status: 403 });
|
|
271
271
|
}
|
|
272
272
|
// emitted assets are fingerprinted so they can be cached aggressively
|
|
273
|
-
return
|
|
273
|
+
return HttpRouter.serve(filePath, req, config.precompress, {
|
|
274
274
|
'Cache-Control': 'public, immutable, max-age=31536000',
|
|
275
275
|
});
|
|
276
276
|
};
|
|
@@ -343,7 +343,7 @@ export class Router {
|
|
|
343
343
|
* Get or create a path matcher for a route using path-to-regexp
|
|
344
344
|
*/
|
|
345
345
|
static #getMatcher(route) {
|
|
346
|
-
const cached =
|
|
346
|
+
const cached = HttpRouter.#matchers.get(route);
|
|
347
347
|
if (cached)
|
|
348
348
|
return cached;
|
|
349
349
|
// convert route tokens back into a path pattern for path-to-regexp to compile
|
|
@@ -352,7 +352,7 @@ export class Router {
|
|
|
352
352
|
const matcher = createMatch(path, {
|
|
353
353
|
decode: false,
|
|
354
354
|
});
|
|
355
|
-
|
|
355
|
+
HttpRouter.#matchers.set(route, matcher);
|
|
356
356
|
return matcher;
|
|
357
357
|
}
|
|
358
358
|
/**
|
|
@@ -375,7 +375,8 @@ export class Router {
|
|
|
375
375
|
for (let index = 0; index < length; index += 1) {
|
|
376
376
|
// prefer static over dynamic and dynamic over wildcard at the
|
|
377
377
|
// first segment position where the two routes differ
|
|
378
|
-
const diff =
|
|
378
|
+
const diff = HttpRouter.#getTokenRank(a.tokens[index]) -
|
|
379
|
+
HttpRouter.#getTokenRank(b.tokens[index]);
|
|
379
380
|
if (diff !== 0)
|
|
380
381
|
return diff;
|
|
381
382
|
}
|
|
@@ -399,11 +400,11 @@ export class Router {
|
|
|
399
400
|
}
|
|
400
401
|
// skip routes that do not fit this path. Only compare specificity
|
|
401
402
|
// across matched routes
|
|
402
|
-
const params =
|
|
403
|
+
const params = HttpRouter.#fit(route, segments);
|
|
403
404
|
if (!params)
|
|
404
405
|
continue;
|
|
405
406
|
// replace the winner only when this route is strictly more specific
|
|
406
|
-
if (!best ||
|
|
407
|
+
if (!best || HttpRouter.#compare(route, best) > 0) {
|
|
407
408
|
best = route;
|
|
408
409
|
bestParams = params;
|
|
409
410
|
}
|
|
@@ -426,7 +427,7 @@ export class Router {
|
|
|
426
427
|
}
|
|
427
428
|
// defer the actual param extraction to the cached path-to-regexp matcher so
|
|
428
429
|
// dynamic and wildcard params stay consistent with registration
|
|
429
|
-
const matched =
|
|
430
|
+
const matched = HttpRouter.#getMatcher(route)(segments.length ? `/${segments.join('/')}` : '/');
|
|
430
431
|
if (!matched)
|
|
431
432
|
return null;
|
|
432
433
|
return matched.params;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PluginConfig } from '../../types.js';
|
|
2
2
|
export type PathPattern = {
|
|
3
3
|
path: string;
|
|
4
4
|
wildcardNames: Set<string>;
|
|
@@ -14,8 +14,16 @@ export declare function toPathPattern(route: string, paramNames?: string[]): {
|
|
|
14
14
|
/**
|
|
15
15
|
* Apply the configured trailing-slash policy to a pathname
|
|
16
16
|
*/
|
|
17
|
-
export declare function normalisePathname(pathname: string, trailingSlash?:
|
|
17
|
+
export declare function normalisePathname(pathname: string, trailingSlash?: PluginConfig['trailingSlash']): string;
|
|
18
18
|
/**
|
|
19
|
-
* Return the other pathname shape for a non-root route
|
|
19
|
+
* Return the other pathname shape for a non-root route. For use within
|
|
20
|
+
* trailingSlash logic to easily switch between shapes
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* getAlternatePathname('/about') // '/about/'
|
|
25
|
+
* getAlternatePathname('/about/') // '/about'
|
|
26
|
+
* getAlternatePathname('/') // '/'
|
|
27
|
+
* ```
|
|
20
28
|
*/
|
|
21
29
|
export declare function getAlternatePathname(pathname: string): string;
|
|
@@ -53,7 +53,15 @@ export function normalisePathname(pathname, trailingSlash = 'never') {
|
|
|
53
53
|
return pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* Return the other pathname shape for a non-root route
|
|
56
|
+
* Return the other pathname shape for a non-root route. For use within
|
|
57
|
+
* trailingSlash logic to easily switch between shapes
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* getAlternatePathname('/about') // '/about/'
|
|
62
|
+
* getAlternatePathname('/about/') // '/about'
|
|
63
|
+
* getAlternatePathname('/') // '/'
|
|
64
|
+
* ```
|
|
57
65
|
*/
|
|
58
66
|
export function getAlternatePathname(pathname) {
|
|
59
67
|
if (pathname === '/')
|
|
@@ -85,15 +85,6 @@ var Metadata;
|
|
|
85
85
|
metadata.link = [...linkMap.values()];
|
|
86
86
|
return metadata;
|
|
87
87
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Clones an object using structuredClone w/ JSON fallback
|
|
90
|
-
*/
|
|
91
|
-
static #clone(obj) {
|
|
92
|
-
if (typeof structuredClone === 'function') {
|
|
93
|
-
return structuredClone(obj);
|
|
94
|
-
}
|
|
95
|
-
return JSON.parse(JSON.stringify(obj));
|
|
96
|
-
}
|
|
97
88
|
/**
|
|
98
89
|
* Gets a unique key for the meta tag
|
|
99
90
|
*/
|
|
@@ -123,6 +114,15 @@ var Metadata;
|
|
|
123
114
|
}
|
|
124
115
|
return this;
|
|
125
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Clones an object using structuredClone w/ JSON fallback
|
|
119
|
+
*/
|
|
120
|
+
static #clone(obj) {
|
|
121
|
+
if (typeof structuredClone === 'function') {
|
|
122
|
+
return structuredClone(obj);
|
|
123
|
+
}
|
|
124
|
+
return JSON.parse(JSON.stringify(obj));
|
|
125
|
+
}
|
|
126
126
|
/**
|
|
127
127
|
* Merges metadata from all sources, sorted by priority
|
|
128
128
|
*/
|
|
@@ -130,7 +130,7 @@ var Metadata;
|
|
|
130
130
|
const items = [...this.#collection].sort((a, b) => a.priority - b.priority);
|
|
131
131
|
if (items.length === 0)
|
|
132
132
|
return Collection.#clone(this.#base);
|
|
133
|
-
let merged =
|
|
133
|
+
let merged = this.#base;
|
|
134
134
|
const res = await Promise.allSettled(items.map(item => item.task));
|
|
135
135
|
const ok = res
|
|
136
136
|
.filter((result) => result.status === 'fulfilled')
|
|
@@ -14,14 +14,9 @@ export declare namespace Prerender {
|
|
|
14
14
|
type Metadata = Pick<Value, 'schema' | 'route' | 'createdAt' | 'mode'>;
|
|
15
15
|
type ManifestEntry = {
|
|
16
16
|
mode: Mode;
|
|
17
|
-
|
|
18
|
-
files?: File[];
|
|
19
|
-
fullPrerenderFilename?: string;
|
|
20
|
-
};
|
|
21
|
-
type Manifest = {
|
|
22
|
-
generatedAt: number;
|
|
23
|
-
routes: Record<string, ManifestEntry>;
|
|
17
|
+
files?: readonly File[];
|
|
24
18
|
};
|
|
19
|
+
type Manifest = Record<string, ManifestEntry>;
|
|
25
20
|
/**
|
|
26
21
|
* Get the root directory path where prerender artifacts are stored,
|
|
27
22
|
* based on the output directory specified in the configuration
|
|
@@ -41,9 +36,9 @@ export declare namespace Prerender {
|
|
|
41
36
|
*/
|
|
42
37
|
function getFilePath(outDir: string, pathname: string, fileName: string): string;
|
|
43
38
|
/**
|
|
44
|
-
*
|
|
39
|
+
* File name used for saved full-prerender html inside each route artifact directory
|
|
45
40
|
*/
|
|
46
|
-
|
|
41
|
+
const FULL_PRERENDER_FILENAME = "prerendered.html";
|
|
47
42
|
/**
|
|
48
43
|
* Load the prerender artifact manifest for faster runtime route mode checks
|
|
49
44
|
*/
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { compile } from 'path-to-regexp';
|
|
3
|
-
import { Solas } from '../solas.js';
|
|
4
3
|
import { Logger } from '../utils/logger.js';
|
|
5
4
|
import { Time } from '../utils/time.js';
|
|
6
|
-
import {
|
|
5
|
+
import { Solas } from '../solas.js';
|
|
6
|
+
import { toPathPattern } from './http-router/utils.js';
|
|
7
7
|
const logger = new Logger();
|
|
8
8
|
export { Prerender };
|
|
9
9
|
var Prerender;
|
|
@@ -63,12 +63,9 @@ var Prerender;
|
|
|
63
63
|
}
|
|
64
64
|
Artifact.getFilePath = getFilePath;
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* File name used for saved full-prerender html inside each route artifact directory
|
|
67
67
|
*/
|
|
68
|
-
|
|
69
|
-
return `html.${Bun.hash(html).toString(16)}.html`;
|
|
70
|
-
}
|
|
71
|
-
Artifact.getFullHtmlFileName = getFullHtmlFileName;
|
|
68
|
+
Artifact.FULL_PRERENDER_FILENAME = 'prerendered.html';
|
|
72
69
|
/**
|
|
73
70
|
* Load the prerender artifact manifest for faster runtime route mode checks
|
|
74
71
|
*/
|
|
@@ -91,12 +88,7 @@ var Prerender;
|
|
|
91
88
|
manifestCache.set(outDir, null);
|
|
92
89
|
return null;
|
|
93
90
|
}
|
|
94
|
-
const generatedAt = value.generatedAt;
|
|
95
91
|
const routes = value.routes;
|
|
96
|
-
if (typeof generatedAt !== 'number') {
|
|
97
|
-
manifestCache.set(outDir, null);
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
92
|
if (!routes || typeof routes !== 'object') {
|
|
101
93
|
manifestCache.set(outDir, null);
|
|
102
94
|
return null;
|
|
@@ -107,16 +99,12 @@ var Prerender;
|
|
|
107
99
|
manifestCache.set(outDir, null);
|
|
108
100
|
return null;
|
|
109
101
|
}
|
|
110
|
-
const { mode,
|
|
102
|
+
const { mode, files } = entry;
|
|
111
103
|
// only allow known modes
|
|
112
104
|
if (mode !== 'full' && mode !== 'ppr') {
|
|
113
105
|
manifestCache.set(outDir, null);
|
|
114
106
|
return null;
|
|
115
107
|
}
|
|
116
|
-
if (typeof createdAt !== 'number') {
|
|
117
|
-
manifestCache.set(outDir, null);
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
108
|
if (files !== undefined) {
|
|
121
109
|
if (!Array.isArray(files)) {
|
|
122
110
|
manifestCache.set(outDir, null);
|
|
@@ -133,13 +121,8 @@ var Prerender;
|
|
|
133
121
|
}
|
|
134
122
|
}
|
|
135
123
|
}
|
|
136
|
-
if (entry.fullPrerenderFilename !== undefined &&
|
|
137
|
-
!isArtifactFileName(entry.fullPrerenderFilename)) {
|
|
138
|
-
manifestCache.set(outDir, null);
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
124
|
}
|
|
142
|
-
const manifest =
|
|
125
|
+
const manifest = routes;
|
|
143
126
|
// cache validated manifest to avoid reparsing on every request
|
|
144
127
|
manifestCache.set(outDir, manifest);
|
|
145
128
|
return manifest;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { use } from 'react';
|
|
3
|
-
import { Solas } from '../../solas.js';
|
|
4
3
|
import { Logger } from '../../utils/logger.js';
|
|
4
|
+
import { Solas } from '../../solas.js';
|
|
5
5
|
const logger = new Logger();
|
|
6
6
|
const cache = new WeakMap();
|
|
7
7
|
/**
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Suspense } from 'react';
|
|
3
|
-
import { HttpException, isHttpException } from '../navigation/http-exception.js';
|
|
4
3
|
import { HttpExceptionBoundary } from '../navigation/http-exception-boundary.js';
|
|
4
|
+
import { HttpException, isHttpException } from '../navigation/http-exception.js';
|
|
5
5
|
import DefaultErr from '../ui/defaults/error.js';
|
|
6
|
+
const UNAUTHORISED_ERROR = new HttpException(401, 'Unauthorised');
|
|
7
|
+
const FORBIDDEN_ERROR = new HttpException(403, 'Forbidden');
|
|
8
|
+
const NOT_FOUND_ERROR = new HttpException(404, 'Not found');
|
|
9
|
+
const SERVER_ERROR = new HttpException(500, 'Internal Server Error');
|
|
6
10
|
/**
|
|
7
11
|
* Render the resolved route tree for a matched page
|
|
8
12
|
*
|
|
@@ -42,7 +46,7 @@ import DefaultErr from '../ui/defaults/error.js';
|
|
|
42
46
|
* ```
|
|
43
47
|
*/
|
|
44
48
|
export function Tree({ depth, params, error, ui, }) {
|
|
45
|
-
const { layouts, Page, '401s':
|
|
49
|
+
const { layouts, Page, '401s': unauthorised, '403s': forbidden, '404s': notFounds, '500s': serverErrors, loaders, } = ui;
|
|
46
50
|
const Shell = layouts[0];
|
|
47
51
|
if (!Shell)
|
|
48
52
|
throw new Error('Shell layout is required in the route tree');
|
|
@@ -50,7 +54,7 @@ export function Tree({ depth, params, error, ui, }) {
|
|
|
50
54
|
let inner = null;
|
|
51
55
|
// map http status codes to exception components
|
|
52
56
|
const httpExceptionMap = {
|
|
53
|
-
401:
|
|
57
|
+
401: unauthorised,
|
|
54
58
|
403: forbidden,
|
|
55
59
|
404: notFounds,
|
|
56
60
|
500: serverErrors,
|
|
@@ -69,7 +73,7 @@ export function Tree({ depth, params, error, ui, }) {
|
|
|
69
73
|
for (let idx = layouts.length - 1; idx >= 1; idx--) {
|
|
70
74
|
const Layout = layouts[idx];
|
|
71
75
|
const Loading = loaders[idx];
|
|
72
|
-
const
|
|
76
|
+
const Unauthorised = unauthorised[idx];
|
|
73
77
|
const Forbidden = forbidden[idx];
|
|
74
78
|
const NotFound = notFounds[idx];
|
|
75
79
|
const ServerError = serverErrors[idx];
|
|
@@ -82,10 +86,10 @@ export function Tree({ depth, params, error, ui, }) {
|
|
|
82
86
|
inner = _jsx(Suspense, { fallback: _jsx(Loading, {}), children: inner });
|
|
83
87
|
}
|
|
84
88
|
const errorBoundaries = {
|
|
85
|
-
401:
|
|
86
|
-
403: Forbidden ? _jsx(Forbidden, { error:
|
|
87
|
-
404: NotFound ? _jsx(NotFound, { error:
|
|
88
|
-
500: ServerError ?
|
|
89
|
+
401: Unauthorised ? _jsx(Unauthorised, { error: UNAUTHORISED_ERROR }) : null,
|
|
90
|
+
403: Forbidden ? _jsx(Forbidden, { error: FORBIDDEN_ERROR }) : null,
|
|
91
|
+
404: NotFound ? _jsx(NotFound, { error: NOT_FOUND_ERROR }) : null,
|
|
92
|
+
500: ServerError ? _jsx(ServerError, { error: SERVER_ERROR }) : null,
|
|
89
93
|
};
|
|
90
94
|
// wrap in error boundaries (if supplied for this segment's http errors)
|
|
91
95
|
if (Object.values(errorBoundaries).some(c => c !== null)) {
|
|
@@ -95,14 +99,14 @@ export function Tree({ depth, params, error, ui, }) {
|
|
|
95
99
|
// now wrap with shell structure: shell renders immediately,
|
|
96
100
|
// inner streams inside Suspense
|
|
97
101
|
const ShellLoading = loaders[0];
|
|
98
|
-
const
|
|
102
|
+
const Shellunauthorised = unauthorised[0];
|
|
99
103
|
const ShellForbidden = forbidden[0];
|
|
100
104
|
const ShellNotFound = notFounds[0];
|
|
101
105
|
const ShellServerError = serverErrors[0];
|
|
102
106
|
return (_jsx(HttpExceptionBoundary, { components: {
|
|
103
|
-
401:
|
|
104
|
-
403: ShellForbidden ? _jsx(ShellForbidden, {}) : null,
|
|
105
|
-
404: ShellNotFound ? _jsx(ShellNotFound, {}) : null,
|
|
106
|
-
500: ShellServerError ? _jsx(ShellServerError, {}) : null,
|
|
107
|
+
401: Shellunauthorised ? _jsx(Shellunauthorised, { error: UNAUTHORISED_ERROR }) : null,
|
|
108
|
+
403: ShellForbidden ? _jsx(ShellForbidden, { error: FORBIDDEN_ERROR }) : null,
|
|
109
|
+
404: ShellNotFound ? _jsx(ShellNotFound, { error: NOT_FOUND_ERROR }) : null,
|
|
110
|
+
500: ShellServerError ? _jsx(ShellServerError, { error: SERVER_ERROR }) : null,
|
|
107
111
|
}, children: _jsx(Suspense, { fallback: ShellLoading ? _jsx(ShellLoading, {}) : null, children: _jsx(Shell, { params: params, children: inner }) }) }));
|
|
108
112
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ImportMap, Manifest, ManifestEntry, Primitive, View } from '
|
|
2
|
-
import
|
|
3
|
-
import { Metadata } from '
|
|
4
|
-
import { HttpException } from '
|
|
1
|
+
import type { ImportMap, Manifest, ManifestEntry, Primitive, View } from '../types.js';
|
|
2
|
+
import { HttpRouter } from './http-router/router.js';
|
|
3
|
+
import { Metadata } from './metadata.js';
|
|
4
|
+
import { HttpException } from './navigation/http-exception.js';
|
|
5
5
|
export declare namespace Resolver {
|
|
6
6
|
type ReconciledMatch = ReturnType<Resolver['reconcile']>;
|
|
7
7
|
type CachedEnhancedMatch = Omit<EnhancedMatch, 'params' | 'error'>;
|
|
@@ -9,11 +9,11 @@ export declare namespace Resolver {
|
|
|
9
9
|
ui: {
|
|
10
10
|
layouts: (View<{
|
|
11
11
|
children?: React.ReactNode;
|
|
12
|
-
params?:
|
|
12
|
+
params?: HttpRouter.Params;
|
|
13
13
|
}> | null)[];
|
|
14
14
|
Page: View<{
|
|
15
15
|
children?: React.ReactNode;
|
|
16
|
-
params?:
|
|
16
|
+
params?: HttpRouter.Params;
|
|
17
17
|
}> | null;
|
|
18
18
|
'401s': (View<{
|
|
19
19
|
children?: React.ReactNode;
|
|
@@ -37,13 +37,13 @@ export declare namespace Resolver {
|
|
|
37
37
|
};
|
|
38
38
|
error?: HttpException | Error;
|
|
39
39
|
endpoint?: (req?: Request & {
|
|
40
|
-
params?:
|
|
40
|
+
params?: HttpRouter.Params;
|
|
41
41
|
}) => unknown;
|
|
42
|
-
metadata?: (input: Metadata.Input<
|
|
42
|
+
metadata?: (input: Metadata.Input<HttpRouter.Params>) => Metadata.Task[];
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Resolve
|
|
46
|
+
* Resolve HttpRouter matches against the application manifest and import map
|
|
47
47
|
*/
|
|
48
48
|
export declare class Resolver {
|
|
49
49
|
#private;
|
|
@@ -55,15 +55,17 @@ export declare class Resolver {
|
|
|
55
55
|
/**
|
|
56
56
|
* Narrow down a route entry to a page entry if it exists
|
|
57
57
|
*/
|
|
58
|
-
static narrow(entry?: ManifestEntry | ManifestEntry[]): import("
|
|
58
|
+
static narrow(entry?: ManifestEntry | ManifestEntry[]): import("../types.js").Segment | null;
|
|
59
59
|
/**
|
|
60
60
|
* Get the status code for a matched route that may or may not have errored
|
|
61
61
|
*/
|
|
62
62
|
static getMatchStatusCode(match: Resolver.ReconciledMatch | Resolver.EnhancedMatch | null): 200 | HttpException.StatusCode;
|
|
63
63
|
/**
|
|
64
|
-
* Reconcile a
|
|
64
|
+
* Reconcile a HttpRouter match against a manifest entry
|
|
65
65
|
*/
|
|
66
|
-
reconcile(path: string, match:
|
|
66
|
+
reconcile(path: string, match: HttpRouter.Match | null, error?: Error): {
|
|
67
|
+
params: HttpRouter.Params;
|
|
68
|
+
error: Error | undefined;
|
|
67
69
|
__id: string;
|
|
68
70
|
__path: string;
|
|
69
71
|
__params: string[];
|
|
@@ -83,9 +85,9 @@ export declare class Resolver {
|
|
|
83
85
|
prerender: "full" | "ppr" | false;
|
|
84
86
|
dynamic: boolean;
|
|
85
87
|
wildcard: boolean;
|
|
86
|
-
params: Router.Params;
|
|
87
|
-
error: Error | undefined;
|
|
88
88
|
} | {
|
|
89
|
+
params: {};
|
|
90
|
+
error: HttpException;
|
|
89
91
|
__id: string;
|
|
90
92
|
__path: string;
|
|
91
93
|
__params: string[];
|
|
@@ -105,40 +107,19 @@ export declare class Resolver {
|
|
|
105
107
|
prerender: "full" | "ppr" | false;
|
|
106
108
|
dynamic: boolean;
|
|
107
109
|
wildcard: boolean;
|
|
108
|
-
params: {};
|
|
109
|
-
error: HttpException;
|
|
110
110
|
} | null;
|
|
111
111
|
/**
|
|
112
112
|
* Enhance a matched route with its associated components
|
|
113
113
|
*/
|
|
114
114
|
enhance(match: Resolver.ReconciledMatch | null): {
|
|
115
|
-
__id: string;
|
|
116
|
-
__path: string;
|
|
117
|
-
__params: string[];
|
|
118
|
-
__kind: "$P";
|
|
119
|
-
__depth: number;
|
|
120
|
-
method: "get";
|
|
121
|
-
paths: {
|
|
122
|
-
layouts: (string | null)[];
|
|
123
|
-
'401s': (string | null)[];
|
|
124
|
-
'403s': (string | null)[];
|
|
125
|
-
'404s': (string | null)[];
|
|
126
|
-
'500s': (string | null)[];
|
|
127
|
-
loaders: (string | null)[];
|
|
128
|
-
middlewares: (string | null)[];
|
|
129
|
-
page?: string | null | undefined;
|
|
130
|
-
};
|
|
131
|
-
prerender: "full" | "ppr" | false;
|
|
132
|
-
dynamic: boolean;
|
|
133
|
-
wildcard: boolean;
|
|
134
115
|
ui: {
|
|
135
116
|
layouts: (View<{
|
|
136
117
|
children?: import("react").ReactNode;
|
|
137
|
-
params?:
|
|
118
|
+
params?: HttpRouter.Params | undefined;
|
|
138
119
|
}> | null)[];
|
|
139
120
|
Page: View<{
|
|
140
121
|
children?: import("react").ReactNode;
|
|
141
|
-
params?:
|
|
122
|
+
params?: HttpRouter.Params | undefined;
|
|
142
123
|
}> | null;
|
|
143
124
|
'401s': (View<{
|
|
144
125
|
children?: import("react").ReactNode;
|
|
@@ -161,14 +142,33 @@ export declare class Resolver {
|
|
|
161
142
|
}> | null)[];
|
|
162
143
|
};
|
|
163
144
|
endpoint?: ((req?: (Request & {
|
|
164
|
-
params?:
|
|
145
|
+
params?: HttpRouter.Params | undefined;
|
|
165
146
|
}) | undefined) => unknown) | undefined;
|
|
166
|
-
metadata?: ((input: Metadata.Input<
|
|
167
|
-
params:
|
|
147
|
+
metadata?: ((input: Metadata.Input<HttpRouter.Params, Error>) => Metadata.Task[]) | undefined;
|
|
148
|
+
params: HttpRouter.Params | {};
|
|
168
149
|
error: Error | HttpException | undefined;
|
|
150
|
+
__id: string;
|
|
151
|
+
__path: string;
|
|
152
|
+
__params: string[];
|
|
153
|
+
__kind: "$P";
|
|
154
|
+
__depth: number;
|
|
155
|
+
method: "get";
|
|
156
|
+
paths: {
|
|
157
|
+
layouts: (string | null)[];
|
|
158
|
+
'401s': (string | null)[];
|
|
159
|
+
'403s': (string | null)[];
|
|
160
|
+
'404s': (string | null)[];
|
|
161
|
+
'500s': (string | null)[];
|
|
162
|
+
loaders: (string | null)[];
|
|
163
|
+
middlewares: (string | null)[];
|
|
164
|
+
page?: string | null | undefined;
|
|
165
|
+
};
|
|
166
|
+
prerender: "full" | "ppr" | false;
|
|
167
|
+
dynamic: boolean;
|
|
168
|
+
wildcard: boolean;
|
|
169
169
|
} | null;
|
|
170
170
|
/**
|
|
171
171
|
* Find the closest ancestor entry for a given path and property
|
|
172
172
|
*/
|
|
173
|
-
closest(path: string, property: string, value?: Omit<Primitive, 'undefined'>): import("
|
|
173
|
+
closest(path: string, property: string, value?: Omit<Primitive, 'undefined'>): import("../types.js").Segment | null;
|
|
174
174
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { lazy } from 'react';
|
|
2
|
-
import { Logger } from '
|
|
3
|
-
import { Build } from '
|
|
4
|
-
import { Metadata } from '
|
|
5
|
-
import { HttpException, isHttpException } from '
|
|
2
|
+
import { Logger } from '../utils/logger.js';
|
|
3
|
+
import { Build } from './build.js';
|
|
4
|
+
import { Metadata } from './metadata.js';
|
|
5
|
+
import { HttpException, isHttpException } from './navigation/http-exception.js';
|
|
6
6
|
const logger = new Logger();
|
|
7
7
|
const IS_DEV = import.meta.env.DEV;
|
|
8
8
|
/**
|
|
9
|
-
* Resolve
|
|
9
|
+
* Resolve HttpRouter matches against the application manifest and import map
|
|
10
10
|
*/
|
|
11
11
|
export class Resolver {
|
|
12
12
|
/**
|
|
@@ -122,13 +122,13 @@ export class Resolver {
|
|
|
122
122
|
return entry.Component;
|
|
123
123
|
}
|
|
124
124
|
/**
|
|
125
|
-
* Reconcile a
|
|
125
|
+
* Reconcile a HttpRouter match against a manifest entry
|
|
126
126
|
*/
|
|
127
127
|
reconcile(path, match, error) {
|
|
128
128
|
if (match) {
|
|
129
129
|
const entry = Resolver.narrow(Resolver.#getEntryByPath(this.#manifest, match.route.path));
|
|
130
130
|
if (entry) {
|
|
131
|
-
// normal case, the
|
|
131
|
+
// normal case, the HttpRouter matched a page route so just attach request state
|
|
132
132
|
return {
|
|
133
133
|
...entry,
|
|
134
134
|
params: match.params,
|
|
@@ -68,7 +68,7 @@ export async function processActionRequest(req) {
|
|
|
68
68
|
// we might have already parsed FormData in the router for multipart action
|
|
69
69
|
// detection should be attached to the SolasRequest, so we can reuse that
|
|
70
70
|
// to avoid parsing twice
|
|
71
|
-
const parsedFormData = req[Solas.Config.
|
|
71
|
+
const parsedFormData = req[Solas.Config.REQUEST_META_KEY]?.parsedFormData;
|
|
72
72
|
const formData = parsedFormData ?? (await req.formData());
|
|
73
73
|
const decodedAction = await decodeAction(formData);
|
|
74
74
|
const result = await decodedAction();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Cookies } from '../../utils/cookies.js';
|
|
2
2
|
/**
|
|
3
3
|
* Get the request cookies as a Cookies instance
|
|
4
|
-
* @returns a read-only Cookies instance containing the
|
|
4
|
+
* @returns a Promise resolving to a read-only Cookies instance containing the
|
|
5
|
+
* request cookies
|
|
5
6
|
*/
|
|
6
|
-
export declare function cookies(): Readonly<ReturnType<typeof Cookies.parse
|
|
7
|
+
export declare function cookies(): Promise<Readonly<ReturnType<typeof Cookies.parse>>>;
|
|
@@ -3,10 +3,11 @@ import { RequestContext } from '../env/request-context.js';
|
|
|
3
3
|
import { dynamic } from './dynamic.js';
|
|
4
4
|
/**
|
|
5
5
|
* Get the request cookies as a Cookies instance
|
|
6
|
-
* @returns a read-only Cookies instance containing the
|
|
6
|
+
* @returns a Promise resolving to a read-only Cookies instance containing the
|
|
7
|
+
* request cookies
|
|
7
8
|
*/
|
|
8
|
-
export function cookies() {
|
|
9
|
-
dynamic();
|
|
9
|
+
export async function cookies() {
|
|
10
|
+
await dynamic();
|
|
10
11
|
const { req, cache } = RequestContext.use();
|
|
11
12
|
// use request cache if possible to avoid reparsing
|
|
12
13
|
if (cache.cookies)
|