@jk2908/solas 0.1.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 (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +333 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +219 -0
  5. package/dist/error-boundary.d.ts +1 -0
  6. package/dist/error-boundary.js +1 -0
  7. package/dist/index.d.ts +7 -0
  8. package/dist/index.js +235 -0
  9. package/dist/internal/build.d.ts +104 -0
  10. package/dist/internal/build.js +633 -0
  11. package/dist/internal/codegen/config.d.ts +5 -0
  12. package/dist/internal/codegen/config.js +19 -0
  13. package/dist/internal/codegen/environments.d.ts +12 -0
  14. package/dist/internal/codegen/environments.js +42 -0
  15. package/dist/internal/codegen/manifest.d.ts +5 -0
  16. package/dist/internal/codegen/manifest.js +15 -0
  17. package/dist/internal/codegen/maps.d.ts +5 -0
  18. package/dist/internal/codegen/maps.js +75 -0
  19. package/dist/internal/codegen/utils.d.ts +1 -0
  20. package/dist/internal/codegen/utils.js +2 -0
  21. package/dist/internal/env/browser.d.ts +4 -0
  22. package/dist/internal/env/browser.js +58 -0
  23. package/dist/internal/env/request-context.d.ts +19 -0
  24. package/dist/internal/env/request-context.js +2 -0
  25. package/dist/internal/env/rsc.d.ts +39 -0
  26. package/dist/internal/env/rsc.js +368 -0
  27. package/dist/internal/env/ssr.d.ts +42 -0
  28. package/dist/internal/env/ssr.js +149 -0
  29. package/dist/internal/env/utils.d.ts +2 -0
  30. package/dist/internal/env/utils.js +28 -0
  31. package/dist/internal/metadata.d.ts +81 -0
  32. package/dist/internal/metadata.js +185 -0
  33. package/dist/internal/navigation/http-exception-boundary.d.ts +12 -0
  34. package/dist/internal/navigation/http-exception-boundary.js +48 -0
  35. package/dist/internal/navigation/http-exception.d.ts +33 -0
  36. package/dist/internal/navigation/http-exception.js +45 -0
  37. package/dist/internal/navigation/link.d.ts +13 -0
  38. package/dist/internal/navigation/link.js +63 -0
  39. package/dist/internal/navigation/redirect-boundary.d.ts +12 -0
  40. package/dist/internal/navigation/redirect-boundary.js +39 -0
  41. package/dist/internal/navigation/redirect.d.ts +21 -0
  42. package/dist/internal/navigation/redirect.js +63 -0
  43. package/dist/internal/navigation/use-search-params.d.ts +1 -0
  44. package/dist/internal/navigation/use-search-params.js +13 -0
  45. package/dist/internal/prerender.d.ts +151 -0
  46. package/dist/internal/prerender.js +422 -0
  47. package/dist/internal/render/head.d.ts +4 -0
  48. package/dist/internal/render/head.js +38 -0
  49. package/dist/internal/render/tree.d.ts +47 -0
  50. package/dist/internal/render/tree.js +108 -0
  51. package/dist/internal/router/create-router.d.ts +6 -0
  52. package/dist/internal/router/create-router.js +95 -0
  53. package/dist/internal/router/pattern.d.ts +8 -0
  54. package/dist/internal/router/pattern.js +31 -0
  55. package/dist/internal/router/prefetcher.d.ts +47 -0
  56. package/dist/internal/router/prefetcher.js +90 -0
  57. package/dist/internal/router/resolver.d.ts +174 -0
  58. package/dist/internal/router/resolver.js +356 -0
  59. package/dist/internal/router/router-context.d.ts +11 -0
  60. package/dist/internal/router/router-context.js +7 -0
  61. package/dist/internal/router/router-provider.d.ts +6 -0
  62. package/dist/internal/router/router-provider.js +131 -0
  63. package/dist/internal/router/router.d.ts +79 -0
  64. package/dist/internal/router/router.js +417 -0
  65. package/dist/internal/router/use-router.d.ts +5 -0
  66. package/dist/internal/router/use-router.js +5 -0
  67. package/dist/internal/server/cookies.d.ts +6 -0
  68. package/dist/internal/server/cookies.js +17 -0
  69. package/dist/internal/server/dynamic.d.ts +9 -0
  70. package/dist/internal/server/dynamic.js +22 -0
  71. package/dist/internal/server/headers.d.ts +5 -0
  72. package/dist/internal/server/headers.js +19 -0
  73. package/dist/internal/server/url.d.ts +5 -0
  74. package/dist/internal/server/url.js +16 -0
  75. package/dist/internal/ui/defaults/error.d.ts +4 -0
  76. package/dist/internal/ui/defaults/error.js +6 -0
  77. package/dist/internal/ui/error-boundary.d.ts +26 -0
  78. package/dist/internal/ui/error-boundary.js +41 -0
  79. package/dist/navigation.d.ts +6 -0
  80. package/dist/navigation.js +6 -0
  81. package/dist/prerender.d.ts +1 -0
  82. package/dist/prerender.js +1 -0
  83. package/dist/router.d.ts +4 -0
  84. package/dist/router.js +4 -0
  85. package/dist/server.d.ts +4 -0
  86. package/dist/server.js +4 -0
  87. package/dist/solas.d.ts +32 -0
  88. package/dist/solas.js +125 -0
  89. package/dist/types.d.ts +93 -0
  90. package/dist/types.js +1 -0
  91. package/dist/utils/compress.d.ts +11 -0
  92. package/dist/utils/compress.js +76 -0
  93. package/dist/utils/context.d.ts +6 -0
  94. package/dist/utils/context.js +25 -0
  95. package/dist/utils/cookies.d.ts +3 -0
  96. package/dist/utils/cookies.js +35 -0
  97. package/dist/utils/export-reader.d.ts +29 -0
  98. package/dist/utils/export-reader.js +117 -0
  99. package/dist/utils/format.d.ts +6 -0
  100. package/dist/utils/format.js +72 -0
  101. package/dist/utils/logger.d.ts +52 -0
  102. package/dist/utils/logger.js +105 -0
  103. package/dist/utils/time.d.ts +4 -0
  104. package/dist/utils/time.js +29 -0
  105. package/package.json +111 -0
@@ -0,0 +1,42 @@
1
+ import type { ReactFormState } from 'react-dom/client';
2
+ type Opts = {
3
+ formState?: ReactFormState;
4
+ nonce?: string;
5
+ ppr?: boolean;
6
+ route?: string;
7
+ };
8
+ /**
9
+ * SSR handler - returns a ReadableStream response for HTML requests
10
+ */
11
+ declare function ssr(rscStream: ReadableStream<Uint8Array>, opts?: Opts): Promise<ReadableStream<any>>;
12
+ /**
13
+ * Build-time prerender artifact generation
14
+ * @description for PPR routes this returns static prelude HTML + opaque postponed state
15
+ */
16
+ declare function prerender(rscStream: ReadableStream<Uint8Array>, opts?: Opts): Promise<{
17
+ schema: string;
18
+ route: string;
19
+ createdAt: number;
20
+ mode: string;
21
+ html: string;
22
+ postponed?: undefined;
23
+ } | {
24
+ schema: string;
25
+ route: string;
26
+ createdAt: number;
27
+ mode: string;
28
+ html: string;
29
+ postponed: import("react-dom/static").PostponedState;
30
+ }>;
31
+ /**
32
+ * Request-time resume for PPR routes
33
+ */
34
+ declare function resume(rscStream: ReadableStream<Uint8Array>, postponedState: unknown, opts?: Pick<Opts, 'nonce'> & {
35
+ injectPayload?: boolean;
36
+ }): Promise<ReadableStream<any>>;
37
+ export type SSRModule = {
38
+ prerender: typeof prerender;
39
+ resume: typeof resume;
40
+ ssr: typeof ssr;
41
+ };
42
+ export { prerender, resume, ssr };
@@ -0,0 +1,149 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Suspense, use } from 'react';
3
+ import { resume as reactResume, renderToReadableStream } from 'react-dom/server.edge';
4
+ import { prerender as reactPrerender } from 'react-dom/static.edge';
5
+ import { createFromReadableStream } from '@vitejs/plugin-rsc/ssr';
6
+ import { injectRSCPayload } from 'rsc-html-stream/server';
7
+ import { Solas } from '../../solas';
8
+ import { Logger } from '../../utils/logger';
9
+ import { getKnownDigest } from './utils';
10
+ import { RedirectBoundary } from '../navigation/redirect-boundary';
11
+ import { Prerender } from '../prerender';
12
+ import { Head } from '../render/head';
13
+ import { RouterProvider } from '../router/router-provider';
14
+ import { ErrorBoundary } from '../ui/error-boundary';
15
+ const logger = new Logger();
16
+ function A({ payloadPromise }) {
17
+ const payload = use(payloadPromise);
18
+ return (_jsx(RedirectBoundary, { children: _jsxs(RouterProvider, { children: [
19
+ _jsx(ErrorBoundary, { fallback: null, children: _jsx(Suspense, { fallback: null, children: _jsx(Head, { metadata: payload.metadata }) }) }), payload.root] }) }));
20
+ }
21
+ /**
22
+ * SSR handler - returns a ReadableStream response for HTML requests
23
+ */
24
+ async function ssr(rscStream, opts = {}) {
25
+ const { formState, nonce, ppr = false } = opts;
26
+ const [s1, s2] = rscStream.tee();
27
+ const payloadPromise = createFromReadableStream(s1);
28
+ const bootstrapScriptContent = await import.meta.viteRsc.loadBootstrapScriptContent('index');
29
+ // ppr uses react prerender where prelude is the static shell
30
+ // dynamic content is streamed via suspense
31
+ // rsc payload is injected after
32
+ if (ppr) {
33
+ const { prelude } = await reactPrerender(_jsx(A, { payloadPromise: payloadPromise }), {
34
+ bootstrapScriptContent,
35
+ onError(err) {
36
+ const digest = getKnownDigest(err);
37
+ if (digest)
38
+ return digest;
39
+ logger.error('[ssr:ppr]', err);
40
+ },
41
+ });
42
+ return prelude.pipeThrough(injectRSCPayload(s2, { nonce }));
43
+ }
44
+ const htmlStream = await renderToReadableStream(_jsx(A, { payloadPromise: payloadPromise }), {
45
+ bootstrapScriptContent,
46
+ nonce,
47
+ formState,
48
+ onError(err) {
49
+ const digest = getKnownDigest(err);
50
+ if (digest)
51
+ return digest;
52
+ logger.error('[ssr]', err);
53
+ },
54
+ });
55
+ return htmlStream.pipeThrough(injectRSCPayload(s2, { nonce }));
56
+ }
57
+ /**
58
+ * Build-time prerender artifact generation
59
+ * @description for PPR routes this returns static prelude HTML + opaque postponed state
60
+ */
61
+ async function prerender(rscStream, opts = {}) {
62
+ const { ppr = false, nonce, route } = opts;
63
+ if (!route) {
64
+ const err = new Error('missing route in ssr.prerender() opts');
65
+ logger.error('[ssr:prerender]', err);
66
+ throw err;
67
+ }
68
+ const [s1, s2] = rscStream.tee();
69
+ const payloadPromise = createFromReadableStream(s1);
70
+ const bootstrapScriptContent = await import.meta.viteRsc.loadBootstrapScriptContent('index');
71
+ const schema = Solas.getVersion();
72
+ if (ppr) {
73
+ const controller = new AbortController();
74
+ // abort on a macrotask so sync and microtask work still lands in prelude
75
+ // unresolved work is captured as postponed state for resume
76
+ setTimeout(() => {
77
+ controller.abort(new Prerender.Runtime.Postponed());
78
+ }, 0);
79
+ const { prelude, postponed } = await reactPrerender(_jsx(A, { payloadPromise: payloadPromise }), {
80
+ bootstrapScriptContent,
81
+ signal: controller.signal,
82
+ onError(err) {
83
+ if (Prerender.Runtime.isPostponed(err))
84
+ return;
85
+ const digest = getKnownDigest(err);
86
+ if (digest)
87
+ return digest;
88
+ logger.error('[ssr:prerender:ppr]', err);
89
+ },
90
+ });
91
+ // if prerender produced no postponed state, this route is effectively
92
+ // fully prerenderable. Emit full HTML with embedded RSC payload
93
+ if (postponed == null) {
94
+ return {
95
+ schema,
96
+ route,
97
+ createdAt: Date.now(),
98
+ mode: 'full',
99
+ html: await new Response(prelude.pipeThrough(injectRSCPayload(s2, { nonce }))).text(),
100
+ };
101
+ }
102
+ return {
103
+ schema,
104
+ route,
105
+ createdAt: Date.now(),
106
+ mode: 'ppr',
107
+ html: await new Response(prelude).text(),
108
+ postponed,
109
+ };
110
+ }
111
+ const stream = await renderToReadableStream(_jsx(A, { payloadPromise: payloadPromise }), {
112
+ bootstrapScriptContent,
113
+ onError(err) {
114
+ const digest = getKnownDigest(err);
115
+ if (digest)
116
+ return digest;
117
+ logger.error('[ssr:prerender:full]', err);
118
+ },
119
+ });
120
+ await stream.allReady;
121
+ return {
122
+ schema,
123
+ route,
124
+ createdAt: Date.now(),
125
+ mode: 'full',
126
+ html: await new Response(stream.pipeThrough(injectRSCPayload(s2, { nonce }))).text(),
127
+ };
128
+ }
129
+ /**
130
+ * Request-time resume for PPR routes
131
+ */
132
+ async function resume(rscStream, postponedState, opts = {}) {
133
+ const { nonce, injectPayload = true } = opts;
134
+ const [s1, s2] = rscStream.tee();
135
+ const payloadPromise = createFromReadableStream(s1);
136
+ const htmlStream = await reactResume(_jsx(A, { payloadPromise: payloadPromise }), postponedState, {
137
+ nonce,
138
+ onError(err) {
139
+ const digest = getKnownDigest(err);
140
+ if (digest)
141
+ return digest;
142
+ logger.error('[ssr:resume]', err);
143
+ },
144
+ });
145
+ if (!injectPayload)
146
+ return htmlStream;
147
+ return htmlStream.pipeThrough(injectRSCPayload(s2, { nonce }));
148
+ }
149
+ export { prerender, resume, ssr };
@@ -0,0 +1,2 @@
1
+ export declare function getKnownDigest(err: unknown): string | null;
2
+ export declare function isKnownError(err: unknown): boolean;
@@ -0,0 +1,28 @@
1
+ import { HTTP_EXCEPTION_DIGEST_PREFIX } from '../navigation/http-exception';
2
+ import { REDIRECT_DIGEST_PREFIX } from '../navigation/redirect';
3
+ const possibilities = [HTTP_EXCEPTION_DIGEST_PREFIX, REDIRECT_DIGEST_PREFIX];
4
+ export function getKnownDigest(err) {
5
+ if (typeof err === 'object' &&
6
+ err !== null &&
7
+ 'digest' in err &&
8
+ typeof err.digest === 'string') {
9
+ for (const p of possibilities) {
10
+ if (!err.digest.startsWith(p))
11
+ continue;
12
+ return err.digest;
13
+ }
14
+ }
15
+ return null;
16
+ }
17
+ export function isKnownError(err) {
18
+ if (getKnownDigest(err))
19
+ return true;
20
+ if (err instanceof Error) {
21
+ if (err.name === 'AbortError')
22
+ return true;
23
+ if (err.message === 'The render was aborted by the server without a reason') {
24
+ return true;
25
+ }
26
+ }
27
+ return false;
28
+ }
@@ -0,0 +1,81 @@
1
+ import type { HttpException } from './navigation/http-exception';
2
+ import { Build } from './build';
3
+ type EntryKind = typeof Build.EntryKind;
4
+ export declare namespace Metadata {
5
+ type EntrySource = Exclude<EntryKind[keyof EntryKind], typeof Build.EntryKind.ENDPOINT | typeof Build.EntryKind.MIDDLEWARE>;
6
+ export const PRIORITY: Record<EntrySource, number>;
7
+ type TagValue = string | number | boolean | undefined;
8
+ export type MetaTag = {
9
+ charSet: string;
10
+ } | {
11
+ name: string;
12
+ content: TagValue;
13
+ } | {
14
+ httpEquiv: string;
15
+ content: TagValue;
16
+ } | {
17
+ property: string;
18
+ content: TagValue;
19
+ };
20
+ export type LinkTag = {
21
+ rel: string;
22
+ href?: string;
23
+ as?: string;
24
+ type?: string;
25
+ media?: string;
26
+ sizes?: string;
27
+ crossOrigin?: 'anonymous' | 'use-credentials';
28
+ };
29
+ export type Item = {
30
+ title?: TagValue;
31
+ meta?: MetaTag[];
32
+ link?: LinkTag[];
33
+ };
34
+ export type Input<TParams = unknown, TError = Error> = {
35
+ params?: TParams;
36
+ error?: TError;
37
+ };
38
+ export type Task = {
39
+ priority: number;
40
+ task: Promise<Item>;
41
+ };
42
+ export type RunMode = 'always' | 'error';
43
+ /**
44
+ * A cached way to load one metadata export for a route.
45
+ * The export itself is loaded once route structure is known, then resolved
46
+ * later with request-specific input such as params or an error.
47
+ */
48
+ export type Source = {
49
+ priority: Task['priority'];
50
+ when?: RunMode;
51
+ status?: HttpException.StatusCode;
52
+ load: () => Promise<unknown>;
53
+ };
54
+ export class Collection {
55
+ #private;
56
+ constructor(base?: Item);
57
+ /**
58
+ * Adds tasks to the collection
59
+ */
60
+ add(...tasks: Task[]): this;
61
+ /**
62
+ * Merges metadata from all sources, sorted by priority
63
+ */
64
+ run(): Promise<Item>;
65
+ /**
66
+ * Get a clone of the base metadata
67
+ */
68
+ get base(): Item;
69
+ }
70
+ /**
71
+ * Normalise a metadata export into a promise of a metadata object
72
+ * Supports both plain object exports and metadata(input) functions
73
+ */
74
+ export function resolve(metadata: unknown, input: Input, onError?: (err: unknown) => void): Promise<{}>;
75
+ /**
76
+ * Turn cached metadata exports into concrete work for the current request/render
77
+ */
78
+ export function tasks(sources: Source[], input: Input, onError?: (err: unknown) => void): Task[];
79
+ export {};
80
+ }
81
+ export {};
@@ -0,0 +1,185 @@
1
+ import { Build } from './build';
2
+ import { isHttpException } from './navigation/http-exception';
3
+ const TITLE_TEMPLATE_STR = '%s';
4
+ export { Metadata };
5
+ var Metadata;
6
+ (function (Metadata) {
7
+ Metadata.PRIORITY = {
8
+ [Build.EntryKind.SHELL]: 10,
9
+ [Build.EntryKind.LAYOUT]: 20,
10
+ [Build.EntryKind.PAGE]: 30,
11
+ [Build.EntryKind['401']]: 40,
12
+ [Build.EntryKind['403']]: 40,
13
+ [Build.EntryKind['404']]: 40,
14
+ [Build.EntryKind['500']]: 40,
15
+ [Build.EntryKind.LOADING]: 50,
16
+ };
17
+ class Collection {
18
+ /**
19
+ * The base metadata object
20
+ * @description - normally extends config.metadata
21
+ */
22
+ #base = {};
23
+ /**
24
+ * The collection of metadata tasks with their priorities
25
+ * @description - each task is a promise that resolves to a metadata object
26
+ */
27
+ #collection = [];
28
+ constructor(base) {
29
+ if (base)
30
+ this.#base = base;
31
+ }
32
+ /**
33
+ * Merges multiple metadata objects into one
34
+ */
35
+ static #merge(...items) {
36
+ if (!items.length)
37
+ return {};
38
+ let titleTemplate;
39
+ let title;
40
+ const metaMap = new Map();
41
+ const linkMap = new Map();
42
+ for (const item of items) {
43
+ if (item.title) {
44
+ const titleStr = item.title.toString();
45
+ if (titleStr.includes(TITLE_TEMPLATE_STR)) {
46
+ titleTemplate = titleStr;
47
+ }
48
+ else {
49
+ title = titleStr;
50
+ }
51
+ }
52
+ if (item.meta) {
53
+ for (const tag of item.meta) {
54
+ metaMap.set(Collection.#getMetaTagKey(tag), tag);
55
+ }
56
+ }
57
+ if (item.link) {
58
+ for (const tag of item.link) {
59
+ linkMap.set(Collection.#getLinkTagKey(tag), tag);
60
+ }
61
+ }
62
+ }
63
+ const metadata = {};
64
+ // build final title
65
+ if (titleTemplate && title) {
66
+ metadata.title = titleTemplate.replace(TITLE_TEMPLATE_STR, title);
67
+ }
68
+ else {
69
+ metadata.title = title ?? titleTemplate?.replace(TITLE_TEMPLATE_STR, '').trim();
70
+ }
71
+ // assign final tags
72
+ metadata.meta = [...metaMap.values()];
73
+ metadata.link = [...linkMap.values()];
74
+ return metadata;
75
+ }
76
+ /**
77
+ * Clones an object using structuredClone w/ JSON fallback
78
+ */
79
+ static #clone(obj) {
80
+ if (typeof structuredClone === 'function') {
81
+ return structuredClone(obj);
82
+ }
83
+ return JSON.parse(JSON.stringify(obj));
84
+ }
85
+ /**
86
+ * Gets a unique key for the meta tag
87
+ */
88
+ static #getMetaTagKey(tag) {
89
+ return 'name' in tag && tag.name
90
+ ? `name:${tag.name}`
91
+ : 'property' in tag && tag.property
92
+ ? `property:${tag.property}`
93
+ : 'httpEquiv' in tag && tag.httpEquiv
94
+ ? `httpEquiv:${tag.httpEquiv}`
95
+ : 'charSet' in tag && tag.charSet
96
+ ? 'charSet'
97
+ : JSON.stringify(tag);
98
+ }
99
+ /**
100
+ * Gets a unique key for the link tag
101
+ */
102
+ static #getLinkTagKey(tag) {
103
+ return tag.rel + (tag.href ?? '');
104
+ }
105
+ /**
106
+ * Adds tasks to the collection
107
+ */
108
+ add(...tasks) {
109
+ for (const { task, priority } of tasks) {
110
+ this.#collection.push({ priority, task });
111
+ }
112
+ return this;
113
+ }
114
+ /**
115
+ * Merges metadata from all sources, sorted by priority
116
+ */
117
+ async run() {
118
+ const items = [...this.#collection].sort((a, b) => a.priority - b.priority);
119
+ if (items.length === 0)
120
+ return Collection.#clone(this.#base);
121
+ let merged = Collection.#clone(this.#base);
122
+ const res = await Promise.allSettled(items.map(entry => entry.task));
123
+ const ok = res
124
+ .filter((result) => result.status === 'fulfilled')
125
+ .map(result => result.value);
126
+ if (ok.length)
127
+ merged = Collection.#merge(merged, ...ok);
128
+ return merged;
129
+ }
130
+ /**
131
+ * Get a clone of the base metadata
132
+ */
133
+ get base() {
134
+ return Collection.#clone(this.#base);
135
+ }
136
+ }
137
+ Metadata.Collection = Collection;
138
+ /**
139
+ * Normalise a metadata export into a promise of a metadata object
140
+ * Supports both plain object exports and metadata(input) functions
141
+ */
142
+ function resolve(metadata, input, onError) {
143
+ if (!metadata)
144
+ return Promise.resolve({});
145
+ if (typeof metadata === 'function') {
146
+ try {
147
+ return Promise.resolve(metadata(input)).catch(err => {
148
+ onError?.(err);
149
+ return {};
150
+ });
151
+ }
152
+ catch (err) {
153
+ onError?.(err);
154
+ return Promise.resolve({});
155
+ }
156
+ }
157
+ if (typeof metadata === 'object')
158
+ return Promise.resolve(metadata);
159
+ return Promise.resolve({});
160
+ }
161
+ Metadata.resolve = resolve;
162
+ /**
163
+ * Turn cached metadata exports into concrete work for the current request/render
164
+ */
165
+ function tasks(sources, input, onError) {
166
+ const tasks = [];
167
+ for (const source of sources) {
168
+ if (source.when === 'error' && !input.error)
169
+ continue;
170
+ if (source.status !== undefined) {
171
+ if (!input.error ||
172
+ !isHttpException(input.error) ||
173
+ input.error.status !== source.status) {
174
+ continue;
175
+ }
176
+ }
177
+ tasks.push({
178
+ task: source.load().then(metadata => resolve(metadata, input, onError)),
179
+ priority: source.priority,
180
+ });
181
+ }
182
+ return tasks;
183
+ }
184
+ Metadata.tasks = tasks;
185
+ })(Metadata || (Metadata = {}));
@@ -0,0 +1,12 @@
1
+ import type { BoundaryError } from '../../types';
2
+ import { type HttpException } from './http-exception';
3
+ type ComponentsMap = Partial<Record<HttpException.StatusCode, React.ReactElement | null>>;
4
+ export type Props = {
5
+ fallback: ((error: BoundaryError) => React.ReactNode) | React.ReactNode;
6
+ children: React.ReactNode;
7
+ };
8
+ export declare function HttpExceptionBoundary({ components, children }: {
9
+ components: ComponentsMap;
10
+ children: React.ReactNode;
11
+ }): import("react/jsx-runtime").JSX.Element;
12
+ export {};
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Component } from 'react';
4
+ import { HTTP_EXCEPTION_DIGEST_PREFIX, isHttpException, } from './http-exception';
5
+ function isSupportedStatusCode(value) {
6
+ return value === 401 || value === 403 || value === 404 || value === 500;
7
+ }
8
+ class Boundary extends Component {
9
+ constructor(props) {
10
+ super(props);
11
+ this.state = { error: null };
12
+ }
13
+ static getDerivedStateFromError(error) {
14
+ return { error };
15
+ }
16
+ render() {
17
+ const { error } = this.state;
18
+ if (!error)
19
+ return this.props.children;
20
+ return typeof this.props.fallback === 'function'
21
+ ? this.props.fallback(error)
22
+ : this.props.fallback;
23
+ }
24
+ }
25
+ export function HttpExceptionBoundary({ components, children, }) {
26
+ return (_jsx(Boundary, { fallback: err => {
27
+ if (!isHttpException(err))
28
+ throw err;
29
+ if ('digest' in err && typeof err.digest === 'string') {
30
+ const [type, ...rest] = err.digest.split(':');
31
+ if (type === HTTP_EXCEPTION_DIGEST_PREFIX) {
32
+ const [code] = rest;
33
+ const status = Number(code);
34
+ if (!isSupportedStatusCode(status))
35
+ throw err;
36
+ const component = components[status];
37
+ // if no component is provided for this status code, re-throw
38
+ // the error to be caught by a higher-level boundary
39
+ // (e.g. the root boundary)
40
+ if (!component)
41
+ throw err;
42
+ return (_jsxs(_Fragment, { children: [
43
+ _jsx("meta", { name: "robots", content: "noindex,nofollow" }), component] }));
44
+ }
45
+ }
46
+ throw err;
47
+ }, children: children }));
48
+ }
@@ -0,0 +1,33 @@
1
+ export declare namespace HttpException {
2
+ type Payload = string | Record<string, unknown>;
3
+ type StatusCode = 401 | 403 | 404 | 500;
4
+ type Options = {
5
+ payload?: Payload;
6
+ cause?: unknown;
7
+ };
8
+ }
9
+ export declare const HTTP_EXCEPTION_NAME_MAP: Record<HttpException.StatusCode, string>;
10
+ /**
11
+ * An exception representing an HTTP error, with an optional payload
12
+ * and cause
13
+ */
14
+ export declare class HttpException extends Error {
15
+ readonly status: HttpException.StatusCode;
16
+ readonly message: string;
17
+ payload?: HttpException.Payload;
18
+ digest?: string;
19
+ constructor(status: HttpException.StatusCode, message: string, opts?: HttpException.Options);
20
+ }
21
+ export declare const HTTP_EXCEPTION_DIGEST_PREFIX = "HTTP_EXCEPTION";
22
+ /**
23
+ * Check if an error is an HTTPException
24
+ * @description uses the digest property to work across server/client boundaries
25
+ */
26
+ export declare function isHttpException(err: unknown): err is HttpException;
27
+ /**
28
+ * Throw an HTTPException
29
+ */
30
+ export declare function abort(status: HttpException.StatusCode, message: string, opts?: {
31
+ payload?: HttpException.Payload;
32
+ cause?: unknown;
33
+ }): never;
@@ -0,0 +1,45 @@
1
+ export const HTTP_EXCEPTION_NAME_MAP = {
2
+ 401: 'UNAUTHORIZED',
3
+ 403: 'FORBIDDEN',
4
+ 404: 'NOT_FOUND',
5
+ 500: 'INTERNAL_SERVER_ERROR',
6
+ };
7
+ /**
8
+ * An exception representing an HTTP error, with an optional payload
9
+ * and cause
10
+ */
11
+ export class HttpException extends Error {
12
+ status;
13
+ message;
14
+ payload;
15
+ digest;
16
+ constructor(status, message, opts) {
17
+ super(message, { cause: opts?.cause });
18
+ this.status = status;
19
+ this.message = message;
20
+ this.name = HTTP_EXCEPTION_NAME_MAP[status];
21
+ this.payload = opts?.payload;
22
+ this.digest = `${HTTP_EXCEPTION_DIGEST_PREFIX}:${status}:${message}`;
23
+ }
24
+ }
25
+ export const HTTP_EXCEPTION_DIGEST_PREFIX = 'HTTP_EXCEPTION';
26
+ /**
27
+ * Check if an error is an HTTPException
28
+ * @description uses the digest property to work across server/client boundaries
29
+ */
30
+ export function isHttpException(err) {
31
+ return (typeof err === 'object' &&
32
+ err !== null &&
33
+ 'digest' in err &&
34
+ typeof err.digest === 'string' &&
35
+ err.digest.startsWith(HTTP_EXCEPTION_DIGEST_PREFIX));
36
+ }
37
+ /**
38
+ * Throw an HTTPException
39
+ */
40
+ export function abort(status, message, opts) {
41
+ throw new HttpException(status, message, {
42
+ payload: opts?.payload,
43
+ cause: opts?.cause,
44
+ });
45
+ }
@@ -0,0 +1,13 @@
1
+ type Props = {
2
+ href: string;
3
+ prefetch?: 'intent' | 'hover' | 'none';
4
+ } & React.ComponentPropsWithRef<'a'>;
5
+ /**
6
+ * A link component that navigates to a given href
7
+ * @param href - the href to navigate to
8
+ * @param prefetch - when to prefetch the linked page, defaults to 'intent'
9
+ * @param rest - other props to pass to the underlying anchor element
10
+ * @returns a link element that navigates to the given href
11
+ */
12
+ export declare function Link({ children, href, prefetch, ...rest }: Props): import("react/jsx-runtime").JSX.Element;
13
+ export {};