@reventlessdev/reventless-core 3.0.0-alpha.260 → 3.0.0-alpha.262

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 (208) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/package.json +10 -10
  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/adapter/Secrets/Secrets_Node_Backend.res +60 -0
  20. package/src/adapter/Secrets/Secrets_Node_Backend.res.mjs +69 -0
  21. package/src/admin/Platform_Admin.res +17 -6
  22. package/src/admin/Platform_AdminApi.res +14 -9
  23. package/src/admin/Platform_Admin_Callback.res +1 -1
  24. package/src/admin/Platform_BakedManifest.res +34 -34
  25. package/src/admin/Platform_ComponentDefinitionsApi.res +9 -21
  26. package/src/admin/Platform_PluginStructuresApi.res +15 -12
  27. package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res +1 -1
  28. package/src/components/Aggregate/Aggregate.res +13 -12
  29. package/src/components/Aggregate/Aggregate_Builder.res +22 -21
  30. package/src/components/Aggregate/Aggregate_Callback.res +91 -86
  31. package/src/components/Api/ApiNoApiHelpers.res +1 -3
  32. package/src/components/Api/Api_Ids.res +0 -1
  33. package/src/components/Api/Api_Naming.res +41 -18
  34. package/src/components/Api/GraphQL_FragmentGenerator.res +38 -51
  35. package/src/components/Api/GraphQL_SchemaInspector.res +5 -3
  36. package/src/components/Api/GraphQL_Stitcher.res +9 -10
  37. package/src/components/Api/MCP_SchemaGenerator.res +8 -12
  38. package/src/components/Api/OwnerScopeDiagnostics.res +1 -2
  39. package/src/components/Api/QueryDbListQuery.res +13 -6
  40. package/src/components/Api/SchemaType.res +28 -23
  41. package/src/components/Api/SuryToJsonSchema.res +9 -10
  42. package/src/components/AutomationSlice/AutomationSlice.res +4 -4
  43. package/src/components/AutomationSlice/AutomationSlice_Builder.res +15 -14
  44. package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -3
  45. package/src/components/Builder_Helpers.res +12 -23
  46. package/src/components/CommandGenerator/CommandGenerator.res +6 -1
  47. package/src/components/CommandGenerator/CommandGenerator_Builder.res +29 -21
  48. package/src/components/CommandGenerator/CommandGenerator_Callback.res +17 -21
  49. package/src/components/CommandTopic/CommandTopic.res +3 -3
  50. package/src/components/CommandTopic/CommandTopic_Builder.res +10 -10
  51. package/src/components/CommandTopic/CommandTopic_Callback.res +1 -4
  52. package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -2
  53. package/src/components/Counter/Counter_Builder.res +1 -6
  54. package/src/components/Counter/Counter_Callback.res +3 -3
  55. package/src/components/Dcb/Dcb_Builder.res +970 -956
  56. package/src/components/DcbEventLog/DcbEventLog.res +4 -4
  57. package/src/components/DcbEventLog/DcbEventLog_Builder.res +0 -1
  58. package/src/components/DcbEventLog/DcbEventLog_Operations.res +17 -18
  59. package/src/components/EventCollector/EventCollector_Builder.res +2 -1
  60. package/src/components/EventLog/EventLog.res +5 -1
  61. package/src/components/EventLog/EventLog_Operations.res +12 -8
  62. package/src/components/EventMapper/EventMapper.res +8 -10
  63. package/src/components/EventMapper/EventMapper_Builder.res +4 -1
  64. package/src/components/EventMapper/EventMapper_Callback.res +46 -31
  65. package/src/components/EventTopic/EventTopic.res +3 -3
  66. package/src/components/EventTopic/EventTopic_Builder.res +1 -1
  67. package/src/components/Extension/Extension.res +4 -1
  68. package/src/components/Extension/Extension_Operations.res +11 -3
  69. package/src/components/ExtensionPoint/ExtensionPoint.res +3 -3
  70. package/src/components/ExtensionPoint/ExtensionPoint_Builder.res +9 -10
  71. package/src/components/ExtensionPoint/ExtensionPoint_Callback.res +2 -6
  72. package/src/components/ExtensionPoint/ExtensionPoint_Operations.res +13 -5
  73. package/src/components/Heartbeat/Heartbeat_Callback.res +3 -3
  74. package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +4 -4
  75. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +7 -10
  76. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice.res +4 -4
  77. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +15 -14
  78. package/src/components/QueryDb/QueryDb_Adapter.res +1 -3
  79. package/src/components/QueryDb/QueryDb_Operations.res +10 -10
  80. package/src/components/ReadModel/ReadModel.res +3 -3
  81. package/src/components/ReadModel/ReadModel_Builder.res +1 -2
  82. package/src/components/ReadModel/ReadModel_Callback.res +2 -1
  83. package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +5 -2
  84. package/src/components/StateChangeSlice/StateChangeSlice.res +3 -3
  85. package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +23 -4
  86. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +22 -6
  87. package/src/components/StateTopic.res +1 -3
  88. package/src/components/StateViewSlice/StateViewSlice.res +4 -4
  89. package/src/components/StateViewSlice/StateViewSlice_Builder.res +79 -68
  90. package/src/components/StateViewSlice/StateViewSlice_Callback.res +3 -1
  91. package/src/components/Task/Task.res +3 -3
  92. package/src/components/Task/Task_Adapter.res +4 -1
  93. package/src/components/Task/Task_Builder.res +24 -21
  94. package/src/plugin/api/PluginBaseFragment.res +9 -3
  95. package/src/plugin/component/Capability_Inference.res +8 -10
  96. package/src/plugin/component/Plugin.res +6 -6
  97. package/src/plugin/component/Plugin_Builder.res +54 -50
  98. package/src/plugin/component/Plugin_Callback.res +10 -6
  99. package/src/plugin/component/Plugin_Helpers.res +264 -230
  100. package/src/plugin/component/Plugin_Structure.res +370 -324
  101. package/src/plugin/component/Plugin_SubscriptionSchema.res +8 -6
  102. package/src/plugin/component/SchemaWalker.res +11 -12
  103. package/src/plugin/connect/PluginConnectExtension_Mapping.res +45 -50
  104. package/src/plugin/lifecycle/PluginBehavior.res +3 -4
  105. package/src/plugin/lifecycle/PluginSpec.res +1 -2
  106. package/src/plugin/lifecycle/PluginsProjection.res +16 -5
  107. package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
  108. package/src/util/CommandPublisher.res +9 -2
  109. package/src/util/CsvStream.res +3 -3
  110. package/src/util/Interstack.res +7 -15
  111. package/src/util/Interstack.resi +3 -3
  112. package/src/util/LogFormat.res +5 -9
  113. package/src/util/Logger.res +1 -2
  114. package/src/util/Lru.res +2 -1
  115. package/src/util/Lru.resi +2 -3
  116. package/src/util/ScheduleOps.res +24 -27
  117. package/src/util/StorePrefixCollision.res +6 -9
  118. package/src/util/Util_Promise.res +4 -1
  119. package/src/util/Util_Promise.res.mjs +1 -1
  120. package/tests/IdentityTest.res +166 -113
  121. package/tests/ResourceAttributionTest.res +28 -25
  122. package/tests/Secrets_Node_BackendTest.res +141 -0
  123. package/tests/Secrets_Node_BackendTest.res.mjs +152 -0
  124. package/tests/StorePrefixCollisionTest.res +2 -1
  125. package/tests/TaskBucketNamingTest.res +3 -2
  126. package/tests/adapter/EventLogProvisioningTest.res +51 -48
  127. package/tests/adapter/MonitoringTest.res +81 -38
  128. package/tests/adapter/QueryInterceptionTest.res +2 -2
  129. package/tests/adapter/RefusalVocabularyTest.res +31 -23
  130. package/tests/adapter/RuntimeExtensionHookReachTest.res +9 -8
  131. package/tests/adapter/RuntimeExtensionTest.res +5 -3
  132. package/tests/admin/AdminApiSchemaDriftTest.res +22 -16
  133. package/tests/admin/Platform_Admin_StructureTest.res +22 -14
  134. package/tests/admin/Platform_Admin_StructureTest.res.mjs +4 -3
  135. package/tests/admin/Platform_BakedManifestTest.res +31 -35
  136. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +18 -15
  137. package/tests/admin/Platform_PluginStructuresApiTest.res +5 -3
  138. package/tests/admin/Platform_UIFragmentsApiTest.res +6 -3
  139. package/tests/aggregate/AggregateCacheTest.res +10 -12
  140. package/tests/aggregate/AggregateCallbackTest.res +75 -46
  141. package/tests/aggregate/AggregateCausationTest.res +19 -20
  142. package/tests/aggregate/AggregateConflictTest.res +41 -19
  143. package/tests/aggregate/AggregateFixtures.res +15 -5
  144. package/tests/aggregate/AggregateRejectionTest.res +97 -80
  145. package/tests/aggregate/AggregateSnapshotTest.res +44 -16
  146. package/tests/api/Api_IdsTest.res +10 -4
  147. package/tests/api/BrandedMarkerCompositionTest.res +48 -36
  148. package/tests/api/GeolocationSemanticTest.res +3 -3
  149. package/tests/api/GraphQL_FragmentGeneratorTest.res +66 -54
  150. package/tests/api/OwnerScopeDiagnosticsTest.res +31 -27
  151. package/tests/api/StorageRefFieldsTest.res +30 -18
  152. package/tests/api/SuryToJsonSchemaTest.res +1315 -1014
  153. package/tests/api/TaggedUnionSchemaTest.res +17 -24
  154. package/tests/commandgenerator/CommandGeneratorCallbackTest.res +235 -179
  155. package/tests/commandgenerator/CommandGeneratorFixtures.res +8 -13
  156. package/tests/commandgenerator/OwnerStampingTest.res +91 -76
  157. package/tests/commandtopic/CommandOutcomeHookTest.res +198 -155
  158. package/tests/commandtopic/CommandTopicCallbackFixtures.res +5 -4
  159. package/tests/commandtopic/CommandTopicCallbackTest.res +61 -43
  160. package/tests/commandtopic/CommandTopicHelpersRejectionTest.res +70 -55
  161. package/tests/counter/CounterCallbackTest.res +40 -34
  162. package/tests/counter/CounterFixtures.res +4 -4
  163. package/tests/dcb/DcbDecodeTest.res +115 -84
  164. package/tests/dcb/DcbEventLogOperationsTest.res +194 -143
  165. package/tests/dcb/DcbEventLogSchemaTest.res +13 -13
  166. package/tests/dcb/DcbFixtures.res +4 -5
  167. package/tests/dcb/DcbScopeInferenceTest.res +173 -114
  168. package/tests/dcb/DcbStateChangeSliceTest.res +391 -262
  169. package/tests/dcb/DcbTagTest.res +22 -11
  170. package/tests/dcb/DcbValidationTest.res +288 -218
  171. package/tests/eventlog/EventLogFixtures.res +4 -4
  172. package/tests/eventlog/EventLogOperationsTest.res +102 -67
  173. package/tests/eventlog/EventLogRetryTest.res +104 -65
  174. package/tests/eventmapper/EventMapperCallbackTest.res +118 -83
  175. package/tests/eventmapper/EventMapperFixtures.res +15 -23
  176. package/tests/eventmapper/EventMapperTest.res +54 -38
  177. package/tests/eventtopic/EventTopicFixtures.res +3 -4
  178. package/tests/eventtopic/EventTopicOperationsTest.res +41 -29
  179. package/tests/extensionpoint/ExtensionPointCallbackTest.res +58 -45
  180. package/tests/extensionpoint/ExtensionPointFixtures.res +16 -20
  181. package/tests/extensionpoint/ExtensionPointOperationsTest.res +70 -49
  182. package/tests/extensionpoint/PluginDisconnectScheduleTest.res +2 -1
  183. package/tests/logger/LogFormatTest.res +104 -82
  184. package/tests/logger/LoggerLazyTest.res +21 -12
  185. package/tests/message/MessageTest.res +96 -93
  186. package/tests/message/MetaEnvelopeTest.res +26 -30
  187. package/tests/plugin/HeartbeatDisconnectGraceTest.res +31 -25
  188. package/tests/plugin/ManifestVisibilityTest.res +20 -12
  189. package/tests/plugin/PluginDefinitionScalars.res +1 -2
  190. package/tests/plugin/PluginLifecycleCorpusTest.res +15 -12
  191. package/tests/plugin/PluginSpecExperiment.res +0 -1
  192. package/tests/plugin/PluginStructureTest.res +1529 -1045
  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 +4 -4
  197. package/tests/plugin/StateView/PsCustomersView.res +4 -4
  198. package/tests/plugin/StateView/PsShipmentsView.res +3 -2
  199. package/tests/projection/LifecycleTrailTest.res +1 -4
  200. package/tests/projection/ProjectionOptimizeTest.res +13 -9
  201. package/tests/querydb/QueryDbFixtures.res +0 -1
  202. package/tests/querydb/QueryDbOperationsTest.res +99 -64
  203. package/tests/querydb/TaggedUnionStampTest.res +10 -8
  204. package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res +50 -35
  205. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +4 -10
  206. package/tests/util/CommandPublisherTest.res +0 -1
  207. package/tests/util/CsvStreamTest.res +3 -3
  208. package/tests/util/LruTest.res +84 -56
@@ -5,7 +5,9 @@
5
5
  let log = Logger.fromEnv()
6
6
 
7
7
  // Set by `check:lifecycle`, and by nothing else. See the note where it is read.
8
- @val external _declaredTransitionsOnly: option<string> = "process.env.REVENTLESS_DECLARED_TRANSITIONS_ONLY"
8
+ @val
9
+ external _declaredTransitionsOnly: option<string> =
10
+ "process.env.REVENTLESS_DECLARED_TRANSITIONS_ONLY"
9
11
  let declaredTransitionsOnly = _declaredTransitionsOnly->Option.isSome
10
12
 
11
13
  // Whether a field can name a record, stated over `SchemaType`'s IR so every
@@ -26,8 +28,11 @@ let rec isLabelShape = (t: SchemaType.schemaType): bool =>
26
28
  // and *exactly*: `customerName` holds a customer's name, not this record's.
27
29
  let conventionalLabelNames = ["name", "title", "label", "displayname"]
28
30
 
29
- let shapeOfField = (~entityName: string, ~name: string, schema: S.t<unknown>): SchemaType.schemaType =>
30
- SchemaType.fromSury(~parentName=entityName, ~fieldName=name, schema)
31
+ let shapeOfField = (
32
+ ~entityName: string,
33
+ ~name: string,
34
+ schema: S.t<unknown>,
35
+ ): SchemaType.schemaType => SchemaType.fromSury(~parentName=entityName, ~fieldName=name, schema)
31
36
 
32
37
  // The field holding the entity's lifecycle, for filtering a per-row command
33
38
  // menu. One rule, stated in `Reventless.Lifecycle` — the projection machinery
@@ -36,9 +41,9 @@ let lifecycleFieldFromStateSchema = Reventless.Lifecycle.fieldName
36
41
 
37
42
  // The field whose truth withdraws a row from ordinary reads. Annotation-only —
38
43
  // no convention rung, since guessing wrong here makes rows vanish.
39
- let retiredFromStateSchema = (
40
- stateSchema: S.t<unknown>,
41
- ): option<Reventless.StateAnnotations.retiredSpec> =>
44
+ let retiredFromStateSchema = (stateSchema: S.t<unknown>): option<
45
+ Reventless.StateAnnotations.retiredSpec,
46
+ > =>
42
47
  switch Reventless.StateAnnotations.getSpec(stateSchema) {
43
48
  | Some(spec) => spec.retired
44
49
  | None => None
@@ -112,11 +117,10 @@ let checkRetiredValue = (~entityName: string, stateSchema: S.t<unknown>): retire
112
117
  Checked(
113
118
  values
114
119
  ->Array.filter(v => !(declared->Array.includes(v)))
115
- ->Array.map(
116
- v =>
117
- `${entityName}: @retired(${v}) names a state "${field}" does not declare — known values: ${declared->Array.join(
118
- ", ",
119
- )}.`,
120
+ ->Array.map(v =>
121
+ `${entityName}: @retired(${v}) names a state "${field}" does not declare — known values: ${declared->Array.join(
122
+ ", ",
123
+ )}.`
120
124
  ),
121
125
  )
122
126
  }
@@ -149,10 +153,9 @@ let reportRetiredStates = (
149
153
  // The states a record's lifecycle field can hold — the field a command's
150
154
  // declared edge is written in terms of. `None`: no lifecycle; `Some([])`: one
151
155
  // with no cases.
152
- let lifecycleStatesFromStateSchema = (
153
- ~entityName: string,
154
- stateSchema: S.t<unknown>,
155
- ): option<array<string>> =>
156
+ let lifecycleStatesFromStateSchema = (~entityName: string, stateSchema: S.t<unknown>): option<
157
+ array<string>,
158
+ > =>
156
159
  lifecycleFieldFromStateSchema(stateSchema)->Option.flatMap(field =>
157
160
  switch stateSchema {
158
161
  | Object({properties}) =>
@@ -214,9 +217,10 @@ let translationTableDrift = (
214
217
  ~observed: array<(string, string)>,
215
218
  ~followed: array<string>,
216
219
  ): (array<string>, array<string>) => {
217
- let declaredEdges = declared->Array.reduce([], (acc, {name, fromEventTypes}) =>
218
- Array.concat(acc, fromEventTypes->Array.map(src => (src, name)))
219
- )
220
+ let declaredEdges =
221
+ declared->Array.reduce([], (acc, {name, fromEventTypes}) =>
222
+ Array.concat(acc, fromEventTypes->Array.map(src => (src, name)))
223
+ )
220
224
  let has = (edges, (src, pub)) => edges->Array.some(((s, p)) => s == src && p == pub)
221
225
 
222
226
  let failures =
@@ -370,13 +374,14 @@ let checkDeclaredTransitions = (
370
374
  },
371
375
  )
372
376
  if Array.length(declared) > 0 {
373
- let known =
374
- w.linkedViews->Array.reduce([], (acc, view) =>
377
+ let known = w.linkedViews->Array.reduce(
378
+ [],
379
+ (acc, view) =>
375
380
  switch lifecycleStatesByView->Dict.get(view) {
376
381
  | Some(states) => Array.concat(acc, states)
377
382
  | None => acc
378
- }
379
- )
383
+ },
384
+ )
380
385
  if Array.length(known) == 0 {
381
386
  unvalidated := unvalidated.contents + 1
382
387
  } else {
@@ -385,21 +390,23 @@ let checkDeclaredTransitions = (
385
390
  // longer declares means the committed model went stale, and stopping is
386
391
  // right — published, it would offer the command on no row at all.
387
392
  let hint = switch cmd.allowedStatesSource {
388
- | Some("derived") =>
389
- " This edge came from the component's own scenarios, so the committed lifecycle model is stale."
393
+ | Some(
394
+ "derived",
395
+ ) => " This edge came from the component's own scenarios, so the committed lifecycle model is stale."
390
396
  | _ => ""
391
397
  }
392
398
  declared
393
399
  ->Array.filter(state => !(known->Array.includes(state)))
394
- ->Array.forEach(state =>
395
- failures
396
- ->Array.push(
397
- `${w.name}.${cmd.name} declares state "${state}", which none of its ` ++
398
- `linked views declare — ${w.linkedViews->Array.join(
399
- ", ",
400
- )} know ${known->Array.join(", ")}.${hint}`,
401
- )
402
- ->ignore
400
+ ->Array.forEach(
401
+ state =>
402
+ failures
403
+ ->Array.push(
404
+ `${w.name}.${cmd.name} declares state "${state}", which none of its ` ++
405
+ `linked views declare — ${w.linkedViews->Array.join(
406
+ ", ",
407
+ )} know ${known->Array.join(", ")}.${hint}`,
408
+ )
409
+ ->ignore,
403
410
  )
404
411
  }
405
412
  }
@@ -412,15 +419,13 @@ let checkDeclaredTransitions = (
412
419
  if unvalidated.contents > 0 {
413
420
  log.warn(
414
421
  ~comp="Plugin_Structure",
415
- `${pluginName}: ${unvalidated.contents->Int.toString} command(s) declare a transition ` ++
416
- `but no linked view declares a lifecycle to check it against.`,
422
+ `${pluginName}: ${unvalidated.contents->Int.toString} command(s) declare a transition ` ++ `but no linked view declares a lifecycle to check it against.`,
417
423
  )
418
424
  }
419
425
 
420
426
  if Array.length(failures) > 0 {
421
427
  JsError.throwWithMessage(
422
- `${pluginName}: a transition names states that do not exist.\n` ++
423
- failures->Array.join("\n"),
428
+ `${pluginName}: a transition names states that do not exist.\n` ++ failures->Array.join("\n"),
424
429
  )
425
430
  }
426
431
  }
@@ -439,11 +444,12 @@ let lifecycleTopologyFindings = (
439
444
  ->Array.forEach(((view, states)) => {
440
445
  // Arrival only, so a creating command (a target and no from-set) counts
441
446
  // towards reachability like any other edge.
442
- let reachable = writables->Array.reduce([], (acc, w) =>
443
- w.linkedViews->Array.includes(view)
444
- ? Array.concat(acc, w.commands->Array.filterMap(cmd => cmd.targetState))
445
- : acc
446
- )
447
+ let reachable =
448
+ writables->Array.reduce([], (acc, w) =>
449
+ w.linkedViews->Array.includes(view)
450
+ ? Array.concat(acc, w.commands->Array.filterMap(cmd => cmd.targetState))
451
+ : acc
452
+ )
447
453
  if Array.length(reachable) > 0 {
448
454
  // Rows start in the first declared state, so nothing pointing at it is fine.
449
455
  let initial = states->Array.get(0)
@@ -453,8 +459,7 @@ let lifecycleTopologyFindings = (
453
459
  findings
454
460
  ->Array.push((
455
461
  view,
456
- `no command declares a transition INTO "${state}" — it is unreachable ` ++
457
- `unless something outside this plugin's declarations puts a row there.`,
462
+ `no command declares a transition INTO "${state}" — it is unreachable ` ++ `unless something outside this plugin's declarations puts a row there.`,
458
463
  ))
459
464
  ->ignore
460
465
  }
@@ -551,9 +556,7 @@ let labelFieldsFromStateSchema = (
551
556
  // A variant's cross-entity references, shared by the command and event walks.
552
557
  // `getFieldTarget`, not `getTarget`: an `array<string>` field declares it on the
553
558
  // element schema.
554
- let extractReferences = (properties: dict<S.t<unknown>>): array<
555
- Reventless.Plugin.fieldReference,
556
- > =>
559
+ let extractReferences = (properties: dict<S.t<unknown>>): array<Reventless.Plugin.fieldReference> =>
557
560
  properties
558
561
  ->Dict.toArray
559
562
  ->Array.flatMap(((fieldName, fieldSchema)) =>
@@ -565,25 +568,26 @@ let extractReferences = (properties: dict<S.t<unknown>>): array<
565
568
  Reventless.Reference.collectFieldTargets(fieldName, fieldSchema)->Array.map(((
566
569
  path,
567
570
  target,
568
- )) => (
569
- {
570
- Reventless.Plugin.fieldName: path,
571
- entity: target.entity,
572
- plugin: target.plugin,
573
- }: Reventless.Plugin.fieldReference
574
- ))
571
+ )): Reventless.Plugin.fieldReference => {
572
+ Reventless.Plugin.fieldName: path,
573
+ entity: target.entity,
574
+ plugin: target.plugin,
575
+ })
575
576
  )
576
577
 
577
578
  let toEventDef = (v: S.t<unknown>): option<Reventless.Plugin.eventDef> => {
578
579
  let mkDef = (~variantName, ~properties) => {
579
580
  let references = extractReferences(properties)
580
- ({
581
- Reventless.Plugin.name: variantName,
582
- // Derived for the same reason as `commandDef.schema`: an event's field
583
- // markers are the write side's half of the same vocabulary.
584
- schema: v->SuryToJsonSchema.deriveObjectSchema->JSON.stringify,
585
- references,
586
- }: Reventless.Plugin.eventDef)
581
+
582
+ (
583
+ {
584
+ Reventless.Plugin.name: variantName,
585
+ // Derived for the same reason as `commandDef.schema`: an event's field
586
+ // markers are the write side's half of the same vocabulary.
587
+ schema: v->SuryToJsonSchema.deriveObjectSchema->JSON.stringify,
588
+ references,
589
+ }: Reventless.Plugin.eventDef
590
+ )
587
591
  }
588
592
  switch v {
589
593
  | Object({properties}) =>
@@ -611,9 +615,11 @@ let extractEventDefs = (eventSchema: S.t<unknown>): array<Reventless.Plugin.even
611
615
  // Errors walk identically to events; only the def type differs, so a consumer
612
616
  // can tell a refusal from a fact. Shared rather than copied, so they cannot drift.
613
617
  let extractErrorDefs = (errorSchema: S.t<unknown>): array<Reventless.Plugin.errorDef> =>
614
- extractEventDefs(errorSchema)->Array.map(({name, schema, references}) => (
615
- {name, schema, references}: Reventless.Plugin.errorDef
616
- ))
618
+ extractEventDefs(errorSchema)->Array.map(({
619
+ name,
620
+ schema,
621
+ references,
622
+ }): Reventless.Plugin.errorDef => {name, schema, references})
617
623
 
618
624
  // Module-level like the event walk: the synthetic Platform_Admin structure has no
619
625
  // `module(Aggregate.T)` to hand `make`, and a second copy of this walk is what let
@@ -643,8 +649,7 @@ let commandLevelAndId = (~isAggregate, ~variantName, properties: dict<S.t<unknow
643
649
  ->Dict.toArray
644
650
  ->Array.filter(((fieldName, fieldSchema)) =>
645
651
  fieldName != "TAG" &&
646
- (Reventless.DcbTag.isTagged(fieldSchema) ||
647
- Reventless.DcbTag.isTaggedArray(fieldSchema))
652
+ (Reventless.DcbTag.isTagged(fieldSchema) || Reventless.DcbTag.isTaggedArray(fieldSchema))
648
653
  )
649
654
  let taggedField =
650
655
  taggedFields
@@ -727,10 +732,12 @@ let toCommandDef = (
727
732
  // Evaluated against a synthetic value per constructor, the same shape the
728
733
  // resolver builds at call time: a payload-bearing variant compiles to
729
734
  // `{TAG, ...}`, a payload-less one to a bare string.
730
- let syntheticCommand: unknown =
731
- Reventless.DcbTag.isVariantPayloadBearing(parentSchema, variantName)
732
- ? {"TAG": variantName}->Obj.magic
733
- : variantName->Obj.magic
735
+ let syntheticCommand: unknown = Reventless.DcbTag.isVariantPayloadBearing(
736
+ parentSchema,
737
+ variantName,
738
+ )
739
+ ? {"TAG": variantName}->Obj.magic
740
+ : variantName->Obj.magic
734
741
  // The spec's own switch, which is exhaustive — so it also speaks for a
735
742
  // constructor the host did not declare but spliced from a trait.
736
743
  //
@@ -814,27 +821,30 @@ let toCommandDef = (
814
821
  } else {
815
822
  jsonSchema
816
823
  }
817
- ({
818
- Reventless.Plugin.name: variantName,
819
- // The derived schema, not sury's raw one, which drops every `x-reventless-*`
820
- // marker the PPX put on the fields. Also carries `x-reventless-graphql-type`.
821
- schema: annotatedSchema->JSON.stringify,
822
- level,
823
- aggregateIdField,
824
- // Empty for a `@noApi` variant: `mutationFieldFor` would resolve it to a
825
- // sibling's field, which reads as callable. Exposed variants are unchanged.
826
- mutationField,
827
- references,
828
- allowedStates,
829
- targetState,
830
- allowedStatesSource: ?allowedStatesSource,
831
- apiExposed: Some(apiExposed),
832
- requiredAccess,
833
- // Resolved from this constructor's own properties, not the union's: two
834
- // commands in one slice can disagree about whether they record an owner,
835
- // and the write path stamps per constructor for the same reason.
836
- ownerField: Reventless.Owner.fieldNamesOfProperties(properties)->Array.get(0),
837
- }: Reventless.Plugin.commandDef)
824
+
825
+ (
826
+ {
827
+ Reventless.Plugin.name: variantName,
828
+ // The derived schema, not sury's raw one, which drops every `x-reventless-*`
829
+ // marker the PPX put on the fields. Also carries `x-reventless-graphql-type`.
830
+ schema: annotatedSchema->JSON.stringify,
831
+ level,
832
+ aggregateIdField,
833
+ // Empty for a `@noApi` variant: `mutationFieldFor` would resolve it to a
834
+ // sibling's field, which reads as callable. Exposed variants are unchanged.
835
+ mutationField,
836
+ references,
837
+ allowedStates,
838
+ targetState,
839
+ ?allowedStatesSource,
840
+ apiExposed: Some(apiExposed),
841
+ requiredAccess,
842
+ // Resolved from this constructor's own properties, not the union's: two
843
+ // commands in one slice can disagree about whether they record an owner,
844
+ // and the write path stamps per constructor for the same reason.
845
+ ownerField: Reventless.Owner.fieldNamesOfProperties(properties)->Array.get(0),
846
+ }: Reventless.Plugin.commandDef
847
+ )
838
848
  }
839
849
  switch v {
840
850
  | Object({properties}) =>
@@ -920,7 +930,7 @@ let queryableDefFromSpec = (
920
930
  // the generated filter/order-by is built from cannot disagree.
921
931
  let keyField = GraphQL_FragmentGenerator.resolveKeyField(~entityName=name, stateSchema)
922
932
  {
923
- Reventless.Plugin.name: name,
933
+ Reventless.Plugin.name,
924
934
  queryField: qf.listFieldName,
925
935
  schema: stateSchema->SuryToJsonSchema.deriveObjectSchema->JSON.stringify,
926
936
  consumedEventTypes,
@@ -946,7 +956,9 @@ let make = (
946
956
  type api role,
947
957
  ~name: string,
948
958
  ~aggregates: array<module(ReventlessInfra.Aggregate.T with type api = api)>=[],
949
- ~readModels: array<module(ReventlessInfra.ReadModel.T with type api = api and type role = role)>=[],
959
+ ~readModels: array<
960
+ module(ReventlessInfra.ReadModel.T with type api = api and type role = role),
961
+ >=[],
950
962
  ~stateViewSlices: array<module(ReventlessInfra.StateViewSlice.T)>=[],
951
963
  ~stateChangeSlices: array<module(ReventlessInfra.StateChangeSlice.T)>=[],
952
964
  ~automationSlices: array<module(ReventlessInfra.AutomationSlice.T)>=[],
@@ -1011,8 +1023,7 @@ let make = (
1011
1023
 
1012
1024
  // ── Cross-reference helpers ────────────────────────────────────────────────
1013
1025
 
1014
- let intersects = (a: array<string>, b: array<string>) =>
1015
- a->Array.some(x => b->Array.includes(x))
1026
+ let intersects = (a: array<string>, b: array<string>) => a->Array.some(x => b->Array.includes(x))
1016
1027
 
1017
1028
  let linkedSvsFor = (producedTypes: array<string>): array<string> =>
1018
1029
  svsConsumed->Array.filterMap(((viewName, consumed)) =>
@@ -1055,7 +1066,11 @@ let make = (
1055
1066
  ->Array.filter(((_, overlap, _)) => overlap > 0)
1056
1067
  ->Array.toSorted(((_, a, aTotal), (_, b, bTotal)) => {
1057
1068
  let cmp = Int.compare(b, a)
1058
- if cmp != Ordering.equal { cmp } else { Int.compare(bTotal, aTotal) }
1069
+ if cmp != Ordering.equal {
1070
+ cmp
1071
+ } else {
1072
+ Int.compare(bTotal, aTotal)
1073
+ }
1059
1074
  })
1060
1075
  switch scored->Array.length {
1061
1076
  | 0 => None
@@ -1168,18 +1183,17 @@ let make = (
1168
1183
  dcbCommandOwners
1169
1184
  ->Dict.toArray
1170
1185
  ->Array.filter(((_, owners)) => owners->Array.length > 1)
1171
- ->Array.toSorted((((a, _)), ((b, _))) => String.compare(a, b))
1186
+ ->Array.toSorted(((a, _), (b, _)) => String.compare(a, b))
1172
1187
  if clashes->Array.length > 0 {
1173
1188
  JsError.throwWithMessage(
1174
1189
  `${name}: ` ++
1175
1190
  clashes
1176
- ->Array.map((((cmd, owners))) =>
1191
+ ->Array.map(((cmd, owners)) =>
1177
1192
  `${owners->Array.join(" and ")} both declare the command "${cmd}"`
1178
1193
  )
1179
1194
  ->Array.join("; ") ++
1180
1195
  `.\n A DCB plugin routes a command by its bare name, so one of these would never run. ` ++
1181
- `Qualify them — the shipped traits' emitters prefix every name with the host they are ` ++
1182
- `grafted onto, for exactly this reason.`,
1196
+ `Qualify them — the shipped traits' emitters prefix every name with the host they are ` ++ `grafted onto, for exactly this reason.`,
1183
1197
  )
1184
1198
  }
1185
1199
 
@@ -1247,12 +1261,12 @@ let make = (
1247
1261
  let requiredCapabilities =
1248
1262
  outboundTranslationSlices
1249
1263
  ->Array.flatMap((module(OTS: ReventlessInfra.OutboundTranslationSlice.T)) =>
1250
- OTS.Spec.capabilityNeeds->Array.map(need => (
1251
- {
1252
- Reventless.Plugin.capability: Reventless.CapabilityNeed.toString(need),
1253
- component: OTS.Spec.name,
1254
- }: Reventless.Plugin.requiredCapabilityDeclaration
1255
- ))
1264
+ OTS.Spec.capabilityNeeds->Array.map((
1265
+ need
1266
+ ): Reventless.Plugin.requiredCapabilityDeclaration => {
1267
+ Reventless.Plugin.capability: Reventless.CapabilityNeed.toString(need),
1268
+ component: OTS.Spec.name,
1269
+ })
1256
1270
  )
1257
1271
  ->Array.toSorted((a, b) =>
1258
1272
  a.capability == b.capability
@@ -1272,14 +1286,12 @@ let make = (
1272
1286
  // hand-typed string this whole mechanism exists to avoid. Sorted and
1273
1287
  // deduplicated for the same reason the two declarations above are.
1274
1288
  let traitDeclarations = {
1275
- let entry = (~component, decl: Reventless.Trait.t) => (
1276
- {
1277
- Reventless.Plugin.trait: decl.trait,
1278
- version: decl.version,
1279
- posture: Reventless.Trait.postureToString(decl.posture),
1280
- component,
1281
- }: Reventless.Plugin.traitDeclaration
1282
- )
1289
+ let entry = (~component, decl: Reventless.Trait.t): Reventless.Plugin.traitDeclaration => {
1290
+ Reventless.Plugin.trait: decl.trait,
1291
+ version: decl.version,
1292
+ posture: Reventless.Trait.postureToString(decl.posture),
1293
+ component,
1294
+ }
1283
1295
  [
1284
1296
  aggregates->Array.flatMap((module(A: ReventlessInfra.Aggregate.T with type api = api)) =>
1285
1297
  A.Spec.traits->Array.map(entry(~component=A.Spec.name, ...))
@@ -1293,7 +1305,9 @@ let make = (
1293
1305
  ]
1294
1306
  ->Array.flat
1295
1307
  ->Array.toSorted((a, b) =>
1296
- a.trait == b.trait ? String.compare(a.component, b.component) : String.compare(a.trait, b.trait)
1308
+ a.trait == b.trait
1309
+ ? String.compare(a.component, b.component)
1310
+ : String.compare(a.trait, b.trait)
1297
1311
  )
1298
1312
  ->Array.reduce([], (acc, d) =>
1299
1313
  switch acc->Array.last {
@@ -1345,26 +1359,23 @@ let make = (
1345
1359
  ->GraphQL_FragmentGenerator.keyFieldGapMessage
1346
1360
  ->Option.forEach(why => log.warn(~comp="Plugin_Structure", `${name}/${entityName} ${why}`))
1347
1361
 
1348
- let readModelDefs =
1349
- readModels
1350
- ->Array.map((
1351
- module(R: ReventlessInfra.ReadModel.T with type api = api and type role = role),
1352
- ) => {
1353
- let qf = Api_Naming.queryFieldNamesForReadModel(~plugin=name, ~name=R.Spec.name)
1354
- let stateSchema = R.Spec.stateSchema->S.castToUnknown
1355
- let label = labelFieldsFromStateSchema(~entityName=R.Spec.name, stateSchema)
1356
- // The same call the capability deriver makes, so the published key and the
1357
- // key the generated filter/order-by is built from cannot disagree.
1358
- let keyField = GraphQL_FragmentGenerator.resolveKeyField(
1359
- ~entityName=R.Spec.name,
1360
- stateSchema,
1361
- )
1362
- // Qualified to the plugin's event ids so they match the producers' nodes.
1363
- let consumed = qualify(~prefix=name, R.consumedEventNames)
1364
- recordRetired(~entityName=R.Spec.name, stateSchema)
1365
- recordLifecycle(~entityName=R.Spec.name, stateSchema)
1366
- recordKeyField(~entityName=R.Spec.name, stateSchema)
1367
- ({
1362
+ let readModelDefs = readModels->Array.map((
1363
+ module(R: ReventlessInfra.ReadModel.T with type api = api and type role = role),
1364
+ ) => {
1365
+ let qf = Api_Naming.queryFieldNamesForReadModel(~plugin=name, ~name=R.Spec.name)
1366
+ let stateSchema = R.Spec.stateSchema->S.castToUnknown
1367
+ let label = labelFieldsFromStateSchema(~entityName=R.Spec.name, stateSchema)
1368
+ // The same call the capability deriver makes, so the published key and the
1369
+ // key the generated filter/order-by is built from cannot disagree.
1370
+ let keyField = GraphQL_FragmentGenerator.resolveKeyField(~entityName=R.Spec.name, stateSchema)
1371
+ // Qualified to the plugin's event ids so they match the producers' nodes.
1372
+ let consumed = qualify(~prefix=name, R.consumedEventNames)
1373
+ recordRetired(~entityName=R.Spec.name, stateSchema)
1374
+ recordLifecycle(~entityName=R.Spec.name, stateSchema)
1375
+ recordKeyField(~entityName=R.Spec.name, stateSchema)
1376
+
1377
+ (
1378
+ {
1368
1379
  Reventless.Plugin.name: R.Spec.name,
1369
1380
  queryField: qf.listFieldName,
1370
1381
  schema: stateSchema->SuryToJsonSchema.deriveObjectSchema->JSON.stringify,
@@ -1389,8 +1400,9 @@ let make = (
1389
1400
  idField: keyField->Option.map(((f, _)) => f),
1390
1401
  idFieldSource: keyField->Option.map(((_, rung)) => rung),
1391
1402
  requiredAccess: accessKeysFor(R.Spec.authorization),
1392
- }: Reventless.Plugin.queryableDef)
1393
- })
1403
+ }: Reventless.Plugin.queryableDef
1404
+ )
1405
+ })
1394
1406
 
1395
1407
  let stateViewDefs =
1396
1408
  stateViewSlices->Array.mapWithIndex((module(SVS: ReventlessInfra.StateViewSlice.T), i) => {
@@ -1405,27 +1417,30 @@ let make = (
1405
1417
  recordRetired(~entityName=SVS.Spec.name, stateSchema)
1406
1418
  recordLifecycle(~entityName=SVS.Spec.name, stateSchema)
1407
1419
  recordKeyField(~entityName=SVS.Spec.name, stateSchema)
1408
- ({
1409
- Reventless.Plugin.name: SVS.Spec.name,
1410
- queryField: qf.listFieldName,
1411
- schema: stateSchema->SuryToJsonSchema.deriveObjectSchema->JSON.stringify,
1412
- consumedEventTypes: consumed,
1413
- linkedWriteSide: linkedWriteSideFor(consumed),
1414
- labelField: label.field,
1415
- searchableFields: label.searchableFields,
1416
- labelFieldSource: Some(labelFieldSourceToString(label.source)),
1417
- lifecycleField: lifecycleFieldFromStateSchema(stateSchema),
1418
- ownerField: Reventless.Owner.fieldNames(stateSchema)->Array.get(0),
1419
- retiredField: retiredFieldFromStateSchema(stateSchema),
1420
- retiredValues: retiredValuesFromStateSchema(stateSchema),
1421
- namedWhenRetired: Some(namedWhenRetiredFromStateSchema(stateSchema)),
1422
- visibility: visibilityTag(SVS.Spec.visibility),
1423
- chapter: chapterOf(SVS.Spec.name),
1424
- singleQueryField: Some(qf.singleFieldName),
1425
- idField: keyField->Option.map(((f, _)) => f),
1426
- idFieldSource: keyField->Option.map(((_, rung)) => rung),
1427
- requiredAccess: accessKeysFor(SVS.Spec.authorization),
1428
- }: Reventless.Plugin.queryableDef)
1420
+
1421
+ (
1422
+ {
1423
+ Reventless.Plugin.name: SVS.Spec.name,
1424
+ queryField: qf.listFieldName,
1425
+ schema: stateSchema->SuryToJsonSchema.deriveObjectSchema->JSON.stringify,
1426
+ consumedEventTypes: consumed,
1427
+ linkedWriteSide: linkedWriteSideFor(consumed),
1428
+ labelField: label.field,
1429
+ searchableFields: label.searchableFields,
1430
+ labelFieldSource: Some(labelFieldSourceToString(label.source)),
1431
+ lifecycleField: lifecycleFieldFromStateSchema(stateSchema),
1432
+ ownerField: Reventless.Owner.fieldNames(stateSchema)->Array.get(0),
1433
+ retiredField: retiredFieldFromStateSchema(stateSchema),
1434
+ retiredValues: retiredValuesFromStateSchema(stateSchema),
1435
+ namedWhenRetired: Some(namedWhenRetiredFromStateSchema(stateSchema)),
1436
+ visibility: visibilityTag(SVS.Spec.visibility),
1437
+ chapter: chapterOf(SVS.Spec.name),
1438
+ singleQueryField: Some(qf.singleFieldName),
1439
+ idField: keyField->Option.map(((f, _)) => f),
1440
+ idFieldSource: keyField->Option.map(((_, rung)) => rung),
1441
+ requiredAccess: accessKeysFor(SVS.Spec.authorization),
1442
+ }: Reventless.Plugin.queryableDef
1443
+ )
1429
1444
  })
1430
1445
 
1431
1446
  // ── Build writable defs ────────────────────────────────────────────────────
@@ -1434,43 +1449,52 @@ let make = (
1434
1449
  stateChangeSlices->Array.mapWithIndex((module(SCS: ReventlessInfra.StateChangeSlice.T), i) => {
1435
1450
  let (_, produced) = scsProduced->Array.getUnsafe(i)
1436
1451
  let (_, consumed) = scsConsumed->Array.getUnsafe(i)
1437
- ({
1438
- Reventless.Plugin.name: SCS.Spec.name,
1439
- commands: extractCommandDefs(
1440
- ~isAggregate=false,
1441
- ~mutationFieldFor=variantName =>
1442
- Api_Naming.sliceMutationFieldFor(
1443
- ~plugin=name,
1444
- ~slice=SCS.Spec.name,
1445
- ~commandSchema=SCS.Spec.commandSchema->S.castToUnknown,
1446
- ~variant=variantName,
1447
- ),
1448
- ~commandAuthorization=SCS.Spec.commandAuthorization->Obj.magic,
1449
- ~commandTransition=SCS.Spec.commandTransition->Obj.magic,
1450
- ~derivedEdgeFor=derivedEdgeFor(~component=SCS.Spec.name, ...),
1451
- SCS.Spec.commandSchema->S.castToUnknown,
1452
- ),
1453
- producedEventTypes: produced,
1454
- consumedEventTypes: consumed,
1455
- linkedViews: linkedSvsFor(produced),
1456
- consistencyRead: consistencyReadFor(consumed),
1457
- events: extractEventDefs(SCS.Spec.eventSchema->S.castToUnknown),
1458
- errors: extractErrorDefs(SCS.Spec.errorSchema->S.castToUnknown),
1459
- chapter: chapterOf(SCS.Spec.name),
1460
- }: Reventless.Plugin.writableDef)
1452
+
1453
+ (
1454
+ {
1455
+ Reventless.Plugin.name: SCS.Spec.name,
1456
+ commands: extractCommandDefs(
1457
+ ~isAggregate=false,
1458
+ ~mutationFieldFor=variantName =>
1459
+ Api_Naming.sliceMutationFieldFor(
1460
+ ~plugin=name,
1461
+ ~slice=SCS.Spec.name,
1462
+ ~commandSchema=SCS.Spec.commandSchema->S.castToUnknown,
1463
+ ~variant=variantName,
1464
+ ),
1465
+ ~commandAuthorization=SCS.Spec.commandAuthorization->Obj.magic,
1466
+ ~commandTransition=SCS.Spec.commandTransition->Obj.magic,
1467
+ ~derivedEdgeFor=derivedEdgeFor(~component=SCS.Spec.name, ...),
1468
+ SCS.Spec.commandSchema->S.castToUnknown,
1469
+ ),
1470
+ producedEventTypes: produced,
1471
+ consumedEventTypes: consumed,
1472
+ linkedViews: linkedSvsFor(produced),
1473
+ consistencyRead: consistencyReadFor(consumed),
1474
+ events: extractEventDefs(SCS.Spec.eventSchema->S.castToUnknown),
1475
+ errors: extractErrorDefs(SCS.Spec.errorSchema->S.castToUnknown),
1476
+ chapter: chapterOf(SCS.Spec.name),
1477
+ }: Reventless.Plugin.writableDef
1478
+ )
1461
1479
  })
1462
1480
 
1463
- let aggregateDefs =
1464
- aggregates->Array.mapWithIndex((
1465
- module(A: ReventlessInfra.Aggregate.T with type api = api),
1466
- i,
1467
- ) => {
1468
- let (_, produced) = aggProduced->Array.getUnsafe(i)
1469
- ({
1481
+ let aggregateDefs = aggregates->Array.mapWithIndex((
1482
+ module(A: ReventlessInfra.Aggregate.T with type api = api),
1483
+ i,
1484
+ ) => {
1485
+ let (_, produced) = aggProduced->Array.getUnsafe(i)
1486
+
1487
+ (
1488
+ {
1470
1489
  Reventless.Plugin.name: A.Spec.name,
1471
1490
  commands: extractCommandDefs(
1472
1491
  ~isAggregate=true,
1473
- ~mutationFieldFor=variantName => Api_Naming.aggregateMutationField(~plugin=name, ~aggregate=A.Spec.name, ~command=variantName),
1492
+ ~mutationFieldFor=variantName =>
1493
+ Api_Naming.aggregateMutationField(
1494
+ ~plugin=name,
1495
+ ~aggregate=A.Spec.name,
1496
+ ~command=variantName,
1497
+ ),
1474
1498
  ~commandAuthorization=A.Spec.commandAuthorization->Obj.magic,
1475
1499
  ~commandTransition=A.Spec.commandTransition->Obj.magic,
1476
1500
  ~derivedEdgeFor=derivedEdgeFor(~component=A.Spec.name, ...),
@@ -1483,8 +1507,9 @@ let make = (
1483
1507
  events: extractEventDefs(A.Spec.eventSchema->S.castToUnknown),
1484
1508
  errors: extractErrorDefs(A.Spec.errorSchema->S.castToUnknown),
1485
1509
  chapter: chapterOf(A.Spec.name),
1486
- }: Reventless.Plugin.writableDef)
1487
- })
1510
+ }: Reventless.Plugin.writableDef
1511
+ )
1512
+ })
1488
1513
 
1489
1514
  // ── Automation slices ────────────────────────────────────────────────────────
1490
1515
 
@@ -1500,22 +1525,30 @@ let make = (
1500
1525
  )
1501
1526
  ->Belt.Set.String.fromArray
1502
1527
  ->Belt.Set.String.toArray
1503
- ({
1504
- Reventless.Plugin.name: AS.Spec.name,
1505
- consumedEventTypes: qualify(~prefix=name, allConsumedVariants),
1506
- producedCommandTypes: qualify(~prefix=name, commandVariantNames(AS.Spec.commandSchema)),
1507
- targetName: AS.Spec.targetName,
1508
- chapter: chapterOf(AS.Spec.name),
1509
- }: Reventless.Plugin.automationSliceDef)
1528
+
1529
+ (
1530
+ {
1531
+ Reventless.Plugin.name: AS.Spec.name,
1532
+ consumedEventTypes: qualify(~prefix=name, allConsumedVariants),
1533
+ producedCommandTypes: qualify(~prefix=name, commandVariantNames(AS.Spec.commandSchema)),
1534
+ targetName: AS.Spec.targetName,
1535
+ chapter: chapterOf(AS.Spec.name),
1536
+ }: Reventless.Plugin.automationSliceDef
1537
+ )
1510
1538
  })
1511
1539
 
1512
1540
  // ── Outbound translation slices ───────────────────────────────────────────
1513
1541
 
1514
1542
  let outboundTranslationSliceDefs =
1515
- outboundTranslationSlices->Array.map((module(OTS: ReventlessInfra.OutboundTranslationSlice.T)) => ({
1543
+ outboundTranslationSlices->Array.map((
1544
+ module(OTS: ReventlessInfra.OutboundTranslationSlice.T),
1545
+ ): Reventless.Plugin.outboundTranslationSliceDef => {
1516
1546
  Reventless.Plugin.name: OTS.Spec.name,
1517
1547
  consumedEventTypes: qualify(~prefix=name, eventVariantNames(OTS.Spec.consumedEventSchema)),
1518
- inboundCommandTypes: qualify(~prefix=name, commandVariantNames(OTS.Spec.inboundCommandSchema)),
1548
+ inboundCommandTypes: qualify(
1549
+ ~prefix=name,
1550
+ commandVariantNames(OTS.Spec.inboundCommandSchema),
1551
+ ),
1519
1552
  targetName: OTS.Spec.targetName,
1520
1553
  externalSystem: OTS.Spec.externalSystem,
1521
1554
  chapter: chapterOf(OTS.Spec.name),
@@ -1523,18 +1556,20 @@ let make = (
1523
1556
  // this plugin's own DCB log, and resolving it here to a derived name would
1524
1557
  // be a second place that name is spelled.
1525
1558
  consumedSources: Some(OTS.Spec.sourceNames),
1526
- }: Reventless.Plugin.outboundTranslationSliceDef))
1559
+ })
1527
1560
 
1528
1561
  // ── Inbound translation slices ────────────────────────────────────────────
1529
1562
 
1530
1563
  let inboundTranslationSliceDefs =
1531
- inboundTranslationSlices->Array.map((module(ITS: ReventlessInfra.InboundTranslationSlice.T)) => ({
1564
+ inboundTranslationSlices->Array.map((
1565
+ module(ITS: ReventlessInfra.InboundTranslationSlice.T),
1566
+ ): Reventless.Plugin.inboundTranslationSliceDef => {
1532
1567
  Reventless.Plugin.name: ITS.Spec.name,
1533
1568
  commandTypes: qualify(~prefix=name, commandVariantNames(ITS.Spec.commandSchema)),
1534
1569
  targetName: ITS.Spec.targetName,
1535
1570
  externalSystem: ITS.Spec.externalSystem,
1536
1571
  chapter: chapterOf(ITS.Spec.name),
1537
- }: Reventless.Plugin.inboundTranslationSliceDef))
1572
+ })
1538
1573
 
1539
1574
  // ── Extensions ───────────────────────────────────────────────────────────
1540
1575
 
@@ -1546,39 +1581,39 @@ let make = (
1546
1581
  // what the author can write, not the payload-filtered subset the edges use.
1547
1582
  let allVariantNames = schema => Reventless.DcbTag.extractAllVariantNames(schema)
1548
1583
 
1549
- let extensionDefs =
1550
- extensions->Array.map((module(E: ReventlessInfra.Extension.Blueprint)) => {
1551
- let delegateNames = E.mappings->Array.map((module(M: E.Mapping)) => M.delegateName)
1552
- let epEventNames = allVariantNames(E.Spec.eventSchema)
1553
- let epCommandNames = allVariantNames(E.Spec.commandSchema)
1554
-
1555
- // Mappings sharing one EP union their tables — the same event may route to
1556
- // a different delegate's command in each.
1557
- let commandsByEvent: Dict.t<array<string>> = Dict.make()
1558
- let eventsByCommand: Dict.t<array<string>> = Dict.make()
1559
- E.mappings->Array.forEach((module(M: E.Mapping)) => {
1560
- let label = `${E.Spec.name} → ${M.delegateName}`
1561
- pushAll(
1562
- tableFailures,
1563
- handledTableFailures(
1564
- ~label,
1565
- ~declared=M.handledEvents,
1566
- ~eventNames=epEventNames,
1567
- ~commandNames=Array.concat(M.delegateCommandNames, epCommandNames),
1568
- ),
1569
- )
1570
- pushAll(
1571
- tableFailures,
1572
- commandTableFailures(
1573
- ~label,
1574
- ~keyed="issuedCommands",
1575
- ~valueKind="comes from",
1576
- ~rows=M.issuedCommands->Array.map(({name, fromEventTypes}) => (name, fromEventTypes)),
1577
- ~keyNames=epCommandNames,
1578
- ~valueNames=M.delegateEventNames,
1579
- ),
1580
- )
1581
- M.issuedCommands->Array.forEach(({name: commandName, fromEventTypes}) => {
1584
+ let extensionDefs = extensions->Array.map((module(E: ReventlessInfra.Extension.Blueprint)) => {
1585
+ let delegateNames = E.mappings->Array.map((module(M: E.Mapping)) => M.delegateName)
1586
+ let epEventNames = allVariantNames(E.Spec.eventSchema)
1587
+ let epCommandNames = allVariantNames(E.Spec.commandSchema)
1588
+
1589
+ // Mappings sharing one EP union their tables — the same event may route to
1590
+ // a different delegate's command in each.
1591
+ let commandsByEvent: Dict.t<array<string>> = Dict.make()
1592
+ let eventsByCommand: Dict.t<array<string>> = Dict.make()
1593
+ E.mappings->Array.forEach((module(M: E.Mapping)) => {
1594
+ let label = `${E.Spec.name} → ${M.delegateName}`
1595
+ pushAll(
1596
+ tableFailures,
1597
+ handledTableFailures(
1598
+ ~label,
1599
+ ~declared=M.handledEvents,
1600
+ ~eventNames=epEventNames,
1601
+ ~commandNames=Array.concat(M.delegateCommandNames, epCommandNames),
1602
+ ),
1603
+ )
1604
+ pushAll(
1605
+ tableFailures,
1606
+ commandTableFailures(
1607
+ ~label,
1608
+ ~keyed="issuedCommands",
1609
+ ~valueKind="comes from",
1610
+ ~rows=M.issuedCommands->Array.map(({name, fromEventTypes}) => (name, fromEventTypes)),
1611
+ ~keyNames=epCommandNames,
1612
+ ~valueNames=M.delegateEventNames,
1613
+ ),
1614
+ )
1615
+ M.issuedCommands->Array.forEach(
1616
+ ({name: commandName, fromEventTypes}) => {
1582
1617
  let key = `${E.Spec.name}.${commandName}`
1583
1618
  eventsByCommand->Dict.set(
1584
1619
  key,
@@ -1587,25 +1622,30 @@ let make = (
1587
1622
  qualify(~prefix=name, fromEventTypes),
1588
1623
  ),
1589
1624
  )
1590
- })
1591
- M.handledEvents->Array.forEach(({name: eventName, toCommandTypes}) => {
1625
+ },
1626
+ )
1627
+ M.handledEvents->Array.forEach(
1628
+ ({name: eventName, toCommandTypes}) => {
1592
1629
  // The qualifier says which way the command goes: plugin-qualified
1593
1630
  // inward, EP-qualified back to the port.
1594
1631
  let qualified =
1595
- toCommandTypes->Array.map(cmd =>
1596
- M.delegateCommandNames->Array.includes(cmd)
1597
- ? `${name}.${cmd}`
1598
- : `${E.Spec.name}.${cmd}`
1632
+ toCommandTypes->Array.map(
1633
+ cmd =>
1634
+ M.delegateCommandNames->Array.includes(cmd)
1635
+ ? `${name}.${cmd}`
1636
+ : `${E.Spec.name}.${cmd}`,
1599
1637
  )
1600
1638
  let key = `${E.Spec.name}.${eventName}`
1601
1639
  commandsByEvent->Dict.set(
1602
1640
  key,
1603
1641
  Array.concat(commandsByEvent->Dict.get(key)->Option.getOr([]), qualified),
1604
1642
  )
1605
- })
1606
- })
1643
+ },
1644
+ )
1645
+ })
1607
1646
 
1608
- ({
1647
+ (
1648
+ {
1609
1649
  Reventless.Plugin.name: E.Spec.name,
1610
1650
  delegateNames,
1611
1651
  eventTypes: qualify(~prefix=E.Spec.name, eventVariantNames(E.Spec.eventSchema)),
@@ -1613,21 +1653,22 @@ let make = (
1613
1653
  handledEvents: Some(
1614
1654
  commandsByEvent
1615
1655
  ->Dict.toArray
1616
- ->Array.map(((eventName, cmds)) => ({
1656
+ ->Array.map(((eventName, cmds)): Reventless.Plugin.handledEventDef => {
1617
1657
  Reventless.Plugin.name: eventName,
1618
1658
  toCommandTypes: dedupe(cmds),
1619
- }: Reventless.Plugin.handledEventDef)),
1659
+ }),
1620
1660
  ),
1621
1661
  issuedCommands: Some(
1622
1662
  eventsByCommand
1623
1663
  ->Dict.toArray
1624
- ->Array.map(((commandName, evs)) => ({
1664
+ ->Array.map(((commandName, evs)): Reventless.Plugin.issuedCommandDef => {
1625
1665
  Reventless.Plugin.name: commandName,
1626
1666
  fromEventTypes: dedupe(evs),
1627
- }: Reventless.Plugin.issuedCommandDef)),
1667
+ }),
1628
1668
  ),
1629
- }: Reventless.Plugin.extensionDef)
1630
- })
1669
+ }: Reventless.Plugin.extensionDef
1670
+ )
1671
+ })
1631
1672
 
1632
1673
  // ── Extension points (producer side) ──────────────────────────────────────
1633
1674
  //
@@ -1646,14 +1687,21 @@ let make = (
1646
1687
 
1647
1688
  extensionPoints->Array.forEach((module(M: ReventlessInfra.ExtensionPointMapping.Mapping)) => {
1648
1689
  let epName = M.ExtensionPoint.name
1649
- let sourceEvents = qualify(~prefix=name, eventVariantNames(M.Delegate.eventSchema->S.castToUnknown))
1690
+ let sourceEvents = qualify(
1691
+ ~prefix=name,
1692
+ eventVariantNames(M.Delegate.eventSchema->S.castToUnknown),
1693
+ )
1650
1694
  // The EP's inbound command protocol (variants of its `command` type). Empty
1651
1695
  // for a `command = unit` EP — an events-out-only boundary that routes nothing.
1652
1696
  let commands = qualify(~prefix=epName, commandVariantNames(M.ExtensionPoint.commandSchema))
1653
1697
  let (dels, evs, cmds) = epByName->Dict.get(epName)->Option.getOr(([], [], []))
1654
1698
  epByName->Dict.set(
1655
1699
  epName,
1656
- (Array.concat(dels, [M.Delegate.name]), Array.concat(evs, sourceEvents), Array.concat(cmds, commands)),
1700
+ (
1701
+ Array.concat(dels, [M.Delegate.name]),
1702
+ Array.concat(evs, sourceEvents),
1703
+ Array.concat(cmds, commands),
1704
+ ),
1657
1705
  )
1658
1706
 
1659
1707
  let label = `${epName} ← ${M.Delegate.name}`
@@ -1690,39 +1738,38 @@ let make = (
1690
1738
  )
1691
1739
  ? Dict.fromArray([("TAG", JSON.Encode.string(cmd))])->JSON.Encode.object
1692
1740
  : JSON.Encode.string(cmd)
1693
- switch (
1694
- try {
1695
- let command =
1696
- Reventless.Message.fillMissingDefaults(
1697
- M.ExtensionPoint.commandSchema,
1698
- synthesised,
1699
- [],
1700
- )->Reventless.Util_Sury.fromJson(M.ExtensionPoint.commandSchema)
1701
- let decodedAs =
1702
- command
1703
- ->Reventless.Message.encode(M.ExtensionPoint.commandSchema)
1704
- ->Reventless.Message.variantNameOfJson
1705
- decodedAs != cmd
1706
- ? Error(`a synthesised "${cmd}" decoded as "${decodedAs}"`)
1707
- : Ok(M.mapIncomingCommand(probeId, command, probeMeta))
1708
- } catch {
1709
- | _ => Error(`the mapping raised on a synthesised "${cmd}"`)
1710
- }
1711
- ) {
1741
+ switch try {
1742
+ let command =
1743
+ Reventless.Message.fillMissingDefaults(
1744
+ M.ExtensionPoint.commandSchema,
1745
+ synthesised,
1746
+ [],
1747
+ )->Reventless.Util_Sury.fromJson(M.ExtensionPoint.commandSchema)
1748
+ let decodedAs =
1749
+ command
1750
+ ->Reventless.Message.encode(M.ExtensionPoint.commandSchema)
1751
+ ->Reventless.Message.variantNameOfJson
1752
+ decodedAs != cmd
1753
+ ? Error(`a synthesised "${cmd}" decoded as "${decodedAs}"`)
1754
+ : Ok(M.mapIncomingCommand(probeId, command, probeMeta))
1755
+ } catch {
1756
+ | _ => Error(`the mapping raised on a synthesised "${cmd}"`)
1757
+ } {
1712
1758
  | Ok(actions) =>
1713
- actions->Array.forEach(action =>
1714
- switch action {
1715
- | ReventlessInfra.ExtensionPointMapping.PublishCommand(_, routed) =>
1716
- acceptedObserved
1717
- ->Array.push((
1718
- cmd,
1719
- routed
1720
- ->Reventless.Message.encode(M.Delegate.commandSchema)
1721
- ->Reventless.Message.variantNameOfJson,
1722
- ))
1723
- ->ignore
1724
- | HandleDirective(_, _) => ()
1725
- }
1759
+ actions->Array.forEach(
1760
+ action =>
1761
+ switch action {
1762
+ | ReventlessInfra.ExtensionPointMapping.PublishCommand(_, routed) =>
1763
+ acceptedObserved
1764
+ ->Array.push((
1765
+ cmd,
1766
+ routed
1767
+ ->Reventless.Message.encode(M.Delegate.commandSchema)
1768
+ ->Reventless.Message.variantNameOfJson,
1769
+ ))
1770
+ ->ignore
1771
+ | HandleDirective(_, _) => ()
1772
+ },
1726
1773
  )
1727
1774
  | Error(reason) =>
1728
1775
  tableWarnings->Array.push(`${label}: not checked against the arms — ${reason}.`)->ignore
@@ -1731,8 +1778,8 @@ let make = (
1731
1778
  acceptedObserved->Array.forEach(((cmd, routed)) =>
1732
1779
  if (
1733
1780
  !(
1734
- M.acceptedCommands->Array.some(({name, toCommandTypes}) =>
1735
- name == cmd && toCommandTypes->Array.includes(routed)
1781
+ M.acceptedCommands->Array.some(
1782
+ ({name, toCommandTypes}) => name == cmd && toCommandTypes->Array.includes(routed),
1736
1783
  )
1737
1784
  )
1738
1785
  ) {
@@ -1764,8 +1811,7 @@ let make = (
1764
1811
  ->Array.push(
1765
1812
  `${label}: publishedEvents declares ${M.publishedEvents
1766
1813
  ->Array.length
1767
- ->Int.toString} event(s), but the mapping has no mapOutgoingEvent and ` ++
1768
- `publishes nothing.`,
1814
+ ->Int.toString} event(s), but the mapping has no mapOutgoingEvent and ` ++ `publishes nothing.`,
1769
1815
  )
1770
1816
  ->ignore
1771
1817
  }
@@ -1785,8 +1831,11 @@ let make = (
1785
1831
  // Filled directly rather than through `parseJsonTolerant`, which warns
1786
1832
  // about inventing values — here the invention is the point.
1787
1833
  let event =
1788
- Reventless.Message.fillMissingDefaults(M.Delegate.eventSchema, synthesised, [])
1789
- ->Reventless.Util_Sury.fromJson(M.Delegate.eventSchema)
1834
+ Reventless.Message.fillMissingDefaults(
1835
+ M.Delegate.eventSchema,
1836
+ synthesised,
1837
+ [],
1838
+ )->Reventless.Util_Sury.fromJson(M.Delegate.eventSchema)
1790
1839
  // A fabricated payload can decode as a sibling constructor; only a
1791
1840
  // value that round-trips is judged.
1792
1841
  let decodedAs =
@@ -1797,26 +1846,28 @@ let make = (
1797
1846
  Error(`a synthesised "${src}" decoded as "${decodedAs}"`)
1798
1847
  } else {
1799
1848
  let actions = mapOutgoing(probeId, event, probeMeta, probeQueryEngine)
1800
- actions->Array.forEach(action =>
1801
- switch action {
1802
- | ReventlessInfra.ExtensionPointMapping.PublishEvent(_, published) =>
1803
- observed
1804
- ->Array.push((
1805
- src,
1806
- published
1807
- ->Reventless.Message.encode(M.ExtensionPoint.eventSchema)
1808
- ->Reventless.Message.variantNameOfJson,
1809
- ))
1810
- ->ignore
1811
- | PublishEventAsync(_) | HandleDirective(_, _) => ()
1812
- }
1849
+ actions->Array.forEach(
1850
+ action =>
1851
+ switch action {
1852
+ | ReventlessInfra.ExtensionPointMapping.PublishEvent(_, published) =>
1853
+ observed
1854
+ ->Array.push((
1855
+ src,
1856
+ published
1857
+ ->Reventless.Message.encode(M.ExtensionPoint.eventSchema)
1858
+ ->Reventless.Message.variantNameOfJson,
1859
+ ))
1860
+ ->ignore
1861
+ | PublishEventAsync(_) | HandleDirective(_, _) => ()
1862
+ },
1813
1863
  )
1814
1864
  // A promise hides what it will publish — leave the arm unjudged.
1815
- actions->Array.some(action =>
1816
- switch action {
1817
- | PublishEventAsync(_) => true
1818
- | PublishEvent(_, _) | HandleDirective(_, _) => false
1819
- }
1865
+ actions->Array.some(
1866
+ action =>
1867
+ switch action {
1868
+ | PublishEventAsync(_) => true
1869
+ | PublishEvent(_, _) | HandleDirective(_, _) => false
1870
+ },
1820
1871
  )
1821
1872
  ? Error(`"${src}" publishes behind a promise`)
1822
1873
  : Ok()
@@ -1857,10 +1908,7 @@ let make = (
1857
1908
  let key = `${epName}.${published}`
1858
1909
  table->Dict.set(
1859
1910
  key,
1860
- Array.concat(
1861
- table->Dict.get(key)->Option.getOr([]),
1862
- qualify(~prefix=name, fromEventTypes),
1863
- ),
1911
+ Array.concat(table->Dict.get(key)->Option.getOr([]), qualify(~prefix=name, fromEventTypes)),
1864
1912
  )
1865
1913
  })
1866
1914
  epPublished->Dict.set(epName, table)
@@ -1869,7 +1917,7 @@ let make = (
1869
1917
  let extensionPointDefs =
1870
1918
  epByName
1871
1919
  ->Dict.toArray
1872
- ->Array.map(((epName, (dels, evs, cmds))) => ({
1920
+ ->Array.map(((epName, (dels, evs, cmds))): Reventless.Plugin.extensionPointDef => {
1873
1921
  Reventless.Plugin.name: epName,
1874
1922
  delegateNames: dedupe(dels),
1875
1923
  sourceEventTypes: dedupe(evs),
@@ -1879,22 +1927,22 @@ let make = (
1879
1927
  ->Dict.get(epName)
1880
1928
  ->Option.getOr(Dict.make())
1881
1929
  ->Dict.toArray
1882
- ->Array.map(((published, sources)) => ({
1930
+ ->Array.map(((published, sources)): Reventless.Plugin.publishedEventDef => {
1883
1931
  Reventless.Plugin.name: published,
1884
1932
  fromEventTypes: dedupe(sources),
1885
- }: Reventless.Plugin.publishedEventDef)),
1933
+ }),
1886
1934
  ),
1887
1935
  acceptedCommands: Some(
1888
1936
  epAccepted
1889
1937
  ->Dict.get(epName)
1890
1938
  ->Option.getOr(Dict.make())
1891
1939
  ->Dict.toArray
1892
- ->Array.map(((accepted, routed)) => ({
1940
+ ->Array.map(((accepted, routed)): Reventless.Plugin.acceptedCommandDef => {
1893
1941
  Reventless.Plugin.name: accepted,
1894
1942
  toCommandTypes: dedupe(routed),
1895
- }: Reventless.Plugin.acceptedCommandDef)),
1943
+ }),
1896
1944
  ),
1897
- }: Reventless.Plugin.extensionPointDef))
1945
+ })
1898
1946
 
1899
1947
  // Dead inbound surface, judged only after every mapping on an EP has been seen:
1900
1948
  // one port's inbound protocol is split across its mappings, so a command the
@@ -1902,16 +1950,14 @@ let make = (
1902
1950
  epByName
1903
1951
  ->Dict.toArray
1904
1952
  ->Array.forEach(((epName, (_, _, cmds))) => {
1905
- let handled =
1906
- epAccepted->Dict.get(epName)->Option.getOr(Dict.make())->Dict.keysToArray
1953
+ let handled = epAccepted->Dict.get(epName)->Option.getOr(Dict.make())->Dict.keysToArray
1907
1954
  cmds
1908
1955
  ->dedupe
1909
1956
  ->Array.forEach(cmd =>
1910
1957
  if !(handled->Array.includes(cmd)) {
1911
1958
  tableWarnings
1912
1959
  ->Array.push(
1913
- `${epName}: the extension point accepts "${cmd}", which no arm handles — a ` ++
1914
- `sender gets no error and nothing happens.`,
1960
+ `${epName}: the extension point accepts "${cmd}", which no arm handles — a ` ++ `sender gets no error and nothing happens.`,
1915
1961
  )
1916
1962
  ->ignore
1917
1963
  }