@alxia/react-router 0.10.0 → 0.12.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.
- package/README.md +370 -20
- package/dist/chunks/index-5rt6hnhn.js +42 -0
- package/dist/chunks/index-5rt6hnhn.js.map +11 -0
- package/dist/chunks/{index-mppz0806.js → index-6jf7zpxz.js} +5 -5
- package/dist/chunks/{index-mppz0806.js.map → index-6jf7zpxz.js.map} +1 -1
- package/dist/chunks/index-y6vpnjr3.js +123 -0
- package/dist/chunks/index-y6vpnjr3.js.map +14 -0
- package/dist/context.d.ts +0 -7
- package/dist/context.d.ts.map +1 -1
- package/dist/document/index.d.ts +11 -0
- package/dist/document/index.d.ts.map +1 -0
- package/dist/document/index.js +19 -0
- package/dist/document/index.js.map +9 -0
- package/dist/document/inline-script.d.ts +27 -0
- package/dist/document/inline-script.d.ts.map +1 -0
- package/dist/document/language-store.d.ts +8 -0
- package/dist/document/language-store.d.ts.map +1 -0
- package/dist/document/nonce.d.ts +34 -0
- package/dist/document/nonce.d.ts.map +1 -0
- package/dist/document/page-language.d.ts +39 -0
- package/dist/document/page-language.d.ts.map +1 -0
- package/dist/document/text-direction.d.ts +10 -0
- package/dist/document/text-direction.d.ts.map +1 -0
- package/dist/entry/abort.d.ts +21 -0
- package/dist/entry/abort.d.ts.map +1 -0
- package/dist/entry/create-entry.d.ts +32 -0
- package/dist/entry/create-entry.d.ts.map +1 -0
- package/dist/entry/index.d.ts +8 -0
- package/dist/entry/index.d.ts.map +1 -0
- package/dist/entry/index.js +128 -0
- package/dist/entry/index.js.map +12 -0
- package/dist/entry/whole-page.d.ts +10 -0
- package/dist/entry/whole-page.d.ts.map +1 -0
- package/dist/errors/api-error.d.ts +86 -0
- package/dist/errors/api-error.d.ts.map +1 -0
- package/dist/errors/api-errors.d.ts +96 -0
- package/dist/errors/api-errors.d.ts.map +1 -0
- package/dist/errors/call-failure.d.ts +45 -0
- package/dist/errors/call-failure.d.ts.map +1 -0
- package/dist/errors/catalogue-check.d.ts +24 -0
- package/dist/errors/catalogue-check.d.ts.map +1 -0
- package/dist/errors/catalogue-read.d.ts +33 -0
- package/dist/errors/catalogue-read.d.ts.map +1 -0
- package/dist/errors/catalogue.d.ts +59 -0
- package/dist/errors/catalogue.d.ts.map +1 -0
- package/dist/errors/code-resolver.d.ts +44 -0
- package/dist/errors/code-resolver.d.ts.map +1 -0
- package/dist/errors/describe-refusal.d.ts +6 -0
- package/dist/errors/describe-refusal.d.ts.map +1 -0
- package/dist/errors/describe.d.ts +3 -0
- package/dist/errors/describe.d.ts.map +1 -0
- package/dist/errors/fetcher-result.d.ts +21 -0
- package/dist/errors/fetcher-result.d.ts.map +1 -0
- package/dist/errors/form-errors.d.ts +8 -0
- package/dist/errors/form-errors.d.ts.map +1 -0
- package/dist/errors/index.d.ts +15 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +602 -0
- package/dist/errors/index.js.map +29 -0
- package/dist/errors/json-actions.d.ts +38 -0
- package/dist/errors/json-actions.d.ts.map +1 -0
- package/dist/errors/log.d.ts +8 -0
- package/dist/errors/log.d.ts.map +1 -0
- package/dist/errors/message-values.d.ts +11 -0
- package/dist/errors/message-values.d.ts.map +1 -0
- package/dist/errors/messages.d.ts +63 -0
- package/dist/errors/messages.d.ts.map +1 -0
- package/dist/errors/page-path.d.ts +9 -0
- package/dist/errors/page-path.d.ts.map +1 -0
- package/dist/errors/provider.d.ts +20 -0
- package/dist/errors/provider.d.ts.map +1 -0
- package/dist/errors/request-id.d.ts +5 -0
- package/dist/errors/request-id.d.ts.map +1 -0
- package/dist/errors/route-error.d.ts +31 -0
- package/dist/errors/route-error.d.ts.map +1 -0
- package/dist/errors/standard-schema.d.ts +34 -0
- package/dist/errors/standard-schema.d.ts.map +1 -0
- package/dist/errors/submit-form.d.ts +16 -0
- package/dist/errors/submit-form.d.ts.map +1 -0
- package/dist/errors/use-json-fetcher.d.ts +18 -0
- package/dist/errors/use-json-fetcher.d.ts.map +1 -0
- package/dist/index.js +13 -34
- package/dist/index.js.map +3 -5
- package/dist/pwa/build/index.js +1 -1
- package/dist/pwa/index.js +6 -6
- package/dist/pwa/index.js.map +1 -1
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +29 -2
- package/dist/vite/index.js.map +5 -4
- package/dist/vite/inline.d.ts +18 -0
- package/dist/vite/inline.d.ts.map +1 -0
- package/docs/README.md +3 -1
- package/docs/guide/csp-nonce.md +35 -3
- package/docs/guide/document.md +310 -0
- package/docs/guide/errors.md +838 -0
- package/docs/roadmap.md +36 -1
- package/docs/troubleshooting.md +712 -1
- package/package.json +33 -3
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// src/document/inline-script.ts
|
|
2
|
+
import { createElement as createElement2 } from "react";
|
|
3
|
+
|
|
4
|
+
// src/document/nonce.ts
|
|
5
|
+
import {
|
|
6
|
+
createContext,
|
|
7
|
+
createElement,
|
|
8
|
+
useContext
|
|
9
|
+
} from "react";
|
|
10
|
+
var KEY = Symbol.for("@alxia/react-router:document-nonce@1");
|
|
11
|
+
var registry = globalThis;
|
|
12
|
+
registry[KEY] ??= createContext(undefined);
|
|
13
|
+
var NonceContext = registry[KEY];
|
|
14
|
+
function DocumentNonce2({
|
|
15
|
+
nonce,
|
|
16
|
+
children
|
|
17
|
+
}) {
|
|
18
|
+
return createElement(NonceContext, { value: nonce }, children);
|
|
19
|
+
}
|
|
20
|
+
var HEAD_NONCE = 'head script[nonce], head meta[property="csp-nonce"]';
|
|
21
|
+
var documentNonce;
|
|
22
|
+
function useNonce2() {
|
|
23
|
+
const serverNonce = useContext(NonceContext);
|
|
24
|
+
if (typeof document === "undefined")
|
|
25
|
+
return serverNonce;
|
|
26
|
+
documentNonce ??= (document.querySelector(HEAD_NONCE) ?? document.querySelector("script[nonce]"))?.nonce ?? "";
|
|
27
|
+
return documentNonce || undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/document/inline-script.ts
|
|
31
|
+
var BREAKS_OUT = /<\/script|<!--/i;
|
|
32
|
+
function InlineScript2({ code, nonce }) {
|
|
33
|
+
if (BREAKS_OUT.test(code)) {
|
|
34
|
+
throw new TypeError('InlineScript(): the code contains "</script" or "<!--", which ends or confuses the script element; escape it, such as "<\\/script" inside a string');
|
|
35
|
+
}
|
|
36
|
+
const fromDocument = useNonce2();
|
|
37
|
+
return createElement2("script", {
|
|
38
|
+
nonce: nonce ?? fromDocument,
|
|
39
|
+
dangerouslySetInnerHTML: { __html: code }
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function CspNonceMeta2() {
|
|
43
|
+
return createElement2("meta", {
|
|
44
|
+
property: "csp-nonce",
|
|
45
|
+
nonce: useNonce2()
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// src/document/page-language.ts
|
|
49
|
+
import { useSyncExternalStore } from "react";
|
|
50
|
+
|
|
51
|
+
// src/document/language-store.ts
|
|
52
|
+
function createLanguageStore() {
|
|
53
|
+
const listeners = new Set;
|
|
54
|
+
let chosen;
|
|
55
|
+
return {
|
|
56
|
+
subscribe(listener) {
|
|
57
|
+
listeners.add(listener);
|
|
58
|
+
return () => {
|
|
59
|
+
listeners.delete(listener);
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
getSnapshot: () => chosen,
|
|
63
|
+
choose(language) {
|
|
64
|
+
chosen = language;
|
|
65
|
+
for (const listener of listeners)
|
|
66
|
+
listener();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/document/page-language.ts
|
|
72
|
+
function createPageLanguage2(options) {
|
|
73
|
+
const { languages, fallback } = options;
|
|
74
|
+
if (!languages.includes(fallback)) {
|
|
75
|
+
throw new TypeError(`createPageLanguage(): the fallback "${fallback}" is not one of the languages; got ${JSON.stringify(languages)}.`);
|
|
76
|
+
}
|
|
77
|
+
const isBrowser = options.browser ?? (() => typeof document !== "undefined");
|
|
78
|
+
const store = createLanguageStore();
|
|
79
|
+
let last;
|
|
80
|
+
function pageLanguage(language) {
|
|
81
|
+
const supported = languages.find((known) => known === language);
|
|
82
|
+
if (!isBrowser())
|
|
83
|
+
return supported ?? fallback;
|
|
84
|
+
const current = store.getSnapshot() ?? supported;
|
|
85
|
+
if (current)
|
|
86
|
+
last = current;
|
|
87
|
+
return last ?? fallback;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
pageLanguage,
|
|
91
|
+
metaLanguage(matches) {
|
|
92
|
+
const root = matches[0];
|
|
93
|
+
return pageLanguage(root?.loaderData?.language);
|
|
94
|
+
},
|
|
95
|
+
chooseLanguage: store.choose,
|
|
96
|
+
useChosenLanguage: () => useSyncExternalStore(store.subscribe, store.getSnapshot, () => {
|
|
97
|
+
return;
|
|
98
|
+
})
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// src/document/text-direction.ts
|
|
102
|
+
var RTL = new Set([
|
|
103
|
+
"ar",
|
|
104
|
+
"fa",
|
|
105
|
+
"he",
|
|
106
|
+
"ur",
|
|
107
|
+
"ps",
|
|
108
|
+
"yi",
|
|
109
|
+
"dv",
|
|
110
|
+
"ckb",
|
|
111
|
+
"sd",
|
|
112
|
+
"ug",
|
|
113
|
+
"iw",
|
|
114
|
+
"ji"
|
|
115
|
+
]);
|
|
116
|
+
function textDirection2(language) {
|
|
117
|
+
const base = language.split(/[-_]/, 1)[0]?.toLowerCase() ?? "";
|
|
118
|
+
return RTL.has(base) ? "rtl" : "ltr";
|
|
119
|
+
}
|
|
120
|
+
export { DocumentNonce2, useNonce2, InlineScript2, CspNonceMeta2, createPageLanguage2, textDirection2 };
|
|
121
|
+
|
|
122
|
+
//# debugId=49CF940BC2A4321264756E2164756E21
|
|
123
|
+
//# sourceMappingURL=index-y6vpnjr3.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/document/inline-script.ts", "../src/document/nonce.ts", "../src/document/page-language.ts", "../src/document/language-store.ts", "../src/document/text-direction.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { createElement, type ReactElement } from 'react';\nimport { useNonce } from './nonce';\n\nconst BREAKS_OUT = /<\\/script|<!--/i;\n\nexport interface InlineScriptProps {\n\t/**\n\t * The script's text, rendered as it is: never escaped, so a `</script`\n\t * or a `<!--` in it is refused (see `InlineScript`).\n\t */\n\tcode: string;\n\t/** The script's nonce; `useNonce()`'s by default. */\n\tnonce?: string | undefined;\n}\n\n/**\n * An inline `<script>` carrying the page's CSP nonce, on the server and in\n * the browser, so hydration finds the attribute it rendered. The code is a\n * constant of the app (a theme's boot script): never put user input in it.\n * With no nonce anywhere, it renders no `nonce` attribute, and the policy\n * must then allow inline scripts some other way.\n *\n * Throws a `TypeError` when the code holds `</script` or `<!--`, which end or\n * confuse the element whatever the nonce: escape them in a string (`<\\/script`).\n */\nexport function InlineScript({ code, nonce }: InlineScriptProps): ReactElement {\n\tif (BREAKS_OUT.test(code)) {\n\t\tthrow new TypeError(\n\t\t\t'InlineScript(): the code contains \"</script\" or \"<!--\", which ends or confuses the script element; escape it, such as \"<\\\\/script\" inside a string',\n\t\t);\n\t}\n\tconst fromDocument = useNonce();\n\treturn createElement('script', {\n\t\tnonce: nonce ?? fromDocument,\n\t\tdangerouslySetInnerHTML: { __html: code },\n\t});\n}\n\n/**\n * `<meta property=\"csp-nonce\" nonce=\"…\">`: the page's nonce for the scripts\n * and styles a library adds at runtime (a UI kit reads it from the DOM).\n */\nexport function CspNonceMeta(): ReactElement {\n\treturn createElement('meta', {\n\t\tproperty: 'csp-nonce',\n\t\tnonce: useNonce(),\n\t});\n}\n",
|
|
6
|
+
"import {\n\tcreateContext,\n\tcreateElement,\n\ttype ReactNode,\n\tuseContext,\n} from 'react';\n\n/**\n * One context per process, whichever copy of this module asks: a package\n * loaded natively that imports `/document` would otherwise get a second\n * context, and its `useNonce()` would never see the entry's `DocumentNonce`.\n * `/document` imports `react` alone, so the few lines of `src/context.ts`'s\n * singleton are written here. The `@1` suffix is the shape of what is stored\n * under the key: a version that changes it bumps the suffix, never the name.\n */\nconst KEY = Symbol.for('@alxia/react-router:document-nonce@1');\nconst registry = globalThis as Record<symbol, unknown>;\nregistry[KEY] ??= createContext<string | undefined>(undefined);\nconst NonceContext = registry[KEY] as ReturnType<\n\ttypeof createContext<string | undefined>\n>;\n\nexport interface DocumentNonceProps {\n\t/** This response's CSP nonce: `nonceOf(loadContext)` in the entry. */\n\tnonce: string | undefined;\n\tchildren?: ReactNode | undefined;\n}\n\n/**\n * Gives the server render this response's CSP nonce, for `useNonce()` and\n * the components built on it. The entry wraps the router in it, so every\n * boundary reads the nonce, an error page after an action included (it\n * runs no loader). Never put the nonce in loader data: the browser hides\n * it from the page, and loader data would print it in the hydration script\n * and every `.data` answer.\n */\nexport function DocumentNonce({\n\tnonce,\n\tchildren,\n}: DocumentNonceProps): ReactNode {\n\treturn createElement(NonceContext, { value: nonce }, children);\n}\n\nconst HEAD_NONCE = 'head script[nonce], head meta[property=\"csp-nonce\"]';\n\nlet documentNonce: string | undefined;\n\n/**\n * The page's CSP nonce, for what the root renders: an inline script, and\n * React Router's `<Links>`, `<Scripts>` and `<ScrollRestoration>`. On the\n * server, the one `DocumentNonce` provides. In the browser, the one the\n * document carries: React Router's client knows no nonce, and React would\n * find `nonce` on the server's elements and none in its own (a hydration\n * mismatch). It is read from the DOM property of the first script or\n * `csp-nonce` meta in the `<head>` (the browser hides the attribute, not\n * the property), else of the first `script[nonce]` anywhere, once for the\n * document. That first read is cached per module copy, so a test with a\n * DOM keeps it across tests: reset modules, or render the server path with\n * `DocumentNonce`.\n *\n * \"In the browser\" means a global `document`. A server that installs a DOM\n * shim on `globalThis` takes the browser path and finds no nonce: it must not\n * render this hook that way.\n */\nexport function useNonce(): string | undefined {\n\tconst serverNonce = useContext(NonceContext);\n\tif (typeof document === 'undefined') return serverNonce;\n\tdocumentNonce ??=\n\t\t(\n\t\t\tdocument.querySelector<HTMLElement>(HEAD_NONCE) ??\n\t\t\tdocument.querySelector<HTMLElement>('script[nonce]')\n\t\t)?.nonce ?? '';\n\treturn documentNonce || undefined;\n}\n",
|
|
7
|
+
"import { useSyncExternalStore } from 'react';\nimport { createLanguageStore } from './language-store';\n\nexport interface PageLanguageOptions<L extends string> {\n\t/** The languages the app serves. */\n\treadonly languages: readonly L[];\n\t/** The language of a page whose root data gives none; one of `languages`. */\n\treadonly fallback: NoInfer<L>;\n\t/**\n\t * Whether this is a browser. Default: `typeof document !== 'undefined'`,\n\t * a global `document`: a server with a DOM shim on `globalThis` must pass\n\t * `() => false`, or the instance remembers a language across requests.\n\t */\n\treadonly browser?: (() => boolean) | undefined;\n}\n\nexport interface PageLanguage<L extends string> {\n\t/**\n\t * The page's language from the root loader's `language`. In the browser,\n\t * the one picked with `chooseLanguage` wins, and the last one seen stands\n\t * in when the root's data is gone (a failed navigation takes the root's\n\t * with it): the error page keeps the page's language, not the fallback's.\n\t * On the server it remembers nothing, since one module serves every request.\n\t */\n\tpageLanguage(language: string | undefined): L;\n\t/** The page's language in a route's `meta`, from `matches[0].loaderData.language`. */\n\tmetaLanguage(matches: ReadonlyArray<unknown>): L;\n\t/**\n\t * Records the language picked in a language menu: from then on the page's\n\t * language in the browser, until the next document. Re-renders every\n\t * `useChosenLanguage()`.\n\t */\n\tchooseLanguage(language: L): void;\n\t/** The language picked in this document, if any; re-renders on a new pick. */\n\tuseChosenLanguage(): L | undefined;\n}\n\n/**\n * The page's language for a root route: `<html lang>`, a route's `meta`, and\n * the language menu. An instance, not module state: keep one per app, in a\n * module both the root and the routes import.\n */\nexport function createPageLanguage<const L extends string>(\n\toptions: PageLanguageOptions<L>,\n): PageLanguage<L> {\n\tconst { languages, fallback } = options;\n\tif (!languages.includes(fallback)) {\n\t\tthrow new TypeError(\n\t\t\t`createPageLanguage(): the fallback \"${fallback}\" is not one of the languages; got ${JSON.stringify(languages)}.`,\n\t\t);\n\t}\n\tconst isBrowser = options.browser ?? (() => typeof document !== 'undefined');\n\tconst store = createLanguageStore<L>();\n\tlet last: L | undefined;\n\n\tfunction pageLanguage(language: string | undefined): L {\n\t\tconst supported = languages.find((known) => known === language);\n\t\tif (!isBrowser()) return supported ?? fallback;\n\t\tconst current = store.getSnapshot() ?? supported;\n\t\tif (current) last = current;\n\t\treturn last ?? fallback;\n\t}\n\n\treturn {\n\t\tpageLanguage,\n\t\tmetaLanguage(matches) {\n\t\t\tconst root = matches[0] as\n\t\t\t\t| { loaderData?: { language?: string } }\n\t\t\t\t| undefined;\n\t\t\treturn pageLanguage(root?.loaderData?.language);\n\t\t},\n\t\tchooseLanguage: store.choose,\n\t\tuseChosenLanguage: () =>\n\t\t\tuseSyncExternalStore(store.subscribe, store.getSnapshot, () => undefined),\n\t};\n}\n",
|
|
8
|
+
"/** The picked language and who listens to it: internal to `createPageLanguage`. */\nexport interface LanguageStore<L extends string> {\n\tsubscribe(listener: () => void): () => void;\n\tgetSnapshot(): L | undefined;\n\tchoose(language: L): void;\n}\n\nexport function createLanguageStore<L extends string>(): LanguageStore<L> {\n\tconst listeners = new Set<() => void>();\n\tlet chosen: L | undefined;\n\treturn {\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tgetSnapshot: () => chosen,\n\t\tchoose(language) {\n\t\t\tchosen = language;\n\t\t\tfor (const listener of listeners) listener();\n\t\t},\n\t};\n}\n",
|
|
9
|
+
"/**\n * Base languages written right to left. A fixed list: a language is added\n * here when it needs it.\n */\nconst RTL = new Set([\n\t'ar',\n\t'fa',\n\t'he',\n\t'ur',\n\t'ps',\n\t'yi',\n\t'dv',\n\t'ckb',\n\t'sd',\n\t'ug',\n\t// Legacy codes of Hebrew and Yiddish.\n\t'iw',\n\t'ji',\n]);\n\n/**\n * The `dir` of a language tag: `'rtl'` for a right-to-left base language,\n * `'ltr'` for the rest. The base tag is read whatever its case, so `ar-EG`\n * and `AR` are `'rtl'` and `en-US` is `'ltr'`.\n *\n * Known gap: a script subtag is not read, so `az-Arab` (Azerbaijani in the\n * Arabic script) is `'ltr'`, as `az` is.\n */\nexport function textDirection(language: string): 'ltr' | 'rtl' {\n\tconst base = language.split(/[-_]/, 1)[0]?.toLowerCase() ?? '';\n\treturn RTL.has(base) ? 'rtl' : 'ltr';\n}\n"
|
|
10
|
+
],
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "49CF940BC2A4321264756E2164756E21",
|
|
13
|
+
"names": []
|
|
14
|
+
}
|
package/dist/context.d.ts
CHANGED
|
@@ -5,13 +5,6 @@
|
|
|
5
5
|
import type { Alxia, AnyAlxia, ContextOf, RegisteredBase } from '@alxia/core';
|
|
6
6
|
import { RouterContextProvider } from 'react-router';
|
|
7
7
|
import type { ReactRouterServer } from './server';
|
|
8
|
-
/**
|
|
9
|
-
* The key `reactRouter()` sets on React Router's context provider, on every
|
|
10
|
-
* request, to what alxia's middlewares built for it. It lives in this package, so
|
|
11
|
-
* it is one object whichever way the server was built or loaded: a key made
|
|
12
|
-
* in the app's own `app/` folder is copied into React Router's build, and
|
|
13
|
-
* the server that imports it separately sets a different one.
|
|
14
|
-
*/
|
|
15
8
|
export declare const alxiaContext: import("react-router").RouterContext<unknown>;
|
|
16
9
|
/**
|
|
17
10
|
* Names the server whose context `alxiaOf(context)` reads when given no
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAiB,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAiB,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAkClD,eAAO,MAAM,YAAY,+CAAiB,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,QAAQ;CAAG;AAE5B,iDAAiD;AACjD,MAAM,MAAM,KAAK,CAAC,MAAM,IACvB,MAAM,SAAS,iBAAiB,CAAC,MAAM,GAAG,CAAC,GAAG,GAAG,GAAG,MAAM,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,KAAK,CAClC;IACC,QAAQ,CAAC,6EAA6E,EAAE,KAAK,CAAC;CAC9F,EACD,EAAE,CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,YAAY,CACvB,CAAC,EACD,IAAI,SAAS,QAAQ,GAAG,cAAc,IACnC,CAAC,SAAS;IACb,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;CAC9B,GACE,MAAM,SAAS,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,GACpD,KAAK,CAAC,MAAM,CAAC,GACb,eAAe,GAChB,IAAI,CAAC;AAER,6HAA6H;AAC7H,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;AAEnD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,OAAO,CACtB,GAAG,SAAS,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,GAAG,aAAa,EACjE,OAAO,EAAE,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,CAQ5C;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,KAAK,CAAC,CAAC;AAExE,2FAA2F;AAC3F,MAAM,MAAM,cAAc,CACzB,GAAG,SAAS,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,GAAG,aAAa,IAC/D,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,OAAO,QAAQ,cAAc,CAAC;IAC7B,UAAU,qBAAqB;QAC9B;;;;;;;;;;;;;;;;WAgBG;QACH,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;KAC/B;CACD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@alxia/react-router/document`: what a root route renders for the page's
|
|
3
|
+
* CSP nonce, on the server and in the browser. `react` is its only import
|
|
4
|
+
* (an optional peer of the package), and nothing runs at the top level but
|
|
5
|
+
* `createContext`.
|
|
6
|
+
*/
|
|
7
|
+
export { CspNonceMeta, InlineScript, type InlineScriptProps, } from './inline-script';
|
|
8
|
+
export { DocumentNonce, type DocumentNonceProps, useNonce, } from './nonce';
|
|
9
|
+
export { createPageLanguage, type PageLanguage, type PageLanguageOptions, } from './page-language';
|
|
10
|
+
export { textDirection } from './text-direction';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/document/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACN,YAAY,EACZ,YAAY,EACZ,KAAK,iBAAiB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACN,aAAa,EACb,KAAK,kBAAkB,EACvB,QAAQ,GACR,MAAM,SAAS,CAAC;AACjB,OAAO,EACN,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,mBAAmB,GACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DocumentNonce2,
|
|
3
|
+
useNonce2,
|
|
4
|
+
InlineScript2,
|
|
5
|
+
CspNonceMeta2,
|
|
6
|
+
createPageLanguage2,
|
|
7
|
+
textDirection2
|
|
8
|
+
} from "../chunks/index-y6vpnjr3.js";
|
|
9
|
+
export {
|
|
10
|
+
CspNonceMeta2 as CspNonceMeta,
|
|
11
|
+
DocumentNonce2 as DocumentNonce,
|
|
12
|
+
InlineScript2 as InlineScript,
|
|
13
|
+
createPageLanguage2 as createPageLanguage,
|
|
14
|
+
textDirection2 as textDirection,
|
|
15
|
+
useNonce2 as useNonce
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
//# debugId=3F5BF8C287845B0F64756E2164756E21
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
export interface InlineScriptProps {
|
|
3
|
+
/**
|
|
4
|
+
* The script's text, rendered as it is: never escaped, so a `</script`
|
|
5
|
+
* or a `<!--` in it is refused (see `InlineScript`).
|
|
6
|
+
*/
|
|
7
|
+
code: string;
|
|
8
|
+
/** The script's nonce; `useNonce()`'s by default. */
|
|
9
|
+
nonce?: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* An inline `<script>` carrying the page's CSP nonce, on the server and in
|
|
13
|
+
* the browser, so hydration finds the attribute it rendered. The code is a
|
|
14
|
+
* constant of the app (a theme's boot script): never put user input in it.
|
|
15
|
+
* With no nonce anywhere, it renders no `nonce` attribute, and the policy
|
|
16
|
+
* must then allow inline scripts some other way.
|
|
17
|
+
*
|
|
18
|
+
* Throws a `TypeError` when the code holds `</script` or `<!--`, which end or
|
|
19
|
+
* confuse the element whatever the nonce: escape them in a string (`<\/script`).
|
|
20
|
+
*/
|
|
21
|
+
export declare function InlineScript({ code, nonce }: InlineScriptProps): ReactElement;
|
|
22
|
+
/**
|
|
23
|
+
* `<meta property="csp-nonce" nonce="…">`: the page's nonce for the scripts
|
|
24
|
+
* and styles a library adds at runtime (a UI kit reads it from the DOM).
|
|
25
|
+
*/
|
|
26
|
+
export declare function CspNonceMeta(): ReactElement;
|
|
27
|
+
//# sourceMappingURL=inline-script.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-script.d.ts","sourceRoot":"","sources":["../../src/document/inline-script.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAKzD,MAAM,WAAW,iBAAiB;IACjC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,iBAAiB,GAAG,YAAY,CAW7E;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,YAAY,CAK3C"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The picked language and who listens to it: internal to `createPageLanguage`. */
|
|
2
|
+
export interface LanguageStore<L extends string> {
|
|
3
|
+
subscribe(listener: () => void): () => void;
|
|
4
|
+
getSnapshot(): L | undefined;
|
|
5
|
+
choose(language: L): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createLanguageStore<L extends string>(): LanguageStore<L>;
|
|
8
|
+
//# sourceMappingURL=language-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"language-store.d.ts","sourceRoot":"","sources":["../../src/document/language-store.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,MAAM;IAC9C,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,WAAW,IAAI,CAAC,GAAG,SAAS,CAAC;IAC7B,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,CAgBxE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface DocumentNonceProps {
|
|
3
|
+
/** This response's CSP nonce: `nonceOf(loadContext)` in the entry. */
|
|
4
|
+
nonce: string | undefined;
|
|
5
|
+
children?: ReactNode | undefined;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Gives the server render this response's CSP nonce, for `useNonce()` and
|
|
9
|
+
* the components built on it. The entry wraps the router in it, so every
|
|
10
|
+
* boundary reads the nonce, an error page after an action included (it
|
|
11
|
+
* runs no loader). Never put the nonce in loader data: the browser hides
|
|
12
|
+
* it from the page, and loader data would print it in the hydration script
|
|
13
|
+
* and every `.data` answer.
|
|
14
|
+
*/
|
|
15
|
+
export declare function DocumentNonce({ nonce, children, }: DocumentNonceProps): ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* The page's CSP nonce, for what the root renders: an inline script, and
|
|
18
|
+
* React Router's `<Links>`, `<Scripts>` and `<ScrollRestoration>`. On the
|
|
19
|
+
* server, the one `DocumentNonce` provides. In the browser, the one the
|
|
20
|
+
* document carries: React Router's client knows no nonce, and React would
|
|
21
|
+
* find `nonce` on the server's elements and none in its own (a hydration
|
|
22
|
+
* mismatch). It is read from the DOM property of the first script or
|
|
23
|
+
* `csp-nonce` meta in the `<head>` (the browser hides the attribute, not
|
|
24
|
+
* the property), else of the first `script[nonce]` anywhere, once for the
|
|
25
|
+
* document. That first read is cached per module copy, so a test with a
|
|
26
|
+
* DOM keeps it across tests: reset modules, or render the server path with
|
|
27
|
+
* `DocumentNonce`.
|
|
28
|
+
*
|
|
29
|
+
* "In the browser" means a global `document`. A server that installs a DOM
|
|
30
|
+
* shim on `globalThis` takes the browser path and finds no nonce: it must not
|
|
31
|
+
* render this hook that way.
|
|
32
|
+
*/
|
|
33
|
+
export declare function useNonce(): string | undefined;
|
|
34
|
+
//# sourceMappingURL=nonce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nonce.d.ts","sourceRoot":"","sources":["../../src/document/nonce.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,SAAS,EAEd,MAAM,OAAO,CAAC;AAiBf,MAAM,WAAW,kBAAkB;IAClC,sEAAsE;IACtE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,EAC7B,KAAK,EACL,QAAQ,GACR,EAAE,kBAAkB,GAAG,SAAS,CAEhC;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,QAAQ,IAAI,MAAM,GAAG,SAAS,CAS7C"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface PageLanguageOptions<L extends string> {
|
|
2
|
+
/** The languages the app serves. */
|
|
3
|
+
readonly languages: readonly L[];
|
|
4
|
+
/** The language of a page whose root data gives none; one of `languages`. */
|
|
5
|
+
readonly fallback: NoInfer<L>;
|
|
6
|
+
/**
|
|
7
|
+
* Whether this is a browser. Default: `typeof document !== 'undefined'`,
|
|
8
|
+
* a global `document`: a server with a DOM shim on `globalThis` must pass
|
|
9
|
+
* `() => false`, or the instance remembers a language across requests.
|
|
10
|
+
*/
|
|
11
|
+
readonly browser?: (() => boolean) | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface PageLanguage<L extends string> {
|
|
14
|
+
/**
|
|
15
|
+
* The page's language from the root loader's `language`. In the browser,
|
|
16
|
+
* the one picked with `chooseLanguage` wins, and the last one seen stands
|
|
17
|
+
* in when the root's data is gone (a failed navigation takes the root's
|
|
18
|
+
* with it): the error page keeps the page's language, not the fallback's.
|
|
19
|
+
* On the server it remembers nothing, since one module serves every request.
|
|
20
|
+
*/
|
|
21
|
+
pageLanguage(language: string | undefined): L;
|
|
22
|
+
/** The page's language in a route's `meta`, from `matches[0].loaderData.language`. */
|
|
23
|
+
metaLanguage(matches: ReadonlyArray<unknown>): L;
|
|
24
|
+
/**
|
|
25
|
+
* Records the language picked in a language menu: from then on the page's
|
|
26
|
+
* language in the browser, until the next document. Re-renders every
|
|
27
|
+
* `useChosenLanguage()`.
|
|
28
|
+
*/
|
|
29
|
+
chooseLanguage(language: L): void;
|
|
30
|
+
/** The language picked in this document, if any; re-renders on a new pick. */
|
|
31
|
+
useChosenLanguage(): L | undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The page's language for a root route: `<html lang>`, a route's `meta`, and
|
|
35
|
+
* the language menu. An instance, not module state: keep one per app, in a
|
|
36
|
+
* module both the root and the routes import.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createPageLanguage<const L extends string>(options: PageLanguageOptions<L>): PageLanguage<L>;
|
|
39
|
+
//# sourceMappingURL=page-language.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-language.d.ts","sourceRoot":"","sources":["../../src/document/page-language.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,MAAM;IACpD,oCAAoC;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,OAAO,CAAC,GAAG,SAAS,CAAC;CAC/C;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,MAAM;IAC7C;;;;;;OAMG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,CAAC,CAAC;IAC9C,sFAAsF;IACtF,YAAY,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjD;;;;OAIG;IACH,cAAc,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC;IAClC,8EAA8E;IAC9E,iBAAiB,IAAI,CAAC,GAAG,SAAS,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,EACxD,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC7B,YAAY,CAAC,CAAC,CAAC,CA+BjB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `dir` of a language tag: `'rtl'` for a right-to-left base language,
|
|
3
|
+
* `'ltr'` for the rest. The base tag is read whatever its case, so `ar-EG`
|
|
4
|
+
* and `AR` are `'rtl'` and `en-US` is `'ltr'`.
|
|
5
|
+
*
|
|
6
|
+
* Known gap: a script subtag is not read, so `az-Arab` (Azerbaijani in the
|
|
7
|
+
* Arabic script) is `'ltr'`, as `az` is.
|
|
8
|
+
*/
|
|
9
|
+
export declare function textDirection(language: string): 'ltr' | 'rtl';
|
|
10
|
+
//# sourceMappingURL=text-direction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-direction.d.ts","sourceRoot":"","sources":["../../src/document/text-direction.ts"],"names":[],"mappings":"AAoBA;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,CAG7D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** The render's abort timer: one per response, cleared when the response is over. */
|
|
2
|
+
export interface Deadline {
|
|
3
|
+
readonly signal: AbortSignal;
|
|
4
|
+
/** Stops the timer. Calling it again does nothing. */
|
|
5
|
+
readonly clear: () => void;
|
|
6
|
+
}
|
|
7
|
+
/** A signal aborted after `ms`, with the means to stop the timer. */
|
|
8
|
+
export declare function deadline(ms: number): Deadline;
|
|
9
|
+
/**
|
|
10
|
+
* The same bytes in a new stream that calls `done` when the body ends,
|
|
11
|
+
* fails or is cancelled (the client went away), so no timer outlives the
|
|
12
|
+
* response. React's `allReady` is not carried over: await it first.
|
|
13
|
+
*/
|
|
14
|
+
export declare function untilDone(body: ReadableStream<Uint8Array>, done: () => void): ReadableStream<Uint8Array>;
|
|
15
|
+
/**
|
|
16
|
+
* Gives up on a render that will not be served: stops its timer and cancels
|
|
17
|
+
* the body, which aborts React's work. A cancel that itself fails is ignored,
|
|
18
|
+
* so the error that got us here is the one the caller rethrows.
|
|
19
|
+
*/
|
|
20
|
+
export declare function abandon(limit: Pick<Deadline, 'clear'>, body: Pick<ReadableStream, 'cancel'> | undefined): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=abort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../../src/entry/abort.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,qEAAqE;AACrE,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,CAI7C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACxB,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,IAAI,EAAE,MAAM,IAAI,GACd,cAAc,CAAC,UAAU,CAAC,CAsB5B;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAC5B,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,EAC9B,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS,GAC9C,OAAO,CAAC,IAAI,CAAC,CAGf"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type EntryContext, type RouterContextProvider } from 'react-router';
|
|
2
|
+
export interface EntryOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Milliseconds React Router waits for a pending promise; the render itself
|
|
5
|
+
* is aborted 1 000 ms later. Export it as `streamTimeout`. Default 5 000.
|
|
6
|
+
*/
|
|
7
|
+
readonly streamTimeout?: number | undefined;
|
|
8
|
+
/** This response's CSP nonce. Default `nonceOf(loadContext)`. */
|
|
9
|
+
readonly nonce?: ((loadContext: RouterContextProvider, request: Request) => string | undefined) | undefined;
|
|
10
|
+
/** Whether a user agent is a bot, which gets the whole page. Default `isbot`, which is then needed. */
|
|
11
|
+
readonly isBot?: ((userAgent: string) => boolean) | undefined;
|
|
12
|
+
/** An error after the shell is out. Default `console.error`. */
|
|
13
|
+
readonly onError?: ((error: unknown) => void) | undefined;
|
|
14
|
+
}
|
|
15
|
+
/** React Router's `handleRequest` export of `entry.server`. */
|
|
16
|
+
export type HandleRequest = (request: Request, responseStatusCode: number, responseHeaders: Headers, routerContext: EntryContext, loadContext: RouterContextProvider) => Promise<Response>;
|
|
17
|
+
/** What `createEntry` returns: the two exports of `entry.server`. */
|
|
18
|
+
export interface Entry {
|
|
19
|
+
readonly handleRequest: HandleRequest;
|
|
20
|
+
readonly streamTimeout: number;
|
|
21
|
+
}
|
|
22
|
+
export declare const streamTimeout: number;
|
|
23
|
+
/**
|
|
24
|
+
* `entry.server`'s `handleRequest` and `streamTimeout`, on the web stream
|
|
25
|
+
* renderer. Streams the shell, waits for the whole page for a bot and in
|
|
26
|
+
* SPA mode, answers HEAD with no body, stops rendering when the client goes
|
|
27
|
+
* away, and gives the router the response's CSP nonce.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createEntry(options?: EntryOptions): Entry;
|
|
30
|
+
/** The default entry: `export { handleRequest as default, streamTimeout } from '@alxia/react-router/entry'`. */
|
|
31
|
+
export declare const handleRequest: HandleRequest;
|
|
32
|
+
//# sourceMappingURL=create-entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-entry.d.ts","sourceRoot":"","sources":["../../src/entry/create-entry.ts"],"names":[],"mappings":"AAKA,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAE1B,MAAM,cAAc,CAAC;AAMtB,MAAM,WAAW,YAAY;IAC5B;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EACZ,CAAC,CACD,WAAW,EAAE,qBAAqB,EAClC,OAAO,EAAE,OAAO,KACX,MAAM,GAAG,SAAS,CAAC,GACxB,SAAS,CAAC;IACb,uGAAuG;IACvG,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC;IAC9D,gEAAgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC1D;AAED,+DAA+D;AAC/D,MAAM,MAAM,aAAa,GAAG,CAC3B,OAAO,EAAE,OAAO,EAChB,kBAAkB,EAAE,MAAM,EAC1B,eAAe,EAAE,OAAO,EACxB,aAAa,EAAE,YAAY,EAC3B,WAAW,EAAE,qBAAqB,KAC9B,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB,qEAAqE;AACrE,MAAM,WAAW,KAAK;IACrB,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,aAAa,EAAE,MAAc,CAAC;AAE3C;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,YAAiB,GAAG,KAAK,CAgE7D;AAID,gHAAgH;AAChH,eAAO,MAAM,aAAa,EAAE,aAAsC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@alxia/react-router/entry`: `entry.server.tsx` for a React Router app on
|
|
3
|
+
* Bun. Server only. It imports `react`, `react-dom/server`, `react-router`
|
|
4
|
+
* and, only when no `isBot` option is given, `isbot`: all optional peers but
|
|
5
|
+
* `react-router`.
|
|
6
|
+
*/
|
|
7
|
+
export { createEntry, type Entry, type EntryOptions, type HandleRequest, handleRequest, streamTimeout, } from './create-entry';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/entry/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACN,WAAW,EACX,KAAK,KAAK,EACV,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,aAAa,EACb,aAAa,GACb,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DocumentNonce2
|
|
3
|
+
} from "../chunks/index-y6vpnjr3.js";
|
|
4
|
+
import {
|
|
5
|
+
nonceOf2
|
|
6
|
+
} from "../chunks/index-5rt6hnhn.js";
|
|
7
|
+
|
|
8
|
+
// src/entry/create-entry.ts
|
|
9
|
+
import { createElement } from "react";
|
|
10
|
+
import {
|
|
11
|
+
renderToReadableStream
|
|
12
|
+
} from "react-dom/server";
|
|
13
|
+
import {
|
|
14
|
+
ServerRouter
|
|
15
|
+
} from "react-router";
|
|
16
|
+
|
|
17
|
+
// src/entry/abort.ts
|
|
18
|
+
function deadline(ms) {
|
|
19
|
+
const controller = new AbortController;
|
|
20
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
21
|
+
return { signal: controller.signal, clear: () => clearTimeout(timer) };
|
|
22
|
+
}
|
|
23
|
+
function untilDone(body, done) {
|
|
24
|
+
const reader = body.getReader();
|
|
25
|
+
return new ReadableStream({
|
|
26
|
+
async pull(controller) {
|
|
27
|
+
try {
|
|
28
|
+
const chunk = await reader.read();
|
|
29
|
+
if (chunk.done) {
|
|
30
|
+
done();
|
|
31
|
+
controller.close();
|
|
32
|
+
} else {
|
|
33
|
+
controller.enqueue(chunk.value);
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
done();
|
|
37
|
+
controller.error(error);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
async cancel(reason) {
|
|
41
|
+
done();
|
|
42
|
+
await reader.cancel(reason);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async function abandon(limit, body) {
|
|
47
|
+
limit.clear();
|
|
48
|
+
await body?.cancel().catch(() => {});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/entry/whole-page.ts
|
|
52
|
+
var loaded;
|
|
53
|
+
function defaultIsBot() {
|
|
54
|
+
loaded ??= import("isbot").then((module) => module.isbot, (error) => {
|
|
55
|
+
loaded = undefined;
|
|
56
|
+
throw error;
|
|
57
|
+
});
|
|
58
|
+
return loaded;
|
|
59
|
+
}
|
|
60
|
+
async function wantsWholePage(request, isSpaMode, isBot) {
|
|
61
|
+
if (isSpaMode)
|
|
62
|
+
return true;
|
|
63
|
+
const agent = request.headers.get("user-agent");
|
|
64
|
+
if (agent === null)
|
|
65
|
+
return false;
|
|
66
|
+
return (isBot ?? await defaultIsBot())(agent);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/entry/create-entry.ts
|
|
70
|
+
var streamTimeout = 5000;
|
|
71
|
+
function createEntry(options = {}) {
|
|
72
|
+
const timeout = options.streamTimeout ?? streamTimeout;
|
|
73
|
+
const nonceFor = options.nonce ?? nonceOf2;
|
|
74
|
+
const report = options.onError ?? console.error;
|
|
75
|
+
const handleRequest = async (request, responseStatusCode, responseHeaders, routerContext, loadContext) => {
|
|
76
|
+
responseHeaders.set("Content-Type", "text/html");
|
|
77
|
+
if (request.method === "HEAD") {
|
|
78
|
+
return new Response(null, {
|
|
79
|
+
status: responseStatusCode,
|
|
80
|
+
headers: responseHeaders
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const nonce = nonceFor(loadContext, request);
|
|
84
|
+
const limit = deadline(timeout + 1000);
|
|
85
|
+
let status = responseStatusCode;
|
|
86
|
+
let shellOut = false;
|
|
87
|
+
let body;
|
|
88
|
+
try {
|
|
89
|
+
body = await renderToReadableStream(createElement(DocumentNonce2, { nonce }, createElement(ServerRouter, {
|
|
90
|
+
context: routerContext,
|
|
91
|
+
url: request.url,
|
|
92
|
+
...nonce === undefined ? {} : { nonce }
|
|
93
|
+
})), {
|
|
94
|
+
nonce,
|
|
95
|
+
signal: AbortSignal.any([limit.signal, request.signal]),
|
|
96
|
+
onError(error) {
|
|
97
|
+
if (request.signal.aborted)
|
|
98
|
+
return;
|
|
99
|
+
status = 500;
|
|
100
|
+
if (shellOut)
|
|
101
|
+
report(error);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
shellOut = true;
|
|
105
|
+
if (await wantsWholePage(request, routerContext.isSpaMode, options.isBot)) {
|
|
106
|
+
await body.allReady;
|
|
107
|
+
}
|
|
108
|
+
return new Response(untilDone(body, limit.clear), {
|
|
109
|
+
status,
|
|
110
|
+
headers: responseHeaders
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
await abandon(limit, body);
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
return { handleRequest, streamTimeout: timeout };
|
|
118
|
+
}
|
|
119
|
+
var defaults = createEntry();
|
|
120
|
+
var handleRequest = defaults.handleRequest;
|
|
121
|
+
export {
|
|
122
|
+
createEntry,
|
|
123
|
+
handleRequest,
|
|
124
|
+
streamTimeout
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
//# debugId=EB32BB9D687DF8D664756E2164756E21
|
|
128
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/entry/create-entry.ts", "../src/entry/abort.ts", "../src/entry/whole-page.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { createElement } from 'react';\nimport {\n\ttype ReactDOMServerReadableStream,\n\trenderToReadableStream,\n} from 'react-dom/server';\nimport {\n\ttype EntryContext,\n\ttype RouterContextProvider,\n\tServerRouter,\n} from 'react-router';\nimport { DocumentNonce } from '../document';\nimport { nonceOf } from '../nonce';\nimport { abandon, deadline, untilDone } from './abort';\nimport { wantsWholePage } from './whole-page';\n\nexport interface EntryOptions {\n\t/**\n\t * Milliseconds React Router waits for a pending promise; the render itself\n\t * is aborted 1 000 ms later. Export it as `streamTimeout`. Default 5 000.\n\t */\n\treadonly streamTimeout?: number | undefined;\n\t/** This response's CSP nonce. Default `nonceOf(loadContext)`. */\n\treadonly nonce?:\n\t\t| ((\n\t\t\t\tloadContext: RouterContextProvider,\n\t\t\t\trequest: Request,\n\t\t ) => string | undefined)\n\t\t| undefined;\n\t/** Whether a user agent is a bot, which gets the whole page. Default `isbot`, which is then needed. */\n\treadonly isBot?: ((userAgent: string) => boolean) | undefined;\n\t/** An error after the shell is out. Default `console.error`. */\n\treadonly onError?: ((error: unknown) => void) | undefined;\n}\n\n/** React Router's `handleRequest` export of `entry.server`. */\nexport type HandleRequest = (\n\trequest: Request,\n\tresponseStatusCode: number,\n\tresponseHeaders: Headers,\n\trouterContext: EntryContext,\n\tloadContext: RouterContextProvider,\n) => Promise<Response>;\n\n/** What `createEntry` returns: the two exports of `entry.server`. */\nexport interface Entry {\n\treadonly handleRequest: HandleRequest;\n\treadonly streamTimeout: number;\n}\n\nexport const streamTimeout: number = 5_000;\n\n/**\n * `entry.server`'s `handleRequest` and `streamTimeout`, on the web stream\n * renderer. Streams the shell, waits for the whole page for a bot and in\n * SPA mode, answers HEAD with no body, stops rendering when the client goes\n * away, and gives the router the response's CSP nonce.\n */\nexport function createEntry(options: EntryOptions = {}): Entry {\n\tconst timeout = options.streamTimeout ?? streamTimeout;\n\tconst nonceFor = options.nonce ?? nonceOf;\n\tconst report = options.onError ?? console.error;\n\n\tconst handleRequest: HandleRequest = async (\n\t\trequest,\n\t\tresponseStatusCode,\n\t\tresponseHeaders,\n\t\trouterContext,\n\t\tloadContext,\n\t) => {\n\t\tresponseHeaders.set('Content-Type', 'text/html');\n\t\tif (request.method === 'HEAD') {\n\t\t\treturn new Response(null, {\n\t\t\t\tstatus: responseStatusCode,\n\t\t\t\theaders: responseHeaders,\n\t\t\t});\n\t\t}\n\t\tconst nonce = nonceFor(loadContext, request);\n\t\tconst limit = deadline(timeout + 1_000);\n\t\tlet status = responseStatusCode;\n\t\tlet shellOut = false;\n\t\tlet body: ReactDOMServerReadableStream | undefined;\n\t\ttry {\n\t\t\tbody = await renderToReadableStream(\n\t\t\t\tcreateElement(\n\t\t\t\t\tDocumentNonce,\n\t\t\t\t\t{ nonce },\n\t\t\t\t\tcreateElement(ServerRouter, {\n\t\t\t\t\t\tcontext: routerContext,\n\t\t\t\t\t\turl: request.url,\n\t\t\t\t\t\t// Under exactOptionalPropertyTypes, ServerRouter's nonce takes no undefined.\n\t\t\t\t\t\t...(nonce === undefined ? {} : { nonce }),\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\t{\n\t\t\t\t\tnonce,\n\t\t\t\t\tsignal: AbortSignal.any([limit.signal, request.signal]),\n\t\t\t\t\tonError(error: unknown) {\n\t\t\t\t\t\t// A client that left is not an error of ours.\n\t\t\t\t\t\tif (request.signal.aborted) return;\n\t\t\t\t\t\tstatus = 500;\n\t\t\t\t\t\tif (shellOut) report(error);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t\tshellOut = true;\n\t\t\tif (\n\t\t\t\tawait wantsWholePage(request, routerContext.isSpaMode, options.isBot)\n\t\t\t) {\n\t\t\t\tawait body.allReady;\n\t\t\t}\n\t\t\treturn new Response(untilDone(body, limit.clear), {\n\t\t\t\tstatus,\n\t\t\t\theaders: responseHeaders,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\t// A throwing isBot or a rejected allReady: stop the render.\n\t\t\tawait abandon(limit, body);\n\t\t\tthrow error;\n\t\t}\n\t};\n\treturn { handleRequest, streamTimeout: timeout };\n}\n\nconst defaults = createEntry();\n\n/** The default entry: `export { handleRequest as default, streamTimeout } from '@alxia/react-router/entry'`. */\nexport const handleRequest: HandleRequest = defaults.handleRequest;\n",
|
|
6
|
+
"/** The render's abort timer: one per response, cleared when the response is over. */\nexport interface Deadline {\n\treadonly signal: AbortSignal;\n\t/** Stops the timer. Calling it again does nothing. */\n\treadonly clear: () => void;\n}\n\n/** A signal aborted after `ms`, with the means to stop the timer. */\nexport function deadline(ms: number): Deadline {\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), ms);\n\treturn { signal: controller.signal, clear: () => clearTimeout(timer) };\n}\n\n/**\n * The same bytes in a new stream that calls `done` when the body ends,\n * fails or is cancelled (the client went away), so no timer outlives the\n * response. React's `allReady` is not carried over: await it first.\n */\nexport function untilDone(\n\tbody: ReadableStream<Uint8Array>,\n\tdone: () => void,\n): ReadableStream<Uint8Array> {\n\tconst reader = body.getReader();\n\treturn new ReadableStream<Uint8Array>({\n\t\tasync pull(controller) {\n\t\t\ttry {\n\t\t\t\tconst chunk = await reader.read();\n\t\t\t\tif (chunk.done) {\n\t\t\t\t\tdone();\n\t\t\t\t\tcontroller.close();\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.enqueue(chunk.value);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tdone();\n\t\t\t\tcontroller.error(error);\n\t\t\t}\n\t\t},\n\t\tasync cancel(reason) {\n\t\t\tdone();\n\t\t\tawait reader.cancel(reason);\n\t\t},\n\t});\n}\n\n/**\n * Gives up on a render that will not be served: stops its timer and cancels\n * the body, which aborts React's work. A cancel that itself fails is ignored,\n * so the error that got us here is the one the caller rethrows.\n */\nexport async function abandon(\n\tlimit: Pick<Deadline, 'clear'>,\n\tbody: Pick<ReadableStream, 'cancel'> | undefined,\n): Promise<void> {\n\tlimit.clear();\n\tawait body?.cancel().catch(() => {});\n}\n",
|
|
7
|
+
"type IsBot = (userAgent: string) => boolean;\n\nlet loaded: Promise<IsBot> | undefined;\n\n/**\n * `isbot`, imported on the first bot check that needs it and kept: an\n * optional peer, so an app that gives `isBot` never loads it. A failed import\n * is not kept, so a later request tries again.\n */\nfunction defaultIsBot(): Promise<IsBot> {\n\tloaded ??= import('isbot').then(\n\t\t(module) => module.isbot,\n\t\t(error: unknown) => {\n\t\t\tloaded = undefined;\n\t\t\tthrow error;\n\t\t},\n\t);\n\treturn loaded;\n}\n\n/**\n * Whether the response waits for the whole page instead of streaming the\n * shell: a bot (`isBot`, `isbot` by default, imported only then) gets\n * complete HTML to index, and SPA mode renders the one shell the build\n * prerenders.\n */\nexport async function wantsWholePage(\n\trequest: Request,\n\tisSpaMode: boolean,\n\tisBot?: IsBot,\n): Promise<boolean> {\n\tif (isSpaMode) return true;\n\tconst agent = request.headers.get('user-agent');\n\tif (agent === null) return false;\n\treturn (isBot ?? (await defaultIsBot()))(agent);\n}\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "EB32BB9D687DF8D664756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type IsBot = (userAgent: string) => boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Whether the response waits for the whole page instead of streaming the
|
|
4
|
+
* shell: a bot (`isBot`, `isbot` by default, imported only then) gets
|
|
5
|
+
* complete HTML to index, and SPA mode renders the one shell the build
|
|
6
|
+
* prerenders.
|
|
7
|
+
*/
|
|
8
|
+
export declare function wantsWholePage(request: Request, isSpaMode: boolean, isBot?: IsBot): Promise<boolean>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=whole-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whole-page.d.ts","sourceRoot":"","sources":["../../src/entry/whole-page.ts"],"names":[],"mappings":"AAAA,KAAK,KAAK,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;AAoB5C;;;;;GAKG;AACH,wBAAsB,cAAc,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,OAAO,EAClB,KAAK,CAAC,EAAE,KAAK,GACX,OAAO,CAAC,OAAO,CAAC,CAKlB"}
|