@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.
Files changed (209) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/package.json +11 -11
  3. package/src/Behavior.res +1 -4
  4. package/src/Message.res +17 -14
  5. package/src/Projection.res +51 -25
  6. package/src/RequestContext.res +8 -9
  7. package/src/ResourceAttribution.res +28 -14
  8. package/src/adapter/Adapter.res +48 -4
  9. package/src/adapter/EventLogProvisioning/EventLogProvisioning.res +5 -5
  10. package/src/adapter/Messaging/Messaging_Log_Backend.res +10 -7
  11. package/src/adapter/Messaging/Messaging_Log_Backend.res.mjs +2 -4
  12. package/src/adapter/Monitoring/Monitoring.res +16 -17
  13. package/src/adapter/QueryInterception/QueryInterception.res +0 -1
  14. package/src/adapter/Runtime/AggregateRuntime_Builder_Common.res +12 -5
  15. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +11 -7
  16. package/src/adapter/Runtime/PluginRuntime_Builder_Micro.res +1 -7
  17. package/src/adapter/Runtime/PluginRuntime_Builder_Single.res +1 -7
  18. package/src/adapter/RuntimeExtension/RuntimeExtension.res +0 -1
  19. package/src/admin/Platform_Admin.res +17 -6
  20. package/src/admin/Platform_AdminApi.res +14 -9
  21. package/src/admin/Platform_Admin_Callback.res +1 -1
  22. package/src/admin/Platform_BakedManifest.res +34 -34
  23. package/src/admin/Platform_ComponentDefinitionsApi.res +9 -21
  24. package/src/admin/Platform_PluginStructuresApi.res +15 -12
  25. package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res +1 -1
  26. package/src/components/Aggregate/Aggregate.res +13 -12
  27. package/src/components/Aggregate/Aggregate_Builder.res +22 -21
  28. package/src/components/Aggregate/Aggregate_Callback.res +91 -86
  29. package/src/components/Api/ApiNoApiHelpers.res +1 -3
  30. package/src/components/Api/Api_Ids.res +0 -1
  31. package/src/components/Api/Api_Naming.res +41 -18
  32. package/src/components/Api/GraphQL_FragmentGenerator.res +42 -52
  33. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +11 -4
  34. package/src/components/Api/GraphQL_SchemaInspector.res +5 -3
  35. package/src/components/Api/GraphQL_Stitcher.res +9 -10
  36. package/src/components/Api/MCP_SchemaGenerator.res +8 -12
  37. package/src/components/Api/OwnerScopeDiagnostics.res +1 -2
  38. package/src/components/Api/QueryDbListQuery.res +13 -6
  39. package/src/components/Api/SchemaType.res +40 -20
  40. package/src/components/Api/SchemaType.res.mjs +16 -0
  41. package/src/components/Api/SuryToJsonSchema.res +18 -10
  42. package/src/components/Api/SuryToJsonSchema.res.mjs +135 -121
  43. package/src/components/AutomationSlice/AutomationSlice.res +4 -4
  44. package/src/components/AutomationSlice/AutomationSlice_Builder.res +15 -14
  45. package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -3
  46. package/src/components/Builder_Helpers.res +12 -23
  47. package/src/components/CommandGenerator/CommandGenerator.res +6 -1
  48. package/src/components/CommandGenerator/CommandGenerator_Builder.res +29 -21
  49. package/src/components/CommandGenerator/CommandGenerator_Callback.res +17 -21
  50. package/src/components/CommandTopic/CommandTopic.res +3 -3
  51. package/src/components/CommandTopic/CommandTopic_Builder.res +10 -10
  52. package/src/components/CommandTopic/CommandTopic_Callback.res +1 -4
  53. package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -2
  54. package/src/components/Counter/Counter_Builder.res +1 -6
  55. package/src/components/Counter/Counter_Callback.res +3 -3
  56. package/src/components/Dcb/Dcb_Builder.res +970 -956
  57. package/src/components/DcbEventLog/DcbEventLog.res +4 -4
  58. package/src/components/DcbEventLog/DcbEventLog_Builder.res +0 -1
  59. package/src/components/DcbEventLog/DcbEventLog_Operations.res +17 -18
  60. package/src/components/EventCollector/EventCollector_Builder.res +2 -1
  61. package/src/components/EventLog/EventLog.res +5 -1
  62. package/src/components/EventLog/EventLog_Operations.res +12 -8
  63. package/src/components/EventMapper/EventMapper.res +8 -10
  64. package/src/components/EventMapper/EventMapper_Builder.res +4 -1
  65. package/src/components/EventMapper/EventMapper_Callback.res +46 -31
  66. package/src/components/EventTopic/EventTopic.res +3 -3
  67. package/src/components/EventTopic/EventTopic_Builder.res +1 -1
  68. package/src/components/Extension/Extension.res +4 -1
  69. package/src/components/Extension/Extension_Operations.res +11 -3
  70. package/src/components/ExtensionPoint/ExtensionPoint.res +3 -3
  71. package/src/components/ExtensionPoint/ExtensionPoint_Builder.res +9 -10
  72. package/src/components/ExtensionPoint/ExtensionPoint_Callback.res +2 -6
  73. package/src/components/ExtensionPoint/ExtensionPoint_Operations.res +13 -5
  74. package/src/components/Heartbeat/Heartbeat_Callback.res +3 -3
  75. package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +4 -4
  76. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +7 -10
  77. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice.res +4 -4
  78. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +15 -14
  79. package/src/components/QueryDb/QueryDb_Adapter.res +1 -3
  80. package/src/components/QueryDb/QueryDb_Operations.res +10 -10
  81. package/src/components/ReadModel/ReadModel.res +3 -3
  82. package/src/components/ReadModel/ReadModel_Builder.res +1 -2
  83. package/src/components/ReadModel/ReadModel_Callback.res +2 -1
  84. package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +5 -2
  85. package/src/components/StateChangeSlice/StateChangeSlice.res +3 -3
  86. package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +23 -4
  87. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +22 -6
  88. package/src/components/StateTopic.res +1 -3
  89. package/src/components/StateViewSlice/StateViewSlice.res +4 -4
  90. package/src/components/StateViewSlice/StateViewSlice_Builder.res +79 -68
  91. package/src/components/StateViewSlice/StateViewSlice_Callback.res +3 -1
  92. package/src/components/Task/Task.res +3 -3
  93. package/src/components/Task/Task_Adapter.res +4 -1
  94. package/src/components/Task/Task_Builder.res +24 -21
  95. package/src/plugin/api/PluginBaseFragment.res +9 -3
  96. package/src/plugin/component/Capability_Inference.res +8 -10
  97. package/src/plugin/component/Plugin.res +6 -6
  98. package/src/plugin/component/Plugin_Builder.res +54 -50
  99. package/src/plugin/component/Plugin_Callback.res +10 -6
  100. package/src/plugin/component/Plugin_Helpers.res +264 -230
  101. package/src/plugin/component/Plugin_Structure.res +370 -324
  102. package/src/plugin/component/Plugin_SubscriptionSchema.res +8 -6
  103. package/src/plugin/component/SchemaWalker.res +11 -12
  104. package/src/plugin/connect/PluginConnectExtension_Mapping.res +45 -50
  105. package/src/plugin/lifecycle/PluginBehavior.res +3 -4
  106. package/src/plugin/lifecycle/PluginSpec.res +1 -2
  107. package/src/plugin/lifecycle/PluginsProjection.res +16 -5
  108. package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
  109. package/src/util/CommandPublisher.res +9 -2
  110. package/src/util/CsvStream.res +3 -3
  111. package/src/util/Interstack.res +7 -15
  112. package/src/util/Interstack.resi +3 -3
  113. package/src/util/LogFormat.res +5 -9
  114. package/src/util/Logger.res +1 -2
  115. package/src/util/Lru.res +2 -1
  116. package/src/util/Lru.resi +2 -3
  117. package/src/util/ScheduleOps.res +24 -27
  118. package/src/util/StorePrefixCollision.res +6 -9
  119. package/src/util/Util_Promise.res +4 -1
  120. package/src/util/Util_Promise.res.mjs +1 -1
  121. package/tests/IdentityTest.res +166 -113
  122. package/tests/ResourceAttributionTest.res +28 -25
  123. package/tests/StorePrefixCollisionTest.res +2 -1
  124. package/tests/TaskBucketNamingTest.res +3 -2
  125. package/tests/adapter/EventLogProvisioningTest.res +51 -48
  126. package/tests/adapter/MonitoringTest.res +81 -38
  127. package/tests/adapter/QueryInterceptionTest.res +2 -2
  128. package/tests/adapter/RefusalVocabularyTest.res +31 -23
  129. package/tests/adapter/RuntimeExtensionHookReachTest.res +9 -8
  130. package/tests/adapter/RuntimeExtensionTest.res +5 -3
  131. package/tests/admin/AdminApiSchemaDriftTest.res +22 -16
  132. package/tests/admin/Platform_Admin_StructureTest.res +10 -11
  133. package/tests/admin/Platform_BakedManifestTest.res +31 -35
  134. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +18 -15
  135. package/tests/admin/Platform_PluginStructuresApiTest.res +5 -3
  136. package/tests/admin/Platform_UIFragmentsApiTest.res +6 -3
  137. package/tests/aggregate/AggregateCacheTest.res +10 -12
  138. package/tests/aggregate/AggregateCallbackTest.res +75 -46
  139. package/tests/aggregate/AggregateCausationTest.res +19 -20
  140. package/tests/aggregate/AggregateConflictTest.res +41 -19
  141. package/tests/aggregate/AggregateFixtures.res +15 -5
  142. package/tests/aggregate/AggregateRejectionTest.res +97 -80
  143. package/tests/aggregate/AggregateSnapshotTest.res +44 -16
  144. package/tests/api/Api_IdsTest.res +10 -4
  145. package/tests/api/BrandedMarkerCompositionTest.res +48 -36
  146. package/tests/api/GeolocationSemanticTest.res +3 -3
  147. package/tests/api/GraphQL_FragmentGeneratorTest.res +66 -54
  148. package/tests/api/OwnerScopeDiagnosticsTest.res +31 -27
  149. package/tests/api/StorageRefFieldsTest.res +30 -18
  150. package/tests/api/SuryToJsonSchemaTest.res +1338 -981
  151. package/tests/api/SuryToJsonSchemaTest.res.mjs +31 -0
  152. package/tests/api/TaggedUnionSchemaTest.res +17 -24
  153. package/tests/commandgenerator/CommandGeneratorCallbackTest.res +235 -179
  154. package/tests/commandgenerator/CommandGeneratorFixtures.res +8 -13
  155. package/tests/commandgenerator/OwnerStampingTest.res +91 -76
  156. package/tests/commandtopic/CommandOutcomeHookTest.res +198 -155
  157. package/tests/commandtopic/CommandTopicCallbackFixtures.res +5 -4
  158. package/tests/commandtopic/CommandTopicCallbackTest.res +61 -43
  159. package/tests/commandtopic/CommandTopicHelpersRejectionTest.res +70 -55
  160. package/tests/counter/CounterCallbackTest.res +40 -34
  161. package/tests/counter/CounterFixtures.res +4 -4
  162. package/tests/dcb/DcbDecodeTest.res +115 -84
  163. package/tests/dcb/DcbEventLogOperationsTest.res +194 -143
  164. package/tests/dcb/DcbEventLogSchemaTest.res +13 -13
  165. package/tests/dcb/DcbFixtures.res +4 -5
  166. package/tests/dcb/DcbScopeInferenceTest.res +173 -114
  167. package/tests/dcb/DcbStateChangeSliceTest.res +391 -262
  168. package/tests/dcb/DcbTagTest.res +22 -11
  169. package/tests/dcb/DcbValidationTest.res +288 -218
  170. package/tests/eventlog/EventLogFixtures.res +4 -4
  171. package/tests/eventlog/EventLogOperationsTest.res +102 -67
  172. package/tests/eventlog/EventLogRetryTest.res +104 -65
  173. package/tests/eventmapper/EventMapperCallbackTest.res +118 -83
  174. package/tests/eventmapper/EventMapperFixtures.res +15 -23
  175. package/tests/eventmapper/EventMapperTest.res +54 -38
  176. package/tests/eventtopic/EventTopicFixtures.res +3 -4
  177. package/tests/eventtopic/EventTopicOperationsTest.res +41 -29
  178. package/tests/extensionpoint/ExtensionPointCallbackTest.res +58 -45
  179. package/tests/extensionpoint/ExtensionPointFixtures.res +16 -20
  180. package/tests/extensionpoint/ExtensionPointOperationsTest.res +70 -49
  181. package/tests/extensionpoint/PluginDisconnectScheduleTest.res +2 -1
  182. package/tests/logger/LogFormatTest.res +104 -82
  183. package/tests/logger/LoggerLazyTest.res +21 -12
  184. package/tests/message/MessageTest.res +96 -93
  185. package/tests/message/MetaEnvelopeTest.res +26 -30
  186. package/tests/plugin/HeartbeatDisconnectGraceTest.res +31 -25
  187. package/tests/plugin/ManifestVisibilityTest.res +20 -12
  188. package/tests/plugin/PluginDefinitionScalars.res +1 -2
  189. package/tests/plugin/PluginLifecycleCorpusTest.res +15 -12
  190. package/tests/plugin/PluginSpecExperiment.res +0 -1
  191. package/tests/plugin/PluginStructureTest.res +1533 -1030
  192. package/tests/plugin/PluginStructureTest.res.mjs +6 -0
  193. package/tests/plugin/Plugin_EventQuerySchemaTest.res +21 -10
  194. package/tests/plugin/SchemaWalkerTest.res +116 -70
  195. package/tests/plugin/StateChange/PsReserveStock.res +4 -4
  196. package/tests/plugin/StateView/PsAnnotatedView.res +6 -4
  197. package/tests/plugin/StateView/PsAnnotatedView.res.mjs +7 -3
  198. package/tests/plugin/StateView/PsCustomersView.res +4 -4
  199. package/tests/plugin/StateView/PsShipmentsView.res +3 -2
  200. package/tests/projection/LifecycleTrailTest.res +1 -4
  201. package/tests/projection/ProjectionOptimizeTest.res +13 -9
  202. package/tests/querydb/QueryDbFixtures.res +0 -1
  203. package/tests/querydb/QueryDbOperationsTest.res +99 -64
  204. package/tests/querydb/TaggedUnionStampTest.res +10 -8
  205. package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res +50 -35
  206. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +4 -10
  207. package/tests/util/CommandPublisherTest.res +0 -1
  208. package/tests/util/CsvStreamTest.res +3 -3
  209. package/tests/util/LruTest.res +84 -56
@@ -1,7 +1,6 @@
1
1
  open JestGlobals
2
2
  open LogFormat
3
3
 
4
-
5
4
  // Sink detection (Reventless.AnsiStyle) defaults a non-TTY stdout — Jest
6
5
  // included — to JSON, which no-ops `bold`/`fmtComp`. The human-format
7
6
  // assertions below expect ANSI, so force text mode for this file. The
@@ -141,34 +140,47 @@ describe("LogFormat", () => {
141
140
  type command = AddProduct({productId: string, name: string, imageUrl?: string})
142
141
  }
143
142
 
144
- testSync("omits an absent optional field", () => {
145
- let json =
146
- OptionalFieldCmd.AddProduct({productId: "prd-1", name: "Cirrus"})
147
- ->Message.encode(OptionalFieldCmd.commandSchema)
148
- expect(variantFields(json))->toEqual(`, {productId:"prd-1",name:"Cirrus"}`)
149
- })
150
-
151
- testSync("keeps a present optional field", () => {
152
- let json =
153
- OptionalFieldCmd.AddProduct({productId: "prd-1", name: "Cirrus", imageUrl: "/u/a.png"})
154
- ->Message.encode(OptionalFieldCmd.commandSchema)
155
- expect(variantFields(json))->toEqual(
156
- `, {productId:"prd-1",name:"Cirrus",imageUrl:"/u/a.png"}`,
157
- )
158
- })
159
-
160
- testSync("elides a value past the budget rather than dropping it", () => {
161
- let long = String.repeat("x", maxValueChars + 20)
162
- let json =
163
- OptionalFieldCmd.AddProduct({productId: "prd-1", name: long})
164
- ->Message.encode(OptionalFieldCmd.commandSchema)
165
- let rendered = variantFields(json)
166
- expect(rendered->String.includes("…(+22 chars)"))->toBe(true)
167
- // The oversized value is capped, not the line: the neighbouring field that
168
- // says which product this is stays intact.
169
- expect(rendered->String.includes(long))->toBe(false)
170
- expect(rendered->String.includes(`productId:"prd-1"`))->toBe(true)
171
- })
143
+ testSync(
144
+ "omits an absent optional field",
145
+ () => {
146
+ let json =
147
+ OptionalFieldCmd.AddProduct({productId: "prd-1", name: "Cirrus"})->Message.encode(
148
+ OptionalFieldCmd.commandSchema,
149
+ )
150
+ expect(variantFields(json))->toEqual(`, {productId:"prd-1",name:"Cirrus"}`)
151
+ },
152
+ )
153
+
154
+ testSync(
155
+ "keeps a present optional field",
156
+ () => {
157
+ let json = OptionalFieldCmd.AddProduct({
158
+ productId: "prd-1",
159
+ name: "Cirrus",
160
+ imageUrl: "/u/a.png",
161
+ })->Message.encode(OptionalFieldCmd.commandSchema)
162
+ expect(
163
+ variantFields(json),
164
+ )->toEqual(`, {productId:"prd-1",name:"Cirrus",imageUrl:"/u/a.png"}`)
165
+ },
166
+ )
167
+
168
+ testSync(
169
+ "elides a value past the budget rather than dropping it",
170
+ () => {
171
+ let long = String.repeat("x", maxValueChars + 20)
172
+ let json =
173
+ OptionalFieldCmd.AddProduct({productId: "prd-1", name: long})->Message.encode(
174
+ OptionalFieldCmd.commandSchema,
175
+ )
176
+ let rendered = variantFields(json)
177
+ expect(rendered->String.includes("…(+22 chars)"))->toBe(true)
178
+ // The oversized value is capped, not the line: the neighbouring field that
179
+ // says which product this is stays intact.
180
+ expect(rendered->String.includes(long))->toBe(false)
181
+ expect(rendered->String.includes(`productId:"prd-1"`))->toBe(true)
182
+ },
183
+ )
172
184
  })
173
185
  })
174
186
 
@@ -207,8 +219,7 @@ let validLevels = ["DEBUG", "INFO", "WARN", "ERROR"]
207
219
  // True when a captured line is a clean JSON log record: parses as JSON, carries
208
220
  // no ANSI escape anywhere, has a string `message` and a known `level`.
209
221
  let isCleanRecord = (line: string): bool =>
210
- !hasAnsi(line) &&
211
- (try {
222
+ !hasAnsi(line) && try {
212
223
  switch line->JSON.parseOrThrow->JSON.Decode.object {
213
224
  | Some(obj) =>
214
225
  let levelOk =
@@ -222,7 +233,7 @@ let isCleanRecord = (line: string): bool =>
222
233
  }
223
234
  } catch {
224
235
  | _ => false
225
- })
236
+ }
226
237
 
227
238
  // Read a top-level string field off a captured JSON record (None if absent).
228
239
  let fieldOf = (line: string, key: string): option<string> =>
@@ -269,8 +280,8 @@ describe("JSON sink", () => {
269
280
  // Event-collector comps carry the component *resource* name (`<Spec><Kind>`),
270
281
  // while the registry holds the bare spec name — the prefix candidate bridges them.
271
282
  Logger.registerComponentPlugin(~componentName="Products", ~pluginName="Catalog")
272
- let lines = captureLogs(() =>
273
- log.info(~comp="EventCollector(ProductsReadModel)", "projecting event")
283
+ let lines = captureLogs(
284
+ () => log.info(~comp="EventCollector(ProductsReadModel)", "projecting event"),
274
285
  )
275
286
  let line = lines->Array.length == 1 ? lines->Array.getUnsafe(0) : ""
276
287
  expect(lines->Array.length == 1 && line->fieldOf("plugin") == Some("Catalog"))->toBe(true)
@@ -280,8 +291,8 @@ describe("JSON sink", () => {
280
291
  // `Order` (Ordering) must not win over `OrderLines` (Billing) for `OrderLinesReadModel`.
281
292
  Logger.registerComponentPlugin(~componentName="Order", ~pluginName="Ordering")
282
293
  Logger.registerComponentPlugin(~componentName="OrderLines", ~pluginName="Billing")
283
- let lines = captureLogs(() =>
284
- log.info(~comp="EventCollector(OrderLinesReadModel)", "projecting event")
294
+ let lines = captureLogs(
295
+ () => log.info(~comp="EventCollector(OrderLinesReadModel)", "projecting event"),
285
296
  )
286
297
  let line = lines->Array.length == 1 ? lines->Array.getUnsafe(0) : ""
287
298
  expect(lines->Array.length == 1 && line->fieldOf("plugin") == Some("Billing"))->toBe(true)
@@ -290,14 +301,15 @@ describe("JSON sink", () => {
290
301
  testSync("dispatch annotation carries comp onto a handler line that passes none", () => {
291
302
  // What makes two collectors in one runtime process separable: the element's comp
292
303
  // comes from the dispatch boundary, not from the application's own log call.
293
- let lines = captureLogs(() =>
294
- EffectLogger.logInfo("projected")
295
- ->Runtime.annotateInvocation(
296
- ~correlationId="cid-1",
297
- ~causationId="parent-1",
298
- ~comp="EventCollector(ProductsReadModel)",
299
- )
300
- ->Effect.runSync
304
+ let lines = captureLogs(
305
+ () =>
306
+ EffectLogger.logInfo("projected")
307
+ ->Runtime.annotateInvocation(
308
+ ~correlationId="cid-1",
309
+ ~causationId="parent-1",
310
+ ~comp="EventCollector(ProductsReadModel)",
311
+ )
312
+ ->Effect.runSync,
301
313
  )
302
314
  let line = lines->Array.length >= 1 ? lines->Array.getUnsafe(0) : ""
303
315
  let ok =
@@ -314,52 +326,56 @@ describe("JSON sink", () => {
314
326
  })
315
327
 
316
328
  testSync("Logger.error with ~data emits one clean JSON record", () => {
317
- let lines = captureLogs(() =>
318
- log.error(
319
- ~comp="Util_AppSync_Caller",
320
- ~data=JSON.parseOrThrow(`{"errors":["boom"]}`),
321
- "query errors",
322
- )
329
+ let lines = captureLogs(
330
+ () =>
331
+ log.error(
332
+ ~comp="Util_AppSync_Caller",
333
+ ~data=JSON.parseOrThrow(`{"errors":["boom"]}`),
334
+ "query errors",
335
+ ),
323
336
  )
324
337
  expect(lines->Array.length == 1 && isCleanRecord(lines->Array.getUnsafe(0)))->toBe(true)
325
338
  })
326
339
 
327
340
  testSync("EffectLogger.logInfo emits clean JSON through install()", () => {
328
- let lines = captureLogs(() =>
329
- EffectLogger.logInfo(~comp="Aggregate(Product)", "replay done")->Effect.runSync
341
+ let lines = captureLogs(
342
+ () => EffectLogger.logInfo(~comp="Aggregate(Product)", "replay done")->Effect.runSync,
330
343
  )
331
344
  let allClean = lines->Array.filter(l => !isCleanRecord(l))->Array.length == 0
332
345
  expect(lines->Array.length >= 1 && allClean)->toBe(true)
333
346
  })
334
347
 
335
348
  testSync("EffectLogger.logInfo carries ~comp + ~detail as structured fields (no \\x00)", () => {
336
- let lines = captureLogs(() =>
337
- EffectLogger.logInfo(
338
- ~comp="Aggregate(Product)",
339
- ~detail=JSON.parseOrThrow(`{"id":"p-1"}`),
340
- "added",
341
- )->Effect.runSync
349
+ let lines = captureLogs(
350
+ () =>
351
+ EffectLogger.logInfo(
352
+ ~comp="Aggregate(Product)",
353
+ ~detail=JSON.parseOrThrow(`{"id":"p-1"}`),
354
+ "added",
355
+ )->Effect.runSync,
342
356
  )
343
- let ok = lines->Array.some(line =>
344
- switch line->JSON.parseOrThrow->JSON.Decode.object {
345
- | Some(obj) =>
346
- obj->Dict.get("comp")->Option.flatMap(JSON.Decode.string) == Some("Aggregate(Product)") &&
347
- obj
348
- ->Dict.get("detail")
349
- ->Option.flatMap(JSON.Decode.object)
350
- ->Option.flatMap(d => d->Dict.get("id"))
351
- ->Option.flatMap(JSON.Decode.string) == Some("p-1")
352
- | None => false
353
- }
357
+ let ok = lines->Array.some(
358
+ line =>
359
+ switch line->JSON.parseOrThrow->JSON.Decode.object {
360
+ | Some(obj) =>
361
+ obj->Dict.get("comp")->Option.flatMap(JSON.Decode.string) == Some("Aggregate(Product)") &&
362
+ obj
363
+ ->Dict.get("detail")
364
+ ->Option.flatMap(JSON.Decode.object)
365
+ ->Option.flatMap(d => d->Dict.get("id"))
366
+ ->Option.flatMap(JSON.Decode.string) == Some("p-1")
367
+ | None => false
368
+ },
354
369
  )
355
370
  expect(ok)->toBe(true)
356
371
  })
357
372
 
358
373
  testSync("Effect.annotateLogs(correlationId) surfaces as a top-level field", () => {
359
- let lines = captureLogs(() =>
360
- EffectLogger.logInfo(~comp="Aggregate(Product)", "handling")
361
- ->Effect.annotateLogs("correlationId", "c-123")
362
- ->Effect.runSync
374
+ let lines = captureLogs(
375
+ () =>
376
+ EffectLogger.logInfo(~comp="Aggregate(Product)", "handling")
377
+ ->Effect.annotateLogs("correlationId", "c-123")
378
+ ->Effect.runSync,
363
379
  )
364
380
  expect(lines->Array.some(line => line->fieldOf("correlationId") == Some("c-123")))->toBe(true)
365
381
  })
@@ -368,10 +384,11 @@ describe("JSON sink", () => {
368
384
  // The component is NOT registered against any plugin; without an explicit
369
385
  // ~plugin annotation, resolvePlugin would fall back through transformations
370
386
  // and emit no plugin field. With the annotation, plugin wins.
371
- let lines = captureLogs(() =>
372
- EffectLogger.logInfo(~comp="Aggregate(Unregistered)", "handling")
373
- ->Effect.annotateLogs("plugin", "Ordering")
374
- ->Effect.runSync
387
+ let lines = captureLogs(
388
+ () =>
389
+ EffectLogger.logInfo(~comp="Aggregate(Unregistered)", "handling")
390
+ ->Effect.annotateLogs("plugin", "Ordering")
391
+ ->Effect.runSync,
375
392
  )
376
393
  expect(lines->Array.some(line => line->fieldOf("plugin") == Some("Ordering")))->toBe(true)
377
394
  })
@@ -398,14 +415,17 @@ describe("JSON sink", () => {
398
415
  let detail = Dict.fromArray([("blob", JSON.Encode.string(big))])->JSON.Encode.object
399
416
  // ~detail flows through emit (Logger.t's ~data goes into the message instead).
400
417
  let direct = captureLogs(() => Logger.emit(~level=Info, ~comp="Platform", ~detail, "big"))
401
- let ok = switch direct->Array.get(0)->Option.flatMap(l => l->JSON.parseOrThrow->JSON.Decode.object) {
418
+ let ok = switch direct
419
+ ->Array.get(0)
420
+ ->Option.flatMap(l => l->JSON.parseOrThrow->JSON.Decode.object) {
402
421
  | Some(obj) =>
403
422
  switch obj->Dict.get("detail")->Option.flatMap(JSON.Decode.object) {
404
423
  | Some(d) =>
405
424
  d->Dict.get("truncated")->Option.flatMap(JSON.Decode.bool) == Some(true) &&
406
- d->Dict.get("preview")->Option.flatMap(JSON.Decode.string)->Option.mapOr(false, p =>
407
- p->String.length <= 512
408
- )
425
+ d
426
+ ->Dict.get("preview")
427
+ ->Option.flatMap(JSON.Decode.string)
428
+ ->Option.mapOr(false, p => p->String.length <= 512)
409
429
  | None => false
410
430
  }
411
431
  | None => false
@@ -425,7 +445,9 @@ describe("JSON sink", () => {
425
445
  let cmd: Message.commandJson = {
426
446
  Message.id: "0",
427
447
  meta,
428
- commandJson: (Heartbeat("0.0.0"): PluginSpec.command)->Message.encode(PluginSpec.commandSchema),
448
+ commandJson: (Heartbeat("0.0.0"): PluginSpec.command)->Message.encode(
449
+ PluginSpec.commandSchema,
450
+ ),
429
451
  }
430
452
  let name = cmd->cmdName
431
453
  expect(!hasAnsi(name) && name == "Heartbeat")->toBe(true)
@@ -8,29 +8,38 @@ describe("Logger.debugLazy", () => {
8
8
  testSync("does NOT invoke the thunk at the default Info level", () => {
9
9
  let called = ref(false)
10
10
  let log = Logger.makeLogger(~minLevel=Logger.Info)
11
- log.debugLazy(~comp="Test", () => {
12
- called := true
13
- "should never be built"
14
- })
11
+ log.debugLazy(
12
+ ~comp="Test",
13
+ () => {
14
+ called := true
15
+ "should never be built"
16
+ },
17
+ )
15
18
  expect(called.contents)->toEqual(false)
16
19
  })
17
20
 
18
21
  testSync("invokes the thunk when debug output is enabled", () => {
19
22
  let called = ref(false)
20
23
  let log = Logger.makeLogger(~minLevel=Logger.Debug)
21
- log.debugLazy(~comp="Test", () => {
22
- called := true
23
- "built"
24
- })
24
+ log.debugLazy(
25
+ ~comp="Test",
26
+ () => {
27
+ called := true
28
+ "built"
29
+ },
30
+ )
25
31
  expect(called.contents)->toEqual(true)
26
32
  })
27
33
 
28
34
  testSync("the silent logger never invokes the thunk", () => {
29
35
  let called = ref(false)
30
- Logger.silent.debugLazy(~comp="Test", () => {
31
- called := true
32
- "x"
33
- })
36
+ Logger.silent.debugLazy(
37
+ ~comp="Test",
38
+ () => {
39
+ called := true
40
+ "x"
41
+ },
42
+ )
34
43
  expect(called.contents)->toEqual(false)
35
44
  })
36
45
  })
@@ -97,67 +97,70 @@ describe("Message should", () => {
97
97
  // before later pluginStructure fields like `events`/`chapter`) existed must still
98
98
  // decode — otherwise the one stale event bricks the whole lifecycle aggregate. See
99
99
  // docs/plans/done/platform-infrastructure-in-plugin-list.md (durable fix option 2).
100
- testSync("tolerantly decode a VersionConnected persisted before kind/structure fields existed", () => {
101
- open PluginSpec
102
- let def: Reventless.Plugin.pluginDefinition = {
103
- id: "id",
104
- name: "Ordering",
105
- version: "1.0.0",
106
- extensionPoints: [],
107
- extensions: [],
108
- eventCollector: "arn",
109
- extensionProtocols: [],
110
- apiSchemaFragment: None,
111
- apiTarget: None,
112
- structure: Some(Reventless.Offload.Inline({
113
- readModels: [],
114
- stateViewSlices: [],
115
- stateChangeSlices: [],
116
- aggregates: [
117
- {
118
- name: "Order",
119
- commands: [],
120
- producedEventTypes: [],
121
- consumedEventTypes: [],
122
- linkedViews: [],
123
- consistencyRead: None,
124
- events: [],
125
- errors: [],
126
- chapter: None,
127
- },
128
- ],
129
- automationSlices: [],
130
- outboundTranslationSlices: [],
131
- inboundTranslationSlices: [],
100
+ testSync(
101
+ "tolerantly decode a VersionConnected persisted before kind/structure fields existed",
102
+ () => {
103
+ open PluginSpec
104
+ let def: Reventless.Plugin.pluginDefinition = {
105
+ id: "id",
106
+ name: "Ordering",
107
+ version: "1.0.0",
108
+ extensionPoints: [],
132
109
  extensions: [],
133
- extensionPoints: None,
134
- requiredStores: None,
135
- requiredStoreDeclarations: None,
136
- requiredCapabilities: None,
137
- traitDeclarations: None,
138
- })),
139
- dcbEventLog: None,
140
- kind: Domain,
141
- }
142
- let event' = {
143
- id: "id",
144
- meta: {
145
- service: "svc",
146
- time: "t",
147
- ip: "ip",
148
- user: "u",
149
- msgId: "m",
150
- correlationId: "c",
151
- },
152
- event: VersionConnected(def),
153
- }
154
- let json = event'->Message.encodeEvent'(S.string, PluginSpec.eventSchema)
155
-
156
- // Simulate the persisted JSON as written before these fields existed: drop the
157
- // mandatory enum `kind`, a top-level `T | null` field, and two nested writableDef
158
- // fields (a mandatory array + a `T | null`).
159
- let oldJson =
160
- %raw(`function(j){
110
+ eventCollector: "arn",
111
+ extensionProtocols: [],
112
+ apiSchemaFragment: None,
113
+ apiTarget: None,
114
+ structure: Some(
115
+ Reventless.Offload.Inline({
116
+ readModels: [],
117
+ stateViewSlices: [],
118
+ stateChangeSlices: [],
119
+ aggregates: [
120
+ {
121
+ name: "Order",
122
+ commands: [],
123
+ producedEventTypes: [],
124
+ consumedEventTypes: [],
125
+ linkedViews: [],
126
+ consistencyRead: None,
127
+ events: [],
128
+ errors: [],
129
+ chapter: None,
130
+ },
131
+ ],
132
+ automationSlices: [],
133
+ outboundTranslationSlices: [],
134
+ inboundTranslationSlices: [],
135
+ extensions: [],
136
+ extensionPoints: None,
137
+ requiredStores: None,
138
+ requiredStoreDeclarations: None,
139
+ requiredCapabilities: None,
140
+ traitDeclarations: None,
141
+ }),
142
+ ),
143
+ dcbEventLog: None,
144
+ kind: Domain,
145
+ }
146
+ let event' = {
147
+ id: "id",
148
+ meta: {
149
+ service: "svc",
150
+ time: "t",
151
+ ip: "ip",
152
+ user: "u",
153
+ msgId: "m",
154
+ correlationId: "c",
155
+ },
156
+ event: VersionConnected(def),
157
+ }
158
+ let json = event'->Message.encodeEvent'(S.string, PluginSpec.eventSchema)
159
+
160
+ // Simulate the persisted JSON as written before these fields existed: drop the
161
+ // mandatory enum `kind`, a top-level `T | null` field, and two nested writableDef
162
+ // fields (a mandatory array + a `T | null`).
163
+ let oldJson = %raw(`function(j){
161
164
  var c = JSON.parse(JSON.stringify(j));
162
165
  var d = c.event._0;
163
166
  delete d.kind;
@@ -168,35 +171,36 @@ describe("Message should", () => {
168
171
  return c;
169
172
  }`)(json)
170
173
 
171
- // Strict decode must reject the stale JSON — the tolerance is what saves it.
172
- let strictThrew = switch oldJson->Reventless.Util_Sury.fromJson(
173
- Message.toEventSchema'(S.string, PluginSpec.eventSchema),
174
- ) {
175
- | _ => false
176
- | exception _ => true
177
- }
178
- expect(strictThrew)->toBe(true)
179
-
180
- // Tolerant decode heals: kind -> Domain (first variant), missing array -> [],
181
- // missing `T | null` fields -> None.
182
- let decoded = oldJson->Message.decodeEvent'(S.string, PluginSpec.eventSchema)
183
- switch decoded.event {
184
- | VersionConnected(d) =>
185
- expect(
186
- switch d.kind {
187
- | Domain => "Domain"
188
- | _ => "other"
189
- },
190
- )->toBe("Domain")
191
- expect(d.apiSchemaFragment->Option.isNone)->toBe(true)
192
- let structure: Reventless.Plugin.pluginStructure =
193
- d.structure->Option.flatMap(Reventless.Offload.getInline)->Option.getOrThrow
194
- let agg = structure.aggregates->Array.getUnsafe(0)
195
- expect(agg.events->Array.length)->toBe(0)
196
- expect(agg.chapter->Option.isNone)->toBe(true)
197
- | _ => expect("wrong-variant")->toBe("VersionConnected")
198
- }
199
- })
174
+ // Strict decode must reject the stale JSON — the tolerance is what saves it.
175
+ let strictThrew = switch oldJson->Reventless.Util_Sury.fromJson(
176
+ Message.toEventSchema'(S.string, PluginSpec.eventSchema),
177
+ ) {
178
+ | _ => false
179
+ | exception _ => true
180
+ }
181
+ expect(strictThrew)->toBe(true)
182
+
183
+ // Tolerant decode heals: kind -> Domain (first variant), missing array -> [],
184
+ // missing `T | null` fields -> None.
185
+ let decoded = oldJson->Message.decodeEvent'(S.string, PluginSpec.eventSchema)
186
+ switch decoded.event {
187
+ | VersionConnected(d) =>
188
+ expect(
189
+ switch d.kind {
190
+ | Domain => "Domain"
191
+ | _ => "other"
192
+ },
193
+ )->toBe("Domain")
194
+ expect(d.apiSchemaFragment->Option.isNone)->toBe(true)
195
+ let structure: Reventless.Plugin.pluginStructure =
196
+ d.structure->Option.flatMap(Reventless.Offload.getInline)->Option.getOrThrow
197
+ let agg = structure.aggregates->Array.getUnsafe(0)
198
+ expect(agg.events->Array.length)->toBe(0)
199
+ expect(agg.chapter->Option.isNone)->toBe(true)
200
+ | _ => expect("wrong-variant")->toBe("VersionConnected")
201
+ }
202
+ },
203
+ )
200
204
 
201
205
  // The exact aggregate-replay path: EventLog_Operations.decodeEvent reassembles the
202
206
  // event variant and calls `Message.decode(Spec.eventSchema)`. This is what actually
@@ -218,10 +222,9 @@ describe("Message should", () => {
218
222
  kind: Domain,
219
223
  }
220
224
  let variantJson = VersionConnected(def)->Message.encode(PluginSpec.eventSchema)
221
- let staleJson =
222
- %raw(`function(j){ var c = JSON.parse(JSON.stringify(j)); delete c._0.kind; return c; }`)(
223
- variantJson,
224
- )
225
+ let staleJson = %raw(`function(j){ var c = JSON.parse(JSON.stringify(j)); delete c._0.kind; return c; }`)(
226
+ variantJson,
227
+ )
225
228
 
226
229
  // Strict decode of the stale variant throws (reproduces the production brick).
227
230
  let strictThrew = switch staleJson->Reventless.Util_Sury.fromJson(PluginSpec.eventSchema) {
@@ -129,10 +129,7 @@ describe("StoredEvent encode/decode round-trip:", () => {
129
129
  position: "000000001",
130
130
  event: "ItemCreated",
131
131
  data: JSON.Object(
132
- Dict.fromArray([
133
- ("itemId", JSON.String("agg-1")),
134
- ("name", JSON.String("Widget")),
135
- ]),
132
+ Dict.fromArray([("itemId", JSON.String("agg-1")), ("name", JSON.String("Widget"))]),
136
133
  ),
137
134
  meta,
138
135
  recordedAt: "2026-05-13T10:00:00.000Z",
@@ -157,10 +154,7 @@ describe("StoredEvent encode/decode round-trip:", () => {
157
154
  data: JSON.Object(Dict.fromArray([("productId", JSON.String("p-1"))])),
158
155
  meta,
159
156
  recordedAt: "2026-05-13T10:00:00.000Z",
160
- tags: [
161
- {key: "productId", value: "p-1"},
162
- {key: "categoryId", value: "c-1"},
163
- ],
157
+ tags: [{key: "productId", value: "p-1"}, {key: "categoryId", value: "c-1"}],
164
158
  }
165
159
  let flat = stored->storedEventToFlatJson(S.string)
166
160
  let roundTrip = flat->flatJsonToStoredEvent(S.string)
@@ -175,26 +169,28 @@ describe("StoredEvent encode/decode round-trip:", () => {
175
169
  expect(first.value)->toBe("p-1")
176
170
  })
177
171
 
178
- testSync("meta is flattened to top-level keys in the on-disk JSON (DynamoDB GSI projectability)", () => {
179
- let meta = generateMeta(~service="catalog")
180
- let stored: Reventless.StoredEvent.storedEvent<string> = {
181
- id: "agg-1",
182
- position: "000000001",
183
- event: "ItemCreated",
184
- data: JSON.Object(Dict.make()),
185
- meta,
186
- recordedAt: "2026-05-13T10:00:00.000Z",
187
- }
188
- let flat = stored->storedEventToFlatJson(S.string)
189
- let obj = flat->JSON.Decode.object->Option.getOrThrow
190
- // meta.* should be top-level (not nested under "meta")
191
- expect(obj->Dict.get("meta"))->toEqual(None)->ignore
192
- expect(obj->Dict.get("service"))->toEqual(Some(JSON.String("catalog")))->ignore
193
- expect(obj->Dict.get("msgId")->Option.isSome)->toBe(true)->ignore
194
- // envelope fields are also top-level
195
- expect(obj->Dict.get("position"))->toEqual(Some(JSON.String("000000001")))->ignore
196
- expect(obj->Dict.get("event"))->toEqual(Some(JSON.String("ItemCreated")))->ignore
197
- expect(obj->Dict.get("recordedAt"))
198
- ->toEqual(Some(JSON.String("2026-05-13T10:00:00.000Z")))
199
- })
172
+ testSync(
173
+ "meta is flattened to top-level keys in the on-disk JSON (DynamoDB GSI projectability)",
174
+ () => {
175
+ let meta = generateMeta(~service="catalog")
176
+ let stored: Reventless.StoredEvent.storedEvent<string> = {
177
+ id: "agg-1",
178
+ position: "000000001",
179
+ event: "ItemCreated",
180
+ data: JSON.Object(Dict.make()),
181
+ meta,
182
+ recordedAt: "2026-05-13T10:00:00.000Z",
183
+ }
184
+ let flat = stored->storedEventToFlatJson(S.string)
185
+ let obj = flat->JSON.Decode.object->Option.getOrThrow
186
+ // meta.* should be top-level (not nested under "meta")
187
+ expect(obj->Dict.get("meta"))->toEqual(None)->ignore
188
+ expect(obj->Dict.get("service"))->toEqual(Some(JSON.String("catalog")))->ignore
189
+ expect(obj->Dict.get("msgId")->Option.isSome)->toBe(true)->ignore
190
+ // envelope fields are also top-level
191
+ expect(obj->Dict.get("position"))->toEqual(Some(JSON.String("000000001")))->ignore
192
+ expect(obj->Dict.get("event"))->toEqual(Some(JSON.String("ItemCreated")))->ignore
193
+ expect(obj->Dict.get("recordedAt"))->toEqual(Some(JSON.String("2026-05-13T10:00:00.000Z")))
194
+ },
195
+ )
200
196
  })