@reventlessdev/reventless-local 3.0.0-alpha.249 → 3.0.0-alpha.250
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 +8 -0
- package/package.json +13 -13
- package/src/BakedManifest.res +5 -6
- package/src/EmitCapabilities.res +3 -1
- package/src/LocalPlatformRegistry.res +3 -1
- package/src/LocalPlatformStart.res +7 -6
- package/src/LocalSeedTarget.res +3 -1
- package/src/Platform.res +291 -215
- package/src/ShellConfig.res +10 -17
- package/src/UiHints.res +8 -14
- package/src/UiSlots.res +10 -17
- package/src/adapter/Api/LocalEvents_Server.res +5 -3
- package/src/adapter/Api/LocalGraphQL_Adapter.res +4 -4
- package/src/adapter/Auth/LocalAuth.res +11 -15
- package/src/adapter/Auth/UserStore.res +5 -7
- package/src/adapter/Backend.res +1 -2
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +42 -26
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +13 -3
- package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +15 -17
- package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +18 -22
- package/src/adapter/Counter/LocalCounterHandler.res +1 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +33 -22
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +57 -57
- package/src/adapter/DomainGraphQL_Server.res +35 -34
- package/src/adapter/DomainMCP_Server.res +45 -41
- package/src/adapter/EventCollector/LocalEventCollectorChannel.res +47 -36
- package/src/adapter/EventHistory/EventHistoryResolvers_GraphQL.res +15 -12
- package/src/adapter/EventLog/EventLogStorage_InMemory.res +9 -4
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +36 -24
- package/src/adapter/EventTopic/LocalEventTopicPublisher.res +9 -3
- package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +9 -14
- package/src/adapter/LocalBus.res +12 -12
- package/src/adapter/LocalCapabilities.res.mjs +1 -1
- package/src/adapter/LocalGeocodeResolvers.res +1 -4
- package/src/adapter/LocalSideEffectHandler.res +1 -1
- package/src/adapter/LocalStateChangeDescriptor.res +2 -1
- package/src/adapter/MCP_ServerInstance.res +51 -39
- package/src/adapter/ObjectStore/LocalObjectStore.res +5 -2
- package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -2
- package/src/adapter/PlatformGraphQL_Server.res +9 -5
- package/src/adapter/ProjectionCheckpoint.res +8 -1
- package/src/adapter/ProjectionPending.res +1 -3
- package/src/adapter/QueryDb/LocalQueryDbStorage.res +2 -1
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +367 -269
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +5 -3
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +63 -40
- package/src/adapter/QueryDb/SortKey_Filter.res +14 -6
- package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +12 -10
- package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +8 -2
- package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +1 -4
- package/src/adapter/Scheduler/LocalScheduledPublisher.res +12 -12
- package/src/adapter/SqliteDriver.res +16 -4
- package/src/adapter/Task/TaskBucket_Sqlite.res +5 -6
- package/src/components/Aggregate_Builder.res +6 -1
- package/src/components/Counter_Builder.res +1 -5
- package/src/components/DcbEventLog_Builder.res +5 -3
- package/src/components/ExtensionPoint_Builder.res +13 -15
- package/src/components/InboundTranslationSlice_Builder.res +3 -2
- package/src/components/OutboundTranslationSlice_Builder.res +3 -2
- package/src/components/ReadModel_Builder.res +18 -20
- package/src/components/StateViewSlice_Builder.res +3 -2
- package/src/components/Task_Builder.res +12 -13
- package/src/reset/LocalSeedReset.res +2 -5
- package/src/test/Mocks/MockDcbEventLogStorage.res +5 -1
- package/src/test/Mocks/MockEventLogStorage.res +8 -3
- package/src/test/Mocks/MockQueryDbStorage.res +3 -2
- package/src/test/TestRunner.res +1 -1
- package/tests/BakedManifestFilesTest.res +7 -10
- package/tests/LocalEventTapTest.res +26 -21
- package/tests/LocalPlatformStartTest.res +33 -28
- package/tests/PluginEventDecodeTest.res +3 -3
- package/tests/ShellConfigTest.res +30 -21
- package/tests/SplitApiFixtures.res +34 -10
- package/tests/SplitApiTest.res +92 -62
- package/tests/adapter/BackendParityTest.res +180 -130
- package/tests/adapter/CollectNEventsTest.res +6 -4
- package/tests/adapter/CommandAuthorizationTest.res +47 -41
- package/tests/adapter/CommandTopicChannelTest.res +163 -139
- package/tests/adapter/CounterHandlerTest.res +95 -77
- package/tests/adapter/DcbEventLogStorageSqliteTest.res +171 -168
- package/tests/adapter/DcbEventLogStorageTest.res +264 -207
- package/tests/adapter/DomainGraphQL_ServerTest.res +53 -43
- package/tests/adapter/EventCollectorChannelTest.res +153 -124
- package/tests/adapter/EventHistoryResolverTest.res +46 -18
- package/tests/adapter/EventLogSnapshotParityTest.res +57 -45
- package/tests/adapter/EventLogStorageSqliteTest.res +9 -10
- package/tests/adapter/EventTapTest.res +37 -20
- package/tests/adapter/EventTopicPublisherTest.res +74 -50
- package/tests/adapter/GraphQLSubscriptionBridgeTest.res +4 -3
- package/tests/adapter/GraphQL_SchemaInspectorTest.res +666 -568
- package/tests/adapter/GraphQL_SubscriptionResolversTest.res +105 -96
- package/tests/adapter/HeartbeatRunnerTest.res +99 -81
- package/tests/adapter/InboundTranslationMutationTest.res +4 -1
- package/tests/adapter/LocalAuthHttpTest.res +79 -85
- package/tests/adapter/LocalAuthLoginTest.res +42 -50
- package/tests/adapter/LocalAuthTest.res +8 -16
- package/tests/adapter/LocalAuthUserStoreTest.res +2 -7
- package/tests/adapter/LocalBusBoundedTest.res +187 -117
- package/tests/adapter/LocalBusPubSubTest.res +118 -76
- package/tests/adapter/LocalDispatchContextTest.res +1 -2
- package/tests/adapter/LocalEvents_ServerTest.res +242 -187
- package/tests/adapter/PgProjectionCatchupTest.res +44 -10
- package/tests/adapter/ProjectionCheckpointTest.res +261 -206
- package/tests/adapter/QueryDbGsiTtlTest.res +123 -83
- package/tests/adapter/QueryDbListPushdownParityTest.res +107 -67
- package/tests/adapter/QueryDbListResolverTest.res +225 -208
- package/tests/adapter/QueryDbResolvedFieldsTest.res +17 -14
- package/tests/adapter/QueryDbStorageSqliteTest.res +33 -49
- package/tests/adapter/QueryDbStorageTest.res +163 -117
- package/tests/adapter/QueryDbUnionRoundTripTest.res +8 -10
- package/tests/adapter/QueryEngineTest.res +179 -126
- package/tests/adapter/ScheduledPublisherTest.res +171 -135
- package/tests/adapter/ServedBucketHttpTest.res +65 -59
- package/tests/adapter/SortKey_FilterTest.res +19 -3
- package/tests/adapter/SqliteDriverTest.res +13 -10
- package/tests/adapter/TaskBucketTest.res +94 -80
- package/tests/components/aggregate/AggregateTest.res +31 -18
- package/tests/components/automationslice/AutomationSliceCallbackTest.res +259 -222
- package/tests/components/automationslice/AutomationSliceFixtures.res +4 -2
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +38 -38
- package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +12 -14
- package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +8 -32
- package/tests/components/commandgenerator/CommandGeneratorFixtures.res +7 -5
- package/tests/components/commandgenerator/CommandGeneratorTest.res +40 -31
- package/tests/components/commandtopic/CommandTopicFixtures.res +3 -4
- package/tests/components/commandtopic/CommandTopicStreamFixtures.res +7 -5
- package/tests/components/commandtopic/CommandTopicTest.res +24 -15
- package/tests/components/counter/CounterFixtures.res +6 -8
- package/tests/components/dcb/DcbCrossPartitionFixtures.res +8 -2
- package/tests/components/dcb/DcbCrossPartitionReadTest.res +20 -13
- package/tests/components/dcb/DcbEventLogAppendStreamTest.res +12 -8
- package/tests/components/dcb/DcbEventLogStreamTest.res +88 -61
- package/tests/components/dcb/DcbFixtures.res +4 -1
- package/tests/components/dcb/DcbTest.res +9 -6
- package/tests/components/eventcollector/EventCollectorFixtures.res +0 -1
- package/tests/components/eventcollector/EventCollectorTest.res +6 -3
- package/tests/components/eventlog/EventLogAppendStreamTest.res +88 -68
- package/tests/components/eventlog/EventLogConflictTest.res +8 -4
- package/tests/components/eventlog/EventLogFixtures.res +3 -3
- package/tests/components/eventlog/EventLogProvisioningSeamTest.res +26 -13
- package/tests/components/eventlog/EventLogStreamTest.res +119 -56
- package/tests/components/eventlog/EventLogTest.res +20 -4
- package/tests/components/eventtopic/EventTopicFixtures.res +3 -3
- package/tests/components/eventtopic/EventTopicStreamFixtures.res +1 -2
- package/tests/components/eventtopic/EventTopicStreamTest.res +7 -1
- package/tests/components/extensionpoint/ExtensionPointFixtures.res +14 -9
- package/tests/components/extensionpoint/ExtensionPointTest.res +3 -2
- package/tests/components/heartbeat/HeartbeatFixtures.res +5 -6
- package/tests/components/heartbeat/HeartbeatTest.res +56 -47
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +271 -249
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +3 -2
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +361 -276
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +8 -10
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +37 -43
- package/tests/components/querydb/QueryDbFixtures.res +1 -5
- package/tests/components/querydb/QueryDbTest.res +2 -4
- package/tests/components/readmodel/DcbReadModelE2EFixtures.res +15 -18
- package/tests/components/readmodel/MixedSourceReadModelFixtures.res +5 -7
- package/tests/components/readmodel/ReadModelFixtures.res +8 -8
- package/tests/components/scheduler/SchedulerTest.res +76 -58
- package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +4 -4
- package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res +5 -7
- package/tests/components/stateviewslice/StateViewSliceFixtures.res +8 -6
- package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +17 -11
- package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +12 -2
- package/tests/components/task/TaskFixtures.res +5 -5
- package/tests/components/task/TaskTest.res +95 -80
- package/tests/plugin/PluginsProjection_GWT.res +3 -1
- package/tests/plugin/UiFragments_GWT.res +6 -1
- package/tests/reset/LocalSeedResetTest.res +1 -0
|
@@ -21,8 +21,7 @@ module SendTrackingEmailSpec = {
|
|
|
21
21
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
22
22
|
|
|
23
23
|
@schema
|
|
24
|
-
type consumedEvent =
|
|
25
|
-
| OrderShipped({orderId: string, email: string})
|
|
24
|
+
type consumedEvent = OrderShipped({orderId: string, email: string})
|
|
26
25
|
|
|
27
26
|
@schema
|
|
28
27
|
type outboundItem = {orderId: string, email: string}
|
|
@@ -56,8 +55,7 @@ module ProcessPaymentSpec = {
|
|
|
56
55
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
57
56
|
|
|
58
57
|
@schema
|
|
59
|
-
type consumedEvent =
|
|
60
|
-
| PaymentReceived({orderId: string, amount: float})
|
|
58
|
+
type consumedEvent = PaymentReceived({orderId: string, amount: float})
|
|
61
59
|
|
|
62
60
|
@schema
|
|
63
61
|
type outboundItem = {orderId: string, amount: float}
|
|
@@ -71,9 +69,9 @@ module ProcessPaymentSpec = {
|
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
// Simulate calling external payment gateway, then return a command
|
|
74
|
-
let translateFn: ref<
|
|
75
|
-
|
|
76
|
-
)
|
|
72
|
+
let translateFn: ref<
|
|
73
|
+
(string, outboundItem) => promise<result<option<(string, inboundCommand)>, string>>,
|
|
74
|
+
> = ref(async (id, _item) => Ok(Some((id, ConfirmPayment({orderId: id})))))
|
|
77
75
|
|
|
78
76
|
// The mock stays two-arg: a test that wants to vary behaviour varies it on
|
|
79
77
|
// the item, and threading capabilities into every fixture would make each
|
|
@@ -82,9 +80,9 @@ module ProcessPaymentSpec = {
|
|
|
82
80
|
|
|
83
81
|
// Overridable like `translateFn`, so a test can assert both answers a slice may
|
|
84
82
|
// give when its budget runs out: say nothing, or tell the domain.
|
|
85
|
-
let onExhaustedFn: ref<
|
|
86
|
-
(
|
|
87
|
-
)
|
|
83
|
+
let onExhaustedFn: ref<
|
|
84
|
+
(string, outboundItem, option<string>) => option<(string, inboundCommand)>,
|
|
85
|
+
> = ref((_id, _item, _lastError) => None)
|
|
88
86
|
let onExhausted = (id, item, ~lastError) => onExhaustedFn.contents(id, item, lastError)
|
|
89
87
|
|
|
90
88
|
let maxRetries = 2
|
package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res
CHANGED
|
@@ -25,19 +25,13 @@ module PlaceSpec = {
|
|
|
25
25
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
26
26
|
|
|
27
27
|
@schema
|
|
28
|
-
type event = Placed({
|
|
29
|
-
orderId: @s.matches(Reventless.DcbTag.string) string,
|
|
30
|
-
customerId: string,
|
|
31
|
-
})
|
|
28
|
+
type event = Placed({orderId: @s.matches(Reventless.DcbTag.string) string, customerId: string})
|
|
32
29
|
|
|
33
30
|
@schema
|
|
34
31
|
type consumedEvent = Placed
|
|
35
32
|
|
|
36
33
|
@schema
|
|
37
|
-
type command = Place({
|
|
38
|
-
orderId: @s.matches(Reventless.DcbTag.string) string,
|
|
39
|
-
customerId: string,
|
|
40
|
-
})
|
|
34
|
+
type command = Place({orderId: @s.matches(Reventless.DcbTag.string) string, customerId: string})
|
|
41
35
|
|
|
42
36
|
@schema
|
|
43
37
|
type error = AlreadyPlaced
|
|
@@ -137,42 +131,40 @@ let dcbEventLog = DcbLogMaker.make(
|
|
|
137
131
|
// ─────────────────────────────────────────────────────────────
|
|
138
132
|
|
|
139
133
|
let publishJsons: ReventlessInfra.CommandTopic.publishJsons = async cmdJsons => {
|
|
140
|
-
let _ =
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
)
|
|
153
|
-
->Option.getOr("")
|
|
154
|
-
| _ => ""
|
|
155
|
-
}
|
|
156
|
-
let fullBody = JSON.Encode.object(
|
|
157
|
-
Dict.fromArray([
|
|
158
|
-
("id", JSON.Encode.string(cmdJson.id)),
|
|
159
|
-
("meta", cmdJson.meta->Reventless.Util_Sury.toJson(Reventless.Message.metaSchema)),
|
|
160
|
-
("command", cmdJson.commandJson),
|
|
161
|
-
]),
|
|
134
|
+
let _ = await cmdJsons
|
|
135
|
+
->Array.map(async cmdJson => {
|
|
136
|
+
let typeName = switch cmdJson.commandJson {
|
|
137
|
+
| JSON.Object(dict) =>
|
|
138
|
+
dict
|
|
139
|
+
->Dict.get("TAG")
|
|
140
|
+
->Option.flatMap(j =>
|
|
141
|
+
switch j {
|
|
142
|
+
| JSON.String(s) => Some(s)
|
|
143
|
+
| _ => None
|
|
144
|
+
}
|
|
162
145
|
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
146
|
+
->Option.getOr("")
|
|
147
|
+
| _ => ""
|
|
148
|
+
}
|
|
149
|
+
let fullBody = JSON.Encode.object(
|
|
150
|
+
Dict.fromArray([
|
|
151
|
+
("id", JSON.Encode.string(cmdJson.id)),
|
|
152
|
+
("meta", cmdJson.meta->Reventless.Util_Sury.toJson(Reventless.Message.metaSchema)),
|
|
153
|
+
("command", cmdJson.commandJson),
|
|
154
|
+
]),
|
|
155
|
+
)
|
|
156
|
+
let handlers = ReventlessCore.CommandTopic.getHandlers(typeName)
|
|
157
|
+
let _ = await handlers
|
|
158
|
+
->Array.map(async entry => {
|
|
159
|
+
let item: ReventlessInfra.CommandTopic.topicItem<JSON.t> = {
|
|
160
|
+
reference: cmdJson.id,
|
|
161
|
+
command: fullBody,
|
|
162
|
+
}
|
|
163
|
+
let _ = await entry.handler(Stream.fromIterable([item]))->Effect.runPromise
|
|
174
164
|
})
|
|
175
165
|
->Promise.all
|
|
166
|
+
})
|
|
167
|
+
->Promise.all
|
|
176
168
|
}
|
|
177
169
|
|
|
178
170
|
let publishJsonsOutput = publishJsons->Pulumi.Output.make
|
|
@@ -199,8 +191,10 @@ let sendConfirmSlice = SendConfirm.make(~dcbEventLog, ~publishJsons=publishJsons
|
|
|
199
191
|
let placeCmdJson = (orderId: string): Reventless.Message.commandJson => {
|
|
200
192
|
id: orderId,
|
|
201
193
|
meta: testMeta,
|
|
202
|
-
commandJson: PlaceSpec.Place({
|
|
203
|
-
|
|
194
|
+
commandJson: PlaceSpec.Place({
|
|
195
|
+
orderId,
|
|
196
|
+
customerId: "cust-" ++ orderId,
|
|
197
|
+
})->Reventless.Util_Sury.toJson(PlaceSpec.commandSchema),
|
|
204
198
|
}
|
|
205
199
|
|
|
206
200
|
let readEventTypes = async (orderId: string) => {
|
|
@@ -30,10 +30,6 @@ let _ = TestRunner.setup()
|
|
|
30
30
|
|
|
31
31
|
module QDbStorage = LocalQueryDbStorage.Make(Bus)
|
|
32
32
|
module QDbResolvers = ReventlessCore.QueryDb_Adapter.NoResolvers(QDbStorage)
|
|
33
|
-
module QueryDbMaker = ReventlessCore.QueryDb_Builder.Make(
|
|
34
|
-
ItemQueryDbSpec,
|
|
35
|
-
QDbStorage,
|
|
36
|
-
QDbResolvers,
|
|
37
|
-
)
|
|
33
|
+
module QueryDbMaker = ReventlessCore.QueryDb_Builder.Make(ItemQueryDbSpec, QDbStorage, QDbResolvers)
|
|
38
34
|
|
|
39
35
|
let queryDb = QueryDbMaker.make(~api=(), ~apiRole=())
|
|
@@ -59,8 +59,7 @@ describe("QueryDb (in-memory)", () => {
|
|
|
59
59
|
testPromise("loadStream emits saved states", async () => {
|
|
60
60
|
let ops = await queryDb->ReventlessCore.Component.operations->TestRunner.resolve
|
|
61
61
|
let _ = await ops.save("stream-item", {name: "Streamed", count: 42}, Init, None)
|
|
62
|
-
let arr =
|
|
63
|
-
await ops.loadStream("stream-item")->Stream.runCollect->Effect.runPromise
|
|
62
|
+
let arr = await ops.loadStream("stream-item")->Stream.runCollect->Effect.runPromise
|
|
64
63
|
expect(arr->Array.length)->toBe(1)
|
|
65
64
|
let s = arr->Array.getUnsafe(0)
|
|
66
65
|
expect((s.name, s.count))->toEqual(("Streamed", 42))
|
|
@@ -68,8 +67,7 @@ describe("QueryDb (in-memory)", () => {
|
|
|
68
67
|
|
|
69
68
|
testPromise("loadStream returns empty for unknown id", async () => {
|
|
70
69
|
let ops = await queryDb->ReventlessCore.Component.operations->TestRunner.resolve
|
|
71
|
-
let arr =
|
|
72
|
-
await ops.loadStream("no-such-id")->Stream.runCollect->Effect.runPromise
|
|
70
|
+
let arr = await ops.loadStream("no-such-id")->Stream.runCollect->Effect.runPromise
|
|
73
71
|
expect(arr->Array.length)->toBe(0)
|
|
74
72
|
})
|
|
75
73
|
|
|
@@ -21,20 +21,13 @@ module AddProductSpec = {
|
|
|
21
21
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
22
22
|
|
|
23
23
|
@schema
|
|
24
|
-
type event =
|
|
25
|
-
ProductAdded({
|
|
26
|
-
productId: @s.matches(Reventless.DcbTag.string) string,
|
|
27
|
-
name: string,
|
|
28
|
-
})
|
|
24
|
+
type event = ProductAdded({productId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
29
25
|
|
|
30
26
|
@schema
|
|
31
27
|
type consumedEvent = ProductAdded
|
|
32
28
|
|
|
33
29
|
@schema
|
|
34
|
-
type command = AddProduct({
|
|
35
|
-
productId: @s.matches(Reventless.DcbTag.string) string,
|
|
36
|
-
name: string,
|
|
37
|
-
})
|
|
30
|
+
type command = AddProduct({productId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
38
31
|
|
|
39
32
|
@schema
|
|
40
33
|
type error = ProductAlreadyExists
|
|
@@ -180,7 +173,9 @@ let _addProductSlice = AddProductMaker.make(~dcbEventLog, ~publishJsons=publishJ
|
|
|
180
173
|
// under the key `<name> ++ "DcbEventLog"`.
|
|
181
174
|
// ─────────────────────────────────────────────────────────────
|
|
182
175
|
|
|
183
|
-
let dcbTopicOutputs: ReventlessInfra.EventTopic.outputs = (
|
|
176
|
+
let dcbTopicOutputs: ReventlessInfra.EventTopic.outputs = (
|
|
177
|
+
dcbEventLog->ReventlessInfra.Component.outputs
|
|
178
|
+
).eventTopic
|
|
184
179
|
|
|
185
180
|
let allEventTopics: ReventlessInfra.EventTopic.allOutputs = Dict.fromArray([
|
|
186
181
|
("ProductCatalogDcbEventLog", dcbTopicOutputs),
|
|
@@ -198,18 +193,20 @@ let dispatch = async (commandJson, id) =>
|
|
|
198
193
|
await publishJsons([{Reventless.Message.id, meta: testMeta, commandJson}])
|
|
199
194
|
|
|
200
195
|
let addProductCmd = (productId, name) =>
|
|
201
|
-
AddProductSpec.AddProduct({productId, name})
|
|
202
|
-
|
|
196
|
+
AddProductSpec.AddProduct({productId, name})->Reventless.Util_Sury.toJson(
|
|
197
|
+
AddProductSpec.commandSchema,
|
|
198
|
+
)
|
|
203
199
|
|
|
204
200
|
let loadState = async productId => {
|
|
205
201
|
switch Bus.getQueryDb("TestProductsReadModel") {
|
|
206
202
|
| None => []
|
|
207
203
|
| Some(ops) =>
|
|
208
|
-
let states =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
204
|
+
let states = await ops.loadStream(productId)
|
|
205
|
+
->Stream.runCollect
|
|
206
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
207
|
+
->Effect.runPromise
|
|
208
|
+
states->Array.map(json =>
|
|
209
|
+
json->Reventless.Util_Sury.fromJson(ProductsReadModelSpec.stateSchema)
|
|
210
|
+
)
|
|
214
211
|
}
|
|
215
212
|
}
|
|
@@ -80,8 +80,7 @@ module DcbMapping = Mapping.Make(
|
|
|
80
80
|
{
|
|
81
81
|
let project = (msg: Message.event'<string, DcbSource.event>) =>
|
|
82
82
|
switch msg.event {
|
|
83
|
-
| DcbItemAdded({name}) =>
|
|
84
|
-
Create(msg.id, ({name, source: "dcb"}: MixedReadModelSpec.state))
|
|
83
|
+
| DcbItemAdded({name}) => Create(msg.id, ({name, source: "dcb"}: MixedReadModelSpec.state))
|
|
85
84
|
| DcbItemNameChanged({name}) => Update(msg.id, state => {...state, name})
|
|
86
85
|
}
|
|
87
86
|
},
|
|
@@ -171,11 +170,10 @@ let loadState = async id => {
|
|
|
171
170
|
switch Bus.getQueryDb("TestMixedReadModel") {
|
|
172
171
|
| None => []
|
|
173
172
|
| Some(ops) =>
|
|
174
|
-
let states =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
->Effect.runPromise
|
|
173
|
+
let states = await ops.loadStream(id)
|
|
174
|
+
->Stream.runCollect
|
|
175
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
176
|
+
->Effect.runPromise
|
|
179
177
|
states->Array.map(json => json->Reventless.Util_Sury.fromJson(MixedReadModelSpec.stateSchema))
|
|
180
178
|
}
|
|
181
179
|
}
|
|
@@ -32,7 +32,7 @@ module ItemEventSource = {
|
|
|
32
32
|
let name = "TestItemEventTopic" // must match allEventTopics key AND meta.service in published events
|
|
33
33
|
|
|
34
34
|
@schema
|
|
35
|
-
type event =
|
|
35
|
+
type event = ItemCreated({name: string})
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -114,8 +114,9 @@ let publishItemCreated = async (id, name) => {
|
|
|
114
114
|
("meta", testMeta->Reventless.Util_Sury.toJson(Message.metaSchema)),
|
|
115
115
|
(
|
|
116
116
|
"event",
|
|
117
|
-
ItemEventSource.ItemCreated({name: name})
|
|
118
|
-
|
|
117
|
+
ItemEventSource.ItemCreated({name: name})->Reventless.Util_Sury.toJson(
|
|
118
|
+
ItemEventSource.eventSchema,
|
|
119
|
+
),
|
|
119
120
|
),
|
|
120
121
|
]),
|
|
121
122
|
)
|
|
@@ -128,11 +129,10 @@ let loadState = async id => {
|
|
|
128
129
|
switch Bus.getQueryDb("TestItemReadModel") {
|
|
129
130
|
| None => []
|
|
130
131
|
| Some(ops) =>
|
|
131
|
-
let states =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
->Effect.runPromise
|
|
132
|
+
let states = await ops.loadStream(id)
|
|
133
|
+
->Stream.runCollect
|
|
134
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
135
|
+
->Effect.runPromise
|
|
136
136
|
states->Array.map(json => json->Reventless.Util_Sury.fromJson(ItemReadModelSpec.stateSchema))
|
|
137
137
|
}
|
|
138
138
|
}
|
|
@@ -10,67 +10,85 @@ describe("Scheduler_Builder.Make:", () => {
|
|
|
10
10
|
let _ = afterEach(() => SP.reset())
|
|
11
11
|
|
|
12
12
|
describe("createSchedule:", () => {
|
|
13
|
-
testPromise(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
13
|
+
testPromise(
|
|
14
|
+
"recurring schedule fires event on each interval advance",
|
|
15
|
+
async () => {
|
|
16
|
+
let ops = schedulerOps.contents->Option.getUnsafe
|
|
17
|
+
let count = ref(0)
|
|
18
|
+
Bus.subscribeToEvents(
|
|
19
|
+
"sched-recurring",
|
|
20
|
+
async (_, _, _) => {
|
|
21
|
+
count := count.contents + 1
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
// createSchedule async body has no await — setInterval registered synchronously
|
|
25
|
+
let _ = ops.createSchedule(
|
|
26
|
+
[makeTopicResource("sched-recurring")],
|
|
27
|
+
{name: "test-recurring", rate: Reventless.Schedule.Minutes(1), payload: "{}"},
|
|
28
|
+
)
|
|
29
|
+
// Two microtask ticks: publishEvent uses Effect.all(concurrency=unbounded) which forks
|
|
30
|
+
// child fibers via startFork — one tick runs the child fiber (Queue.offer),
|
|
31
|
+
// a second tick runs the drain fiber callback.
|
|
32
|
+
jest->advanceTimersByTime(60 * 1000)
|
|
33
|
+
let _ = await Promise.resolve()
|
|
34
|
+
let _ = await Promise.resolve()
|
|
35
|
+
expect(count.contents)->toBe(1)
|
|
36
|
+
jest->advanceTimersByTime(60 * 1000)
|
|
37
|
+
let _ = await Promise.resolve()
|
|
38
|
+
let _ = await Promise.resolve()
|
|
39
|
+
expect(count.contents)->toBe(2)
|
|
40
|
+
},
|
|
41
|
+
)
|
|
36
42
|
|
|
37
|
-
testPromise(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
43
|
+
testPromise(
|
|
44
|
+
"single-shot schedule fires once then stops",
|
|
45
|
+
async () => {
|
|
46
|
+
let ops = schedulerOps.contents->Option.getUnsafe
|
|
47
|
+
let count = ref(0)
|
|
48
|
+
Bus.subscribeToEvents(
|
|
49
|
+
"sched-single",
|
|
50
|
+
async (_, _, _) => {
|
|
51
|
+
count := count.contents + 1
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
let _ = ops.createSchedule(
|
|
55
|
+
[makeTopicResource("sched-single")],
|
|
56
|
+
{
|
|
57
|
+
name: "test-single",
|
|
58
|
+
rate: Reventless.Schedule.Single(2024, 1, 1, 0, 0),
|
|
59
|
+
payload: "{}",
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
jest->advanceTimersByTime(0)
|
|
63
|
+
let _ = await Promise.resolve()
|
|
64
|
+
let _ = await Promise.resolve()
|
|
65
|
+
expect(count.contents)->toBe(1)
|
|
66
|
+
jest->advanceTimersByTime(60 * 1000)
|
|
67
|
+
expect(count.contents)->toBe(1) // No further firing
|
|
68
|
+
},
|
|
69
|
+
)
|
|
58
70
|
})
|
|
59
71
|
|
|
60
72
|
describe("deleteSchedule:", () => {
|
|
61
|
-
testPromise(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
testPromise(
|
|
74
|
+
"deleted schedule does not fire after deletion",
|
|
75
|
+
async () => {
|
|
76
|
+
let ops = schedulerOps.contents->Option.getUnsafe
|
|
77
|
+
let count = ref(0)
|
|
78
|
+
Bus.subscribeToEvents(
|
|
79
|
+
"sched-delete",
|
|
80
|
+
async (_, _, _) => {
|
|
81
|
+
count := count.contents + 1
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
let _ = ops.createSchedule(
|
|
85
|
+
[makeTopicResource("sched-delete")],
|
|
86
|
+
{name: "test-delete", rate: Reventless.Schedule.Minutes(2), payload: "{}"},
|
|
87
|
+
)
|
|
88
|
+
let _ = ops.deleteSchedule([], "test-delete")
|
|
89
|
+
jest->advanceTimersByTime(2 * 60 * 1000)
|
|
90
|
+
expect(count.contents)->toBe(0)
|
|
91
|
+
},
|
|
92
|
+
)
|
|
75
93
|
})
|
|
76
94
|
})
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
open TestFixtures
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
// ─────────────────────────────────────────────────────────────
|
|
8
7
|
// Isolated bus + Pulumi mock mode
|
|
9
8
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -20,7 +19,7 @@ module TestSEHSource = {
|
|
|
20
19
|
let name = "TestSEHSource"
|
|
21
20
|
|
|
22
21
|
@schema
|
|
23
|
-
type event =
|
|
22
|
+
type event = OrderPlaced({orderId: string})
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -119,8 +118,9 @@ let publishOrderPlaced = async (id, orderId) => {
|
|
|
119
118
|
("meta", testMeta->Reventless.Util_Sury.toJson(Reventless.Message.metaSchema)),
|
|
120
119
|
(
|
|
121
120
|
"event",
|
|
122
|
-
TestSEHSource.OrderPlaced({orderId: orderId})
|
|
123
|
-
|
|
121
|
+
TestSEHSource.OrderPlaced({orderId: orderId})->Reventless.Util_Sury.toJson(
|
|
122
|
+
TestSEHSource.eventSchema,
|
|
123
|
+
),
|
|
124
124
|
),
|
|
125
125
|
]),
|
|
126
126
|
)
|
|
@@ -48,8 +48,7 @@ module OrdersViewProjection = {
|
|
|
48
48
|
|
|
49
49
|
let project = ({event}: Reventless.StateViewSlice.consumed<consumedEvent>) =>
|
|
50
50
|
switch event {
|
|
51
|
-
| OrderPlaced({id, placedAt}) =>
|
|
52
|
-
[Set(id, {id, placedAt, shippedAt: "", displayName: None})]
|
|
51
|
+
| OrderPlaced({id, placedAt}) => [Set(id, {id, placedAt, shippedAt: "", displayName: None})]
|
|
53
52
|
| OrderShipped({id, shippedAt}) => [Update(id, s => {...s, shippedAt})]
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -88,11 +87,10 @@ let loadState = async id => {
|
|
|
88
87
|
switch Bus.getQueryDb("DnOrdersView") {
|
|
89
88
|
| None => []
|
|
90
89
|
| Some(ops) =>
|
|
91
|
-
let states =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
->Effect.runPromise
|
|
90
|
+
let states = await ops.loadStream(id)
|
|
91
|
+
->Stream.runCollect
|
|
92
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
93
|
+
->Effect.runPromise
|
|
96
94
|
states->Array.map(json => json->Reventless.Util_Sury.fromJson(OrdersViewSpec.stateSchema))
|
|
97
95
|
}
|
|
98
96
|
}
|
|
@@ -67,7 +67,10 @@ let _ = TestRunner.setup()
|
|
|
67
67
|
// ─────────────────────────────────────────────────────────────
|
|
68
68
|
|
|
69
69
|
module ItemEventLogMaker = DcbEventLog_Builder.Make(Bus)
|
|
70
|
-
let eventLog = ItemEventLogMaker.make(
|
|
70
|
+
let eventLog = ItemEventLogMaker.make(
|
|
71
|
+
~name="ItemEventLog",
|
|
72
|
+
~partitionTag=Reventless.DcbTag.Simple({key: "id"}),
|
|
73
|
+
)
|
|
71
74
|
|
|
72
75
|
// ─────────────────────────────────────────────────────────────
|
|
73
76
|
// Build StateViewSlice
|
|
@@ -107,11 +110,10 @@ let loadState = async id => {
|
|
|
107
110
|
switch Bus.getQueryDb("ItemsView") {
|
|
108
111
|
| None => []
|
|
109
112
|
| Some(ops) =>
|
|
110
|
-
let states =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
->Effect.runPromise
|
|
113
|
+
let states = await ops.loadStream(id)
|
|
114
|
+
->Stream.runCollect
|
|
115
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
116
|
+
->Effect.runPromise
|
|
115
117
|
states->Array.map(json => json->Reventless.Util_Sury.fromJson(ItemsViewSpec.stateSchema))
|
|
116
118
|
}
|
|
117
119
|
}
|
|
@@ -11,8 +11,17 @@ open Reventless.Projection
|
|
|
11
11
|
module ScoreEventLog = {
|
|
12
12
|
@schema
|
|
13
13
|
type event =
|
|
14
|
-
| ScoreRecorded({
|
|
15
|
-
|
|
14
|
+
| ScoreRecorded({
|
|
15
|
+
id: @s.matches(Reventless.DcbTag.string) string,
|
|
16
|
+
category: string,
|
|
17
|
+
date: string,
|
|
18
|
+
score: int,
|
|
19
|
+
})
|
|
20
|
+
| ScoreRemoved({
|
|
21
|
+
id: @s.matches(Reventless.DcbTag.string) string,
|
|
22
|
+
category: string,
|
|
23
|
+
date: string,
|
|
24
|
+
})
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -46,10 +55,8 @@ module ScoresViewProjection = {
|
|
|
46
55
|
|
|
47
56
|
let project = ({event}: Reventless.StateViewSlice.consumed<consumedEvent>) =>
|
|
48
57
|
switch event {
|
|
49
|
-
| ScoreRecorded({id, category, date, score}) =>
|
|
50
|
-
|
|
51
|
-
| ScoreRemoved({id, category: _, date: _}) =>
|
|
52
|
-
[Delete(id)]
|
|
58
|
+
| ScoreRecorded({id, category, date, score}) => [Set(id, {id, category, date, score})]
|
|
59
|
+
| ScoreRemoved({id, category: _, date: _}) => [Delete(id)]
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
62
|
|
|
@@ -107,11 +114,10 @@ let loadScores = async id => {
|
|
|
107
114
|
switch Bus.getQueryDb("ScoresView") {
|
|
108
115
|
| None => []
|
|
109
116
|
| Some(ops) =>
|
|
110
|
-
let states =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
->Effect.runPromise
|
|
117
|
+
let states = await ops.loadStream(id)
|
|
118
|
+
->Stream.runCollect
|
|
119
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
120
|
+
->Effect.runPromise
|
|
115
121
|
states->Array.map(json => json->Reventless.Util_Sury.fromJson(ScoresViewSpec.stateSchema))
|
|
116
122
|
}
|
|
117
123
|
}
|
|
@@ -29,7 +29,12 @@ describe("StateViewSlice sub-ID E2E", () => {
|
|
|
29
29
|
ScoreEventLog.ScoreRecorded({id: "player-2", category: "math", date: "2026-01", score: 80}),
|
|
30
30
|
)
|
|
31
31
|
let _ = await appendEvent(
|
|
32
|
-
ScoreEventLog.ScoreRecorded({
|
|
32
|
+
ScoreEventLog.ScoreRecorded({
|
|
33
|
+
id: "player-2",
|
|
34
|
+
category: "science",
|
|
35
|
+
date: "2026-01",
|
|
36
|
+
score: 95,
|
|
37
|
+
}),
|
|
33
38
|
)
|
|
34
39
|
let _ = await appendEvent(
|
|
35
40
|
ScoreEventLog.ScoreRecorded({id: "player-2", category: "math", date: "2026-02", score: 85}),
|
|
@@ -40,7 +45,12 @@ describe("StateViewSlice sub-ID E2E", () => {
|
|
|
40
45
|
|
|
41
46
|
testPromise("loadStream returns sub-key items in alphabetical order", async () => {
|
|
42
47
|
let _ = await appendEvent(
|
|
43
|
-
ScoreEventLog.ScoreRecorded({
|
|
48
|
+
ScoreEventLog.ScoreRecorded({
|
|
49
|
+
id: "player-3",
|
|
50
|
+
category: "science",
|
|
51
|
+
date: "2026-03",
|
|
52
|
+
score: 70,
|
|
53
|
+
}),
|
|
44
54
|
)
|
|
45
55
|
let _ = await appendEvent(
|
|
46
56
|
ScoreEventLog.ScoreRecorded({id: "player-3", category: "math", date: "2026-01", score: 88}),
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Fixtures for Task integration tests.
|
|
2
2
|
// Tests Task_Builder.Make wiring with in-memory adapters.
|
|
3
3
|
|
|
4
|
-
|
|
5
4
|
// Activate Pulumi mock mode (must be called before any Component.make)
|
|
6
5
|
let _ = TestRunner.setup()
|
|
7
6
|
|
|
@@ -52,8 +51,7 @@ module OneBucketSpec = {
|
|
|
52
51
|
bucketName: "Reports",
|
|
53
52
|
bucketMode: Reventless.Task.Read,
|
|
54
53
|
callback: async (~eventName, ~key) => {
|
|
55
|
-
capturedEvents :=
|
|
56
|
-
capturedEvents.contents->Array.concat([(eventName, key)])
|
|
54
|
+
capturedEvents := capturedEvents.contents->Array.concat([(eventName, key)])
|
|
57
55
|
[]
|
|
58
56
|
},
|
|
59
57
|
},
|
|
@@ -67,8 +65,10 @@ module OneBucketSpec = {
|
|
|
67
65
|
|
|
68
66
|
let mockPublishToAggregates: dict<ReventlessInfra.CommandTopic.publishJsons> = Dict.make()
|
|
69
67
|
|
|
70
|
-
let mockQueryBucketName: ReventlessInfra.Task.queryBucketName = (
|
|
71
|
-
|
|
68
|
+
let mockQueryBucketName: ReventlessInfra.Task.queryBucketName = (
|
|
69
|
+
~taskName as _,
|
|
70
|
+
~bucketName as _=?,
|
|
71
|
+
) => "in-memory-bucket"
|
|
72
72
|
|
|
73
73
|
// ─────────────────────────────────────────────────────────────
|
|
74
74
|
// Pre-built task makers
|