@antelopejs/dms-frontend 0.5.5 → 0.6.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 +377 -19
- package/dist/app-configs.d.ts +9 -0
- package/dist/app-configs.js +52 -0
- package/dist/asset-roots.d.ts +7 -0
- package/dist/asset-roots.js +34 -0
- package/dist/commands/build-action.js +9 -0
- package/dist/commands/dev-action.js +88 -19
- package/dist/commands/start-action.js +2 -0
- package/dist/common.d.ts +1 -0
- package/dist/common.js +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +9 -3
- package/dist/derived-outputs.d.ts +2 -0
- package/dist/derived-outputs.js +38 -31
- package/dist/help.js +10 -3
- package/dist/layer-watch.js +123 -131
- package/dist/layers.js +2 -0
- package/dist/manifest-secrets.js +5 -5
- package/dist/manifest.js +7 -1
- package/dist/materialize.js +21 -1
- package/dist/module-set.d.ts +16 -0
- package/dist/module-set.js +80 -0
- package/dist/server-process.d.ts +1 -0
- package/dist/server-process.js +28 -2
- package/dist/verify-source-build.d.ts +11 -0
- package/dist/verify-source-build.js +113 -0
- package/dist/verify-source-runner.js +14 -57
- package/dist/workspace.d.ts +9 -0
- package/package.json +2 -1
- package/templates/vue/app-runtime.ts +58 -16
- package/templates/vue/client-server-guard.mjs +73 -0
- package/templates/vue/email-renderer.ts +17 -4
- package/templates/vue/email-runtime.ts +1 -1
- package/templates/vue/frontend-module.ts +1817 -259
- package/templates/vue/globals.d.ts +1 -1
- package/templates/vue/icon-hydration.ts +106 -1
- package/templates/vue/index.html +0 -1
- package/templates/vue/layer-app-config.ts +92 -0
- package/templates/vue/main.ts +10 -2
- package/templates/vue/package.json +5 -1
- package/templates/vue/prepare-types.mjs +34 -0
- package/templates/vue/server/auth/backend.mjs +13 -19
- package/templates/vue/server/auth/client-ip.mjs +27 -0
- package/templates/vue/server/auth/establish-endpoints.mjs +70 -0
- package/templates/vue/server/auth/session.mjs +6 -97
- package/templates/vue/server/auth-hooks.mjs +84 -0
- package/templates/vue/server/backend-proxies.mjs +110 -0
- package/templates/vue/server/dev-private-files.mjs +99 -0
- package/templates/vue/server/email.mjs +11 -33
- package/templates/vue/server/favicon.mjs +20 -0
- package/templates/vue/server/inertia.mjs +48 -3
- package/templates/vue/server/listen-host.mjs +19 -0
- package/templates/vue/server/module-event.mjs +265 -0
- package/templates/vue/server/module-routes.mjs +513 -0
- package/templates/vue/server/module-server.mjs +225 -0
- package/templates/vue/server/module-testing.mjs +65 -0
- package/templates/vue/server/page-props.mjs +151 -0
- package/templates/vue/server/path-patterns.mjs +38 -0
- package/templates/vue/server/proxy-headers.mjs +25 -0
- package/templates/vue/server/public-endpoints.mjs +69 -11
- package/templates/vue/server/request-routing.mjs +47 -0
- package/templates/vue/server/route-rules.mjs +129 -0
- package/templates/vue/server-module.ts +294 -0
- package/templates/vue/server.mjs +178 -169
- package/templates/vue/ssr-renderer.ts +60 -19
- package/templates/vue/tsconfig.json +2 -1
- package/templates/vue/ui-app-config.ts +18 -6
- package/templates/vue/ui-inertia-page.mjs +37 -0
- package/templates/vue/vite.config.ts +31 -10
- package/templates/vue/vite.email.config.ts +1 -1
- package/templates/vue/vite.server.config.ts +57 -0
- package/templates/vue/server/auth/oauth.mjs +0 -233
- package/templates/vue/server/auth/routes.mjs +0 -444
- package/templates/vue/server/render-token.mjs +0 -81
- package/templates/vue/server/tester.mjs +0 -227
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Route rules the modules declare in their `dms.server.ts`: how a page is
|
|
2
|
+
// rendered and which headers its document and Inertia answers carry. They
|
|
3
|
+
// change rendering and caching, never who may see a page.
|
|
4
|
+
|
|
5
|
+
import { compilePathPattern } from "./path-patterns.mjs";
|
|
6
|
+
|
|
7
|
+
const HEADER_NAME = /^[!#$%&'*+.^_`|~0-9a-z-]+$/;
|
|
8
|
+
/** Whether a header value holds a control character other than a tab: a CR
|
|
9
|
+
* or an LF would split the answer. */
|
|
10
|
+
function hasControlCharacter(value) {
|
|
11
|
+
return [...value].some((character) => {
|
|
12
|
+
const code = character.charCodeAt(0);
|
|
13
|
+
return (code < 0x20 && code !== 0x09) || code === 0x7f;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/** Set by the server itself: a rule's `cache` decides `cache-control`. */
|
|
17
|
+
const RESERVED_HEADERS = new Set([
|
|
18
|
+
"cache-control",
|
|
19
|
+
"set-cookie",
|
|
20
|
+
"vary",
|
|
21
|
+
"content-type",
|
|
22
|
+
"content-length",
|
|
23
|
+
"x-inertia",
|
|
24
|
+
"x-inertia-version",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
function isSeconds(value) {
|
|
28
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function headersRejection(headers) {
|
|
32
|
+
if (headers === undefined) return undefined;
|
|
33
|
+
if (typeof headers !== "object" || headers === null || Array.isArray(headers))
|
|
34
|
+
return "headers is not an object";
|
|
35
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
36
|
+
const lower = name.toLowerCase();
|
|
37
|
+
if (
|
|
38
|
+
!HEADER_NAME.test(lower) ||
|
|
39
|
+
typeof value !== "string" ||
|
|
40
|
+
hasControlCharacter(value)
|
|
41
|
+
)
|
|
42
|
+
return `"${name}" is not a header with a text value`;
|
|
43
|
+
if (RESERVED_HEADERS.has(lower))
|
|
44
|
+
return `the server sets "${name}" itself (use cache for cache-control)`;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function cacheRejection(cache) {
|
|
50
|
+
if (cache === undefined || cache === false) return undefined;
|
|
51
|
+
if (
|
|
52
|
+
typeof cache !== "object" ||
|
|
53
|
+
cache === null ||
|
|
54
|
+
cache.public !== true ||
|
|
55
|
+
!isSeconds(cache.maxAge) ||
|
|
56
|
+
(cache.staleWhileRevalidate !== undefined &&
|
|
57
|
+
!isSeconds(cache.staleWhileRevalidate))
|
|
58
|
+
)
|
|
59
|
+
return "cache is neither false nor { public: true, maxAge, staleWhileRevalidate? } in seconds";
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Why a rule is ignored, or `undefined`. */
|
|
64
|
+
export function ruleRejection(pattern, rule) {
|
|
65
|
+
if (!compilePathPattern(pattern))
|
|
66
|
+
return "the pattern is not /x, /x/* or /x/**";
|
|
67
|
+
if (typeof rule !== "object" || rule === null) return "it is not an object";
|
|
68
|
+
if (rule.ssr !== undefined && rule.ssr !== false)
|
|
69
|
+
return "ssr is either left out or false";
|
|
70
|
+
return headersRejection(rule.headers) ?? cacheRejection(rule.cache);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The rules that apply to `pathname`, merged from the least to the most
|
|
75
|
+
* specific pattern: a more specific rule overrides `ssr`, `cache` and each
|
|
76
|
+
* header it sets.
|
|
77
|
+
*
|
|
78
|
+
* @param rules The resolved rules, `{ pattern, rule }` each
|
|
79
|
+
* @param pathname The page's path
|
|
80
|
+
*/
|
|
81
|
+
export function routeRuleFor(rules, pathname) {
|
|
82
|
+
const merged = { headers: {} };
|
|
83
|
+
const matching = rules
|
|
84
|
+
.filter(({ pattern }) => pattern.test(pathname))
|
|
85
|
+
.sort(
|
|
86
|
+
(left, right) => left.pattern.specificity - right.pattern.specificity,
|
|
87
|
+
);
|
|
88
|
+
for (const { rule } of matching) {
|
|
89
|
+
if (rule.ssr !== undefined) merged.ssr = rule.ssr;
|
|
90
|
+
if (rule.cache !== undefined) merged.cache = rule.cache;
|
|
91
|
+
Object.assign(merged.headers, rule.headers);
|
|
92
|
+
}
|
|
93
|
+
return merged;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function publicCacheControl(cache) {
|
|
97
|
+
const stale =
|
|
98
|
+
cache.staleWhileRevalidate === undefined
|
|
99
|
+
? ""
|
|
100
|
+
: `, stale-while-revalidate=${cache.staleWhileRevalidate}`;
|
|
101
|
+
return `public, max-age=${cache.maxAge}${stale}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The headers a page answer adds for its rule. A public `cache` only applies
|
|
106
|
+
* to a page the backend serves to anyone, asked without any cookie or
|
|
107
|
+
* content language: the answer then holds nobody's data, and `Vary: Cookie`
|
|
108
|
+
* keeps a shared cache from serving it to a visitor who has some.
|
|
109
|
+
*
|
|
110
|
+
* @param rule What `routeRuleFor` returned
|
|
111
|
+
* @param request The page request
|
|
112
|
+
* @param route The page's route, as the backend described it
|
|
113
|
+
* @param status The answer's status: only a 200 is cached
|
|
114
|
+
*/
|
|
115
|
+
export function ruleHeaders(rule, request, route, status) {
|
|
116
|
+
const headers = { ...rule.headers };
|
|
117
|
+
if (
|
|
118
|
+
rule.cache &&
|
|
119
|
+
status === 200 &&
|
|
120
|
+
request.method === "GET" &&
|
|
121
|
+
!request.headers.cookie &&
|
|
122
|
+
!request.headers["x-content-language"] &&
|
|
123
|
+
route?.publicAccess === true
|
|
124
|
+
) {
|
|
125
|
+
headers["cache-control"] = publicCacheControl(rule.cache);
|
|
126
|
+
headers.vary = "X-Inertia, Cookie";
|
|
127
|
+
}
|
|
128
|
+
return headers;
|
|
129
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* What a frontend module's `dms.server.ts` declares: the routes it serves from
|
|
4
|
+
* the frontend server, its auth hooks, its route rules and the backend paths
|
|
5
|
+
* the server proxies. Imported as `#dms/server`.
|
|
6
|
+
*
|
|
7
|
+
* The entry runs in the frontend server's Node.js process, next to the
|
|
8
|
+
* session secret: it is the module's own trusted code, as its backend half
|
|
9
|
+
* is, not a sandbox. It may import `#dms/server`, Node.js built-ins, npm
|
|
10
|
+
* dependencies and the module's own server files, never the application's
|
|
11
|
+
* code (`.vue` files, `dms.frontend.ts`). Nothing it imports reaches the
|
|
12
|
+
* browser bundle.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The methods a route can answer. */
|
|
16
|
+
export type DmsServerMethod =
|
|
17
|
+
| "GET"
|
|
18
|
+
| "HEAD"
|
|
19
|
+
| "POST"
|
|
20
|
+
| "PUT"
|
|
21
|
+
| "PATCH"
|
|
22
|
+
| "DELETE"
|
|
23
|
+
| "OPTIONS";
|
|
24
|
+
|
|
25
|
+
/** The session the frontend server keeps in its sealed session cookie. */
|
|
26
|
+
export interface DmsSession {
|
|
27
|
+
user?: Record<string, unknown> & { language?: string };
|
|
28
|
+
accessToken?: string;
|
|
29
|
+
refreshToken?: string;
|
|
30
|
+
accountId?: string;
|
|
31
|
+
activeTenantId?: string;
|
|
32
|
+
/** False when the session ends with the browser. */
|
|
33
|
+
persistent?: boolean;
|
|
34
|
+
/** Stamped by the frontend server on every write. */
|
|
35
|
+
updatedAt?: number;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DmsSealedCookieOptions {
|
|
40
|
+
/** Seconds. Without one, the cookie ends with the browser session. */
|
|
41
|
+
maxAge?: number;
|
|
42
|
+
/** `/` by default. A flow cookie keeps to the paths of its flow. */
|
|
43
|
+
path?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DmsBackendRequest {
|
|
47
|
+
method?: string;
|
|
48
|
+
/** Sent as JSON. */
|
|
49
|
+
body?: unknown;
|
|
50
|
+
/** Sent as `Authorization: Bearer <token>`. */
|
|
51
|
+
token?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Extra request headers. The client's address (`x-forwarded-for`) and the
|
|
54
|
+
* headers the frontend server relays to every backend request (the
|
|
55
|
+
* client's country, the user agent, the content language) are its own.
|
|
56
|
+
*/
|
|
57
|
+
headers?: Record<string, string>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** What a session shows the browser: the `user` and `session` page props. */
|
|
61
|
+
export interface DmsPublicSession {
|
|
62
|
+
user?: unknown;
|
|
63
|
+
session?: Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** What a route handler receives. */
|
|
67
|
+
export interface DmsServerEvent {
|
|
68
|
+
/**
|
|
69
|
+
* The request, Web-standard. Its body stream stops at the route's body
|
|
70
|
+
* limit (64 KiB by default) with a 413.
|
|
71
|
+
*/
|
|
72
|
+
request: Request;
|
|
73
|
+
url: URL;
|
|
74
|
+
/** The values of the route's `:name` segments. */
|
|
75
|
+
params: Readonly<Record<string, string>>;
|
|
76
|
+
/** The body as JSON, `{}` when empty; a 400 when it is not JSON. */
|
|
77
|
+
readJson<T = Record<string, unknown>>(): Promise<T>;
|
|
78
|
+
session: {
|
|
79
|
+
read(): DmsSession | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* What the session shows a page (`user`, `session`), as the module that
|
|
82
|
+
* owns sign-in renders it: the page props, and what
|
|
83
|
+
* `useDmsUserSession().fetch()` reads.
|
|
84
|
+
*/
|
|
85
|
+
view(): DmsPublicSession;
|
|
86
|
+
/**
|
|
87
|
+
* Writes the session cookie, stamped with `updatedAt`; it ends with the
|
|
88
|
+
* browser unless `persistent` is true or unset.
|
|
89
|
+
*/
|
|
90
|
+
write(session: DmsSession): void;
|
|
91
|
+
clear(): void;
|
|
92
|
+
/** The session the rest of this request runs with (page props, retries). */
|
|
93
|
+
useForRequest(session: DmsSession | undefined): void;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Sealed (encrypted, authenticated) cookies, by logical name: the frontend
|
|
97
|
+
* server scopes the name to its backend and sets `HttpOnly`, `SameSite=Lax`
|
|
98
|
+
* and `Secure`. The session cookie is not reachable here. The `dms_*`
|
|
99
|
+
* names are the module that owns sign-in's (`registerAuth`): any other
|
|
100
|
+
* module's cookie is stored as `<module id>.<name>`, so no module reads or
|
|
101
|
+
* overwrites another's, and naming a `dms_*` cookie throws.
|
|
102
|
+
*/
|
|
103
|
+
cookies: {
|
|
104
|
+
readSealed<T = unknown>(name: string): T | undefined;
|
|
105
|
+
writeSealed(
|
|
106
|
+
name: string,
|
|
107
|
+
value: unknown,
|
|
108
|
+
options?: DmsSealedCookieOptions,
|
|
109
|
+
): void;
|
|
110
|
+
clear(name: string, options?: { path?: string }): void;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Calls the backend, JSON in and out. A refusal throws an error carrying
|
|
114
|
+
* the status and the backend's public `error.*` message, which the server
|
|
115
|
+
* answers as is when the handler lets it through.
|
|
116
|
+
*/
|
|
117
|
+
backend<T = unknown>(path: string, init?: DmsBackendRequest): Promise<T>;
|
|
118
|
+
/**
|
|
119
|
+
* The backend URL of an absolute path, for a call `backend` cannot make (a
|
|
120
|
+
* raw response, no relayed header): always on the backend's origin,
|
|
121
|
+
* whatever the path holds.
|
|
122
|
+
*/
|
|
123
|
+
backendUrl(path: string): URL;
|
|
124
|
+
/** The client's address, behind `DMS_TRUSTED_PROXY_HOPS` proxies. */
|
|
125
|
+
clientIp(): string;
|
|
126
|
+
/** This module, and only this one. */
|
|
127
|
+
module: {
|
|
128
|
+
id: string;
|
|
129
|
+
options: Readonly<Record<string, unknown>>;
|
|
130
|
+
privateOptions: Readonly<Record<string, unknown>>;
|
|
131
|
+
};
|
|
132
|
+
/** Renders an email template of the application (`dms.email.ts`). */
|
|
133
|
+
renderEmail(
|
|
134
|
+
template: string,
|
|
135
|
+
props: Record<string, unknown>,
|
|
136
|
+
options?: { locale?: string },
|
|
137
|
+
): Promise<string>;
|
|
138
|
+
/** Deployment data from the manifest, read-only. */
|
|
139
|
+
app: {
|
|
140
|
+
/** Backend endpoints declared as session-opening (`authEstablishEndpoints`). */
|
|
141
|
+
authEstablishEndpoints: readonly string[];
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A route handler. A `Response` is sent as it is; any other value is sent as
|
|
147
|
+
* JSON with a 200, `null` or nothing as an empty 204. Cookies written through
|
|
148
|
+
* the event are added to the response either way.
|
|
149
|
+
*/
|
|
150
|
+
export type DmsServerHandler = (
|
|
151
|
+
event: DmsServerEvent,
|
|
152
|
+
) => Response | unknown | Promise<Response | unknown>;
|
|
153
|
+
|
|
154
|
+
export interface DmsServerRouteOptions {
|
|
155
|
+
/**
|
|
156
|
+
* Whether a request with a state-changing method must carry the frontend's
|
|
157
|
+
* own `Origin`. `true` by default; only a route authenticated otherwise (a
|
|
158
|
+
* service token) turns it off.
|
|
159
|
+
*/
|
|
160
|
+
sameOrigin?: boolean;
|
|
161
|
+
/** Request body limit in bytes, 64 KiB by default. */
|
|
162
|
+
bodyLimit?: number;
|
|
163
|
+
/**
|
|
164
|
+
* The path is under `/api/` or `/dms/`, the backend's namespace, outside an
|
|
165
|
+
* underscore prefix (`/api/_<module>/`): the route shadows whatever the
|
|
166
|
+
* backend serves there. Say so to silence the warning.
|
|
167
|
+
*/
|
|
168
|
+
overridesBackend?: boolean;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The session logic the frontend server runs outside any route, which the
|
|
173
|
+
* module owning sign-in provides. Without `publicSession`, pages carry no
|
|
174
|
+
* `user` and `session`; without `refreshSession`, a session the backend
|
|
175
|
+
* refuses is not renewed; `signInPath` is `/auth` by default.
|
|
176
|
+
*/
|
|
177
|
+
export interface DmsServerAuth {
|
|
178
|
+
/** Where a signed-out visit to a private page goes, with `?redirect=`. */
|
|
179
|
+
signInPath?: string;
|
|
180
|
+
/**
|
|
181
|
+
* Pages a signed-out visit is never sent to sign in from when the backend
|
|
182
|
+
* refuses it: `/x`, `/x/*` (one segment below) or `/x/**` (the page and
|
|
183
|
+
* everything below). Left out, `<signInPath>/**` and `/onboarding`.
|
|
184
|
+
*/
|
|
185
|
+
signInExempt?: readonly string[];
|
|
186
|
+
/**
|
|
187
|
+
* Renews a session the backend refused (a 401 to a signed-in page visit,
|
|
188
|
+
* which is then retried once): writes it with `event.session.write` and
|
|
189
|
+
* returns it, or clears it and returns `undefined`.
|
|
190
|
+
*/
|
|
191
|
+
refreshSession?(
|
|
192
|
+
event: DmsServerEvent,
|
|
193
|
+
session: DmsSession,
|
|
194
|
+
): Promise<DmsSession | undefined>;
|
|
195
|
+
/**
|
|
196
|
+
* The page props `user` and `session` of a signed-in session. The server
|
|
197
|
+
* adds `session.updatedAt` itself.
|
|
198
|
+
*/
|
|
199
|
+
publicSession?(session: DmsSession): DmsPublicSession;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* How a page is rendered, and what its document and Inertia answers carry.
|
|
204
|
+
* A rule changes rendering and caching, never who may see the page.
|
|
205
|
+
*/
|
|
206
|
+
export interface DmsRouteRule {
|
|
207
|
+
/**
|
|
208
|
+
* `false`: a document request gets the client-rendered shell instead of a
|
|
209
|
+
* server render. Inertia visits are unchanged.
|
|
210
|
+
*/
|
|
211
|
+
ssr?: false;
|
|
212
|
+
/** Headers added to the page's answers (not `cache-control`: see `cache`). */
|
|
213
|
+
headers?: Record<string, string>;
|
|
214
|
+
/**
|
|
215
|
+
* Public caching, for a page the backend serves to anyone, asked without
|
|
216
|
+
* any cookie or content language; any other request keeps the private
|
|
217
|
+
* default. `false` keeps the default under a broader rule.
|
|
218
|
+
*/
|
|
219
|
+
cache?:
|
|
220
|
+
| { public: true; maxAge: number; staleWhileRevalidate?: number }
|
|
221
|
+
| false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface DmsServer {
|
|
225
|
+
/**
|
|
226
|
+
* Serves `path` for `method` from the frontend server, before the backend
|
|
227
|
+
* proxy. A path is literal segments and `:name` parameters
|
|
228
|
+
* (`/auth/oauth/:provider/start`); a trailing slash is ignored.
|
|
229
|
+
*/
|
|
230
|
+
registerRoute(
|
|
231
|
+
method: DmsServerMethod | readonly DmsServerMethod[],
|
|
232
|
+
path: string,
|
|
233
|
+
handler: DmsServerHandler,
|
|
234
|
+
options?: DmsServerRouteOptions,
|
|
235
|
+
): void;
|
|
236
|
+
/**
|
|
237
|
+
* Provides the session logic the server runs outside routes. The
|
|
238
|
+
* highest-priority module that registers it provides it; another one is
|
|
239
|
+
* reported and ignored.
|
|
240
|
+
*/
|
|
241
|
+
registerAuth(auth: DmsServerAuth): void;
|
|
242
|
+
/**
|
|
243
|
+
* Route rules by page pattern: `/x`, `/x/*` (one segment below) or `/x/**`
|
|
244
|
+
* (the page and everything below). The rules matching a page merge from the
|
|
245
|
+
* least to the most specific pattern; for one pattern, the
|
|
246
|
+
* highest-priority module's rule wins.
|
|
247
|
+
*/
|
|
248
|
+
registerRouteRules(rules: Readonly<Record<string, DmsRouteRule>>): void;
|
|
249
|
+
/**
|
|
250
|
+
* Proxies the paths `pattern` covers to the backend (`/x`, `/x/*` one
|
|
251
|
+
* segment below, `/x/**` the path and everything below; the first segment
|
|
252
|
+
* literal). The frontend server sends nothing else to the backend: a path no
|
|
253
|
+
* proxy covers is a page, or a `404` for a method other than `GET` and
|
|
254
|
+
* `HEAD`. Of two patterns covering a path, the more specific wins; for one
|
|
255
|
+
* pattern, the highest-priority module's.
|
|
256
|
+
*
|
|
257
|
+
* ```ts
|
|
258
|
+
* server.registerBackendProxy("/api/**");
|
|
259
|
+
* server.registerBackendProxy("/media/**", { auth: "none" });
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
registerBackendProxy(pattern: string, options?: DmsBackendProxyOptions): void;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** How the frontend server authenticates a proxied request to the backend. */
|
|
266
|
+
export interface DmsBackendProxyOptions {
|
|
267
|
+
/**
|
|
268
|
+
* `session` (the default): the server adds the session's access token
|
|
269
|
+
* (`Authorization: Bearer`), which never reaches the browser, and a request
|
|
270
|
+
* with a state-changing method needs the frontend's own `Origin`, except a
|
|
271
|
+
* public endpoint a backend module declares (`publicEndpoints`), sent
|
|
272
|
+
* without the session. `none`: never sends the session, so no `Origin` is
|
|
273
|
+
* needed.
|
|
274
|
+
*/
|
|
275
|
+
auth?: "session" | "none";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export type DmsServerSetup = (server: DmsServer) => void;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Declares what a frontend module serves from the frontend server, as the
|
|
282
|
+
* default export of its `dms.server.ts`:
|
|
283
|
+
*
|
|
284
|
+
* ```ts
|
|
285
|
+
* import { defineDmsServer } from "#dms/server";
|
|
286
|
+
*
|
|
287
|
+
* export default defineDmsServer((server) => {
|
|
288
|
+
* server.registerRoute("GET", "/api/_acme/ping", () => ({ ok: true }));
|
|
289
|
+
* });
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
export function defineDmsServer(setup: DmsServerSetup): DmsServerSetup {
|
|
293
|
+
return setup;
|
|
294
|
+
}
|