@cosmicdrift/kumiko-framework 0.2.3 → 0.4.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 +93 -0
- package/package.json +124 -39
- 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/compliance/profiles.ts +8 -8
- 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 +3 -5
- package/src/db/tenant-db.ts +9 -9
- package/src/engine/__tests__/_pipeline-test-utils.ts +23 -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 -1804
- 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 +88 -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/factories.ts +12 -12
- 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 +7 -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 +49 -1
- package/src/engine/feature-ast/render.ts +17 -1
- package/src/engine/index.ts +44 -2
- package/src/engine/pattern-library/__tests__/library.test.ts +6 -0
- package/src/engine/pattern-library/library.ts +42 -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 +2 -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 +93 -1
- package/src/engine/types/handlers.ts +18 -10
- package/src/engine/types/index.ts +11 -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 +132 -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/file-routes.ts +1 -1
- package/src/files/types.ts +2 -2
- 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 -2602
|
@@ -56,15 +56,20 @@ import type {
|
|
|
56
56
|
SecretOptions,
|
|
57
57
|
TranslationKeys,
|
|
58
58
|
TranslationsDef,
|
|
59
|
+
TreeActionDef,
|
|
60
|
+
TreeActionsHandle,
|
|
61
|
+
TreeChildrenSubscribe,
|
|
59
62
|
ValidationHookFn,
|
|
60
63
|
WriteHandlerDef,
|
|
61
64
|
WriteHandlerFn,
|
|
62
65
|
} from "./types";
|
|
63
66
|
import { HookPhases } from "./types";
|
|
67
|
+
import type { RequiresApi } from "./types/feature";
|
|
64
68
|
import { resolveName } from "./types/handlers";
|
|
65
69
|
import type { HttpRouteDefinition } from "./types/http-route";
|
|
66
70
|
import type { NavDefinition } from "./types/nav";
|
|
67
71
|
import type { ScreenDefinition } from "./types/screen";
|
|
72
|
+
import type { PipelineDef } from "./types/step";
|
|
68
73
|
import type { WorkspaceDefinition } from "./types/workspace";
|
|
69
74
|
|
|
70
75
|
const LIFECYCLE_TYPES = Object.values(LifecycleHookTypes);
|
|
@@ -87,6 +92,11 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
87
92
|
): FeatureDefinition & { readonly exports: TExports } {
|
|
88
93
|
const requires: string[] = [];
|
|
89
94
|
const optionalRequires: string[] = [];
|
|
95
|
+
// Read-side projection-tables declared via r.requires.projection("table").
|
|
96
|
+
// Boot-validator checks unsafeProjection-* step calls against this set.
|
|
97
|
+
const requiredProjections = new Set<string>();
|
|
98
|
+
// Tier-2 step kinds declared via r.requires.step("webhook.send"). Q9.
|
|
99
|
+
const requiredSteps = new Set<string>();
|
|
90
100
|
const entities: Record<string, EntityDefinition> = {};
|
|
91
101
|
const relations: Record<string, Record<string, RelationDefinition>> = {};
|
|
92
102
|
const writeHandlers: Record<string, WriteHandlerDef> = {};
|
|
@@ -135,6 +145,14 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
135
145
|
|
|
136
146
|
let isSystemScoped = false;
|
|
137
147
|
let toggleableDefault: boolean | undefined;
|
|
148
|
+
// Visual-Tree-Slots — at-most-one per feature, only-once-guard im
|
|
149
|
+
// registrar (siehe r.treeActions / r.tree). Undefined wenn das Feature
|
|
150
|
+
// keinen Visual-Tree-Beitrag liefert (Zero-Whitelist-Filter).
|
|
151
|
+
// Name-Collision-Sicherheit: object-literal-method-Names im registrar
|
|
152
|
+
// sind keine bindings im closure-scope, daher kollidiert die `treeActions`
|
|
153
|
+
// closure-let-var nicht mit der `treeActions(...)` registrar-Methode.
|
|
154
|
+
let treeActions: Readonly<Record<string, TreeActionDef>> | undefined;
|
|
155
|
+
let treeProvider: TreeChildrenSubscribe | undefined;
|
|
138
156
|
|
|
139
157
|
// Map handler name to entity via colon convention.
|
|
140
158
|
// "task:create" → entity "task". No colon → standalone handler, no mapping.
|
|
@@ -153,9 +171,18 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
153
171
|
isSystemScoped = true;
|
|
154
172
|
},
|
|
155
173
|
|
|
156
|
-
requires
|
|
157
|
-
|
|
158
|
-
|
|
174
|
+
requires: (() => {
|
|
175
|
+
const fn = (...featureNames: string[]) => {
|
|
176
|
+
requires.push(...featureNames);
|
|
177
|
+
};
|
|
178
|
+
fn.projection = (tableName: string) => {
|
|
179
|
+
requiredProjections.add(tableName);
|
|
180
|
+
};
|
|
181
|
+
fn.step = (stepKind: string) => {
|
|
182
|
+
requiredSteps.add(stepKind);
|
|
183
|
+
};
|
|
184
|
+
return fn as RequiresApi;
|
|
185
|
+
})(),
|
|
159
186
|
|
|
160
187
|
optionalRequires(...featureNames: string[]): void {
|
|
161
188
|
optionalRequires.push(...featureNames);
|
|
@@ -186,11 +213,27 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
186
213
|
writeHandlers[def.name] = {
|
|
187
214
|
name: def.name,
|
|
188
215
|
schema: def.schema,
|
|
189
|
-
// @cast-boundary engine-bridge — typed Dev-API
|
|
216
|
+
// @cast-boundary engine-bridge — typed Dev-API's handler is
|
|
217
|
+
// generic over the schema's parsed payload (`WriteEvent<output<TSchema>>`),
|
|
218
|
+
// the storage form WriteHandlerFn carries `WriteEvent<unknown>`.
|
|
219
|
+
// Function-arg variance: TS sees the typed handler as stricter
|
|
220
|
+
// than the loose storage shape and rejects direct assignment.
|
|
221
|
+
// The runtime value is identical — the cast crosses that boundary.
|
|
222
|
+
// `satisfies` does not work here (it asserts assignability, which
|
|
223
|
+
// is what fails). Explicit cast is the right tool.
|
|
190
224
|
handler: def.handler as WriteHandlerFn,
|
|
191
225
|
...(def.access && { access: def.access }),
|
|
192
|
-
...(def.
|
|
226
|
+
...(def.unsafeSkipTransitionGuard && { unsafeSkipTransitionGuard: true }),
|
|
193
227
|
...(def.rateLimit && { rateLimit: def.rateLimit }),
|
|
228
|
+
// Forward the pipeline-build closure so boot-validators and
|
|
229
|
+
// Designer/AI tooling can inspect the step list. Absent on
|
|
230
|
+
// free-form handlers — defineWriteHandler only sets `perform`
|
|
231
|
+
// when the author used the pipeline form. Variance cast
|
|
232
|
+
// mirrors the handler-cast above: PipelineDef<output<TSchema>>
|
|
233
|
+
// is stricter than PipelineDef<unknown> for the same reason.
|
|
234
|
+
...(def.perform !== undefined && {
|
|
235
|
+
perform: def.perform as PipelineDef,
|
|
236
|
+
}),
|
|
194
237
|
};
|
|
195
238
|
tryMapEntity(def.name);
|
|
196
239
|
return { name: def.name };
|
|
@@ -220,7 +263,7 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
220
263
|
name: def.name,
|
|
221
264
|
schema: def.schema,
|
|
222
265
|
// @cast-boundary engine-bridge — typed Dev-API → erased internal storage
|
|
223
|
-
handler: def.handler as QueryHandlerFn,
|
|
266
|
+
handler: def.handler as QueryHandlerFn, // @cast-boundary engine-bridge
|
|
224
267
|
...(def.access && { access: def.access }),
|
|
225
268
|
...(def.rateLimit && { rateLimit: def.rateLimit }),
|
|
226
269
|
};
|
|
@@ -345,7 +388,7 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
345
388
|
? {
|
|
346
389
|
on: Array.isArray(options.trigger.on)
|
|
347
390
|
? options.trigger.on.map(resolveName)
|
|
348
|
-
: resolveName(options.trigger.on as NameOrRef),
|
|
391
|
+
: resolveName(options.trigger.on as NameOrRef), // @cast-boundary engine-bridge
|
|
349
392
|
}
|
|
350
393
|
: options.trigger;
|
|
351
394
|
jobs[jobName] = { ...options, trigger, name: jobName, handler };
|
|
@@ -723,9 +766,41 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
723
766
|
};
|
|
724
767
|
return { name: qualifiedName, type: options.type };
|
|
725
768
|
},
|
|
769
|
+
|
|
770
|
+
treeActions<const TActions extends Record<string, TreeActionDef>>(
|
|
771
|
+
actions: TActions,
|
|
772
|
+
): TreeActionsHandle<TName, TActions> {
|
|
773
|
+
// Only-once-guard: zweiter Aufruf ist Author-Bug, soll am
|
|
774
|
+
// Feature-File aufschlagen (gleicher Stil wie r.toggleable).
|
|
775
|
+
if (treeActions !== undefined) {
|
|
776
|
+
throw new Error(
|
|
777
|
+
`[Feature ${name}] r.treeActions() already called. ` +
|
|
778
|
+
`Each feature may declare a single tree-actions schema.`,
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
treeActions = actions;
|
|
782
|
+
// Return typed handle für setup-export. Frozen damit Caller die
|
|
783
|
+
// Map nicht nachträglich mutieren (würde Pattern-AST + Runtime-
|
|
784
|
+
// Lookup divergieren lassen).
|
|
785
|
+
return Object.freeze({
|
|
786
|
+
id: name,
|
|
787
|
+
treeActions: actions,
|
|
788
|
+
});
|
|
789
|
+
},
|
|
790
|
+
|
|
791
|
+
tree(provider: TreeChildrenSubscribe): void {
|
|
792
|
+
// Only-once-guard analog zu r.treeActions.
|
|
793
|
+
if (treeProvider !== undefined) {
|
|
794
|
+
throw new Error(
|
|
795
|
+
`[Feature ${name}] r.tree() already called. ` +
|
|
796
|
+
`Each feature may declare a single tree-provider.`,
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
treeProvider = provider;
|
|
800
|
+
},
|
|
726
801
|
};
|
|
727
802
|
|
|
728
|
-
const exports = setup(registrar) as TExports;
|
|
803
|
+
const exports = setup(registrar) as TExports; // @cast-boundary engine-bridge
|
|
729
804
|
|
|
730
805
|
return {
|
|
731
806
|
name,
|
|
@@ -733,6 +808,8 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
733
808
|
exports,
|
|
734
809
|
requires,
|
|
735
810
|
optionalRequires,
|
|
811
|
+
requiredProjections,
|
|
812
|
+
requiredSteps,
|
|
736
813
|
...(toggleableDefault !== undefined && { toggleableDefault }),
|
|
737
814
|
entities,
|
|
738
815
|
relations,
|
|
@@ -746,7 +823,7 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
746
823
|
preDelete: phasedLifecycleHooks.preDelete,
|
|
747
824
|
postDelete: phasedLifecycleHooks.postDelete,
|
|
748
825
|
preQuery: lifecycleHooks["preQuery"] ?? {},
|
|
749
|
-
} as HookMap,
|
|
826
|
+
} as HookMap, // @cast-boundary engine-payload
|
|
750
827
|
entityHooks: {
|
|
751
828
|
postSave: entityPostSave,
|
|
752
829
|
preDelete: entityPreDelete,
|
|
@@ -775,5 +852,7 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
775
852
|
workspaces,
|
|
776
853
|
httpRoutes,
|
|
777
854
|
rawTables,
|
|
855
|
+
...(treeActions !== undefined && { treeActions }),
|
|
856
|
+
...(treeProvider !== undefined && { treeProvider }),
|
|
778
857
|
};
|
|
779
858
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ZodType, z } from "zod";
|
|
2
|
+
import { runPipeline } from "./run-pipeline";
|
|
2
3
|
import type {
|
|
3
4
|
AccessRule,
|
|
4
5
|
HandlerContext,
|
|
@@ -8,6 +9,7 @@ import type {
|
|
|
8
9
|
WriteEvent,
|
|
9
10
|
WriteResult,
|
|
10
11
|
} from "./types";
|
|
12
|
+
import type { PipelineDef } from "./types/step";
|
|
11
13
|
|
|
12
14
|
// --- Write Handler Definition ---
|
|
13
15
|
//
|
|
@@ -19,6 +21,10 @@ import type {
|
|
|
19
21
|
// definition-site (framework's compile, where the augmentation isn't
|
|
20
22
|
// visible) and collapse `keyof TMap` to `never`. See the spike-findings
|
|
21
23
|
// memory for the empirical proof.
|
|
24
|
+
//
|
|
25
|
+
// Two authoring forms — `handler` (free-form) or `perform: pipeline(...)`
|
|
26
|
+
// (step-pipeline). A `perform` is compiled to a handler-function at
|
|
27
|
+
// definition time; the dispatcher only ever sees `handler`.
|
|
22
28
|
|
|
23
29
|
export type WriteHandlerDefinition<
|
|
24
30
|
TName extends string = string,
|
|
@@ -29,23 +35,103 @@ export type WriteHandlerDefinition<
|
|
|
29
35
|
readonly name: TName;
|
|
30
36
|
readonly schema: TSchema;
|
|
31
37
|
readonly access?: AccessRule;
|
|
32
|
-
readonly
|
|
38
|
+
readonly unsafeSkipTransitionGuard?: boolean;
|
|
33
39
|
readonly rateLimit?: RateLimitOption;
|
|
34
40
|
readonly handler: (
|
|
35
41
|
event: WriteEvent<z.infer<TSchema>>,
|
|
36
42
|
context: HandlerContext<TMap>,
|
|
37
43
|
) => Promise<WriteResult<TData>>;
|
|
44
|
+
// Preserved when the author wrote a `perform` block — the original
|
|
45
|
+
// PipelineDef. Designer/AI/AST tools read this when present; the
|
|
46
|
+
// dispatcher ignores it and just calls `handler`. Absent on free-form
|
|
47
|
+
// handlers.
|
|
48
|
+
readonly perform?: PipelineDef<z.infer<TSchema>, TData>;
|
|
38
49
|
};
|
|
39
50
|
|
|
51
|
+
// Author-facing input — accepts either the free-form `handler` or the
|
|
52
|
+
// pipeline-form `perform`. defineWriteHandler narrows them to the
|
|
53
|
+
// canonical WriteHandlerDefinition shape.
|
|
54
|
+
export type WriteHandlerInput<
|
|
55
|
+
TName extends string = string,
|
|
56
|
+
TSchema extends ZodType = ZodType,
|
|
57
|
+
TData = unknown,
|
|
58
|
+
TMap extends object = KumikoEventTypeMap,
|
|
59
|
+
> = {
|
|
60
|
+
readonly name: TName;
|
|
61
|
+
readonly schema: TSchema;
|
|
62
|
+
readonly access?: AccessRule;
|
|
63
|
+
readonly unsafeSkipTransitionGuard?: boolean;
|
|
64
|
+
readonly rateLimit?: RateLimitOption;
|
|
65
|
+
} & (
|
|
66
|
+
| {
|
|
67
|
+
readonly handler: (
|
|
68
|
+
event: WriteEvent<z.infer<TSchema>>,
|
|
69
|
+
context: HandlerContext<TMap>,
|
|
70
|
+
) => Promise<WriteResult<TData>>;
|
|
71
|
+
readonly perform?: never;
|
|
72
|
+
}
|
|
73
|
+
| {
|
|
74
|
+
readonly perform: PipelineDef<z.infer<TSchema>, TData>;
|
|
75
|
+
readonly handler?: never;
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
40
79
|
export function defineWriteHandler<
|
|
41
80
|
const TName extends string,
|
|
42
81
|
TSchema extends ZodType,
|
|
43
82
|
TData = unknown,
|
|
44
83
|
TMap extends object = KumikoEventTypeMap,
|
|
45
84
|
>(
|
|
46
|
-
def:
|
|
85
|
+
def: WriteHandlerInput<TName, TSchema, TData, TMap>,
|
|
47
86
|
): WriteHandlerDefinition<TName, TSchema, TData, TMap> {
|
|
48
|
-
|
|
87
|
+
// Runtime-guard against accidentally setting BOTH handler+perform.
|
|
88
|
+
// The discriminated-union type-error
|
|
89
|
+
// "Type 'PipelineDef<...>' is not assignable to type 'undefined'."
|
|
90
|
+
// is functional but cryptic for less TS-experienced users; this throws
|
|
91
|
+
// a name-and-explanation error message instead. Followup #3.
|
|
92
|
+
// The cast is necessary because the discriminated union narrows
|
|
93
|
+
// `handler` away once `perform` is present (and vice-versa) — at this
|
|
94
|
+
// boundary we want to read both regardless of the narrowing.
|
|
95
|
+
const probe = def as {
|
|
96
|
+
readonly handler?: unknown;
|
|
97
|
+
readonly perform?: unknown;
|
|
98
|
+
readonly name: TName;
|
|
99
|
+
};
|
|
100
|
+
if (probe.handler !== undefined && probe.perform !== undefined) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`defineWriteHandler("${def.name}"): both \`handler\` and \`perform\` are set. ` +
|
|
103
|
+
`Pick one — \`handler\` for the free-form async function, ` +
|
|
104
|
+
`\`perform: pipeline(...)\` for the step-pipeline form. ` +
|
|
105
|
+
`(See step-vocabulary.md for which form fits.)`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Conditional spreads (`...(def.access && { access: def.access })`)
|
|
110
|
+
// mirror the existing convention in entity-handlers.ts /
|
|
111
|
+
// define-feature.ts — optional fields stay absent rather than being
|
|
112
|
+
// serialised as `key: undefined`.
|
|
113
|
+
const base = {
|
|
114
|
+
name: def.name,
|
|
115
|
+
schema: def.schema,
|
|
116
|
+
...(def.access && { access: def.access }),
|
|
117
|
+
...(def.unsafeSkipTransitionGuard && {
|
|
118
|
+
unsafeSkipTransitionGuard: def.unsafeSkipTransitionGuard,
|
|
119
|
+
}),
|
|
120
|
+
...(def.rateLimit && { rateLimit: def.rateLimit }),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if ("perform" in def && def.perform !== undefined) {
|
|
124
|
+
const performDef = def.perform;
|
|
125
|
+
const compiledHandler = async (
|
|
126
|
+
event: WriteEvent<z.infer<TSchema>>,
|
|
127
|
+
ctx: HandlerContext<TMap>,
|
|
128
|
+
): Promise<WriteResult<TData>> => {
|
|
129
|
+
return runPipeline<z.infer<TSchema>, TData, TMap>(performDef, event, ctx);
|
|
130
|
+
};
|
|
131
|
+
return { ...base, handler: compiledHandler, perform: performDef };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { ...base, handler: def.handler };
|
|
49
135
|
}
|
|
50
136
|
|
|
51
137
|
// --- Query Handler Definition ---
|
|
@@ -11,9 +11,9 @@ type RoleMap<T extends readonly string[]> = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export function defineRoles<const T extends readonly string[]>(roles: T): RoleMap<T> {
|
|
14
|
-
const map = {} as Record<string, string>;
|
|
14
|
+
const map = {} as Record<string, string>; // @cast-boundary schema-walk
|
|
15
15
|
for (const role of roles) {
|
|
16
16
|
map[role] = role;
|
|
17
17
|
}
|
|
18
|
-
return map as RoleMap<T>;
|
|
18
|
+
return map as RoleMap<T>; // @cast-boundary schema-walk
|
|
19
19
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Step-Registry + defineStep factory.
|
|
2
|
+
//
|
|
3
|
+
// Steps are registered at module-load time via defineStep(). The runtime
|
|
4
|
+
// (run-pipeline.ts) looks them up by `kind` to dispatch run-calls.
|
|
5
|
+
//
|
|
6
|
+
// The registry is process-global; Tier-2 step opt-in via
|
|
7
|
+
// `r.requires.step("…")` (Q9 in step-vocabulary.md) is a future pass.
|
|
8
|
+
|
|
9
|
+
import type { StepDef, StepKind } from "./types/step";
|
|
10
|
+
|
|
11
|
+
const stepRegistry = new Map<StepKind, StepDef>();
|
|
12
|
+
|
|
13
|
+
export function defineStep<TArgs, TResult>(def: StepDef<TArgs, TResult>): StepDef<TArgs, TResult> {
|
|
14
|
+
const existing = stepRegistry.get(def.kind);
|
|
15
|
+
if (existing && existing !== def) {
|
|
16
|
+
throw new Error(`Step kind "${def.kind}" is already registered with a different definition`);
|
|
17
|
+
}
|
|
18
|
+
stepRegistry.set(def.kind, def as StepDef);
|
|
19
|
+
return def;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getStep(kind: StepKind): StepDef | undefined {
|
|
23
|
+
return stepRegistry.get(kind);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function listStepKinds(): readonly StepKind[] {
|
|
27
|
+
return Array.from(stepRegistry.keys());
|
|
28
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// defineWorkflow — define a persistent, suspendable workflow.
|
|
2
|
+
// Tier-3 / Workflow-only mount-point. See step-vocabulary.md Sample 2 for
|
|
3
|
+
// the full lifecycle (run.started → wait → resume → run.completed) and
|
|
4
|
+
// Q7 (Snapshot-at-Start) for the in-flight upgrade story.
|
|
5
|
+
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import type { WriteEvent } from "./types/handlers";
|
|
8
|
+
import type { PipelineDef } from "./types/step";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Trigger configuration for a workflow. Determines what starts a run.
|
|
12
|
+
*/
|
|
13
|
+
export type WorkflowTrigger =
|
|
14
|
+
| {
|
|
15
|
+
readonly kind: "event";
|
|
16
|
+
readonly eventType: string;
|
|
17
|
+
readonly filter?: (event: WriteEvent) => boolean;
|
|
18
|
+
}
|
|
19
|
+
| {
|
|
20
|
+
readonly kind: "cron";
|
|
21
|
+
readonly schedule: string; // cron expression
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
readonly kind: "webhook";
|
|
25
|
+
readonly path: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Workflow definition — the result of defineWorkflow().
|
|
30
|
+
*/
|
|
31
|
+
export type WorkflowDefinition<TPayload = unknown, TData = unknown> = {
|
|
32
|
+
readonly __kind: "workflow";
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly trigger: WorkflowTrigger;
|
|
35
|
+
/** The pipeline definition containing the step list closure. */
|
|
36
|
+
readonly pipelineDef: PipelineDef<TPayload, TData>;
|
|
37
|
+
/** Idempotency key for deduplication — prevents duplicate runs. */
|
|
38
|
+
readonly idempotencyKey?: string | ((event: WriteEvent<TPayload>) => string);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Input shape for defineWorkflow() — the user-facing API.
|
|
43
|
+
*/
|
|
44
|
+
export type WorkflowInput<TPayload = unknown, TData = unknown> = {
|
|
45
|
+
readonly name: string;
|
|
46
|
+
readonly trigger: WorkflowTrigger;
|
|
47
|
+
readonly steps: PipelineDef<TPayload, TData>;
|
|
48
|
+
readonly idempotencyKey?: string | ((event: WriteEvent<TPayload>) => string);
|
|
49
|
+
readonly onError?: PipelineDef<unknown>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Define a suspendable workflow.
|
|
54
|
+
*
|
|
55
|
+
* Example:
|
|
56
|
+
* ```ts
|
|
57
|
+
* defineWorkflow({
|
|
58
|
+
* name: "user-onboarding",
|
|
59
|
+
* trigger: { kind: "event", eventType: "user.signed-up" },
|
|
60
|
+
* steps: pipeline(({ event, r }) => [
|
|
61
|
+
* r.step.mail.send({ to: () => event.payload.email, subject: "Welcome!", body: "..." }),
|
|
62
|
+
* r.step.wait({ for: "P1D" }),
|
|
63
|
+
* r.step.read.findOne("user", { table: userTable, where: ... }),
|
|
64
|
+
* r.step.branch({ if: ({ steps }) => ..., onTrue: [...], onFalse: [...] }),
|
|
65
|
+
* r.step.retry({ times: 3, backoff: "exponential", do: [
|
|
66
|
+
* r.step.webhook.send({ url: "...", mode: "deferred" }),
|
|
67
|
+
* ]}),
|
|
68
|
+
* ]),
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function defineWorkflow<TPayload = unknown, TData = unknown>(
|
|
73
|
+
input: WorkflowInput<TPayload, TData>,
|
|
74
|
+
): WorkflowDefinition<TPayload, TData> {
|
|
75
|
+
return {
|
|
76
|
+
__kind: "workflow",
|
|
77
|
+
name: input.name,
|
|
78
|
+
trigger: input.trigger,
|
|
79
|
+
pipelineDef: input.steps,
|
|
80
|
+
idempotencyKey: input.idempotencyKey,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Q7 Snapshot-at-Start fingerprint. SHA-256 over the workflow's stable
|
|
86
|
+
* identity (name + trigger + serialized pipeline-closure source). Persisted
|
|
87
|
+
* in `workflow.run.started` and re-checked at every resume so a library-
|
|
88
|
+
* upgrade that changes the closure source surfaces as a loud
|
|
89
|
+
* `workflow-definition-changed` failure on in-flight runs instead of a
|
|
90
|
+
* silent semantic drift.
|
|
91
|
+
*
|
|
92
|
+
* Limitations (will be tightened in M.5 with the Designer/AST layer):
|
|
93
|
+
* - `build.toString()` captures the closure source but not bindings —
|
|
94
|
+
* two definitions that import different external helpers with the
|
|
95
|
+
* same source bytes would collide. Acceptable for M.4 because the
|
|
96
|
+
* fingerprint is a *change-detector*, not a deep semantic identity.
|
|
97
|
+
* - Minifiers / source-maps will produce different fingerprints across
|
|
98
|
+
* environments. Run-the-fingerprint-in-the-same-environment is the
|
|
99
|
+
* contract; cross-env replay is out of scope.
|
|
100
|
+
*/
|
|
101
|
+
export function computeDefinitionFingerprint(
|
|
102
|
+
workflow: Pick<WorkflowDefinition, "name" | "trigger" | "pipelineDef">,
|
|
103
|
+
): string {
|
|
104
|
+
const material = JSON.stringify({
|
|
105
|
+
name: workflow.name,
|
|
106
|
+
trigger: workflow.trigger,
|
|
107
|
+
source: workflow.pipelineDef.build.toString(),
|
|
108
|
+
});
|
|
109
|
+
return createHash("sha256").update(material).digest("hex");
|
|
110
|
+
}
|
|
@@ -112,8 +112,8 @@ function parseHandlerName<TVerb extends string>(
|
|
|
112
112
|
if (!entityName) {
|
|
113
113
|
throw new Error(`Handler name "${name}" is missing the entity part before the colon.`);
|
|
114
114
|
}
|
|
115
|
-
// @cast-boundary engine-bridge
|
|
116
|
-
if (!
|
|
115
|
+
const verbs = validVerbs as readonly string[]; // @cast-boundary engine-bridge
|
|
116
|
+
if (!verbs.includes(verbCandidate)) {
|
|
117
117
|
throw new Error(
|
|
118
118
|
`Unknown verb "${verbCandidate}" in handler name "${name}". Standard verbs: ${validVerbs.join("/")}. For custom verbs use the explicit r.writeHandler / r.queryHandler form.`,
|
|
119
119
|
);
|
|
@@ -151,17 +151,17 @@ export function defineEntityWriteHandler(
|
|
|
151
151
|
changes: buildUpdateSchema(entity),
|
|
152
152
|
});
|
|
153
153
|
handler = async (event, ctx) =>
|
|
154
|
-
executor.update(event.payload as UpdatePayload, event.user, ctx.db);
|
|
154
|
+
executor.update(event.payload as UpdatePayload, event.user, ctx.db); // @cast-boundary engine-payload
|
|
155
155
|
break;
|
|
156
156
|
case "delete":
|
|
157
157
|
schema = idSchema;
|
|
158
158
|
handler = async (event, ctx) =>
|
|
159
|
-
executor.delete(event.payload as IdPayload, event.user, ctx.db);
|
|
159
|
+
executor.delete(event.payload as IdPayload, event.user, ctx.db); // @cast-boundary engine-payload
|
|
160
160
|
break;
|
|
161
161
|
case "restore":
|
|
162
162
|
schema = idSchema;
|
|
163
163
|
handler = async (event, ctx) =>
|
|
164
|
-
executor.restore(event.payload as IdPayload, event.user, ctx.db);
|
|
164
|
+
executor.restore(event.payload as IdPayload, event.user, ctx.db); // @cast-boundary engine-payload
|
|
165
165
|
break;
|
|
166
166
|
default:
|
|
167
167
|
assertUnreachable(verb, "write verb");
|
|
@@ -205,7 +205,8 @@ export function defineEntityQueryHandler(
|
|
|
205
205
|
// läuft (Remote-Combobox-Search). Der executor wird beim
|
|
206
206
|
// Definition-Time gebaut, kennt den Adapter also nicht —
|
|
207
207
|
// Runtime-Override holt das.
|
|
208
|
-
const
|
|
208
|
+
const listPayload = query.payload as ListPayload; // @cast-boundary engine-payload
|
|
209
|
+
const result = await executor.list(listPayload, query.user, ctx.db, {
|
|
209
210
|
...(ctx.searchAdapter !== undefined && { searchAdapter: ctx.searchAdapter }),
|
|
210
211
|
});
|
|
211
212
|
if (!hasRefFields) return result;
|
|
@@ -221,7 +222,7 @@ export function defineEntityQueryHandler(
|
|
|
221
222
|
case "detail":
|
|
222
223
|
schema = idSchema;
|
|
223
224
|
handler = async (query, ctx) => {
|
|
224
|
-
const row = await executor.detail(query.payload as IdPayload, query.user, ctx.db);
|
|
225
|
+
const row = await executor.detail(query.payload as IdPayload, query.user, ctx.db); // @cast-boundary engine-payload
|
|
225
226
|
if (row === null || !hasRefFields) return row;
|
|
226
227
|
return enrichRowWithReferences(row, entity, (name) => ctx.registry.getEntity(name), ctx.db);
|
|
227
228
|
};
|
|
@@ -345,7 +346,7 @@ export function createEntityExecutor(
|
|
|
345
346
|
export function defineProjectionQueryHandler(
|
|
346
347
|
name: string,
|
|
347
348
|
projectionQualifiedName: string,
|
|
348
|
-
options?: { access?: AccessRule;
|
|
349
|
+
options?: { access?: AccessRule; unsafeAllTenants?: boolean },
|
|
349
350
|
): QueryHandlerDef {
|
|
350
351
|
return {
|
|
351
352
|
name,
|
|
@@ -357,7 +358,7 @@ export function defineProjectionQueryHandler(
|
|
|
357
358
|
handler: async (_query, ctx) =>
|
|
358
359
|
ctx.queryProjection(
|
|
359
360
|
projectionQualifiedName,
|
|
360
|
-
options?.
|
|
361
|
+
options?.unsafeAllTenants ? { unsafeAllTenants: true } : undefined,
|
|
361
362
|
),
|
|
362
363
|
...(options?.access && { access: options.access }),
|
|
363
364
|
};
|
|
@@ -4,11 +4,11 @@ import type { AppendEventArgs, EventDef, HandlerContext } from "./types/handlers
|
|
|
4
4
|
// MultiStreamApplyContext-style callers pass their own appendEvent without
|
|
5
5
|
// a full HandlerContext. Real handlers just pass `ctx`.
|
|
6
6
|
//
|
|
7
|
-
// Uses
|
|
7
|
+
// Uses unsafeAppendEvent internally because EventDef's TPayload comes from
|
|
8
8
|
// a runtime-defined zod-schema — emitEvent does the type-check itself via
|
|
9
9
|
// the EventDef generic, so it doesn't need the strict KumikoEventTypeMap
|
|
10
10
|
// path. The strict appendEvent is for direct in-handler callsites.
|
|
11
|
-
export type EmitCtx = Pick<HandlerContext, "
|
|
11
|
+
export type EmitCtx = Pick<HandlerContext, "unsafeAppendEvent">;
|
|
12
12
|
|
|
13
13
|
// Typed wrapper around ctx.appendEvent. Two wins over the raw call:
|
|
14
14
|
//
|
|
@@ -42,7 +42,7 @@ export async function emitEvent<TPayload>(
|
|
|
42
42
|
type: eventDef.name,
|
|
43
43
|
payload: args.payload,
|
|
44
44
|
};
|
|
45
|
-
await ctx.
|
|
45
|
+
await ctx.unsafeAppendEvent(appendArgs);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// Read-side counterpart: narrow a StoredEvent's `payload` (declared as
|
|
@@ -69,5 +69,5 @@ export function typedPayload<TPayload>(
|
|
|
69
69
|
`Check the projection-apply / reducer mapping — the event was routed to the wrong handler.`,
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
|
-
return event.payload as TPayload;
|
|
72
|
+
return event.payload as TPayload; // @cast-boundary engine-payload
|
|
73
73
|
}
|
package/src/engine/factories.ts
CHANGED
|
@@ -39,7 +39,7 @@ export function createTextField<R extends true | false = false>(
|
|
|
39
39
|
searchable: false,
|
|
40
40
|
sortable: false,
|
|
41
41
|
...overrides,
|
|
42
|
-
} as TextFieldDef & { required: R };
|
|
42
|
+
} as TextFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
@@ -59,7 +59,7 @@ export function createLongTextField<R extends true | false = false>(
|
|
|
59
59
|
type: "longText",
|
|
60
60
|
required: false,
|
|
61
61
|
...overrides,
|
|
62
|
-
} as LongTextFieldDef & { required: R };
|
|
62
|
+
} as LongTextFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
export function createBooleanField<R extends true | false = false>(
|
|
@@ -70,7 +70,7 @@ export function createBooleanField<R extends true | false = false>(
|
|
|
70
70
|
required: false,
|
|
71
71
|
default: false,
|
|
72
72
|
...overrides,
|
|
73
|
-
} as BooleanFieldDef & { required: R };
|
|
73
|
+
} as BooleanFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
export function createSelectField<
|
|
@@ -85,7 +85,7 @@ export function createSelectField<
|
|
|
85
85
|
type: "select",
|
|
86
86
|
required: false,
|
|
87
87
|
...opts,
|
|
88
|
-
} as SelectFieldDef<TOptions> & { required: R };
|
|
88
|
+
} as SelectFieldDef<TOptions> & { required: R }; // @cast-boundary engine-payload
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
@@ -126,7 +126,7 @@ export function createNumberField<R extends true | false = false>(
|
|
|
126
126
|
type: "number",
|
|
127
127
|
required: false,
|
|
128
128
|
...overrides,
|
|
129
|
-
} as NumberFieldDef & { required: R };
|
|
129
|
+
} as NumberFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
export function createBigIntField<R extends true | false = false>(
|
|
@@ -136,7 +136,7 @@ export function createBigIntField<R extends true | false = false>(
|
|
|
136
136
|
type: "bigInt",
|
|
137
137
|
required: false,
|
|
138
138
|
...overrides,
|
|
139
|
-
} as BigIntFieldDef & { required: R };
|
|
139
|
+
} as BigIntFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
export function createMoneyField<R extends true | false = false>(
|
|
@@ -145,7 +145,7 @@ export function createMoneyField<R extends true | false = false>(
|
|
|
145
145
|
return {
|
|
146
146
|
type: "money",
|
|
147
147
|
...overrides,
|
|
148
|
-
} as MoneyFieldDef & { required: R };
|
|
148
|
+
} as MoneyFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
export function createEmbeddedField(
|
|
@@ -166,7 +166,7 @@ export function createDateField<R extends true | false = false>(
|
|
|
166
166
|
type: "date",
|
|
167
167
|
required: false,
|
|
168
168
|
...overrides,
|
|
169
|
-
} as DateFieldDef & { required: R };
|
|
169
|
+
} as DateFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
/**
|
|
@@ -186,7 +186,7 @@ export function createTimestampField<R extends true | false = false>(
|
|
|
186
186
|
return {
|
|
187
187
|
...overrides,
|
|
188
188
|
type: "timestamp",
|
|
189
|
-
required: (overrides?.required ?? false) as R,
|
|
189
|
+
required: (overrides?.required ?? false) as R, // @cast-boundary engine-payload
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
192
|
|
|
@@ -201,7 +201,7 @@ export function createTzField<R extends true | false = false>(
|
|
|
201
201
|
type: "tz",
|
|
202
202
|
required: false,
|
|
203
203
|
...overrides,
|
|
204
|
-
} as TzFieldDef & { required: R };
|
|
204
|
+
} as TzFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
/**
|
|
@@ -241,7 +241,7 @@ export function createLocatedTimestampField<R extends true | false = false>(
|
|
|
241
241
|
type: "locatedTimestamp",
|
|
242
242
|
required: false,
|
|
243
243
|
...overrides,
|
|
244
|
-
} as LocatedTimestampFieldDef & { required: R };
|
|
244
|
+
} as LocatedTimestampFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
/**
|
|
@@ -334,5 +334,5 @@ export function createEntity<F>(def: {
|
|
|
334
334
|
// aggregate-ids are UUID. Opt-out with `idType: "serial"` for pre-ES
|
|
335
335
|
// legacy tables (should be rare).
|
|
336
336
|
...def,
|
|
337
|
-
} as F extends FieldsMap ? EntityDefinition<F> : never;
|
|
337
|
+
} as F extends FieldsMap ? EntityDefinition<F> : never; // @cast-boundary engine-payload
|
|
338
338
|
}
|