@reventlessdev/reventless-core 3.0.0-alpha.250 → 3.0.0-alpha.252

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 (70) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/package.json +7 -7
  3. package/src/admin/Platform_Admin_Structure.res +4 -1
  4. package/src/admin/Platform_Admin_Structure.res.mjs +4 -2
  5. package/src/admin/Platform_BakedManifest.res.mjs +3 -1
  6. package/src/admin/Platform_ComponentDefinitionsApi.res +31 -1
  7. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +29 -1
  8. package/src/admin/Platform_PluginStructuresApi.res +80 -8
  9. package/src/admin/Platform_PluginStructuresApi.res.mjs +76 -2
  10. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +8 -0
  11. package/src/components/Api/ApiNoApiHelpers.res +37 -3
  12. package/src/components/Api/ApiNoApiHelpers.res.mjs +29 -2
  13. package/src/components/AutomationSlice/AutomationSlice_Callback.res +4 -4
  14. package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +3 -3
  15. package/src/plugin/api/PluginBaseFragment.res +3 -3
  16. package/src/plugin/component/Plugin_Structure.res +787 -384
  17. package/src/plugin/component/Plugin_Structure.res.mjs +430 -20
  18. package/src/plugin/connect/PluginConnectExtension_Mapping.res +3 -6
  19. package/src/plugin/connect/PluginConnectExtension_Mapping.res.mjs +14 -2
  20. package/src/plugin/connect/PluginExtensionPoint_Plugin.res +0 -1
  21. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +63 -3
  22. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +22 -3
  23. package/src/plugin/lifecycle/PluginBehavior.res +11 -11
  24. package/src/plugin/lifecycle/PluginBehavior.res.mjs +11 -11
  25. package/src/plugin/lifecycle/PluginSpec.res +11 -3
  26. package/src/plugin/lifecycle/PluginSpec.res.mjs +11 -3
  27. package/tests/admin/AdminApiSchemaDriftTest.res +486 -0
  28. package/tests/admin/AdminApiSchemaDriftTest.res.mjs +619 -0
  29. package/tests/admin/Platform_Admin_StructureTest.res +8 -3
  30. package/tests/admin/Platform_Admin_StructureTest.res.mjs +8 -2
  31. package/tests/admin/Platform_BakedManifestTest.res +2 -0
  32. package/tests/admin/Platform_BakedManifestTest.res.mjs +3 -1
  33. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +10 -0
  34. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +30 -8
  35. package/tests/admin/Platform_PluginStructuresApiTest.res +28 -1
  36. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +27 -5
  37. package/tests/aggregate/AggregateCacheTest.res.mjs +15 -3
  38. package/tests/aggregate/AggregateConflictTest.res.mjs +15 -3
  39. package/tests/aggregate/AggregateFixtures.res.mjs +15 -3
  40. package/tests/aggregate/AggregateSnapshotTest.res.mjs +21 -5
  41. package/tests/api/ApiNoApiTest.res +46 -0
  42. package/tests/api/ApiNoApiTest.res.mjs +49 -1
  43. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +27 -3
  44. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +12 -2
  45. package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +2 -0
  46. package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +12 -2
  47. package/tests/dcb/DcbFixtures.res.mjs +8 -0
  48. package/tests/dcb/DcbStateChangeSliceTest.res.mjs +18 -0
  49. package/tests/eventmapper/EventMapperFixtures.res.mjs +15 -3
  50. package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +9 -1
  51. package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +9 -1
  52. package/tests/message/MessageTest.res +2 -0
  53. package/tests/message/MessageTest.res.mjs +3 -1
  54. package/tests/plugin/PluginStructureAccessTest.res.mjs +2 -0
  55. package/tests/plugin/PluginStructureTest.res +294 -1
  56. package/tests/plugin/PluginStructureTest.res.mjs +373 -3
  57. package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +8 -0
  58. package/tests/plugin/StateChangeSlice/PsChangePhoto.res.mjs +8 -0
  59. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res.mjs +8 -0
  60. package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +8 -0
  61. package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +8 -0
  62. package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +8 -0
  63. package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +8 -0
  64. package/tests/plugin/StateChangeSlice/PsTransitionSwitch.res +59 -0
  65. package/tests/plugin/StateChangeSlice/PsTransitionSwitch.res.mjs +115 -0
  66. package/tests/plugin/StateChangeSlice/PsTypoStore.res.mjs +8 -0
  67. package/tests/plugin/StateChangeSlice/PsUploadAvatar.res.mjs +8 -0
  68. package/tests/plugin/StateChangeSlice/PsUploadImages.res.mjs +8 -0
  69. package/tests/plugin/pluginDefinitionRequiredScalars.txt +26 -0
  70. package/tests/util/CommandPublisherTest.res.mjs +9 -1
@@ -9,12 +9,17 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
9
9
  import * as Owner$Reventless from "@reventlessdev/reventless-spec/src/components/Owner.res.mjs";
10
10
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
11
11
  import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
12
+ import * as Trait$Reventless from "@reventlessdev/reventless-spec/src/types/Trait.res.mjs";
12
13
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
14
+ import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
13
15
  import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/components/Reference.res.mjs";
16
+ import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
14
17
  import * as Logger$ReventlessCore from "../../util/Logger.res.mjs";
15
18
  import * as StorageRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/StorageRef.res.mjs";
19
+ import * as Transition$Reventless from "@reventlessdev/reventless-spec/src/types/Transition.res.mjs";
16
20
  import * as DisplayName$Reventless from "@reventlessdev/reventless-spec/src/components/DisplayName.res.mjs";
17
21
  import * as Api_Naming$ReventlessCore from "../../components/Api/Api_Naming.res.mjs";
22
+ import * as CapabilityNeed$Reventless from "@reventlessdev/reventless-spec/src/semantic/CapabilityNeed.res.mjs";
18
23
  import * as SchemaType$ReventlessCore from "../../components/Api/SchemaType.res.mjs";
19
24
  import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
20
25
  import * as ApiNoApiHelpers$ReventlessCore from "../../components/Api/ApiNoApiHelpers.res.mjs";
@@ -191,6 +196,132 @@ function lifecycleStatesFromStateSchema(entityName, stateSchema) {
191
196
  });
192
197
  }
193
198
 
199
+ function translationTableFailures(label, declared, publishedNames, sourceNames) {
200
+ let failures = [];
201
+ let push = msg => {
202
+ failures.push(msg);
203
+ };
204
+ declared.forEach(param => {
205
+ let name = param.name;
206
+ if (!publishedNames.includes(name)) {
207
+ push(label + `: publishedEvents names "` + name + `", which the extension point does not ` + (`publish — it declares ` + publishedNames.join(", ") + `.`));
208
+ }
209
+ param.fromEventTypes.forEach(src => {
210
+ if (!sourceNames.includes(src)) {
211
+ return push(label + `: publishedEvents says "` + name + `" comes from "` + src + `", which is not an ` + (`event of ` + sourceNames.join(", ") + `.`));
212
+ }
213
+ });
214
+ });
215
+ return failures;
216
+ }
217
+
218
+ function translationTableDrift(label, declared, observed, followed) {
219
+ let declaredEdges = Stdlib_Array.reduce(declared, [], (acc, param) => {
220
+ let name = param.name;
221
+ return acc.concat(param.fromEventTypes.map(src => [
222
+ src,
223
+ name
224
+ ]));
225
+ });
226
+ let has = (edges, param) => {
227
+ let pub = param[1];
228
+ let src = param[0];
229
+ return edges.some(param => {
230
+ if (Primitive_object.equal(param[0], src)) {
231
+ return Primitive_object.equal(param[1], pub);
232
+ } else {
233
+ return false;
234
+ }
235
+ });
236
+ };
237
+ let failures = observed.filter(edge => !has(declaredEdges, edge)).map(param => label + `: "` + param[0] + `" publishes "` + param[1] + `", which publishedEvents does not declare.`);
238
+ let warnings = declaredEdges.filter(param => {
239
+ let src = param[0];
240
+ if (followed.includes(src)) {
241
+ return !has(observed, [
242
+ src,
243
+ param[1]
244
+ ]);
245
+ } else {
246
+ return false;
247
+ }
248
+ }).map(param => {
249
+ let src = param[0];
250
+ return label + `: publishedEvents declares "` + src + `" → "` + param[1] + `", which the mapping did not ` + (`produce for a synthesised "` + src + `".`);
251
+ });
252
+ return [
253
+ failures,
254
+ warnings
255
+ ];
256
+ }
257
+
258
+ function handledTableFailures(label, declared, eventNames, commandNames) {
259
+ let failures = [];
260
+ declared.forEach(param => {
261
+ let name = param.name;
262
+ if (!eventNames.includes(name)) {
263
+ failures.push(label + `: handledEvents names "` + name + `", which the extension point does not ` + (`publish — it declares ` + eventNames.join(", ") + `.`));
264
+ }
265
+ param.toCommandTypes.forEach(cmd => {
266
+ if (!commandNames.includes(cmd)) {
267
+ failures.push(label + `: handledEvents says "` + name + `" routes to "` + cmd + `", which is neither a ` + (`delegate command nor an extension point command — ` + commandNames.join(", ") + `.`));
268
+ return;
269
+ }
270
+ });
271
+ });
272
+ return failures;
273
+ }
274
+
275
+ function commandTableFailures(label, keyed, valueKind, rows, keyNames, valueNames) {
276
+ let failures = [];
277
+ let push = msg => {
278
+ failures.push(msg);
279
+ };
280
+ rows.forEach(param => {
281
+ let name = param[0];
282
+ if (!keyNames.includes(name)) {
283
+ push(label + `: ` + keyed + ` names "` + name + `", which is not a command of the extension ` + (`point — it declares ` + keyNames.join(", ") + `.`));
284
+ }
285
+ param[1].forEach(v => {
286
+ if (!valueNames.includes(v)) {
287
+ return push(label + `: ` + keyed + ` says "` + name + `" ` + valueKind + ` "` + v + `", which the delegate does ` + (`not declare — ` + valueNames.join(", ") + `.`));
288
+ }
289
+ });
290
+ });
291
+ return failures;
292
+ }
293
+
294
+ let probeId = "probe";
295
+
296
+ let probeMeta = {
297
+ service: "Plugin_Structure",
298
+ time: "",
299
+ msgId: "",
300
+ correlationId: ""
301
+ };
302
+
303
+ async function probeQueryEngine_scan(param, param$1, param$2) {
304
+ return [];
305
+ }
306
+
307
+ async function probeQueryEngine_query(param, param$1, param$2, param$3, param$4, param$5, param$6) {
308
+ return [];
309
+ }
310
+
311
+ let probeQueryEngine = {
312
+ scan: probeQueryEngine_scan,
313
+ query: probeQueryEngine_query
314
+ };
315
+
316
+ function reportTranslationTables(pluginName, failures, warnings) {
317
+ if (warnings.length !== 0) {
318
+ log.warn("Plugin_Structure", undefined, pluginName + `:\n` + warnings.join("\n"));
319
+ }
320
+ if (failures.length !== 0) {
321
+ return Stdlib_JsError.throwWithMessage(pluginName + `: the declared translation table does not match the mapping.\n` + failures.join("\n"));
322
+ }
323
+ }
324
+
194
325
  function checkDeclaredTransitions(pluginName, writables, lifecycleStatesByView) {
195
326
  let unvalidated = {
196
327
  contents: 0
@@ -215,16 +346,16 @@ function checkDeclaredTransitions(pluginName, writables, lifecycleStatesByView)
215
346
  unvalidated.contents = unvalidated.contents + 1 | 0;
216
347
  } else {
217
348
  declared.filter(state => !known.includes(state)).forEach(state => {
218
- failures.push(w.name + `.` + cmd.name + `: @transition names "` + state + `", which none of its ` + (`linked views declare — ` + w.linkedViews.join(", ") + ` know ` + known.join(", ") + `.`));
349
+ failures.push(w.name + `.` + cmd.name + ` declares state "` + state + `", which none of its ` + (`linked views declare — ` + w.linkedViews.join(", ") + ` know ` + known.join(", ") + `.`));
219
350
  });
220
351
  }
221
352
  });
222
353
  });
223
354
  if (unvalidated.contents > 0) {
224
- log.warn("Plugin_Structure", undefined, pluginName + `: ` + unvalidated.contents.toString() + ` command(s) declare a @transition but no linked view declares a lifecycle to check it against.`);
355
+ log.warn("Plugin_Structure", undefined, pluginName + `: ` + unvalidated.contents.toString() + ` command(s) declare a transition but no linked view declares a lifecycle to check it against.`);
225
356
  }
226
357
  if (failures.length !== 0) {
227
- return Stdlib_JsError.throwWithMessage(pluginName + `: @transition names states that do not exist.\n` + failures.join("\n"));
358
+ return Stdlib_JsError.throwWithMessage(pluginName + `: a declared transition names states that do not exist.\n` + failures.join("\n"));
228
359
  }
229
360
  }
230
361
 
@@ -463,20 +594,27 @@ function annotateArgTypes(schema, argTypes) {
463
594
  return schema;
464
595
  }
465
596
 
466
- function toCommandDef(isAggregate, mutationFieldFor, parentSchema, commandAuthorization, v) {
597
+ function toCommandDef(isAggregate, mutationFieldFor, parentSchema, commandAuthorization, commandTransition, v) {
467
598
  let mkDef = (variantName, properties) => {
468
599
  let match = commandLevelAndId(isAggregate, variantName, properties);
469
600
  let references = extractReferences(properties);
470
- let allowedStates = ApiAllowedStatesHelpers$ReventlessCore.getAllowedStates(parentSchema, variantName);
471
- let targetState = ApiTargetStateHelpers$ReventlessCore.getTargetState(parentSchema, variantName);
601
+ let syntheticCommand = DcbTag$Reventless.isVariantPayloadBearing(parentSchema, variantName) ? ({
602
+ TAG: variantName
603
+ }) : variantName;
604
+ let declared = commandTransition(syntheticCommand);
605
+ let match$1;
606
+ match$1 = typeof declared !== "object" ? [
607
+ ApiAllowedStatesHelpers$ReventlessCore.getAllowedStates(parentSchema, variantName),
608
+ ApiTargetStateHelpers$ReventlessCore.getTargetState(parentSchema, variantName)
609
+ ] : [
610
+ Transition$Reventless.allowedStates(declared),
611
+ Transition$Reventless.targetState(declared)
612
+ ];
472
613
  let apiExposed = false;
473
614
  if (!ApiNoApiHelpers$ReventlessCore.isNoApi(parentSchema)) {
474
615
  let excluded = ApiNoApiHelpers$ReventlessCore.getExcludedVariants(parentSchema);
475
616
  apiExposed = excluded !== undefined ? !excluded.has(variantName) : true;
476
617
  }
477
- let syntheticCommand = DcbTag$Reventless.isVariantPayloadBearing(parentSchema, variantName) ? ({
478
- TAG: variantName
479
- }) : variantName;
480
618
  let requiredAccess = accessKeysFor(commandAuthorization(syntheticCommand));
481
619
  let mutationField = apiExposed ? mutationFieldFor(variantName) : "";
482
620
  let jsonSchema = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(v);
@@ -488,8 +626,8 @@ function toCommandDef(isAggregate, mutationFieldFor, parentSchema, commandAuthor
488
626
  aggregateIdField: match[1],
489
627
  mutationField: mutationField,
490
628
  references: references,
491
- allowedStates: allowedStates,
492
- targetState: targetState,
629
+ allowedStates: match$1[0],
630
+ targetState: match$1[1],
493
631
  apiExposed: apiExposed,
494
632
  requiredAccess: requiredAccess,
495
633
  ownerField: Owner$Reventless.fieldNamesOfProperties(properties)[0]
@@ -519,11 +657,11 @@ function toCommandDef(isAggregate, mutationFieldFor, parentSchema, commandAuthor
519
657
  }
520
658
  }
521
659
 
522
- function extractCommandDefs(isAggregate, mutationFieldFor, commandAuthorization, commandSchema) {
660
+ function extractCommandDefs(isAggregate, mutationFieldFor, commandAuthorization, commandTransition, commandSchema) {
523
661
  if (commandSchema.type === "anyOf") {
524
- return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(isAggregate, mutationFieldFor, commandSchema, commandAuthorization, v));
662
+ return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(isAggregate, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, v));
525
663
  } else {
526
- return Stdlib_Option.mapOr(toCommandDef(isAggregate, mutationFieldFor, commandSchema, commandAuthorization, commandSchema), [], def => [def]);
664
+ return Stdlib_Option.mapOr(toCommandDef(isAggregate, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, commandSchema), [], def => [def]);
527
665
  }
528
666
  }
529
667
 
@@ -663,6 +801,17 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
663
801
  return Primitive_string.compare(a.field, b.field);
664
802
  }
665
803
  };
804
+ let dcbCommandOwners = {};
805
+ stateChangeSlices.forEach(SCS => {
806
+ DcbTag$Reventless.extractAllVariantNames(SCS.Spec.commandSchema).forEach(cmd => {
807
+ let owners = Stdlib_Option.getOr(dcbCommandOwners[cmd], []);
808
+ dcbCommandOwners[cmd] = owners.concat([SCS.Spec.name]);
809
+ });
810
+ });
811
+ let clashes = Object.entries(dcbCommandOwners).filter(param => param[1].length > 1).toSorted((param, param$1) => Primitive_string.compare(param[0], param$1[0]));
812
+ if (clashes.length !== 0) {
813
+ Stdlib_JsError.throwWithMessage(name + `: ` + clashes.map(param => param[1].join(" and ") + ` both declare the command "` + param[0] + `"`).join("; ") + `.\n A DCB plugin routes a command by its bare name, so one of these would never run. Qualify them — the shipped traits' emitters prefix every name with the host they are grafted onto, for exactly this reason.`);
814
+ }
666
815
  let storeDeclarationSites = [
667
816
  aggregates.flatMap(A => [
668
817
  [
@@ -746,6 +895,55 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
746
895
  if (found.length !== 0) {
747
896
  Stdlib_JsError.throwWithMessage(name + `: two declared object stores look like one store misspelled.\n` + found.map(Capability_Inference$ReventlessCore.collisionMessage).join("\n"));
748
897
  }
898
+ let requiredCapabilities = Stdlib_Array.reduce(outboundTranslationSlices.flatMap(OTS => OTS.Spec.capabilityNeeds.map(need => ({
899
+ capability: CapabilityNeed$Reventless.toString(need),
900
+ component: OTS.Spec.name
901
+ }))).toSorted((a, b) => {
902
+ if (a.capability === b.capability) {
903
+ return Primitive_string.compare(a.component, b.component);
904
+ } else {
905
+ return Primitive_string.compare(a.capability, b.capability);
906
+ }
907
+ }), [], (acc, d) => {
908
+ let prev = Stdlib_Array.last(acc);
909
+ if (prev !== undefined) {
910
+ if (Primitive_object.equal(prev, d)) {
911
+ return acc;
912
+ } else {
913
+ return acc.concat([d]);
914
+ }
915
+ } else {
916
+ return acc.concat([d]);
917
+ }
918
+ });
919
+ let entry = (component, decl) => ({
920
+ trait: decl.trait,
921
+ version: decl.version,
922
+ posture: Trait$Reventless.postureToString(decl.posture),
923
+ component: component
924
+ });
925
+ let traitDeclarations = Stdlib_Array.reduce([
926
+ aggregates.flatMap(A => A.Spec.traits.map(extra => entry(A.Spec.name, extra))),
927
+ stateChangeSlices.flatMap(SCS => SCS.Spec.traits.map(extra => entry(SCS.Spec.name, extra))),
928
+ outboundTranslationSlices.flatMap(OTS => OTS.Spec.traits.map(extra => entry(OTS.Spec.name, extra)))
929
+ ].flat().toSorted((a, b) => {
930
+ if (a.trait === b.trait) {
931
+ return Primitive_string.compare(a.component, b.component);
932
+ } else {
933
+ return Primitive_string.compare(a.trait, b.trait);
934
+ }
935
+ }), [], (acc, d) => {
936
+ let prev = Stdlib_Array.last(acc);
937
+ if (prev !== undefined) {
938
+ if (Primitive_object.equal(prev, d)) {
939
+ return acc;
940
+ } else {
941
+ return acc.concat([d]);
942
+ }
943
+ } else {
944
+ return acc.concat([d]);
945
+ }
946
+ });
749
947
  storeDeclarationSites.forEach(param => {
750
948
  Capability_Inference$ReventlessCore.scanSchema(param[0], param[1]).forEach(w => log.warn("Plugin_Structure", undefined, Capability_Inference$ReventlessCore.message(w)));
751
949
  });
@@ -840,7 +1038,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
840
1038
  let consumed = match$1[1];
841
1039
  return {
842
1040
  name: SCS.Spec.name,
843
- commands: extractCommandDefs(false, variantName => Api_Naming$ReventlessCore.sliceMutationFieldFor(name, SCS.Spec.name, SCS.Spec.commandSchema, variantName), SCS.Spec.commandAuthorization, SCS.Spec.commandSchema),
1041
+ commands: extractCommandDefs(false, variantName => Api_Naming$ReventlessCore.sliceMutationFieldFor(name, SCS.Spec.name, SCS.Spec.commandSchema, variantName), SCS.Spec.commandAuthorization, SCS.Spec.commandTransition, SCS.Spec.commandSchema),
844
1042
  producedEventTypes: produced,
845
1043
  consumedEventTypes: consumed,
846
1044
  linkedViews: linkedSvsFor(produced),
@@ -855,7 +1053,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
855
1053
  let produced = match[1];
856
1054
  return {
857
1055
  name: A.Spec.name,
858
- commands: extractCommandDefs(true, variantName => Api_Naming$ReventlessCore.aggregateMutationField(name, A.Spec.name, variantName), A.Spec.commandAuthorization, A.Spec.commandSchema),
1056
+ commands: extractCommandDefs(true, variantName => Api_Naming$ReventlessCore.aggregateMutationField(name, A.Spec.name, variantName), A.Spec.commandAuthorization, A.Spec.commandTransition, A.Spec.commandSchema),
859
1057
  producedEventTypes: produced,
860
1058
  consumedEventTypes: [],
861
1059
  linkedViews: linkedSvsFor(produced).concat(linkedReadModelsFor(A.Spec.name)),
@@ -890,16 +1088,60 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
890
1088
  externalSystem: ITS.Spec.externalSystem,
891
1089
  chapter: componentChapters[ITS.Spec.name]
892
1090
  }));
1091
+ let tableFailures = [];
1092
+ let tableWarnings = [];
1093
+ let pushAll = (into, xs) => {
1094
+ xs.forEach(x => {
1095
+ into.push(x);
1096
+ });
1097
+ };
893
1098
  let extensionDefs = extensions.map(E => {
894
1099
  let delegateNames = E.mappings.map(M => M.delegateName);
1100
+ let epEventNames = DcbTag$Reventless.extractAllVariantNames(E.Spec.eventSchema);
1101
+ let epCommandNames = DcbTag$Reventless.extractAllVariantNames(E.Spec.commandSchema);
1102
+ let commandsByEvent = {};
1103
+ let eventsByCommand = {};
1104
+ E.mappings.forEach(M => {
1105
+ let label = E.Spec.name + ` → ` + M.delegateName;
1106
+ pushAll(tableFailures, handledTableFailures(label, M.handledEvents, epEventNames, M.delegateCommandNames.concat(epCommandNames)));
1107
+ pushAll(tableFailures, commandTableFailures(label, "issuedCommands", "comes from", M.issuedCommands.map(param => [
1108
+ param.name,
1109
+ param.fromEventTypes
1110
+ ]), epCommandNames, M.delegateEventNames));
1111
+ M.issuedCommands.forEach(param => {
1112
+ let key = E.Spec.name + `.` + param.name;
1113
+ eventsByCommand[key] = Stdlib_Option.getOr(eventsByCommand[key], []).concat(qualify(name, param.fromEventTypes));
1114
+ });
1115
+ M.handledEvents.forEach(param => {
1116
+ let qualified = param.toCommandTypes.map(cmd => {
1117
+ if (M.delegateCommandNames.includes(cmd)) {
1118
+ return name + `.` + cmd;
1119
+ } else {
1120
+ return E.Spec.name + `.` + cmd;
1121
+ }
1122
+ });
1123
+ let key = E.Spec.name + `.` + param.name;
1124
+ commandsByEvent[key] = Stdlib_Option.getOr(commandsByEvent[key], []).concat(qualified);
1125
+ });
1126
+ });
895
1127
  return {
896
1128
  name: E.Spec.name,
897
1129
  delegateNames: delegateNames,
898
1130
  eventTypes: qualify(E.Spec.name, DcbTag$Reventless.extractVariantNames(E.Spec.eventSchema)),
899
- commandTypes: qualify(E.Spec.name, DcbTag$Reventless.extractAllVariantNames(E.Spec.commandSchema))
1131
+ commandTypes: qualify(E.Spec.name, DcbTag$Reventless.extractAllVariantNames(E.Spec.commandSchema)),
1132
+ handledEvents: Object.entries(commandsByEvent).map(param => ({
1133
+ name: param[0],
1134
+ toCommandTypes: Belt_SetString.toArray(Belt_SetString.fromArray(param[1]))
1135
+ })),
1136
+ issuedCommands: Object.entries(eventsByCommand).map(param => ({
1137
+ name: param[0],
1138
+ fromEventTypes: Belt_SetString.toArray(Belt_SetString.fromArray(param[1]))
1139
+ }))
900
1140
  };
901
1141
  });
902
1142
  let epByName = {};
1143
+ let epPublished = {};
1144
+ let epAccepted = {};
903
1145
  extensionPoints.forEach(M => {
904
1146
  let epName = M.ExtensionPoint.name;
905
1147
  let sourceEvents = qualify(name, DcbTag$Reventless.extractVariantNames(M.Delegate.eventSchema));
@@ -914,16 +1156,174 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
914
1156
  match[1].concat(sourceEvents),
915
1157
  match[2].concat(commands)
916
1158
  ];
1159
+ let label = epName + ` ← ` + M.Delegate.name;
1160
+ let publishedNames = DcbTag$Reventless.extractAllVariantNames(M.ExtensionPoint.eventSchema);
1161
+ let sourceNames = DcbTag$Reventless.extractAllVariantNames(M.Delegate.eventSchema);
1162
+ pushAll(tableFailures, translationTableFailures(label, M.publishedEvents, publishedNames, sourceNames));
1163
+ let acceptedNames = DcbTag$Reventless.extractAllVariantNames(M.ExtensionPoint.commandSchema);
1164
+ let delegateCommandNames = DcbTag$Reventless.extractAllVariantNames(M.Delegate.commandSchema);
1165
+ pushAll(tableFailures, commandTableFailures(label, "acceptedCommands", "routes to", M.acceptedCommands.map(param => [
1166
+ param.name,
1167
+ param.toCommandTypes
1168
+ ]), acceptedNames, delegateCommandNames));
1169
+ let acceptedObserved = [];
1170
+ acceptedNames.forEach(cmd => {
1171
+ let synthesised = DcbTag$Reventless.isVariantPayloadBearing(M.ExtensionPoint.commandSchema, cmd) ? Object.fromEntries([[
1172
+ "TAG",
1173
+ cmd
1174
+ ]]) : cmd;
1175
+ let actions;
1176
+ try {
1177
+ let command = Util_Sury$Reventless.fromJson(Message$Reventless.fillMissingDefaults(M.ExtensionPoint.commandSchema, synthesised, []), M.ExtensionPoint.commandSchema);
1178
+ let decodedAs = Message$Reventless.variantNameOfJson(Message$Reventless.encode(command, M.ExtensionPoint.commandSchema));
1179
+ actions = decodedAs !== cmd ? ({
1180
+ TAG: "Error",
1181
+ _0: `a synthesised "` + cmd + `" decoded as "` + decodedAs + `"`
1182
+ }) : ({
1183
+ TAG: "Ok",
1184
+ _0: M.mapIncomingCommand(probeId, command, probeMeta)
1185
+ });
1186
+ } catch (exn) {
1187
+ actions = {
1188
+ TAG: "Error",
1189
+ _0: `the mapping raised on a synthesised "` + cmd + `"`
1190
+ };
1191
+ }
1192
+ if (actions.TAG === "Ok") {
1193
+ actions._0.forEach(action => {
1194
+ if (action.TAG !== "PublishCommand") {
1195
+ return;
1196
+ }
1197
+ acceptedObserved.push([
1198
+ cmd,
1199
+ Message$Reventless.variantNameOfJson(Message$Reventless.encode(action._1, M.Delegate.commandSchema))
1200
+ ]);
1201
+ });
1202
+ return;
1203
+ }
1204
+ tableWarnings.push(label + `: not checked against the arms — ` + actions._0 + `.`);
1205
+ });
1206
+ acceptedObserved.forEach(param => {
1207
+ let routed = param[1];
1208
+ let cmd = param[0];
1209
+ if (!M.acceptedCommands.some(param => param.name === cmd ? param.toCommandTypes.includes(routed) : false)) {
1210
+ tableFailures.push(label + `: "` + cmd + `" routes to "` + routed + `", which acceptedCommands does not declare.`);
1211
+ return;
1212
+ }
1213
+ });
1214
+ let acceptedTable = Stdlib_Option.getOr(epAccepted[epName], {});
1215
+ M.acceptedCommands.forEach(param => {
1216
+ let key = epName + `.` + param.name;
1217
+ acceptedTable[key] = Stdlib_Option.getOr(acceptedTable[key], []).concat(qualify(name, param.toCommandTypes));
1218
+ });
1219
+ epAccepted[epName] = acceptedTable;
1220
+ let mapOutgoing = M.mapOutgoingEvent;
1221
+ if (mapOutgoing !== undefined) {
1222
+ let observed = [];
1223
+ let followed = [];
1224
+ sourceNames.forEach(src => {
1225
+ let synthesised = DcbTag$Reventless.isVariantPayloadBearing(M.Delegate.eventSchema, src) ? Object.fromEntries([[
1226
+ "TAG",
1227
+ src
1228
+ ]]) : src;
1229
+ let outcome;
1230
+ try {
1231
+ let event = Util_Sury$Reventless.fromJson(Message$Reventless.fillMissingDefaults(M.Delegate.eventSchema, synthesised, []), M.Delegate.eventSchema);
1232
+ let decodedAs = Message$Reventless.variantNameOfJson(Message$Reventless.encode(event, M.Delegate.eventSchema));
1233
+ if (decodedAs !== src) {
1234
+ outcome = {
1235
+ TAG: "Error",
1236
+ _0: `a synthesised "` + src + `" decoded as "` + decodedAs + `"`
1237
+ };
1238
+ } else {
1239
+ let actions = mapOutgoing(probeId, event, probeMeta, probeQueryEngine);
1240
+ actions.forEach(action => {
1241
+ switch (action.TAG) {
1242
+ case "PublishEvent" :
1243
+ observed.push([
1244
+ src,
1245
+ Message$Reventless.variantNameOfJson(Message$Reventless.encode(action._1, M.ExtensionPoint.eventSchema))
1246
+ ]);
1247
+ return;
1248
+ case "PublishEventAsync" :
1249
+ case "HandleDirective" :
1250
+ return;
1251
+ }
1252
+ });
1253
+ outcome = actions.some(action => {
1254
+ switch (action.TAG) {
1255
+ case "PublishEventAsync" :
1256
+ return true;
1257
+ case "PublishEvent" :
1258
+ case "HandleDirective" :
1259
+ return false;
1260
+ }
1261
+ }) ? ({
1262
+ TAG: "Error",
1263
+ _0: `"` + src + `" publishes behind a promise`
1264
+ }) : ({
1265
+ TAG: "Ok",
1266
+ _0: undefined
1267
+ });
1268
+ }
1269
+ } catch (exn) {
1270
+ outcome = {
1271
+ TAG: "Error",
1272
+ _0: `the mapping raised on a synthesised "` + src + `"`
1273
+ };
1274
+ }
1275
+ if (outcome.TAG === "Ok") {
1276
+ followed.push(src);
1277
+ return;
1278
+ }
1279
+ tableWarnings.push(label + `: not checked against the arms — ` + outcome._0 + `.`);
1280
+ });
1281
+ let match$1 = translationTableDrift(label, M.publishedEvents, observed, followed);
1282
+ pushAll(tableFailures, match$1[0]);
1283
+ pushAll(tableWarnings, match$1[1]);
1284
+ } else if (M.publishedEvents.length !== 0) {
1285
+ tableFailures.push(label + `: publishedEvents declares ` + M.publishedEvents.length.toString() + ` event(s), but the mapping has no mapOutgoingEvent and publishes nothing.`);
1286
+ }
1287
+ let declaredNames = M.publishedEvents.map(p => p.name);
1288
+ publishedNames.filter(p => !declaredNames.includes(p)).forEach(p => {
1289
+ tableWarnings.push(label + `: the extension point publishes "` + p + `", which no arm produces.`);
1290
+ });
1291
+ let table = Stdlib_Option.getOr(epPublished[epName], {});
1292
+ M.publishedEvents.forEach(param => {
1293
+ let key = epName + `.` + param.name;
1294
+ table[key] = Stdlib_Option.getOr(table[key], []).concat(qualify(name, param.fromEventTypes));
1295
+ });
1296
+ epPublished[epName] = table;
917
1297
  });
918
1298
  let extensionPointDefs = Object.entries(epByName).map(param => {
919
1299
  let match = param[1];
1300
+ let epName = param[0];
920
1301
  return {
921
- name: param[0],
1302
+ name: epName,
922
1303
  delegateNames: Belt_SetString.toArray(Belt_SetString.fromArray(match[0])),
923
1304
  sourceEventTypes: Belt_SetString.toArray(Belt_SetString.fromArray(match[1])),
924
- commandTypes: Belt_SetString.toArray(Belt_SetString.fromArray(match[2]))
1305
+ commandTypes: Belt_SetString.toArray(Belt_SetString.fromArray(match[2])),
1306
+ publishedEvents: Object.entries(Stdlib_Option.getOr(epPublished[epName], {})).map(param => ({
1307
+ name: param[0],
1308
+ fromEventTypes: Belt_SetString.toArray(Belt_SetString.fromArray(param[1]))
1309
+ })),
1310
+ acceptedCommands: Object.entries(Stdlib_Option.getOr(epAccepted[epName], {})).map(param => ({
1311
+ name: param[0],
1312
+ toCommandTypes: Belt_SetString.toArray(Belt_SetString.fromArray(param[1]))
1313
+ }))
925
1314
  };
926
1315
  });
1316
+ Object.entries(epByName).forEach(param => {
1317
+ let epName = param[0];
1318
+ let handled = Object.keys(Stdlib_Option.getOr(epAccepted[epName], {}));
1319
+ Belt_SetString.toArray(Belt_SetString.fromArray(param[1][2])).forEach(cmd => {
1320
+ if (!handled.includes(cmd)) {
1321
+ tableWarnings.push(epName + `: the extension point accepts "` + cmd + `", which no arm handles — a sender gets no error and nothing happens.`);
1322
+ return;
1323
+ }
1324
+ });
1325
+ });
1326
+ reportTranslationTables(name, tableFailures, tableWarnings);
927
1327
  checkDeclaredTransitions(name, stateChangeDefs.concat(aggregateDefs), lifecycleStatesByView);
928
1328
  checkLifecycleTopology(name, stateChangeDefs.concat(aggregateDefs), lifecycleStatesByView);
929
1329
  reportRetiredStates(name, retiredFailures, retiredUnchecked);
@@ -938,7 +1338,9 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
938
1338
  extensions: extensionDefs,
939
1339
  extensionPoints: extensionPointDefs,
940
1340
  requiredStores: requiredStores,
941
- requiredStoreDeclarations: requiredStoreDeclarations
1341
+ requiredStoreDeclarations: requiredStoreDeclarations,
1342
+ requiredCapabilities: requiredCapabilities,
1343
+ traitDeclarations: traitDeclarations
942
1344
  };
943
1345
  }
944
1346
 
@@ -956,6 +1358,14 @@ export {
956
1358
  checkRetiredValue,
957
1359
  reportRetiredStates,
958
1360
  lifecycleStatesFromStateSchema,
1361
+ translationTableFailures,
1362
+ translationTableDrift,
1363
+ handledTableFailures,
1364
+ commandTableFailures,
1365
+ probeId,
1366
+ probeMeta,
1367
+ probeQueryEngine,
1368
+ reportTranslationTables,
959
1369
  checkDeclaredTransitions,
960
1370
  lifecycleTopologyFindings,
961
1371
  checkLifecycleTopology,
@@ -74,12 +74,9 @@ module Make = (Spec: Spec) => {
74
74
  ),
75
75
  ]
76
76
  }
77
- // PluginConnected / PluginReconnected / PluginDeactivated for peer
78
- // plugins used to call DoConnectPlugin / DoDisconnectPlugin here so
79
- // each plugin's EC Lambda could subscribe / unsubscribe its own
80
- // queue to peer EP topics. Admin owns that work now
81
- // (manageSubscriptions in EventCollectorEntryPoint.mjs) — these
82
- // cases drop to no-ops.
77
+ // A peer's PluginConnected / PluginDeactivated used to subscribe or
78
+ // unsubscribe this plugin's own queue to peer EP topics. Admin owns that
79
+ // now (manageSubscriptions), so these drop to no-ops.
83
80
  | _ => []
84
81
  }
85
82
  }
@@ -45,6 +45,14 @@ function Make(Spec) {
45
45
  }];
46
46
  }
47
47
  };
48
+ let handledEvents = [{
49
+ name: "UnknownPluginDetected",
50
+ toCommandTypes: [
51
+ "ConnectPlugin",
52
+ "RegisterUiFragment"
53
+ ]
54
+ }];
55
+ let issuedCommands = [];
48
56
  let $$let = ExtensionMapping$ReventlessInfra.NoDelegate.Id;
49
57
  let ConnectPluginMapping = ExtensionMapping$ReventlessInfra.Make({
50
58
  ExtensionPoint: {
@@ -67,12 +75,16 @@ function Make(Spec) {
67
75
  errorSchema: ExtensionMapping$ReventlessInfra.NoDelegate.errorSchema,
68
76
  commandSchema: ExtensionMapping$ReventlessInfra.NoDelegate.commandSchema,
69
77
  moduleUrl: ExtensionMapping$ReventlessInfra.NoDelegate.moduleUrl,
70
- commandAuthorization: ExtensionMapping$ReventlessInfra.NoDelegate.commandAuthorization
78
+ commandAuthorization: ExtensionMapping$ReventlessInfra.NoDelegate.commandAuthorization,
79
+ commandTransition: ExtensionMapping$ReventlessInfra.NoDelegate.commandTransition,
80
+ traits: ExtensionMapping$ReventlessInfra.NoDelegate.traits
71
81
  },
72
82
  moduleUrl: PluginExtensionPointSpec$ReventlessInfra.moduleUrl,
73
83
  delegateModuleUrl: delegateModuleUrl,
74
84
  mapIncomingEvent: mapIncomingEvent,
75
- mapOutgoingEvent: undefined
85
+ mapOutgoingEvent: undefined,
86
+ handledEvents: handledEvents,
87
+ issuedCommands: issuedCommands
76
88
  });
77
89
  let moduleUrl = import.meta.url;
78
90
  let mappings = [ConnectPluginMapping];
@@ -1,7 +1,6 @@
1
1
  @@reventless.spec
2
2
 
3
3
  open ReventlessInfra.ExtensionPointMapping
4
- open Reventless.Plugin
5
4
 
6
5
  module PluginExtensionPointSpec = ReventlessInfra.PluginExtensionPointSpec
7
6