@cosmicdrift/kumiko-framework 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.
Files changed (167) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/package.json +124 -39
  3. package/src/__tests__/full-stack.integration.ts +2 -2
  4. package/src/api/auth-routes.ts +5 -5
  5. package/src/api/jwt.ts +2 -2
  6. package/src/api/route-registrars.ts +1 -1
  7. package/src/api/routes.ts +3 -3
  8. package/src/api/server.ts +6 -7
  9. package/src/compliance/profiles.ts +8 -8
  10. package/src/db/assert-exists-in.ts +2 -2
  11. package/src/db/cursor.ts +3 -3
  12. package/src/db/event-store-executor.ts +19 -13
  13. package/src/db/located-timestamp.ts +1 -1
  14. package/src/db/money.ts +12 -2
  15. package/src/db/pg-error.ts +1 -1
  16. package/src/db/row-helpers.ts +1 -1
  17. package/src/db/table-builder.ts +3 -5
  18. package/src/db/tenant-db.ts +9 -9
  19. package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
  20. package/src/engine/__tests__/build-target.test.ts +135 -0
  21. package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
  22. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  23. package/src/engine/__tests__/event-helpers.test.ts +4 -4
  24. package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
  25. package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
  26. package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
  27. package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
  28. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
  29. package/src/engine/__tests__/raw-table.test.ts +2 -2
  30. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
  31. package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
  32. package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
  33. package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
  34. package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
  35. package/src/engine/__tests__/steps-read.test.ts +142 -0
  36. package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
  37. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
  38. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
  39. package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
  40. package/src/engine/__tests__/steps-workflow.test.ts +198 -0
  41. package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
  42. package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
  43. package/src/engine/boot-validator/api-ext.ts +77 -0
  44. package/src/engine/boot-validator/config-deps.ts +163 -0
  45. package/src/engine/boot-validator/entity-handler.ts +466 -0
  46. package/src/engine/boot-validator/index.ts +159 -0
  47. package/src/engine/boot-validator/ownership.ts +198 -0
  48. package/src/engine/boot-validator/pii-retention.ts +155 -0
  49. package/src/engine/boot-validator/screens-nav.ts +624 -0
  50. package/src/engine/boot-validator.ts +1 -1804
  51. package/src/engine/build-app-schema.ts +1 -1
  52. package/src/engine/build-target.ts +99 -0
  53. package/src/engine/codemod/index.ts +15 -0
  54. package/src/engine/codemod/pipeline-codemod.ts +641 -0
  55. package/src/engine/config-helpers.ts +9 -19
  56. package/src/engine/constants.ts +1 -1
  57. package/src/engine/define-feature.ts +88 -9
  58. package/src/engine/define-handler.ts +89 -3
  59. package/src/engine/define-roles.ts +2 -2
  60. package/src/engine/define-step.ts +28 -0
  61. package/src/engine/define-workflow.ts +110 -0
  62. package/src/engine/entity-handlers.ts +10 -9
  63. package/src/engine/event-helpers.ts +4 -4
  64. package/src/engine/factories.ts +12 -12
  65. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
  66. package/src/engine/feature-ast/extractors/index.ts +74 -0
  67. package/src/engine/feature-ast/extractors/round1.ts +110 -0
  68. package/src/engine/feature-ast/extractors/round2.ts +253 -0
  69. package/src/engine/feature-ast/extractors/round3.ts +471 -0
  70. package/src/engine/feature-ast/extractors/round4.ts +1365 -0
  71. package/src/engine/feature-ast/extractors/round5.ts +72 -0
  72. package/src/engine/feature-ast/extractors/round6.ts +66 -0
  73. package/src/engine/feature-ast/extractors/shared.ts +177 -0
  74. package/src/engine/feature-ast/parse.ts +7 -0
  75. package/src/engine/feature-ast/patch.ts +9 -1
  76. package/src/engine/feature-ast/patcher.ts +10 -3
  77. package/src/engine/feature-ast/patterns.ts +49 -1
  78. package/src/engine/feature-ast/render.ts +17 -1
  79. package/src/engine/index.ts +45 -2
  80. package/src/engine/pattern-library/__tests__/library.test.ts +6 -0
  81. package/src/engine/pattern-library/library.ts +42 -2
  82. package/src/engine/pipeline.ts +88 -0
  83. package/src/engine/projection-helpers.ts +1 -1
  84. package/src/engine/read-claim.ts +1 -1
  85. package/src/engine/registry.ts +30 -2
  86. package/src/engine/resolve-config-or-param.ts +4 -0
  87. package/src/engine/run-pipeline.ts +162 -0
  88. package/src/engine/schema-builder.ts +2 -4
  89. package/src/engine/state-machine.ts +1 -1
  90. package/src/engine/steps/_drizzle-boundary.ts +19 -0
  91. package/src/engine/steps/_duration-utils.ts +33 -0
  92. package/src/engine/steps/_no-return-guard.ts +21 -0
  93. package/src/engine/steps/_resolver-utils.ts +42 -0
  94. package/src/engine/steps/_step-dispatch-constants.ts +38 -0
  95. package/src/engine/steps/aggregate-append-event.ts +56 -0
  96. package/src/engine/steps/aggregate-create.ts +56 -0
  97. package/src/engine/steps/aggregate-update.ts +68 -0
  98. package/src/engine/steps/branch.ts +84 -0
  99. package/src/engine/steps/call-feature.ts +49 -0
  100. package/src/engine/steps/compute.ts +41 -0
  101. package/src/engine/steps/for-each.ts +111 -0
  102. package/src/engine/steps/mail-send.ts +44 -0
  103. package/src/engine/steps/read-find-many.ts +51 -0
  104. package/src/engine/steps/read-find-one.ts +58 -0
  105. package/src/engine/steps/retry.ts +87 -0
  106. package/src/engine/steps/return.ts +34 -0
  107. package/src/engine/steps/unsafe-projection-delete.ts +46 -0
  108. package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
  109. package/src/engine/steps/wait-for-event.ts +71 -0
  110. package/src/engine/steps/wait.ts +69 -0
  111. package/src/engine/steps/webhook-send.ts +71 -0
  112. package/src/engine/system-user.ts +1 -1
  113. package/src/engine/types/feature.ts +92 -1
  114. package/src/engine/types/handlers.ts +18 -10
  115. package/src/engine/types/identifiers.ts +1 -0
  116. package/src/engine/types/index.ts +12 -1
  117. package/src/engine/types/step.ts +334 -0
  118. package/src/engine/types/target-ref.ts +21 -0
  119. package/src/engine/types/tree-node.ts +130 -0
  120. package/src/engine/types/workspace.ts +7 -0
  121. package/src/engine/validate-projection-allowlist.ts +161 -0
  122. package/src/event-store/snapshot.ts +1 -1
  123. package/src/event-store/upcaster-dead-letter.ts +1 -1
  124. package/src/event-store/upcaster.ts +1 -1
  125. package/src/files/file-routes.ts +1 -1
  126. package/src/files/types.ts +2 -2
  127. package/src/jobs/job-runner.ts +10 -10
  128. package/src/lifecycle/lifecycle.ts +0 -3
  129. package/src/logging/index.ts +1 -0
  130. package/src/logging/pino-logger.ts +11 -7
  131. package/src/logging/utils.ts +24 -0
  132. package/src/observability/prometheus-meter.ts +7 -5
  133. package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
  134. package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
  135. package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
  136. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
  137. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
  138. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
  139. package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
  140. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
  141. package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
  142. package/src/pipeline/append-event-core.ts +22 -6
  143. package/src/pipeline/dispatcher-utils.ts +188 -0
  144. package/src/pipeline/dispatcher.ts +63 -283
  145. package/src/pipeline/distributed-lock.ts +1 -1
  146. package/src/pipeline/entity-cache.ts +2 -2
  147. package/src/pipeline/event-consumer-state.ts +0 -13
  148. package/src/pipeline/event-dispatcher.ts +4 -4
  149. package/src/pipeline/index.ts +0 -2
  150. package/src/pipeline/lifecycle-pipeline.ts +6 -12
  151. package/src/pipeline/msp-rebuild.ts +5 -5
  152. package/src/pipeline/multi-stream-apply-context.ts +6 -7
  153. package/src/pipeline/projection-rebuild.ts +2 -2
  154. package/src/pipeline/projection-state.ts +0 -12
  155. package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
  156. package/src/rate-limit/resolver.ts +1 -1
  157. package/src/search/in-memory-adapter.ts +1 -1
  158. package/src/search/meilisearch-adapter.ts +3 -3
  159. package/src/search/types.ts +1 -1
  160. package/src/secrets/leak-guard.ts +2 -2
  161. package/src/stack/request-helper.ts +9 -5
  162. package/src/stack/test-stack.ts +1 -1
  163. package/src/testing/handler-context.ts +4 -4
  164. package/src/testing/http-cookies.ts +1 -1
  165. package/src/time/tz-context.ts +1 -2
  166. package/src/ui-types/index.ts +4 -0
  167. package/src/engine/feature-ast/extractors.ts +0 -2602
@@ -0,0 +1,215 @@
1
+ // Pipeline-engine unit tests — defineWriteHandler({ perform: pipeline(...) })
2
+ // boundary, run-pipeline runner contract, defineStep registry guards.
3
+ // Sub-step-builders (branch, forEach) live in pipeline-sub-pipelines.test.ts;
4
+ // boot-validator in validate-projection-allowlist.test.ts.
5
+
6
+ import { randomUUID } from "node:crypto";
7
+ import { describe, expect, it } from "vitest";
8
+ import { z } from "zod";
9
+ import { TestUsers } from "../../stack";
10
+ import { defineWriteHandler } from "../define-handler";
11
+ import { defineStep } from "../define-step";
12
+ import { pipeline } from "../pipeline";
13
+ import type { WriteEvent } from "../types/handlers";
14
+ import { buildMinimalCtx } from "./_pipeline-test-utils";
15
+
16
+ describe("pipeline engine (return / compute / registry guards)", () => {
17
+ it("compiles a perform-block into a callable handler that returns the resolver's WriteResult", async () => {
18
+ const handlerDef = defineWriteHandler({
19
+ name: "demo:noop",
20
+ schema: z.object({ greeting: z.string() }),
21
+ access: { roles: ["User"] },
22
+ perform: pipeline<{ greeting: string }, { echoed: string }>(({ event, r }) => [
23
+ r.step.return(() => ({
24
+ isSuccess: true as const,
25
+ data: { echoed: event.payload.greeting },
26
+ })),
27
+ ]),
28
+ });
29
+
30
+ expect(typeof handlerDef.handler).toBe("function");
31
+ expect(handlerDef.perform).toBeDefined();
32
+ expect(handlerDef.perform?.__kind).toBe("pipeline");
33
+
34
+ const event: WriteEvent<{ greeting: string }> = {
35
+ type: "demo:noop",
36
+ payload: { greeting: "hello" },
37
+ user: TestUsers.admin,
38
+ };
39
+
40
+ const result = await handlerDef.handler(event, buildMinimalCtx());
41
+ expect(result.isSuccess).toBe(true);
42
+ if (result.isSuccess) {
43
+ expect(result.data).toEqual({ echoed: "hello" });
44
+ }
45
+ });
46
+
47
+ it("supports a static (non-resolver) WriteResult passed to r.step.return", async () => {
48
+ const handlerDef = defineWriteHandler({
49
+ name: "demo:static",
50
+ schema: z.object({}),
51
+ access: { roles: ["User"] },
52
+ perform: pipeline<Record<string, never>, { ok: boolean }>(({ r }) => [
53
+ r.step.return({ isSuccess: true as const, data: { ok: true } }),
54
+ ]),
55
+ });
56
+
57
+ const result = await handlerDef.handler(
58
+ { type: "demo:static", payload: {}, user: TestUsers.admin },
59
+ buildMinimalCtx(),
60
+ );
61
+ expect(result).toEqual({ isSuccess: true, data: { ok: true } });
62
+ });
63
+
64
+ it("preserves the free-form handler path unchanged", async () => {
65
+ const handlerDef = defineWriteHandler({
66
+ name: "demo:freeform",
67
+ schema: z.object({ n: z.number() }),
68
+ access: { roles: ["User"] },
69
+ handler: async (event) => ({
70
+ isSuccess: true as const,
71
+ data: { doubled: event.payload.n * 2 },
72
+ }),
73
+ });
74
+
75
+ expect(handlerDef.perform).toBeUndefined();
76
+
77
+ const result = await handlerDef.handler(
78
+ { type: "demo:freeform", payload: { n: 21 }, user: TestUsers.admin },
79
+ buildMinimalCtx(),
80
+ );
81
+ expect(result.isSuccess).toBe(true);
82
+ if (result.isSuccess) {
83
+ expect(result.data).toEqual({ doubled: 42 });
84
+ }
85
+ });
86
+
87
+ it("surfaces an explicit error when a pipeline ends without r.step.return", async () => {
88
+ const handlerDef = defineWriteHandler({
89
+ name: "demo:no-return",
90
+ schema: z.object({}),
91
+ access: { roles: ["User"] },
92
+ perform: pipeline<Record<string, never>, never>(() => []),
93
+ });
94
+
95
+ await expect(
96
+ handlerDef.handler(
97
+ { type: "demo:no-return", payload: {}, user: TestUsers.admin },
98
+ buildMinimalCtx(),
99
+ ),
100
+ ).rejects.toThrow(/r\.step\.return/);
101
+ });
102
+
103
+ it("threads compute results into subsequent step resolvers via steps.<name>", async () => {
104
+ const handlerDef = defineWriteHandler({
105
+ name: "demo:thread",
106
+ schema: z.object({ base: z.number() }),
107
+ access: { roles: ["User"] },
108
+ perform: pipeline<{ base: number }, { sum: number }>(({ event, r }) => [
109
+ r.step.compute("offset", () => 10),
110
+ r.step.compute("doubledBase", () => event.payload.base * 2),
111
+ r.step.return(({ steps }) => ({
112
+ isSuccess: true as const,
113
+ data: {
114
+ sum: (steps["offset"] as number) + (steps["doubledBase"] as number),
115
+ },
116
+ })),
117
+ ]),
118
+ });
119
+
120
+ const result = await handlerDef.handler(
121
+ { type: "demo:thread", payload: { base: 5 }, user: TestUsers.admin },
122
+ buildMinimalCtx(),
123
+ );
124
+ expect(result.isSuccess).toBe(true);
125
+ if (result.isSuccess) {
126
+ expect(result.data).toEqual({ sum: 20 });
127
+ }
128
+ });
129
+
130
+ it("re-evaluates compute resolvers per pipeline run (not at build time)", async () => {
131
+ let counter = 0;
132
+ const handlerDef = defineWriteHandler({
133
+ name: "demo:fresh",
134
+ schema: z.object({}),
135
+ access: { roles: ["User"] },
136
+ perform: pipeline<Record<string, never>, { tick: number }>(({ r }) => [
137
+ r.step.compute("tick", () => ++counter),
138
+ r.step.return(({ steps }) => ({
139
+ isSuccess: true as const,
140
+ data: { tick: steps["tick"] as number },
141
+ })),
142
+ ]),
143
+ });
144
+
145
+ const a = await handlerDef.handler(
146
+ { type: "demo:fresh", payload: {}, user: TestUsers.admin },
147
+ buildMinimalCtx(),
148
+ );
149
+ const b = await handlerDef.handler(
150
+ { type: "demo:fresh", payload: {}, user: TestUsers.admin },
151
+ buildMinimalCtx(),
152
+ );
153
+ if (a.isSuccess && b.isSuccess) {
154
+ expect(a.data.tick).toBe(1);
155
+ expect(b.data.tick).toBe(2);
156
+ }
157
+ });
158
+
159
+ it("rejects a step with an unknown kind at runtime", async () => {
160
+ const handlerDef = defineWriteHandler({
161
+ name: "demo:unknown-kind",
162
+ schema: z.object({}),
163
+ access: { roles: ["User"] },
164
+ perform: pipeline<Record<string, never>, never>(() => [
165
+ // Hand-crafted instance with a kind that's never been registered —
166
+ // simulates a typo in a future step-builder factory.
167
+ { kind: "this-step-does-not-exist", args: {} },
168
+ ]),
169
+ });
170
+
171
+ await expect(
172
+ handlerDef.handler(
173
+ { type: "demo:unknown-kind", payload: {}, user: TestUsers.admin },
174
+ buildMinimalCtx(),
175
+ ),
176
+ ).rejects.toThrow(/Unknown step kind "this-step-does-not-exist"/);
177
+ });
178
+
179
+ it("defineWriteHandler throws a clear runtime error when both handler+perform are set (#3)", () => {
180
+ // Type-system rejects this via discriminated union (handler?: never /
181
+ // perform?: never), but the error message ('Type X not assignable to
182
+ // undefined') is opaque. The runtime guard fires regardless of how
183
+ // the type-error was bypassed (any-cast, generated code, JS-call) and
184
+ // names BOTH fields explicitly.
185
+ // Deliberately bypass the discriminated union via `as never` so the
186
+ // runtime guard is what's under test (the type-system path already
187
+ // rejects the conflict).
188
+ const conflictingDef = {
189
+ name: "demo:both",
190
+ schema: z.object({}),
191
+ access: { roles: ["User"] },
192
+ handler: async () => ({ isSuccess: true as const, data: {} }),
193
+ perform: pipeline<Record<string, never>, { ok: true }>(({ r }) => [
194
+ r.step.return({ isSuccess: true as const, data: { ok: true } }),
195
+ ]),
196
+ } as unknown as Parameters<typeof defineWriteHandler>[0];
197
+ expect(() => defineWriteHandler(conflictingDef)).toThrow(
198
+ /both `handler` and `perform` are set/,
199
+ );
200
+ });
201
+
202
+ it("defineStep throws when the same kind is registered with a different definition", () => {
203
+ // Unique-per-run kind so this test is safe under vitest --watch
204
+ // (where the file may re-execute in the same process). Without the
205
+ // unique-kind, the first defineStep call on the second run would
206
+ // already throw against the registration left from the first run.
207
+ const kind = `test-only:duplicate-guard:${randomUUID()}`;
208
+
209
+ defineStep({ kind, defaultFailureStrategy: "throw", run: () => undefined });
210
+
211
+ expect(() =>
212
+ defineStep({ kind, defaultFailureStrategy: "throw", run: () => "different" }),
213
+ ).toThrow(/already registered/);
214
+ });
215
+ });