@reventlessdev/reventless-core 3.0.0-alpha.266 → 3.0.0-alpha.267
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 +19 -0
- package/package.json +6 -6
- 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 +131 -15
- package/src/plugin/component/Plugin_Structure.res.mjs +108 -24
- 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 +99 -0
- package/tests/plugin/IdentityReferenceTest.res.mjs +169 -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, 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 ? 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, 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 references = extractReferences(properties);
|
|
646
|
+
let references = extractReferences(identityViews, properties);
|
|
604
647
|
let syntheticCommand = DcbTag$Reventless.isVariantPayloadBearing(parentSchema, variantName) ? ({
|
|
605
648
|
TAG: variantName
|
|
606
649
|
}) : variantName;
|
|
@@ -683,12 +726,12 @@ function toCommandDef(isAggregate, partitionKey, mutationFieldFor, parentSchema,
|
|
|
683
726
|
}
|
|
684
727
|
}
|
|
685
728
|
|
|
686
|
-
function extractCommandDefs(isAggregate, partitionKey, mutationFieldFor, commandAuthorization, commandTransition, derivedEdgeForOpt, commandSchema) {
|
|
729
|
+
function extractCommandDefs(identityViews, isAggregate, partitionKey, mutationFieldFor, commandAuthorization, commandTransition, derivedEdgeForOpt, commandSchema) {
|
|
687
730
|
let derivedEdgeFor = derivedEdgeForOpt !== undefined ? derivedEdgeForOpt : param => {};
|
|
688
731
|
if (commandSchema.type === "anyOf") {
|
|
689
|
-
return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(isAggregate, partitionKey, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, v));
|
|
732
|
+
return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, v));
|
|
690
733
|
} else {
|
|
691
|
-
return Stdlib_Option.mapOr(toCommandDef(isAggregate, partitionKey, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, commandSchema), [], def => [def]);
|
|
734
|
+
return Stdlib_Option.mapOr(toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema, commandAuthorization, commandTransition, derivedEdgeFor, commandSchema), [], def => [def]);
|
|
692
735
|
}
|
|
693
736
|
}
|
|
694
737
|
|
|
@@ -1101,6 +1144,46 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1101
1144
|
};
|
|
1102
1145
|
}
|
|
1103
1146
|
let partitionFailures = [];
|
|
1147
|
+
let keyIdentityOf = (stateSchema, idField) => Stdlib_Option.map(idField, field => {
|
|
1148
|
+
let tmp;
|
|
1149
|
+
tmp = stateSchema.type === "object" ? Stdlib_Option.flatMap(stateSchema.properties[field], Semantic$Reventless.identityKey) : undefined;
|
|
1150
|
+
return Stdlib_Option.getOr(tmp, field);
|
|
1151
|
+
});
|
|
1152
|
+
let viewKeys = readModels.map((R, i) => {
|
|
1153
|
+
let def = readModelDefs[i];
|
|
1154
|
+
let declared = Semantic$Reventless.identityKey(R.Spec.Id.schema);
|
|
1155
|
+
let key = declared !== undefined ? declared : keyIdentityOf(R.Spec.stateSchema, def.idField);
|
|
1156
|
+
return [
|
|
1157
|
+
R.Spec.name,
|
|
1158
|
+
key
|
|
1159
|
+
];
|
|
1160
|
+
}).concat(stateViewSlices.map((SVS, i) => {
|
|
1161
|
+
let def = stateViewDefs[i];
|
|
1162
|
+
return [
|
|
1163
|
+
SVS.Spec.name,
|
|
1164
|
+
keyIdentityOf(SVS.Spec.stateSchema, def.idField)
|
|
1165
|
+
];
|
|
1166
|
+
}));
|
|
1167
|
+
let viewsByKey = {};
|
|
1168
|
+
viewKeys.forEach(param => {
|
|
1169
|
+
let view = param[0];
|
|
1170
|
+
Stdlib_Option.forEach(param[1], k => {
|
|
1171
|
+
viewsByKey[k] = Stdlib_Option.getOr(viewsByKey[k], []).concat([view]);
|
|
1172
|
+
});
|
|
1173
|
+
});
|
|
1174
|
+
let reported = new Set();
|
|
1175
|
+
let identityViews_viewNames = viewKeys.map(param => param[0]);
|
|
1176
|
+
let identityViews_report = message => {
|
|
1177
|
+
if (!reported.has(message)) {
|
|
1178
|
+
reported.add(message);
|
|
1179
|
+
return log.warn("Plugin_Structure", undefined, name + `: ` + message);
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
let identityViews = {
|
|
1183
|
+
viewsByKey: viewsByKey,
|
|
1184
|
+
viewNames: identityViews_viewNames,
|
|
1185
|
+
report: identityViews_report
|
|
1186
|
+
};
|
|
1104
1187
|
let stateChangeDefs = stateChangeSlices.map((SCS, i) => {
|
|
1105
1188
|
let match = scsProduced[i];
|
|
1106
1189
|
let produced = match[1];
|
|
@@ -1108,12 +1191,12 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1108
1191
|
let consumed = match$1[1];
|
|
1109
1192
|
return {
|
|
1110
1193
|
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),
|
|
1194
|
+
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
1195
|
producedEventTypes: produced,
|
|
1113
1196
|
consumedEventTypes: consumed,
|
|
1114
1197
|
linkedViews: linkedSvsFor(produced),
|
|
1115
1198
|
consistencyRead: consistencyReadFor(consumed),
|
|
1116
|
-
events: extractEventDefs(SCS.Spec.eventSchema),
|
|
1199
|
+
events: extractEventDefs(identityViews, SCS.Spec.eventSchema),
|
|
1117
1200
|
errors: extractErrorDefs(SCS.Spec.errorSchema),
|
|
1118
1201
|
chapter: componentChapters[SCS.Spec.name]
|
|
1119
1202
|
};
|
|
@@ -1123,12 +1206,12 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1123
1206
|
let produced = match[1];
|
|
1124
1207
|
return {
|
|
1125
1208
|
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),
|
|
1209
|
+
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
1210
|
producedEventTypes: produced,
|
|
1128
1211
|
consumedEventTypes: [],
|
|
1129
1212
|
linkedViews: linkedSvsFor(produced).concat(linkedReadModelsFor(A.Spec.name)),
|
|
1130
1213
|
consistencyRead: undefined,
|
|
1131
|
-
events: extractEventDefs(A.Spec.eventSchema),
|
|
1214
|
+
events: extractEventDefs(identityViews, A.Spec.eventSchema),
|
|
1132
1215
|
errors: extractErrorDefs(A.Spec.errorSchema),
|
|
1133
1216
|
chapter: componentChapters[A.Spec.name]
|
|
1134
1217
|
};
|
|
@@ -1452,6 +1535,7 @@ export {
|
|
|
1452
1535
|
checkLifecycleTopology,
|
|
1453
1536
|
labelFieldSourceToString,
|
|
1454
1537
|
labelFieldsFromStateSchema,
|
|
1538
|
+
identityReference,
|
|
1455
1539
|
extractReferences,
|
|
1456
1540
|
toEventDef,
|
|
1457
1541
|
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,99 @@
|
|
|
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, 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, 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
|
+
testSync("an untyped field is left to its name, as before", () => {
|
|
67
|
+
let (refs, reports) = refsOf(
|
|
68
|
+
~viewsByKey=[("customerId", ["Customers"])],
|
|
69
|
+
S.schema(s => {"customerId": s.matches(S.string)}),
|
|
70
|
+
)
|
|
71
|
+
expect((refs, reports))->toEqual(([], []))
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe("a declared @ref on a typed id", () => {
|
|
76
|
+
let referenced = S.schema(s =>
|
|
77
|
+
{"buyer": s.matches(CustomerId.schema->Reventless.Reference.mark("Products"))}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
testSync("is kept, and checked against the views keyed by the identity", () => {
|
|
81
|
+
let (refs, reports) = refsOf(
|
|
82
|
+
~viewsByKey=[("customerId", ["Customers"]), ("productId", ["Products"])],
|
|
83
|
+
referenced,
|
|
84
|
+
)
|
|
85
|
+
expect(refs)->toEqual([("buyer", "Products")])
|
|
86
|
+
expect(reports->Array.some(r => r->String.includes("not keyed by it")))->toBe(true)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
testSync("naming a view keyed by the identity is fine", () => {
|
|
90
|
+
let chosen = S.schema(
|
|
91
|
+
s => {"buyer": s.matches(CustomerId.schema->Reventless.Reference.mark("Customers"))},
|
|
92
|
+
)
|
|
93
|
+
let (refs, reports) = refsOf(
|
|
94
|
+
~viewsByKey=[("customerId", ["Customers", "VipCustomers"])],
|
|
95
|
+
chosen,
|
|
96
|
+
)
|
|
97
|
+
expect((refs, reports))->toEqual(([("buyer", "Customers")], []))
|
|
98
|
+
})
|
|
99
|
+
})
|
|
@@ -0,0 +1,169 @@
|
|
|
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, 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, 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
|
+
], 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
|
+
], 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
|
+
]], 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("an untyped field is left to its name, as before", () => {
|
|
102
|
+
let match = refsOf([[
|
|
103
|
+
"customerId",
|
|
104
|
+
["Customers"]
|
|
105
|
+
]], Sury.$schema(s => ({
|
|
106
|
+
customerId: s.m(Sury.string)
|
|
107
|
+
})));
|
|
108
|
+
globalThis.expect([
|
|
109
|
+
match[0],
|
|
110
|
+
match[1]
|
|
111
|
+
]).toEqual([
|
|
112
|
+
[],
|
|
113
|
+
[]
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
globalThis.describe("a declared @ref on a typed id", () => {
|
|
119
|
+
let referenced = Sury.$schema(s => ({
|
|
120
|
+
buyer: s.m(Reference$Reventless.mark(CustomerId.schema, undefined, undefined, "Products"))
|
|
121
|
+
}));
|
|
122
|
+
globalThis.test("is kept, and checked against the views keyed by the identity", () => {
|
|
123
|
+
let match = refsOf([
|
|
124
|
+
[
|
|
125
|
+
"customerId",
|
|
126
|
+
["Customers"]
|
|
127
|
+
],
|
|
128
|
+
[
|
|
129
|
+
"productId",
|
|
130
|
+
["Products"]
|
|
131
|
+
]
|
|
132
|
+
], referenced);
|
|
133
|
+
globalThis.expect(match[0]).toEqual([[
|
|
134
|
+
"buyer",
|
|
135
|
+
"Products"
|
|
136
|
+
]]);
|
|
137
|
+
globalThis.expect(match[1].some(r => r.includes("not keyed by it"))).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
globalThis.test("naming a view keyed by the identity is fine", () => {
|
|
140
|
+
let chosen = Sury.$schema(s => ({
|
|
141
|
+
buyer: s.m(Reference$Reventless.mark(CustomerId.schema, undefined, undefined, "Customers"))
|
|
142
|
+
}));
|
|
143
|
+
let match = refsOf([[
|
|
144
|
+
"customerId",
|
|
145
|
+
[
|
|
146
|
+
"Customers",
|
|
147
|
+
"VipCustomers"
|
|
148
|
+
]
|
|
149
|
+
]], chosen);
|
|
150
|
+
globalThis.expect([
|
|
151
|
+
match[0],
|
|
152
|
+
match[1]
|
|
153
|
+
]).toEqual([
|
|
154
|
+
[[
|
|
155
|
+
"buyer",
|
|
156
|
+
"Customers"
|
|
157
|
+
]],
|
|
158
|
+
[]
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
ProductId,
|
|
165
|
+
CustomerId,
|
|
166
|
+
refsOf,
|
|
167
|
+
order,
|
|
168
|
+
}
|
|
169
|
+
/* 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,
|