@cosmicdrift/kumiko-dev-server 0.297.0 → 0.299.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.299.0",
|
|
4
4
|
"description": "Dev-tooling for Kumiko apps: local dev-server bootstrap (runDevApp), scaffolding, codegen. Its compose-stacks/env-schema subpaths are consumed at prod boot too — see @cosmicdrift/kumiko-server-runtime for the prod runner itself.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -59,10 +59,10 @@
|
|
|
59
59
|
"kumiko-upgrade": "./bin/kumiko-upgrade.ts"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
63
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
64
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
65
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
62
|
+
"@cosmicdrift/kumiko-bundled-features": "0.299.0",
|
|
63
|
+
"@cosmicdrift/kumiko-framework": "0.299.0",
|
|
64
|
+
"@cosmicdrift/kumiko-headless": "0.299.0",
|
|
65
|
+
"@cosmicdrift/kumiko-server-runtime": "0.299.0",
|
|
66
66
|
"ts-morph": "^28.0.0"
|
|
67
67
|
},
|
|
68
68
|
"publishConfig": {
|
|
@@ -2,18 +2,25 @@ import { afterEach, describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ExtraRouteRejection,
|
|
7
|
+
NO_ROUTE_MATCH_HEADER_NAME,
|
|
8
|
+
signatureRoute,
|
|
9
|
+
} from "@cosmicdrift/kumiko-framework/api";
|
|
6
10
|
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
7
11
|
import {
|
|
8
12
|
createBooleanField,
|
|
9
13
|
createEntity,
|
|
10
14
|
createTextField,
|
|
11
15
|
defineFeature,
|
|
16
|
+
type TenantId,
|
|
12
17
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
13
18
|
import { TestUsers } from "@cosmicdrift/kumiko-framework/stack";
|
|
14
19
|
import { z } from "zod";
|
|
15
20
|
import { createKumikoServer, type KumikoServerHandle } from "../create-kumiko-server";
|
|
16
21
|
|
|
22
|
+
const TENANT_ID = "00000000-0000-4000-8000-000000000001" as TenantId;
|
|
23
|
+
|
|
17
24
|
// Integration-Test: bootet createKumikoServer mit echtem Postgres,
|
|
18
25
|
// echtem Redis, echter Kumiko-Pipeline. Treibt den fetch-Handler
|
|
19
26
|
// direkt an (nicht über Bun.serve + Socket), weil vitest unter Node
|
|
@@ -30,8 +37,16 @@ const probeEntity = createEntity({
|
|
|
30
37
|
|
|
31
38
|
const probeFeature = defineFeature("dev-server-probe", (r) => {
|
|
32
39
|
r.entity("probe", probeEntity);
|
|
33
|
-
//
|
|
34
|
-
//
|
|
40
|
+
// Anonymous query for hostDispatch's deps.systemQuery — proves the
|
|
41
|
+
// dev path gets the same anonymous-role dispatch-deps shape as prod.
|
|
42
|
+
r.queryHandler({
|
|
43
|
+
name: "ping",
|
|
44
|
+
schema: z.object({}),
|
|
45
|
+
access: { roles: ["anonymous"] },
|
|
46
|
+
handler: async () => ({ pong: true }),
|
|
47
|
+
});
|
|
48
|
+
// SystemAdmin-gated write — Ziel der entry:"signature" / `wire`-Tests
|
|
49
|
+
// (252/2): Echo von user.tenantId + roles beweist Dispatch durch
|
|
35
50
|
// den echten Dispatcher (Zod + Access-Check) mit Ziel-Tenant-SystemUser.
|
|
36
51
|
r.writeHandler({
|
|
37
52
|
name: "probe-write",
|
|
@@ -320,35 +335,77 @@ describe("createKumikoServer (Multi-Entry)", () => {
|
|
|
320
335
|
);
|
|
321
336
|
expect(res.status).toBe(404);
|
|
322
337
|
});
|
|
338
|
+
|
|
339
|
+
test("hostDispatch: async fn erhält deps.systemQuery, 'not-found' → 404 (kumiko-framework#3050 dev-Pendant)", async () => {
|
|
340
|
+
tmpDir = mkdtempSync(join(tmpdir(), "kumiko-multi-it-"));
|
|
341
|
+
const publicEntry = join(tmpDir, "client-public.tsx");
|
|
342
|
+
writeFileSync(publicEntry, "// public");
|
|
343
|
+
let systemQueryPonged = false;
|
|
344
|
+
|
|
345
|
+
handle = await createKumikoServer({
|
|
346
|
+
features: [probeFeature],
|
|
347
|
+
port: 0,
|
|
348
|
+
installSignalHandlers: false,
|
|
349
|
+
clientEntries: [{ name: "public", sourceFile: publicEntry }],
|
|
350
|
+
_buildBundle: async () => ({ js: "// PUBLIC-BUNDLE", map: "" }),
|
|
351
|
+
hostDispatch: async (_req, deps) => {
|
|
352
|
+
const result = await deps.systemQuery("dev-server-probe:query:ping", {}, TENANT_ID);
|
|
353
|
+
systemQueryPonged = (result as { pong: boolean }).pong === true; // @cast-boundary engine-payload
|
|
354
|
+
return { kind: "not-found" };
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const res = await handle.fetch(new Request("http://localhost/"));
|
|
359
|
+
expect(res.status).toBe(404);
|
|
360
|
+
expect(systemQueryPonged).toBe(true);
|
|
361
|
+
});
|
|
323
362
|
});
|
|
324
363
|
|
|
325
|
-
// 252/2:
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
|
|
364
|
+
// 252/2: the dev path shares extra-routes-deps.ts with runProdApp —
|
|
365
|
+
// here the analogous proof against createKumikoServer, with `wire` for the
|
|
366
|
+
// registry/dispatchSystemWrite check and an entry:"signature" route for
|
|
367
|
+
// the webhook dispatch.
|
|
368
|
+
describe("createKumikoServer extraRoutes / wire deps", () => {
|
|
369
|
+
test("wire: dispatchSystemWrite schreibt als SystemAdmin des Ziel-Tenants, registry verfügbar", async () => {
|
|
330
370
|
const tenantId = "00000000-0000-4000-8000-000000000042";
|
|
331
371
|
let registryHasProbe = false;
|
|
332
372
|
handle = await createKumikoServer({
|
|
333
373
|
features: [probeFeature],
|
|
334
374
|
port: 0,
|
|
335
375
|
installSignalHandlers: false,
|
|
336
|
-
extraRoutes:
|
|
376
|
+
extraRoutes: [
|
|
377
|
+
signatureRoute({
|
|
378
|
+
method: "POST",
|
|
379
|
+
path: "/webhook-probe",
|
|
380
|
+
entry: "signature",
|
|
381
|
+
verify: async (req) => {
|
|
382
|
+
if (req.headers["x-probe-signature"] !== "test-secret") {
|
|
383
|
+
throw new ExtraRouteRejection(401, { error: "bad-signature" });
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
handler: async (c, _verified, deps) => {
|
|
387
|
+
const result = await deps.dispatchSystemWrite({
|
|
388
|
+
handlerQn: "dev-server-probe:write:probe-write",
|
|
389
|
+
payload: { note: "from-webhook" },
|
|
390
|
+
tenantId: tenantId as import("@cosmicdrift/kumiko-framework/engine").TenantId,
|
|
391
|
+
});
|
|
392
|
+
return c.json(result);
|
|
393
|
+
},
|
|
394
|
+
}),
|
|
395
|
+
],
|
|
396
|
+
wire: (deps) => {
|
|
337
397
|
registryHasProbe = deps.registry.features.has("dev-server-probe");
|
|
338
|
-
app.post("/webhook-probe", async (c) => {
|
|
339
|
-
const result = await deps.dispatchSystemWrite({
|
|
340
|
-
handlerQn: "dev-server-probe:write:probe-write",
|
|
341
|
-
payload: { note: "from-webhook" },
|
|
342
|
-
tenantId: tenantId as import("@cosmicdrift/kumiko-framework/engine").TenantId,
|
|
343
|
-
});
|
|
344
|
-
return c.json(result);
|
|
345
|
-
});
|
|
346
398
|
},
|
|
347
399
|
});
|
|
348
400
|
|
|
349
401
|
expect(registryHasProbe).toBe(true);
|
|
350
402
|
|
|
351
|
-
const res = await handle.fetch(
|
|
403
|
+
const res = await handle.fetch(
|
|
404
|
+
new Request("http://test/webhook-probe", {
|
|
405
|
+
method: "POST",
|
|
406
|
+
headers: { "x-probe-signature": "test-secret" },
|
|
407
|
+
}),
|
|
408
|
+
);
|
|
352
409
|
expect(res.status).toBe(200);
|
|
353
410
|
const body = (await res.json()) as {
|
|
354
411
|
isSuccess: boolean;
|
|
@@ -369,9 +426,14 @@ describe("createKumikoServer — extraRoutes vs. onAfterSetup ordering", () => {
|
|
|
369
426
|
features: [probeFeature],
|
|
370
427
|
port: 0,
|
|
371
428
|
installSignalHandlers: false,
|
|
372
|
-
extraRoutes:
|
|
373
|
-
|
|
374
|
-
|
|
429
|
+
extraRoutes: [
|
|
430
|
+
{
|
|
431
|
+
method: "GET",
|
|
432
|
+
path: "/probe",
|
|
433
|
+
entry: "anonymous",
|
|
434
|
+
handler: (c) => c.json({ ok: true }),
|
|
435
|
+
},
|
|
436
|
+
],
|
|
375
437
|
onAfterSetup: async (stack) => {
|
|
376
438
|
await stack.http.writeOk(
|
|
377
439
|
"dev-server-probe:write:probe-write",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// scaffoldApp unit-tests (DX-1.0 + #352 deploy/schema scaffold).
|
|
2
2
|
|
|
3
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { type ScaffoldTestSetup, scaffoldApp } from "../scaffold-app";
|
|
@@ -380,12 +380,13 @@ describe("scaffoldApp", () => {
|
|
|
380
380
|
expect(main).toContain("...kmsWiring,");
|
|
381
381
|
});
|
|
382
382
|
|
|
383
|
-
test("src/run-config.ts mounts secrets + sessions + tasks + HAS_AUTH", async () => {
|
|
383
|
+
test("src/run-config.ts mounts secrets + auth-foundation + sessions + tasks + HAS_AUTH", async () => {
|
|
384
384
|
const dest = join(tmp, "my-shop");
|
|
385
385
|
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
386
386
|
|
|
387
387
|
const runConfig = readFileSync(join(dest, "src/run-config.ts"), "utf-8");
|
|
388
388
|
expect(runConfig).toContain("createSecretsFeature()");
|
|
389
|
+
expect(runConfig).toContain("authFoundationFeature");
|
|
389
390
|
expect(runConfig).toContain("createSessionsFeature()");
|
|
390
391
|
expect(runConfig).toContain("tasksFeature");
|
|
391
392
|
expect(runConfig).toContain('from "./features/tasks"');
|
|
@@ -562,3 +563,56 @@ describe("scaffoldApp", () => {
|
|
|
562
563
|
expect(uuidA).toBe(uuidB);
|
|
563
564
|
});
|
|
564
565
|
});
|
|
566
|
+
|
|
567
|
+
// File-content assertions on run-config.ts can't catch a missing sessionStore
|
|
568
|
+
// provider — only actually booting the generated bin/main.ts can.
|
|
569
|
+
describe("generated default app actually boots (kumiko-framework#3120)", () => {
|
|
570
|
+
const FIXTURE_ROOT = join(import.meta.dir, ".tmp-fixtures");
|
|
571
|
+
const createdDirs: string[] = [];
|
|
572
|
+
|
|
573
|
+
afterAll(() => {
|
|
574
|
+
for (const d of createdDirs) {
|
|
575
|
+
try {
|
|
576
|
+
rmSync(d, { recursive: true, force: true });
|
|
577
|
+
} catch {
|
|
578
|
+
// ignore — best-effort
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
test("KUMIKO_DRY_RUN_ENV=boot bun bin/main.ts exits 0 against the default scaffold", async () => {
|
|
584
|
+
mkdirSync(FIXTURE_ROOT, { recursive: true });
|
|
585
|
+
const cwd = mkdtempSync(join(FIXTURE_ROOT, "boot-"));
|
|
586
|
+
createdDirs.push(cwd);
|
|
587
|
+
const dest = join(cwd, "boot-fixture");
|
|
588
|
+
|
|
589
|
+
await scaffoldApp({ name: "boot-fixture", destination: dest });
|
|
590
|
+
|
|
591
|
+
const proc = Bun.spawn({
|
|
592
|
+
cmd: ["bun", "bin/main.ts"],
|
|
593
|
+
cwd: dest,
|
|
594
|
+
env: {
|
|
595
|
+
...process.env,
|
|
596
|
+
KUMIKO_DRY_RUN_ENV: "boot",
|
|
597
|
+
JWT_SECRET: "a".repeat(32),
|
|
598
|
+
KUMIKO_SECRETS_MASTER_KEY_V1: Buffer.alloc(32, 7).toString("base64"),
|
|
599
|
+
DATABASE_URL: "postgres://dummy:dummy@127.0.0.1:1/dummy",
|
|
600
|
+
REDIS_URL: "redis://127.0.0.1:1",
|
|
601
|
+
PLATFORM_KEK: "",
|
|
602
|
+
SUBJECT_KEYS_DATABASE_URL: "",
|
|
603
|
+
KUMIKO_BLIND_INDEX_KEY: "",
|
|
604
|
+
},
|
|
605
|
+
stdout: "pipe",
|
|
606
|
+
stderr: "pipe",
|
|
607
|
+
});
|
|
608
|
+
const [stdout, stderr, code] = await Promise.all([
|
|
609
|
+
new Response(proc.stdout).text(),
|
|
610
|
+
new Response(proc.stderr).text(),
|
|
611
|
+
proc.exited,
|
|
612
|
+
]);
|
|
613
|
+
|
|
614
|
+
expect(code, `stdout:\n${stdout}\nstderr:\n${stderr}`).toBe(0);
|
|
615
|
+
expect(`${stdout}${stderr}`).not.toContain("BOOT ABORTED");
|
|
616
|
+
expect(stdout).toContain("boot validation OK");
|
|
617
|
+
});
|
|
618
|
+
});
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.299.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Scaffolded docker-compose.yml uses the correct Postgres 18 data path; default app mounts auth-foundation so it boots",
|
|
6
|
+
"migration": "No action needed for existing apps; only newly scaffolded apps are affected."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.298.0",
|
|
10
|
+
"type": "breaking",
|
|
11
|
+
"title": "createKumikoServer/runDevApp take ExtraRouteDefinition[]; dev hostDispatch gets systemQuery",
|
|
12
|
+
"migration": "extraRoutes on createKumikoServer/runDevApp changes from (app, deps) => void to readonly ExtraRouteDefinition[] - see the framework core changelog entry for the route-kind/dep breakdown. wire?: (deps: SystemWireDeps) => void | Promise<void> replaces non-route setup previously done inside the old extraRoutes callback. The dev hostDispatch callback now receives a second argument { systemQuery }; a dispatch implementation reading the dev db directly switches to systemQuery."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.291.0",
|
|
4
16
|
"type": "fix",
|
|
@@ -23,6 +23,7 @@ import { resolveAnonymousAccessFromRegistry } from "@cosmicdrift/kumiko-bundled-
|
|
|
23
23
|
import {
|
|
24
24
|
type AuthRoutesConfig,
|
|
25
25
|
buildRequestContextDataFromRequest,
|
|
26
|
+
type ExtraRouteDefinition,
|
|
26
27
|
generateToken,
|
|
27
28
|
requestContext,
|
|
28
29
|
} from "@cosmicdrift/kumiko-framework/api";
|
|
@@ -47,8 +48,8 @@ import {
|
|
|
47
48
|
import { startDevJobRunners } from "@cosmicdrift/kumiko-server-runtime/boot/job-run-logger";
|
|
48
49
|
import { buildBunServeOptions } from "@cosmicdrift/kumiko-server-runtime/bun-serve-options";
|
|
49
50
|
import {
|
|
50
|
-
type ExtraRoutesSystemDeps,
|
|
51
51
|
makeDispatchSystemWrite,
|
|
52
|
+
type SystemWireDeps,
|
|
52
53
|
} from "@cosmicdrift/kumiko-server-runtime/extra-routes-deps";
|
|
53
54
|
import { injectSchema } from "@cosmicdrift/kumiko-server-runtime/inject-schema";
|
|
54
55
|
import {
|
|
@@ -127,7 +128,10 @@ export type DevHostDispatchResult =
|
|
|
127
128
|
|
|
128
129
|
/** Picks an entry by inspecting the incoming request. Wird von
|
|
129
130
|
* Multi-Entry-Apps gesetzt; im Single-Entry-Mode irrelevant. */
|
|
130
|
-
export type DevHostDispatch = (
|
|
131
|
+
export type DevHostDispatch = (
|
|
132
|
+
req: Request,
|
|
133
|
+
deps: { readonly systemQuery: PageHeadSystemQuery },
|
|
134
|
+
) => DevHostDispatchResult | Promise<DevHostDispatchResult>;
|
|
131
135
|
|
|
132
136
|
export type CreateKumikoServerOptions = {
|
|
133
137
|
/** Features whose entities, handlers, and screens get wired into the
|
|
@@ -217,12 +221,15 @@ export type CreateKumikoServerOptions = {
|
|
|
217
221
|
* tenant, …). Muss idempotent sein — im persistent-DB-Modus läuft
|
|
218
222
|
* es bei jedem Boot. */
|
|
219
223
|
readonly onAfterSetup?: (stack: TestStack) => Promise<void>;
|
|
220
|
-
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* /og-image, …)
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
/** Declarative HTTP-routes outside the /api/write|query|batch pipeline —
|
|
225
|
+
* symmetric to runProdApp.extraRoutes (kumiko-framework#3050). Mounted
|
|
226
|
+
* inside buildServer (setupTestStack), before the Static/HTML-fallback,
|
|
227
|
+
* so an own GET (/feed.xml, /og-image, …) wins over the dev-asset path. */
|
|
228
|
+
readonly extraRoutes?: readonly ExtraRouteDefinition[];
|
|
229
|
+
/** Hook for app-wired co-running components that need the system-write
|
|
230
|
+
* dispatcher — runs after buildServer, before onAfterSetup (seeds), with
|
|
231
|
+
* NO `app` (routes are declared via `extraRoutes`, not wired here). */
|
|
232
|
+
readonly wire?: (deps: SystemWireDeps) => void | Promise<void>;
|
|
226
233
|
/** Per-request head-metadata resolver (Open-Graph/title/description) —
|
|
227
234
|
* same option, same `PageHeadResolver` signature, and same shared
|
|
228
235
|
* resolve+inject call (`resolveAndInjectPageHead`) as `runProdApp`'s
|
|
@@ -830,6 +837,7 @@ export async function createKumikoServer(
|
|
|
830
837
|
...(options.effectiveFeatures !== undefined && {
|
|
831
838
|
effectiveFeatures: options.effectiveFeatures,
|
|
832
839
|
}),
|
|
840
|
+
...(options.extraRoutes !== undefined && { extraRoutes: options.extraRoutes }),
|
|
833
841
|
// jobs: {} = enqueuer-only; startDevJobRunners below is the sole
|
|
834
842
|
// consumer/cron-scheduler per lane, so runOnBoot/cron jobs don't double-fire.
|
|
835
843
|
jobs: {},
|
|
@@ -837,16 +845,12 @@ export async function createKumikoServer(
|
|
|
837
845
|
await createEventsTable(stack.db);
|
|
838
846
|
await pushEntityProjectionTables(stack, stack.registry);
|
|
839
847
|
|
|
840
|
-
//
|
|
841
|
-
//
|
|
842
|
-
//
|
|
843
|
-
//
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
// baut Honos Matcher — danach wirft jedes weitere app.get() mit
|
|
847
|
-
// "Can not add a route since the matcher is already built".
|
|
848
|
-
if (options.extraRoutes !== undefined) {
|
|
849
|
-
options.extraRoutes(stack.app, {
|
|
848
|
+
// `extraRoutes` is already mounted — setupTestStack's buildServer() call
|
|
849
|
+
// wired it above, before this function ever runs. `wire` is the
|
|
850
|
+
// remaining escape-hatch for app-wired co-running components that need
|
|
851
|
+
// the system-write dispatcher but don't declare a route.
|
|
852
|
+
if (options.wire !== undefined) {
|
|
853
|
+
await options.wire({
|
|
850
854
|
db: stack.db,
|
|
851
855
|
// Der nackte ioredis-Client (nicht der TestRedis-Wrapper) —
|
|
852
856
|
// Parität mit runProdApp, App-Code soll in dev+prod dasselbe sehen.
|
|
@@ -861,8 +865,6 @@ export async function createKumikoServer(
|
|
|
861
865
|
// zugreifen kann, und VOR dem Server-Start damit der erste HTTP-Request
|
|
862
866
|
// bereits gegen einen gefüllten State läuft. Idempotenz ist Caller-
|
|
863
867
|
// Verantwortung (persistent-DB-Modus läuft es bei jedem Boot).
|
|
864
|
-
// Dispatching through stack.http below builds Hono's matcher — no route
|
|
865
|
-
// may be registered after this point (see extraRoutes above).
|
|
866
868
|
if (options.onAfterSetup !== undefined) {
|
|
867
869
|
await options.onAfterSetup(stack);
|
|
868
870
|
}
|
|
@@ -943,6 +945,15 @@ export async function createKumikoServer(
|
|
|
943
945
|
// ist es immer "client" mit Schema-Inject true (Single-Entry-Default
|
|
944
946
|
// damit der Client TypeScript-Schemas findet).
|
|
945
947
|
//
|
|
948
|
+
// Anonymous-role systemQuery, shared by resolvePageHead and hostDispatch —
|
|
949
|
+
// mirrors runProdApp's HostDispatchFn deps so dev/prod stay symmetric.
|
|
950
|
+
const buildDevSystemQuery =
|
|
951
|
+
(req: Request): PageHeadSystemQuery =>
|
|
952
|
+
(type, payload, tenantId) =>
|
|
953
|
+
requestContext.run(requestContext.get() ?? buildRequestContextDataFromRequest(req), () =>
|
|
954
|
+
stack.dispatcher.query(type, payload, createAnonymousUser(tenantId)),
|
|
955
|
+
);
|
|
956
|
+
|
|
946
957
|
// resolvePageHead goes through the same headless resolveAndInjectPageHead
|
|
947
958
|
// that runProdApp uses, so dev and e2e exercise the one timeout and
|
|
948
959
|
// fallback path instead of a second copy of it (#3026).
|
|
@@ -966,14 +977,10 @@ export async function createKumikoServer(
|
|
|
966
977
|
if (options.resolvePageHead !== undefined) {
|
|
967
978
|
const url = new URL(req.url);
|
|
968
979
|
const host = req.headers.get("host") ?? url.host;
|
|
969
|
-
const systemQuery: PageHeadSystemQuery = (type, payload, tenantId) =>
|
|
970
|
-
requestContext.run(requestContext.get() ?? buildRequestContextDataFromRequest(req), () =>
|
|
971
|
-
stack.dispatcher.query(type, payload, createAnonymousUser(tenantId)),
|
|
972
|
-
);
|
|
973
980
|
html = await resolveAndInjectPageHead(html, options.resolvePageHead, {
|
|
974
981
|
path: url.pathname,
|
|
975
982
|
host,
|
|
976
|
-
systemQuery,
|
|
983
|
+
systemQuery: buildDevSystemQuery(req),
|
|
977
984
|
});
|
|
978
985
|
}
|
|
979
986
|
return new Response(html, { headers });
|
|
@@ -1085,7 +1092,7 @@ export async function createKumikoServer(
|
|
|
1085
1092
|
// Discriminated-Dispatch — symmetric zu prod. Ohne hostDispatch
|
|
1086
1093
|
// landet das im Single-Entry-Default ("client" + Schema-Inject).
|
|
1087
1094
|
if (options.hostDispatch !== undefined) {
|
|
1088
|
-
const dispatch = options.hostDispatch(req);
|
|
1095
|
+
const dispatch = await options.hostDispatch(req, { systemQuery: buildDevSystemQuery(req) });
|
|
1089
1096
|
if (dispatch.kind === "redirect") {
|
|
1090
1097
|
return new Response(null, {
|
|
1091
1098
|
status: dispatch.status ?? 302,
|
package/src/run-dev-app.ts
CHANGED
|
@@ -251,6 +251,9 @@ export type RunDevAppOptions = {
|
|
|
251
251
|
* Hono-app gehängt, läuft VOR dem static-asset-Pfad. Symmetrisch zur
|
|
252
252
|
* gleichnamigen Option in runProdApp. */
|
|
253
253
|
readonly extraRoutes?: CreateKumikoServerOptions["extraRoutes"];
|
|
254
|
+
/** Hook for app-wired co-running components — symmetric to
|
|
255
|
+
* runProdApp.wire (kumiko-framework#3050). */
|
|
256
|
+
readonly wire?: CreateKumikoServerOptions["wire"];
|
|
254
257
|
/** Feature-toggle resolver — durchgereicht an createKumikoServer →
|
|
255
258
|
* setupTestStack. Sprint-8 Tier-Composition: per-Tenant unterschied-
|
|
256
259
|
* liche features aktiv via globalFeatureToggleRuntime. Pattern in
|
|
@@ -486,6 +489,7 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
|
|
|
486
489
|
...(options.anonymousAccess !== undefined && { anonymousAccess: options.anonymousAccess }),
|
|
487
490
|
...(options.files !== undefined && { files: options.files }),
|
|
488
491
|
...(options.extraRoutes !== undefined && { extraRoutes: options.extraRoutes }),
|
|
492
|
+
...(options.wire !== undefined && { wire: options.wire }),
|
|
489
493
|
...(finalEffectiveFeatures !== undefined && {
|
|
490
494
|
effectiveFeatures: finalEffectiveFeatures,
|
|
491
495
|
}),
|
package/src/scaffold-app.ts
CHANGED
|
@@ -72,7 +72,7 @@ export type ScaffoldAppOptions = {
|
|
|
72
72
|
readonly cwd?: string;
|
|
73
73
|
/** npm-version-pin for @cosmicdrift/* deps. Default "*" for latest. */
|
|
74
74
|
readonly frameworkVersion?: string;
|
|
75
|
-
/** Bundled-features to mount in run-config.ts. Default: secrets + sessions
|
|
75
|
+
/** Bundled-features to mount in run-config.ts. Default: secrets + auth-foundation + sessions
|
|
76
76
|
* (the historical foundation). create-kumiko-app passes the picker output
|
|
77
77
|
* here so the generated APP_FEATURES reflects the user's selection. */
|
|
78
78
|
readonly features?: ReadonlyArray<ScaffoldFeatureEntry>;
|
|
@@ -386,6 +386,13 @@ const FOUNDATION_FEATURES: ReadonlyArray<ScaffoldFeatureEntry> = [
|
|
|
386
386
|
exportName: "createSecretsFeature",
|
|
387
387
|
callExpression: "createSecretsFeature()",
|
|
388
388
|
},
|
|
389
|
+
// sessions' `sessionStore` extension requires auth-foundation to be mounted.
|
|
390
|
+
{
|
|
391
|
+
name: "auth-foundation",
|
|
392
|
+
importPath: "@cosmicdrift/kumiko-bundled-features/auth-foundation",
|
|
393
|
+
exportName: "authFoundationFeature",
|
|
394
|
+
callExpression: "authFoundationFeature",
|
|
395
|
+
},
|
|
389
396
|
{
|
|
390
397
|
name: "sessions",
|
|
391
398
|
importPath: "@cosmicdrift/kumiko-bundled-features/sessions",
|
|
@@ -790,7 +797,7 @@ services:
|
|
|
790
797
|
ports:
|
|
791
798
|
- "127.0.0.1:5432:5432"
|
|
792
799
|
volumes:
|
|
793
|
-
- kumiko-pg:/var/lib/postgresql
|
|
800
|
+
- kumiko-pg:/var/lib/postgresql
|
|
794
801
|
redis:
|
|
795
802
|
image: redis:7
|
|
796
803
|
ports:
|