@cosmicdrift/kumiko-server-runtime 0.329.0 → 0.330.2

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.
Files changed (90) hide show
  1. package/README.md +15 -0
  2. package/dist/boot/apply-boot-seeds.d.ts +8 -0
  3. package/{src/boot/apply-boot-seeds.ts → dist/boot/apply-boot-seeds.js} +2 -10
  4. package/dist/boot/boot-crypto.d.ts +9 -0
  5. package/dist/boot/boot-crypto.js +39 -0
  6. package/dist/boot/job-run-logger.d.ts +19 -0
  7. package/dist/boot/job-run-logger.js +44 -0
  8. package/dist/boot/pii-event-backfill-on-boot.d.ts +13 -0
  9. package/dist/boot/pii-event-backfill-on-boot.js +25 -0
  10. package/dist/build-prod-bundle.d.ts +103 -0
  11. package/dist/build-prod-bundle.js +751 -0
  12. package/dist/bun-serve-options.d.ts +32 -0
  13. package/dist/bun-serve-options.js +49 -0
  14. package/dist/compose-features.d.ts +31 -0
  15. package/dist/compose-features.js +124 -0
  16. package/dist/extra-routes-deps.d.ts +30 -0
  17. package/dist/extra-routes-deps.js +4 -0
  18. package/dist/index.d.ts +6 -0
  19. package/dist/index.js +7 -0
  20. package/dist/pii-boot-gate.d.ts +13 -0
  21. package/dist/pii-boot-gate.js +46 -0
  22. package/dist/renderer-web-fonts.d.ts +9 -0
  23. package/{src/renderer-web-fonts.ts → dist/renderer-web-fonts.js} +14 -18
  24. package/dist/resolve-tailwind-cli.d.ts +14 -0
  25. package/dist/resolve-tailwind-cli.js +36 -0
  26. package/dist/run-prod-app-boot-context.d.ts +43 -0
  27. package/dist/run-prod-app-boot-context.js +198 -0
  28. package/dist/run-prod-app-static-files.d.ts +32 -0
  29. package/dist/run-prod-app-static-files.js +280 -0
  30. package/dist/run-prod-app.d.ts +471 -0
  31. package/dist/run-prod-app.js +741 -0
  32. package/dist/run-worker-app.d.ts +92 -0
  33. package/dist/run-worker-app.js +220 -0
  34. package/dist/security-headers.d.ts +20 -0
  35. package/dist/security-headers.js +49 -0
  36. package/dist/session-boot-gate.d.ts +11 -0
  37. package/dist/session-boot-gate.js +22 -0
  38. package/dist/session-wiring.d.ts +11 -0
  39. package/{src/session-wiring.ts → dist/session-wiring.js} +2 -6
  40. package/dist/try-hono-first.d.ts +24 -0
  41. package/{src/try-hono-first.ts → dist/try-hono-first.js} +8 -36
  42. package/package.json +102 -36
  43. package/src/changes.json +6 -0
  44. package/src/__tests__/boot-extra-context.test.ts +0 -163
  45. package/src/__tests__/boot-probe-fixture.ts +0 -64
  46. package/src/__tests__/build-prod-bundle.inprocess.test.ts +0 -233
  47. package/src/__tests__/build-prod-bundle.test.ts +0 -724
  48. package/src/__tests__/cache-headers.test.ts +0 -83
  49. package/src/__tests__/compose-features-mfa-wiring.integration.test.ts +0 -308
  50. package/src/__tests__/compose-features-wiring.integration.test.ts +0 -609
  51. package/src/__tests__/compose-features.test.ts +0 -206
  52. package/src/__tests__/config-seed-boot.integration.test.ts +0 -158
  53. package/src/__tests__/pii-boot-gate.test.ts +0 -112
  54. package/src/__tests__/renderer-web-css-relocation.integration.test.ts +0 -85
  55. package/src/__tests__/renderer-web-shell-sentinel.test.ts +0 -35
  56. package/src/__tests__/renderer-web-theme-plugin.integration.test.ts +0 -77
  57. package/src/__tests__/require-env.test.ts +0 -34
  58. package/src/__tests__/resolve-auth-mail.test.ts +0 -144
  59. package/src/__tests__/resolve-tailwind-cli.test.ts +0 -81
  60. package/src/__tests__/run-prod-app-dry-run.test.ts +0 -135
  61. package/src/__tests__/run-prod-app-env-source.test.ts +0 -118
  62. package/src/__tests__/run-prod-app-kms-slots.test.ts +0 -108
  63. package/src/__tests__/run-prod-app-spec.test.ts +0 -167
  64. package/src/__tests__/run-prod-app-static-files.test.ts +0 -298
  65. package/src/__tests__/run-prod-app.integration.test.ts +0 -1525
  66. package/src/__tests__/run-worker-app-boot.test.ts +0 -183
  67. package/src/__tests__/run-worker-app.integration.test.ts +0 -218
  68. package/src/__tests__/security-headers.test.ts +0 -88
  69. package/src/__tests__/session-boot-gate.test.ts +0 -70
  70. package/src/__tests__/session-wiring.test.ts +0 -16
  71. package/src/__tests__/try-hono-first.test.ts +0 -108
  72. package/src/__tests__/websocket-route.integration.test.ts +0 -695
  73. package/src/boot/__tests__/job-run-logger.integration.test.ts +0 -262
  74. package/src/boot/__tests__/job-run-logger.test.ts +0 -26
  75. package/src/boot/boot-crypto.ts +0 -67
  76. package/src/boot/job-run-logger.ts +0 -63
  77. package/src/boot/pii-event-backfill-on-boot.ts +0 -43
  78. package/src/build-prod-bundle.ts +0 -996
  79. package/src/bun-serve-options.ts +0 -110
  80. package/src/compose-features.ts +0 -197
  81. package/src/extra-routes-deps.ts +0 -38
  82. package/src/index.ts +0 -27
  83. package/src/pii-boot-gate.ts +0 -80
  84. package/src/resolve-tailwind-cli.ts +0 -45
  85. package/src/run-prod-app-boot-context.ts +0 -319
  86. package/src/run-prod-app-static-files.ts +0 -359
  87. package/src/run-prod-app.ts +0 -1420
  88. package/src/run-worker-app.ts +0 -366
  89. package/src/security-headers.ts +0 -67
  90. package/src/session-boot-gate.ts +0 -34
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # @cosmicdrift/kumiko-server-runtime
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, scaffold or codegen tooling.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add @cosmicdrift/kumiko-server-runtime
9
+ ```
10
+
11
+ ```ts
12
+ import { runProdApp } from "@cosmicdrift/kumiko-server-runtime/run-prod-app";
13
+ ```
14
+
15
+ Documentation: https://kumiko.rocks
@@ -0,0 +1,8 @@
1
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
+ import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
3
+ import type { EnvelopeCipher } from "@cosmicdrift/kumiko-framework/secrets";
4
+ export declare function applyBootSeeds(deps: {
5
+ registry: Registry;
6
+ db: DbConnection;
7
+ cipher?: EnvelopeCipher;
8
+ }): Promise<void>;
@@ -1,8 +1,4 @@
1
1
  import { seedAllConfigValues } from "@cosmicdrift/kumiko-bundled-features/config";
2
- import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
- import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
4
- import type { EnvelopeCipher } from "@cosmicdrift/kumiko-framework/secrets";
5
-
6
2
  // Single boot-seed entry-point. runDevApp + runProdApp both call this
7
3
  // from their post-stack hook, so the wiring lives in exactly one place
8
4
  // — config-seed-boot.integration.ts pins this helper, which means a
@@ -10,10 +6,6 @@ import type { EnvelopeCipher } from "@cosmicdrift/kumiko-framework/secrets";
10
6
  // surfaces as a missing-helper-use in code review rather than silently
11
7
  // shipping a server that never seeds.
12
8
  // @wrapper-known semantic-alias
13
- export async function applyBootSeeds(deps: {
14
- registry: Registry;
15
- db: DbConnection;
16
- cipher?: EnvelopeCipher;
17
- }): Promise<void> {
18
- await seedAllConfigValues(deps.registry, deps.db, deps.cipher);
9
+ export async function applyBootSeeds(deps) {
10
+ await seedAllConfigValues(deps.registry, deps.db, deps.cipher);
19
11
  }
@@ -0,0 +1,9 @@
1
+ import { type DekCache, type EnvelopeCipher, type MasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
2
+ export declare function envHasMasterKek(env: Record<string, string | undefined>): boolean;
3
+ export type BootCrypto = {
4
+ readonly masterKeyProvider?: MasterKeyProvider;
5
+ readonly configCipher?: EnvelopeCipher;
6
+ readonly entityFieldCipher?: EnvelopeCipher;
7
+ readonly dekCache: DekCache;
8
+ };
9
+ export declare function resolveBootCrypto(envSource: Record<string, string | undefined>, masterKeyOverride?: MasterKeyProvider): BootCrypto;
@@ -0,0 +1,39 @@
1
+ // One place that turns env/options into the app-wide crypto objects:
2
+ // the MasterKeyProvider (KUMIKO_SECRETS_MASTER_KEY_V<n> keyring) and the
3
+ // EnvelopeCipher for `encrypted: true` config keys. runProdApp and
4
+ // runDevApp both resolve this once and thread it into
5
+ // buildBootExtraContext + applyBootSeeds so resolver, set-handler and
6
+ // seeds share the same cipher instance (and DEK cache).
7
+ import { createDekCache, createEnvelopeCipher, createEnvMasterKeyProvider, } from "@cosmicdrift/kumiko-framework/secrets";
8
+ // Keyring detection without throwing: only build the env provider when at
9
+ // least one versioned KEK is actually set. Covers (a) apps without any
10
+ // encryption — no KEK requirement, and (b) dev with an app-supplied
11
+ // provider in options.masterKey — env stays irrelevant.
12
+ const MASTER_KEK_VAR = /^KUMIKO_SECRETS_MASTER_KEY_V\d+$/;
13
+ export function envHasMasterKek(env) {
14
+ return Object.entries(env).some(([k, v]) => MASTER_KEK_VAR.test(k) && !!v);
15
+ }
16
+ export function resolveBootCrypto(envSource, masterKeyOverride) {
17
+ const masterKeyProvider = masterKeyOverride ??
18
+ (envHasMasterKek(envSource)
19
+ ? createEnvMasterKeyProvider({
20
+ // CURRENT_VERSION default "1" spiegelt secretsEnvSchema — ohne
21
+ // ihn wirft der raw-env-Provider, obwohl V1 gesetzt ist.
22
+ env: {
23
+ ...envSource,
24
+ KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION: envSource["KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION"] ?? "1",
25
+ },
26
+ })
27
+ : undefined);
28
+ const dekCache = createDekCache();
29
+ const configCipher = masterKeyProvider
30
+ ? createEnvelopeCipher(masterKeyProvider, { dekCache })
31
+ : undefined;
32
+ const entityFieldCipher = configCipher;
33
+ return {
34
+ ...(masterKeyProvider && { masterKeyProvider }),
35
+ ...(configCipher && { configCipher }),
36
+ ...(entityFieldCipher && { entityFieldCipher }),
37
+ dekCache,
38
+ };
39
+ }
@@ -0,0 +1,19 @@
1
+ import { createJobRunLogger } from "@cosmicdrift/kumiko-bundled-features/jobs";
2
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
+ import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
4
+ import type { AppContext } from "@cosmicdrift/kumiko-framework/engine/types";
5
+ import { type JobRunner } from "@cosmicdrift/kumiko-framework/jobs";
6
+ import { type Dispatcher } from "@cosmicdrift/kumiko-framework/pipeline";
7
+ export declare function jobRunLoggerCallbacks(registry: Registry, db: DbConnection): ReturnType<typeof createJobRunLogger> | undefined;
8
+ /** Dev-server parity: consume api + worker lanes when jobs are registered. */
9
+ export declare function startDevJobRunners(opts: {
10
+ readonly registry: Registry;
11
+ readonly db: DbConnection;
12
+ readonly context: AppContext;
13
+ readonly redisUrl: string;
14
+ readonly dispatcher: Dispatcher;
15
+ readonly queueNamePrefix: string;
16
+ }): Promise<{
17
+ readonly runners: readonly JobRunner[];
18
+ readonly stop: () => Promise<void>;
19
+ }>;
@@ -0,0 +1,44 @@
1
+ import { createJobRunLogger } from "@cosmicdrift/kumiko-bundled-features/jobs";
2
+ import { createJobRunner } from "@cosmicdrift/kumiko-framework/jobs";
3
+ import { dispatcherToWriteRef } from "@cosmicdrift/kumiko-framework/pipeline";
4
+ export function jobRunLoggerCallbacks(registry, db) {
5
+ if (registry.getFeature("jobs") === undefined)
6
+ return undefined;
7
+ return createJobRunLogger({ db, registry });
8
+ }
9
+ /** Dev-server parity: consume api + worker lanes when jobs are registered. */
10
+ export async function startDevJobRunners(opts) {
11
+ const jobs = [...opts.registry.getAllJobs().values()];
12
+ if (opts.registry.getFeature("jobs") === undefined || jobs.length === 0) {
13
+ return { runners: [], stop: async () => { } };
14
+ }
15
+ const logger = createJobRunLogger({ db: opts.db, registry: opts.registry });
16
+ const runners = [];
17
+ // `?? "worker"` mirrors job-runner's laneForJob: a job without an explicit
18
+ // runIn is enqueued onto the worker lane, so filtering those out would leave
19
+ // its queue without a consumer whenever no other job named the lane.
20
+ const lanes = new Set(jobs.map((j) => j.runIn ?? "worker"));
21
+ for (const lane of lanes) {
22
+ const jr = createJobRunner({
23
+ registry: opts.registry,
24
+ context: { ...opts.context, db: opts.db },
25
+ redisUrl: opts.redisUrl,
26
+ queueNamePrefix: opts.queueNamePrefix,
27
+ consumerLane: lane,
28
+ ...logger,
29
+ });
30
+ // Without this, ctx.write/ctx.queryAs inside a dev-run job throw
31
+ // "dispatcher attached — call attachDispatcher() first" on their first
32
+ // call — the prod entrypoints attach automatically, dev boot must too.
33
+ jr.attachDispatcher(dispatcherToWriteRef(opts.dispatcher));
34
+ await jr.start();
35
+ runners.push(jr);
36
+ }
37
+ return {
38
+ runners,
39
+ stop: async () => {
40
+ for (const runner of runners)
41
+ await runner.stop();
42
+ },
43
+ };
44
+ }
@@ -0,0 +1,13 @@
1
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
+ import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
3
+ export declare const SKIP_PII_BACKFILL_ENV = "KUMIKO_SKIP_PII_BACKFILL";
4
+ type ShutdownRegistry = {
5
+ registerShutdownHook(name: string, fn: () => Promise<void>): void;
6
+ };
7
+ export declare function startPiiEventBackfillOnBoot(opts: {
8
+ readonly db: DbConnection;
9
+ readonly registry: Registry;
10
+ readonly lifecycle: ShutdownRegistry;
11
+ readonly envSource: Readonly<Record<string, string | undefined>>;
12
+ }): void;
13
+ export {};
@@ -0,0 +1,25 @@
1
+ import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
2
+ import { runPiiEventBackfill } from "@cosmicdrift/kumiko-framework/migrations";
3
+ export const SKIP_PII_BACKFILL_ENV = "KUMIKO_SKIP_PII_BACKFILL";
4
+ // Runs in the background instead of blocking boot: the first run after a bump
5
+ // scans the whole store and would delay readiness in proportion to its size.
6
+ // Every batch is its own transaction, so an abort at any point is safe.
7
+ export function startPiiEventBackfillOnBoot(opts) {
8
+ if (opts.envSource[SKIP_PII_BACKFILL_ENV] === "1") {
9
+ // skip: operator opted out via KUMIKO_SKIP_PII_BACKFILL
10
+ return;
11
+ }
12
+ const log = createFallbackLogger("pii-event-backfill");
13
+ const abortController = new AbortController();
14
+ const run = runPiiEventBackfill(opts.db, opts.registry, {
15
+ signal: abortController.signal,
16
+ }).then(() => undefined, (e) => {
17
+ log.error("PII event backfill crashed; it will be retried on the next boot", {
18
+ error: e instanceof Error ? e.message : String(e),
19
+ });
20
+ });
21
+ opts.lifecycle.registerShutdownHook("piiEventBackfill", async () => {
22
+ abortController.abort();
23
+ await run;
24
+ });
25
+ }
@@ -0,0 +1,103 @@
1
+ export type BuildProdBundleOptions = {
2
+ /** App-Root. Default: process.cwd(). */
3
+ readonly cwd?: string;
4
+ /** Output-Folder relativ zu cwd. Default: "dist". */
5
+ readonly outDir?: string;
6
+ /** Stylesheet-Override. Default: erst src/styles.css, dann
7
+ * @cosmicdrift/kumiko-renderer-web/styles.css wenn clientEntry da ist.
8
+ * `false` deaktiviert die CSS-Pipeline explizit. */
9
+ readonly stylesheet?: string | false;
10
+ } & ClientEntriesConfig;
11
+ /** One declared client entry, read from package.json `kumiko.clientEntries`.
12
+ * `name` becomes the output filename (`client-<name>.js` / `<name>.html`
13
+ * unless `htmlPath` overrides it). */
14
+ export type ClientEntryDeclaration = {
15
+ readonly name: string;
16
+ readonly sourceFile: string;
17
+ readonly htmlPath?: string;
18
+ };
19
+ /** The two mutually exclusive shapes read from package.json `kumiko.*`.
20
+ * Neither set → convention mode (src/client.tsx | src/client.ts). */
21
+ export type ClientEntriesConfig = {
22
+ readonly clientEntry?: string;
23
+ readonly clientEntries?: readonly ClientEntryDeclaration[];
24
+ };
25
+ export type BuildManifest = Readonly<Record<string, string>>;
26
+ export type BuildResult = {
27
+ readonly outDir: string;
28
+ /** Logical → hashed-URL mapping. Beispiel:
29
+ * { "client.js": "/assets/client-a3f2.js",
30
+ * "styles.css": "/assets/styles-9b4c.css" } */
31
+ readonly manifest: BuildManifest;
32
+ /** Build-Identität (Hash über die Asset-URLs) + lesbarer Zeitstempel.
33
+ * undefined bei vanilla public-only-Builds (kein Bundle). */
34
+ readonly buildInfo?: BuildInfo;
35
+ };
36
+ /** In index.html gebacken (`window.__KUMIKO_BUILD__`) UND als
37
+ * dist/build-info.json geschrieben. Der UpdateChecker pollt build-info.json
38
+ * und vergleicht `id` gegen den geladenen Stand → Reload-Banner bei Drift. */
39
+ export type BuildInfo = {
40
+ /** Hash über die sortierten (content-gehashten) Asset-URLs. Ändert sich
41
+ * gdw. sich ein Asset ändert — selbsttragend, kein Env-Var/Dockerfile. */
42
+ readonly id: string;
43
+ /** ISO-Zeitstempel des Builds, lesbare Anzeige-Version (ersetzt den rohen sha). */
44
+ readonly builtAt: string;
45
+ };
46
+ /** Folder name relative to dist/ for hashed assets (JS, CSS, file-loader
47
+ * outputs). Exported damit runProdApp dieselbe Konvention für Cache-
48
+ * Header nutzt — Drift verhindern. */
49
+ export declare const ASSETS_DIR = "assets";
50
+ export declare function buildProdBundle(options?: BuildProdBundleOptions): Promise<BuildResult>;
51
+ export type ClientEntry = {
52
+ /** Logical name. "client" for single-entry mode (package.json
53
+ * `kumiko.clientEntry` or the src/client.tsx convention); otherwise the
54
+ * `name` declared in package.json `kumiko.clientEntries[]`. */
55
+ readonly name: string;
56
+ /** Absolute path to the TypeScript entry module. */
57
+ readonly sourceFile: string;
58
+ /** Manifest key & logical asset path. "client.js" for single-entry mode,
59
+ * otherwise "client-<name>.js". */
60
+ readonly manifestKey: string;
61
+ /** HTML template path — absolute when declared or discovered on disk,
62
+ * otherwise a bare default filename ("index.html", "<name>.html")
63
+ * resolved against cwd by the caller. Naming is deliberately symmetric
64
+ * to `runDevApp.clientEntries[].htmlPath` so build and dev server agree
65
+ * on the same convention. */
66
+ readonly htmlPath: string;
67
+ };
68
+ /** Resolves the client entries to build. Public — also called from
69
+ * `kumiko-build`/`kumiko build` via `@cosmicdrift/kumiko-dev-server/build`,
70
+ * not just from unit tests.
71
+ *
72
+ * Precedence (fw#2320 — explicit declaration, no filename inference):
73
+ * 1. `declared.clientEntry` and `declared.clientEntries` both set → throw.
74
+ * 2. `declared.clientEntries` (non-empty) → one entry per declaration.
75
+ * 3. `declared.clientEntry` → single entry, name "client".
76
+ * 4. Neither → convention: src/client.tsx | src/client.ts (name "client").
77
+ * If that convention finds nothing but src/ still has legacy
78
+ * `client-<suffix>.tsx` files, throws a migration error instead of
79
+ * silently shipping a build without their bundles. */
80
+ export declare function resolveClientEntries(cwd: string, declared: ClientEntriesConfig): readonly ClientEntry[];
81
+ /** Reads package.json → `kumiko.clientEntry` / `kumiko.clientEntries`.
82
+ * Missing file or missing keys → `{}` (falls back to convention mode). A
83
+ * malformed value under either key throws — a typo here must fail the
84
+ * build loudly instead of silently dropping declared entries. */
85
+ export declare function readClientEntriesConfig(cwd: string): ClientEntriesConfig;
86
+ type ResolvedStylesheet = {
87
+ readonly path: string;
88
+ /** True nur wenn wir auf das gepackte renderer-web-styles.css zurückfallen
89
+ * (App hat clientEntry + renderer-web-Dep, aber kein eigenes src/styles.css).
90
+ * Steuert den Shell-Klassen-Sentinel im Build. */
91
+ readonly isRendererWebFallback: boolean;
92
+ };
93
+ export declare function assertRendererWebShellPresent(css: string, stylesheet: ResolvedStylesheet): void;
94
+ export declare function discoverHtmlTemplate(cwd: string): string | undefined;
95
+ export declare function runTailwindOnce(entry: string, cwd: string): Promise<string>;
96
+ export declare function buildMissingTemplateError(manifest: BuildManifest, entry: ClientEntry): string;
97
+ export declare function injectAssetTags(html: string, manifest: BuildManifest, entry: ClientEntry, buildInfo?: BuildInfo): string;
98
+ export declare function computeBuildId(manifest: BuildManifest): string;
99
+ /** Formatiert ein BuildResult als CLI-freundliche Mehrzeilen-Zusammenfassung
100
+ * mit ANSI-Farben. Wird sowohl von `kumiko build` als auch von dem
101
+ * hoisted `kumiko-build`-Bin verwendet, damit das Output konsistent ist. */
102
+ export declare function formatBuildResult(result: BuildResult, durationMs: number): string;
103
+ export {};