@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.
- package/CHANGELOG.md +54 -0
- package/package.json +124 -38
- package/src/__tests__/full-stack.integration.ts +2 -2
- package/src/api/auth-routes.ts +5 -5
- package/src/api/jwt.ts +2 -2
- package/src/api/route-registrars.ts +1 -1
- package/src/api/routes.ts +3 -3
- package/src/api/server.ts +6 -7
- package/src/auth/__tests__/roles.test.ts +24 -0
- package/src/auth/index.ts +7 -0
- package/src/auth/roles.ts +42 -0
- package/src/compliance/__tests__/duration-spec.test.ts +72 -0
- package/src/compliance/__tests__/profiles.test.ts +308 -0
- package/src/compliance/__tests__/sub-processors.test.ts +139 -0
- package/src/compliance/duration-spec.ts +44 -0
- package/src/compliance/index.ts +31 -0
- package/src/compliance/override-schema.ts +136 -0
- package/src/compliance/profiles.ts +427 -0
- package/src/compliance/sub-processors.ts +152 -0
- package/src/db/__tests__/big-int-field.test.ts +131 -0
- package/src/db/assert-exists-in.ts +2 -2
- package/src/db/cursor.ts +3 -3
- package/src/db/event-store-executor.ts +19 -13
- package/src/db/located-timestamp.ts +1 -1
- package/src/db/money.ts +12 -2
- package/src/db/pg-error.ts +1 -1
- package/src/db/row-helpers.ts +1 -1
- package/src/db/table-builder.ts +20 -5
- package/src/db/tenant-db.ts +9 -9
- package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
- package/src/engine/__tests__/boot-validator-api-exposure.test.ts +142 -0
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +570 -0
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +160 -0
- package/src/engine/__tests__/build-target.test.ts +135 -0
- package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
- package/src/engine/__tests__/entity-handlers.test.ts +3 -3
- package/src/engine/__tests__/event-helpers.test.ts +4 -4
- package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
- package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
- package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
- package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
- package/src/engine/__tests__/raw-table.test.ts +2 -2
- package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
- package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
- package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
- package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
- package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
- package/src/engine/__tests__/steps-read.test.ts +142 -0
- package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
- package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
- package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
- package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
- package/src/engine/__tests__/steps-workflow.test.ts +198 -0
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
- package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
- package/src/engine/boot-validator/api-ext.ts +77 -0
- package/src/engine/boot-validator/config-deps.ts +163 -0
- package/src/engine/boot-validator/entity-handler.ts +466 -0
- package/src/engine/boot-validator/index.ts +159 -0
- package/src/engine/boot-validator/ownership.ts +198 -0
- package/src/engine/boot-validator/pii-retention.ts +155 -0
- package/src/engine/boot-validator/screens-nav.ts +624 -0
- package/src/engine/boot-validator.ts +1 -1528
- package/src/engine/build-app-schema.ts +1 -1
- package/src/engine/build-target.ts +99 -0
- package/src/engine/codemod/index.ts +15 -0
- package/src/engine/codemod/pipeline-codemod.ts +641 -0
- package/src/engine/config-helpers.ts +9 -19
- package/src/engine/constants.ts +1 -1
- package/src/engine/define-feature.ts +127 -9
- package/src/engine/define-handler.ts +89 -3
- package/src/engine/define-roles.ts +2 -2
- package/src/engine/define-step.ts +28 -0
- package/src/engine/define-workflow.ts +110 -0
- package/src/engine/entity-handlers.ts +10 -9
- package/src/engine/event-helpers.ts +4 -4
- package/src/engine/extension-names.ts +105 -0
- package/src/engine/extensions/user-data.ts +106 -0
- package/src/engine/factories.ts +26 -16
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
- package/src/engine/feature-ast/extractors/index.ts +74 -0
- package/src/engine/feature-ast/extractors/round1.ts +110 -0
- package/src/engine/feature-ast/extractors/round2.ts +253 -0
- package/src/engine/feature-ast/extractors/round3.ts +471 -0
- package/src/engine/feature-ast/extractors/round4.ts +1365 -0
- package/src/engine/feature-ast/extractors/round5.ts +72 -0
- package/src/engine/feature-ast/extractors/round6.ts +66 -0
- package/src/engine/feature-ast/extractors/shared.ts +177 -0
- package/src/engine/feature-ast/parse.ts +13 -0
- package/src/engine/feature-ast/patch.ts +9 -1
- package/src/engine/feature-ast/patcher.ts +10 -3
- package/src/engine/feature-ast/patterns.ts +71 -1
- package/src/engine/feature-ast/render.ts +31 -1
- package/src/engine/index.ts +66 -2
- package/src/engine/pattern-library/__tests__/library.test.ts +11 -0
- package/src/engine/pattern-library/library.ts +78 -2
- package/src/engine/pipeline.ts +88 -0
- package/src/engine/projection-helpers.ts +1 -1
- package/src/engine/read-claim.ts +1 -1
- package/src/engine/registry.ts +30 -2
- package/src/engine/resolve-config-or-param.ts +4 -0
- package/src/engine/run-pipeline.ts +162 -0
- package/src/engine/schema-builder.ts +10 -4
- package/src/engine/state-machine.ts +1 -1
- package/src/engine/steps/_drizzle-boundary.ts +19 -0
- package/src/engine/steps/_duration-utils.ts +33 -0
- package/src/engine/steps/_no-return-guard.ts +21 -0
- package/src/engine/steps/_resolver-utils.ts +42 -0
- package/src/engine/steps/_step-dispatch-constants.ts +38 -0
- package/src/engine/steps/aggregate-append-event.ts +56 -0
- package/src/engine/steps/aggregate-create.ts +56 -0
- package/src/engine/steps/aggregate-update.ts +68 -0
- package/src/engine/steps/branch.ts +84 -0
- package/src/engine/steps/call-feature.ts +49 -0
- package/src/engine/steps/compute.ts +41 -0
- package/src/engine/steps/for-each.ts +111 -0
- package/src/engine/steps/mail-send.ts +44 -0
- package/src/engine/steps/read-find-many.ts +51 -0
- package/src/engine/steps/read-find-one.ts +58 -0
- package/src/engine/steps/retry.ts +87 -0
- package/src/engine/steps/return.ts +34 -0
- package/src/engine/steps/unsafe-projection-delete.ts +46 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
- package/src/engine/steps/wait-for-event.ts +71 -0
- package/src/engine/steps/wait.ts +69 -0
- package/src/engine/steps/webhook-send.ts +71 -0
- package/src/engine/system-user.ts +1 -1
- package/src/engine/types/feature.ts +143 -1
- package/src/engine/types/fields.ts +134 -10
- package/src/engine/types/handlers.ts +18 -10
- package/src/engine/types/identifiers.ts +1 -0
- package/src/engine/types/index.ts +15 -1
- package/src/engine/types/step.ts +334 -0
- package/src/engine/types/target-ref.ts +21 -0
- package/src/engine/types/tree-node.ts +130 -0
- package/src/engine/types/workspace.ts +7 -0
- package/src/engine/validate-projection-allowlist.ts +161 -0
- package/src/event-store/snapshot.ts +1 -1
- package/src/event-store/upcaster-dead-letter.ts +1 -1
- package/src/event-store/upcaster.ts +1 -1
- package/src/files/__tests__/read-stream.test.ts +105 -0
- package/src/files/__tests__/write-stream.test.ts +233 -0
- package/src/files/__tests__/zip-stream.test.ts +357 -0
- package/src/files/file-routes.ts +1 -1
- package/src/files/in-memory-provider.ts +38 -0
- package/src/files/index.ts +3 -0
- package/src/files/local-provider.ts +58 -1
- package/src/files/types.ts +36 -8
- package/src/files/zip-stream.ts +251 -0
- package/src/jobs/job-runner.ts +10 -10
- package/src/lifecycle/lifecycle.ts +0 -3
- package/src/logging/index.ts +1 -0
- package/src/logging/pino-logger.ts +11 -7
- package/src/logging/utils.ts +24 -0
- package/src/observability/prometheus-meter.ts +7 -5
- package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
- package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
- package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
- package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
- package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
- package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
- package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
- package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
- package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
- package/src/pipeline/append-event-core.ts +22 -6
- package/src/pipeline/dispatcher-utils.ts +188 -0
- package/src/pipeline/dispatcher.ts +63 -283
- package/src/pipeline/distributed-lock.ts +1 -1
- package/src/pipeline/entity-cache.ts +2 -2
- package/src/pipeline/event-consumer-state.ts +0 -13
- package/src/pipeline/event-dispatcher.ts +4 -4
- package/src/pipeline/index.ts +0 -2
- package/src/pipeline/lifecycle-pipeline.ts +6 -12
- package/src/pipeline/msp-rebuild.ts +5 -5
- package/src/pipeline/multi-stream-apply-context.ts +6 -7
- package/src/pipeline/projection-rebuild.ts +2 -2
- package/src/pipeline/projection-state.ts +0 -12
- package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
- package/src/rate-limit/resolver.ts +1 -1
- package/src/search/in-memory-adapter.ts +1 -1
- package/src/search/meilisearch-adapter.ts +3 -3
- package/src/search/types.ts +1 -1
- package/src/secrets/leak-guard.ts +2 -2
- package/src/stack/request-helper.ts +9 -5
- package/src/stack/test-stack.ts +1 -1
- package/src/testing/handler-context.ts +4 -4
- package/src/testing/http-cookies.ts +1 -1
- package/src/time/tz-context.ts +1 -2
- package/src/ui-types/index.ts +4 -0
- package/src/engine/feature-ast/extractors.ts +0 -2562
package/src/engine/index.ts
CHANGED
|
@@ -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 {
|
|
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: "
|
|
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)
|
package/src/engine/read-claim.ts
CHANGED
|
@@ -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
|
}
|
package/src/engine/registry.ts
CHANGED
|
@@ -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
|
-
|
|
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);
|