@cosmicdrift/kumiko-server-runtime 0.366.0 → 0.368.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 +1 -1
- package/dist/boot/worker-boot-core.js +8 -2
- package/dist/run-prod-app-boot-context.d.ts +3 -0
- package/dist/run-prod-app-boot-context.js +3 -1
- package/dist/run-prod-app.d.ts +21 -0
- package/dist/run-prod-app.js +50 -9
- package/dist/seed-dispatcher.d.ts +11 -0
- package/dist/seed-dispatcher.js +36 -0
- package/package.json +11 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @cosmicdrift/kumiko-server-runtime
|
|
2
2
|
|
|
3
|
-
Production server boot for Kumiko apps: connections, schema drift gate, seeds, lifecycle and graceful shutdown. The prod counterpart to `runDevApp` in `@cosmicdrift/kumiko-dev-server`, without dev
|
|
3
|
+
Production server boot for Kumiko apps: connections, schema drift gate, seeds, lifecycle and graceful shutdown. The prod counterpart to `runDevApp` in `@cosmicdrift/kumiko-dev-server`, without dev or scaffold tooling.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -105,8 +105,14 @@ export async function bootWorkerProcess(options, envSource, profile) {
|
|
|
105
105
|
throw new Error(`[${processName}] BOOT ABORTED — KMS health check failed (latency ${kmsHealth.latencyMs}ms)`);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
const { db, close: closeDb } = createDbConnection(
|
|
109
|
-
|
|
108
|
+
const { db, close: closeDb } = createDbConnection({
|
|
109
|
+
url: databaseUrl,
|
|
110
|
+
...dbConnectionOptionsFromEnv(envSource),
|
|
111
|
+
});
|
|
112
|
+
const { db: outsideTransactionDb, close: closeOutsideTransactionDb } = createDbConnection({
|
|
113
|
+
url: databaseUrl,
|
|
114
|
+
...outsideTransactionPoolOptionsFromEnv(envSource),
|
|
115
|
+
});
|
|
110
116
|
const redis = new Redis(redisUrl, { maxRetriesPerRequest: null });
|
|
111
117
|
const cacheSync = createRedisCacheSyncBus({
|
|
112
118
|
redisUrl,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type EmailVerificationOptions, type PasswordResetOptions } from "@cosmicdrift/kumiko-bundled-features/auth-email-password";
|
|
2
2
|
import { resolveSessionStore } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
|
|
3
|
+
import { type UnsubscribeLinkConfig } from "@cosmicdrift/kumiko-bundled-features/delivery";
|
|
3
4
|
import type { SseBroker } from "@cosmicdrift/kumiko-framework/api";
|
|
4
5
|
import type { KmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
|
|
5
6
|
import { type DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
@@ -30,6 +31,8 @@ export declare function buildBootExtraContext(opts: {
|
|
|
30
31
|
readonly deliverQueuedInline?: boolean;
|
|
31
32
|
/** Receives redacted delivery failures; without it they go to the console. */
|
|
32
33
|
readonly log?: Logger;
|
|
34
|
+
/** Signs `unsubscribe: true` notifications' mail links; see runProdApp `unsubscribe`. */
|
|
35
|
+
readonly unsubscribeLinks?: UnsubscribeLinkConfig;
|
|
33
36
|
}): Record<string, unknown>;
|
|
34
37
|
/** Die Auth-Felder die resolveAuthMail liest/normalisiert — beide
|
|
35
38
|
* App-Auth-Typen (prod + dev) erfüllen das strukturell. */
|
|
@@ -60,12 +60,13 @@ function buildDeliveryNotifyFactory(opts) {
|
|
|
60
60
|
...(opts.escapeHatchAuditSink && { escapeHatchAuditSink: opts.escapeHatchAuditSink }),
|
|
61
61
|
...(opts.log && { log: opts.log }),
|
|
62
62
|
...(opts.tenantUserIdsQuery && { tenantUserIdsQuery: opts.tenantUserIdsQuery }),
|
|
63
|
+
...(opts.unsubscribeLinks && { unsubscribeLinks: opts.unsubscribeLinks }),
|
|
63
64
|
...(configAccessorFactory && {
|
|
64
65
|
tenantConfigFor: (tenantId) => {
|
|
65
66
|
const systemUser = createSystemUser(tenantId);
|
|
66
67
|
return configAccessorFactory({
|
|
67
68
|
user: { id: systemUser.id, tenantId },
|
|
68
|
-
db: createTenantDb(opts.db, tenantId),
|
|
69
|
+
db: createTenantDb({ db: opts.db, tenantId }),
|
|
69
70
|
...(opts.secrets && { secrets: opts.secrets }),
|
|
70
71
|
});
|
|
71
72
|
},
|
|
@@ -147,6 +148,7 @@ export function buildBootExtraContext(opts) {
|
|
|
147
148
|
...(hasTenantFeature && { tenantUserIdsQuery: TenantQueries.resolveUserIds }),
|
|
148
149
|
configResolver: bootConfigResolver,
|
|
149
150
|
deliverQueuedInline: opts.deliverQueuedInline,
|
|
151
|
+
unsubscribeLinks: opts.unsubscribeLinks,
|
|
150
152
|
}),
|
|
151
153
|
}),
|
|
152
154
|
// Top-level provider so feature jobs (secrets rotate, config reencrypt)
|
package/dist/run-prod-app.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="bun-types" preserve="true" />
|
|
2
2
|
import { type AccountLockoutOptions, type AccountUnlockOptions, type AuthMailLocale, type AuthPaths, type EmailVerificationOptions, type InviteOptions, type PasswordResetOptions, type SignupOptions } from "@cosmicdrift/kumiko-bundled-features/auth-email-password";
|
|
3
3
|
import { type SeedAdminOptions } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/seeding";
|
|
4
|
+
import { type UnsubscribeLinkConfig } from "@cosmicdrift/kumiko-bundled-features/delivery";
|
|
4
5
|
import { type ExtraRouteDefinition, type KumikoServeEnv, type PostAuthLandingResolver, type SseBroker } from "@cosmicdrift/kumiko-framework/api";
|
|
5
6
|
import { type KmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
|
|
6
7
|
import { type EffectiveFeaturesResolver, type FeatureDefinition, type ValidateBootOptions } from "@cosmicdrift/kumiko-framework/engine";
|
|
@@ -14,6 +15,7 @@ import type { PageHeadMeta, PageHeadResolver, PageHeadSystemQuery } from "@cosmi
|
|
|
14
15
|
import { Redis } from "ioredis";
|
|
15
16
|
import { type SystemWireDeps } from "./extra-routes-deps.js";
|
|
16
17
|
import { type SecurityHeadersOption } from "./security-headers.js";
|
|
18
|
+
import { type SeedCreateFileRef } from "./seed-dispatcher.js";
|
|
17
19
|
export { buildBunServeOptions } from "./bun-serve-options.js";
|
|
18
20
|
export { addConfigAccessorFactory, buildBootExtraContext, loggerFromExtraContext, resolveAuthMail, } from "./run-prod-app-boot-context.js";
|
|
19
21
|
export { staticCachePolicy } from "./run-prod-app-static-files.js";
|
|
@@ -175,6 +177,9 @@ export type ProdSeedFn = (deps: {
|
|
|
175
177
|
db: import("@cosmicdrift/kumiko-framework/db").DbConnection;
|
|
176
178
|
registry: import("@cosmicdrift/kumiko-framework/engine").Registry;
|
|
177
179
|
dispatcher: import("@cosmicdrift/kumiko-framework/pipeline").Dispatcher;
|
|
180
|
+
/** Stores bytes as a fileRef of `tenantId` (same validation and storage
|
|
181
|
+
* layout as POST /files) without a helper write handler. */
|
|
182
|
+
createFileRef: SeedCreateFileRef;
|
|
178
183
|
}) => Promise<void>;
|
|
179
184
|
/** Boot-Time-Deps die `extraContext` + `anonymousAccess` Factories als
|
|
180
185
|
* Argument bekommen. Closure dann in der returned Config (z.B. ein
|
|
@@ -392,6 +397,22 @@ export type RunProdAppOptions = {
|
|
|
392
397
|
* inside buildServer, right after the r.httpRoute loop, before seeds
|
|
393
398
|
* (kumiko-framework#3050). */
|
|
394
399
|
readonly extraRoutes?: readonly ExtraRouteDefinition[];
|
|
400
|
+
/** Framework-managed unsubscribe links. Mounts `createUnsubscribeRoutes({ secret })` and makes
|
|
401
|
+
* delivery sign `data.unsubscribeUrl` (path `DELIVERY_UNSUBSCRIBE_PATH`) for every email
|
|
402
|
+
* notification declared with `unsubscribe: true`, for user and `route` recipients; a link the
|
|
403
|
+
* caller put into `data.unsubscribeUrl` wins. `secret` must be dedicated (≥32 chars), not the
|
|
404
|
+
* session JWT secret. Links point at the origin of `auth.publicOrigin`, then `auth.mail.baseUrl`,
|
|
405
|
+
* then the `APP_BASE_URL` env; with none of them no link is added (boot warning). Address
|
|
406
|
+
* recipients need a blind-index key, otherwise they get no link. Do not also mount
|
|
407
|
+
* `createUnsubscribeRoutes` via `extraRoutes`. `tenantOrigin` points a tenant's links at
|
|
408
|
+
* its own subdomain or custom domain (the host must serve this app's `/api` routes); null,
|
|
409
|
+
* a throw or a non-https value fall back to the app origin. The link carries the mail's
|
|
410
|
+
* locale, so the confirmation pages speak the mail's language. An app reacts to an address
|
|
411
|
+
* opt-out with an `r.job` triggered on `DeliveryHandlers.unsubscribeAddress`. */
|
|
412
|
+
readonly unsubscribe?: {
|
|
413
|
+
readonly secret: string;
|
|
414
|
+
readonly tenantOrigin?: UnsubscribeLinkConfig["tenantOrigin"];
|
|
415
|
+
};
|
|
395
416
|
/** Hook for app-wired co-running components that need the system-write
|
|
396
417
|
* dispatcher (e.g. wiring a webhook plugin's system-scoped secrets, or
|
|
397
418
|
* starting a co-running supervisor) — runs after buildServer, before
|
package/dist/run-prod-app.js
CHANGED
|
@@ -43,12 +43,13 @@ import { AuthErrors, AuthHandlers, AuthQueries, } from "@cosmicdrift/kumiko-bund
|
|
|
43
43
|
import { seedAdminGuarded, } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/seeding";
|
|
44
44
|
import { EXT_SESSION_STORE, EXT_TOKEN_VERIFIER, resolveAnonymousAccessFromRegistry, resolveTokenVerifier, } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
|
|
45
45
|
import { AUTH_MFA_FEATURE, AuthMfaHandlers } from "@cosmicdrift/kumiko-bundled-features/auth-mfa";
|
|
46
|
+
import { createUnsubscribeRoutes, } from "@cosmicdrift/kumiko-bundled-features/delivery";
|
|
46
47
|
import { PAT_FEATURE, patRateLimitFromFeature, } from "@cosmicdrift/kumiko-bundled-features/personal-access-tokens";
|
|
47
48
|
import { SECRETS_MASTER_KEK_ENV_KEYS } from "@cosmicdrift/kumiko-bundled-features/secrets";
|
|
48
49
|
import { SESSIONS_FEATURE } from "@cosmicdrift/kumiko-bundled-features/sessions";
|
|
49
50
|
import { TenantQueries } from "@cosmicdrift/kumiko-bundled-features/tenant";
|
|
50
51
|
import { UserQueries } from "@cosmicdrift/kumiko-bundled-features/user";
|
|
51
|
-
import { createDefaultSseBroker, createRedisLoginRateLimiter, loadJwtSecretOrKeyring, parseTrustedProxyHopsEnv, TRUSTED_PROXY_HOPS_ENV, withFileProviderResolver, } from "@cosmicdrift/kumiko-framework/api";
|
|
52
|
+
import { createDefaultSseBroker, createRedisLoginRateLimiter, loadJwtSecretOrKeyring, parseTrustedProxyHopsEnv, publicOriginOf, TRUSTED_PROXY_HOPS_ENV, withFileProviderResolver, } from "@cosmicdrift/kumiko-framework/api";
|
|
52
53
|
import { configureBlindIndexKey, configurePiiSubjectKms, resolvePlatformKeks, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
53
54
|
import { configureEntityFieldEncryption, createDbConnection, dbConnectionOptionsFromEnv, outsideTransactionPoolOptionsFromEnv, } from "@cosmicdrift/kumiko-framework/db";
|
|
54
55
|
import { collectWriteHandlerQns, createRegistry, findTierResolverUsage, requireTierResolverPlugin, validateAppCustomScreenWriteQns, validateBoot, } from "@cosmicdrift/kumiko-framework/engine";
|
|
@@ -57,6 +58,7 @@ import { KumikoBootError, kmsSlotsOf, parseEnv, withOptionalEnvKeys, } from "@co
|
|
|
57
58
|
import { renderDryRun } from "@cosmicdrift/kumiko-framework/env/dry-run";
|
|
58
59
|
import { createEsOperationsTable, createSeedMigrationContext, runPendingSeedMigrations, } from "@cosmicdrift/kumiko-framework/es-ops";
|
|
59
60
|
import { assertSeedModeNotInProduction } from "@cosmicdrift/kumiko-framework/event-store";
|
|
61
|
+
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
60
62
|
import { assertKumikoSchemaCurrent, SchemaDriftError, } from "@cosmicdrift/kumiko-framework/migrations";
|
|
61
63
|
import { createMetricsForFactory, } from "@cosmicdrift/kumiko-framework/observability";
|
|
62
64
|
import { createDispatcher, createEntityCache, createEventDedup, createIdempotencyGuard, flushEscapeHatchAudits, } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
@@ -73,10 +75,11 @@ import { buildBunServeOptions, resolveDerivedMaxRequestBodySize } from "./bun-se
|
|
|
73
75
|
import { buildComposeAuthOptions, composeFeatures } from "./compose-features.js";
|
|
74
76
|
import { makeDispatchSystemWrite } from "./extra-routes-deps.js";
|
|
75
77
|
import { assertPiiBootInvariants } from "./pii-boot-gate.js";
|
|
76
|
-
import { resolveAuthPublicOrigin } from "./resolve-auth-public-origin.js";
|
|
78
|
+
import { APP_BASE_URL_ENV, resolveAuthPublicOrigin } from "./resolve-auth-public-origin.js";
|
|
77
79
|
import { addConfigAccessorFactory, buildBootExtraContext, buildProdSessionAuth, loggerFromExtraContext, resolveAuthMail, wireProdPatAutoRevoke, } from "./run-prod-app-boot-context.js";
|
|
78
80
|
import { buildStaticFallback } from "./run-prod-app-static-files.js";
|
|
79
81
|
import { withSecurityHeaders } from "./security-headers.js";
|
|
82
|
+
import { makeSeedCreateFileRef, withSeedErrorContext, } from "./seed-dispatcher.js";
|
|
80
83
|
import { assertSessionBootInvariants } from "./session-boot-gate.js";
|
|
81
84
|
import { shouldWireProdSessions } from "./session-wiring.js";
|
|
82
85
|
import { stripNoRouteMatchHeader } from "./try-hono-first.js";
|
|
@@ -288,6 +291,27 @@ export async function runProdApp(options) {
|
|
|
288
291
|
console.warn(message);
|
|
289
292
|
})
|
|
290
293
|
: undefined;
|
|
294
|
+
const unsubscribeBaseUrl = options.unsubscribe
|
|
295
|
+
? publicOriginOf(effectiveAuth?.publicOrigin ??
|
|
296
|
+
effectiveAuth?.mail?.baseUrl ??
|
|
297
|
+
envSource[APP_BASE_URL_ENV] ??
|
|
298
|
+
"")
|
|
299
|
+
: undefined;
|
|
300
|
+
if (options.unsubscribe && unsubscribeBaseUrl === undefined) {
|
|
301
|
+
createFallbackLogger("runProdApp").warn(`unsubscribe is enabled but no valid public app URL is known (auth.publicOrigin, auth.mail.baseUrl or ${APP_BASE_URL_ENV}), so mails get no unsubscribe link.`);
|
|
302
|
+
}
|
|
303
|
+
const unsubscribeLinks = options.unsubscribe && unsubscribeBaseUrl !== undefined
|
|
304
|
+
? {
|
|
305
|
+
secret: options.unsubscribe.secret,
|
|
306
|
+
baseUrl: unsubscribeBaseUrl,
|
|
307
|
+
...(options.unsubscribe.tenantOrigin !== undefined && {
|
|
308
|
+
tenantOrigin: options.unsubscribe.tenantOrigin,
|
|
309
|
+
}),
|
|
310
|
+
}
|
|
311
|
+
: undefined;
|
|
312
|
+
const unsubscribeRoutes = options.unsubscribe
|
|
313
|
+
? createUnsubscribeRoutes({ secret: options.unsubscribe.secret })
|
|
314
|
+
: [];
|
|
291
315
|
// 3. Feature registry. Auth-mode auto-mixes config/user/tenant/auth-email-
|
|
292
316
|
// password via composeFeatures — same source-of-truth as runDevApp
|
|
293
317
|
// AND the per-app drizzle-Schema-Generator, so Migration und Runtime
|
|
@@ -346,8 +370,14 @@ export async function runProdApp(options) {
|
|
|
346
370
|
throw new Error(`[runProdApp] BOOT ABORTED — KMS health check failed (latency ${kmsHealth.latencyMs}ms)`);
|
|
347
371
|
}
|
|
348
372
|
}
|
|
349
|
-
const { db, close: closeDb } = createDbConnection(
|
|
350
|
-
|
|
373
|
+
const { db, close: closeDb } = createDbConnection({
|
|
374
|
+
url: databaseUrl,
|
|
375
|
+
...dbConnectionOptionsFromEnv(envSource),
|
|
376
|
+
});
|
|
377
|
+
const { db: outsideTransactionDb, close: closeOutsideTransactionDb } = createDbConnection({
|
|
378
|
+
url: databaseUrl,
|
|
379
|
+
...outsideTransactionPoolOptionsFromEnv(envSource),
|
|
380
|
+
});
|
|
351
381
|
const redis = new Redis(redisUrl, { maxRetriesPerRequest: null });
|
|
352
382
|
await waitForDatabase(db, { processName: "runProdApp" });
|
|
353
383
|
// Sprint-8a Tier-Composition auto-wire: scan features for a
|
|
@@ -452,6 +482,7 @@ export async function runProdApp(options) {
|
|
|
452
482
|
crypto: bootCrypto,
|
|
453
483
|
...(options.kms && { kms: options.kms }),
|
|
454
484
|
...(bootLogger && { log: bootLogger }),
|
|
485
|
+
...(unsubscribeLinks && { unsubscribeLinks }),
|
|
455
486
|
});
|
|
456
487
|
const extraContext = {
|
|
457
488
|
...addConfigAccessorFactory({ ...autoExtraContext, ...resolvedExtraContext }, registry),
|
|
@@ -534,7 +565,9 @@ export async function runProdApp(options) {
|
|
|
534
565
|
...(options.rateLimit && { rateLimit: options.rateLimit }),
|
|
535
566
|
...(trustedProxyHops !== undefined && { trustedProxyHops }),
|
|
536
567
|
...(options.errorDocs && { errorDocs: options.errorDocs }),
|
|
537
|
-
...(options.extraRoutes
|
|
568
|
+
...((options.extraRoutes || options.unsubscribe) && {
|
|
569
|
+
extraRoutes: [...(options.extraRoutes ?? []), ...unsubscribeRoutes],
|
|
570
|
+
}),
|
|
538
571
|
...(effectiveAuth && {
|
|
539
572
|
auth: {
|
|
540
573
|
membershipQuery: TenantQueries.memberships,
|
|
@@ -703,12 +736,20 @@ export async function runProdApp(options) {
|
|
|
703
736
|
db,
|
|
704
737
|
...(bootCrypto.configCipher && { cipher: bootCrypto.configCipher }),
|
|
705
738
|
});
|
|
706
|
-
const
|
|
707
|
-
|
|
739
|
+
const hasSeeds = (options.seeds?.length ?? 0) > 0 || options.seedsDir !== undefined;
|
|
740
|
+
const seedContext = hasSeeds
|
|
741
|
+
? withFileProviderResolver(registry, { db, redis, entityCache, registry, ...extraContext }, cacheSync)
|
|
742
|
+
: undefined;
|
|
743
|
+
const seedDispatcher = seedContext
|
|
744
|
+
? withSeedErrorContext(createDispatcher(registry, seedContext, {
|
|
745
|
+
...baseEntrypointOptions.dispatcherOptions,
|
|
746
|
+
...(entrypoint.mode === "all-in-one" && { jobRunner: entrypoint.jobRunner }),
|
|
747
|
+
}))
|
|
708
748
|
: undefined;
|
|
709
|
-
if (seedDispatcher) {
|
|
749
|
+
if (seedDispatcher && seedContext) {
|
|
750
|
+
const createFileRef = makeSeedCreateFileRef({ db, registry, context: seedContext });
|
|
710
751
|
for (const seed of options.seeds ?? []) {
|
|
711
|
-
await seed({ db, registry, dispatcher: seedDispatcher });
|
|
752
|
+
await seed({ db, registry, dispatcher: seedDispatcher, createFileRef });
|
|
712
753
|
}
|
|
713
754
|
}
|
|
714
755
|
// ES-Operations / Seed-Migrations (Phase 1). Läuft NACH applyBootSeeds +
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import { type AppContext, type Registry, type TenantId, type WriteResult } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { type CreatedFileRef, type CreateFileRefInput } from "@cosmicdrift/kumiko-framework/files";
|
|
4
|
+
import type { Dispatcher } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
5
|
+
export type SeedCreateFileRef = (tenantId: TenantId, input: CreateFileRefInput) => Promise<WriteResult<CreatedFileRef>>;
|
|
6
|
+
export declare function makeSeedCreateFileRef(deps: {
|
|
7
|
+
readonly db: DbConnection;
|
|
8
|
+
readonly registry: Registry;
|
|
9
|
+
readonly context: AppContext;
|
|
10
|
+
}): SeedCreateFileRef;
|
|
11
|
+
export declare function withSeedErrorContext<D extends Pick<Dispatcher, "write">>(dispatcher: D): D;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createTenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import { createSystemUser, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { reraiseAsKumikoError } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
|
+
import { createFileContext, createFileRefFromBytes, } from "@cosmicdrift/kumiko-framework/files";
|
|
5
|
+
export function makeSeedCreateFileRef(deps) {
|
|
6
|
+
const { db, registry, context } = deps;
|
|
7
|
+
const fileResolver = context._fileProviderResolver;
|
|
8
|
+
return (tenantId, input) => createFileRefFromBytes({
|
|
9
|
+
db: createTenantDb({ db, tenantId }),
|
|
10
|
+
files: fileResolver
|
|
11
|
+
? createFileContext(() => fileResolver(tenantId), tenantId)
|
|
12
|
+
: context.files,
|
|
13
|
+
registry,
|
|
14
|
+
user: createSystemUser(tenantId),
|
|
15
|
+
}, input);
|
|
16
|
+
}
|
|
17
|
+
// The dispatcher masks a thrown handler error as "internal error" and keeps the
|
|
18
|
+
// cause only on a side channel; a seed has no HTTP client to protect, so the
|
|
19
|
+
// handler name and cause message go into the message the seed caller sees.
|
|
20
|
+
export function withSeedErrorContext(dispatcher) {
|
|
21
|
+
return {
|
|
22
|
+
...dispatcher,
|
|
23
|
+
async write(type, payload, user, requestId) {
|
|
24
|
+
const result = await dispatcher.write(type, payload, user, requestId);
|
|
25
|
+
if (result.isSuccess || result.error.code !== "internal_error")
|
|
26
|
+
return result;
|
|
27
|
+
const cause = reraiseAsKumikoError(result.error).cause;
|
|
28
|
+
const causeMessage = cause instanceof Error ? cause.message : undefined;
|
|
29
|
+
const detail = causeMessage ? `${type}: ${causeMessage}` : type;
|
|
30
|
+
return {
|
|
31
|
+
isSuccess: false,
|
|
32
|
+
error: { ...result.error, message: `${result.error.message} (${detail})` },
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-server-runtime",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Production server-boot runtime for Kumiko apps: connections, schema-drift-gate, seeds, lifecycle, graceful shutdown. Symmetric to kumiko-dev-server's runDevApp, without dev/scaffold
|
|
3
|
+
"version": "0.368.0",
|
|
4
|
+
"description": "Production server-boot runtime for Kumiko apps: connections, schema-drift-gate, seeds, lifecycle, graceful shutdown. Symmetric to kumiko-dev-server's runDevApp, without dev/scaffold tooling.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
7
7
|
"repository": {
|
|
@@ -84,16 +84,19 @@
|
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
88
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
89
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
90
|
-
"@cosmicdrift/kumiko-renderer-mail-html": "0.
|
|
91
|
-
"@cosmicdrift/kumiko-renderer-web": "0.
|
|
92
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
87
|
+
"@cosmicdrift/kumiko-bundled-features": "0.368.0",
|
|
88
|
+
"@cosmicdrift/kumiko-framework": "0.368.0",
|
|
89
|
+
"@cosmicdrift/kumiko-headless": "0.368.0",
|
|
90
|
+
"@cosmicdrift/kumiko-renderer-mail-html": "0.368.0",
|
|
91
|
+
"@cosmicdrift/kumiko-renderer-web": "0.368.0",
|
|
92
|
+
"@cosmicdrift/kumiko-types": "0.368.0",
|
|
93
93
|
"bun-types": "^1.3.13",
|
|
94
94
|
"ioredis": "^5.10.1",
|
|
95
95
|
"temporal-polyfill": "^0.3.2"
|
|
96
96
|
},
|
|
97
|
+
"devDependencies": {
|
|
98
|
+
"@cosmicdrift/kumiko-testing": "0.368.0"
|
|
99
|
+
},
|
|
97
100
|
"publishConfig": {
|
|
98
101
|
"registry": "https://registry.npmjs.org",
|
|
99
102
|
"access": "public",
|