@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,188 @@
1
+ import { parseQn, qn } from "../engine/qualified-name";
2
+ import type {
3
+ HandlerRef,
4
+ LifecycleResult,
5
+ Registry,
6
+ SessionUser,
7
+ WriteResult,
8
+ } from "../engine/types";
9
+ import { InternalError, isKumikoError, type KumikoError, type WriteErrorInfo } from "../errors";
10
+
11
+ export type FailedWriteResult = Extract<WriteResult, { isSuccess: false }>;
12
+
13
+ export function isFailedWriteResult(result: unknown): result is FailedWriteResult {
14
+ return (
15
+ !!result && typeof result === "object" && "isSuccess" in result && result.isSuccess === false
16
+ );
17
+ }
18
+
19
+ export function isLifecycleResult(data: unknown): data is LifecycleResult {
20
+ return !!data && typeof data === "object" && "kind" in data;
21
+ }
22
+
23
+ export function isWriteResultShape(result: unknown): boolean {
24
+ return (
25
+ !!result &&
26
+ typeof result === "object" &&
27
+ "isSuccess" in result &&
28
+ typeof result.isSuccess === "boolean"
29
+ );
30
+ }
31
+
32
+ export function describeShape(result: unknown): string {
33
+ if (result === null) return "null";
34
+ if (result === undefined) return "undefined";
35
+ if (typeof result !== "object") return typeof result;
36
+ return `object with keys [${Object.keys(result).slice(0, 6).join(", ")}]`;
37
+ }
38
+
39
+ export function dispatcherSpanAttributes(
40
+ type: string,
41
+ operation: "query" | "write",
42
+ user: SessionUser,
43
+ feature: string | undefined,
44
+ ) {
45
+ const attrs: Record<string, string | number | boolean> = {
46
+ "kumiko.handler": type,
47
+ "kumiko.operation": operation,
48
+ "kumiko.user_id": user.id,
49
+ "kumiko.tenant_id": user.tenantId,
50
+ };
51
+ if (feature) attrs["kumiko.feature"] = feature;
52
+ return attrs;
53
+ }
54
+
55
+ export type AfterCommitHook = () => Promise<void>;
56
+
57
+ export type NestedSpec = {
58
+ readonly key: string;
59
+ readonly subType: string;
60
+ readonly foreignKey: string;
61
+ readonly items: readonly unknown[];
62
+ };
63
+
64
+ export type NestedTypeIssue = {
65
+ readonly path: string;
66
+ readonly code: string;
67
+ readonly i18nKey: string;
68
+ };
69
+
70
+ export function extractNestedSpecs(
71
+ parentType: string,
72
+ payload: unknown,
73
+ registry: Registry,
74
+ ): {
75
+ cleanPayload: Record<string, unknown>;
76
+ specs: readonly NestedSpec[];
77
+ typeIssues: readonly NestedTypeIssue[];
78
+ } | null {
79
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
80
+
81
+ let parsed: ReturnType<typeof parseQn>;
82
+ try {
83
+ parsed = parseQn(parentType);
84
+ } catch {
85
+ return null;
86
+ }
87
+ // v1 scope: only create. Update/delete-nested are explicit future work —
88
+ // they'd need different sub-types and id-handling semantics.
89
+ if (!parsed.name.endsWith(":create")) return null;
90
+
91
+ const entityName = registry.getHandlerEntity(parentType);
92
+ if (!entityName) return null;
93
+
94
+ const relations = registry.getRelations(entityName);
95
+ const source = payload as Record<string, unknown>; // @cast-boundary engine-payload — generic dispatch über alle Entity-Types
96
+ const clean: Record<string, unknown> = { ...source };
97
+ const specs: NestedSpec[] = [];
98
+ const typeIssues: NestedTypeIssue[] = [];
99
+
100
+ for (const [relKey, rel] of Object.entries(relations)) {
101
+ if (rel.type !== "hasMany" || !rel.nestedWrite) continue;
102
+ if (!(relKey in source)) continue;
103
+ const value = source[relKey];
104
+
105
+ // Non-array under a nested-write key is a client shape error. Silent
106
+ // strip (via default zod stripping) would hide it — a client sending
107
+ // `tasks: "bogus"` or `tasks: null` has to know the field was ignored,
108
+ // or they'll wonder why their data never showed up. Fail loud.
109
+ if (!Array.isArray(value)) {
110
+ typeIssues.push({
111
+ path: relKey,
112
+ code: "invalid_type",
113
+ i18nKey: "errors.validation.invalid_type",
114
+ });
115
+ // Still strip from clean payload — we're not letting the parent handler
116
+ // see a malformed value either.
117
+ delete clean[relKey];
118
+ continue;
119
+ }
120
+
121
+ // Strip the relation key from the clean payload — the parent handler
122
+ // only sees columns it actually owns.
123
+ delete clean[relKey];
124
+
125
+ // Sub-type composition: derive scope + operation from the parent qn,
126
+ // swap the entity segment. "feat:write:project:create" → "feat:write:task:create".
127
+ // Assumes target entity has a `:create` handler in the SAME feature scope
128
+ // as the parent. Cross-feature nested-writes are out of scope for v1;
129
+ // when needed, the registry would have to carry a back-pointer from
130
+ // entity → defining feature.
131
+ const subType = qn(parsed.scope, parsed.type, `${rel.target}:create`);
132
+
133
+ specs.push({
134
+ key: relKey,
135
+ subType,
136
+ foreignKey: rel.foreignKey,
137
+ items: value,
138
+ });
139
+ }
140
+
141
+ if (specs.length === 0 && typeIssues.length === 0) return null;
142
+ return { cleanPayload: clean, specs, typeIssues };
143
+ }
144
+
145
+ export function prefixValidationPath(info: WriteErrorInfo, prefix: string): WriteErrorInfo {
146
+ if (info.code !== "validation_error") return info;
147
+ const details = info.details as // @cast-boundary error-details
148
+ | {
149
+ fields?: readonly {
150
+ path: string;
151
+ code: string;
152
+ i18nKey: string;
153
+ params?: Readonly<Record<string, unknown>>;
154
+ }[];
155
+ }
156
+ | undefined;
157
+ const fields = details?.fields;
158
+ if (!fields) return info;
159
+ return {
160
+ ...info,
161
+ details: {
162
+ ...details,
163
+ fields: fields.map((f) => ({ ...f, path: `${prefix}.${f.path}` })),
164
+ },
165
+ };
166
+ }
167
+
168
+ export class BatchRollback extends Error {
169
+ constructor(
170
+ readonly failedIndex: number,
171
+ readonly failureError: WriteErrorInfo,
172
+ ) {
173
+ super(`batch rollback at command ${failedIndex}: ${failureError.code}`);
174
+ this.name = "BatchRollback";
175
+ }
176
+ }
177
+
178
+ export type HandlerType = string | HandlerRef;
179
+
180
+ export function resolveType(type: HandlerType): string {
181
+ return typeof type === "string" ? type : type.name;
182
+ }
183
+
184
+ export function wrapToKumiko(e: unknown): KumikoError {
185
+ if (isKumikoError(e)) return e;
186
+ if (e instanceof Error) return new InternalError({ cause: e });
187
+ return new InternalError({ message: String(e) });
188
+ }