@cosmicdrift/kumiko-dev-server 0.2.3 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # @cosmicdrift/kumiko-dev-server
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0.3.0 bringt zwei neue Subsysteme (Step-Engine Tier-3 + Visual-Tree) plus
8
+ eine AST-Codemod-Pipeline als Vorarbeit für den L2-AI-Layer.
9
+
10
+ ### Breaking Changes
11
+
12
+ - `skipTransitionGuard` → `unsafeSkipTransitionGuard` (Rename in
13
+ feature-ast + engine). Der `unsafe`-Prefix macht die Tragweite des
14
+ Casts sichtbar und ist konsistent zur `unsafeProjectionUpsert`- und
15
+ `r.rawTable`-Konvention. Migration: 1:1-Ersetzung, keine Verhaltens-Änderung.
16
+
17
+ ### Features
18
+
19
+ - **Step-Engine M.4 — Tier-3 Workflow-Engine.** Neue Step-Vocabulary
20
+ `wait`, `waitForEvent`, `retry` ermöglicht persistierte Long-Running-Flows
21
+ über Job-Boundaries hinweg. Q7 Snapshot-at-Start hängt jedem Step-Run
22
+ einen SHA-256-Fingerprint des Aggregat-Zustands an, sodass Replays
23
+ deterministisch gegen den ursprünglichen Eingangszustand laufen.
24
+ - **Visual-Tree V.1.x — Tree-API + Editor-Panel.** Neue `VisualTree`-
25
+ Component plus TreeProvider-Pattern; erste TreeProviders für
26
+ `text-content` und `legal-pages` (CMS-light + Impressum/Privacy).
27
+ Fundament für den späteren No-Code-Designer (~3000 LOC, 98 Tests).
28
+ - **Codemod-Pipeline.** AST-basierte Patcher-Module für strukturelle
29
+ Feature-Edits — wird vom kommenden L2-AI-Layer als Tool-Surface
30
+ verwendet, ist aber eigenständig nutzbar für ts-morph-style Migrationen.
31
+ - **user-data-rights Sample-Recipe.** DSGVO Art. 15/17/18/20 vollständig
32
+ als Sample-Recipe (`samples/recipes/`) inklusive README — zeigt die
33
+ Export- und Forget-Pipeline gegen den `compliance-profiles`-Default
34
+ (`eu-dsgvo`).
35
+
36
+ ### Fixes
37
+
38
+ - `tier-engine`: auto-default-tier-Hook benutzt jetzt `ctx.db.raw` für
39
+ Event-Store-Operationen (#37, vorher: stiller Bug, 22 Tage live).
40
+ - `engine`: unsafe-projection-upsert nutzt `as never` statt `as any` —
41
+ schmaler Cast-Surface, weniger Compiler-Knebel.
42
+ - `visual-tree`: runtime-isolation marker für client-konsumierte Files,
43
+ damit der Multi-Entry-Build den richtigen Bundle-Split bekommt.
44
+ - `feature-ast`: vollständiger `unsafeSkipTransitionGuard`-Rename (war
45
+ in zwei Modulen noch der alte Name).
46
+ - `framework`: Error-Reasons + `noConsole`-Lint + No-Date-API-Guard
47
+ wieder push-ready.
48
+
49
+ ### Library-Updates
50
+
51
+ hono 4.12, jose 6.2, stripe 22.1, meilisearch 0.58, marked 18,
52
+ bun-types 1.3.13, lucide-react 1.14, bullmq 5.76, ioredis 5.10,
53
+ i18next 26.0, react + radix-ui-primitives auf aktuelle Minors.
54
+
55
+ ### Patch Changes
56
+
57
+ - Updated dependencies
58
+ - @cosmicdrift/kumiko-framework@0.3.0
59
+ - @cosmicdrift/kumiko-bundled-features@0.3.0
60
+
3
61
  ## 0.2.3
4
62
 
5
63
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-dev-server",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Development server bootstrap for Kumiko apps. Bundles the client, mints dev-JWTs, injects the resolved AppSchema, and seeds an admin. Not for production.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -18,20 +18,38 @@
18
18
  "runtime": "dev"
19
19
  },
20
20
  "exports": {
21
- ".": "./src/index.ts",
22
- "./build": "./src/build.ts",
23
- "./compose-features": "./src/compose-features.ts",
24
- "./drizzle-config": "./src/drizzle-config.ts",
25
- "./drizzle-tables-auth-mode": "./src/drizzle-tables-auth-mode.ts",
26
- "./drizzle-tables-minimal": "./src/drizzle-tables-minimal.ts"
21
+ ".": {
22
+ "types": "./src/index.ts",
23
+ "default": "./src/index.ts"
24
+ },
25
+ "./build": {
26
+ "types": "./src/build.ts",
27
+ "default": "./src/build.ts"
28
+ },
29
+ "./compose-features": {
30
+ "types": "./src/compose-features.ts",
31
+ "default": "./src/compose-features.ts"
32
+ },
33
+ "./drizzle-config": {
34
+ "types": "./src/drizzle-config.ts",
35
+ "default": "./src/drizzle-config.ts"
36
+ },
37
+ "./drizzle-tables-auth-mode": {
38
+ "types": "./src/drizzle-tables-auth-mode.ts",
39
+ "default": "./src/drizzle-tables-auth-mode.ts"
40
+ },
41
+ "./drizzle-tables-minimal": {
42
+ "types": "./src/drizzle-tables-minimal.ts",
43
+ "default": "./src/drizzle-tables-minimal.ts"
44
+ }
27
45
  },
28
46
  "bin": {
29
47
  "kumiko-build": "./bin/kumiko-build.ts",
30
48
  "kumiko-dev": "./bin/kumiko-dev.ts"
31
49
  },
32
50
  "dependencies": {
33
- "@cosmicdrift/kumiko-bundled-features": "0.2.3",
34
- "@cosmicdrift/kumiko-framework": "0.2.3"
51
+ "@cosmicdrift/kumiko-bundled-features": "0.3.0",
52
+ "@cosmicdrift/kumiko-framework": "0.3.0"
35
53
  },
36
54
  "publishConfig": {
37
55
  "registry": "https://registry.npmjs.org",
@@ -1,39 +1,12 @@
1
- // Regression-Guard für die EIGENTLICHE Behauptung der Codegen-Pipeline:
2
- // `ctx.appendEvent` wird via Lokal-Wrapper STRICT typgeprüft.
3
- //
4
- // Ohne diesen Test verifizieren die anderen 12 Codegen-Tests nur, dass
5
- // die richtigen Strings ins File geschrieben werden. Wenn jemand später
6
- // das `export *`-Shadowing kaputt macht, den `KumikoEventTypeMap`-
7
- // Re-Export aus `engine/index.ts` entfernt, oder TS-Verhalten in einer
8
- // neuen Version subtil bricht — die anderen Tests bleiben grün und der
9
- // strict-mode stirbt schweigend. Genau das wäre der Fall den dieser
10
- // Test fängt.
11
- //
12
- // Ablauf pro Test-Case:
13
- // 1. tmp-App mit feature.ts + events.ts + bin/main.ts erzeugen.
14
- // 2. `runCodegen` auf die tmp-App fahren — schreibt
15
- // `.kumiko/types.generated.d.ts` + `define.ts`.
16
- // 3. Eine synthetische Test-Datei mit den gewünschten Aufrufen schreiben.
17
- // 4. ts.createProgram über die App + paths-alias zur framework-source.
18
- // 5. Diagnostics auswerten — auf konkrete TS-Codes prüfen.
19
- //
20
- // `paths` zeigt direkt auf die framework-source (`packages/framework/src`),
21
- // damit der TS-Type-Checker die Augmentation als Teil DESSELBEN Compiles
22
- // sieht (Use-Site-Substitution funktioniert nur so — siehe
23
- // project_x1_typemap_findings memory).
24
-
25
1
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
26
2
  import { dirname, join } from "node:path";
27
3
  import * as ts from "typescript";
28
- import { afterAll, describe, expect, test } from "vitest";
4
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
29
5
  import { runCodegen } from "../run-codegen";
30
6
 
31
7
  const REPO_ROOT = join(__dirname, "../../../../..");
32
8
  const FRAMEWORK_SRC = join(REPO_ROOT, "packages/framework/src");
33
9
 
34
- // Test-Apps werden IM Repo-Tree angelegt (gitignored), damit Node's
35
- // natürliches `node_modules`-Hochsuchen 'zod' & Co finden kann. tmpdir
36
- // liegt außerhalb des Repo-Trees → keine node_modules-Sicht.
37
10
  const TEST_FIXTURE_DIR = join(__dirname, ".tmp-fixtures");
38
11
  const createdDirs: string[] = [];
39
12
 
@@ -48,15 +21,11 @@ afterAll(() => {
48
21
  for (const d of createdDirs) {
49
22
  try {
50
23
  rmSync(d, { recursive: true, force: true });
51
- } catch {
52
- // best-effort cleanup
53
- }
24
+ } catch {}
54
25
  }
55
26
  try {
56
27
  rmSync(TEST_FIXTURE_DIR, { recursive: true, force: true });
57
- } catch {
58
- // ditto
59
- }
28
+ } catch {}
60
29
  });
61
30
 
62
31
  function write(dir: string, relPath: string, content: string): string {
@@ -66,16 +35,7 @@ function write(dir: string, relPath: string, content: string): string {
66
35
  return full;
67
36
  }
68
37
 
69
- /**
70
- * Baut ein TS-Program über die App + framework-source, gibt die
71
- * semantischen Diagnostics zurück. Lib-files werden vom installierten
72
- * typescript-Package geholt; sonst meckert TS über fehlende DOM-types.
73
- */
74
38
  function compileApp(appRoot: string): readonly ts.Diagnostic[] {
75
- // Wir lassen ts node_modules vom REPO_ROOT auflösen (tmp-Dir hat kein
76
- // eigenes node_modules). `baseUrl` zeigt auf repo, `paths` mappt
77
- // framework + tmp-app explizit; rest fällt auf node_modules-Lookup
78
- // im repo-Tree zurück.
79
39
  const compilerOptions: ts.CompilerOptions = {
80
40
  target: ts.ScriptTarget.ESNext,
81
41
  module: ts.ModuleKind.ESNext,
@@ -91,8 +51,6 @@ function compileApp(appRoot: string): readonly ts.Diagnostic[] {
91
51
  types: [],
92
52
  };
93
53
 
94
- // Sammle alle .ts-Files unter src/ + .kumiko/, plus die framework-
95
- // source-tree die wir via paths erreichen wollen.
96
54
  const program = ts.createProgram({
97
55
  rootNames: collectFiles(appRoot),
98
56
  options: compilerOptions,
@@ -125,140 +83,214 @@ function collectFiles(dir: string): string[] {
125
83
  return out;
126
84
  }
127
85
 
128
- // Default-shape feature: setup callback registers `placed` and returns
129
- // nothing. Used by tests that exercise the standard `ctx.appendEvent({
130
- // type: "orders:event:placed", ... })` literal-string path.
131
- function setupApp(): string {
132
- const appRoot = makeAppDir();
133
- writeOrderPlacedSchema(appRoot);
134
- write(
135
- appRoot,
136
- "src/feature/feature.ts",
137
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
138
- import { orderPlacedSchema } from "./events";
139
-
140
- export const ordersFeature = defineFeature("orders", (r) => {
141
- r.defineEvent("placed", orderPlacedSchema);
142
- });
143
- `,
144
- );
145
- return appRoot;
146
- }
147
-
148
- // Exports-shape feature: setup callback returns `{ placed }` so handler
149
- // modules can do `ordersFeature.exports.placed.name` and pick up the
150
- // literal type. Used by the eventDef.name pattern test.
151
- function setupAppWithExports(): string {
152
- const appRoot = makeAppDir();
153
- writeOrderPlacedSchema(appRoot);
154
- write(
155
- appRoot,
156
- "src/feature/feature.ts",
157
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
158
- import { orderPlacedSchema } from "./events";
159
-
160
- export const ordersFeature = defineFeature("orders", (r) => ({
161
- placed: r.defineEvent("placed", orderPlacedSchema),
162
- }));
163
- `,
164
- );
165
- return appRoot;
166
- }
167
-
168
86
  function writeOrderPlacedSchema(appRoot: string): void {
169
87
  write(
170
88
  appRoot,
171
89
  "src/feature/events.ts",
172
- `import { z } from "zod";
173
- export const orderPlacedSchema = z.object({
174
- orderId: z.string(),
175
- customerId: z.string(),
176
- amount: z.number(),
177
- });
178
- `,
90
+ [
91
+ 'import { z } from "zod";',
92
+ "export const orderPlacedSchema = z.object({",
93
+ " orderId: z.string(),",
94
+ " customerId: z.string(),",
95
+ " amount: z.number(),",
96
+ "});",
97
+ "",
98
+ ].join("\n"),
179
99
  );
180
100
  }
181
101
 
182
- // Each test compiles a tmp-app via ts.createProgram, which traverses
183
- // the framework-source via paths-alias (~700 files). Lokal ~1.7s pro
184
- // test; CI auf cdgs-runner (Hetzner CAX21 ARM64) braucht teilweise
185
- // >60s — vermutlich kleinere Cores + weniger RAM-cache als M-Series-
186
- // Mac. 120s gibt genug Puffer für den langsamsten Run-Path
187
- // (eventDef.name pattern lädt full augmented map) ohne echte Hänger
188
- // zu maskieren.
189
- const STRICT_MODE_TIMEOUT_MS = 120_000;
102
+ describe("strict-mode diagnostics -- the actual contract of the codegen", () => {
103
+ let appRoot: string;
104
+ let allDiagnostics: readonly ts.Diagnostic[];
190
105
 
191
- describe("strict-mode diagnostics — the actual contract of the codegen", () => {
192
- test("good ctx.appendEvent compiles cleanly", { timeout: STRICT_MODE_TIMEOUT_MS }, () => {
193
- const appRoot = setupApp();
106
+ beforeAll(() => {
107
+ appRoot = makeAppDir();
108
+ writeOrderPlacedSchema(appRoot);
109
+ write(
110
+ appRoot,
111
+ "src/feature/feature.ts",
112
+ [
113
+ 'import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";',
114
+ 'import { orderPlacedSchema } from "./events";',
115
+ "",
116
+ 'export const ordersFeature = defineFeature("orders", (r) => ({',
117
+ ' placed: r.defineEvent("placed", orderPlacedSchema),',
118
+ "}));",
119
+ "",
120
+ ].join("\n"),
121
+ );
194
122
  runCodegen({ appRoot });
195
123
 
196
124
  write(
197
125
  appRoot,
198
- "src/feature/handler.ts",
199
- `import { defineWriteHandler } from "../../.kumiko/define";
200
- import { z } from "zod";
126
+ "src/feature/handler-good.ts",
127
+ [
128
+ 'import { defineWriteHandler } from "../../.kumiko/define";',
129
+ 'import { z } from "zod";',
130
+ "",
131
+ "export const placeOrder = defineWriteHandler({",
132
+ ' name: "orders.placeOrder",',
133
+ " schema: z.object({}),",
134
+ ' access: { roles: ["Admin"] },',
135
+ " handler: async (_event, ctx) => {",
136
+ " await ctx.appendEvent({",
137
+ ' aggregateId: "x",',
138
+ ' aggregateType: "order",',
139
+ ' type: "orders:event:placed",',
140
+ ' payload: { orderId: "o1", customerId: "c1", amount: 99 },',
141
+ " });",
142
+ ' return { isSuccess: true as const, data: { id: "o1" } };',
143
+ " },",
144
+ "});",
145
+ "",
146
+ ].join("\n"),
147
+ );
201
148
 
202
- export const placeOrder = defineWriteHandler({
203
- name: "orders.placeOrder",
204
- schema: z.object({}),
205
- access: { roles: ["Admin"] },
206
- handler: async (_event, ctx) => {
207
- await ctx.appendEvent({
208
- aggregateId: "x",
209
- aggregateType: "order",
210
- type: "orders:event:placed",
211
- payload: { orderId: "o1", customerId: "c1", amount: 99 },
212
- });
213
- return { isSuccess: true as const, data: { id: "o1" } };
214
- },
215
- });
216
- `,
149
+ write(
150
+ appRoot,
151
+ "src/feature/handler-unknown-type.ts",
152
+ [
153
+ 'import { defineWriteHandler } from "../../.kumiko/define";',
154
+ 'import { z } from "zod";',
155
+ "",
156
+ "export const placeOrder = defineWriteHandler({",
157
+ ' name: "orders.placeOrder",',
158
+ " schema: z.object({}),",
159
+ ' access: { roles: ["Admin"] },',
160
+ " handler: async (_event, ctx) => {",
161
+ " await ctx.appendEvent({",
162
+ ' aggregateId: "x",',
163
+ ' aggregateType: "order",',
164
+ ' type: "totally:made:up",',
165
+ " payload: { whatever: 1 },",
166
+ " });",
167
+ ' return { isSuccess: true as const, data: { id: "x" } };',
168
+ " },",
169
+ "});",
170
+ "",
171
+ ].join("\n"),
217
172
  );
218
173
 
219
- const diagnostics = compileApp(appRoot);
220
- const handlerErrors = diagnostics.filter((d) =>
221
- d.file?.fileName.endsWith("/feature/handler.ts"),
174
+ write(
175
+ appRoot,
176
+ "src/feature/handler-payload-mismatch.ts",
177
+ [
178
+ 'import { defineWriteHandler } from "../../.kumiko/define";',
179
+ 'import { z } from "zod";',
180
+ "",
181
+ "export const placeOrder = defineWriteHandler({",
182
+ ' name: "orders.placeOrder",',
183
+ " schema: z.object({}),",
184
+ ' access: { roles: ["Admin"] },',
185
+ " handler: async (_event, ctx) => {",
186
+ " await ctx.appendEvent({",
187
+ ' aggregateId: "x",',
188
+ ' aggregateType: "order",',
189
+ ' type: "orders:event:placed",',
190
+ ' payload: { orderId: "o1", customerId: "c1", amount: 99, bogus: "extra" },',
191
+ " });",
192
+ ' return { isSuccess: true as const, data: { id: "o1" } };',
193
+ " },",
194
+ "});",
195
+ "",
196
+ ].join("\n"),
222
197
  );
223
- expect(handlerErrors).toHaveLength(0);
224
- });
225
198
 
226
- test("unknown event-type triggers TS2322 with augmented map in error message", {
227
- timeout: STRICT_MODE_TIMEOUT_MS,
228
- }, () => {
229
- const appRoot = setupApp();
230
- runCodegen({ appRoot });
199
+ write(
200
+ appRoot,
201
+ "src/feature/handler-direct.ts",
202
+ [
203
+ 'import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";',
204
+ 'import { z } from "zod";',
205
+ "",
206
+ "export const placeOrder = defineWriteHandler({",
207
+ ' name: "orders.placeOrder",',
208
+ " schema: z.object({}),",
209
+ ' access: { roles: ["Admin"] },',
210
+ " handler: async (_event, ctx) => {",
211
+ " await ctx.appendEvent({",
212
+ ' aggregateId: "x",',
213
+ ' aggregateType: "order",',
214
+ ' type: "orders:event:placed",',
215
+ ' payload: { orderId: "o1", customerId: "c1", amount: 99 },',
216
+ " });",
217
+ ' return { isSuccess: true as const, data: { id: "o1" } };',
218
+ " },",
219
+ "});",
220
+ "",
221
+ ].join("\n"),
222
+ );
231
223
 
232
224
  write(
233
225
  appRoot,
234
- "src/feature/handler.ts",
235
- `import { defineWriteHandler } from "../../.kumiko/define";
236
- import { z } from "zod";
226
+ "src/feature/handler-byname-good.ts",
227
+ [
228
+ 'import { defineWriteHandler } from "../../.kumiko/define";',
229
+ 'import { z } from "zod";',
230
+ 'import { ordersFeature } from "./feature";',
231
+ "",
232
+ "const { placed } = ordersFeature.exports;",
233
+ "",
234
+ "export const placeOrder = defineWriteHandler({",
235
+ ' name: "orders.placeOrder",',
236
+ " schema: z.object({}),",
237
+ ' access: { roles: ["Admin"] },',
238
+ " handler: async (_event, ctx) => {",
239
+ " await ctx.appendEvent({",
240
+ ' aggregateId: "x",',
241
+ ' aggregateType: "order",',
242
+ " type: placed.name,",
243
+ ' payload: { orderId: "o1", customerId: "c1", amount: 99 },',
244
+ " });",
245
+ ' return { isSuccess: true as const, data: { id: "o1" } };',
246
+ " },",
247
+ "});",
248
+ "",
249
+ ].join("\n"),
250
+ );
237
251
 
238
- export const placeOrder = defineWriteHandler({
239
- name: "orders.placeOrder",
240
- schema: z.object({}),
241
- access: { roles: ["Admin"] },
242
- handler: async (_event, ctx) => {
243
- await ctx.appendEvent({
244
- aggregateId: "x",
245
- aggregateType: "order",
246
- type: "totally:made:up",
247
- payload: { whatever: 1 },
248
- });
249
- return { isSuccess: true as const, data: { id: "x" } };
250
- },
251
- });
252
- `,
252
+ write(
253
+ appRoot,
254
+ "src/feature/handler-byname-bad.ts",
255
+ [
256
+ 'import { defineWriteHandler } from "../../.kumiko/define";',
257
+ 'import { z } from "zod";',
258
+ 'import { ordersFeature } from "./feature";',
259
+ "",
260
+ "const { placed } = ordersFeature.exports;",
261
+ "",
262
+ "export const placeOrder = defineWriteHandler({",
263
+ ' name: "orders.placeOrder",',
264
+ " schema: z.object({}),",
265
+ ' access: { roles: ["Admin"] },',
266
+ " handler: async (_event, ctx) => {",
267
+ " await ctx.appendEvent({",
268
+ ' aggregateId: "x",',
269
+ ' aggregateType: "order",',
270
+ " type: placed.name,",
271
+ ' payload: { orderId: "o1", customerId: "c1", amount: 99, bogus: "extra" },',
272
+ " });",
273
+ ' return { isSuccess: true as const, data: { id: "o1" } };',
274
+ " },",
275
+ "});",
276
+ "",
277
+ ].join("\n"),
253
278
  );
254
279
 
255
- const diagnostics = compileApp(appRoot);
256
- const handlerErrors = diagnostics.filter((d) =>
257
- d.file?.fileName.endsWith("/feature/handler.ts"),
280
+ allDiagnostics = compileApp(appRoot);
281
+ }, 120_000);
282
+
283
+ test("good ctx.appendEvent compiles cleanly", () => {
284
+ const handlerErrors = allDiagnostics.filter((d) =>
285
+ d.file?.fileName.endsWith("/feature/handler-good.ts"),
286
+ );
287
+ expect(handlerErrors).toHaveLength(0);
288
+ });
289
+
290
+ test("unknown event-type triggers TS2322 with augmented map in error message", () => {
291
+ const handlerErrors = allDiagnostics.filter((d) =>
292
+ d.file?.fileName.endsWith("/feature/handler-unknown-type.ts"),
258
293
  );
259
- // We expect at least one TS2322 ("not assignable") for the bogus
260
- // type-string. The exact column may move with TS versions; the code
261
- // + the type-name are the stable contract.
262
294
  const ts2322 = handlerErrors.filter((d) => d.code === 2322);
263
295
  expect(ts2322.length).toBeGreaterThan(0);
264
296
  const flattened = ts2322
@@ -267,42 +299,10 @@ export const placeOrder = defineWriteHandler({
267
299
  expect(flattened).toMatch(/keyof KumikoEventTypeMap|"orders:event:placed"/);
268
300
  });
269
301
 
270
- test("payload-shape mismatch triggers a property-error", {
271
- timeout: STRICT_MODE_TIMEOUT_MS,
272
- }, () => {
273
- const appRoot = setupApp();
274
- runCodegen({ appRoot });
275
-
276
- write(
277
- appRoot,
278
- "src/feature/handler.ts",
279
- `import { defineWriteHandler } from "../../.kumiko/define";
280
- import { z } from "zod";
281
-
282
- export const placeOrder = defineWriteHandler({
283
- name: "orders.placeOrder",
284
- schema: z.object({}),
285
- access: { roles: ["Admin"] },
286
- handler: async (_event, ctx) => {
287
- await ctx.appendEvent({
288
- aggregateId: "x",
289
- aggregateType: "order",
290
- type: "orders:event:placed",
291
- payload: { orderId: "o1", customerId: "c1", amount: 99, bogus: "extra" },
292
- });
293
- return { isSuccess: true as const, data: { id: "o1" } };
294
- },
295
- });
296
- `,
297
- );
298
-
299
- const diagnostics = compileApp(appRoot);
300
- const handlerErrors = diagnostics.filter((d) =>
301
- d.file?.fileName.endsWith("/feature/handler.ts"),
302
+ test("payload-shape mismatch triggers a property-error", () => {
303
+ const handlerErrors = allDiagnostics.filter((d) =>
304
+ d.file?.fileName.endsWith("/feature/handler-payload-mismatch.ts"),
302
305
  );
303
- // TS2353 = "Object literal may only specify known properties, and
304
- // 'bogus' does not exist in type". This is the property-level
305
- // strict-check we promised.
306
306
  const propErrors = handlerErrors.filter((d) => d.code === 2353);
307
307
  expect(propErrors.length).toBeGreaterThan(0);
308
308
  const flattened = propErrors
@@ -311,151 +311,26 @@ export const placeOrder = defineWriteHandler({
311
311
  expect(flattened).toMatch(/'bogus'/);
312
312
  });
313
313
 
314
- test("direct framework-import + augmentation-included compiles strict too", {
315
- timeout: STRICT_MODE_TIMEOUT_MS,
316
- }, () => {
317
- // Sanity-Check: in einem isolated app-tsc (tmp-fixture mit paths-
318
- // mapping zur framework-source UND .kumiko/types.generated.d.ts im
319
- // include-Glob) greift strict-mode auch beim direct framework-import.
320
- // Generic-function-inference nimmt die augmentation am use-site wahr.
321
- //
322
- // Konsequenz: der Wrapper ist NICHT der einzige Weg zu strict —
323
- // aber er ist DER ROBUSTE Weg. Er importiert `types.generated`
324
- // explicit als side-effect, sodass die Augmentation auch in
325
- // partial-builds / IDE-Sprachserver-stati garantiert visible ist.
326
- // Direkter Import setzt voraus, dass das tsconfig-Setup stimmt.
327
- //
328
- // Die alte "K=never"-Beobachtung aus den 13 Probes war im
329
- // bundled-features-Compile, wo das `.kumiko/`-Output nicht im
330
- // include-Glob lag — die Augmentation aus inline `declare module`
331
- // hatte einen anderen Resolution-Pfad. Der Wrapper bleibt der
332
- // empfohlene Pfad für Apps, weil er diese Setup-Sensibilität wegabstrahiert.
333
- const appRoot = setupApp();
334
- runCodegen({ appRoot });
335
-
336
- write(
337
- appRoot,
338
- "src/feature/handler-direct.ts",
339
- `import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
340
- import { z } from "zod";
341
-
342
- export const placeOrder = defineWriteHandler({
343
- name: "orders.placeOrder",
344
- schema: z.object({}),
345
- access: { roles: ["Admin"] },
346
- handler: async (_event, ctx) => {
347
- await ctx.appendEvent({
348
- aggregateId: "x",
349
- aggregateType: "order",
350
- type: "orders:event:placed",
351
- payload: { orderId: "o1", customerId: "c1", amount: 99 },
352
- });
353
- return { isSuccess: true as const, data: { id: "o1" } };
354
- },
355
- });
356
- `,
357
- );
358
-
359
- const diagnostics = compileApp(appRoot);
360
- const handlerErrors = diagnostics.filter((d) =>
314
+ test("direct framework-import + augmentation-included compiles strict too", () => {
315
+ const handlerErrors = allDiagnostics.filter((d) =>
361
316
  d.file?.fileName.endsWith("/feature/handler-direct.ts"),
362
317
  );
363
- // Good call should compile — augmentation is visible via include of
364
- // `.kumiko/types.generated.d.ts`.
365
318
  expect(handlerErrors).toHaveLength(0);
366
319
  });
367
320
 
368
- test("eventDef.name pattern: literal-typed name resolves to correct payload-shape", {
369
- timeout: STRICT_MODE_TIMEOUT_MS,
370
- }, () => {
371
- // Marten pattern: `const placed = r.defineEvent(...)`, then
372
- // `type: placed.name` in appendEvent. This requires `EventDef.name`
373
- // to be LITERAL-typed (`"orders:event:placed"`, NOT `string`) —
374
- // otherwise the lookup collapses to `string` and the strict check
375
- // silently disappears.
376
- //
377
- // This test catches regressions in `EventDef<TPayload, TName>` and
378
- // the `<const TInner>` inference in `defineFeature`/`defineEvent`
379
- // — both have to cooperate so that `placed.name` resolves as a
380
- // literal into the `KumikoEventTypeMap` key.
381
- //
382
- // Setup: `setupAppWithExports` returns `{ placed }` from the
383
- // defineFeature callback so handler modules can read it as
384
- // `ordersFeature.exports.placed.name`.
385
- const appRoot = setupAppWithExports();
386
- runCodegen({ appRoot });
387
-
388
- write(
389
- appRoot,
390
- "src/feature/handler-byname.ts",
391
- `import { defineWriteHandler } from "../../.kumiko/define";
392
- import { z } from "zod";
393
- import { ordersFeature } from "./feature";
394
-
395
- const { placed } = ordersFeature.exports;
396
-
397
- export const placeOrder = defineWriteHandler({
398
- name: "orders.placeOrder",
399
- schema: z.object({}),
400
- access: { roles: ["Admin"] },
401
- handler: async (_event, ctx) => {
402
- await ctx.appendEvent({
403
- aggregateId: "x",
404
- aggregateType: "order",
405
- type: placed.name,
406
- payload: { orderId: "o1", customerId: "c1", amount: 99 },
407
- });
408
- return { isSuccess: true as const, data: { id: "o1" } };
409
- },
410
- });
411
- `,
412
- );
413
-
414
- const goodDiagnostics = compileApp(appRoot);
415
- const goodErrors = goodDiagnostics.filter((d) =>
416
- d.file?.fileName.endsWith("/feature/handler-byname.ts"),
321
+ test("eventDef.name pattern: literal-typed name resolves to correct payload-shape", () => {
322
+ const goodErrors = allDiagnostics.filter((d) =>
323
+ d.file?.fileName.endsWith("/feature/handler-byname-good.ts"),
417
324
  );
418
325
  if (goodErrors.length > 0) {
419
326
  const msgs = goodErrors
420
327
  .map((d) => ` TS${d.code}: ${ts.flattenDiagnosticMessageText(d.messageText, "\n")}`)
421
328
  .join("\n");
422
- throw new Error(`expected handler-byname.ts to compile cleanly, got:\n${msgs}`);
329
+ throw new Error(`expected handler-byname-good.ts to compile cleanly, got:\n${msgs}`);
423
330
  }
424
331
 
425
- // Negative-Case: bad payload (extra property) → TS2353. Der
426
- // entscheidende Punkt — wenn `placed.name` zu `string` kollabiert
427
- // wäre, würde TS hier eine `Record<string, unknown>` annehmen und
428
- // die extra property NICHT melden. TS2353 hier beweist die
429
- // literal-typed Auflösung über `.name`.
430
- write(
431
- appRoot,
432
- "src/feature/handler-byname.ts",
433
- `import { defineWriteHandler } from "../../.kumiko/define";
434
- import { z } from "zod";
435
- import { ordersFeature } from "./feature";
436
-
437
- const { placed } = ordersFeature.exports;
438
-
439
- export const placeOrder = defineWriteHandler({
440
- name: "orders.placeOrder",
441
- schema: z.object({}),
442
- access: { roles: ["Admin"] },
443
- handler: async (_event, ctx) => {
444
- await ctx.appendEvent({
445
- aggregateId: "x",
446
- aggregateType: "order",
447
- type: placed.name,
448
- payload: { orderId: "o1", customerId: "c1", amount: 99, bogus: "extra" },
449
- });
450
- return { isSuccess: true as const, data: { id: "o1" } };
451
- },
452
- });
453
- `,
454
- );
455
-
456
- const badDiagnostics = compileApp(appRoot);
457
- const badErrors = badDiagnostics.filter((d) =>
458
- d.file?.fileName.endsWith("/feature/handler-byname.ts"),
332
+ const badErrors = allDiagnostics.filter((d) =>
333
+ d.file?.fileName.endsWith("/feature/handler-byname-bad.ts"),
459
334
  );
460
335
  const propErrors = badErrors.filter((d) => d.code === 2353);
461
336
  expect(propErrors.length).toBeGreaterThan(0);
@@ -192,6 +192,7 @@ export function renderDefineFile(): string {
192
192
  `import type {`,
193
193
  ` KumikoEventTypeMap,`,
194
194
  ` WriteHandlerDefinition,`,
195
+ ` WriteHandlerInput,`,
195
196
  ` QueryHandlerDefinition,`,
196
197
  `} from "@cosmicdrift/kumiko-framework/engine";`,
197
198
  `import type { ZodType } from "zod";`,
@@ -204,7 +205,7 @@ export function renderDefineFile(): string {
204
205
  ` TSchema extends ZodType,`,
205
206
  ` TData = unknown,`,
206
207
  `>(`,
207
- ` def: WriteHandlerDefinition<TName, TSchema, TData, KumikoEventTypeMap>,`,
208
+ ` def: WriteHandlerInput<TName, TSchema, TData, KumikoEventTypeMap>,`,
208
209
  `): WriteHandlerDefinition<TName, TSchema, TData, KumikoEventTypeMap> {`,
209
210
  ` return fwDefineWriteHandler<TName, TSchema, TData, KumikoEventTypeMap>(def);`,
210
211
  `}`,