@cosmicdrift/kumiko-framework 0.2.2 → 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 (191) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/package.json +124 -38
  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/auth/__tests__/roles.test.ts +24 -0
  10. package/src/auth/index.ts +7 -0
  11. package/src/auth/roles.ts +42 -0
  12. package/src/compliance/__tests__/duration-spec.test.ts +72 -0
  13. package/src/compliance/__tests__/profiles.test.ts +308 -0
  14. package/src/compliance/__tests__/sub-processors.test.ts +139 -0
  15. package/src/compliance/duration-spec.ts +44 -0
  16. package/src/compliance/index.ts +31 -0
  17. package/src/compliance/override-schema.ts +136 -0
  18. package/src/compliance/profiles.ts +427 -0
  19. package/src/compliance/sub-processors.ts +152 -0
  20. package/src/db/__tests__/big-int-field.test.ts +131 -0
  21. package/src/db/assert-exists-in.ts +2 -2
  22. package/src/db/cursor.ts +3 -3
  23. package/src/db/event-store-executor.ts +19 -13
  24. package/src/db/located-timestamp.ts +1 -1
  25. package/src/db/money.ts +12 -2
  26. package/src/db/pg-error.ts +1 -1
  27. package/src/db/row-helpers.ts +1 -1
  28. package/src/db/table-builder.ts +20 -5
  29. package/src/db/tenant-db.ts +9 -9
  30. package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
  31. package/src/engine/__tests__/boot-validator-api-exposure.test.ts +142 -0
  32. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +570 -0
  33. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +160 -0
  34. package/src/engine/__tests__/build-target.test.ts +135 -0
  35. package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
  36. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  37. package/src/engine/__tests__/event-helpers.test.ts +4 -4
  38. package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
  39. package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
  40. package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
  41. package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
  42. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
  43. package/src/engine/__tests__/raw-table.test.ts +2 -2
  44. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
  45. package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
  46. package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
  47. package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
  48. package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
  49. package/src/engine/__tests__/steps-read.test.ts +142 -0
  50. package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
  51. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
  52. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
  53. package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
  54. package/src/engine/__tests__/steps-workflow.test.ts +198 -0
  55. package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
  56. package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
  57. package/src/engine/boot-validator/api-ext.ts +77 -0
  58. package/src/engine/boot-validator/config-deps.ts +163 -0
  59. package/src/engine/boot-validator/entity-handler.ts +466 -0
  60. package/src/engine/boot-validator/index.ts +159 -0
  61. package/src/engine/boot-validator/ownership.ts +198 -0
  62. package/src/engine/boot-validator/pii-retention.ts +155 -0
  63. package/src/engine/boot-validator/screens-nav.ts +624 -0
  64. package/src/engine/boot-validator.ts +1 -1528
  65. package/src/engine/build-app-schema.ts +1 -1
  66. package/src/engine/build-target.ts +99 -0
  67. package/src/engine/codemod/index.ts +15 -0
  68. package/src/engine/codemod/pipeline-codemod.ts +641 -0
  69. package/src/engine/config-helpers.ts +9 -19
  70. package/src/engine/constants.ts +1 -1
  71. package/src/engine/define-feature.ts +127 -9
  72. package/src/engine/define-handler.ts +89 -3
  73. package/src/engine/define-roles.ts +2 -2
  74. package/src/engine/define-step.ts +28 -0
  75. package/src/engine/define-workflow.ts +110 -0
  76. package/src/engine/entity-handlers.ts +10 -9
  77. package/src/engine/event-helpers.ts +4 -4
  78. package/src/engine/extension-names.ts +105 -0
  79. package/src/engine/extensions/user-data.ts +106 -0
  80. package/src/engine/factories.ts +26 -16
  81. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
  82. package/src/engine/feature-ast/extractors/index.ts +74 -0
  83. package/src/engine/feature-ast/extractors/round1.ts +110 -0
  84. package/src/engine/feature-ast/extractors/round2.ts +253 -0
  85. package/src/engine/feature-ast/extractors/round3.ts +471 -0
  86. package/src/engine/feature-ast/extractors/round4.ts +1365 -0
  87. package/src/engine/feature-ast/extractors/round5.ts +72 -0
  88. package/src/engine/feature-ast/extractors/round6.ts +66 -0
  89. package/src/engine/feature-ast/extractors/shared.ts +177 -0
  90. package/src/engine/feature-ast/parse.ts +13 -0
  91. package/src/engine/feature-ast/patch.ts +9 -1
  92. package/src/engine/feature-ast/patcher.ts +10 -3
  93. package/src/engine/feature-ast/patterns.ts +71 -1
  94. package/src/engine/feature-ast/render.ts +31 -1
  95. package/src/engine/index.ts +66 -2
  96. package/src/engine/pattern-library/__tests__/library.test.ts +11 -0
  97. package/src/engine/pattern-library/library.ts +78 -2
  98. package/src/engine/pipeline.ts +88 -0
  99. package/src/engine/projection-helpers.ts +1 -1
  100. package/src/engine/read-claim.ts +1 -1
  101. package/src/engine/registry.ts +30 -2
  102. package/src/engine/resolve-config-or-param.ts +4 -0
  103. package/src/engine/run-pipeline.ts +162 -0
  104. package/src/engine/schema-builder.ts +10 -4
  105. package/src/engine/state-machine.ts +1 -1
  106. package/src/engine/steps/_drizzle-boundary.ts +19 -0
  107. package/src/engine/steps/_duration-utils.ts +33 -0
  108. package/src/engine/steps/_no-return-guard.ts +21 -0
  109. package/src/engine/steps/_resolver-utils.ts +42 -0
  110. package/src/engine/steps/_step-dispatch-constants.ts +38 -0
  111. package/src/engine/steps/aggregate-append-event.ts +56 -0
  112. package/src/engine/steps/aggregate-create.ts +56 -0
  113. package/src/engine/steps/aggregate-update.ts +68 -0
  114. package/src/engine/steps/branch.ts +84 -0
  115. package/src/engine/steps/call-feature.ts +49 -0
  116. package/src/engine/steps/compute.ts +41 -0
  117. package/src/engine/steps/for-each.ts +111 -0
  118. package/src/engine/steps/mail-send.ts +44 -0
  119. package/src/engine/steps/read-find-many.ts +51 -0
  120. package/src/engine/steps/read-find-one.ts +58 -0
  121. package/src/engine/steps/retry.ts +87 -0
  122. package/src/engine/steps/return.ts +34 -0
  123. package/src/engine/steps/unsafe-projection-delete.ts +46 -0
  124. package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
  125. package/src/engine/steps/wait-for-event.ts +71 -0
  126. package/src/engine/steps/wait.ts +69 -0
  127. package/src/engine/steps/webhook-send.ts +71 -0
  128. package/src/engine/system-user.ts +1 -1
  129. package/src/engine/types/feature.ts +143 -1
  130. package/src/engine/types/fields.ts +134 -10
  131. package/src/engine/types/handlers.ts +18 -10
  132. package/src/engine/types/identifiers.ts +1 -0
  133. package/src/engine/types/index.ts +15 -1
  134. package/src/engine/types/step.ts +334 -0
  135. package/src/engine/types/target-ref.ts +21 -0
  136. package/src/engine/types/tree-node.ts +130 -0
  137. package/src/engine/types/workspace.ts +7 -0
  138. package/src/engine/validate-projection-allowlist.ts +161 -0
  139. package/src/event-store/snapshot.ts +1 -1
  140. package/src/event-store/upcaster-dead-letter.ts +1 -1
  141. package/src/event-store/upcaster.ts +1 -1
  142. package/src/files/__tests__/read-stream.test.ts +105 -0
  143. package/src/files/__tests__/write-stream.test.ts +233 -0
  144. package/src/files/__tests__/zip-stream.test.ts +357 -0
  145. package/src/files/file-routes.ts +1 -1
  146. package/src/files/in-memory-provider.ts +38 -0
  147. package/src/files/index.ts +3 -0
  148. package/src/files/local-provider.ts +58 -1
  149. package/src/files/types.ts +36 -8
  150. package/src/files/zip-stream.ts +251 -0
  151. package/src/jobs/job-runner.ts +10 -10
  152. package/src/lifecycle/lifecycle.ts +0 -3
  153. package/src/logging/index.ts +1 -0
  154. package/src/logging/pino-logger.ts +11 -7
  155. package/src/logging/utils.ts +24 -0
  156. package/src/observability/prometheus-meter.ts +7 -5
  157. package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
  158. package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
  159. package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
  160. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
  161. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
  162. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
  163. package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
  164. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
  165. package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
  166. package/src/pipeline/append-event-core.ts +22 -6
  167. package/src/pipeline/dispatcher-utils.ts +188 -0
  168. package/src/pipeline/dispatcher.ts +63 -283
  169. package/src/pipeline/distributed-lock.ts +1 -1
  170. package/src/pipeline/entity-cache.ts +2 -2
  171. package/src/pipeline/event-consumer-state.ts +0 -13
  172. package/src/pipeline/event-dispatcher.ts +4 -4
  173. package/src/pipeline/index.ts +0 -2
  174. package/src/pipeline/lifecycle-pipeline.ts +6 -12
  175. package/src/pipeline/msp-rebuild.ts +5 -5
  176. package/src/pipeline/multi-stream-apply-context.ts +6 -7
  177. package/src/pipeline/projection-rebuild.ts +2 -2
  178. package/src/pipeline/projection-state.ts +0 -12
  179. package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
  180. package/src/rate-limit/resolver.ts +1 -1
  181. package/src/search/in-memory-adapter.ts +1 -1
  182. package/src/search/meilisearch-adapter.ts +3 -3
  183. package/src/search/types.ts +1 -1
  184. package/src/secrets/leak-guard.ts +2 -2
  185. package/src/stack/request-helper.ts +9 -5
  186. package/src/stack/test-stack.ts +1 -1
  187. package/src/testing/handler-context.ts +4 -4
  188. package/src/testing/http-cookies.ts +1 -1
  189. package/src/time/tz-context.ts +1 -2
  190. package/src/ui-types/index.ts +4 -0
  191. package/src/engine/feature-ast/extractors.ts +0 -2562
@@ -3,6 +3,7 @@
3
3
  export { hasAccess } from "./access";
4
4
  export { validateBoot } from "./boot-validator";
5
5
  export { buildAppSchema } from "./build-app-schema";
6
+ export { buildTarget } from "./build-target";
6
7
  export { access, createSystemConfig, createTenantConfig, createUserConfig } from "./config-helpers";
7
8
  export type { SystemHookName } from "./constants";
8
9
  export {
@@ -18,9 +19,16 @@ export {
18
19
  export type { App, AppConfig } from "./create-app";
19
20
  export { createApp } from "./create-app";
20
21
  export { defineFeature } from "./define-feature";
21
- export type { QueryHandlerDefinition, WriteHandlerDefinition } from "./define-handler";
22
+ export type {
23
+ QueryHandlerDefinition,
24
+ WriteHandlerDefinition,
25
+ WriteHandlerInput,
26
+ } from "./define-handler";
22
27
  export { defineQueryHandler, defineWriteHandler } from "./define-handler";
23
28
  export { defineRoles } from "./define-roles";
29
+ export { defineStep, getStep, listStepKinds } from "./define-step";
30
+ export type { WorkflowDefinition, WorkflowInput, WorkflowTrigger } from "./define-workflow";
31
+ export { computeDefinitionFingerprint, defineWorkflow } from "./define-workflow";
24
32
  export type { ToggleReader } from "./effective-features";
25
33
  export { computeEffectiveFeatures } from "./effective-features";
26
34
  export {
@@ -41,7 +49,25 @@ export {
41
49
  } from "./entity-handlers";
42
50
  export type { EmitCtx } from "./event-helpers";
43
51
  export { emitEvent, typedPayload } from "./event-helpers";
52
+ export type { KumikoExtensionName } from "./extension-names";
53
+ export {
54
+ EXT_EXTERNAL_RESOURCE,
55
+ EXT_INFRA_RESOURCE,
56
+ EXT_SEARCH_ADAPTER,
57
+ EXT_STORAGE_PROVIDER,
58
+ EXT_TENANT_DATA,
59
+ EXT_USER_DATA,
60
+ } from "./extension-names";
61
+ export type {
62
+ UserDataDeleteHook,
63
+ UserDataDeleteStrategy,
64
+ UserDataExportHook,
65
+ UserDataExportSnippet,
66
+ UserDataExtensionHooks,
67
+ UserDataHookCtx,
68
+ } from "./extensions/user-data";
44
69
  export {
70
+ createBigIntField,
45
71
  createBooleanField,
46
72
  createDateField,
47
73
  createEmbeddedField,
@@ -107,6 +133,7 @@ export {
107
133
  } from "./field-access";
108
134
  export type { OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule } from "./ownership";
109
135
  export { from } from "./ownership";
136
+ export { buildPipelineSteps, pipeline } from "./pipeline";
110
137
  export { defineApply, defineMspApply, setFields } from "./projection-helpers";
111
138
  export type { BuiltinQnType, ParsedQn, QnType } from "./qualified-name";
112
139
  export { isValidQn, parseQn, QnTypes, qn, toKebab } from "./qualified-name";
@@ -115,9 +142,22 @@ export { createRegistry } from "./registry";
115
142
  export type { ClampInfo, ResolveOptions } from "./resolve-config-or-param";
116
143
  export { resolveConfigOrParam } from "./resolve-config-or-param";
117
144
  export { runsInLane } from "./run-in";
145
+ export type { StepListOutcome } from "./run-pipeline";
146
+ export { runPipeline, runStepList } from "./run-pipeline";
118
147
  export { buildInsertSchema, buildUpdateSchema } from "./schema-builder";
119
148
  export type { TransitionGraph } from "./state-machine";
120
149
  export { defineTransitions, guardTransition } from "./state-machine";
150
+ export {
151
+ SUSPEND_SENTINEL,
152
+ WORKFLOW_AGGREGATE_TYPE,
153
+ WORKFLOW_RESUMED_TYPE,
154
+ WORKFLOW_RETRY_SCHEDULED_TYPE,
155
+ WORKFLOW_RUN_COMPLETED_TYPE,
156
+ WORKFLOW_RUN_FAILED_TYPE,
157
+ WORKFLOW_RUN_STARTED_TYPE,
158
+ WORKFLOW_WAITING_FOR_EVENT_TYPE,
159
+ WORKFLOW_WAITING_TYPE,
160
+ } from "./steps/_step-dispatch-constants";
121
161
  export {
122
162
  ANONYMOUS_ROLE,
123
163
  ANONYMOUS_USER_ID,
@@ -139,11 +179,11 @@ export type {
139
179
  AppContext,
140
180
  AppendEventArgs,
141
181
  AppendEventFn,
142
- AppendEventUnsafeFn,
143
182
  AuthClaimsContext,
144
183
  AuthClaimsFn,
145
184
  AuthClaimsHookDef,
146
185
  BelongsToRelation,
186
+ BigIntFieldDef,
147
187
  BooleanFieldDef,
148
188
  CamelToKebab,
149
189
  ClaimKeyDefinition,
@@ -220,6 +260,7 @@ export type {
220
260
  NotifyPriority,
221
261
  NumberFieldDef,
222
262
  OnDeleteStrategy,
263
+ PiiAnnotations,
223
264
  PlatformComponent,
224
265
  PostDeleteHookFn,
225
266
  PostSaveHookFn,
@@ -234,17 +275,28 @@ export type {
234
275
  QueryHandlerFn,
235
276
  Registry,
236
277
  RelationDefinition,
278
+ RetentionDef,
237
279
  RowAction,
238
280
  SaveContext,
239
281
  ScreenDefinition,
240
282
  ScreenSlots,
241
283
  SelectFieldDef,
242
284
  SessionUser,
285
+ Subscribe,
286
+ TargetRef,
243
287
  TenantId,
244
288
  TextFieldDef,
245
289
  ToolbarAction,
246
290
  TranslationKeys,
247
291
  TranslationsDef,
292
+ TreeAction,
293
+ TreeActionDef,
294
+ TreeActionsHandle,
295
+ TreeChildrenSubscribe,
296
+ TreeContext,
297
+ TreeNode,
298
+ TreeNodeState,
299
+ UnsafeAppendEventFn,
248
300
  ValidationError,
249
301
  ValidationHookFn,
250
302
  WorkspaceDefinition,
@@ -257,4 +309,16 @@ export { DEFAULT_CURRENCIES, HookPhases } from "./types";
257
309
  export { resolveName, withResponseData } from "./types/handlers";
258
310
  export { isSystemTenant, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers";
259
311
  export { normalizeEditField, normalizeListColumn } from "./types/screen";
312
+ export type {
313
+ PipelineBuildCtx,
314
+ PipelineCtx,
315
+ PipelineDef,
316
+ StepBuilder,
317
+ StepDef,
318
+ StepFailureStrategy,
319
+ StepInstance,
320
+ StepKind,
321
+ StepNamespace,
322
+ StepResolver,
323
+ } from "./types/step";
260
324
  export { runValidation } from "./validation";
@@ -55,6 +55,10 @@ const ALL_KINDS: readonly FeaturePatternKind[] = [
55
55
  "defineEvent",
56
56
  "eventMigration",
57
57
  "extendsRegistrar",
58
+ "usesApi",
59
+ "exposesApi",
60
+ "treeActions",
61
+ "tree",
58
62
  "unknown",
59
63
  ];
60
64
 
@@ -338,8 +342,15 @@ function makePlaceholderPattern(kind: FeaturePatternKind): FeaturePattern {
338
342
  extensionName: "x",
339
343
  defBody: PLACEHOLDER_BODY_LOC,
340
344
  };
345
+ case "treeActions":
346
+ return { kind, source: PLACEHOLDER_LOC, definitions: {} };
347
+ case "tree":
348
+ return { kind, source: PLACEHOLDER_LOC, providerBody: PLACEHOLDER_BODY_LOC };
341
349
  case "unknown":
342
350
  return { kind, source: PLACEHOLDER_LOC, methodName: "x" };
351
+ case "usesApi":
352
+ case "exposesApi":
353
+ return { kind, source: PLACEHOLDER_LOC, apiName: "demo.api" };
343
354
  default: {
344
355
  const _exhaustive: never = kind;
345
356
  return _exhaustive;
@@ -597,7 +597,7 @@ const writeHandlerSchema: PatternFormSchema = {
597
597
  input: "json-readonly",
598
598
  },
599
599
  {
600
- path: "skipTransitionGuard",
600
+ path: "unsafeSkipTransitionGuard",
601
601
  label: { en: "Skip transition guard", de: "Übergangs-Guard überspringen" },
602
602
  input: "boolean",
603
603
  },
@@ -1020,6 +1020,78 @@ const extendsRegistrarSchema: PatternFormSchema = {
1020
1020
  ],
1021
1021
  };
1022
1022
 
1023
+ const usesApiSchema: PatternFormSchema = {
1024
+ kind: "usesApi",
1025
+ label: { en: "Uses API", de: "Nutzt API" },
1026
+ summary: {
1027
+ en: "Cross-feature handler-ID dependency. Boot fails if no other feature exposes it.",
1028
+ },
1029
+ category: "advanced",
1030
+ editability: "static",
1031
+ fields: [
1032
+ {
1033
+ path: "apiName",
1034
+ label: { en: "API name", de: "API-Name" },
1035
+ input: "text",
1036
+ required: true,
1037
+ },
1038
+ ],
1039
+ };
1040
+
1041
+ const exposesApiSchema: PatternFormSchema = {
1042
+ kind: "exposesApi",
1043
+ label: { en: "Exposes API", de: "Stellt API bereit" },
1044
+ summary: { en: "Declares this feature provides a handler matching the cross-feature contract." },
1045
+ category: "advanced",
1046
+ editability: "static",
1047
+ fields: [
1048
+ {
1049
+ path: "apiName",
1050
+ label: { en: "API name", de: "API-Name" },
1051
+ input: "text",
1052
+ required: true,
1053
+ },
1054
+ ],
1055
+ };
1056
+
1057
+ // Visual-Tree pattern schemas. treeActions is a static map (Designer
1058
+ // renders the action-name → ActionDef pairs as a nested form), tree is
1059
+ // closure-only (Designer shows the provider body as read-only code).
1060
+ const treeActionsSchema: PatternFormSchema = {
1061
+ kind: "treeActions",
1062
+ label: { en: "Tree actions", de: "Tree-Actions" },
1063
+ summary: { en: "Action verbs the Visual-Tree dispatches via buildTarget." },
1064
+ category: "ui",
1065
+ editability: "static",
1066
+ singleton: true,
1067
+ fields: [
1068
+ {
1069
+ path: "definitions",
1070
+ label: { en: "Action definitions", de: "Action-Definitionen" },
1071
+ input: "json-readonly",
1072
+ readOnly: true,
1073
+ },
1074
+ ],
1075
+ };
1076
+
1077
+ const treeSchema: PatternFormSchema = {
1078
+ kind: "tree",
1079
+ label: { en: "Tree provider", de: "Tree-Provider" },
1080
+ summary: { en: "Subscribe-Function emitting top-level Visual-Tree nodes." },
1081
+ category: "ui",
1082
+ editability: "opaque",
1083
+ singleton: true,
1084
+ fields: [
1085
+ {
1086
+ path: "providerBody",
1087
+ label: { en: "Provider body (source)", de: "Provider-Body (Source)" },
1088
+ input: "code-block",
1089
+ language: "typescript",
1090
+ readOnly: true,
1091
+ },
1092
+ ],
1093
+ };
1094
+
1023
1095
  const unknownSchema: PatternFormSchema = {
1024
1096
  kind: "unknown",
1025
1097
  label: { en: "Unknown call", de: "Unbekannter Call" },
@@ -1077,8 +1149,12 @@ export const PATTERN_LIBRARY: Readonly<Record<FeaturePatternKind, PatternFormSch
1077
1149
  defineEvent: defineEventSchema,
1078
1150
  eventMigration: eventMigrationSchema,
1079
1151
  extendsRegistrar: extendsRegistrarSchema,
1152
+ usesApi: usesApiSchema,
1153
+ exposesApi: exposesApiSchema,
1154
+ treeActions: treeActionsSchema,
1155
+ tree: treeSchema,
1080
1156
  unknown: unknownSchema,
1081
- };
1157
+ } satisfies Readonly<Record<FeaturePatternKind, PatternFormSchema>>;
1082
1158
 
1083
1159
  /**
1084
1160
  * Lookup helper — convenience over `PATTERN_LIBRARY[kind]`. Throws when
@@ -0,0 +1,88 @@
1
+ // pipeline() — public factory used in defineWriteHandler({ perform: pipeline(...) }).
2
+ //
3
+ // The closure receives { event, r } and returns the immutable list of
4
+ // step instances. `r` is the StepBuilder singleton; new tier-1 steps
5
+ // add a builder factory in steps/<x>.ts and expose it under `step` below.
6
+ //
7
+ // `steps` and `scope` are NOT exposed at build time — they only exist on
8
+ // the resolver-side PipelineCtx (run-pipeline.ts). Resolvers that need
9
+ // prior step results destructure them from the resolver's ctx.
10
+ //
11
+ // Naming note (Followup #1): the public `pipeline()` helper shares its
12
+ // name with the internal `packages/framework/src/pipeline/` directory
13
+ // (dispatcher, lifecycle, outbox-poller). No user-visible collision —
14
+ // the internal directory isn't an export — but maintainer repo-wide
15
+ // grep for `pipeline` returns mixed results. If a rename ever lands,
16
+ // candidates are `r.steps([...])` for the public API or
17
+ // `engine-pipeline/` / `runtime/` for the internal directory.
18
+ // Decision-cost grows with each new caller; the rename window narrows
19
+ // after the first external consumer.
20
+
21
+ import { buildAggregateAppendEventStep } from "./steps/aggregate-append-event";
22
+ import { buildAggregateCreateStep } from "./steps/aggregate-create";
23
+ import { buildAggregateUpdateStep } from "./steps/aggregate-update";
24
+ import { buildBranchStep } from "./steps/branch";
25
+ import { buildCallFeatureStep } from "./steps/call-feature";
26
+ import { buildComputeStep } from "./steps/compute";
27
+ import { buildForEachStep } from "./steps/for-each";
28
+ import { buildMailSendStep } from "./steps/mail-send";
29
+ import { buildReadFindManyStep } from "./steps/read-find-many";
30
+ import { buildReadFindOneStep } from "./steps/read-find-one";
31
+ import { buildRetryStep } from "./steps/retry";
32
+ import { buildReturnStep } from "./steps/return";
33
+ import { buildUnsafeProjectionDeleteStep } from "./steps/unsafe-projection-delete";
34
+ import { buildUnsafeProjectionUpsertStep } from "./steps/unsafe-projection-upsert";
35
+ import { buildWaitStep } from "./steps/wait";
36
+ import { buildWaitForEventStep } from "./steps/wait-for-event";
37
+ import { buildWebhookSendStep } from "./steps/webhook-send";
38
+ import type { WriteEvent } from "./types/handlers";
39
+ import type { PipelineBuildCtx, PipelineDef, StepBuilder, StepInstance } from "./types/step";
40
+
41
+ const stepBuilder: StepBuilder = {
42
+ step: {
43
+ return: buildReturnStep,
44
+ compute: buildComputeStep,
45
+ branch: buildBranchStep,
46
+ forEach: buildForEachStep,
47
+ unsafeProjectionUpsert: buildUnsafeProjectionUpsertStep,
48
+ unsafeProjectionDelete: buildUnsafeProjectionDeleteStep,
49
+ aggregate: {
50
+ create: buildAggregateCreateStep,
51
+ update: buildAggregateUpdateStep,
52
+ appendEvent: buildAggregateAppendEventStep,
53
+ },
54
+ read: {
55
+ findOne: buildReadFindOneStep,
56
+ findMany: buildReadFindManyStep,
57
+ },
58
+ webhook: {
59
+ send: buildWebhookSendStep,
60
+ },
61
+ mail: {
62
+ send: buildMailSendStep,
63
+ },
64
+ callFeature: buildCallFeatureStep,
65
+ // Tier-3 / Workflow-only steps
66
+ wait: buildWaitStep,
67
+ waitForEvent: buildWaitForEventStep,
68
+ retry: buildRetryStep,
69
+ },
70
+ };
71
+
72
+ export function pipeline<TPayload = unknown, TData = unknown>(
73
+ closure: (ctx: PipelineBuildCtx<TPayload>) => readonly StepInstance[],
74
+ ): PipelineDef<TPayload, TData> {
75
+ return {
76
+ __kind: "pipeline",
77
+ build: closure,
78
+ };
79
+ }
80
+
81
+ // Internal: invoked by run-pipeline.ts to materialise the step list.
82
+ // Not exported from the engine barrel — pipeline-internal plumbing.
83
+ export function buildPipelineSteps<TPayload>(
84
+ pipelineDef: PipelineDef<TPayload>,
85
+ event: WriteEvent<TPayload>,
86
+ ): readonly StepInstance[] {
87
+ return pipelineDef.build({ event, r: stepBuilder });
88
+ }
@@ -76,7 +76,7 @@ export function setFields(
76
76
  // strict about the concrete row, so we feed it the erased value; the
77
77
  // type-safety guarantee for `values` lives at the setFields call-site.
78
78
  // biome-ignore lint/suspicious/noExplicitAny: see note above.
79
- const set = values as any;
79
+ const set = values as any; // @cast-boundary engine-bridge
80
80
  await tx
81
81
  .update(table)
82
82
  .set(set)
@@ -27,5 +27,5 @@ export function readClaim<T extends ClaimKeyType>(
27
27
  if (!claims) return undefined;
28
28
  const raw = claims[handle.name];
29
29
  if (raw === undefined || raw === null) return undefined;
30
- return raw as ClaimKeyJsType<T>;
30
+ return raw as ClaimKeyJsType<T>; // @cast-boundary schema-walk
31
31
  }
@@ -35,6 +35,8 @@ import type {
35
35
  ScreenDefinition,
36
36
  SecretKeyDefinition,
37
37
  TranslationKeys,
38
+ TreeActionDef,
39
+ TreeChildrenSubscribe,
38
40
  WorkspaceDefinition,
39
41
  WriteHandlerDef,
40
42
  } from "./types";
@@ -198,6 +200,14 @@ export function createRegistry(features: readonly FeatureDefinition[]): Registry
198
200
  const navsByWorkspace = new Map<string, string[]>();
199
201
  let defaultWorkspace: WorkspaceDefinition | undefined;
200
202
 
203
+ // Visual-Tree-Provider — keyed by declaring feature name (NOT qualified;
204
+ // ein Feature liefert genau einen Provider). Visual-Tree-Component
205
+ // iteriert die Map zur Mount-Zeit. Tree-Actions parallel — featureName
206
+ // → erased Action-Map (compile-time-typed Variante geht über
207
+ // setup-export-handle, siehe FeatureRegistrar.treeActions docs).
208
+ const treeProvidersMap = new Map<string, TreeChildrenSubscribe>();
209
+ const treeActionsMap = new Map<string, Readonly<Record<string, TreeActionDef>>>();
210
+
201
211
  // Local alias for readability — `qualifyEntityName` is the shared helper
202
212
  // from qualified-name.ts, also used by validateBoot to keep ingest and
203
213
  // validation in lockstep on the qualification rule.
@@ -596,6 +606,16 @@ export function createRegistry(features: readonly FeatureDefinition[]): Registry
596
606
  }
597
607
  }
598
608
 
609
+ // Visual-Tree slots — at-most-one per feature (only-once-guard im
610
+ // registrar). Erased Maps für Runtime-Lookup; compile-time-typed
611
+ // Surface läuft über FeatureDefinition.exports (TreeActionsHandle).
612
+ if (feature.treeProvider !== undefined) {
613
+ treeProvidersMap.set(feature.name, feature.treeProvider);
614
+ }
615
+ if (feature.treeActions !== undefined) {
616
+ treeActionsMap.set(feature.name, feature.treeActions);
617
+ }
618
+
599
619
  // Auth-claims hooks: order of registration is preserved. Feature name is
600
620
  // captured alongside so the resolver can apply the auto-prefix at merge
601
621
  // time — the feature author never ships pre-prefixed keys.
@@ -984,7 +1004,7 @@ export function createRegistry(features: readonly FeatureDefinition[]): Registry
984
1004
  const allHandlerNames = new Set([...writeHandlerMap.keys(), ...queryHandlerMap.keys()]);
985
1005
  for (const [qualifiedName, notifDef] of notificationMap) {
986
1006
  // Both maps are populated in lockstep — same key-set by construction.
987
- const featureName = notificationFeatureMap.get(qualifiedName) as string;
1007
+ const featureName = notificationFeatureMap.get(qualifiedName) as string; // @cast-boundary engine-bridge
988
1008
  // I'll try the easy path first: if the trigger is already a fully qualified QN
989
1009
  // (cross-feature), I take it as-is. Otherwise I qualify with the own feature —
990
1010
  // as a write handler first (the common case), then as a query. If nothing
@@ -1124,7 +1144,7 @@ export function createRegistry(features: readonly FeatureDefinition[]): Registry
1124
1144
  },
1125
1145
 
1126
1146
  getRelations(entityName: string): EntityRelations {
1127
- return (relationMap.get(entityName) ?? {}) as EntityRelations;
1147
+ return (relationMap.get(entityName) ?? {}) as EntityRelations; // @cast-boundary schema-walk
1128
1148
  },
1129
1149
 
1130
1150
  getSearchIncludes(entityName: string): ReadonlyMap<string, readonly string[]> {
@@ -1355,6 +1375,14 @@ export function createRegistry(features: readonly FeatureDefinition[]): Registry
1355
1375
  getDefaultWorkspace(): WorkspaceDefinition | undefined {
1356
1376
  return defaultWorkspace;
1357
1377
  },
1378
+
1379
+ getTreeProviders(): ReadonlyMap<string, TreeChildrenSubscribe> {
1380
+ return treeProvidersMap;
1381
+ },
1382
+
1383
+ getTreeActions(featureName: string): Readonly<Record<string, TreeActionDef>> | undefined {
1384
+ return treeActionsMap.get(featureName);
1385
+ },
1358
1386
  };
1359
1387
  }
1360
1388
 
@@ -141,6 +141,10 @@ export async function resolveConfigOrParam<T extends ConfigKeyType>(
141
141
  // The caller is signalling intent; we honour the constraint instead.
142
142
  return ctx.config(handle);
143
143
  }
144
+ default: {
145
+ const _exhaustive: never = keyDef.type;
146
+ throw new Error(`resolveConfigOrParam: unhandled config key type "${_exhaustive}"`);
147
+ }
144
148
  }
145
149
  }
146
150
 
@@ -0,0 +1,162 @@
1
+ // run-pipeline — executes a PipelineDef against (event, ctx) and
2
+ // returns a WriteResult.
3
+ //
4
+ // Execution model:
5
+ // 1. Build the immutable step list by invoking the pipeline closure.
6
+ // 2. Walk the list sequentially via runStepList. For each step:
7
+ // - Look up the registered StepDef by kind.
8
+ // - Build the live PipelineCtx (handler-ctx + accumulated steps + scope).
9
+ // - Call step.run(args, ctx) — capture its return value.
10
+ // - Detect the sentinel RETURN_RESULT_KEY → end pipeline with that
11
+ // WriteResult; otherwise stash the value under resultKey if any.
12
+ // 3. If the loop exhausts without a `return` step, surface a loud error
13
+ // so a forgotten r.step.return doesn't fall through silently.
14
+ //
15
+ // runStepList is exported for sub-step builders (branch/forEach in M.1.6)
16
+ // — they invoke it recursively over their own step-arrays, sharing the
17
+ // outer pipeline's stepsAcc + scopeAcc maps so sub-step results
18
+ // propagate up to subsequent top-level steps.
19
+ //
20
+ // Failure-strategy is "throw" only in M.1 — runPipeline lets thrown
21
+ // errors propagate to the dispatcher's catch which maps them to
22
+ // WriteFailure / HTTP. "return" / "skip" / fallback strategies land in
23
+ // later slices together with their own integration tests.
24
+
25
+ import { getStep } from "./define-step";
26
+ import { buildPipelineSteps } from "./pipeline";
27
+ import { SUSPEND_SENTINEL } from "./steps/_step-dispatch-constants";
28
+ import { RETURN_RESULT_KEY } from "./steps/return";
29
+ import type { KumikoEventTypeMap } from "./types/event-type-map";
30
+ import type { HandlerContext, WriteEvent, WriteResult } from "./types/handlers";
31
+ import type { PipelineCtx, PipelineDef, StepInstance } from "./types/step";
32
+
33
+ // Result of walking a step-list. "return" surfaces the WriteResult of an
34
+ // r.step.return; "exhausted" means all steps ran without hitting a return
35
+ // — the caller (top-level pipeline) treats that as an error, sub-step
36
+ // callers (branch/forEach) treat it as normal completion.
37
+ // "suspended" means a Tier-3 step returned SUSPEND_SENTINEL — the pipeline
38
+ // is paused pending external (time/event) and must be resumed later.
39
+ export type StepListOutcome =
40
+ | { readonly kind: "return"; readonly result: WriteResult<unknown> }
41
+ | { readonly kind: "suspended"; readonly stepIndex: number }
42
+ | { readonly kind: "exhausted" };
43
+
44
+ export async function runPipeline<TPayload, TData, TMap extends object = KumikoEventTypeMap>(
45
+ pipelineDef: PipelineDef<TPayload, TData>,
46
+ event: WriteEvent<TPayload>,
47
+ handlerCtx: HandlerContext<TMap>,
48
+ workflow?: PipelineCtx["workflow"],
49
+ resumeFrom?: number,
50
+ ): Promise<WriteResult<TData>> {
51
+ const steps = buildPipelineSteps(pipelineDef, event);
52
+ const stepsAcc: Record<string, unknown> = {};
53
+ const scopeAcc: Record<string, unknown> = {};
54
+
55
+ const outcome = await runStepList(
56
+ steps,
57
+ event,
58
+ handlerCtx,
59
+ stepsAcc,
60
+ scopeAcc,
61
+ workflow,
62
+ resumeFrom,
63
+ );
64
+ if (outcome.kind === "return") {
65
+ // RETURN_RESULT_KEY is only produced by r.step.return, whose run()
66
+ // returns WriteResult<unknown>. The pipeline's generic TData is
67
+ // bound at build time (defineWriteHandler ↔ pipeline<P, D>(...));
68
+ // matching the runtime value to that compile-time type is the
69
+ // contract user-side. Cast crosses that boundary.
70
+ return outcome.result as WriteResult<TData>;
71
+ }
72
+
73
+ if (outcome.kind === "suspended") {
74
+ // Suspension is only valid when running inside a workflow context.
75
+ // The caller (workflow-engine) handles the suspension lifecycle;
76
+ // we throw here because runPipeline's contract requires a WriteResult.
77
+ // The workflow engine calls runStepList directly to detect suspension.
78
+ if (!workflow) {
79
+ throw new Error(
80
+ "Pipeline suspended without a workflow context — Tier-3 steps are only allowed inside defineWorkflow.",
81
+ );
82
+ }
83
+ // Return a minimal WriteResult signalling suspension. The workflow
84
+ // engine extracts the outcome from runStepList directly.
85
+ return { isSuccess: true, data: undefined } as unknown as WriteResult<TData>;
86
+ }
87
+
88
+ throw new Error(
89
+ "Pipeline ended without an r.step.return(...) — every pipeline must explicitly return a WriteResult.",
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Walk a step-list. Stateful in `stepsAcc` + `scopeAcc` (the caller's
95
+ * mutable maps) — sub-step builders share those with the outer pipeline
96
+ * so step results propagate. Returns either an early r.step.return
97
+ * outcome or an "exhausted" signal.
98
+ *
99
+ * Sub-step builders (branch.run, forEach.run) re-enter via this
100
+ * function. The same TMap-variance bridge applies — sub-steps treat
101
+ * ctx as PipelineCtx<unknown, KumikoEventTypeMap>, the runtime value
102
+ * is the outer's full HandlerContext.
103
+ */
104
+ export async function runStepList<TPayload, TMap extends object = KumikoEventTypeMap>(
105
+ steps: readonly StepInstance[],
106
+ event: WriteEvent<TPayload>,
107
+ handlerCtx: HandlerContext<TMap>,
108
+ stepsAcc: Record<string, unknown>,
109
+ scopeAcc: Record<string, unknown>,
110
+ workflow?: PipelineCtx["workflow"],
111
+ resumeFrom?: number,
112
+ ): Promise<StepListOutcome> {
113
+ for (const [i, instance] of steps.entries()) {
114
+ // On resume, skip steps at or before the resume point — their
115
+ // effects (waiting/retry-scheduled events) were already written
116
+ // during the original pipeline run. The next unexecuted step
117
+ // is at resumeFrom + 1.
118
+ if (resumeFrom !== undefined && i < resumeFrom) {
119
+ continue;
120
+ }
121
+
122
+ // When resuming, re-execute the suspended step itself. Steps
123
+ // like wait/waitForEvent detect resumption by checking if a
124
+ // waiting event for their stepIndex already exists; retry
125
+ // uses retryAttempt from the workflow context.
126
+ // Steps that don't handle resumption (read/compute/aggregate)
127
+ // re-execute naturally — idempotent reads are safe, and
128
+ // event-sourced writes append new positions.
129
+ const stepDef = getStep(instance.kind);
130
+ if (!stepDef) {
131
+ throw new Error(`Unknown step kind "${instance.kind}" at step index ${i}`);
132
+ }
133
+
134
+ const pipelineCtx: PipelineCtx<TPayload, TMap> = {
135
+ ...handlerCtx,
136
+ event,
137
+ steps: stepsAcc,
138
+ scope: scopeAcc,
139
+ ...(workflow && {
140
+ workflow: { ...workflow, stepIndex: i },
141
+ }),
142
+ } as PipelineCtx<TPayload, TMap>;
143
+
144
+ const value = await stepDef.run(instance.args, pipelineCtx as unknown as PipelineCtx);
145
+
146
+ // Tier-3 suspension: the step wrote a waiting event and returned
147
+ // SUSPEND_SENTINEL to signal the pipeline should stop. The caller
148
+ // (defineWorkflow/workflow-engine) persists the suspension state.
149
+ if (value === SUSPEND_SENTINEL) {
150
+ return { kind: "suspended", stepIndex: i };
151
+ }
152
+
153
+ const key = stepDef.resultKey?.(instance.args);
154
+ if (key === RETURN_RESULT_KEY) {
155
+ return { kind: "return", result: value as WriteResult<unknown> };
156
+ }
157
+ if (key !== undefined) {
158
+ stepsAcc[key] = value;
159
+ }
160
+ }
161
+ return { kind: "exhausted" };
162
+ }
@@ -60,6 +60,14 @@ function fieldToZod(field: FieldDefinition, currencies: readonly string[]): z.Zo
60
60
  const schema = z.number();
61
61
  return field.default !== undefined ? schema.default(field.default) : schema;
62
62
  }
63
+ case "bigInt": {
64
+ // JS-`number`-Round-trip via mode:"number"; sicher bis 2^53.
65
+ // safe-integer-Cap ist explizit damit ein Caller, der einen
66
+ // Float reinwirft (z.B. parseFloat-Bug), beim Insert sofort
67
+ // failed statt silent-Truncation zu kassieren.
68
+ const schema = z.number().int().safe();
69
+ return field.default !== undefined ? schema.default(field.default) : schema;
70
+ }
63
71
  case "money": {
64
72
  const [first, ...rest] = currencies;
65
73
  if (!first) throw new Error("No currencies configured");
@@ -165,10 +173,8 @@ export function buildUpdateSchema(
165
173
  // data via the event-store-executor's `changes` payload.
166
174
  // Cast widens the discriminated union so destructure works for variants
167
175
  // without a `default` field; remainder is structurally a FieldDefinition.
168
- const { default: _default, ...stripped } = field as FieldDefinition & {
169
- default?: unknown;
170
- };
171
- shape[name] = fieldToZod(stripped as FieldDefinition, currencies).optional();
176
+ const { default: _default, ...stripped } = field as FieldDefinition & { default?: unknown }; // @cast-boundary schema-walk
177
+ shape[name] = fieldToZod(stripped as FieldDefinition, currencies).optional(); // @cast-boundary schema-walk
172
178
  }
173
179
 
174
180
  return z.object(shape);
@@ -36,7 +36,7 @@ export function defineTransitions<const TMap extends Record<string, readonly str
36
36
  canTransition: (from, to) => internal.get(from)?.has(to) === true,
37
37
  allowedFrom: (from) => {
38
38
  const set = internal.get(from);
39
- return set ? ([...set] as TStates[]) : [];
39
+ return set ? ([...set] as TStates[]) : []; // @cast-boundary schema-walk
40
40
  },
41
41
  assertTransition: (from, to) => {
42
42
  const set = internal.get(from);