@reventlessdev/reventless-core 3.0.0-alpha.259 → 3.0.0-alpha.261
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 +15 -0
- package/package.json +11 -11
- package/src/Behavior.res +1 -4
- package/src/Message.res +17 -14
- package/src/Projection.res +51 -25
- package/src/RequestContext.res +8 -9
- package/src/ResourceAttribution.res +28 -14
- package/src/adapter/Adapter.res +48 -4
- package/src/adapter/EventLogProvisioning/EventLogProvisioning.res +5 -5
- package/src/adapter/Messaging/Messaging_Log_Backend.res +10 -7
- package/src/adapter/Messaging/Messaging_Log_Backend.res.mjs +2 -4
- package/src/adapter/Monitoring/Monitoring.res +16 -17
- package/src/adapter/QueryInterception/QueryInterception.res +0 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_Common.res +12 -5
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +11 -7
- package/src/adapter/Runtime/PluginRuntime_Builder_Micro.res +1 -7
- package/src/adapter/Runtime/PluginRuntime_Builder_Single.res +1 -7
- package/src/adapter/RuntimeExtension/RuntimeExtension.res +0 -1
- package/src/admin/Platform_Admin.res +17 -6
- package/src/admin/Platform_AdminApi.res +14 -9
- package/src/admin/Platform_Admin_Callback.res +1 -1
- package/src/admin/Platform_BakedManifest.res +34 -34
- package/src/admin/Platform_ComponentDefinitionsApi.res +9 -21
- package/src/admin/Platform_PluginStructuresApi.res +15 -12
- package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res +1 -1
- package/src/components/Aggregate/Aggregate.res +13 -12
- package/src/components/Aggregate/Aggregate_Builder.res +22 -21
- package/src/components/Aggregate/Aggregate_Callback.res +91 -86
- package/src/components/Api/ApiNoApiHelpers.res +1 -3
- package/src/components/Api/Api_Ids.res +0 -1
- package/src/components/Api/Api_Naming.res +41 -18
- package/src/components/Api/GraphQL_FragmentGenerator.res +42 -52
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +11 -4
- package/src/components/Api/GraphQL_SchemaInspector.res +5 -3
- package/src/components/Api/GraphQL_Stitcher.res +9 -10
- package/src/components/Api/MCP_SchemaGenerator.res +8 -12
- package/src/components/Api/OwnerScopeDiagnostics.res +1 -2
- package/src/components/Api/QueryDbListQuery.res +13 -6
- package/src/components/Api/SchemaType.res +40 -20
- package/src/components/Api/SchemaType.res.mjs +16 -0
- package/src/components/Api/SuryToJsonSchema.res +18 -10
- package/src/components/Api/SuryToJsonSchema.res.mjs +135 -121
- package/src/components/AutomationSlice/AutomationSlice.res +4 -4
- package/src/components/AutomationSlice/AutomationSlice_Builder.res +15 -14
- package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -3
- package/src/components/Builder_Helpers.res +12 -23
- package/src/components/CommandGenerator/CommandGenerator.res +6 -1
- package/src/components/CommandGenerator/CommandGenerator_Builder.res +29 -21
- package/src/components/CommandGenerator/CommandGenerator_Callback.res +17 -21
- package/src/components/CommandTopic/CommandTopic.res +3 -3
- package/src/components/CommandTopic/CommandTopic_Builder.res +10 -10
- package/src/components/CommandTopic/CommandTopic_Callback.res +1 -4
- package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -2
- package/src/components/Counter/Counter_Builder.res +1 -6
- package/src/components/Counter/Counter_Callback.res +3 -3
- package/src/components/Dcb/Dcb_Builder.res +970 -956
- package/src/components/DcbEventLog/DcbEventLog.res +4 -4
- package/src/components/DcbEventLog/DcbEventLog_Builder.res +0 -1
- package/src/components/DcbEventLog/DcbEventLog_Operations.res +17 -18
- package/src/components/EventCollector/EventCollector_Builder.res +2 -1
- package/src/components/EventLog/EventLog.res +5 -1
- package/src/components/EventLog/EventLog_Operations.res +12 -8
- package/src/components/EventMapper/EventMapper.res +8 -10
- package/src/components/EventMapper/EventMapper_Builder.res +4 -1
- package/src/components/EventMapper/EventMapper_Callback.res +46 -31
- package/src/components/EventTopic/EventTopic.res +3 -3
- package/src/components/EventTopic/EventTopic_Builder.res +1 -1
- package/src/components/Extension/Extension.res +4 -1
- package/src/components/Extension/Extension_Operations.res +11 -3
- package/src/components/ExtensionPoint/ExtensionPoint.res +3 -3
- package/src/components/ExtensionPoint/ExtensionPoint_Builder.res +9 -10
- package/src/components/ExtensionPoint/ExtensionPoint_Callback.res +2 -6
- package/src/components/ExtensionPoint/ExtensionPoint_Operations.res +13 -5
- package/src/components/Heartbeat/Heartbeat_Callback.res +3 -3
- package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +4 -4
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +7 -10
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice.res +4 -4
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +15 -14
- package/src/components/QueryDb/QueryDb_Adapter.res +1 -3
- package/src/components/QueryDb/QueryDb_Operations.res +10 -10
- package/src/components/ReadModel/ReadModel.res +3 -3
- package/src/components/ReadModel/ReadModel_Builder.res +1 -2
- package/src/components/ReadModel/ReadModel_Callback.res +2 -1
- package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +5 -2
- package/src/components/StateChangeSlice/StateChangeSlice.res +3 -3
- package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +23 -4
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +22 -6
- package/src/components/StateTopic.res +1 -3
- package/src/components/StateViewSlice/StateViewSlice.res +4 -4
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +79 -68
- package/src/components/StateViewSlice/StateViewSlice_Callback.res +3 -1
- package/src/components/Task/Task.res +3 -3
- package/src/components/Task/Task_Adapter.res +4 -1
- package/src/components/Task/Task_Builder.res +24 -21
- package/src/plugin/api/PluginBaseFragment.res +9 -3
- package/src/plugin/component/Capability_Inference.res +8 -10
- package/src/plugin/component/Plugin.res +6 -6
- package/src/plugin/component/Plugin_Builder.res +54 -50
- package/src/plugin/component/Plugin_Callback.res +10 -6
- package/src/plugin/component/Plugin_Helpers.res +264 -230
- package/src/plugin/component/Plugin_Structure.res +370 -324
- package/src/plugin/component/Plugin_SubscriptionSchema.res +8 -6
- package/src/plugin/component/SchemaWalker.res +11 -12
- package/src/plugin/connect/PluginConnectExtension_Mapping.res +45 -50
- package/src/plugin/lifecycle/PluginBehavior.res +3 -4
- package/src/plugin/lifecycle/PluginSpec.res +1 -2
- package/src/plugin/lifecycle/PluginsProjection.res +16 -5
- package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
- package/src/util/CommandPublisher.res +9 -2
- package/src/util/CsvStream.res +3 -3
- package/src/util/Interstack.res +7 -15
- package/src/util/Interstack.resi +3 -3
- package/src/util/LogFormat.res +5 -9
- package/src/util/Logger.res +1 -2
- package/src/util/Lru.res +2 -1
- package/src/util/Lru.resi +2 -3
- package/src/util/ScheduleOps.res +24 -27
- package/src/util/StorePrefixCollision.res +6 -9
- package/src/util/Util_Promise.res +4 -1
- package/src/util/Util_Promise.res.mjs +1 -1
- package/tests/IdentityTest.res +166 -113
- package/tests/ResourceAttributionTest.res +28 -25
- package/tests/StorePrefixCollisionTest.res +2 -1
- package/tests/TaskBucketNamingTest.res +3 -2
- package/tests/adapter/EventLogProvisioningTest.res +51 -48
- package/tests/adapter/MonitoringTest.res +81 -38
- package/tests/adapter/QueryInterceptionTest.res +2 -2
- package/tests/adapter/RefusalVocabularyTest.res +31 -23
- package/tests/adapter/RuntimeExtensionHookReachTest.res +9 -8
- package/tests/adapter/RuntimeExtensionTest.res +5 -3
- package/tests/admin/AdminApiSchemaDriftTest.res +22 -16
- package/tests/admin/Platform_Admin_StructureTest.res +10 -11
- package/tests/admin/Platform_BakedManifestTest.res +31 -35
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +18 -15
- package/tests/admin/Platform_PluginStructuresApiTest.res +5 -3
- package/tests/admin/Platform_UIFragmentsApiTest.res +6 -3
- package/tests/aggregate/AggregateCacheTest.res +10 -12
- package/tests/aggregate/AggregateCallbackTest.res +75 -46
- package/tests/aggregate/AggregateCausationTest.res +19 -20
- package/tests/aggregate/AggregateConflictTest.res +41 -19
- package/tests/aggregate/AggregateFixtures.res +15 -5
- package/tests/aggregate/AggregateRejectionTest.res +97 -80
- package/tests/aggregate/AggregateSnapshotTest.res +44 -16
- package/tests/api/Api_IdsTest.res +10 -4
- package/tests/api/BrandedMarkerCompositionTest.res +48 -36
- package/tests/api/GeolocationSemanticTest.res +3 -3
- package/tests/api/GraphQL_FragmentGeneratorTest.res +66 -54
- package/tests/api/OwnerScopeDiagnosticsTest.res +31 -27
- package/tests/api/StorageRefFieldsTest.res +30 -18
- package/tests/api/SuryToJsonSchemaTest.res +1338 -981
- package/tests/api/SuryToJsonSchemaTest.res.mjs +31 -0
- package/tests/api/TaggedUnionSchemaTest.res +17 -24
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res +235 -179
- package/tests/commandgenerator/CommandGeneratorFixtures.res +8 -13
- package/tests/commandgenerator/OwnerStampingTest.res +91 -76
- package/tests/commandtopic/CommandOutcomeHookTest.res +198 -155
- package/tests/commandtopic/CommandTopicCallbackFixtures.res +5 -4
- package/tests/commandtopic/CommandTopicCallbackTest.res +61 -43
- package/tests/commandtopic/CommandTopicHelpersRejectionTest.res +70 -55
- package/tests/counter/CounterCallbackTest.res +40 -34
- package/tests/counter/CounterFixtures.res +4 -4
- package/tests/dcb/DcbDecodeTest.res +115 -84
- package/tests/dcb/DcbEventLogOperationsTest.res +194 -143
- package/tests/dcb/DcbEventLogSchemaTest.res +13 -13
- package/tests/dcb/DcbFixtures.res +4 -5
- package/tests/dcb/DcbScopeInferenceTest.res +173 -114
- package/tests/dcb/DcbStateChangeSliceTest.res +391 -262
- package/tests/dcb/DcbTagTest.res +22 -11
- package/tests/dcb/DcbValidationTest.res +288 -218
- package/tests/eventlog/EventLogFixtures.res +4 -4
- package/tests/eventlog/EventLogOperationsTest.res +102 -67
- package/tests/eventlog/EventLogRetryTest.res +104 -65
- package/tests/eventmapper/EventMapperCallbackTest.res +118 -83
- package/tests/eventmapper/EventMapperFixtures.res +15 -23
- package/tests/eventmapper/EventMapperTest.res +54 -38
- package/tests/eventtopic/EventTopicFixtures.res +3 -4
- package/tests/eventtopic/EventTopicOperationsTest.res +41 -29
- package/tests/extensionpoint/ExtensionPointCallbackTest.res +58 -45
- package/tests/extensionpoint/ExtensionPointFixtures.res +16 -20
- package/tests/extensionpoint/ExtensionPointOperationsTest.res +70 -49
- package/tests/extensionpoint/PluginDisconnectScheduleTest.res +2 -1
- package/tests/logger/LogFormatTest.res +104 -82
- package/tests/logger/LoggerLazyTest.res +21 -12
- package/tests/message/MessageTest.res +96 -93
- package/tests/message/MetaEnvelopeTest.res +26 -30
- package/tests/plugin/HeartbeatDisconnectGraceTest.res +31 -25
- package/tests/plugin/ManifestVisibilityTest.res +20 -12
- package/tests/plugin/PluginDefinitionScalars.res +1 -2
- package/tests/plugin/PluginLifecycleCorpusTest.res +15 -12
- package/tests/plugin/PluginSpecExperiment.res +0 -1
- package/tests/plugin/PluginStructureTest.res +1533 -1030
- package/tests/plugin/PluginStructureTest.res.mjs +6 -0
- package/tests/plugin/Plugin_EventQuerySchemaTest.res +21 -10
- package/tests/plugin/SchemaWalkerTest.res +116 -70
- package/tests/plugin/StateChange/PsReserveStock.res +4 -4
- package/tests/plugin/StateView/PsAnnotatedView.res +6 -4
- package/tests/plugin/StateView/PsAnnotatedView.res.mjs +7 -3
- package/tests/plugin/StateView/PsCustomersView.res +4 -4
- package/tests/plugin/StateView/PsShipmentsView.res +3 -2
- package/tests/projection/LifecycleTrailTest.res +1 -4
- package/tests/projection/ProjectionOptimizeTest.res +13 -9
- package/tests/querydb/QueryDbFixtures.res +0 -1
- package/tests/querydb/QueryDbOperationsTest.res +99 -64
- package/tests/querydb/TaggedUnionStampTest.res +10 -8
- package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res +50 -35
- package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +4 -10
- package/tests/util/CommandPublisherTest.res +0 -1
- package/tests/util/CsvStreamTest.res +3 -3
- package/tests/util/LruTest.res +84 -56
package/tests/dcb/DcbTagTest.res
CHANGED
|
@@ -417,7 +417,11 @@ describe("DcbTag:", () => {
|
|
|
417
417
|
DcbFixtures.compositeEventSchema,
|
|
418
418
|
)
|
|
419
419
|
expect(fields->Array.length)->toBe(3)->ignore
|
|
420
|
-
expect(fields->Array.map(f => f.name))->toEqual([
|
|
420
|
+
expect(fields->Array.map(f => f.name))->toEqual([
|
|
421
|
+
"environment",
|
|
422
|
+
"platformName",
|
|
423
|
+
"pluginName",
|
|
424
|
+
])
|
|
421
425
|
},
|
|
422
426
|
)
|
|
423
427
|
|
|
@@ -601,10 +605,12 @@ describe("DcbTag:", () => {
|
|
|
601
605
|
| JsExn(err) =>
|
|
602
606
|
threw := true
|
|
603
607
|
expect(
|
|
604
|
-
JsExn.message(err)
|
|
605
|
-
|
|
606
|
-
),
|
|
607
|
-
)
|
|
608
|
+
JsExn.message(err)
|
|
609
|
+
->Option.getOr("")
|
|
610
|
+
->String.includes("mixes @compositePartitionTag and @partitionTag"),
|
|
611
|
+
)
|
|
612
|
+
->toBe(true)
|
|
613
|
+
->ignore
|
|
608
614
|
}
|
|
609
615
|
expect(threw.contents)->toBe(true)
|
|
610
616
|
},
|
|
@@ -623,7 +629,9 @@ describe("DcbTag:", () => {
|
|
|
623
629
|
threw := true
|
|
624
630
|
expect(
|
|
625
631
|
JsExn.message(err)->Option.getOr("")->String.includes("at least 2 annotated fields"),
|
|
626
|
-
)
|
|
632
|
+
)
|
|
633
|
+
->toBe(true)
|
|
634
|
+
->ignore
|
|
627
635
|
}
|
|
628
636
|
expect(threw.contents)->toBe(true)
|
|
629
637
|
},
|
|
@@ -636,7 +644,9 @@ describe("DcbTag:", () => {
|
|
|
636
644
|
() =>
|
|
637
645
|
expect(
|
|
638
646
|
Reventless.DcbTag.isCompositePartitionMember(
|
|
639
|
-
Reventless.DcbTag.compositePartitionMember(
|
|
647
|
+
Reventless.DcbTag.compositePartitionMember(
|
|
648
|
+
~position=0,
|
|
649
|
+
)->Reventless.DcbTag.toUnknownSchema,
|
|
640
650
|
),
|
|
641
651
|
)->toBe(true),
|
|
642
652
|
)
|
|
@@ -655,9 +665,7 @@ describe("DcbTag:", () => {
|
|
|
655
665
|
"returns false for plain S.string",
|
|
656
666
|
() =>
|
|
657
667
|
expect(
|
|
658
|
-
Reventless.DcbTag.isCompositePartitionMember(
|
|
659
|
-
S.string->Reventless.DcbTag.toUnknownSchema,
|
|
660
|
-
),
|
|
668
|
+
Reventless.DcbTag.isCompositePartitionMember(S.string->Reventless.DcbTag.toUnknownSchema),
|
|
661
669
|
)->toBe(false),
|
|
662
670
|
)
|
|
663
671
|
})
|
|
@@ -844,7 +852,10 @@ describe("DcbTag:", () => {
|
|
|
844
852
|
~crossPartitionTagKeys=["studentId"],
|
|
845
853
|
),
|
|
846
854
|
)->toEqual([
|
|
847
|
-
{
|
|
855
|
+
{
|
|
856
|
+
Reventless.DcbTag.eventTypes: ["StudentSubscribed"],
|
|
857
|
+
tags: [{key: "courseId", value: "C1"}],
|
|
858
|
+
},
|
|
848
859
|
{eventTypes: ["StudentSubscribed"], tags: [{key: "studentId", value: "S1"}]},
|
|
849
860
|
]),
|
|
850
861
|
)
|
|
@@ -7,28 +7,25 @@ type producedA =
|
|
|
7
7
|
| ItemRenamed({itemId: @s.matches(Reventless.DcbTag.string) string, newName: string})
|
|
8
8
|
|
|
9
9
|
@schema
|
|
10
|
-
type producedB =
|
|
11
|
-
| ItemArchived({itemId: @s.matches(Reventless.DcbTag.string) string})
|
|
10
|
+
type producedB = ItemArchived({itemId: @s.matches(Reventless.DcbTag.string) string})
|
|
12
11
|
|
|
13
12
|
// A second producer for ItemCreated with matching fields/tags
|
|
14
13
|
@schema
|
|
15
14
|
type producedADuplicate =
|
|
16
|
-
|
|
15
|
+
ItemCreated({itemId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
17
16
|
|
|
18
17
|
// A second producer for ItemCreated with DIFFERENT fields (missing name)
|
|
19
18
|
@schema
|
|
20
|
-
type producedAMismatchFields =
|
|
21
|
-
| ItemCreated({itemId: @s.matches(Reventless.DcbTag.string) string})
|
|
19
|
+
type producedAMismatchFields = ItemCreated({itemId: @s.matches(Reventless.DcbTag.string) string})
|
|
22
20
|
|
|
23
21
|
// A second producer for ItemCreated with mismatched tag annotations
|
|
24
22
|
@schema
|
|
25
|
-
type producedAMismatchTags =
|
|
26
|
-
| ItemCreated({itemId: string, name: string})
|
|
23
|
+
type producedAMismatchTags = ItemCreated({itemId: string, name: string})
|
|
27
24
|
|
|
28
25
|
// A second producer for ItemCreated with mismatched field type
|
|
29
26
|
@schema
|
|
30
27
|
type producedAMismatchType =
|
|
31
|
-
|
|
28
|
+
ItemCreated({itemId: @s.matches(Reventless.DcbTag.string) string, name: float})
|
|
32
29
|
|
|
33
30
|
// Consumed events — full shape
|
|
34
31
|
@schema
|
|
@@ -38,8 +35,7 @@ type consumedFull =
|
|
|
38
35
|
|
|
39
36
|
// Consumed events — partial projection (only itemId)
|
|
40
37
|
@schema
|
|
41
|
-
type consumedPartial =
|
|
42
|
-
| ItemCreated({itemId: string})
|
|
38
|
+
type consumedPartial = ItemCreated({itemId: string})
|
|
43
39
|
|
|
44
40
|
// Consumed events — payload-less
|
|
45
41
|
@schema
|
|
@@ -49,18 +45,15 @@ type consumedPayloadLess =
|
|
|
49
45
|
|
|
50
46
|
// Consumed events — references nonexistent producer
|
|
51
47
|
@schema
|
|
52
|
-
type consumedDangling =
|
|
53
|
-
| ItemDeleted({itemId: string})
|
|
48
|
+
type consumedDangling = ItemDeleted({itemId: string})
|
|
54
49
|
|
|
55
50
|
// Consumed events — field not in produced shape
|
|
56
51
|
@schema
|
|
57
|
-
type consumedBadField =
|
|
58
|
-
| ItemCreated({itemId: string, rating: float})
|
|
52
|
+
type consumedBadField = ItemCreated({itemId: string, rating: float})
|
|
59
53
|
|
|
60
54
|
// Consumed events — type mismatch
|
|
61
55
|
@schema
|
|
62
|
-
type consumedBadType =
|
|
63
|
-
| ItemCreated({itemId: string, name: float})
|
|
56
|
+
type consumedBadType = ItemCreated({itemId: string, name: float})
|
|
64
57
|
|
|
65
58
|
// --- Composite-read (Issue 5) fixtures ---
|
|
66
59
|
|
|
@@ -74,8 +67,7 @@ type recordDemandCommand =
|
|
|
74
67
|
|
|
75
68
|
// Single-tag command → partition read, never composite.
|
|
76
69
|
@schema
|
|
77
|
-
type touchDemandCommand =
|
|
78
|
-
| TouchDemand({productId: @s.matches(Reventless.DcbTag.string) string})
|
|
70
|
+
type touchDemandCommand = TouchDemand({productId: @s.matches(Reventless.DcbTag.string) string})
|
|
79
71
|
|
|
80
72
|
// Tagged-array command → per-element OR (single-tag) clauses, never composite.
|
|
81
73
|
@schema
|
|
@@ -137,213 +129,291 @@ type flaggedPartitionScoped =
|
|
|
137
129
|
|
|
138
130
|
describe("DcbValidation:", () => {
|
|
139
131
|
describe("validateProducedAndConsumed", () => {
|
|
140
|
-
testSync(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
let
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
132
|
+
testSync(
|
|
133
|
+
"passes when all consumed events match produced events",
|
|
134
|
+
() => {
|
|
135
|
+
let result = validate(
|
|
136
|
+
~produced=[("AddItem", producedASchema->u), ("ArchiveItem", producedBSchema->u)],
|
|
137
|
+
~consumed=[("ItemView", consumedFullSchema->u)],
|
|
138
|
+
)
|
|
139
|
+
expect(result)->toEqual(Ok())
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
testSync(
|
|
144
|
+
"passes with partial field projection",
|
|
145
|
+
() => {
|
|
146
|
+
let result = validate(
|
|
147
|
+
~produced=[("AddItem", producedASchema->u)],
|
|
148
|
+
~consumed=[("CheckExists", consumedPartialSchema->u)],
|
|
149
|
+
)
|
|
150
|
+
expect(result)->toEqual(Ok())
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
testSync(
|
|
155
|
+
"passes with payload-less consumed events",
|
|
156
|
+
() => {
|
|
157
|
+
let result = validate(
|
|
158
|
+
~produced=[("AddItem", producedASchema->u), ("ArchiveItem", producedBSchema->u)],
|
|
159
|
+
~consumed=[("CheckExists", consumedPayloadLessSchema->u)],
|
|
160
|
+
)
|
|
161
|
+
expect(result)->toEqual(Ok())
|
|
162
|
+
},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
testSync(
|
|
166
|
+
"passes when multiple producers have identical shapes and tags",
|
|
167
|
+
() => {
|
|
168
|
+
let result = validate(
|
|
169
|
+
~produced=[("AddItem", producedASchema->u), ("CloneItem", producedADuplicateSchema->u)],
|
|
170
|
+
~consumed=[],
|
|
171
|
+
)
|
|
172
|
+
expect(result)->toEqual(Ok())
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
testSync(
|
|
177
|
+
"fails when consumed event has no producer",
|
|
178
|
+
() => {
|
|
179
|
+
let result = validate(
|
|
180
|
+
~produced=[("AddItem", producedASchema->u)],
|
|
181
|
+
~consumed=[("DeleteHandler", consumedDanglingSchema->u)],
|
|
182
|
+
)
|
|
183
|
+
switch result {
|
|
184
|
+
| Error(errors) =>
|
|
185
|
+
expect(errors->Array.length)->toBe(1)->ignore
|
|
186
|
+
let err = errors->Array.getUnsafe(0)
|
|
187
|
+
expect(err.sliceName)->toBe("DeleteHandler")->ignore
|
|
188
|
+
expect(err.message->String.includes("ItemDeleted"))->toBe(true)->ignore
|
|
189
|
+
expect(err.message->String.includes("no slice produces it"))->toBe(true)
|
|
190
|
+
| Ok() => fail("Expected validation error")
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
testSync(
|
|
196
|
+
"fails when consumed event references field not in produced shape",
|
|
197
|
+
() => {
|
|
198
|
+
let result = validate(
|
|
199
|
+
~produced=[("AddItem", producedASchema->u)],
|
|
200
|
+
~consumed=[("RatingView", consumedBadFieldSchema->u)],
|
|
201
|
+
)
|
|
202
|
+
switch result {
|
|
203
|
+
| Error(errors) =>
|
|
204
|
+
expect(errors->Array.length)->toBe(1)->ignore
|
|
205
|
+
let err = errors->Array.getUnsafe(0)
|
|
206
|
+
expect(err.sliceName)->toBe("RatingView")->ignore
|
|
207
|
+
expect(err.message->String.includes("rating"))->toBe(true)->ignore
|
|
208
|
+
expect(err.message->String.includes("no field"))->toBe(true)
|
|
209
|
+
| Ok() => fail("Expected validation error")
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
testSync(
|
|
215
|
+
"fails when consumed field type mismatches produced field type",
|
|
216
|
+
() => {
|
|
217
|
+
let result = validate(
|
|
218
|
+
~produced=[("AddItem", producedASchema->u)],
|
|
219
|
+
~consumed=[("BadTypeView", consumedBadTypeSchema->u)],
|
|
220
|
+
)
|
|
221
|
+
switch result {
|
|
222
|
+
| Error(errors) =>
|
|
223
|
+
expect(errors->Array.length)->toBe(1)->ignore
|
|
224
|
+
let err = errors->Array.getUnsafe(0)
|
|
225
|
+
expect(err.sliceName)->toBe("BadTypeView")->ignore
|
|
226
|
+
expect(err.message->String.includes("name"))->toBe(true)->ignore
|
|
227
|
+
expect(err.message->String.includes("float"))->toBe(true)->ignore
|
|
228
|
+
expect(err.message->String.includes("string"))->toBe(true)
|
|
229
|
+
| Ok() => fail("Expected validation error")
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
testSync(
|
|
235
|
+
"fails when producers have different fields for same TAG",
|
|
236
|
+
() => {
|
|
237
|
+
let result = validate(
|
|
238
|
+
~produced=[
|
|
239
|
+
("AddItem", producedASchema->u),
|
|
240
|
+
("ImportItem", producedAMismatchFieldsSchema->u),
|
|
241
|
+
],
|
|
242
|
+
~consumed=[],
|
|
243
|
+
)
|
|
244
|
+
switch result {
|
|
245
|
+
| Error(errors) =>
|
|
246
|
+
expect(errors->Array.length)->toBeGreaterThanOrEqual(1)->ignore
|
|
247
|
+
let err = errors->Array.getUnsafe(0)
|
|
248
|
+
expect(err.message->String.includes("AddItem"))->toBe(true)->ignore
|
|
249
|
+
expect(err.message->String.includes("ImportItem"))->toBe(true)->ignore
|
|
250
|
+
expect(err.message->String.includes("name"))->toBe(true)
|
|
251
|
+
| Ok() => fail("Expected validation error")
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
testSync(
|
|
257
|
+
"fails when producers have different tag annotations for same TAG",
|
|
258
|
+
() => {
|
|
259
|
+
let result = validate(
|
|
260
|
+
~produced=[
|
|
261
|
+
("AddItem", producedASchema->u),
|
|
262
|
+
("UntaggerItem", producedAMismatchTagsSchema->u),
|
|
263
|
+
],
|
|
264
|
+
~consumed=[],
|
|
265
|
+
)
|
|
266
|
+
switch result {
|
|
267
|
+
| Error(errors) =>
|
|
268
|
+
expect(errors->Array.length)->toBeGreaterThanOrEqual(1)->ignore
|
|
269
|
+
let hasTagError =
|
|
270
|
+
errors->Array.some(err => err.message->String.includes("tag annotations"))
|
|
271
|
+
expect(hasTagError)->toBe(true)
|
|
272
|
+
| Ok() => fail("Expected validation error")
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
testSync(
|
|
278
|
+
"fails when producers have different field types for same TAG",
|
|
279
|
+
() => {
|
|
280
|
+
let result = validate(
|
|
281
|
+
~produced=[
|
|
282
|
+
("AddItem", producedASchema->u),
|
|
283
|
+
("BadTypeItem", producedAMismatchTypeSchema->u),
|
|
284
|
+
],
|
|
285
|
+
~consumed=[],
|
|
286
|
+
)
|
|
287
|
+
switch result {
|
|
288
|
+
| Error(errors) =>
|
|
289
|
+
expect(errors->Array.length)->toBeGreaterThanOrEqual(1)->ignore
|
|
290
|
+
let err = errors->Array.getUnsafe(0)
|
|
291
|
+
expect(err.message->String.includes("name"))->toBe(true)->ignore
|
|
292
|
+
expect(err.message->String.includes("string"))->toBe(true)->ignore
|
|
293
|
+
expect(err.message->String.includes("float"))->toBe(true)
|
|
294
|
+
| Ok() => fail("Expected validation error")
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
testSync(
|
|
300
|
+
"passes with empty produced and consumed",
|
|
301
|
+
() => {
|
|
302
|
+
let result = validate(~produced=[], ~consumed=[])
|
|
303
|
+
expect(result)->toEqual(Ok())
|
|
304
|
+
},
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
testSync(
|
|
308
|
+
"collects multiple errors at once",
|
|
309
|
+
() => {
|
|
310
|
+
let result = validate(
|
|
311
|
+
~produced=[("AddItem", producedASchema->u)],
|
|
312
|
+
~consumed=[
|
|
313
|
+
("DeleteHandler", consumedDanglingSchema->u),
|
|
314
|
+
("BadTypeView", consumedBadTypeSchema->u),
|
|
315
|
+
],
|
|
316
|
+
)
|
|
317
|
+
switch result {
|
|
318
|
+
| Error(errors) => expect(errors->Array.length)->toBeGreaterThanOrEqual(2)
|
|
319
|
+
| Ok() => fail("Expected validation errors")
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
)
|
|
282
323
|
})
|
|
283
324
|
|
|
284
325
|
describe("validateCompositeReads (Issue 5)", () => {
|
|
285
|
-
let check = slices =>
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
326
|
+
let check = slices => Reventless.DcbValidation.validateCompositeReads(~slices, ~producedTagKeys)
|
|
327
|
+
|
|
328
|
+
testSync(
|
|
329
|
+
"warns when a composite read's consumed type carries an extra tag",
|
|
330
|
+
() => {
|
|
331
|
+
let warnings = check([
|
|
332
|
+
("RecordDemand", recordDemandCommandSchema->u, demandConsumedSchema->u),
|
|
333
|
+
])
|
|
334
|
+
// ProductDemandRevoked carries customerId beyond [productId, orderId] → missed.
|
|
335
|
+
expect(warnings->Array.length)->toBe(1)
|
|
336
|
+
let w = warnings->Array.getUnsafe(0)
|
|
337
|
+
expect(w.sliceName)->toBe("RecordDemand")
|
|
338
|
+
expect(w.message->String.includes("ProductDemandRevoked"))->toBe(true)
|
|
339
|
+
expect(w.message->String.includes("customerId"))->toBe(true)
|
|
340
|
+
},
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
testSync(
|
|
344
|
+
"does not warn for an exact-match consumed type",
|
|
345
|
+
() => {
|
|
346
|
+
// ProductDemandRecorded carries exactly [productId, orderId] — read finds it.
|
|
347
|
+
let warnings = check([
|
|
348
|
+
("RecordDemand", recordDemandCommandSchema->u, demandConsumedSchema->u),
|
|
349
|
+
])
|
|
350
|
+
expect(
|
|
351
|
+
warnings->Array.some(w => w.message->String.includes("ProductDemandRecorded")),
|
|
352
|
+
)->toBe(false)
|
|
353
|
+
},
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
testSync(
|
|
357
|
+
"ignores single-tag commands (partition read, not composite)",
|
|
358
|
+
() => {
|
|
359
|
+
let warnings = check([
|
|
360
|
+
("TouchDemand", touchDemandCommandSchema->u, demandConsumedSchema->u),
|
|
361
|
+
])
|
|
362
|
+
expect(warnings)->toEqual([])
|
|
363
|
+
},
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
testSync(
|
|
367
|
+
"ignores tagged-array commands (per-element OR, not composite)",
|
|
368
|
+
() => {
|
|
369
|
+
let warnings = check([("PlaceOrder", placeOrderCommandSchema->u, demandConsumedSchema->u)])
|
|
370
|
+
expect(warnings)->toEqual([])
|
|
371
|
+
},
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
testSync(
|
|
375
|
+
"no warnings when nothing reads compositely",
|
|
376
|
+
() => {
|
|
377
|
+
expect(check([]))->toEqual([])
|
|
378
|
+
},
|
|
379
|
+
)
|
|
319
380
|
})
|
|
320
381
|
|
|
321
382
|
describe("validateCrossPartitionScope (Issue 13)", () => {
|
|
322
383
|
let check = producers => Reventless.DcbValidation.validateCrossPartitionScope(~producers)
|
|
323
384
|
|
|
324
|
-
testSync(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
385
|
+
testSync(
|
|
386
|
+
"no warning when every carrier of a key agrees on cross-partition scope",
|
|
387
|
+
() => {
|
|
388
|
+
let warnings = check([
|
|
389
|
+
("Subscribe", subscribedCrossPartitionSchema->u),
|
|
390
|
+
("Unsubscribe", unsubscribedCrossPartitionSchema->u),
|
|
391
|
+
])
|
|
392
|
+
expect(warnings)->toEqual([])
|
|
393
|
+
},
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
testSync(
|
|
397
|
+
"warns when one producer leaves a cross-partition key partition-scoped",
|
|
398
|
+
() => {
|
|
399
|
+
let warnings = check([
|
|
400
|
+
("Subscribe", subscribedCrossPartitionSchema->u),
|
|
401
|
+
("Flag", flaggedPartitionScopedSchema->u),
|
|
402
|
+
])
|
|
403
|
+
// 'Flag' carries studentId but partition-scoped, while 'Subscribe' declares it
|
|
404
|
+
// @crossPartition → scope mismatch.
|
|
405
|
+
expect(warnings->Array.length)->toBe(1)
|
|
406
|
+
let w = warnings->Array.getUnsafe(0)
|
|
407
|
+
expect(w.sliceName)->toBe("Flag")
|
|
408
|
+
expect(w.message->String.includes("studentId"))->toBe(true)
|
|
409
|
+
},
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
testSync(
|
|
413
|
+
"no warning when no key is cross-partition anywhere",
|
|
414
|
+
() => {
|
|
415
|
+
expect(check([("Flag", flaggedPartitionScopedSchema->u)]))->toEqual([])
|
|
416
|
+
},
|
|
417
|
+
)
|
|
348
418
|
})
|
|
349
419
|
})
|