@reventlessdev/reventless-core 3.0.0-alpha.255 → 3.0.0-alpha.257
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/package.json +9 -9
- package/src/adapter/Monitoring/Monitoring.res +79 -5
- package/src/adapter/Monitoring/Monitoring.res.mjs +42 -2
- package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
- package/src/admin/Platform_ComponentDefinitionsApi.res +5 -1
- package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +5 -1
- package/src/admin/Platform_UiSlots.res +21 -0
- package/src/admin/Platform_UiSlots.res.mjs +15 -0
- package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +1 -1
- package/src/components/Aggregate/Aggregate_Callback.res +13 -4
- package/src/components/Aggregate/Aggregate_Callback.res.mjs +6 -5
- package/src/components/Api/GraphQL_FragmentGenerator.res +2 -0
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +4 -0
- package/src/components/Api/MCP_SchemaGenerator.res.mjs +2 -2
- package/src/components/Api/SchemaType.res +8 -0
- package/src/components/Api/SchemaType.res.mjs +15 -5
- package/src/components/Api/SuryToJsonSchema.res +70 -12
- package/src/components/Api/SuryToJsonSchema.res.mjs +43 -16
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +8 -3
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +1 -1
- package/src/plugin/component/Plugin.res +1 -0
- package/src/plugin/component/Plugin_Builder.res +2 -0
- package/src/plugin/component/Plugin_Builder.res.mjs +3 -2
- package/src/plugin/component/Plugin_Structure.res +105 -10
- package/src/plugin/component/Plugin_Structure.res.mjs +71 -27
- package/src/plugin/lifecycle/PluginsReadModelSpec.res +1 -1
- package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +1 -1
- package/src/util/Validation.res +1 -1
- package/src/util/Validation.res.mjs +2 -2
- package/tests/adapter/MonitoringTest.res +46 -7
- package/tests/adapter/MonitoringTest.res.mjs +47 -5
- package/tests/admin/AdminApiSchemaDriftTest.res +1 -0
- package/tests/admin/AdminApiSchemaDriftTest.res.mjs +3 -0
- package/tests/admin/Platform_Admin_StructureTest.res.mjs +2 -14
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +1 -0
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +2 -0
- package/tests/admin/Platform_PluginStructuresApiTest.res +8 -14
- package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +3 -3
- package/tests/aggregate/AggregateCacheTest.res.mjs +1 -1
- package/tests/aggregate/AggregateConflictTest.res.mjs +1 -1
- package/tests/aggregate/AggregateFixtures.res.mjs +1 -1
- package/tests/aggregate/AggregateSnapshotTest.res.mjs +1 -1
- package/tests/api/GraphQL_FragmentGeneratorTest.res +6 -4
- package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +5 -5
- package/tests/api/SuryToJsonSchemaTest.res.mjs +57 -57
- package/tests/api/TaggedUnionSchemaTest.res.mjs +1 -1
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +1 -1
- package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +1 -1
- package/tests/dcb/DcbFixtures.res.mjs +1 -1
- package/tests/eventmapper/EventMapperFixtures.res.mjs +1 -1
- package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +1 -1
- package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +1 -1
- package/tests/fixtures/plugin-lifecycle/2026-07-22-versionconnected-no-requiredstores.json +1 -1
- package/tests/fixtures/plugin-lifecycle/2026-07-22-versionsuperseded-nested-definitions.json +1 -1
- package/tests/fixtures/plugin-lifecycle/2026-07-28-versionconnected-requiredstores-only.json +1 -1
- package/tests/fixtures/plugin-lifecycle/2026-07-30-versionconnected-declarations-without-annotation.json +1 -1
- package/tests/fixtures/plugin-lifecycle/2026-07-30-versiondisconnected-declarations-without-annotation.json +1 -1
- package/tests/fixtures/plugin-lifecycle/README.md +12 -0
- package/tests/logger/LogFormatTest.res +1 -1
- package/tests/logger/LogFormatTest.res.mjs +1 -1
- package/tests/plugin/ManifestVisibilityTest.res.mjs +1 -1
- package/tests/plugin/PluginDefinitionRequiredScalarsTest.res +1 -1
- package/tests/plugin/PluginDefinitionScalars.res +13 -8
- package/tests/plugin/PluginDefinitionScalars.res.mjs +13 -5
- package/tests/plugin/PluginStructureAccessTest.res.mjs +1 -1
- package/tests/plugin/PluginStructureTest.res +184 -0
- package/tests/plugin/PluginStructureTest.res.mjs +142 -17
- package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsChangePhoto.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsReserveStock.res +14 -1
- package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +9 -2
- package/tests/plugin/StateChangeSlice/PsTypoStore.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsUploadAvatar.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsUploadImages.res.mjs +1 -1
- package/tests/plugin/pluginDefinitionRequiredScalars.txt +13 -14
- package/tests/util/CommandPublisherTest.res.mjs +1 -1
|
@@ -29,6 +29,8 @@ import * as GraphQL_FragmentGenerator$ReventlessCore from "../../components/Api/
|
|
|
29
29
|
|
|
30
30
|
let log = Logger$ReventlessCore.fromEnv();
|
|
31
31
|
|
|
32
|
+
let declaredTransitionsOnly = Stdlib_Option.isSome(process.env.REVENTLESS_DECLARED_TRANSITIONS_ONLY);
|
|
33
|
+
|
|
32
34
|
function isLabelShape(_t) {
|
|
33
35
|
while (true) {
|
|
34
36
|
let t = _t;
|
|
@@ -342,18 +344,20 @@ function checkDeclaredTransitions(pluginName, writables, lifecycleStatesByView)
|
|
|
342
344
|
});
|
|
343
345
|
if (known.length === 0) {
|
|
344
346
|
unvalidated.contents = unvalidated.contents + 1 | 0;
|
|
345
|
-
|
|
346
|
-
declared.filter(state => !known.includes(state)).forEach(state => {
|
|
347
|
-
failures.push(w.name + `.` + cmd.name + ` declares state "` + state + `", which none of its ` + (`linked views declare — ` + w.linkedViews.join(", ") + ` know ` + known.join(", ") + `.`));
|
|
348
|
-
});
|
|
347
|
+
return;
|
|
349
348
|
}
|
|
349
|
+
let match = cmd.allowedStatesSource;
|
|
350
|
+
let hint = match === "derived" ? " This edge came from the component's own scenarios, so the committed lifecycle model is stale." : "";
|
|
351
|
+
declared.filter(state => !known.includes(state)).forEach(state => {
|
|
352
|
+
failures.push(w.name + `.` + cmd.name + ` declares state "` + state + `", which none of its ` + (`linked views declare — ` + w.linkedViews.join(", ") + ` know ` + known.join(", ") + `.` + hint));
|
|
353
|
+
});
|
|
350
354
|
});
|
|
351
355
|
});
|
|
352
356
|
if (unvalidated.contents > 0) {
|
|
353
357
|
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.`);
|
|
354
358
|
}
|
|
355
359
|
if (failures.length !== 0) {
|
|
356
|
-
return Stdlib_JsError.throwWithMessage(pluginName + `: a
|
|
360
|
+
return Stdlib_JsError.throwWithMessage(pluginName + `: a transition names states that do not exist.\n` + failures.join("\n"));
|
|
357
361
|
}
|
|
358
362
|
}
|
|
359
363
|
|
|
@@ -449,14 +453,14 @@ function labelFieldsFromStateSchema(entityName, stateSchema) {
|
|
|
449
453
|
}
|
|
450
454
|
|
|
451
455
|
function extractReferences(properties) {
|
|
452
|
-
return
|
|
453
|
-
let
|
|
454
|
-
return
|
|
455
|
-
fieldName:
|
|
456
|
+
return Object.entries(properties).flatMap(param => Reference$Reventless.collectFieldTargets(param[0], param[1]).map(param => {
|
|
457
|
+
let target = param[1];
|
|
458
|
+
return {
|
|
459
|
+
fieldName: param[0],
|
|
456
460
|
entity: target.entity,
|
|
457
461
|
plugin: target.plugin
|
|
458
|
-
}
|
|
459
|
-
});
|
|
462
|
+
};
|
|
463
|
+
}));
|
|
460
464
|
}
|
|
461
465
|
|
|
462
466
|
function toEventDef(v) {
|
|
@@ -464,7 +468,7 @@ function toEventDef(v) {
|
|
|
464
468
|
let references = extractReferences(properties);
|
|
465
469
|
return {
|
|
466
470
|
name: variantName,
|
|
467
|
-
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(v)),
|
|
471
|
+
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(undefined, undefined, v)),
|
|
468
472
|
references: references
|
|
469
473
|
};
|
|
470
474
|
};
|
|
@@ -592,7 +596,7 @@ function annotateArgTypes(schema, argTypes) {
|
|
|
592
596
|
return schema;
|
|
593
597
|
}
|
|
594
598
|
|
|
595
|
-
function toCommandDef(isAggregate, mutationFieldFor, parentSchema, commandAuthorization, commandTransition, v) {
|
|
599
|
+
function toCommandDef(isAggregate, mutationFieldFor, parentSchema, commandAuthorization, commandTransition, derivedEdgeFor, v) {
|
|
596
600
|
let mkDef = (variantName, properties) => {
|
|
597
601
|
let match = commandLevelAndId(isAggregate, variantName, properties);
|
|
598
602
|
let references = extractReferences(properties);
|
|
@@ -600,8 +604,36 @@ function toCommandDef(isAggregate, mutationFieldFor, parentSchema, commandAuthor
|
|
|
600
604
|
TAG: variantName
|
|
601
605
|
}) : variantName;
|
|
602
606
|
let declared = commandTransition(syntheticCommand);
|
|
603
|
-
let
|
|
604
|
-
let
|
|
607
|
+
let derived = derivedEdgeFor(variantName);
|
|
608
|
+
let unrestricted = Transition$Reventless.isUnrestricted(declared);
|
|
609
|
+
let match$1 = unrestricted ? undefined : Stdlib_Option.flatMap(derived, d => {
|
|
610
|
+
if (d.allowedStates.length !== 0) {
|
|
611
|
+
return d.allowedStates;
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
let match$2 = Transition$Reventless.allowedStates(declared);
|
|
615
|
+
let match$3 = match$1 !== undefined ? [
|
|
616
|
+
match$1,
|
|
617
|
+
"derived"
|
|
618
|
+
] : (
|
|
619
|
+
match$2 !== undefined ? [
|
|
620
|
+
match$2,
|
|
621
|
+
"declared"
|
|
622
|
+
] : [
|
|
623
|
+
undefined,
|
|
624
|
+
unrestricted ? "unrestricted" : undefined
|
|
625
|
+
]
|
|
626
|
+
);
|
|
627
|
+
let observed = Stdlib_Option.flatMap(derived, d => {
|
|
628
|
+
let match = d.targets;
|
|
629
|
+
if (match.length !== 1) {
|
|
630
|
+
return;
|
|
631
|
+
} else {
|
|
632
|
+
return match[0];
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
let targetState = observed !== undefined ? observed : Transition$Reventless.targetState(declared);
|
|
636
|
+
let level = Stdlib_Option.getOr(Stdlib_Option.flatMap(derived, d => d.level), match[0]);
|
|
605
637
|
let apiExposed = false;
|
|
606
638
|
if (!ApiNoApiHelpers$ReventlessCore.isNoApi(parentSchema)) {
|
|
607
639
|
let excluded = ApiNoApiHelpers$ReventlessCore.getExcludedVariants(parentSchema);
|
|
@@ -609,17 +641,18 @@ function toCommandDef(isAggregate, mutationFieldFor, parentSchema, commandAuthor
|
|
|
609
641
|
}
|
|
610
642
|
let requiredAccess = accessKeysFor(commandAuthorization(syntheticCommand));
|
|
611
643
|
let mutationField = apiExposed ? mutationFieldFor(variantName) : "";
|
|
612
|
-
let jsonSchema = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(v);
|
|
644
|
+
let jsonSchema = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(apiExposed, mutationField, v);
|
|
613
645
|
let annotatedSchema = apiExposed ? Stdlib_Option.mapOr(GraphQL_FragmentGenerator$ReventlessCore.mutationArgTypes(mutationField, v), jsonSchema, __x => annotateArgTypes(jsonSchema, __x)) : jsonSchema;
|
|
614
646
|
return {
|
|
615
647
|
name: variantName,
|
|
616
648
|
schema: JSON.stringify(annotatedSchema),
|
|
617
|
-
level:
|
|
649
|
+
level: level,
|
|
618
650
|
aggregateIdField: match[1],
|
|
619
651
|
mutationField: mutationField,
|
|
620
652
|
references: references,
|
|
621
|
-
allowedStates:
|
|
653
|
+
allowedStates: match$3[0],
|
|
622
654
|
targetState: targetState,
|
|
655
|
+
allowedStatesSource: match$3[1],
|
|
623
656
|
apiExposed: apiExposed,
|
|
624
657
|
requiredAccess: requiredAccess,
|
|
625
658
|
ownerField: Owner$Reventless.fieldNamesOfProperties(properties)[0]
|
|
@@ -649,11 +682,12 @@ function toCommandDef(isAggregate, mutationFieldFor, parentSchema, commandAuthor
|
|
|
649
682
|
}
|
|
650
683
|
}
|
|
651
684
|
|
|
652
|
-
function extractCommandDefs(isAggregate, mutationFieldFor, commandAuthorization, commandTransition, commandSchema) {
|
|
685
|
+
function extractCommandDefs(isAggregate, mutationFieldFor, commandAuthorization, commandTransition, derivedEdgeForOpt, commandSchema) {
|
|
686
|
+
let derivedEdgeFor = derivedEdgeForOpt !== undefined ? derivedEdgeForOpt : param => {};
|
|
653
687
|
if (commandSchema.type === "anyOf") {
|
|
654
|
-
return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(isAggregate, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, v));
|
|
688
|
+
return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(isAggregate, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, v));
|
|
655
689
|
} else {
|
|
656
|
-
return Stdlib_Option.mapOr(toCommandDef(isAggregate, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, commandSchema), [], def => [def]);
|
|
690
|
+
return Stdlib_Option.mapOr(toCommandDef(isAggregate, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, commandSchema), [], def => [def]);
|
|
657
691
|
}
|
|
658
692
|
}
|
|
659
693
|
|
|
@@ -675,7 +709,7 @@ function queryableDefFromSpec(plugin, name, stateSchema, authorization, visibili
|
|
|
675
709
|
return {
|
|
676
710
|
name: name,
|
|
677
711
|
queryField: qf.listFieldName,
|
|
678
|
-
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(stateSchema)),
|
|
712
|
+
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(undefined, undefined, stateSchema)),
|
|
679
713
|
consumedEventTypes: consumedEventTypes,
|
|
680
714
|
linkedWriteSide: linkedWriteSide,
|
|
681
715
|
labelField: label.field,
|
|
@@ -695,7 +729,7 @@ function queryableDefFromSpec(plugin, name, stateSchema, authorization, visibili
|
|
|
695
729
|
};
|
|
696
730
|
}
|
|
697
731
|
|
|
698
|
-
function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChangeSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, extensionsOpt, extensionPointsOpt, componentChaptersOpt) {
|
|
732
|
+
function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChangeSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, extensionsOpt, extensionPointsOpt, componentChaptersOpt, lifecycleModelOpt) {
|
|
699
733
|
let aggregates = aggregatesOpt !== undefined ? aggregatesOpt : [];
|
|
700
734
|
let readModels = readModelsOpt !== undefined ? readModelsOpt : [];
|
|
701
735
|
let stateViewSlices = stateViewSlicesOpt !== undefined ? stateViewSlicesOpt : [];
|
|
@@ -706,6 +740,15 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
706
740
|
let extensions = extensionsOpt !== undefined ? extensionsOpt : [];
|
|
707
741
|
let extensionPoints = extensionPointsOpt !== undefined ? extensionPointsOpt : [];
|
|
708
742
|
let componentChapters = componentChaptersOpt !== undefined ? componentChaptersOpt : ({});
|
|
743
|
+
let lifecycleModel = lifecycleModelOpt !== undefined ? lifecycleModelOpt : [];
|
|
744
|
+
let model = declaredTransitionsOnly ? [] : lifecycleModel;
|
|
745
|
+
let derivedEdgeFor = (component, command) => model.find(e => {
|
|
746
|
+
if (e.component === component) {
|
|
747
|
+
return e.command === command;
|
|
748
|
+
} else {
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
});
|
|
709
752
|
let qualify = (prefix, names) => names.map(n => prefix + "." + n);
|
|
710
753
|
let scsProduced = stateChangeSlices.map(SCS => [
|
|
711
754
|
SCS.Spec.name,
|
|
@@ -975,7 +1018,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
975
1018
|
return {
|
|
976
1019
|
name: R.Spec.name,
|
|
977
1020
|
queryField: qf.listFieldName,
|
|
978
|
-
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(stateSchema)),
|
|
1021
|
+
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(undefined, undefined, stateSchema)),
|
|
979
1022
|
consumedEventTypes: consumed,
|
|
980
1023
|
linkedWriteSide: linkedWriteSideFor(consumed),
|
|
981
1024
|
labelField: label.field,
|
|
@@ -1007,7 +1050,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1007
1050
|
return {
|
|
1008
1051
|
name: SVS.Spec.name,
|
|
1009
1052
|
queryField: qf.listFieldName,
|
|
1010
|
-
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(stateSchema)),
|
|
1053
|
+
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(undefined, undefined, stateSchema)),
|
|
1011
1054
|
consumedEventTypes: consumed,
|
|
1012
1055
|
linkedWriteSide: linkedWriteSideFor(consumed),
|
|
1013
1056
|
labelField: label.field,
|
|
@@ -1033,7 +1076,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1033
1076
|
let consumed = match$1[1];
|
|
1034
1077
|
return {
|
|
1035
1078
|
name: SCS.Spec.name,
|
|
1036
|
-
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),
|
|
1079
|
+
commands: extractCommandDefs(false, variantName => Api_Naming$ReventlessCore.sliceMutationFieldFor(name, SCS.Spec.name, SCS.Spec.commandSchema, variantName), SCS.Spec.commandAuthorization, SCS.Spec.commandTransition, extra => derivedEdgeFor(SCS.Spec.name, extra), SCS.Spec.commandSchema),
|
|
1037
1080
|
producedEventTypes: produced,
|
|
1038
1081
|
consumedEventTypes: consumed,
|
|
1039
1082
|
linkedViews: linkedSvsFor(produced),
|
|
@@ -1048,7 +1091,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1048
1091
|
let produced = match[1];
|
|
1049
1092
|
return {
|
|
1050
1093
|
name: A.Spec.name,
|
|
1051
|
-
commands: extractCommandDefs(true, variantName => Api_Naming$ReventlessCore.aggregateMutationField(name, A.Spec.name, variantName), A.Spec.commandAuthorization, A.Spec.commandTransition, A.Spec.commandSchema),
|
|
1094
|
+
commands: extractCommandDefs(true, variantName => Api_Naming$ReventlessCore.aggregateMutationField(name, A.Spec.name, variantName), A.Spec.commandAuthorization, A.Spec.commandTransition, extra => derivedEdgeFor(A.Spec.name, extra), A.Spec.commandSchema),
|
|
1052
1095
|
producedEventTypes: produced,
|
|
1053
1096
|
consumedEventTypes: [],
|
|
1054
1097
|
linkedViews: linkedSvsFor(produced).concat(linkedReadModelsFor(A.Spec.name)),
|
|
@@ -1342,6 +1385,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1342
1385
|
|
|
1343
1386
|
export {
|
|
1344
1387
|
log,
|
|
1388
|
+
declaredTransitionsOnly,
|
|
1345
1389
|
isLabelShape,
|
|
1346
1390
|
isLifecycleShape,
|
|
1347
1391
|
conventionalLabelNames,
|
|
@@ -71,7 +71,7 @@ type state = {
|
|
|
71
71
|
// Admin's manageSubscriptions uses this to wire cross-plugin SNS subscriptions
|
|
72
72
|
// from this plugin's DCB topic → peer EventCollectors (and vice-versa). None
|
|
73
73
|
// for pure-aggregate plugins or for plugins persisted before Phase 4.
|
|
74
|
-
dcbEventLog:
|
|
74
|
+
dcbEventLog: option<Reventless.Plugin.dcbEventLogDefinition>,
|
|
75
75
|
// Business role of the plugin (from pluginDefinition.kind). Lets the admin Plugins
|
|
76
76
|
// view segregate PlatformInfrastructure / Commercial / Marketplace from Domain.
|
|
77
77
|
// @scan opts the field into server-side equality filtering so the connection gains
|
|
@@ -26,7 +26,7 @@ let stateSchema = Sury.$schema(s => ({
|
|
|
26
26
|
apiSchemaFragment: s.m(Sury.$option(Sury.json)),
|
|
27
27
|
apiTarget: s.m(Sury.$option(Sury.string)),
|
|
28
28
|
structure: s.m(Sury.$option(Sury.json)),
|
|
29
|
-
dcbEventLog: s.m(Plugin$Reventless.
|
|
29
|
+
dcbEventLog: s.m(Sury.$option(Plugin$Reventless.dcbEventLogDefinitionSchema)),
|
|
30
30
|
kind: s.m(Sury.$option(Plugin$Reventless.pluginKindSchema)),
|
|
31
31
|
otherConnectedVersions: s.m(Sury.array(Plugin$Reventless.versionSchema))
|
|
32
32
|
}));
|
package/src/util/Validation.res
CHANGED
|
@@ -31,7 +31,7 @@ let merge: (t<'a, 'b>, t<'a, 'b>) => t<'a, 'b> = (v1, v2) => {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
let defaultErrorHandler = err => {
|
|
34
|
-
EffectLogger.logError(~comp=__MODULE__, err->
|
|
34
|
+
EffectLogger.logError(~comp=__MODULE__, err->Reventless.Util_Sury.exnMessage)->Effect.runSync
|
|
35
35
|
Failure("Couldn't resolve promise.")
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
3
|
import * as Effect from "effect/Effect";
|
|
5
4
|
import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
5
|
+
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
6
6
|
import * as EffectLogger$ReventlessCore from "./EffectLogger.res.mjs";
|
|
7
7
|
|
|
8
8
|
function merge(v1, v2) {
|
|
@@ -105,7 +105,7 @@ function merge(v1, v2) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
function defaultErrorHandler(err) {
|
|
108
|
-
Effect.runSync(EffectLogger$ReventlessCore.logError("Validation-ReventlessCore", undefined,
|
|
108
|
+
Effect.runSync(EffectLogger$ReventlessCore.logError("Validation-ReventlessCore", undefined, Util_Sury$Reventless.exnMessage(err, undefined)));
|
|
109
109
|
return {
|
|
110
110
|
TAG: "Failure",
|
|
111
111
|
_0: "Couldn't resolve promise."
|
|
@@ -6,10 +6,49 @@ open JestGlobals
|
|
|
6
6
|
let stubResource: ReventlessInfra.Adapter.resource = %raw(`{}`)
|
|
7
7
|
|
|
8
8
|
describe("Monitoring", () => {
|
|
9
|
-
testSync("
|
|
10
|
-
// No backend registered — the default Noop backend swallows the call.
|
|
9
|
+
testSync("no backend registered: notify is silent and does not throw", () => {
|
|
11
10
|
Monitoring.notify(~kind=CommandHandler, ~name="before", ~component=stubResource)
|
|
12
11
|
expect(true)->toBe(true)
|
|
12
|
+
Monitoring.reset()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// The case the dead-letter sink is: a module that provisions at import time announces
|
|
16
|
+
// itself before any statement of the deploy program has run, so it is *always* earlier
|
|
17
|
+
// than the `use` call. Dropping those announcements made that one unit unmonitorable no
|
|
18
|
+
// matter what a backend did.
|
|
19
|
+
testSync("an announcement made before any backend is registered reaches the first one", () => {
|
|
20
|
+
let recorded: array<(Monitoring.unitKind, string, option<string>)> = []
|
|
21
|
+
module Late: Monitoring.Backend = {
|
|
22
|
+
let onProvisioned = (~kind, ~name, ~component as _, ~plugin, ~platform as _, ~logLocator as _) =>
|
|
23
|
+
recorded->Array.push((kind, name, plugin))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Provisioned at import time, inside a construct scope that is long gone by the time
|
|
27
|
+
// anyone registers — so the owner must be captured now, not at delivery.
|
|
28
|
+
let prev = ResourceAttribution.enter(~platform="online-shop", ~plugin="Ordering")
|
|
29
|
+
Monitoring.notify(~kind=DeadLetterSink, ~name="DeadLetterQueue", ~component=stubResource)
|
|
30
|
+
ResourceAttribution.restore(prev)
|
|
31
|
+
Monitoring.notify(~kind=Scheduler, ~name="Heartbeat", ~component=stubResource)
|
|
32
|
+
|
|
33
|
+
expect(recorded)->toEqual([])
|
|
34
|
+
|
|
35
|
+
Monitoring.use(module(Late: Monitoring.Backend))
|
|
36
|
+
|
|
37
|
+
expect(recorded)->toEqual([
|
|
38
|
+
(Monitoring.DeadLetterSink, "DeadLetterQueue", Some("Ordering")),
|
|
39
|
+
(Monitoring.Scheduler, "Heartbeat", None),
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
// Drained: a second registration does not receive them again.
|
|
43
|
+
let second: array<string> = []
|
|
44
|
+
module Other: Monitoring.Backend = {
|
|
45
|
+
let onProvisioned = (~kind as _, ~name, ~component as _, ~plugin as _, ~platform as _, ~logLocator as _) =>
|
|
46
|
+
second->Array.push(name)
|
|
47
|
+
}
|
|
48
|
+
Monitoring.use(module(Other: Monitoring.Backend))
|
|
49
|
+
expect(second)->toEqual([])
|
|
50
|
+
|
|
51
|
+
Monitoring.reset()
|
|
13
52
|
})
|
|
14
53
|
|
|
15
54
|
testSync("use registers a backend that receives every notify, role and name", () => {
|
|
@@ -32,8 +71,8 @@ describe("Monitoring", () => {
|
|
|
32
71
|
(Monitoring.Other("Counter"), "ProductCounter"),
|
|
33
72
|
])
|
|
34
73
|
|
|
35
|
-
// Restore the
|
|
36
|
-
Monitoring.
|
|
74
|
+
// Restore the initial state so registry and buffer don't leak to other cases.
|
|
75
|
+
Monitoring.reset()
|
|
37
76
|
})
|
|
38
77
|
|
|
39
78
|
testSync("notify forwards the static name and component resource to the backend", () => {
|
|
@@ -50,7 +89,7 @@ describe("Monitoring", () => {
|
|
|
50
89
|
| None => expect("no notify received")->toBe("a notify")
|
|
51
90
|
}
|
|
52
91
|
|
|
53
|
-
Monitoring.
|
|
92
|
+
Monitoring.reset()
|
|
54
93
|
})
|
|
55
94
|
|
|
56
95
|
testSync("notify delivers the ambient plugin/platform inside a construct scope, None outside", () => {
|
|
@@ -78,7 +117,7 @@ describe("Monitoring", () => {
|
|
|
78
117
|
(None, None),
|
|
79
118
|
])
|
|
80
119
|
|
|
81
|
-
Monitoring.
|
|
120
|
+
Monitoring.reset()
|
|
82
121
|
})
|
|
83
122
|
|
|
84
123
|
// Unlike the owner, the locator is not ambient — the seam only forwards what the provisioning
|
|
@@ -105,6 +144,6 @@ describe("Monitoring", () => {
|
|
|
105
144
|
|
|
106
145
|
expect(seen)->toEqual([true, false])
|
|
107
146
|
|
|
108
|
-
Monitoring.
|
|
147
|
+
Monitoring.reset()
|
|
109
148
|
})
|
|
110
149
|
})
|
|
@@ -7,9 +7,51 @@ import * as ResourceAttribution$ReventlessCore from "../../src/ResourceAttributi
|
|
|
7
7
|
let stubResource = {};
|
|
8
8
|
|
|
9
9
|
globalThis.describe("Monitoring", () => {
|
|
10
|
-
globalThis.test("
|
|
10
|
+
globalThis.test("no backend registered: notify is silent and does not throw", () => {
|
|
11
11
|
Monitoring$ReventlessCore.notify("CommandHandler", "before", stubResource, undefined);
|
|
12
12
|
globalThis.expect(true).toBe(true);
|
|
13
|
+
Monitoring$ReventlessCore.reset();
|
|
14
|
+
});
|
|
15
|
+
globalThis.test("an announcement made before any backend is registered reaches the first one", () => {
|
|
16
|
+
let recorded = [];
|
|
17
|
+
let onProvisioned = (kind, name, param, plugin, param$1, param$2) => {
|
|
18
|
+
recorded.push([
|
|
19
|
+
kind,
|
|
20
|
+
name,
|
|
21
|
+
plugin
|
|
22
|
+
]);
|
|
23
|
+
};
|
|
24
|
+
let Late = {
|
|
25
|
+
onProvisioned: onProvisioned
|
|
26
|
+
};
|
|
27
|
+
let prev = ResourceAttribution$ReventlessCore.enter("online-shop", "Ordering");
|
|
28
|
+
Monitoring$ReventlessCore.notify("DeadLetterSink", "DeadLetterQueue", stubResource, undefined);
|
|
29
|
+
ResourceAttribution$ReventlessCore.restore(prev);
|
|
30
|
+
Monitoring$ReventlessCore.notify("Scheduler", "Heartbeat", stubResource, undefined);
|
|
31
|
+
globalThis.expect(recorded).toEqual([]);
|
|
32
|
+
Monitoring$ReventlessCore.use(Late);
|
|
33
|
+
globalThis.expect(recorded).toEqual([
|
|
34
|
+
[
|
|
35
|
+
"DeadLetterSink",
|
|
36
|
+
"DeadLetterQueue",
|
|
37
|
+
"Ordering"
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
"Scheduler",
|
|
41
|
+
"Heartbeat",
|
|
42
|
+
undefined
|
|
43
|
+
]
|
|
44
|
+
]);
|
|
45
|
+
let second = [];
|
|
46
|
+
let onProvisioned$1 = (param, name, param$1, param$2, param$3, param$4) => {
|
|
47
|
+
second.push(name);
|
|
48
|
+
};
|
|
49
|
+
let Other = {
|
|
50
|
+
onProvisioned: onProvisioned$1
|
|
51
|
+
};
|
|
52
|
+
Monitoring$ReventlessCore.use(Other);
|
|
53
|
+
globalThis.expect(second).toEqual([]);
|
|
54
|
+
Monitoring$ReventlessCore.reset();
|
|
13
55
|
});
|
|
14
56
|
globalThis.test("use registers a backend that receives every notify, role and name", () => {
|
|
15
57
|
let recorded = [];
|
|
@@ -51,7 +93,7 @@ globalThis.describe("Monitoring", () => {
|
|
|
51
93
|
"ProductCounter"
|
|
52
94
|
]
|
|
53
95
|
]);
|
|
54
|
-
Monitoring$ReventlessCore.
|
|
96
|
+
Monitoring$ReventlessCore.reset();
|
|
55
97
|
});
|
|
56
98
|
globalThis.test("notify forwards the static name and component resource to the backend", () => {
|
|
57
99
|
let seen = {
|
|
@@ -74,7 +116,7 @@ globalThis.describe("Monitoring", () => {
|
|
|
74
116
|
} else {
|
|
75
117
|
globalThis.expect("no notify received").toBe("a notify");
|
|
76
118
|
}
|
|
77
|
-
Monitoring$ReventlessCore.
|
|
119
|
+
Monitoring$ReventlessCore.reset();
|
|
78
120
|
});
|
|
79
121
|
globalThis.test("notify delivers the ambient plugin/platform inside a construct scope, None outside", () => {
|
|
80
122
|
let seen = [];
|
|
@@ -107,7 +149,7 @@ globalThis.describe("Monitoring", () => {
|
|
|
107
149
|
undefined
|
|
108
150
|
]
|
|
109
151
|
]);
|
|
110
|
-
Monitoring$ReventlessCore.
|
|
152
|
+
Monitoring$ReventlessCore.reset();
|
|
111
153
|
});
|
|
112
154
|
globalThis.test("notify forwards the log locator when given one, None when omitted", () => {
|
|
113
155
|
let stubLocator = {};
|
|
@@ -125,7 +167,7 @@ globalThis.describe("Monitoring", () => {
|
|
|
125
167
|
true,
|
|
126
168
|
false
|
|
127
169
|
]);
|
|
128
|
-
Monitoring$ReventlessCore.
|
|
170
|
+
Monitoring$ReventlessCore.reset();
|
|
129
171
|
});
|
|
130
172
|
});
|
|
131
173
|
|
|
@@ -61,6 +61,8 @@ let cmd_allowedStates = ["Draft"];
|
|
|
61
61
|
|
|
62
62
|
let cmd_targetState = "Active";
|
|
63
63
|
|
|
64
|
+
let cmd_allowedStatesSource = "declared";
|
|
65
|
+
|
|
64
66
|
let cmd_apiExposed = true;
|
|
65
67
|
|
|
66
68
|
let cmd_requiredAccess = ["catalog.write"];
|
|
@@ -76,6 +78,7 @@ let cmd = {
|
|
|
76
78
|
references: cmd_references,
|
|
77
79
|
allowedStates: cmd_allowedStates,
|
|
78
80
|
targetState: cmd_targetState,
|
|
81
|
+
allowedStatesSource: cmd_allowedStatesSource,
|
|
79
82
|
apiExposed: cmd_apiExposed,
|
|
80
83
|
requiredAccess: cmd_requiredAccess,
|
|
81
84
|
ownerField: cmd_ownerField
|
|
@@ -63,21 +63,9 @@ globalThis.describe("the admin structure's declared transitions are checked", ()
|
|
|
63
63
|
globalThis.expect(raises([Platform_Admin_Structure$ReventlessCore.pluginAggregate])).toEqual(false);
|
|
64
64
|
});
|
|
65
65
|
globalThis.test("and a state the view does not declare is caught", () => {
|
|
66
|
-
let
|
|
66
|
+
let newrecord = {...commandNamed("Activate")};
|
|
67
67
|
let bogus_name = Platform_Admin_Structure$ReventlessCore.pluginAggregate.name;
|
|
68
|
-
let bogus_commands = [
|
|
69
|
-
name: init.name,
|
|
70
|
-
schema: init.schema,
|
|
71
|
-
level: init.level,
|
|
72
|
-
aggregateIdField: init.aggregateIdField,
|
|
73
|
-
mutationField: init.mutationField,
|
|
74
|
-
references: init.references,
|
|
75
|
-
allowedStates: init.allowedStates,
|
|
76
|
-
targetState: "Conected",
|
|
77
|
-
apiExposed: init.apiExposed,
|
|
78
|
-
requiredAccess: init.requiredAccess,
|
|
79
|
-
ownerField: init.ownerField
|
|
80
|
-
}];
|
|
68
|
+
let bogus_commands = [(newrecord.targetState = "Conected", newrecord)];
|
|
81
69
|
let bogus_producedEventTypes = Platform_Admin_Structure$ReventlessCore.pluginAggregate.producedEventTypes;
|
|
82
70
|
let bogus_consumedEventTypes = Platform_Admin_Structure$ReventlessCore.pluginAggregate.consumedEventTypes;
|
|
83
71
|
let bogus_linkedViews = Platform_Admin_Structure$ReventlessCore.pluginAggregate.linkedViews;
|
|
@@ -355,6 +355,7 @@ describe("allowedStates + lifecycleField populated", () => {
|
|
|
355
355
|
references: [],
|
|
356
356
|
allowedStates: Some(["Inactive"]),
|
|
357
357
|
targetState: Some("Active"),
|
|
358
|
+
allowedStatesSource: "declared",
|
|
358
359
|
apiExposed: Some(true),
|
|
359
360
|
requiredAccess: None,
|
|
360
361
|
ownerField: None,
|
|
@@ -410,6 +410,7 @@ globalThis.describe("allowedStates + lifecycleField populated", () => {
|
|
|
410
410
|
let cmdWithStates_references = [];
|
|
411
411
|
let cmdWithStates_allowedStates = ["Inactive"];
|
|
412
412
|
let cmdWithStates_targetState = "Active";
|
|
413
|
+
let cmdWithStates_allowedStatesSource = "declared";
|
|
413
414
|
let cmdWithStates_apiExposed = true;
|
|
414
415
|
let cmdWithStates = {
|
|
415
416
|
name: "Activate",
|
|
@@ -420,6 +421,7 @@ globalThis.describe("allowedStates + lifecycleField populated", () => {
|
|
|
420
421
|
references: cmdWithStates_references,
|
|
421
422
|
allowedStates: cmdWithStates_allowedStates,
|
|
422
423
|
targetState: cmdWithStates_targetState,
|
|
424
|
+
allowedStatesSource: cmdWithStates_allowedStatesSource,
|
|
423
425
|
apiExposed: cmdWithStates_apiExposed,
|
|
424
426
|
requiredAccess: undefined,
|
|
425
427
|
ownerField: undefined
|
|
@@ -187,29 +187,23 @@ describe("absent optional collections", () => {
|
|
|
187
187
|
// The case the frozen corpus cannot reach: every fixture in it carries
|
|
188
188
|
// `"outboundTranslationSlices":[]`, so no stored payload has an outbound slice
|
|
189
189
|
// to be missing a field. A structure written before `consumedSources` existed
|
|
190
|
-
// has no such key
|
|
191
|
-
//
|
|
192
|
-
// jsonable validation inside a union payload, which is where a structure
|
|
193
|
-
// travels. `parseJsonTolerant` is what makes the absence survivable, and it is
|
|
194
|
-
// the path the Plugin aggregate replays through.
|
|
190
|
+
// has no such key. `parseJsonTolerant` is what makes the absence survivable,
|
|
191
|
+
// and it is the path the Plugin aggregate replays through.
|
|
195
192
|
testSync("an outbound slice stored before consumedSources existed still replays", () =>
|
|
196
193
|
expect(
|
|
197
|
-
|
|
198
|
-
`{"name":"ToShipper","consumedEventTypes":[],"inboundCommandTypes":[],` ++
|
|
199
|
-
`"targetName":null,"externalSystem":null,"chapter":null}`
|
|
200
|
-
)
|
|
194
|
+
`{"name":"ToShipper","consumedEventTypes":[],"inboundCommandTypes":[]}`
|
|
201
195
|
->JSON.parseOrThrow
|
|
202
196
|
->Reventless.Message.parseJsonTolerant(outboundTranslationSliceDefSchema)
|
|
203
197
|
->(slice => slice.consumedSources),
|
|
204
198
|
)->toEqual(None)
|
|
205
199
|
)
|
|
206
200
|
|
|
207
|
-
|
|
201
|
+
// Absent needs no healer: an omitted key IS the optional encoding, so strict
|
|
202
|
+
// decode takes it. `parseJsonTolerant` above is for the fields a stale payload
|
|
203
|
+
// is missing that are NOT optional.
|
|
204
|
+
testSync("an omitted optional decodes as None on the strict path", () =>
|
|
208
205
|
expect(
|
|
209
|
-
|
|
210
|
-
`{"name":"ToShipper","consumedEventTypes":[],"inboundCommandTypes":[],` ++
|
|
211
|
-
`"targetName":null,"externalSystem":null,"chapter":null,"consumedSources":null}`
|
|
212
|
-
)
|
|
206
|
+
`{"name":"ToShipper","consumedEventTypes":[],"inboundCommandTypes":[]}`
|
|
213
207
|
->JSON.parseOrThrow
|
|
214
208
|
->Reventless.Util_Sury.fromJson(outboundTranslationSliceDefSchema)
|
|
215
209
|
->(slice => slice.consumedSources),
|
|
@@ -241,10 +241,10 @@ globalThis.describe("absent optional collections", () => {
|
|
|
241
241
|
globalThis.expect(bareJson.includes("\"requiredStores\":null")).toEqual(true);
|
|
242
242
|
});
|
|
243
243
|
globalThis.test("an outbound slice stored before consumedSources existed still replays", () => {
|
|
244
|
-
globalThis.expect(Message$Reventless.parseJsonTolerant(JSON.parse(`{"name":"ToShipper","consumedEventTypes":[],"inboundCommandTypes":[]
|
|
244
|
+
globalThis.expect(Message$Reventless.parseJsonTolerant(JSON.parse(`{"name":"ToShipper","consumedEventTypes":[],"inboundCommandTypes":[]}`), Plugin$Reventless.outboundTranslationSliceDefSchema).consumedSources).toEqual(undefined);
|
|
245
245
|
});
|
|
246
|
-
globalThis.test("an
|
|
247
|
-
globalThis.expect(Util_Sury$Reventless.fromJson(JSON.parse(`{"name":"ToShipper","consumedEventTypes":[],"inboundCommandTypes":[]
|
|
246
|
+
globalThis.test("an omitted optional decodes as None on the strict path", () => {
|
|
247
|
+
globalThis.expect(Util_Sury$Reventless.fromJson(JSON.parse(`{"name":"ToShipper","consumedEventTypes":[],"inboundCommandTypes":[]}`), Plugin$Reventless.outboundTranslationSliceDefSchema).consumedSources).toEqual(undefined);
|
|
248
248
|
});
|
|
249
249
|
});
|
|
250
250
|
|
|
@@ -357,11 +357,13 @@ describe("GraphQL_FragmentGenerator.mutationArgTypes", () => {
|
|
|
357
357
|
expect((typeOf("tip"), typeOf("total")))->toEqual((Some("MoneyInput"), Some("MoneyInput!")))
|
|
358
358
|
)
|
|
359
359
|
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
360
|
+
// `orderId` differs from what the JSON-Schema type alone suggests — a plain
|
|
361
|
+
// string there, an `ID!` here — which is the narrower reason a client cannot
|
|
362
|
+
// derive even the scalars itself. `itemCount` is an `Int!` on both sides: sury
|
|
363
|
+
// carries the whole-number format and the walk now keeps it, so a quantity is
|
|
364
|
+
// no longer a field a client could legally send `2.5` in.
|
|
363
365
|
testSync("reports the scalar the server chose, not the JSON-Schema one", () =>
|
|
364
|
-
expect((typeOf("orderId"), typeOf("itemCount")))->toEqual((Some("ID!"), Some("
|
|
366
|
+
expect((typeOf("orderId"), typeOf("itemCount")))->toEqual((Some("ID!"), Some("Int!")))
|
|
365
367
|
)
|
|
366
368
|
|
|
367
369
|
// The property that makes publishing worth more than re-deriving downstream:
|