@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.
Files changed (170) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +13 -13
  3. package/src/BakedManifest.res +5 -6
  4. package/src/EmitCapabilities.res +3 -1
  5. package/src/LocalPlatformRegistry.res +3 -1
  6. package/src/LocalPlatformStart.res +7 -6
  7. package/src/LocalSeedTarget.res +3 -1
  8. package/src/Platform.res +291 -215
  9. package/src/ShellConfig.res +10 -17
  10. package/src/UiHints.res +8 -14
  11. package/src/UiSlots.res +10 -17
  12. package/src/adapter/Api/LocalEvents_Server.res +5 -3
  13. package/src/adapter/Api/LocalGraphQL_Adapter.res +4 -4
  14. package/src/adapter/Auth/LocalAuth.res +11 -15
  15. package/src/adapter/Auth/UserStore.res +5 -7
  16. package/src/adapter/Backend.res +1 -2
  17. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +42 -26
  18. package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +13 -3
  19. package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +15 -17
  20. package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +18 -22
  21. package/src/adapter/Counter/LocalCounterHandler.res +1 -1
  22. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +33 -22
  23. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +57 -57
  24. package/src/adapter/DomainGraphQL_Server.res +35 -34
  25. package/src/adapter/DomainMCP_Server.res +45 -41
  26. package/src/adapter/EventCollector/LocalEventCollectorChannel.res +47 -36
  27. package/src/adapter/EventHistory/EventHistoryResolvers_GraphQL.res +15 -12
  28. package/src/adapter/EventLog/EventLogStorage_InMemory.res +9 -4
  29. package/src/adapter/EventLog/EventLogStorage_Sqlite.res +36 -24
  30. package/src/adapter/EventTopic/LocalEventTopicPublisher.res +9 -3
  31. package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +9 -14
  32. package/src/adapter/LocalBus.res +12 -12
  33. package/src/adapter/LocalCapabilities.res.mjs +1 -1
  34. package/src/adapter/LocalGeocodeResolvers.res +1 -4
  35. package/src/adapter/LocalSideEffectHandler.res +1 -1
  36. package/src/adapter/LocalStateChangeDescriptor.res +2 -1
  37. package/src/adapter/MCP_ServerInstance.res +51 -39
  38. package/src/adapter/ObjectStore/LocalObjectStore.res +5 -2
  39. package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -2
  40. package/src/adapter/PlatformGraphQL_Server.res +9 -5
  41. package/src/adapter/ProjectionCheckpoint.res +8 -1
  42. package/src/adapter/ProjectionPending.res +1 -3
  43. package/src/adapter/QueryDb/LocalQueryDbStorage.res +2 -1
  44. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +367 -269
  45. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +5 -3
  46. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +63 -40
  47. package/src/adapter/QueryDb/SortKey_Filter.res +14 -6
  48. package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +12 -10
  49. package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +8 -2
  50. package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +1 -4
  51. package/src/adapter/Scheduler/LocalScheduledPublisher.res +12 -12
  52. package/src/adapter/SqliteDriver.res +16 -4
  53. package/src/adapter/Task/TaskBucket_Sqlite.res +5 -6
  54. package/src/components/Aggregate_Builder.res +6 -1
  55. package/src/components/Counter_Builder.res +1 -5
  56. package/src/components/DcbEventLog_Builder.res +5 -3
  57. package/src/components/ExtensionPoint_Builder.res +13 -15
  58. package/src/components/InboundTranslationSlice_Builder.res +3 -2
  59. package/src/components/OutboundTranslationSlice_Builder.res +3 -2
  60. package/src/components/ReadModel_Builder.res +18 -20
  61. package/src/components/StateViewSlice_Builder.res +3 -2
  62. package/src/components/Task_Builder.res +12 -13
  63. package/src/reset/LocalSeedReset.res +2 -5
  64. package/src/test/Mocks/MockDcbEventLogStorage.res +5 -1
  65. package/src/test/Mocks/MockEventLogStorage.res +8 -3
  66. package/src/test/Mocks/MockQueryDbStorage.res +3 -2
  67. package/src/test/TestRunner.res +1 -1
  68. package/tests/BakedManifestFilesTest.res +7 -10
  69. package/tests/LocalEventTapTest.res +26 -21
  70. package/tests/LocalPlatformStartTest.res +33 -28
  71. package/tests/PluginEventDecodeTest.res +3 -3
  72. package/tests/ShellConfigTest.res +30 -21
  73. package/tests/SplitApiFixtures.res +34 -10
  74. package/tests/SplitApiTest.res +92 -62
  75. package/tests/adapter/BackendParityTest.res +180 -130
  76. package/tests/adapter/CollectNEventsTest.res +6 -4
  77. package/tests/adapter/CommandAuthorizationTest.res +47 -41
  78. package/tests/adapter/CommandTopicChannelTest.res +163 -139
  79. package/tests/adapter/CounterHandlerTest.res +95 -77
  80. package/tests/adapter/DcbEventLogStorageSqliteTest.res +171 -168
  81. package/tests/adapter/DcbEventLogStorageTest.res +264 -207
  82. package/tests/adapter/DomainGraphQL_ServerTest.res +53 -43
  83. package/tests/adapter/EventCollectorChannelTest.res +153 -124
  84. package/tests/adapter/EventHistoryResolverTest.res +46 -18
  85. package/tests/adapter/EventLogSnapshotParityTest.res +57 -45
  86. package/tests/adapter/EventLogStorageSqliteTest.res +9 -10
  87. package/tests/adapter/EventTapTest.res +37 -20
  88. package/tests/adapter/EventTopicPublisherTest.res +74 -50
  89. package/tests/adapter/GraphQLSubscriptionBridgeTest.res +4 -3
  90. package/tests/adapter/GraphQL_SchemaInspectorTest.res +666 -568
  91. package/tests/adapter/GraphQL_SubscriptionResolversTest.res +105 -96
  92. package/tests/adapter/HeartbeatRunnerTest.res +99 -81
  93. package/tests/adapter/InboundTranslationMutationTest.res +4 -1
  94. package/tests/adapter/LocalAuthHttpTest.res +79 -85
  95. package/tests/adapter/LocalAuthLoginTest.res +42 -50
  96. package/tests/adapter/LocalAuthTest.res +8 -16
  97. package/tests/adapter/LocalAuthUserStoreTest.res +2 -7
  98. package/tests/adapter/LocalBusBoundedTest.res +187 -117
  99. package/tests/adapter/LocalBusPubSubTest.res +118 -76
  100. package/tests/adapter/LocalDispatchContextTest.res +1 -2
  101. package/tests/adapter/LocalEvents_ServerTest.res +242 -187
  102. package/tests/adapter/PgProjectionCatchupTest.res +44 -10
  103. package/tests/adapter/ProjectionCheckpointTest.res +261 -206
  104. package/tests/adapter/QueryDbGsiTtlTest.res +123 -83
  105. package/tests/adapter/QueryDbListPushdownParityTest.res +107 -67
  106. package/tests/adapter/QueryDbListResolverTest.res +225 -208
  107. package/tests/adapter/QueryDbResolvedFieldsTest.res +17 -14
  108. package/tests/adapter/QueryDbStorageSqliteTest.res +33 -49
  109. package/tests/adapter/QueryDbStorageTest.res +163 -117
  110. package/tests/adapter/QueryDbUnionRoundTripTest.res +8 -10
  111. package/tests/adapter/QueryEngineTest.res +179 -126
  112. package/tests/adapter/ScheduledPublisherTest.res +171 -135
  113. package/tests/adapter/ServedBucketHttpTest.res +65 -59
  114. package/tests/adapter/SortKey_FilterTest.res +19 -3
  115. package/tests/adapter/SqliteDriverTest.res +13 -10
  116. package/tests/adapter/TaskBucketTest.res +94 -80
  117. package/tests/components/aggregate/AggregateTest.res +31 -18
  118. package/tests/components/automationslice/AutomationSliceCallbackTest.res +259 -222
  119. package/tests/components/automationslice/AutomationSliceFixtures.res +4 -2
  120. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +38 -38
  121. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +12 -14
  122. package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +8 -32
  123. package/tests/components/commandgenerator/CommandGeneratorFixtures.res +7 -5
  124. package/tests/components/commandgenerator/CommandGeneratorTest.res +40 -31
  125. package/tests/components/commandtopic/CommandTopicFixtures.res +3 -4
  126. package/tests/components/commandtopic/CommandTopicStreamFixtures.res +7 -5
  127. package/tests/components/commandtopic/CommandTopicTest.res +24 -15
  128. package/tests/components/counter/CounterFixtures.res +6 -8
  129. package/tests/components/dcb/DcbCrossPartitionFixtures.res +8 -2
  130. package/tests/components/dcb/DcbCrossPartitionReadTest.res +20 -13
  131. package/tests/components/dcb/DcbEventLogAppendStreamTest.res +12 -8
  132. package/tests/components/dcb/DcbEventLogStreamTest.res +88 -61
  133. package/tests/components/dcb/DcbFixtures.res +4 -1
  134. package/tests/components/dcb/DcbTest.res +9 -6
  135. package/tests/components/eventcollector/EventCollectorFixtures.res +0 -1
  136. package/tests/components/eventcollector/EventCollectorTest.res +6 -3
  137. package/tests/components/eventlog/EventLogAppendStreamTest.res +88 -68
  138. package/tests/components/eventlog/EventLogConflictTest.res +8 -4
  139. package/tests/components/eventlog/EventLogFixtures.res +3 -3
  140. package/tests/components/eventlog/EventLogProvisioningSeamTest.res +26 -13
  141. package/tests/components/eventlog/EventLogStreamTest.res +119 -56
  142. package/tests/components/eventlog/EventLogTest.res +20 -4
  143. package/tests/components/eventtopic/EventTopicFixtures.res +3 -3
  144. package/tests/components/eventtopic/EventTopicStreamFixtures.res +1 -2
  145. package/tests/components/eventtopic/EventTopicStreamTest.res +7 -1
  146. package/tests/components/extensionpoint/ExtensionPointFixtures.res +14 -9
  147. package/tests/components/extensionpoint/ExtensionPointTest.res +3 -2
  148. package/tests/components/heartbeat/HeartbeatFixtures.res +5 -6
  149. package/tests/components/heartbeat/HeartbeatTest.res +56 -47
  150. package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +271 -249
  151. package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +3 -2
  152. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +361 -276
  153. package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +8 -10
  154. package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +37 -43
  155. package/tests/components/querydb/QueryDbFixtures.res +1 -5
  156. package/tests/components/querydb/QueryDbTest.res +2 -4
  157. package/tests/components/readmodel/DcbReadModelE2EFixtures.res +15 -18
  158. package/tests/components/readmodel/MixedSourceReadModelFixtures.res +5 -7
  159. package/tests/components/readmodel/ReadModelFixtures.res +8 -8
  160. package/tests/components/scheduler/SchedulerTest.res +76 -58
  161. package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +4 -4
  162. package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res +5 -7
  163. package/tests/components/stateviewslice/StateViewSliceFixtures.res +8 -6
  164. package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +17 -11
  165. package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +12 -2
  166. package/tests/components/task/TaskFixtures.res +5 -5
  167. package/tests/components/task/TaskTest.res +95 -80
  168. package/tests/plugin/PluginsProjection_GWT.res +3 -1
  169. package/tests/plugin/UiFragments_GWT.res +6 -1
  170. package/tests/reset/LocalSeedResetTest.res +1 -0
@@ -10,7 +10,6 @@
10
10
 
11
11
  open JestGlobals
12
12
 
13
-
14
13
  let _ = TestRunner.setup()
15
14
  let opts: Pulumi.CustomResourceOptions.t = {}
16
15
 
@@ -80,21 +79,22 @@ describe("Backend parity (Memory vs Sqlite)", () => {
80
79
  let scenario = async () => {
81
80
  module TestBus = LocalBus.Make()
82
81
  module Storage = LocalQueryDbStorage.Make(TestBus)
83
- let s = Storage.make(~name="parity-qdb", ~indexes=[], ~api=(), ~apiRole=(), ~owner=None, ~opts)
82
+ let s = Storage.make(
83
+ ~name="parity-qdb",
84
+ ~indexes=[],
85
+ ~api=(),
86
+ ~apiRole=(),
87
+ ~owner=None,
88
+ ~opts,
89
+ )
84
90
  let ops = await s.operations->TestRunner.resolve
85
91
 
86
- let _ = await ops.save(
87
- "k",
88
- JSON.Encode.string("val"),
89
- ReventlessCore.QueryDb.Any,
90
- None,
91
- )
92
+ let _ = await ops.save("k", JSON.Encode.string("val"), ReventlessCore.QueryDb.Any, None)
92
93
 
93
- let items =
94
- await ops.loadStream("k")
95
- ->Stream.runCollect
96
- ->Effect.catchAll(_ => Effect.succeed([]))
97
- ->Effect.runPromise
94
+ let items = await ops.loadStream("k")
95
+ ->Stream.runCollect
96
+ ->Effect.catchAll(_ => Effect.succeed([]))
97
+ ->Effect.runPromise
98
98
  expect(items->Array.length)->toBe(1)
99
99
  expect(items->Array.getUnsafe(0))->toEqual(JSON.Encode.string("val"))
100
100
  }
@@ -103,46 +103,62 @@ describe("Backend parity (Memory vs Sqlite)", () => {
103
103
  await runUnderSqlite(scenario)
104
104
  })
105
105
 
106
- testPromise("QueryDb: count returns a running total and loadStream reflects it under both", async () => {
107
- let scenario = async () => {
108
- module TestBus = LocalBus.Make()
109
- module Storage = LocalQueryDbStorage.Make(TestBus)
110
- let s = Storage.make(~name="parity-count", ~indexes=[], ~api=(), ~apiRole=(), ~owner=None, ~opts)
111
- let ops = await s.operations->TestRunner.resolve
106
+ testPromise(
107
+ "QueryDb: count returns a running total and loadStream reflects it under both",
108
+ async () => {
109
+ let scenario = async () => {
110
+ module TestBus = LocalBus.Make()
111
+ module Storage = LocalQueryDbStorage.Make(TestBus)
112
+ let s = Storage.make(
113
+ ~name="parity-count",
114
+ ~indexes=[],
115
+ ~api=(),
116
+ ~apiRole=(),
117
+ ~owner=None,
118
+ ~opts,
119
+ )
120
+ let ops = await s.operations->TestRunner.resolve
112
121
 
113
- // First increment creates the counter item and returns the new total.
114
- let r1 = await ops.count("prod-1", "orderCount", 3)
115
- expect(r1)->toEqual(Ok(3))
116
- // Second increment accumulates (not just echoes the increment).
117
- let r2 = await ops.count("prod-1", "orderCount", 2)
118
- expect(r2)->toEqual(Ok(5))
122
+ // First increment creates the counter item and returns the new total.
123
+ let r1 = await ops.count("prod-1", "orderCount", 3)
124
+ expect(r1)->toEqual(Ok(3))
125
+ // Second increment accumulates (not just echoes the increment).
126
+ let r2 = await ops.count("prod-1", "orderCount", 2)
127
+ expect(r2)->toEqual(Ok(5))
119
128
 
120
- // loadStream must see the persisted counter — count is not a side channel.
121
- let items =
122
- await ops.loadStream("prod-1")
129
+ // loadStream must see the persisted counter — count is not a side channel.
130
+ let items = await ops.loadStream("prod-1")
123
131
  ->Stream.runCollect
124
132
  ->Effect.catchAll(_ => Effect.succeed([]))
125
133
  ->Effect.runPromise
126
- expect(items->Array.length)->toBe(1)
127
- let field =
128
- items
129
- ->Array.getUnsafe(0)
130
- ->JSON.Decode.object
131
- ->Option.flatMap(o => o->Dict.get("orderCount"))
132
- ->Option.flatMap(JSON.Decode.float)
133
- ->Option.mapOr(0, Float.toInt)
134
- expect(field)->toBe(5)
135
- }
134
+ expect(items->Array.length)->toBe(1)
135
+ let field =
136
+ items
137
+ ->Array.getUnsafe(0)
138
+ ->JSON.Decode.object
139
+ ->Option.flatMap(o => o->Dict.get("orderCount"))
140
+ ->Option.flatMap(JSON.Decode.float)
141
+ ->Option.mapOr(0, Float.toInt)
142
+ expect(field)->toBe(5)
143
+ }
136
144
 
137
- await runUnderMemory(scenario)
138
- await runUnderSqlite(scenario)
139
- })
145
+ await runUnderMemory(scenario)
146
+ await runUnderSqlite(scenario)
147
+ },
148
+ )
140
149
 
141
150
  testPromise("QueryDb: an expired-TTL item is filtered from loadStream under both", async () => {
142
151
  let scenario = async () => {
143
152
  module TestBus = LocalBus.Make()
144
153
  module Storage = LocalQueryDbStorage.Make(TestBus)
145
- let s = Storage.make(~name="parity-ttl", ~indexes=[], ~api=(), ~apiRole=(), ~owner=None, ~opts)
154
+ let s = Storage.make(
155
+ ~name="parity-ttl",
156
+ ~indexes=[],
157
+ ~api=(),
158
+ ~apiRole=(),
159
+ ~owner=None,
160
+ ~opts,
161
+ )
146
162
  let ops = await s.operations->TestRunner.resolve
147
163
 
148
164
  let item = k => JSON.Encode.object(Dict.fromArray([("id", JSON.Encode.string(k))]))
@@ -171,16 +187,25 @@ describe("Backend parity (Memory vs Sqlite)", () => {
171
187
  let scenario = async () => {
172
188
  module TestBus = LocalBus.Make()
173
189
  module Storage = LocalQueryDbStorage.Make(TestBus)
174
- let s = Storage.make(~name="parity-kind", ~indexes=[], ~api=(), ~apiRole=(), ~owner=None, ~opts)
190
+ let s = Storage.make(
191
+ ~name="parity-kind",
192
+ ~indexes=[],
193
+ ~api=(),
194
+ ~apiRole=(),
195
+ ~owner=None,
196
+ ~opts,
197
+ )
175
198
  let ops = await s.operations->TestRunner.resolve
176
199
 
177
200
  let kinds = []
178
- TestBus.subscribeToStateChanges("parity-kind", descriptor =>
179
- descriptor
180
- ->JSON.Decode.object
181
- ->Option.flatMap(o => o->Dict.get("changeKind"))
182
- ->Option.flatMap(JSON.Decode.string)
183
- ->Option.forEach(k => kinds->Array.push(k))
201
+ TestBus.subscribeToStateChanges(
202
+ "parity-kind",
203
+ descriptor =>
204
+ descriptor
205
+ ->JSON.Decode.object
206
+ ->Option.flatMap(o => o->Dict.get("changeKind"))
207
+ ->Option.flatMap(JSON.Decode.string)
208
+ ->Option.forEach(k => kinds->Array.push(k)),
184
209
  )
185
210
 
186
211
  let item = (k, v) =>
@@ -205,101 +230,126 @@ describe("Backend parity (Memory vs Sqlite)", () => {
205
230
  await runUnderSqlite(scenario)
206
231
  })
207
232
 
208
- testPromise("QueryDb: a descriptor carries the saved row and a rising seq under both", async () => {
209
- let scenario = async () => {
210
- module TestBus = LocalBus.Make()
211
- module Storage = LocalQueryDbStorage.Make(TestBus)
212
- let s = Storage.make(
213
- ~name="parity-payload",
214
- ~indexes=[],
215
- ~api=(),
216
- ~apiRole=(),
217
- ~owner=None,
218
- ~opts,
219
- )
220
- let ops = await s.operations->TestRunner.resolve
221
-
222
- let descriptors = []
223
- TestBus.subscribeToStateChanges("parity-payload", d => descriptors->Array.push(d))
224
-
225
- let field = (d, key) => d->JSON.Decode.object->Option.flatMap(o => o->Dict.get(key))
226
- let item = (k, v) =>
227
- JSON.Encode.object(
228
- Dict.fromArray([("id", JSON.Encode.string(k)), ("v", JSON.Encode.string(v))]),
233
+ testPromise(
234
+ "QueryDb: a descriptor carries the saved row and a rising seq under both",
235
+ async () => {
236
+ let scenario = async () => {
237
+ module TestBus = LocalBus.Make()
238
+ module Storage = LocalQueryDbStorage.Make(TestBus)
239
+ let s = Storage.make(
240
+ ~name="parity-payload",
241
+ ~indexes=[],
242
+ ~api=(),
243
+ ~apiRole=(),
244
+ ~owner=None,
245
+ ~opts,
229
246
  )
230
- let stateAt = i => descriptors->Array.get(i)->Option.flatMap(d => field(d, "state"))
231
-
232
- let _ = await ops.save("p1", item("p1", "one"), ReventlessCore.QueryDb.Any, None)
233
- let _ = await ops.save("p1", item("p1", "two"), ReventlessCore.QueryDb.Any, None)
234
- let _ = await ops.delete("p1", None)
247
+ let ops = await s.operations->TestRunner.resolve
248
+
249
+ let descriptors = []
250
+ TestBus.subscribeToStateChanges("parity-payload", d => descriptors->Array.push(d))
251
+
252
+ let field = (d, key) => d->JSON.Decode.object->Option.flatMap(o => o->Dict.get(key))
253
+ let item = (k, v) =>
254
+ JSON.Encode.object(
255
+ Dict.fromArray([("id", JSON.Encode.string(k)), ("v", JSON.Encode.string(v))]),
256
+ )
257
+ let stateAt = i => descriptors->Array.get(i)->Option.flatMap(d => field(d, "state"))
258
+
259
+ let _ = await ops.save("p1", item("p1", "one"), ReventlessCore.QueryDb.Any, None)
260
+ let _ = await ops.save("p1", item("p1", "two"), ReventlessCore.QueryDb.Any, None)
261
+ let _ = await ops.delete("p1", None)
262
+
263
+ // The point of the payload: a subscriber can apply the row it was handed
264
+ // instead of spending a round-trip to fetch what the platform already had.
265
+ expect(stateAt(0))->toEqual(Some(item("p1", "one")))
266
+ expect(stateAt(1))->toEqual(Some(item("p1", "two")))
267
+ // A delete has no new row to carry.
268
+ expect(stateAt(2))->toEqual(None)
269
+
270
+ // seq only has to rise — it is monotonic, not consecutive, so a client can
271
+ // reject a stale payload but cannot count gaps.
272
+ let seqs =
273
+ descriptors->Array.filterMap(
274
+ d =>
275
+ field(d, "seq")->Option.flatMap(JSON.Decode.string)->Option.flatMap(Float.fromString),
276
+ )
277
+ expect(seqs->Array.length)->toBe(3)
278
+ let rising =
279
+ seqs->Array.everyWithIndex((v, i) => i == 0 || v > seqs->Array.getUnsafe(i - 1))
280
+ expect(rising)->toBe(true)
281
+ }
235
282
 
236
- // The point of the payload: a subscriber can apply the row it was handed
237
- // instead of spending a round-trip to fetch what the platform already had.
238
- expect(stateAt(0))->toEqual(Some(item("p1", "one")))
239
- expect(stateAt(1))->toEqual(Some(item("p1", "two")))
240
- // A delete has no new row to carry.
241
- expect(stateAt(2))->toEqual(None)
242
-
243
- // seq only has to rise — it is monotonic, not consecutive, so a client can
244
- // reject a stale payload but cannot count gaps.
245
- let seqs =
246
- descriptors->Array.filterMap(d =>
247
- field(d, "seq")->Option.flatMap(JSON.Decode.string)->Option.flatMap(Float.fromString)
283
+ await runUnderMemory(scenario)
284
+ await runUnderSqlite(scenario)
285
+ },
286
+ )
287
+
288
+ testPromise(
289
+ "QueryDb: saveBatch reports Added then Updated for a repeated key under both",
290
+ async () => {
291
+ let scenario = async () => {
292
+ module TestBus = LocalBus.Make()
293
+ module Storage = LocalQueryDbStorage.Make(TestBus)
294
+ let s = Storage.make(
295
+ ~name="parity-batch",
296
+ ~indexes=[],
297
+ ~api=(),
298
+ ~apiRole=(),
299
+ ~owner=None,
300
+ ~opts,
301
+ )
302
+ let ops = await s.operations->TestRunner.resolve
303
+
304
+ let kinds = []
305
+ TestBus.subscribeToStateChanges(
306
+ "parity-batch",
307
+ descriptor =>
308
+ descriptor
309
+ ->JSON.Decode.object
310
+ ->Option.flatMap(o => o->Dict.get("changeKind"))
311
+ ->Option.flatMap(JSON.Decode.string)
312
+ ->Option.forEach(k => kinds->Array.push(k)),
248
313
  )
249
- expect(seqs->Array.length)->toBe(3)
250
- let rising =
251
- seqs->Array.everyWithIndex((v, i) => i == 0 || v > seqs->Array.getUnsafe(i - 1))
252
- expect(rising)->toBe(true)
253
- }
254
-
255
- await runUnderMemory(scenario)
256
- await runUnderSqlite(scenario)
257
- })
258
-
259
- testPromise("QueryDb: saveBatch reports Added then Updated for a repeated key under both", async () => {
260
- let scenario = async () => {
261
- module TestBus = LocalBus.Make()
262
- module Storage = LocalQueryDbStorage.Make(TestBus)
263
- let s = Storage.make(~name="parity-batch", ~indexes=[], ~api=(), ~apiRole=(), ~owner=None, ~opts)
264
- let ops = await s.operations->TestRunner.resolve
265
314
 
266
- let kinds = []
267
- TestBus.subscribeToStateChanges("parity-batch", descriptor =>
268
- descriptor
269
- ->JSON.Decode.object
270
- ->Option.flatMap(o => o->Dict.get("changeKind"))
271
- ->Option.flatMap(JSON.Decode.string)
272
- ->Option.forEach(k => kinds->Array.push(k))
273
- )
315
+ let item = k => JSON.Encode.object(Dict.fromArray([("id", JSON.Encode.string(k))]))
316
+ let _ = await ops.saveBatch([
317
+ ("a", item("a"), None),
318
+ ("b", item("b"), None),
319
+ ("a", item("a"), None),
320
+ ])
274
321
 
275
- let item = k => JSON.Encode.object(Dict.fromArray([("id", JSON.Encode.string(k))]))
276
- let _ = await ops.saveBatch([
277
- ("a", item("a"), None),
278
- ("b", item("b"), None),
279
- ("a", item("a"), None),
280
- ])
322
+ expect(kinds)->toEqual(["Added", "Added", "Updated"])
323
+ }
281
324
 
282
- expect(kinds)->toEqual(["Added", "Added", "Updated"])
283
- }
284
-
285
- await runUnderMemory(scenario)
286
- await runUnderSqlite(scenario)
287
- })
325
+ await runUnderMemory(scenario)
326
+ await runUnderSqlite(scenario)
327
+ },
328
+ )
288
329
 
289
330
  testPromise("QueryDb: a counter's first increment is Added under both", async () => {
290
331
  let scenario = async () => {
291
332
  module TestBus = LocalBus.Make()
292
333
  module Storage = LocalQueryDbStorage.Make(TestBus)
293
- let s = Storage.make(~name="parity-ckind", ~indexes=[], ~api=(), ~apiRole=(), ~owner=None, ~opts)
334
+ let s = Storage.make(
335
+ ~name="parity-ckind",
336
+ ~indexes=[],
337
+ ~api=(),
338
+ ~apiRole=(),
339
+ ~owner=None,
340
+ ~opts,
341
+ )
294
342
  let ops = await s.operations->TestRunner.resolve
295
343
 
296
344
  let kinds = []
297
- TestBus.subscribeToStateChanges("parity-ckind", descriptor =>
298
- descriptor
299
- ->JSON.Decode.object
300
- ->Option.flatMap(o => o->Dict.get("changeKind"))
301
- ->Option.flatMap(JSON.Decode.string)
302
- ->Option.forEach(k => kinds->Array.push(k))
345
+ TestBus.subscribeToStateChanges(
346
+ "parity-ckind",
347
+ descriptor =>
348
+ descriptor
349
+ ->JSON.Decode.object
350
+ ->Option.flatMap(o => o->Dict.get("changeKind"))
351
+ ->Option.flatMap(JSON.Decode.string)
352
+ ->Option.forEach(k => kinds->Array.push(k)),
303
353
  )
304
354
 
305
355
  let _ = await ops.count("prod-1", "orderCount", 1)
@@ -50,10 +50,12 @@ describe("TestRunner.collectNEvents", () => {
50
50
  let collecting = TestRunner.collectNEvents(Bus.subscribeToEvents, "T", 3)
51
51
  let json = JSON.parseOrThrow("{}")
52
52
  let resolved = ref(false)
53
- let _ = collecting->Promise.then(events => {
54
- resolved := true
55
- Promise.resolve(events)
56
- })
53
+ let _ = collecting->Promise.then(
54
+ events => {
55
+ resolved := true
56
+ Promise.resolve(events)
57
+ },
58
+ )
57
59
  await Bus.publishEvent("T", "svc", meta, json)
58
60
  await Bus.publishEvent("T", "svc", meta, json)
59
61
  // Only 2 events so far — promise should not yet be resolved.
@@ -49,14 +49,11 @@ let userIdentity: Reventless.Identity.t = {
49
49
  }
50
50
 
51
51
  let ctxFor = (identity: Reventless.Identity.t): JSON.t =>
52
- JSON.Encode.object(
53
- Dict.fromArray([("identity", (identity: Reventless.Identity.t)->Obj.magic)]),
54
- )
52
+ JSON.Encode.object(Dict.fromArray([("identity", (identity: Reventless.Identity.t)->Obj.magic)]))
55
53
 
56
- let anonymousCtx: JSON.t =
57
- JSON.Encode.object(
58
- Dict.fromArray([("identity", (Reventless.Identity.anonymous: Reventless.Identity.t)->Obj.magic)]),
59
- )
54
+ let anonymousCtx: JSON.t = JSON.Encode.object(
55
+ Dict.fromArray([("identity", (Reventless.Identity.anonymous: Reventless.Identity.t)->Obj.magic)]),
56
+ )
60
57
 
61
58
  let getTypename = (response: JSON.t): string =>
62
59
  response
@@ -125,16 +122,22 @@ describe("CommandGeneratorResolvers_GraphQL — per-constructor authorization",
125
122
 
126
123
  testPromise("admin invoking payload-less Archive succeeds", async () => {
127
124
  let (_, _, archiveResolver, calls) = buildFixture(~namespace="Cat1")
128
- let response =
129
- await archiveResolver(JSON.Encode.null, JSON.Encode.object(Dict.make()), ctxFor(adminIdentity))
125
+ let response = await archiveResolver(
126
+ JSON.Encode.null,
127
+ JSON.Encode.object(Dict.make()),
128
+ ctxFor(adminIdentity),
129
+ )
130
130
  expect(getTypename(response))->toEqual("CommandAccepted")
131
131
  expect(calls.contents)->toEqual(["Archive"])
132
132
  })
133
133
 
134
134
  testPromise("regular user invoking Archive is rejected with Forbidden", async () => {
135
135
  let (_, _, archiveResolver, calls) = buildFixture(~namespace="Cat2")
136
- let response =
137
- await archiveResolver(JSON.Encode.null, JSON.Encode.object(Dict.make()), ctxFor(userIdentity))
136
+ let response = await archiveResolver(
137
+ JSON.Encode.null,
138
+ JSON.Encode.object(Dict.make()),
139
+ ctxFor(userIdentity),
140
+ )
138
141
  expect(getTypename(response))->toEqual("CommandRejected")
139
142
  expect(getErrorCode(response))->toEqual("Forbidden")
140
143
  expect(calls.contents)->toEqual([])
@@ -142,8 +145,11 @@ describe("CommandGeneratorResolvers_GraphQL — per-constructor authorization",
142
145
 
143
146
  testPromise("anonymous invoking Archive is rejected with Forbidden", async () => {
144
147
  let (_, _, archiveResolver, calls) = buildFixture(~namespace="Cat3")
145
- let response =
146
- await archiveResolver(JSON.Encode.null, JSON.Encode.object(Dict.make()), anonymousCtx)
148
+ let response = await archiveResolver(
149
+ JSON.Encode.null,
150
+ JSON.Encode.object(Dict.make()),
151
+ anonymousCtx,
152
+ )
147
153
  expect(getTypename(response))->toEqual("CommandRejected")
148
154
  expect(getErrorCode(response))->toEqual("Forbidden")
149
155
  expect(calls.contents)->toEqual([])
@@ -151,24 +157,22 @@ describe("CommandGeneratorResolvers_GraphQL — per-constructor authorization",
151
157
 
152
158
  testPromise("regular user invoking Add (default AllowAuthenticated) succeeds", async () => {
153
159
  let (addResolver, _, _, calls) = buildFixture(~namespace="Cat4")
154
- let response =
155
- await addResolver(
156
- JSON.Encode.null,
157
- JSON.Encode.object(Dict.fromArray([("name", JSON.Encode.string("Books"))])),
158
- ctxFor(userIdentity),
159
- )
160
+ let response = await addResolver(
161
+ JSON.Encode.null,
162
+ JSON.Encode.object(Dict.fromArray([("name", JSON.Encode.string("Books"))])),
163
+ ctxFor(userIdentity),
164
+ )
160
165
  expect(getTypename(response))->toEqual("CommandAccepted")
161
166
  expect(calls.contents)->toEqual(["Add"])
162
167
  })
163
168
 
164
169
  testPromise("anonymous invoking Add is rejected (AllowAuthenticated default)", async () => {
165
170
  let (addResolver, _, _, calls) = buildFixture(~namespace="Cat5")
166
- let response =
167
- await addResolver(
168
- JSON.Encode.null,
169
- JSON.Encode.object(Dict.fromArray([("name", JSON.Encode.string("Books"))])),
170
- anonymousCtx,
171
- )
171
+ let response = await addResolver(
172
+ JSON.Encode.null,
173
+ JSON.Encode.object(Dict.fromArray([("name", JSON.Encode.string("Books"))])),
174
+ anonymousCtx,
175
+ )
172
176
  expect(getTypename(response))->toEqual("CommandRejected")
173
177
  expect(getErrorCode(response))->toEqual("Forbidden")
174
178
  expect(calls.contents)->toEqual([])
@@ -176,12 +180,11 @@ describe("CommandGeneratorResolvers_GraphQL — per-constructor authorization",
176
180
 
177
181
  testPromise("admin invoking Add also succeeds (admin has User group too)", async () => {
178
182
  let (addResolver, _, _, calls) = buildFixture(~namespace="Cat6")
179
- let response =
180
- await addResolver(
181
- JSON.Encode.null,
182
- JSON.Encode.object(Dict.fromArray([("name", JSON.Encode.string("Books"))])),
183
- ctxFor(adminIdentity),
184
- )
183
+ let response = await addResolver(
184
+ JSON.Encode.null,
185
+ JSON.Encode.object(Dict.fromArray([("name", JSON.Encode.string("Books"))])),
186
+ ctxFor(adminIdentity),
187
+ )
185
188
  expect(getTypename(response))->toEqual("CommandAccepted")
186
189
  expect(calls.contents)->toEqual(["Add"])
187
190
  })
@@ -246,10 +249,10 @@ describe("CommandGeneratorResolvers_GraphQL — a failure the caller may read",
246
249
  })
247
250
 
248
251
  testPromise("a caller-fault failure is rethrown as an unmasked GraphQL error", async () => {
249
- let resolver = failingFixture(~namespace="Fault1", ~failWith=() =>
250
- ReventlessCore.Plugin_ResolverError.throwCallerFault(
251
- `Error: Couldn't decode: Expected string | undefined, received null`,
252
- )
252
+ let resolver = failingFixture(
253
+ ~namespace="Fault1",
254
+ ~failWith=() =>
255
+ ReventlessCore.Plugin_ResolverError.throwCallerFault(`Error: Couldn't decode: Expected string | undefined, received null`),
253
256
  )
254
257
  let caught = await invoke(resolver)
255
258
  expect((
@@ -261,10 +264,12 @@ describe("CommandGeneratorResolvers_GraphQL — a failure the caller may read",
261
264
  // The reason is the point: masked, the caller learns only that something
262
265
  // went wrong on a request that is theirs to fix.
263
266
  testPromise("and carries the reason it was given", async () => {
264
- let resolver = failingFixture(~namespace="Fault2", ~failWith=() =>
265
- ReventlessCore.Plugin_ResolverError.throwCallerFault(
266
- "Expected string | undefined, received null",
267
- )
267
+ let resolver = failingFixture(
268
+ ~namespace="Fault2",
269
+ ~failWith=() =>
270
+ ReventlessCore.Plugin_ResolverError.throwCallerFault(
271
+ "Expected string | undefined, received null",
272
+ ),
268
273
  )
269
274
  let caught = await invoke(resolver)
270
275
  expect(
@@ -277,8 +282,9 @@ describe("CommandGeneratorResolvers_GraphQL — a failure the caller may read",
277
282
  // The control, and the reason the mark exists: an internal failure is not the
278
283
  // caller's business and must keep being masked.
279
284
  testPromise("an unmarked failure is left to be masked", async () => {
280
- let resolver = failingFixture(~namespace="Fault3", ~failWith=() =>
281
- JsError.throwWithMessage("connection to the event store was reset")
285
+ let resolver = failingFixture(
286
+ ~namespace="Fault3",
287
+ ~failWith=() => JsError.throwWithMessage("connection to the event store was reset"),
282
288
  )
283
289
  let caught = await invoke(resolver)
284
290
  expect(caught->Option.flatMap(JsExn.name))->not_->toEqual(Some("GraphQLError"))