@reventlessdev/reventless-core 3.0.0-alpha.266 → 3.0.0-alpha.268
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 +26 -0
- package/package.json +5 -5
- package/src/Projection.res +0 -10
- package/src/Projection.res.mjs +0 -14
- package/src/ProjectionMapper.res +8 -2
- package/src/ProjectionMapper.res.mjs +3 -3
- package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
- package/src/admin/UiFragmentRegistry/StateView/UiFragments.res.mjs +3 -0
- package/src/components/EventMapper/EventMapper_Builder.res +19 -1
- package/src/components/EventMapper/EventMapper_Builder.res.mjs +10 -1
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +11 -1
- package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +2 -1
- package/src/components/StateViewSlice/StateViewSlice_Callback.res +8 -1
- package/src/components/StateViewSlice/StateViewSlice_Callback.res.mjs +2 -1
- package/src/plugin/component/Plugin_Structure.res +139 -15
- package/src/plugin/component/Plugin_Structure.res.mjs +110 -25
- package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +0 -1
- package/tests/dcb/DcbStateChangeSliceTest.res.mjs +0 -3
- package/tests/eventmapper/EventMapperFixtures.res +2 -2
- package/tests/eventmapper/EventMapperFixtures.res.mjs +14 -2
- package/tests/plugin/IdentityReferenceTest.res +118 -0
- package/tests/plugin/IdentityReferenceTest.res.mjs +204 -0
- package/tests/plugin/PluginStructureAccessTest.res.mjs +14 -1
- package/tests/plugin/PluginStructureTest.res.mjs +42 -10
- package/tests/plugin/StateView/PsAnnotatedView.res.mjs +3 -0
- package/tests/plugin/StateView/PsAvailableProductsView.res.mjs +3 -0
- package/tests/plugin/StateView/PsCategoriesView.res.mjs +3 -0
- package/tests/plugin/StateView/PsCustomersView.res.mjs +3 -0
- package/tests/plugin/StateView/PsGatedView.res.mjs +3 -0
- package/tests/plugin/StateView/PsOrdersView.res.mjs +3 -0
- package/tests/plugin/StateView/PsShipmentsView.res.mjs +3 -0
|
@@ -12,6 +12,7 @@ import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js
|
|
|
12
12
|
import * as Trait$Reventless from "@reventlessdev/reventless-spec/src/types/Trait.res.mjs";
|
|
13
13
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
14
14
|
import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
|
|
15
|
+
import * as Semantic$Reventless from "@reventlessdev/reventless-spec/src/semantic/Semantic.res.mjs";
|
|
15
16
|
import * as Lifecycle$Reventless from "@reventlessdev/reventless-spec/src/types/Lifecycle.res.mjs";
|
|
16
17
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
17
18
|
import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/components/Reference.res.mjs";
|
|
@@ -444,20 +445,62 @@ function labelFieldsFromStateSchema(entityName, stateSchema) {
|
|
|
444
445
|
};
|
|
445
446
|
}
|
|
446
447
|
|
|
447
|
-
function
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
return
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
448
|
+
function identityReference(identityViews, fieldName, fieldSchema, declared) {
|
|
449
|
+
let key = Semantic$Reventless.fieldIdentityKey(fieldSchema);
|
|
450
|
+
if (key === undefined) {
|
|
451
|
+
return declared;
|
|
452
|
+
}
|
|
453
|
+
let keyed = Stdlib_Option.getOr(identityViews.viewsByKey[key], []);
|
|
454
|
+
if (declared.length !== 0) {
|
|
455
|
+
declared.forEach(param => {
|
|
456
|
+
let target = param[1];
|
|
457
|
+
if (Stdlib_Option.isNone(target.plugin) && identityViews.viewNames.includes(target.entity) && !keyed.includes(target.entity)) {
|
|
458
|
+
return identityViews.report(fieldName + ` is a ` + key + `, and its @ref names ` + target.entity + `, which is not keyed by it.`);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
return declared;
|
|
462
|
+
}
|
|
463
|
+
let len = keyed.length;
|
|
464
|
+
if (len !== 1) {
|
|
465
|
+
if (len !== 0) {
|
|
466
|
+
identityViews.report(fieldName + ` is a ` + key + `, and ` + keyed.join(" and ") + ` are all keyed by it. Say which it references with @ref.`);
|
|
467
|
+
return [];
|
|
468
|
+
} else {
|
|
469
|
+
identityViews.report(fieldName + ` is a ` + key + `, and no view in this plugin is keyed by it, so it references nothing. List the identity in a view, or reference another plugin's with @ref("Plugin.View").`);
|
|
470
|
+
return [];
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
let view = keyed[0];
|
|
474
|
+
return [[
|
|
475
|
+
fieldName,
|
|
476
|
+
{
|
|
477
|
+
entity: view,
|
|
478
|
+
plugin: undefined,
|
|
479
|
+
identity: key
|
|
480
|
+
}
|
|
481
|
+
]];
|
|
456
482
|
}
|
|
457
483
|
|
|
458
|
-
function
|
|
484
|
+
function extractReferences(identityViews, minted, properties) {
|
|
485
|
+
return Object.entries(properties).flatMap(param => {
|
|
486
|
+
let fieldSchema = param[1];
|
|
487
|
+
let fieldName = param[0];
|
|
488
|
+
let declared = Reference$Reventless.collectFieldTargets(fieldName, fieldSchema);
|
|
489
|
+
let targets = identityViews !== undefined && Primitive_object.notequal(minted, fieldName) ? identityReference(identityViews, fieldName, fieldSchema, declared) : declared;
|
|
490
|
+
return targets.map(param => {
|
|
491
|
+
let target = param[1];
|
|
492
|
+
return {
|
|
493
|
+
fieldName: param[0],
|
|
494
|
+
entity: target.entity,
|
|
495
|
+
plugin: target.plugin
|
|
496
|
+
};
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function toEventDef(identityViews, v) {
|
|
459
502
|
let mkDef = (variantName, properties) => {
|
|
460
|
-
let references = extractReferences(properties);
|
|
503
|
+
let references = extractReferences(identityViews, undefined, properties);
|
|
461
504
|
return {
|
|
462
505
|
name: variantName,
|
|
463
506
|
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(undefined, undefined, v)),
|
|
@@ -488,16 +531,16 @@ function toEventDef(v) {
|
|
|
488
531
|
}
|
|
489
532
|
}
|
|
490
533
|
|
|
491
|
-
function extractEventDefs(eventSchema) {
|
|
534
|
+
function extractEventDefs(identityViews, eventSchema) {
|
|
492
535
|
if (eventSchema.type === "anyOf") {
|
|
493
|
-
return Stdlib_Array.filterMap(eventSchema.anyOf, toEventDef);
|
|
536
|
+
return Stdlib_Array.filterMap(eventSchema.anyOf, v => toEventDef(identityViews, v));
|
|
494
537
|
} else {
|
|
495
|
-
return Stdlib_Option.mapOr(toEventDef(eventSchema), [], def => [def]);
|
|
538
|
+
return Stdlib_Option.mapOr(toEventDef(identityViews, eventSchema), [], def => [def]);
|
|
496
539
|
}
|
|
497
540
|
}
|
|
498
541
|
|
|
499
542
|
function extractErrorDefs(errorSchema) {
|
|
500
|
-
return extractEventDefs(errorSchema).map(param => ({
|
|
543
|
+
return extractEventDefs(undefined, errorSchema).map(param => ({
|
|
501
544
|
name: param.name,
|
|
502
545
|
schema: param.schema,
|
|
503
546
|
references: param.references
|
|
@@ -597,10 +640,10 @@ function annotateArgTypes(schema, argTypes) {
|
|
|
597
640
|
return schema;
|
|
598
641
|
}
|
|
599
642
|
|
|
600
|
-
function toCommandDef(isAggregate, partitionKey, mutationFieldFor, parentSchema, commandAuthorization, commandTransition, derivedEdgeFor, v) {
|
|
643
|
+
function toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, parentSchema, commandAuthorization, commandTransition, derivedEdgeFor, v) {
|
|
601
644
|
let mkDef = (variantName, properties) => {
|
|
602
645
|
let match = commandLevelAndId(isAggregate, partitionKey, variantName, properties);
|
|
603
|
-
let
|
|
646
|
+
let aggregateIdField = match[1];
|
|
604
647
|
let syntheticCommand = DcbTag$Reventless.isVariantPayloadBearing(parentSchema, variantName) ? ({
|
|
605
648
|
TAG: variantName
|
|
606
649
|
}) : variantName;
|
|
@@ -635,6 +678,7 @@ function toCommandDef(isAggregate, partitionKey, mutationFieldFor, parentSchema,
|
|
|
635
678
|
});
|
|
636
679
|
let targetState = observed !== undefined ? observed : Transition$Reventless.targetState(declared);
|
|
637
680
|
let level = Stdlib_Option.getOr(Stdlib_Option.flatMap(derived, d => d.level), match[0]);
|
|
681
|
+
let references = extractReferences(identityViews, level === "Collection" ? aggregateIdField : undefined, properties);
|
|
638
682
|
let apiExposed = false;
|
|
639
683
|
if (!ApiNoApiHelpers$ReventlessCore.isNoApi(parentSchema)) {
|
|
640
684
|
let excluded = ApiNoApiHelpers$ReventlessCore.getExcludedVariants(parentSchema);
|
|
@@ -648,7 +692,7 @@ function toCommandDef(isAggregate, partitionKey, mutationFieldFor, parentSchema,
|
|
|
648
692
|
name: variantName,
|
|
649
693
|
schema: JSON.stringify(annotatedSchema),
|
|
650
694
|
level: level,
|
|
651
|
-
aggregateIdField:
|
|
695
|
+
aggregateIdField: aggregateIdField,
|
|
652
696
|
mutationField: mutationField,
|
|
653
697
|
references: references,
|
|
654
698
|
allowedStates: match$3[0],
|
|
@@ -683,12 +727,12 @@ function toCommandDef(isAggregate, partitionKey, mutationFieldFor, parentSchema,
|
|
|
683
727
|
}
|
|
684
728
|
}
|
|
685
729
|
|
|
686
|
-
function extractCommandDefs(isAggregate, partitionKey, mutationFieldFor, commandAuthorization, commandTransition, derivedEdgeForOpt, commandSchema) {
|
|
730
|
+
function extractCommandDefs(identityViews, isAggregate, partitionKey, mutationFieldFor, commandAuthorization, commandTransition, derivedEdgeForOpt, commandSchema) {
|
|
687
731
|
let derivedEdgeFor = derivedEdgeForOpt !== undefined ? derivedEdgeForOpt : param => {};
|
|
688
732
|
if (commandSchema.type === "anyOf") {
|
|
689
|
-
return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(isAggregate, partitionKey, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, v));
|
|
733
|
+
return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, v));
|
|
690
734
|
} else {
|
|
691
|
-
return Stdlib_Option.mapOr(toCommandDef(isAggregate, partitionKey, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, commandSchema), [], def => [def]);
|
|
735
|
+
return Stdlib_Option.mapOr(toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, commandSchema), [], def => [def]);
|
|
692
736
|
}
|
|
693
737
|
}
|
|
694
738
|
|
|
@@ -1101,6 +1145,46 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1101
1145
|
};
|
|
1102
1146
|
}
|
|
1103
1147
|
let partitionFailures = [];
|
|
1148
|
+
let keyIdentityOf = (stateSchema, idField) => Stdlib_Option.map(idField, field => {
|
|
1149
|
+
let tmp;
|
|
1150
|
+
tmp = stateSchema.type === "object" ? Stdlib_Option.flatMap(stateSchema.properties[field], Semantic$Reventless.identityKey) : undefined;
|
|
1151
|
+
return Stdlib_Option.getOr(tmp, field);
|
|
1152
|
+
});
|
|
1153
|
+
let viewKeys = readModels.map((R, i) => {
|
|
1154
|
+
let def = readModelDefs[i];
|
|
1155
|
+
let declared = Semantic$Reventless.identityKey(R.Spec.Id.schema);
|
|
1156
|
+
let key = declared !== undefined ? declared : keyIdentityOf(R.Spec.stateSchema, def.idField);
|
|
1157
|
+
return [
|
|
1158
|
+
R.Spec.name,
|
|
1159
|
+
key
|
|
1160
|
+
];
|
|
1161
|
+
}).concat(stateViewSlices.map((SVS, i) => {
|
|
1162
|
+
let def = stateViewDefs[i];
|
|
1163
|
+
return [
|
|
1164
|
+
SVS.Spec.name,
|
|
1165
|
+
keyIdentityOf(SVS.Spec.stateSchema, def.idField)
|
|
1166
|
+
];
|
|
1167
|
+
}));
|
|
1168
|
+
let viewsByKey = {};
|
|
1169
|
+
viewKeys.forEach(param => {
|
|
1170
|
+
let view = param[0];
|
|
1171
|
+
Stdlib_Option.forEach(param[1], k => {
|
|
1172
|
+
viewsByKey[k] = Stdlib_Option.getOr(viewsByKey[k], []).concat([view]);
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
1175
|
+
let reported = new Set();
|
|
1176
|
+
let identityViews_viewNames = viewKeys.map(param => param[0]);
|
|
1177
|
+
let identityViews_report = message => {
|
|
1178
|
+
if (!reported.has(message)) {
|
|
1179
|
+
reported.add(message);
|
|
1180
|
+
return log.warn("Plugin_Structure", undefined, name + `: ` + message);
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
let identityViews = {
|
|
1184
|
+
viewsByKey: viewsByKey,
|
|
1185
|
+
viewNames: identityViews_viewNames,
|
|
1186
|
+
report: identityViews_report
|
|
1187
|
+
};
|
|
1104
1188
|
let stateChangeDefs = stateChangeSlices.map((SCS, i) => {
|
|
1105
1189
|
let match = scsProduced[i];
|
|
1106
1190
|
let produced = match[1];
|
|
@@ -1108,12 +1192,12 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1108
1192
|
let consumed = match$1[1];
|
|
1109
1193
|
return {
|
|
1110
1194
|
name: SCS.Spec.name,
|
|
1111
|
-
commands: extractCommandDefs(false, partitionBySlice[SCS.Spec.name], 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),
|
|
1195
|
+
commands: extractCommandDefs(identityViews, false, partitionBySlice[SCS.Spec.name], 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),
|
|
1112
1196
|
producedEventTypes: produced,
|
|
1113
1197
|
consumedEventTypes: consumed,
|
|
1114
1198
|
linkedViews: linkedSvsFor(produced),
|
|
1115
1199
|
consistencyRead: consistencyReadFor(consumed),
|
|
1116
|
-
events: extractEventDefs(SCS.Spec.eventSchema),
|
|
1200
|
+
events: extractEventDefs(identityViews, SCS.Spec.eventSchema),
|
|
1117
1201
|
errors: extractErrorDefs(SCS.Spec.errorSchema),
|
|
1118
1202
|
chapter: componentChapters[SCS.Spec.name]
|
|
1119
1203
|
};
|
|
@@ -1123,12 +1207,12 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1123
1207
|
let produced = match[1];
|
|
1124
1208
|
return {
|
|
1125
1209
|
name: A.Spec.name,
|
|
1126
|
-
commands: extractCommandDefs(true, undefined, 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),
|
|
1210
|
+
commands: extractCommandDefs(identityViews, true, undefined, 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),
|
|
1127
1211
|
producedEventTypes: produced,
|
|
1128
1212
|
consumedEventTypes: [],
|
|
1129
1213
|
linkedViews: linkedSvsFor(produced).concat(linkedReadModelsFor(A.Spec.name)),
|
|
1130
1214
|
consistencyRead: undefined,
|
|
1131
|
-
events: extractEventDefs(A.Spec.eventSchema),
|
|
1215
|
+
events: extractEventDefs(identityViews, A.Spec.eventSchema),
|
|
1132
1216
|
errors: extractErrorDefs(A.Spec.errorSchema),
|
|
1133
1217
|
chapter: componentChapters[A.Spec.name]
|
|
1134
1218
|
};
|
|
@@ -1452,6 +1536,7 @@ export {
|
|
|
1452
1536
|
checkLifecycleTopology,
|
|
1453
1537
|
labelFieldSourceToString,
|
|
1454
1538
|
labelFieldsFromStateSchema,
|
|
1539
|
+
identityReference,
|
|
1455
1540
|
extractReferences,
|
|
1456
1541
|
toEventDef,
|
|
1457
1542
|
extractEventDefs,
|
|
@@ -59,7 +59,6 @@ let SliceBehavior = {
|
|
|
59
59
|
let SliceHandler = StateChangeSlice_Callback$ReventlessCore.Make({
|
|
60
60
|
name: DcbFixtures$ReventlessCore.TestCommandSpec.name,
|
|
61
61
|
moduleUrl: DcbFixtures$ReventlessCore.TestCommandSpec.moduleUrl,
|
|
62
|
-
Id: Id$Reventless.$$String,
|
|
63
62
|
consumedEventSchema: DcbFixtures$ReventlessCore.TestCommandSpec.consumedEventSchema,
|
|
64
63
|
errorSchema: DcbFixtures$ReventlessCore.TestCommandSpec.errorSchema,
|
|
65
64
|
eventSchema: DcbFixtures$ReventlessCore.TestCommandSpec.eventSchema,
|
|
@@ -55,7 +55,6 @@ let TestCommandBehavior = {
|
|
|
55
55
|
let TestHandler = StateChangeSlice_Callback$ReventlessCore.Make({
|
|
56
56
|
name: DcbFixtures$ReventlessCore.TestCommandSpec.name,
|
|
57
57
|
moduleUrl: DcbFixtures$ReventlessCore.TestCommandSpec.moduleUrl,
|
|
58
|
-
Id: Id$Reventless.$$String,
|
|
59
58
|
consumedEventSchema: DcbFixtures$ReventlessCore.TestCommandSpec.consumedEventSchema,
|
|
60
59
|
errorSchema: DcbFixtures$ReventlessCore.TestCommandSpec.errorSchema,
|
|
61
60
|
eventSchema: DcbFixtures$ReventlessCore.TestCommandSpec.eventSchema,
|
|
@@ -155,7 +154,6 @@ let EventualSpec = {
|
|
|
155
154
|
let StrongHandler = StateChangeSlice_Callback$ReventlessCore.Make({
|
|
156
155
|
name: name,
|
|
157
156
|
moduleUrl: moduleUrl$1,
|
|
158
|
-
Id: Id$Reventless.$$String,
|
|
159
157
|
consumedEventSchema: consumedEventSchema,
|
|
160
158
|
errorSchema: errorSchema,
|
|
161
159
|
eventSchema: eventSchema,
|
|
@@ -169,7 +167,6 @@ let StrongHandler = StateChangeSlice_Callback$ReventlessCore.Make({
|
|
|
169
167
|
let EventualHandler = StateChangeSlice_Callback$ReventlessCore.Make({
|
|
170
168
|
name: name$1,
|
|
171
169
|
moduleUrl: moduleUrl$2,
|
|
172
|
-
Id: Id$Reventless.$$String,
|
|
173
170
|
consumedEventSchema: consumedEventSchema$1,
|
|
174
171
|
errorSchema: errorSchema$1,
|
|
175
172
|
eventSchema: eventSchema$1,
|
|
@@ -5,7 +5,7 @@ open Reventless.Projection
|
|
|
5
5
|
// ─────────────────────────────────────────────────────────────
|
|
6
6
|
|
|
7
7
|
module SourceSpec = {
|
|
8
|
-
module Id = Reventless.Id.
|
|
8
|
+
module Id = Reventless.Id.StringPure
|
|
9
9
|
let name = "SourceAggregate"
|
|
10
10
|
|
|
11
11
|
@schema
|
|
@@ -22,7 +22,7 @@ module SourceSpec = {
|
|
|
22
22
|
// ─────────────────────────────────────────────────────────────
|
|
23
23
|
|
|
24
24
|
module TargetSpec = {
|
|
25
|
-
module Id = Reventless.Id.
|
|
25
|
+
module Id = Reventless.Id.StringPure
|
|
26
26
|
let name = "ItemCatalog"
|
|
27
27
|
|
|
28
28
|
@schema
|
|
@@ -81,11 +81,23 @@ function project(msg) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
let ItemMapping = Projection$Reventless.Mapping.Make({
|
|
84
|
-
Id:
|
|
84
|
+
Id: {
|
|
85
|
+
schema: Id$Reventless.StringPure.schema,
|
|
86
|
+
make: prim => prim,
|
|
87
|
+
makeFromString: prim => prim,
|
|
88
|
+
toString: prim => prim,
|
|
89
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
90
|
+
},
|
|
85
91
|
name: name,
|
|
86
92
|
eventSchema: eventSchema
|
|
87
93
|
})({
|
|
88
|
-
Id:
|
|
94
|
+
Id: {
|
|
95
|
+
schema: Id$Reventless.StringPure.schema,
|
|
96
|
+
make: prim => prim,
|
|
97
|
+
makeFromString: prim => prim,
|
|
98
|
+
toString: prim => prim,
|
|
99
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
100
|
+
},
|
|
89
101
|
name: name$1,
|
|
90
102
|
stateSchema: stateSchema,
|
|
91
103
|
subIdConfig: undefined
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// A typed id finds the view that lists it without an `@ref`, whenever exactly one
|
|
2
|
+
// view is keyed by its identity. Where the type cannot decide, it says so rather
|
|
3
|
+
// than guessing: which list a reference means is a product decision.
|
|
4
|
+
|
|
5
|
+
open JestGlobals
|
|
6
|
+
|
|
7
|
+
module ProductId = Reventless.Id.Make({
|
|
8
|
+
let key = "productId"
|
|
9
|
+
})
|
|
10
|
+
module CustomerId = Reventless.Id.Make({
|
|
11
|
+
let key = "customerId"
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
let refsOf = (~viewsByKey, ~minted=?, schema: S.t<'a>) => {
|
|
15
|
+
let reports = []
|
|
16
|
+
let identityViews: Plugin_Structure.identityViews = {
|
|
17
|
+
viewsByKey: Dict.fromArray(viewsByKey),
|
|
18
|
+
viewNames: viewsByKey->Array.flatMap(((_, views)) => views),
|
|
19
|
+
report: message => reports->Array.push(message),
|
|
20
|
+
}
|
|
21
|
+
let properties = switch schema->S.castToUnknown {
|
|
22
|
+
| Object({properties}) => properties
|
|
23
|
+
| _ => Dict.make()
|
|
24
|
+
}
|
|
25
|
+
let refs =
|
|
26
|
+
Plugin_Structure.extractReferences(~identityViews, ~minted?, properties)->Array.map(r => (
|
|
27
|
+
r.fieldName,
|
|
28
|
+
r.entity,
|
|
29
|
+
))
|
|
30
|
+
(refs, reports)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let order = S.schema(s =>
|
|
34
|
+
{
|
|
35
|
+
"buyer": s.matches(CustomerId.schema),
|
|
36
|
+
"productIds": s.matches(S.array(ProductId.schema)),
|
|
37
|
+
"note": s.matches(S.string),
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
describe("a reference derived from the type", () => {
|
|
42
|
+
testSync("exactly one view keyed by the identity is the reference", () => {
|
|
43
|
+
let (refs, reports) = refsOf(
|
|
44
|
+
~viewsByKey=[("customerId", ["Customers"]), ("productId", ["AvailableProducts"])],
|
|
45
|
+
order,
|
|
46
|
+
)
|
|
47
|
+
expect(refs)->toEqual([("buyer", "Customers"), ("productIds", "AvailableProducts")])
|
|
48
|
+
expect(reports)->toEqual([])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
testSync("several views keyed by it ask for @ref instead of guessing", () => {
|
|
52
|
+
let (refs, reports) = refsOf(
|
|
53
|
+
~viewsByKey=[("customerId", ["Customers"]), ("productId", ["Products", "ProductDemand"])],
|
|
54
|
+
order,
|
|
55
|
+
)
|
|
56
|
+
expect(refs)->toEqual([("buyer", "Customers")])
|
|
57
|
+
expect(reports->Array.some(r => r->String.includes("Products and ProductDemand")))->toBe(true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
testSync("no view keyed by it is reported", () => {
|
|
61
|
+
let (refs, reports) = refsOf(~viewsByKey=[("productId", ["AvailableProducts"])], order)
|
|
62
|
+
expect(refs)->toEqual([("productIds", "AvailableProducts")])
|
|
63
|
+
expect(reports->Array.some(r => r->String.includes("no view in this plugin")))->toBe(true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// A create form mints this id; a list of the rows that already exist is the
|
|
67
|
+
// wrong control for it.
|
|
68
|
+
testSync("the id a creating command mints references nothing", () => {
|
|
69
|
+
let (refs, reports) = refsOf(
|
|
70
|
+
~viewsByKey=[("customerId", ["Customers"]), ("productId", ["AvailableProducts"])],
|
|
71
|
+
~minted="buyer",
|
|
72
|
+
order,
|
|
73
|
+
)
|
|
74
|
+
expect((refs, reports))->toEqual(([("productIds", "AvailableProducts")], []))
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
testSync("an untyped field is left to its name, as before", () => {
|
|
78
|
+
let (refs, reports) = refsOf(
|
|
79
|
+
~viewsByKey=[("customerId", ["Customers"])],
|
|
80
|
+
S.schema(s => {"customerId": s.matches(S.string)}),
|
|
81
|
+
)
|
|
82
|
+
expect((refs, reports))->toEqual(([], []))
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe("a declared @ref on a typed id", () => {
|
|
87
|
+
let referenced = S.schema(s =>
|
|
88
|
+
{"buyer": s.matches(CustomerId.schema->Reventless.Reference.mark("Products"))}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
testSync("is kept, and checked against the views keyed by the identity", () => {
|
|
92
|
+
let (refs, reports) = refsOf(
|
|
93
|
+
~viewsByKey=[("customerId", ["Customers"]), ("productId", ["Products"])],
|
|
94
|
+
referenced,
|
|
95
|
+
)
|
|
96
|
+
expect(refs)->toEqual([("buyer", "Products")])
|
|
97
|
+
expect(reports->Array.some(r => r->String.includes("not keyed by it")))->toBe(true)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
testSync("naming a view keyed by the identity is fine", () => {
|
|
101
|
+
let chosen = S.schema(
|
|
102
|
+
s => {"buyer": s.matches(CustomerId.schema->Reventless.Reference.mark("Customers"))},
|
|
103
|
+
)
|
|
104
|
+
let (refs, reports) = refsOf(
|
|
105
|
+
~viewsByKey=[("customerId", ["Customers", "VipCustomers"])],
|
|
106
|
+
chosen,
|
|
107
|
+
)
|
|
108
|
+
expect((refs, reports))->toEqual(([("buyer", "Customers")], []))
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
testSync("stands on a minted id: only the derived one steps aside", () => {
|
|
112
|
+
let chosen = S.schema(
|
|
113
|
+
s => {"buyer": s.matches(CustomerId.schema->Reventless.Reference.mark("Customers"))},
|
|
114
|
+
)
|
|
115
|
+
let (refs, _) = refsOf(~viewsByKey=[("customerId", ["Customers"])], ~minted="buyer", chosen)
|
|
116
|
+
expect(refs)->toEqual([("buyer", "Customers")])
|
|
117
|
+
})
|
|
118
|
+
})
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
5
|
+
import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/components/Reference.res.mjs";
|
|
6
|
+
import * as Plugin_Structure$ReventlessCore from "../../src/plugin/component/Plugin_Structure.res.mjs";
|
|
7
|
+
|
|
8
|
+
let ProductId = Id$Reventless.Make({
|
|
9
|
+
key: "productId"
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
let CustomerId = Id$Reventless.Make({
|
|
13
|
+
key: "customerId"
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function refsOf(viewsByKey, minted, schema) {
|
|
17
|
+
let reports = [];
|
|
18
|
+
let identityViews_viewsByKey = Object.fromEntries(viewsByKey);
|
|
19
|
+
let identityViews_viewNames = viewsByKey.flatMap(param => param[1]);
|
|
20
|
+
let identityViews_report = message => {
|
|
21
|
+
reports.push(message);
|
|
22
|
+
};
|
|
23
|
+
let identityViews = {
|
|
24
|
+
viewsByKey: identityViews_viewsByKey,
|
|
25
|
+
viewNames: identityViews_viewNames,
|
|
26
|
+
report: identityViews_report
|
|
27
|
+
};
|
|
28
|
+
let properties;
|
|
29
|
+
properties = schema.type === "object" ? schema.properties : ({});
|
|
30
|
+
let refs = Plugin_Structure$ReventlessCore.extractReferences(identityViews, minted, properties).map(r => [
|
|
31
|
+
r.fieldName,
|
|
32
|
+
r.entity
|
|
33
|
+
]);
|
|
34
|
+
return [
|
|
35
|
+
refs,
|
|
36
|
+
reports
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let order = Sury.$schema(s => ({
|
|
41
|
+
buyer: s.m(CustomerId.schema),
|
|
42
|
+
productIds: s.m(Sury.array(ProductId.schema)),
|
|
43
|
+
note: s.m(Sury.string)
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
globalThis.describe("a reference derived from the type", () => {
|
|
47
|
+
globalThis.test("exactly one view keyed by the identity is the reference", () => {
|
|
48
|
+
let match = refsOf([
|
|
49
|
+
[
|
|
50
|
+
"customerId",
|
|
51
|
+
["Customers"]
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"productId",
|
|
55
|
+
["AvailableProducts"]
|
|
56
|
+
]
|
|
57
|
+
], undefined, order);
|
|
58
|
+
globalThis.expect(match[0]).toEqual([
|
|
59
|
+
[
|
|
60
|
+
"buyer",
|
|
61
|
+
"Customers"
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
"productIds",
|
|
65
|
+
"AvailableProducts"
|
|
66
|
+
]
|
|
67
|
+
]);
|
|
68
|
+
globalThis.expect(match[1]).toEqual([]);
|
|
69
|
+
});
|
|
70
|
+
globalThis.test("several views keyed by it ask for @ref instead of guessing", () => {
|
|
71
|
+
let match = refsOf([
|
|
72
|
+
[
|
|
73
|
+
"customerId",
|
|
74
|
+
["Customers"]
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
"productId",
|
|
78
|
+
[
|
|
79
|
+
"Products",
|
|
80
|
+
"ProductDemand"
|
|
81
|
+
]
|
|
82
|
+
]
|
|
83
|
+
], undefined, order);
|
|
84
|
+
globalThis.expect(match[0]).toEqual([[
|
|
85
|
+
"buyer",
|
|
86
|
+
"Customers"
|
|
87
|
+
]]);
|
|
88
|
+
globalThis.expect(match[1].some(r => r.includes("Products and ProductDemand"))).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
globalThis.test("no view keyed by it is reported", () => {
|
|
91
|
+
let match = refsOf([[
|
|
92
|
+
"productId",
|
|
93
|
+
["AvailableProducts"]
|
|
94
|
+
]], undefined, order);
|
|
95
|
+
globalThis.expect(match[0]).toEqual([[
|
|
96
|
+
"productIds",
|
|
97
|
+
"AvailableProducts"
|
|
98
|
+
]]);
|
|
99
|
+
globalThis.expect(match[1].some(r => r.includes("no view in this plugin"))).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
globalThis.test("the id a creating command mints references nothing", () => {
|
|
102
|
+
let match = refsOf([
|
|
103
|
+
[
|
|
104
|
+
"customerId",
|
|
105
|
+
["Customers"]
|
|
106
|
+
],
|
|
107
|
+
[
|
|
108
|
+
"productId",
|
|
109
|
+
["AvailableProducts"]
|
|
110
|
+
]
|
|
111
|
+
], "buyer", order);
|
|
112
|
+
globalThis.expect([
|
|
113
|
+
match[0],
|
|
114
|
+
match[1]
|
|
115
|
+
]).toEqual([
|
|
116
|
+
[[
|
|
117
|
+
"productIds",
|
|
118
|
+
"AvailableProducts"
|
|
119
|
+
]],
|
|
120
|
+
[]
|
|
121
|
+
]);
|
|
122
|
+
});
|
|
123
|
+
globalThis.test("an untyped field is left to its name, as before", () => {
|
|
124
|
+
let match = refsOf([[
|
|
125
|
+
"customerId",
|
|
126
|
+
["Customers"]
|
|
127
|
+
]], undefined, Sury.$schema(s => ({
|
|
128
|
+
customerId: s.m(Sury.string)
|
|
129
|
+
})));
|
|
130
|
+
globalThis.expect([
|
|
131
|
+
match[0],
|
|
132
|
+
match[1]
|
|
133
|
+
]).toEqual([
|
|
134
|
+
[],
|
|
135
|
+
[]
|
|
136
|
+
]);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
globalThis.describe("a declared @ref on a typed id", () => {
|
|
141
|
+
let referenced = Sury.$schema(s => ({
|
|
142
|
+
buyer: s.m(Reference$Reventless.mark(CustomerId.schema, undefined, undefined, "Products"))
|
|
143
|
+
}));
|
|
144
|
+
globalThis.test("is kept, and checked against the views keyed by the identity", () => {
|
|
145
|
+
let match = refsOf([
|
|
146
|
+
[
|
|
147
|
+
"customerId",
|
|
148
|
+
["Customers"]
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
"productId",
|
|
152
|
+
["Products"]
|
|
153
|
+
]
|
|
154
|
+
], undefined, referenced);
|
|
155
|
+
globalThis.expect(match[0]).toEqual([[
|
|
156
|
+
"buyer",
|
|
157
|
+
"Products"
|
|
158
|
+
]]);
|
|
159
|
+
globalThis.expect(match[1].some(r => r.includes("not keyed by it"))).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
globalThis.test("naming a view keyed by the identity is fine", () => {
|
|
162
|
+
let chosen = Sury.$schema(s => ({
|
|
163
|
+
buyer: s.m(Reference$Reventless.mark(CustomerId.schema, undefined, undefined, "Customers"))
|
|
164
|
+
}));
|
|
165
|
+
let match = refsOf([[
|
|
166
|
+
"customerId",
|
|
167
|
+
[
|
|
168
|
+
"Customers",
|
|
169
|
+
"VipCustomers"
|
|
170
|
+
]
|
|
171
|
+
]], undefined, chosen);
|
|
172
|
+
globalThis.expect([
|
|
173
|
+
match[0],
|
|
174
|
+
match[1]
|
|
175
|
+
]).toEqual([
|
|
176
|
+
[[
|
|
177
|
+
"buyer",
|
|
178
|
+
"Customers"
|
|
179
|
+
]],
|
|
180
|
+
[]
|
|
181
|
+
]);
|
|
182
|
+
});
|
|
183
|
+
globalThis.test("stands on a minted id: only the derived one steps aside", () => {
|
|
184
|
+
let chosen = Sury.$schema(s => ({
|
|
185
|
+
buyer: s.m(Reference$Reventless.mark(CustomerId.schema, undefined, undefined, "Customers"))
|
|
186
|
+
}));
|
|
187
|
+
let match = refsOf([[
|
|
188
|
+
"customerId",
|
|
189
|
+
["Customers"]
|
|
190
|
+
]], "buyer", chosen);
|
|
191
|
+
globalThis.expect(match[0]).toEqual([[
|
|
192
|
+
"buyer",
|
|
193
|
+
"Customers"
|
|
194
|
+
]]);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
export {
|
|
199
|
+
ProductId,
|
|
200
|
+
CustomerId,
|
|
201
|
+
refsOf,
|
|
202
|
+
order,
|
|
203
|
+
}
|
|
204
|
+
/* ProductId Not a pure module */
|
|
@@ -22,7 +22,6 @@ function make(param, param$1, param$2, param$3, param$4, param$5, param$6) {
|
|
|
22
22
|
let PsGatedCommandsSlice_Spec = {
|
|
23
23
|
name: PsGatedCommands$ReventlessCore.name,
|
|
24
24
|
moduleUrl: PsGatedCommands$ReventlessCore.moduleUrl,
|
|
25
|
-
Id: Id$Reventless.$$String,
|
|
26
25
|
consumedEventSchema: PsGatedCommands$ReventlessCore.consumedEventSchema,
|
|
27
26
|
errorSchema: PsGatedCommands$ReventlessCore.errorSchema,
|
|
28
27
|
eventSchema: PsGatedCommands$ReventlessCore.eventSchema,
|
|
@@ -53,6 +52,13 @@ let PsGatedViewSlice_Spec = {
|
|
|
53
52
|
name: PsGatedView$ReventlessCore.name,
|
|
54
53
|
moduleUrl: PsGatedView$ReventlessCore.moduleUrl,
|
|
55
54
|
stateSchema: PsGatedView$ReventlessCore.stateSchema,
|
|
55
|
+
Key: {
|
|
56
|
+
schema: Id$Reventless.StringPure.schema,
|
|
57
|
+
make: prim => prim,
|
|
58
|
+
makeFromString: prim => prim,
|
|
59
|
+
toString: prim => prim,
|
|
60
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
61
|
+
},
|
|
56
62
|
consumedEventSchema: PsGatedView$ReventlessCore.consumedEventSchema,
|
|
57
63
|
config: PsGatedView$ReventlessCore.config,
|
|
58
64
|
subIdConfig: undefined,
|
|
@@ -79,6 +85,13 @@ let PsOrdersViewSlice_Spec = {
|
|
|
79
85
|
name: PsOrdersView$ReventlessCore.name,
|
|
80
86
|
moduleUrl: PsOrdersView$ReventlessCore.moduleUrl,
|
|
81
87
|
stateSchema: PsOrdersView$ReventlessCore.stateSchema,
|
|
88
|
+
Key: {
|
|
89
|
+
schema: Id$Reventless.StringPure.schema,
|
|
90
|
+
make: prim => prim,
|
|
91
|
+
makeFromString: prim => prim,
|
|
92
|
+
toString: prim => prim,
|
|
93
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
94
|
+
},
|
|
82
95
|
consumedEventSchema: PsOrdersView$ReventlessCore.consumedEventSchema,
|
|
83
96
|
config: PsOrdersView$ReventlessCore.config,
|
|
84
97
|
subIdConfig: undefined,
|