@antelopejs/dms-frontend 0.9.2 → 0.10.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 +18 -24
- package/dist/commands/build-action.js +3 -2
- package/dist/commands/dev-action.js +2 -1
- package/dist/commands/prepare-action.js +1 -1
- package/dist/commands/start-action.js +3 -24
- package/dist/commands/workspace-task.js +2 -1
- package/dist/config.js +3 -0
- package/dist/derived-outputs.js +3 -3
- package/dist/discovery.js +17 -6
- package/dist/entry-weight.js +15 -0
- package/dist/fs-sync.js +7 -4
- package/dist/index.js +2 -1
- package/dist/internal/built-workspace.d.ts +4 -0
- package/dist/internal/built-workspace.js +43 -0
- package/dist/internal/ensure.d.ts +1 -0
- package/dist/internal/ensure.js +8 -0
- package/dist/internal/locale-catalogs.d.ts +8 -3
- package/dist/internal/locale-catalogs.js +16 -6
- package/dist/internal/manifest-decoder.d.ts +7 -0
- package/dist/internal/manifest-decoder.js +81 -0
- package/dist/internal/own-release.d.ts +5 -0
- package/dist/internal/own-release.js +20 -0
- package/dist/internal/plain-object.d.ts +1 -0
- package/dist/internal/plain-object.js +6 -0
- package/dist/internal/text-list.d.ts +1 -0
- package/dist/internal/text-list.js +10 -0
- package/dist/internal/vite-manifest.d.ts +6 -0
- package/dist/internal/vite-manifest.js +24 -0
- package/dist/layer-watch.js +25 -24
- package/dist/layers.d.ts +1 -1
- package/dist/layers.js +3 -1
- package/dist/locale-files.d.ts +2 -1
- package/dist/locale-files.js +4 -0
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +44 -31
- package/dist/materialize.js +6 -2
- package/dist/module-set.js +2 -1
- package/dist/node-options.d.ts +1 -0
- package/dist/node-options.js +8 -0
- package/dist/output.d.ts +15 -9
- package/dist/output.js +30 -18
- package/dist/private-values.d.ts +1 -1
- package/dist/private-values.js +5 -3
- package/dist/project-modules.js +13 -8
- package/dist/renderer-range.js +5 -3
- package/dist/server-process.js +1 -1
- package/dist/verify-source-build.js +10 -2
- package/dist/verify-source-client-bundle.d.ts +1 -0
- package/dist/verify-source-client-bundle.js +30 -0
- package/dist/verify-source-runner.js +44 -60
- package/dist/verify-source-sources.js +49 -11
- package/dist/workspace-setup.js +1 -1
- package/package.json +6 -4
- package/skills/dms-frontend/SKILL.md +1 -1
- package/templates/vue/app-runtime.ts +90 -16
- package/templates/vue/client-server-guard.mjs +16 -0
- package/templates/vue/compress-assets.mjs +9 -1
- package/templates/vue/entry-weight.mjs +22 -0
- package/templates/vue/frontend-module.ts +1 -0
- package/templates/vue/frontend-runtime/module-registration.ts +2 -2
- package/templates/vue/frontend-runtime/state.ts +2 -2
- package/templates/vue/frontend-runtime/types.ts +8 -2
- package/templates/vue/globals.d.ts +5 -0
- package/templates/vue/head-order.mjs +7 -0
- package/templates/vue/icon-bundle.ts +31 -0
- package/templates/vue/icon-scan.mjs +4 -0
- package/templates/vue/message-subset.ts +65 -0
- package/templates/vue/network-error.ts +4 -0
- package/templates/vue/server/auth/backend.mjs +41 -4
- package/templates/vue/server/auth/client-ip.mjs +52 -4
- package/templates/vue/server/auth/establish-endpoints.mjs +10 -6
- package/templates/vue/server/auth/session.mjs +81 -2
- package/templates/vue/server/auth-hooks.mjs +15 -5
- package/templates/vue/server/backend-proxies.mjs +65 -9
- package/templates/vue/server/backend-request.mjs +61 -0
- package/templates/vue/server/backend-response.mjs +23 -6
- package/templates/vue/server/built-files.mjs +7 -0
- package/templates/vue/server/cli-report.mjs +53 -11
- package/templates/vue/server/client-manifest.mjs +23 -1
- package/templates/vue/server/content.mjs +18 -1
- package/templates/vue/server/dev-private-files.mjs +17 -1
- package/templates/vue/server/dev-styles.mjs +38 -0
- package/templates/vue/server/email.mjs +9 -3
- package/templates/vue/server/favicon.mjs +3 -0
- package/templates/vue/server/generated-files.mjs +211 -0
- package/templates/vue/server/homepage.mjs +21 -9
- package/templates/vue/server/html-tag.mjs +4 -0
- package/templates/vue/server/icons.mjs +80 -7
- package/templates/vue/server/in-place-render.mjs +35 -0
- package/templates/vue/server/inertia.mjs +61 -2
- package/templates/vue/server/module-event.mjs +98 -17
- package/templates/vue/server/module-route-types.mjs +78 -0
- package/templates/vue/server/module-routes.mjs +138 -31
- package/templates/vue/server/module-server.mjs +98 -27
- package/templates/vue/server/module-testing.mjs +14 -5
- package/templates/vue/server/page-props.mjs +92 -2
- package/templates/vue/server/page-redirects.mjs +37 -0
- package/templates/vue/server/path-patterns.mjs +13 -1
- package/templates/vue/server/proxy-compression.mjs +22 -0
- package/templates/vue/server/proxy-headers.mjs +11 -1
- package/templates/vue/server/proxy-redirects.mjs +20 -5
- package/templates/vue/server/proxy-request.mjs +57 -0
- package/templates/vue/server/proxy-transfers.mjs +33 -5
- package/templates/vue/server/public-endpoints.mjs +138 -23
- package/templates/vue/server/public-options.mjs +20 -6
- package/templates/vue/server/request-failure.mjs +23 -0
- package/templates/vue/server/request-routing.mjs +26 -9
- package/templates/vue/server/request-target.mjs +9 -3
- package/templates/vue/server/route-rules.mjs +39 -15
- package/templates/vue/server/ssr-module.mjs +32 -0
- package/templates/vue/server/static-files.mjs +24 -1
- package/templates/vue/server/type-guards.mjs +71 -0
- package/templates/vue/server/vite-errors.mjs +32 -5
- package/templates/vue/server/web-body.mjs +3 -0
- package/templates/vue/server-module.ts +10 -1
- package/templates/vue/server.mjs +132 -148
- package/templates/vue/sfc-global-types.mjs +112 -13
- package/templates/vue/ssr-renderer.ts +12 -3
- package/templates/vue/ui-inertia-page.mjs +16 -1
- package/templates/vue/ui-kbd.mjs +2 -1
- package/templates/vue/ui-lazy-icons.mjs +44 -0
- package/templates/vue/ui-request-state.mjs +2 -1
- package/templates/vue/vite.config.ts +2 -0
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { compilePathPattern } from "./path-patterns.mjs";
|
|
2
|
+
import { isNonNullObject, isObjectLike, isString } from "./type-guards.mjs";
|
|
3
|
+
|
|
4
|
+
/** @typedef {import("../server-module.ts").DmsRouteRule} RouteRule */
|
|
5
|
+
/** @typedef {RouteRule & { headers: Record<string, string> }} MergedRouteRule */
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} ResolvedRouteRule
|
|
8
|
+
* @property {string} id The declaring module
|
|
9
|
+
* @property {number} priority The declaring module's priority
|
|
10
|
+
* @property {import("./path-patterns.mjs").CompiledPathPattern} pattern
|
|
11
|
+
* @property {RouteRule} rule
|
|
12
|
+
*/
|
|
2
13
|
|
|
3
14
|
const HEADER_NAME = /^[!#$%&'*+.^_`|~0-9a-z-]+$/;
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
15
|
+
/**
|
|
16
|
+
* Whether a header value holds a control character other than a tab: a CR
|
|
17
|
+
* or an LF would split the answer.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} value
|
|
20
|
+
*/
|
|
6
21
|
function hasControlCharacter(value) {
|
|
7
|
-
return
|
|
22
|
+
return Array.from(value).some((character) => {
|
|
8
23
|
const code = character.charCodeAt(0);
|
|
9
24
|
return (code < 0x20 && code !== 0x09) || code === 0x7f;
|
|
10
25
|
});
|
|
@@ -20,19 +35,21 @@ const RESERVED_HEADERS = new Set([
|
|
|
20
35
|
"x-inertia-version",
|
|
21
36
|
]);
|
|
22
37
|
|
|
38
|
+
/** @param {unknown} value */
|
|
23
39
|
function isSeconds(value) {
|
|
24
40
|
return Number.isSafeInteger(value) && value >= 0;
|
|
25
41
|
}
|
|
26
42
|
|
|
43
|
+
/** @param {unknown} headers */
|
|
27
44
|
function headersRejection(headers) {
|
|
28
45
|
if (headers === undefined) return undefined;
|
|
29
|
-
if (
|
|
46
|
+
if (!isNonNullObject(headers) || Array.isArray(headers))
|
|
30
47
|
return "headers is not an object";
|
|
31
48
|
for (const [name, value] of Object.entries(headers)) {
|
|
32
49
|
const lower = name.toLowerCase();
|
|
33
50
|
if (
|
|
34
51
|
!HEADER_NAME.test(lower) ||
|
|
35
|
-
|
|
52
|
+
!isString(value) ||
|
|
36
53
|
hasControlCharacter(value)
|
|
37
54
|
)
|
|
38
55
|
return `"${name}" is not a header with a text value`;
|
|
@@ -42,11 +59,11 @@ function headersRejection(headers) {
|
|
|
42
59
|
return undefined;
|
|
43
60
|
}
|
|
44
61
|
|
|
62
|
+
/** @param {unknown} cache */
|
|
45
63
|
function cacheRejection(cache) {
|
|
46
64
|
if (cache === undefined || cache === false) return undefined;
|
|
47
65
|
if (
|
|
48
|
-
|
|
49
|
-
cache === null ||
|
|
66
|
+
!isNonNullObject(cache) ||
|
|
50
67
|
cache.public !== true ||
|
|
51
68
|
!isSeconds(cache.maxAge) ||
|
|
52
69
|
(cache.staleWhileRevalidate !== undefined &&
|
|
@@ -62,11 +79,14 @@ function cacheRejection(cache) {
|
|
|
62
79
|
* Route rules the modules declare in their `dms.server.ts`: how a page is
|
|
63
80
|
* rendered and which headers its document and Inertia answers carry. They
|
|
64
81
|
* change rendering and caching, never who may see a page.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} pattern
|
|
84
|
+
* @param {unknown} rule
|
|
65
85
|
*/
|
|
66
86
|
export function ruleRejection(pattern, rule) {
|
|
67
87
|
if (!compilePathPattern(pattern))
|
|
68
88
|
return "the pattern is not /x, /x/* or /x/**";
|
|
69
|
-
if (
|
|
89
|
+
if (!isNonNullObject(rule)) return "it is not an object";
|
|
70
90
|
if (rule.ssr !== undefined && rule.ssr !== false)
|
|
71
91
|
return "ssr is either left out or false";
|
|
72
92
|
return headersRejection(rule.headers) ?? cacheRejection(rule.cache);
|
|
@@ -77,10 +97,12 @@ export function ruleRejection(pattern, rule) {
|
|
|
77
97
|
* specific pattern: a more specific rule overrides `ssr`, `cache` and each
|
|
78
98
|
* header it sets.
|
|
79
99
|
*
|
|
80
|
-
* @param rules The resolved rules, `{ pattern, rule }` each
|
|
81
|
-
* @param pathname The page's path
|
|
100
|
+
* @param {readonly ResolvedRouteRule[]} rules The resolved rules, `{ pattern, rule }` each
|
|
101
|
+
* @param {string} pathname The page's path
|
|
102
|
+
* @returns {MergedRouteRule}
|
|
82
103
|
*/
|
|
83
104
|
export function routeRuleFor(rules, pathname) {
|
|
105
|
+
/** @type {MergedRouteRule} */
|
|
84
106
|
const merged = { headers: {} };
|
|
85
107
|
const matching = rules
|
|
86
108
|
.filter(({ pattern }) => pattern.test(pathname))
|
|
@@ -95,6 +117,7 @@ export function routeRuleFor(rules, pathname) {
|
|
|
95
117
|
return merged;
|
|
96
118
|
}
|
|
97
119
|
|
|
120
|
+
/** @param {{ maxAge: number, staleWhileRevalidate?: number }} cache */
|
|
98
121
|
function publicCacheControl(cache) {
|
|
99
122
|
const stale =
|
|
100
123
|
cache.staleWhileRevalidate === undefined
|
|
@@ -109,10 +132,10 @@ function publicCacheControl(cache) {
|
|
|
109
132
|
* content language: the answer then holds nobody's data, and `Vary: Cookie`
|
|
110
133
|
* keeps a shared cache from serving it to a visitor who has some.
|
|
111
134
|
*
|
|
112
|
-
* @param rule What `routeRuleFor` returned
|
|
113
|
-
* @param request The page request
|
|
114
|
-
* @param route The page's route, as the backend described it
|
|
115
|
-
* @param status The answer's status: only a 200 is cached
|
|
135
|
+
* @param {MergedRouteRule} rule What `routeRuleFor` returned
|
|
136
|
+
* @param {import("node:http").IncomingMessage} request The page request
|
|
137
|
+
* @param {unknown} route The page's route, as the backend described it: any JSON value, only `publicAccess` is read
|
|
138
|
+
* @param {number} status The answer's status: only a 200 is cached
|
|
116
139
|
*/
|
|
117
140
|
export function ruleHeaders(rule, request, route, status) {
|
|
118
141
|
const headers = { ...rule.headers };
|
|
@@ -122,7 +145,8 @@ export function ruleHeaders(rule, request, route, status) {
|
|
|
122
145
|
request.method === "GET" &&
|
|
123
146
|
!request.headers.cookie &&
|
|
124
147
|
!request.headers["x-content-language"] &&
|
|
125
|
-
route
|
|
148
|
+
isObjectLike(route) &&
|
|
149
|
+
route.publicAccess === true
|
|
126
150
|
) {
|
|
127
151
|
headers["cache-control"] = publicCacheControl(rule.cache);
|
|
128
152
|
headers.vary = "X-Inertia, Cookie";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as icons from "./icons.mjs";
|
|
2
|
+
|
|
3
|
+
/** @typedef {import("vite").ViteDevServer} ViteDevServer */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A URL, not a path: on Windows, `import()` reads `C:\...` as a URL whose
|
|
7
|
+
* scheme is `c:` and refuses it.
|
|
8
|
+
*/
|
|
9
|
+
const BUILT_SSR_RENDERER_URL = new URL(
|
|
10
|
+
"../dist/ssr/ssr-renderer.js",
|
|
11
|
+
import.meta.url,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
/** @type {Promise<typeof import("../ssr-renderer.ts")> | undefined} */
|
|
15
|
+
let productionSsrRenderer;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The SSR renderer of the workspace: the development server's module, else
|
|
19
|
+
* the built bundle, loaded once.
|
|
20
|
+
*
|
|
21
|
+
* @param {ViteDevServer | undefined} devServer
|
|
22
|
+
* @returns {Promise<typeof import("../ssr-renderer.ts")>}
|
|
23
|
+
*/
|
|
24
|
+
export async function ssrRenderer(devServer) {
|
|
25
|
+
/** @type {typeof import("../ssr-renderer.ts")} */
|
|
26
|
+
// oxlint-disable-next-line typescript/no-unsafe-assignment -- `ssrLoadModule` types every module `Record<string, any>` and the production import takes a URL computed at run time; both load this template's own `ssr-renderer.ts`, the type named above.
|
|
27
|
+
const renderer = devServer
|
|
28
|
+
? await devServer.ssrLoadModule("/ssr-renderer.ts")
|
|
29
|
+
: await (productionSsrRenderer ??= import(BUILT_SSR_RENDERER_URL.href));
|
|
30
|
+
await icons.provideIconCollections(renderer);
|
|
31
|
+
return renderer;
|
|
32
|
+
}
|
|
@@ -5,6 +5,8 @@ import { extname } from "node:path";
|
|
|
5
5
|
* What a build or a module's `public/` directory may hold. A type missing
|
|
6
6
|
* here went out as `application/octet-stream`: fonts among them, which a
|
|
7
7
|
* browser then reports as a mismatch against the `font/woff2` it preloaded.
|
|
8
|
+
*
|
|
9
|
+
* @type {Record<string, string | undefined>}
|
|
8
10
|
*/
|
|
9
11
|
const MIME_TYPES = {
|
|
10
12
|
".avif": "image/avif",
|
|
@@ -46,9 +48,15 @@ const IMMUTABLE_CACHE = "public, max-age=31536000, immutable";
|
|
|
46
48
|
*/
|
|
47
49
|
const REVALIDATED_CACHE = "no-cache";
|
|
48
50
|
|
|
51
|
+
/** @param {string} path */
|
|
49
52
|
const isFile = (path) => existsSync(path) && statSync(path).isFile();
|
|
50
53
|
|
|
51
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* The precompressed variant of `file` the client accepts, if the build wrote one.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} file
|
|
58
|
+
* @param {import("node:http").IncomingMessage} request
|
|
59
|
+
*/
|
|
52
60
|
function acceptedVariant(file, request) {
|
|
53
61
|
const accepted = request.headers["accept-encoding"] ?? "";
|
|
54
62
|
if (accepted.includes("br") && isFile(`${file}.br`))
|
|
@@ -61,17 +69,26 @@ function acceptedVariant(file, request) {
|
|
|
61
69
|
/**
|
|
62
70
|
* Weak, as the bytes of one representation are not compared: the size and
|
|
63
71
|
* modification time of the file sent, so each encoding has its own.
|
|
72
|
+
*
|
|
73
|
+
* @param {import("node:fs").Stats} stats
|
|
74
|
+
* @returns {string}
|
|
64
75
|
*/
|
|
65
76
|
function entityTag(stats) {
|
|
66
77
|
return `W/"${stats.size.toString(16)}-${Math.floor(stats.mtimeMs).toString(16)}"`;
|
|
67
78
|
}
|
|
68
79
|
|
|
80
|
+
/** @param {string} tag */
|
|
69
81
|
const opaqueTag = (tag) => tag.trim().replace(/^W\//, "");
|
|
70
82
|
|
|
71
83
|
/**
|
|
72
84
|
* RFC 9110 §13.1.2 and §13.1.3: `If-None-Match` first, the date otherwise.
|
|
73
85
|
*
|
|
74
86
|
* The header has a one-second resolution.
|
|
87
|
+
*
|
|
88
|
+
* @param {import("node:http").IncomingMessage} request
|
|
89
|
+
* @param {string} tag
|
|
90
|
+
* @param {import("node:fs").Stats} stats
|
|
91
|
+
* @returns {boolean}
|
|
75
92
|
*/
|
|
76
93
|
function isNotModified(request, tag, stats) {
|
|
77
94
|
const noneMatch = request.headers["if-none-match"];
|
|
@@ -92,11 +109,17 @@ function isNotModified(request, tag, stats) {
|
|
|
92
109
|
* Send `file`, a file the caller already resolved and checked (see
|
|
93
110
|
* `publicBuildFile`), as the variant the client accepts, or answer 304 when the
|
|
94
111
|
* client's copy is current. `immutable` for a file named after its content.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} file
|
|
114
|
+
* @param {import("node:http").IncomingMessage} request
|
|
115
|
+
* @param {import("node:http").ServerResponse} response
|
|
116
|
+
* @param {{ immutable: boolean }} options
|
|
95
117
|
*/
|
|
96
118
|
export function serveStaticFile(file, request, response, { immutable }) {
|
|
97
119
|
const selected = acceptedVariant(file, request);
|
|
98
120
|
const stats = statSync(selected.file);
|
|
99
121
|
const tag = entityTag(stats);
|
|
122
|
+
/** @type {import("node:http").OutgoingHttpHeaders} */
|
|
100
123
|
const headers = {
|
|
101
124
|
"cache-control": immutable ? IMMUTABLE_CACHE : REVALIDATED_CACHE,
|
|
102
125
|
etag: tag,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether `value` is a string primitive, a boxed `String` excluded.
|
|
3
|
+
*
|
|
4
|
+
* @param {unknown} value The value to test
|
|
5
|
+
* @returns {value is string} Whether `typeof value` is `"string"`
|
|
6
|
+
*/
|
|
7
|
+
export function isString(value) {
|
|
8
|
+
return typeof value === "string";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Whether `value` is a number primitive, `NaN` and the infinities included.
|
|
13
|
+
*
|
|
14
|
+
* @param {unknown} value The value to test
|
|
15
|
+
* @returns {value is number} Whether `typeof value` is `"number"`
|
|
16
|
+
*/
|
|
17
|
+
export function isNumber(value) {
|
|
18
|
+
return typeof value === "number";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether `value` is a boolean primitive.
|
|
23
|
+
*
|
|
24
|
+
* @param {unknown} value The value to test
|
|
25
|
+
* @returns {value is boolean} Whether `typeof value` is `"boolean"`
|
|
26
|
+
*/
|
|
27
|
+
export function isBoolean(value) {
|
|
28
|
+
return typeof value === "boolean";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether `value` is callable: a function, an arrow, a class or a method.
|
|
33
|
+
*
|
|
34
|
+
* @param {unknown} value The value to test
|
|
35
|
+
* @returns {value is Function} Whether `typeof value` is `"function"`
|
|
36
|
+
*/
|
|
37
|
+
export function isFunction(value) {
|
|
38
|
+
return typeof value === "function";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether `value` is an object other than `null`: arrays are objects.
|
|
43
|
+
*
|
|
44
|
+
* @param {unknown} value The value to test
|
|
45
|
+
* @returns {value is object} Whether `typeof value` is `"object"` and `value` is not `null`
|
|
46
|
+
*/
|
|
47
|
+
export function isNonNullObject(value) {
|
|
48
|
+
return typeof value === "object" && value !== null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether `value` can carry properties a caller reads: an object or a
|
|
53
|
+
* function, `null` excluded. Every property read through it is `unknown`.
|
|
54
|
+
*
|
|
55
|
+
* @param {unknown} value The value to test
|
|
56
|
+
* @returns {value is Record<string, unknown>} Whether `value` is an object or a function
|
|
57
|
+
*/
|
|
58
|
+
export function isObjectLike(value) {
|
|
59
|
+
return isNonNullObject(value) || isFunction(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether `value` is an object other than `null`, read by its properties:
|
|
64
|
+
* arrays are objects, functions are not.
|
|
65
|
+
*
|
|
66
|
+
* @param {unknown} value The value to test
|
|
67
|
+
* @returns {value is Record<string, unknown>} Whether `typeof value` is `"object"` and `value` is not `null`
|
|
68
|
+
*/
|
|
69
|
+
export function isRecord(value) {
|
|
70
|
+
return isNonNullObject(value);
|
|
71
|
+
}
|
|
@@ -2,12 +2,26 @@ const INTERNAL_ERROR_STATUS = 500;
|
|
|
2
2
|
const ERROR_CONTENT_TYPE = "text/plain; charset=utf-8";
|
|
3
3
|
const VITE_ERROR = Symbol("dms.viteError");
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A request that carries the error Vite's middlewares failed with.
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("node:http").IncomingMessage & { [VITE_ERROR]?: unknown }} ViteRequest
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* What Vite's middlewares did with a request.
|
|
13
|
+
*
|
|
14
|
+
* @typedef {{ status: "handled" } | { status: "unhandled" } | { status: "failed", error: unknown }} ViteMiddlewareResult
|
|
15
|
+
*/
|
|
16
|
+
|
|
5
17
|
/**
|
|
6
18
|
* In middleware mode, Vite's own error middleware logs an error, sends it to
|
|
7
19
|
* the client overlay, then passes the request on with no error: a module that
|
|
8
20
|
* fails to transform would fall through to page handling and answer 404. This
|
|
9
21
|
* plugin runs just before it, keeps the error on the request and hands it on,
|
|
10
22
|
* so Vite still logs it and shows the overlay.
|
|
23
|
+
*
|
|
24
|
+
* @returns {import("vite").Plugin}
|
|
11
25
|
*/
|
|
12
26
|
export function captureViteErrors() {
|
|
13
27
|
return {
|
|
@@ -15,10 +29,18 @@ export function captureViteErrors() {
|
|
|
15
29
|
apply: "serve",
|
|
16
30
|
configureServer(server) {
|
|
17
31
|
return () =>
|
|
18
|
-
server.middlewares.use(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
32
|
+
server.middlewares.use(
|
|
33
|
+
/**
|
|
34
|
+
* @param {unknown} error
|
|
35
|
+
* @param {ViteRequest} request
|
|
36
|
+
* @param {import("node:http").ServerResponse} _response
|
|
37
|
+
* @param {(error?: unknown) => void} next
|
|
38
|
+
*/
|
|
39
|
+
(error, request, _response, next) => {
|
|
40
|
+
request[VITE_ERROR] = error;
|
|
41
|
+
next(error);
|
|
42
|
+
},
|
|
43
|
+
);
|
|
22
44
|
},
|
|
23
45
|
};
|
|
24
46
|
}
|
|
@@ -27,10 +49,15 @@ export function captureViteErrors() {
|
|
|
27
49
|
* Hands a request to Vite's middlewares. Resolves `handled` once Vite answered
|
|
28
50
|
* it, `unhandled` when Vite passed it on, and `failed` with the error when a
|
|
29
51
|
* Vite middleware failed on it.
|
|
52
|
+
*
|
|
53
|
+
* @param {import("vite").ViteDevServer} devServer
|
|
54
|
+
* @param {ViteRequest} request
|
|
55
|
+
* @param {import("node:http").ServerResponse} response
|
|
56
|
+
* @returns {Promise<ViteMiddlewareResult>}
|
|
30
57
|
*/
|
|
31
58
|
export function runViteMiddlewares(devServer, request, response) {
|
|
32
59
|
return new Promise((resolve) => {
|
|
33
|
-
devServer.middlewares(request, response, (error) => {
|
|
60
|
+
devServer.middlewares(request, response, (/** @type {unknown} */ error) => {
|
|
34
61
|
const failure = error ?? request[VITE_ERROR];
|
|
35
62
|
resolve(
|
|
36
63
|
failure
|
|
@@ -11,6 +11,9 @@ import { pipeline } from "node:stream/promises";
|
|
|
11
11
|
*
|
|
12
12
|
* Writes a Web-standard body, a backend answer above all, to a Node response.
|
|
13
13
|
* It lives apart from the proxy and the module routes, which both send one.
|
|
14
|
+
*
|
|
15
|
+
* @param {import("node:stream/web").ReadableStream} body
|
|
16
|
+
* @param {...import("node:stream").Writable} streams
|
|
14
17
|
*/
|
|
15
18
|
export async function pipeWebBody(body, ...streams) {
|
|
16
19
|
const response = streams.at(-1);
|
|
@@ -101,7 +101,16 @@ export interface DmsServerEvent {
|
|
|
101
101
|
* overwrites another's, and naming a `dms_*` cookie throws.
|
|
102
102
|
*/
|
|
103
103
|
cookies: {
|
|
104
|
-
|
|
104
|
+
/**
|
|
105
|
+
* The value a sealed cookie holds, when `accepts` takes it. A cookie that
|
|
106
|
+
* is absent, does not open, or holds a value `accepts` refuses reads as
|
|
107
|
+
* `undefined`. `accepts` receives the JSON the cookie was written from and
|
|
108
|
+
* is never called for an absent or unreadable cookie.
|
|
109
|
+
*/
|
|
110
|
+
readSealed<T>(
|
|
111
|
+
name: string,
|
|
112
|
+
accepts: (value: unknown) => value is T,
|
|
113
|
+
): T | undefined;
|
|
105
114
|
writeSealed(
|
|
106
115
|
name: string,
|
|
107
116
|
value: unknown,
|