@cosmicdrift/kumiko-server-runtime 0.386.0 → 0.387.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/LICENSE +21 -57
- package/dist/boot/boot-crypto.js +1 -2
- package/dist/boot/worker-boot-core.js +2 -2
- package/dist/build-prod-bundle.d.ts +16 -15
- package/dist/build-prod-bundle.js +106 -108
- package/dist/extra-routes-deps.d.ts +1 -1
- package/dist/index.js +4 -4
- package/dist/pii-boot-gate.js +1 -1
- package/dist/resolve-tailwind-cli.js +5 -7
- package/dist/run-prod-app-boot-context.d.ts +2 -2
- package/dist/run-prod-app-boot-context.js +21 -22
- package/dist/run-prod-app-static-files.js +43 -50
- package/dist/run-prod-app.d.ts +73 -85
- package/dist/run-prod-app.js +97 -114
- package/dist/run-worker-app.js +5 -5
- package/dist/session-boot-gate.d.ts +1 -1
- package/dist/session-boot-gate.js +1 -1
- package/dist/session-wiring.d.ts +1 -1
- package/dist/session-wiring.js +1 -1
- package/dist/try-hono-first.js +1 -1
- package/package.json +8 -8
|
@@ -9,28 +9,27 @@ import { CSP_HASHES_FILE, parseCspHashes } from "./csp-hashes.js";
|
|
|
9
9
|
import { buildRequestBoundSystemQuery, } from "./request-bound-system-query.js";
|
|
10
10
|
import { stripNoRouteMatchHeader, tryHonoFirst } from "./try-hono-first.js";
|
|
11
11
|
// Static-asset + SPA-fallback serving for runProdApp's HTTP handler. Split
|
|
12
|
-
// out of run-prod-app.ts
|
|
12
|
+
// out of run-prod-app.ts — mechanical relocation, these
|
|
13
13
|
// functions are self-contained (no closure over runProdApp's local boot
|
|
14
14
|
// state), only params.
|
|
15
|
-
// Static
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// Static fallback: try the Hono app first and serve a file from staticDir only
|
|
16
|
+
// when Hono returns 404, so /api/* stays on the dispatcher while HTML,
|
|
17
|
+
// JavaScript, CSS, and images come from disk.
|
|
18
18
|
//
|
|
19
|
-
// Cache-
|
|
19
|
+
// Cache-header strategy:
|
|
20
20
|
// /assets/* → public, max-age=31536000, immutable
|
|
21
|
-
// (
|
|
21
|
+
// (build-generated hashed filenames are safe to cache)
|
|
22
22
|
// /index.html → no-cache, must-revalidate
|
|
23
|
-
// (HTML
|
|
23
|
+
// (the HTML shell must reload after deployment)
|
|
24
24
|
// /manifest.json, /sw.js → no-cache
|
|
25
|
-
// (
|
|
26
|
-
//
|
|
27
|
-
// (
|
|
28
|
-
//
|
|
29
|
-
// Bun.file
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// ~1 MB.
|
|
25
|
+
// (update-detection mechanisms must stay fresh)
|
|
26
|
+
// everything else → no explicit header
|
|
27
|
+
// (browser default, including public/ files such as favicon)
|
|
28
|
+
//
|
|
29
|
+
// Use node:fs/promises instead of Bun.file so this path also runs in vitest+node
|
|
30
|
+
// integration tests; Bun.file is Bun-only. Full-buffer reads are acceptable because
|
|
31
|
+
// production static files are typically 1–200 KB; streaming would matter only above
|
|
32
|
+
// roughly 1 MB.
|
|
34
33
|
export async function readStaticFile(filePath) {
|
|
35
34
|
try {
|
|
36
35
|
const { readFile, stat } = await import("node:fs/promises");
|
|
@@ -99,10 +98,9 @@ export function serveDiskFile(req, pathname, file) {
|
|
|
99
98
|
lastModified: new Date(file.mtimeMs),
|
|
100
99
|
});
|
|
101
100
|
}
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
// fragt bei unbekanntem MIME nach).
|
|
101
|
+
// Covers files produced by kumiko-build and typical public/ contents. Bun.file
|
|
102
|
+
// infers the type from the suffix, but the node path must do so explicitly;
|
|
103
|
+
// unknown types use octet-stream so the browser can handle them safely.
|
|
106
104
|
export function mimeTypeFor(filePath) {
|
|
107
105
|
const ext = filePath.toLowerCase().split(".").pop() ?? "";
|
|
108
106
|
switch (ext) {
|
|
@@ -190,9 +188,9 @@ hostDispatchScriptHashes = []) {
|
|
|
190
188
|
// inject into) → the original html object is returned untouched.
|
|
191
189
|
//
|
|
192
190
|
// The resolve+timeout+inject itself runs through headless's
|
|
193
|
-
// resolveAndInjectPageHead
|
|
194
|
-
// (
|
|
195
|
-
//
|
|
191
|
+
// resolveAndInjectPageHead, the same call site runDevApp uses, so only the
|
|
192
|
+
// systemQuery/dispatcher wiring below (Request-bound, prod-specific) and the
|
|
193
|
+
// etag recompute are local.
|
|
196
194
|
async function applyPageHead(req, html, socketAddress) {
|
|
197
195
|
if (!pageHead)
|
|
198
196
|
return html;
|
|
@@ -224,10 +222,9 @@ hostDispatchScriptHashes = []) {
|
|
|
224
222
|
etag: computeStrongEtag(encoded),
|
|
225
223
|
};
|
|
226
224
|
}
|
|
227
|
-
// hostDispatch
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
// Pfad weiterlaufen soll.
|
|
225
|
+
// Consult hostDispatch when the request reaches the HTML fallback (the root or
|
|
226
|
+
// an SPA route). It can return a resolved response (redirect, 404, or HTML),
|
|
227
|
+
// or null to continue with the default path.
|
|
231
228
|
async function tryHostDispatch(req, socketAddress) {
|
|
232
229
|
if (!hostDispatch)
|
|
233
230
|
return null;
|
|
@@ -261,8 +258,8 @@ hostDispatchScriptHashes = []) {
|
|
|
261
258
|
async function serveHostDispatchFile(req, file, csp, socketAddress, status) {
|
|
262
259
|
const html = await readHtmlFile(`${staticDir}/${file}`);
|
|
263
260
|
if (!html) {
|
|
264
|
-
//
|
|
265
|
-
//
|
|
261
|
+
// Configuration error: hostDispatch references a nonexistent file. Return 500
|
|
262
|
+
// instead of silently returning 404 so the problem is visible immediately.
|
|
266
263
|
return new Response(`hostDispatch: file not found: ${file}`, { status: 500 });
|
|
267
264
|
}
|
|
268
265
|
// Per-host body (hostDispatch picks the file by host) → Vary: Host,
|
|
@@ -282,26 +279,24 @@ hostDispatchScriptHashes = []) {
|
|
|
282
279
|
if (url.pathname.startsWith("/api/") || url.pathname === "/health") {
|
|
283
280
|
return stripNoRouteMatchHeader(await apiHandler(req, socketAddress));
|
|
284
281
|
}
|
|
285
|
-
// Hono
|
|
286
|
-
// /sitemap.xml)
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
// createKumikoServer.handleFetch damit beide IDENTISCHE Semantik haben.
|
|
282
|
+
// Try Hono before disk lookup so extraRoutes (for example /feed.xml and
|
|
283
|
+
// /sitemap.xml) and r.httpRoute features (for example /legal/*) are handled
|
|
284
|
+
// before unknown paths fall through to index.html. Keep this consistent with
|
|
285
|
+
// dev-server's createKumikoServer.handleFetch so both paths have identical semantics.
|
|
290
286
|
const honoTry = await tryHonoFirst({ fetch: apiHandler }, req, socketAddress);
|
|
291
287
|
if (honoTry.matched) {
|
|
292
288
|
return honoTry.response;
|
|
293
289
|
}
|
|
294
290
|
const honoRes = honoTry.response;
|
|
295
|
-
//
|
|
296
|
-
// (
|
|
297
|
-
// Hono
|
|
298
|
-
//
|
|
291
|
+
// The disk/SPA fallback accepts only GET and HEAD. A non-GET without a Hono
|
|
292
|
+
// match (for example, POST to a misconfigured webhook path) must preserve the
|
|
293
|
+
// Hono 404: returning index.html with 200 could make the provider report the
|
|
294
|
+
// event as delivered even though it was dropped.
|
|
299
295
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
300
296
|
return honoRes;
|
|
301
297
|
}
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
// aktiv geladen, kein Server-side Routing nötig.
|
|
298
|
+
// Asset paths are host-independent because the client actively loads the
|
|
299
|
+
// bundles in /assets/*; they do not require server-side routing.
|
|
305
300
|
const isIndexRequest = url.pathname === "/" || url.pathname === "/index.html";
|
|
306
301
|
if (!isIndexRequest) {
|
|
307
302
|
const relPath = url.pathname.slice(1);
|
|
@@ -324,18 +319,17 @@ hostDispatchScriptHashes = []) {
|
|
|
324
319
|
const withHead = await applyPageHead(req, index, socketAddress);
|
|
325
320
|
return serveHtmlFile(req, "/index.html", withHead, pageHead && { vary: PAGE_HEAD_VARY });
|
|
326
321
|
}
|
|
327
|
-
//
|
|
328
|
-
//
|
|
322
|
+
// If Hono found no route and neither a disk file nor index.html exists, preserve
|
|
323
|
+
// its original 404 instead of issuing another request.
|
|
329
324
|
return honoRes;
|
|
330
325
|
};
|
|
331
326
|
}
|
|
332
327
|
// The resolved page head depends on host and preferred locale; ?lang= is part of the URL and
|
|
333
328
|
// already keys the cache.
|
|
334
329
|
const PAGE_HEAD_VARY = "Host, Accept-Language";
|
|
335
|
-
// Map URL
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
// runProdApp.
|
|
330
|
+
// Map URL paths to cache policies. Hashed asset paths (/assets/*) are immutable;
|
|
331
|
+
// other paths use revalidation or no-cache so updates take effect without a hard
|
|
332
|
+
// reload. Exported for unit tests; consumers access it through runProdApp.
|
|
339
333
|
export function staticCachePolicy(pathname) {
|
|
340
334
|
if (pathname.startsWith(`/${ASSETS_DIR}/`)) {
|
|
341
335
|
return { kind: "immutable" };
|
|
@@ -345,9 +339,8 @@ export function staticCachePolicy(pathname) {
|
|
|
345
339
|
}
|
|
346
340
|
if (pathname === "/manifest.json" ||
|
|
347
341
|
pathname === "/sw.js" ||
|
|
348
|
-
// ponytail: build-info.json
|
|
349
|
-
//
|
|
350
|
-
// UpdateChecker eine veraltete id.
|
|
342
|
+
// ponytail: build-info.json is static, so no /api/version endpoint is needed; the disk
|
|
343
|
+
// fallback serves it. Use no-cache so UpdateChecker does not poll a stale id.
|
|
351
344
|
pathname === "/build-info.json") {
|
|
352
345
|
return { kind: "no-cache" };
|
|
353
346
|
}
|
package/dist/run-prod-app.d.ts
CHANGED
|
@@ -48,17 +48,17 @@ export type PasswordResetSetup = Omit<PasswordResetOptions, "hmacSecret"> & {
|
|
|
48
48
|
export type EmailVerificationSetup = Omit<EmailVerificationOptions, "hmacSecret"> & {
|
|
49
49
|
readonly hmacSecret?: string;
|
|
50
50
|
};
|
|
51
|
-
/** Wrapper
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
51
|
+
/** Wrapper API for Magic-Link self-signup. = SignupOptions (appUrl; mail
|
|
52
|
+
* is sent through delivery/ctx.notify like reset/verify). Unlike reset/verify,
|
|
53
|
+
* there is no hmacSecret: signup tokens are opaque random values in Redis,
|
|
54
|
+
* not HMAC-signed. */
|
|
55
55
|
export type SignupSetup = SignupOptions;
|
|
56
|
-
/** Wrapper
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* AuthHandlers
|
|
56
|
+
/** Wrapper API for tenant-invite Magic-Link. = InviteOptions (appUrl; the
|
|
57
|
+
* invite mail is sent through delivery like reset/verify/signup). The
|
|
58
|
+
* framework has three accept branches; handler names are fixed in
|
|
59
|
+
* run-prod-app through AuthHandlers, as with signup. */
|
|
60
60
|
export type InviteSetup = InviteOptions;
|
|
61
|
-
/** Wrapper API for the account-unlock flow
|
|
61
|
+
/** Wrapper API for the account-unlock flow. = AccountUnlockOptions
|
|
62
62
|
* (appUrl via delivery, symmetric to PasswordResetSetup). Self-service
|
|
63
63
|
* escape hatch for accountLockout's failure counter; the lockout is always
|
|
64
64
|
* on, so `auth.mail` mounts this flow by default. */
|
|
@@ -68,23 +68,25 @@ export type AccountUnlockSetup = Omit<AccountUnlockOptions, "hmacSecret"> & {
|
|
|
68
68
|
/** Wrapper API for the account-lockout brute-force guard on the login
|
|
69
69
|
* handler. = AccountLockoutOptions (maxFailedAttempts, lockoutDurationMinutes
|
|
70
70
|
* — no mail/appUrl fields, it doesn't mount any routes). Symmetric to
|
|
71
|
-
* RunDevAppAuthOptions.accountLockout
|
|
71
|
+
* RunDevAppAuthOptions.accountLockout — before this,
|
|
72
72
|
* `accountUnlock` existed on both wrappers with no way to ever set the
|
|
73
73
|
* lockout it's meant to escape. */
|
|
74
74
|
export type AccountLockoutSetup = AccountLockoutOptions;
|
|
75
|
-
/** Auth-
|
|
76
|
-
*
|
|
77
|
-
*
|
|
75
|
+
/** Auth-mail convenience options shared by runProdApp and runDevApp.
|
|
76
|
+
* Wires all five mail flows from an env-based SMTP transport and default
|
|
77
|
+
* templates (see `auth.mail` and resolveAuthMail). */
|
|
78
78
|
export type AuthMailOptions = {
|
|
79
79
|
/** App-Basis-URL inkl. Schema (z.B. "https://app.example.com"). */
|
|
80
80
|
readonly baseUrl: string;
|
|
81
|
-
/** App
|
|
81
|
+
/** App name used in the mail subject and body. Default: "Account". */
|
|
82
82
|
readonly appName?: string;
|
|
83
|
-
/** Locale
|
|
83
|
+
/** Locale used by the mail templates. Default: "de". */
|
|
84
84
|
readonly locale?: AuthMailLocale;
|
|
85
|
-
/** "strict"
|
|
85
|
+
/** "strict" blocks login until emailVerified; "off" mounts the flow without
|
|
86
|
+
* that gate. */
|
|
86
87
|
readonly emailVerificationMode?: "strict" | "off";
|
|
87
|
-
/** Fallback
|
|
88
|
+
/** Fallback From address when the `SMTP_FROM` env var is absent; the env var
|
|
89
|
+
* takes precedence. */
|
|
88
90
|
readonly from?: string;
|
|
89
91
|
/** Overrides single auth paths (default DEFAULT_AUTH_PATHS). */
|
|
90
92
|
readonly paths?: Partial<AuthPaths>;
|
|
@@ -119,16 +121,15 @@ export type RunProdAppAuthOptions = {
|
|
|
119
121
|
readonly passwordReset?: PasswordResetSetup;
|
|
120
122
|
/** Email-verification flow. Symmetric to passwordReset. */
|
|
121
123
|
readonly emailVerification?: EmailVerificationSetup;
|
|
122
|
-
/** Self-
|
|
123
|
-
* /api/auth/signup-confirm are mounted; signup-confirm
|
|
124
|
-
*
|
|
125
|
-
* Activation). */
|
|
124
|
+
/** Self-signup flow (Magic-Link). When set, /api/auth/signup-request and
|
|
125
|
+
* /api/auth/signup-confirm are mounted; signup-confirm mints a JWT and
|
|
126
|
+
* cookies like a successful login (automatic login after activation). */
|
|
126
127
|
readonly signup?: SignupSetup;
|
|
127
128
|
/** Tenant-Invite flow (Magic-Link). When set, /api/auth/invite-accept,
|
|
128
129
|
* /api/auth/invite-accept-with-login, /api/auth/invite-signup-complete
|
|
129
130
|
* are mounted. */
|
|
130
131
|
readonly invite?: InviteSetup;
|
|
131
|
-
/** Account-unlock flow
|
|
132
|
+
/** Account-unlock flow. When set, /api/auth/request-account-unlock
|
|
132
133
|
* + /api/auth/confirm-account-unlock are mounted. Self-service escape
|
|
133
134
|
* hatch for accountLockout's monotonic failure-counter — confirming
|
|
134
135
|
* clears the Redis lockout state, no entity write. */
|
|
@@ -163,13 +164,12 @@ export type RunProdAppAuthOptions = {
|
|
|
163
164
|
* `mail.baseUrl`, then of the APP_BASE_URL env. */
|
|
164
165
|
readonly publicOrigin?: string;
|
|
165
166
|
/** Number of trusted reverse-proxy hops between the client and this
|
|
166
|
-
* process for client-IP derivation (see AuthRoutesConfig.trustedProxyHops
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* this and the env var. */
|
|
167
|
+
* process for client-IP derivation (see AuthRoutesConfig.trustedProxyHops)
|
|
168
|
+
* — closes the X-Forwarded-For spoofing hole on the auth rate-limiters.
|
|
169
|
+
* Falls back to the `KUMIKO_TRUSTED_PROXY_HOPS` env var when unset; both
|
|
170
|
+
* unset means 0 hops, the safe socket-only default (only the socket address
|
|
171
|
+
* counts, XFF ignored). Deprecated in favor of the top-level
|
|
172
|
+
* `trustedProxyHops` option, which wins over this and the env var. */
|
|
173
173
|
readonly trustedProxyHops?: number;
|
|
174
174
|
/** Server-computed post-auth redirect (see AuthRoutesConfig.postAuthLanding).
|
|
175
175
|
* Result lands in the login/signup/invite response as `landingPath`; an
|
|
@@ -192,12 +192,10 @@ export type ProdSeedFn = (deps: {
|
|
|
192
192
|
masterKeyProvider?: import("@cosmicdrift/kumiko-framework/secrets").MasterKeyProvider;
|
|
193
193
|
configEncryption?: import("@cosmicdrift/kumiko-framework/secrets").EnvelopeCipher;
|
|
194
194
|
}) => Promise<void>;
|
|
195
|
-
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* setupTestStack's extraContext-Factory-Shape damit Test/Prod gleich
|
|
200
|
-
* aussehen. */
|
|
195
|
+
/** Dependencies passed to the `extraContext` and `anonymousAccess` factories.
|
|
196
|
+
* The returned config can close over them, for example a TenantResolver that
|
|
197
|
+
* queries `db` or an extra-context provider that publishes SSE events. The
|
|
198
|
+
* shape matches setupTestStack so test and production wiring remain aligned. */
|
|
201
199
|
export type RunProdAppDeps = {
|
|
202
200
|
readonly db: import("@cosmicdrift/kumiko-framework/db").DbConnection;
|
|
203
201
|
readonly redis: Redis;
|
|
@@ -282,20 +280,16 @@ export type RunProdAppOptions = {
|
|
|
282
280
|
* @example
|
|
283
281
|
* seeds: { demoTenant: { key: "demo", name: "Demo" }, sysadminFromEnv: true, run: [seedDemoData] } */
|
|
284
282
|
readonly seeds?: readonly ProdSeedFn[] | ProdSeedsConfig;
|
|
285
|
-
/**
|
|
286
|
-
* (file-
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
* Plan: kumiko-platform/docs/plans/features/es-ops.md */
|
|
283
|
+
/** Path to the seeds directory for ES operations and seed migrations
|
|
284
|
+
* (file-based like drizzle-migrate). When set and KUMIKO_SKIP_ES_OPS != "1",
|
|
285
|
+
* runProdApp scans it for `<id>.ts` files, diffs them against the
|
|
286
|
+
* kumiko_es_operations table, and runs pending operations in a transaction. */
|
|
290
287
|
readonly seedsDir?: string;
|
|
291
|
-
/** Anonymous
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
* TenantResolver damit z.B. ein Subdomain → Tenant-Lookup gegen die
|
|
297
|
-
* DB möglich ist (siehe samples/showcases/publicstatus für das
|
|
298
|
-
* Multi-Tenant-Pattern). */
|
|
288
|
+
/** Anonymous access for public endpoints (same shape as runDevApp).
|
|
289
|
+
* Accepts either a static config object or a factory
|
|
290
|
+
* `({db, redis, registry}) => Config`, called once at boot after
|
|
291
|
+
* db/redis/registry have been constructed. The factory can close over these
|
|
292
|
+
* dependencies so a TenantResolver can look up a subdomain in the database. */
|
|
299
293
|
readonly anonymousAccess?: AnonymousAccessOption;
|
|
300
294
|
/** Static-file root for HTML / assets. Served on the catch-all route
|
|
301
295
|
* for any path that doesn't match an /api/ handler. Use this for the
|
|
@@ -350,25 +344,24 @@ export type RunProdAppOptions = {
|
|
|
350
344
|
* (AWS/GCP/Azure); with it set, `KUMIKO_SECRETS_MASTER_KEY_V1` is not
|
|
351
345
|
* required in the env. */
|
|
352
346
|
readonly masterKey?: MasterKeyProvider;
|
|
353
|
-
/** Subject-
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
* ohne Adapter bleibt Crypto-Shredding aus. */
|
|
347
|
+
/** Subject-key adapter for crypto-shredding (GDPR Art. 17). When set, it is
|
|
348
|
+
* exposed to feature code as `ctx.kms`, and boot checks its health so the app
|
|
349
|
+
* cannot start against an unreachable KMS. Without an adapter,
|
|
350
|
+
* crypto-shredding remains disabled. */
|
|
358
351
|
readonly kms?: KmsAdapter;
|
|
359
|
-
/** 32-
|
|
360
|
-
* (env: KUMIKO_BLIND_INDEX_KEY, `openssl rand -base64 32`).
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
352
|
+
/** 32-byte base64 key for blind-index HMACs on `lookupable` fields
|
|
353
|
+
* (env: KUMIKO_BLIND_INDEX_KEY, `openssl rand -base64 32`). It is separate
|
|
354
|
+
* from PLATFORM_KEK and not part of the KmsAdapter contract. It is required
|
|
355
|
+
* when `kms` is set and a feature declares lookupable fields; otherwise
|
|
356
|
+
* equality lookups on ciphertext, such as login by email, fail and boot
|
|
357
|
+
* aborts. */
|
|
365
358
|
readonly blindIndexKey?: string;
|
|
366
|
-
/**
|
|
367
|
-
* pii/userOwned/tenantOwned
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
359
|
+
/** Explicit opt-out from the strict PII boot gate. Features may declare
|
|
360
|
+
* pii/userOwned/tenantOwned fields without a provisioned `kms`; those fields
|
|
361
|
+
* remain plaintext in rows and events, and GDPR erasure through
|
|
362
|
+
* crypto-shredding is unavailable. The value records the reason for the
|
|
363
|
+
* audit trail (for example, "kms rollout pending"). Without this flag and
|
|
364
|
+
* without `kms`, boot aborts. */
|
|
372
365
|
readonly allowPlaintextPii?: string;
|
|
373
366
|
/** Short form over `kms` / `blindIndexKey` / `allowPlaintextPii` that wires
|
|
374
367
|
* the subject-key KMS from the env; not combinable with them. Unset keeps
|
|
@@ -422,8 +415,8 @@ export type RunProdAppOptions = {
|
|
|
422
415
|
/** With `runSingleInstance: false`: the worker mounts the same mail / fileStorage features
|
|
423
416
|
* (its own boot options or by hand), so the boot warning about them stays silent. */
|
|
424
417
|
readonly workerMountsFeatures?: boolean;
|
|
425
|
-
/** Job
|
|
426
|
-
*
|
|
418
|
+
/** Job settings. Jobs a feature registers with `r.job(...)` are wired
|
|
419
|
+
* automatically (see runSingleInstance). */
|
|
427
420
|
readonly jobs?: {
|
|
428
421
|
/** BullMQ-Queue-Prefix (default "kumiko"). */
|
|
429
422
|
readonly queueNamePrefix?: string;
|
|
@@ -433,25 +426,20 @@ export type RunProdAppOptions = {
|
|
|
433
426
|
* the app manages tenants outside that feature. */
|
|
434
427
|
readonly getActiveTenantIds?: JobRunnerOptions["getActiveTenantIds"];
|
|
435
428
|
};
|
|
436
|
-
/** Event
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
* Read-Projektion (z.B. custom-fields jsonb) blieb in Prod leer,
|
|
441
|
-
* kumiko_event_consumers blieb ohne Rows. `disabled: true` nur für
|
|
442
|
-
* Setups mit dezidiertem Worker-Process. */
|
|
429
|
+
/** Event dispatcher (MSP application) in the API process. Enabled by default
|
|
430
|
+
* because the single-container deployment has no separate worker process to
|
|
431
|
+
* apply multiStreamProjections. Set `disabled: true` only when a dedicated
|
|
432
|
+
* worker process takes over the dispatcher. */
|
|
443
433
|
readonly eventDispatcher?: {
|
|
444
434
|
readonly disabled?: boolean;
|
|
445
|
-
/**
|
|
446
|
-
*
|
|
447
|
-
* späteren pgClient-Pass-through. */
|
|
435
|
+
/** Dispatcher-loop polling interval (default: see createEventDispatcher).
|
|
436
|
+
* LISTEN/NOTIFY wiring comes with a later pgClient pass-through. */
|
|
448
437
|
readonly pollIntervalMs?: number;
|
|
449
438
|
};
|
|
450
439
|
/** Declarative HTTP-routes outside the /api/write|query|batch pipeline —
|
|
451
440
|
* each entry declares its access tier (`entry`: "anonymous" | "user" |
|
|
452
441
|
* "signature"), buildServer wires the matching guard + deps. Mounted
|
|
453
|
-
* inside buildServer, right after the r.httpRoute loop, before seeds
|
|
454
|
-
* (kumiko-framework#3050). */
|
|
442
|
+
* inside buildServer, right after the r.httpRoute loop, before seeds. */
|
|
455
443
|
readonly extraRoutes?: readonly ExtraRouteDefinition[];
|
|
456
444
|
/** Framework-managed unsubscribe links. Mounts `createUnsubscribeRoutes({ secret })` and makes
|
|
457
445
|
* delivery sign `data.unsubscribeUrl` (path `DELIVERY_UNSUBSCRIBE_PATH`) for every email
|
|
@@ -508,13 +496,13 @@ export type RunProdAppOptions = {
|
|
|
508
496
|
* Tests use this to feed crafted env-maps without polluting the
|
|
509
497
|
* global. */
|
|
510
498
|
readonly envSource?: Record<string, string | undefined>;
|
|
511
|
-
/** Observability
|
|
512
|
-
*
|
|
513
|
-
*
|
|
499
|
+
/** Observability provider (tracer and meter). Default: Noop, with no overhead
|
|
500
|
+
* or `/metrics` endpoint. Supply a provider based on
|
|
501
|
+
* `createPrometheusMeter()` together with `metrics` to expose `/metrics`. */
|
|
514
502
|
readonly observability?: ObservabilityProvider;
|
|
515
|
-
/**
|
|
516
|
-
* PrometheusMeter-
|
|
517
|
-
*
|
|
503
|
+
/** Enables the `/metrics` endpoint in OpenMetrics format. Without a
|
|
504
|
+
* PrometheusMeter-based `observability` provider, `/metrics` returns 503
|
|
505
|
+
* with a misconfiguration message instead of real metrics. */
|
|
518
506
|
readonly metrics?: import("@cosmicdrift/kumiko-framework/api").ServerOptions["metrics"];
|
|
519
507
|
/** L1 (global per-IP limit) + L2 (auth endpoints) rate-limit middleware, passed
|
|
520
508
|
* to `buildServer`'s `ServerOptions.rateLimit`. Without a value L1 allows 600/60 s
|