@reventlessdev/reventless-spec 3.0.0-alpha.129 → 3.0.0-alpha.131
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 +23 -0
- package/package.json +3 -2
- package/run-check-lifecycle.mjs +2 -0
- package/schema/platform-api.graphql +1 -0
- package/src/components/DcbTag.res +229 -32
- package/src/components/DcbTag.res.mjs +232 -41
- package/src/components/Plugin.res +47 -4
- package/src/components/Plugin.res.mjs +1 -0
- package/src/components/Reference.res +48 -0
- package/src/components/Reference.res.mjs +28 -0
- package/src/generator/Codegen.res +14 -1
- package/src/generator/Codegen.res.mjs +9 -5
- package/src/generator/PluginGenerator.res +4 -1
- package/src/generator/PluginGenerator.res.mjs +2 -1
- package/src/lifecycle/CheckLifecycleModel.res +1337 -0
- package/src/lifecycle/CheckLifecycleModel.res.mjs +1107 -0
- package/src/lifecycle/loadPluginStructure.mjs +66 -0
- package/src/types/Transition.res +30 -3
- package/src/types/Transition.res.mjs +13 -4
|
@@ -8,6 +8,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
8
8
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
9
9
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
10
10
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
11
|
+
import * as Semantic$Reventless from "../semantic/Semantic.res.mjs";
|
|
11
12
|
import * as Util_Sury$Reventless from "../util/Util_Sury.res.mjs";
|
|
12
13
|
import * as DcbScopeInference$Reventless from "./DcbScopeInference.res.mjs";
|
|
13
14
|
|
|
@@ -86,6 +87,62 @@ function isTaggedArray(fieldSchema) {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
function nestedRecordProperties(fieldSchema) {
|
|
91
|
+
let unwrapped = Stdlib_Option.getOr(Semantic$Reventless.unwrapOptional(fieldSchema), fieldSchema);
|
|
92
|
+
let match;
|
|
93
|
+
if (unwrapped.type === "array") {
|
|
94
|
+
let item = unwrapped.additionalItems;
|
|
95
|
+
match = item === "strip" || item === "strict" ? [
|
|
96
|
+
unwrapped,
|
|
97
|
+
false
|
|
98
|
+
] : [
|
|
99
|
+
Stdlib_Option.getOr(Semantic$Reventless.unwrapOptional(item), item),
|
|
100
|
+
true
|
|
101
|
+
];
|
|
102
|
+
} else {
|
|
103
|
+
match = [
|
|
104
|
+
unwrapped,
|
|
105
|
+
false
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
let candidate = match[0];
|
|
109
|
+
if (candidate.type === "object") {
|
|
110
|
+
return [
|
|
111
|
+
candidate.properties,
|
|
112
|
+
match[1]
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function propertiesCarryTags(properties) {
|
|
118
|
+
return Object.entries(properties).some(param => {
|
|
119
|
+
let s = param[1];
|
|
120
|
+
if (Stdlib_Option.isSome(Sury.$Metadata_get(s, dcbTagId))) {
|
|
121
|
+
return true;
|
|
122
|
+
} else {
|
|
123
|
+
return isTaggedArray(s);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isTaggedRecordArray(fieldSchema) {
|
|
129
|
+
let match = nestedRecordProperties(fieldSchema);
|
|
130
|
+
if (match !== undefined && match[1]) {
|
|
131
|
+
return propertiesCarryTags(match[0]);
|
|
132
|
+
} else {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function hasNestedTags(fieldSchema) {
|
|
138
|
+
let match = nestedRecordProperties(fieldSchema);
|
|
139
|
+
if (match !== undefined) {
|
|
140
|
+
return propertiesCarryTags(match[0]);
|
|
141
|
+
} else {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
89
146
|
function isPartitionTag(fieldSchema) {
|
|
90
147
|
return Stdlib_Option.isSome(Sury.$Metadata_get(fieldSchema, dcbPartitionTagId));
|
|
91
148
|
}
|
|
@@ -139,17 +196,102 @@ function jsonValueToString(json) {
|
|
|
139
196
|
}
|
|
140
197
|
}
|
|
141
198
|
|
|
199
|
+
function dedupeTags(tags) {
|
|
200
|
+
let seen = new Set();
|
|
201
|
+
return tags.filter(param => {
|
|
202
|
+
let identity = JSON.stringify(param.key) + param.value;
|
|
203
|
+
if (seen.has(identity)) {
|
|
204
|
+
return false;
|
|
205
|
+
} else {
|
|
206
|
+
seen.add(identity);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function flatFieldTagsExpanded(fieldName, fieldSchema, jsonDict) {
|
|
213
|
+
if (Stdlib_Option.isSome(Sury.$Metadata_get(fieldSchema, dcbTagId))) {
|
|
214
|
+
let jsonValue = jsonDict[fieldName];
|
|
215
|
+
if (jsonValue !== undefined) {
|
|
216
|
+
return [{
|
|
217
|
+
key: resolveTagKey(fieldName, fieldSchema),
|
|
218
|
+
value: jsonValueToString(jsonValue)
|
|
219
|
+
}];
|
|
220
|
+
} else {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!isTaggedArray(fieldSchema)) {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
let match = jsonDict[fieldName];
|
|
228
|
+
if (match === undefined) {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
if (!Array.isArray(match)) {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
let tagKey = resolveArrayTagKey(fieldName, fieldSchema);
|
|
235
|
+
return match.map(element => ({
|
|
236
|
+
key: tagKey,
|
|
237
|
+
value: jsonValueToString(element)
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function flatTagsExpanded(properties, jsonDict) {
|
|
242
|
+
return Object.entries(properties).flatMap(param => flatFieldTagsExpanded(param[0], param[1], jsonDict));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function nestedRecordTags(fieldSchema, jsonValue) {
|
|
246
|
+
let match = nestedRecordProperties(fieldSchema);
|
|
247
|
+
if (match === undefined) {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
let properties = match[0];
|
|
251
|
+
let ofObject = json => {
|
|
252
|
+
let elementDict = Stdlib_JSON.Decode.object(json);
|
|
253
|
+
if (elementDict !== undefined) {
|
|
254
|
+
return flatTagsExpanded(properties, elementDict);
|
|
255
|
+
} else {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
if (match[1]) {
|
|
260
|
+
if (Array.isArray(jsonValue)) {
|
|
261
|
+
return jsonValue.flatMap(ofObject);
|
|
262
|
+
} else {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
return ofObject(jsonValue);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
142
270
|
function extractTagsFromProperties(properties, jsonDict) {
|
|
143
|
-
return
|
|
271
|
+
return dedupeTags(Object.entries(properties).flatMap(param => {
|
|
144
272
|
let fieldSchema = param[1];
|
|
145
273
|
let fieldName = param[0];
|
|
146
274
|
if (Stdlib_Option.isSome(Sury.$Metadata_get(fieldSchema, dcbTagId))) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
275
|
+
let jsonValue = jsonDict[fieldName];
|
|
276
|
+
if (jsonValue !== undefined) {
|
|
277
|
+
return [{
|
|
278
|
+
key: resolveTagKey(fieldName, fieldSchema),
|
|
279
|
+
value: jsonValueToString(jsonValue)
|
|
280
|
+
}];
|
|
281
|
+
} else {
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
151
284
|
}
|
|
152
|
-
|
|
285
|
+
if (!hasNestedTags(fieldSchema)) {
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
let jsonValue$1 = jsonDict[fieldName];
|
|
289
|
+
if (jsonValue$1 !== undefined) {
|
|
290
|
+
return nestedRecordTags(fieldSchema, jsonValue$1);
|
|
291
|
+
} else {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
}));
|
|
153
295
|
}
|
|
154
296
|
|
|
155
297
|
function variantTagName(properties) {
|
|
@@ -275,36 +417,22 @@ function isVariantPayloadBearing(schema, name) {
|
|
|
275
417
|
}
|
|
276
418
|
|
|
277
419
|
function extractTagsFromPropertiesExpanded(properties, jsonDict) {
|
|
278
|
-
return Object.entries(properties).flatMap(param => {
|
|
420
|
+
return dedupeTags(Object.entries(properties).flatMap(param => {
|
|
279
421
|
let fieldSchema = param[1];
|
|
280
422
|
let fieldName = param[0];
|
|
281
|
-
if (Stdlib_Option.isSome(Sury.$Metadata_get(fieldSchema, dcbTagId))) {
|
|
282
|
-
|
|
283
|
-
if (jsonValue !== undefined) {
|
|
284
|
-
return [{
|
|
285
|
-
key: resolveTagKey(fieldName, fieldSchema),
|
|
286
|
-
value: jsonValueToString(jsonValue)
|
|
287
|
-
}];
|
|
288
|
-
} else {
|
|
289
|
-
return [];
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
if (!isTaggedArray(fieldSchema)) {
|
|
293
|
-
return [];
|
|
423
|
+
if (Stdlib_Option.isSome(Sury.$Metadata_get(fieldSchema, dcbTagId)) || isTaggedArray(fieldSchema)) {
|
|
424
|
+
return flatFieldTagsExpanded(fieldName, fieldSchema, jsonDict);
|
|
294
425
|
}
|
|
295
|
-
|
|
296
|
-
if (match === undefined) {
|
|
426
|
+
if (!hasNestedTags(fieldSchema)) {
|
|
297
427
|
return [];
|
|
298
428
|
}
|
|
299
|
-
|
|
429
|
+
let jsonValue = jsonDict[fieldName];
|
|
430
|
+
if (jsonValue !== undefined) {
|
|
431
|
+
return nestedRecordTags(fieldSchema, jsonValue);
|
|
432
|
+
} else {
|
|
300
433
|
return [];
|
|
301
434
|
}
|
|
302
|
-
|
|
303
|
-
return match.map(element => ({
|
|
304
|
-
key: tagKey,
|
|
305
|
-
value: jsonValueToString(element)
|
|
306
|
-
}));
|
|
307
|
-
});
|
|
435
|
+
}));
|
|
308
436
|
}
|
|
309
437
|
|
|
310
438
|
function extractTagsFromJsonExpanded(schema, json) {
|
|
@@ -352,11 +480,25 @@ function extractTagsExpanded(schema, value) {
|
|
|
352
480
|
function hasTaggedArrayFields(schema) {
|
|
353
481
|
switch (schema.type) {
|
|
354
482
|
case "object" :
|
|
355
|
-
return Object.entries(schema.properties).some(param =>
|
|
483
|
+
return Object.entries(schema.properties).some(param => {
|
|
484
|
+
let fieldSchema = param[1];
|
|
485
|
+
if (isTaggedArray(fieldSchema)) {
|
|
486
|
+
return true;
|
|
487
|
+
} else {
|
|
488
|
+
return isTaggedRecordArray(fieldSchema);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
356
491
|
case "anyOf" :
|
|
357
492
|
return schema.anyOf.some(variantSchema => {
|
|
358
493
|
if (variantSchema.type === "object") {
|
|
359
|
-
return Object.entries(variantSchema.properties).some(param =>
|
|
494
|
+
return Object.entries(variantSchema.properties).some(param => {
|
|
495
|
+
let fieldSchema = param[1];
|
|
496
|
+
if (isTaggedArray(fieldSchema)) {
|
|
497
|
+
return true;
|
|
498
|
+
} else {
|
|
499
|
+
return isTaggedRecordArray(fieldSchema);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
360
502
|
} else {
|
|
361
503
|
return false;
|
|
362
504
|
}
|
|
@@ -366,7 +508,7 @@ function hasTaggedArrayFields(schema) {
|
|
|
366
508
|
}
|
|
367
509
|
}
|
|
368
510
|
|
|
369
|
-
function
|
|
511
|
+
function flatTagKeysOfProperties(properties) {
|
|
370
512
|
return Stdlib_Array.filterMap(Object.entries(properties), param => {
|
|
371
513
|
let fieldSchema = param[1];
|
|
372
514
|
let fieldName = param[0];
|
|
@@ -380,6 +522,25 @@ function tagKeysOfProperties(properties) {
|
|
|
380
522
|
});
|
|
381
523
|
}
|
|
382
524
|
|
|
525
|
+
function tagKeysOfProperties(properties) {
|
|
526
|
+
return Object.entries(properties).flatMap(param => {
|
|
527
|
+
let fieldSchema = param[1];
|
|
528
|
+
let fieldName = param[0];
|
|
529
|
+
if (Stdlib_Option.isSome(Sury.$Metadata_get(fieldSchema, dcbTagId))) {
|
|
530
|
+
return [resolveTagKey(fieldName, fieldSchema)];
|
|
531
|
+
}
|
|
532
|
+
if (isTaggedArray(fieldSchema)) {
|
|
533
|
+
return [resolveArrayTagKey(fieldName, fieldSchema)];
|
|
534
|
+
}
|
|
535
|
+
let match = nestedRecordProperties(fieldSchema);
|
|
536
|
+
if (match !== undefined) {
|
|
537
|
+
return flatTagKeysOfProperties(match[0]);
|
|
538
|
+
} else {
|
|
539
|
+
return [];
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
383
544
|
function extractTagKeysByEventType(schema) {
|
|
384
545
|
let result = {};
|
|
385
546
|
let addVariant = variantSchema => {
|
|
@@ -530,17 +691,38 @@ function extractCrossPartitionTagKeys(schema) {
|
|
|
530
691
|
}
|
|
531
692
|
|
|
532
693
|
function idFieldsOfProperties(properties) {
|
|
533
|
-
|
|
694
|
+
let isIdName = name => {
|
|
695
|
+
if (name.endsWith("Ids")) {
|
|
696
|
+
return true;
|
|
697
|
+
} else {
|
|
698
|
+
return name.endsWith("Id");
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
return Object.entries(properties).flatMap(param => {
|
|
702
|
+
let fieldSchema = param[1];
|
|
534
703
|
let name = param[0];
|
|
535
|
-
if (
|
|
536
|
-
|
|
704
|
+
if (isIdName(name)) {
|
|
705
|
+
let isList;
|
|
706
|
+
isList = fieldSchema.type === "array";
|
|
707
|
+
return [{
|
|
708
|
+
name: name,
|
|
709
|
+
isList: isList
|
|
710
|
+
}];
|
|
537
711
|
}
|
|
538
|
-
let
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
712
|
+
let match = nestedRecordProperties(fieldSchema);
|
|
713
|
+
if (match === undefined) {
|
|
714
|
+
return [];
|
|
715
|
+
}
|
|
716
|
+
let nestedIsList = match[1];
|
|
717
|
+
return Stdlib_Array.filterMap(Object.entries(match[0]), param => {
|
|
718
|
+
let nestedName = param[0];
|
|
719
|
+
if (isIdName(nestedName)) {
|
|
720
|
+
return {
|
|
721
|
+
name: nestedName,
|
|
722
|
+
isList: nestedIsList
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
});
|
|
544
726
|
});
|
|
545
727
|
}
|
|
546
728
|
|
|
@@ -862,12 +1044,20 @@ export {
|
|
|
862
1044
|
derivedPartitionTagSchema,
|
|
863
1045
|
isTagged,
|
|
864
1046
|
isTaggedArray,
|
|
1047
|
+
nestedRecordProperties,
|
|
1048
|
+
propertiesCarryTags,
|
|
1049
|
+
isTaggedRecordArray,
|
|
1050
|
+
hasNestedTags,
|
|
865
1051
|
isPartitionTag,
|
|
866
1052
|
isCrossPartitionTag,
|
|
867
1053
|
isCrossPartitionTaggedArray,
|
|
868
1054
|
resolveTagKey,
|
|
869
1055
|
resolveArrayTagKey,
|
|
870
1056
|
jsonValueToString,
|
|
1057
|
+
dedupeTags,
|
|
1058
|
+
flatFieldTagsExpanded,
|
|
1059
|
+
flatTagsExpanded,
|
|
1060
|
+
nestedRecordTags,
|
|
871
1061
|
extractTagsFromProperties,
|
|
872
1062
|
variantTagName,
|
|
873
1063
|
extractTagsFromJson,
|
|
@@ -879,6 +1069,7 @@ export {
|
|
|
879
1069
|
extractTagsFromJsonExpanded,
|
|
880
1070
|
extractTagsExpanded,
|
|
881
1071
|
hasTaggedArrayFields,
|
|
1072
|
+
flatTagKeysOfProperties,
|
|
882
1073
|
tagKeysOfProperties,
|
|
883
1074
|
extractTagKeysByEventType,
|
|
884
1075
|
mergeTagKeysByEventType,
|
|
@@ -122,6 +122,32 @@ let uiFragmentManifestOptionSchema = S.option(uiFragmentManifestSchema)
|
|
|
122
122
|
@schema
|
|
123
123
|
type commandLevel = Collection | Instance
|
|
124
124
|
|
|
125
|
+
/** One command's lifecycle edge as the component's own scenarios describe it:
|
|
126
|
+
where a scenario shows the command taking effect, where those scenarios land,
|
|
127
|
+
and whether it brings a row into existence or acts on one.
|
|
128
|
+
|
|
129
|
+
Harvested from the GWT corpus by `check:lifecycle` and committed beside the
|
|
130
|
+
plugin as `src/LifecycleModel.res`, so structure assembly reads a value rather
|
|
131
|
+
than the test tree — which is not published with a plugin package.
|
|
132
|
+
|
|
133
|
+
No `@schema`: nothing serialises this. It is read once, while the structure is
|
|
134
|
+
assembled, and what leaves is the `commandDef` fields it resolved. */
|
|
135
|
+
type derivedEdge = {
|
|
136
|
+
/** The writable's `Spec.name`, which is what the structure knows it by. */
|
|
137
|
+
component: string,
|
|
138
|
+
command: string,
|
|
139
|
+
/** Absent where the corpus could not label a history — the honest answer when
|
|
140
|
+
the linked views declare no lifecycle field, and the reason the name-stem
|
|
141
|
+
guess is still there to fall back to. */
|
|
142
|
+
level?: commandLevel,
|
|
143
|
+
/** States a scenario shows the command taking effect from. Empty means the
|
|
144
|
+
corpus said nothing, NOT that the command belongs nowhere. */
|
|
145
|
+
allowedStates: array<string>,
|
|
146
|
+
/** States those scenarios land in. More than one is an edge a single
|
|
147
|
+
`targetState` cannot carry, so it is left to the declaration. */
|
|
148
|
+
targets: array<string>,
|
|
149
|
+
}
|
|
150
|
+
|
|
125
151
|
@schema
|
|
126
152
|
type fieldReference = {
|
|
127
153
|
fieldName: string,
|
|
@@ -137,12 +163,29 @@ type commandDef = {
|
|
|
137
163
|
aggregateIdField: option<string>,
|
|
138
164
|
mutationField: string,
|
|
139
165
|
references: array<fieldReference>,
|
|
140
|
-
/** The
|
|
141
|
-
|
|
166
|
+
/** The *from* set — lifecycle states this command is meaningful in. `None`
|
|
167
|
+
means always available; `Some([])` means never show. */
|
|
142
168
|
allowedStates: option<array<string>>,
|
|
143
|
-
/** The
|
|
144
|
-
|
|
169
|
+
/** The *to* state this command's handler writes. `None` with a from-set present
|
|
170
|
+
means the command does not move the row. */
|
|
145
171
|
targetState: option<string>,
|
|
172
|
+
/** Where the from-set came from, ranking an inherited edge against an authored
|
|
173
|
+
one the way `queryableDef.labelFieldSource` does for its field:
|
|
174
|
+
|
|
175
|
+
- `"derived"` — the component's own scenarios.
|
|
176
|
+
- `"declared"` — its `commandTransition` switch.
|
|
177
|
+
- `"unrestricted"` — the switch declares the command legal in *every*
|
|
178
|
+
state, so there is no from-set to publish and `allowedStates` is absent.
|
|
179
|
+
|
|
180
|
+
Absent means the command names no states to come from and nothing said that
|
|
181
|
+
was deliberate — a spec that wrote no switch, or one whose command creates
|
|
182
|
+
the row. So a consumer drawing a state machine can tell an edge left
|
|
183
|
+
unconstrained on purpose from one that simply has no from-set.
|
|
184
|
+
|
|
185
|
+
It speaks for the from-set only. The two halves of an edge are reported
|
|
186
|
+
separately because they fail separately: a corpus routinely shows a command
|
|
187
|
+
taking effect without ever showing where it lands. */
|
|
188
|
+
allowedStatesSource?: string,
|
|
146
189
|
/** Whether the variant is exposed in the generated API (non-`@noApi`). */
|
|
147
190
|
apiExposed: option<bool>,
|
|
148
191
|
/** Access keys — any one of them — a caller needs to be *offered* this command.
|
|
@@ -99,6 +99,7 @@ let commandDefSchema = Sury.$schema(s => ({
|
|
|
99
99
|
references: s.m(Sury.array(fieldReferenceSchema)),
|
|
100
100
|
allowedStates: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
101
101
|
targetState: s.m(Sury.$option(Sury.string)),
|
|
102
|
+
allowedStatesSource: s.m(Sury.$option(Sury.string)),
|
|
102
103
|
apiExposed: s.m(Sury.$option(Sury.bool)),
|
|
103
104
|
requiredAccess: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
104
105
|
ownerField: s.m(Sury.$option(Sury.string))
|
|
@@ -62,6 +62,11 @@ Only wrappers around the field's own value are followed — the optional union a
|
|
|
62
62
|
the array element, to any depth. Object properties are not: a reference declared
|
|
63
63
|
on a nested record's field belongs to that field, and attributing it to the
|
|
64
64
|
enclosing one would name the wrong field.
|
|
65
|
+
|
|
66
|
+
That is still the right answer to the question this asks. A caller collecting
|
|
67
|
+
*every* reference a command or event declares — including the ones on the fields
|
|
68
|
+
of records it holds — wants [`collectFieldTargets`], which names each one by its
|
|
69
|
+
own path.
|
|
65
70
|
*/
|
|
66
71
|
let rec getFieldTarget = (schema: S.t<unknown>): option<target> =>
|
|
67
72
|
switch getTarget(schema) {
|
|
@@ -75,6 +80,49 @@ let rec getFieldTarget = (schema: S.t<unknown>): option<target> =>
|
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
/**
|
|
84
|
+
Every reference a field declares, each paired with the path that names it.
|
|
85
|
+
|
|
86
|
+
A reference on the field's own value keeps the bare field name (`customerId`), so
|
|
87
|
+
nothing that reads today's paths has to learn the new form to go on working. One
|
|
88
|
+
declared on a field of a record the field holds is named by its path:
|
|
89
|
+
`lineItems[].productId` — `[]` for "each element of", `.` for "property of", and
|
|
90
|
+
no index, because a marker lives on the element *schema* and so applies to every
|
|
91
|
+
element or to none.
|
|
92
|
+
|
|
93
|
+
⚠️ A path is a **widened** contract, not a compatible one, for a consumer that
|
|
94
|
+
uses the name as a dictionary key into a value's properties: such a consumer
|
|
95
|
+
simply misses a nested reference and falls back to whatever it does for an
|
|
96
|
+
unreferenced field. That degradation is the correct one, but it is a degradation.
|
|
97
|
+
|
|
98
|
+
Only one record deep, matching [`DcbTag.nestedRecordProperties`], which is the
|
|
99
|
+
walk shared with the tag extraction so a marker is found in the same places by
|
|
100
|
+
both.
|
|
101
|
+
*/
|
|
102
|
+
let collectFieldTargets = (
|
|
103
|
+
fieldName: string,
|
|
104
|
+
schema: S.t<unknown>,
|
|
105
|
+
): array<(string, target)> =>
|
|
106
|
+
switch getFieldTarget(schema) {
|
|
107
|
+
| Some(target) => [(fieldName, target)]
|
|
108
|
+
| None =>
|
|
109
|
+
switch DcbTag.nestedRecordProperties(schema) {
|
|
110
|
+
| None => []
|
|
111
|
+
| Some((properties, isList)) =>
|
|
112
|
+
let prefix = isList ? fieldName ++ "[]" : fieldName
|
|
113
|
+
properties
|
|
114
|
+
->Dict.toArray
|
|
115
|
+
->Array.filterMap(((propName, propSchema)) =>
|
|
116
|
+
propName === "TAG"
|
|
117
|
+
? None
|
|
118
|
+
: getFieldTarget(propSchema)->Option.map(target => (
|
|
119
|
+
`${prefix}.${propName}`,
|
|
120
|
+
target,
|
|
121
|
+
))
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
78
126
|
/**
|
|
79
127
|
Like `to_` but does not imply DCB tag semantics.
|
|
80
128
|
Use with `@ref("Entity") @noDcbTag` when the field references another entity
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Sury from "sury";
|
|
4
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
6
|
import * as DcbTag$Reventless from "./DcbTag.res.mjs";
|
|
6
7
|
import * as Semantic$Reventless from "../semantic/Semantic.res.mjs";
|
|
@@ -53,6 +54,32 @@ function getFieldTarget(_schema) {
|
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
function collectFieldTargets(fieldName, schema) {
|
|
58
|
+
let target = getFieldTarget(schema);
|
|
59
|
+
if (target !== undefined) {
|
|
60
|
+
return [[
|
|
61
|
+
fieldName,
|
|
62
|
+
target
|
|
63
|
+
]];
|
|
64
|
+
}
|
|
65
|
+
let match = DcbTag$Reventless.nestedRecordProperties(schema);
|
|
66
|
+
if (match === undefined) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
let prefix = match[1] ? fieldName + "[]" : fieldName;
|
|
70
|
+
return Stdlib_Array.filterMap(Object.entries(match[0]), param => {
|
|
71
|
+
let propName = param[0];
|
|
72
|
+
if (propName === "TAG") {
|
|
73
|
+
return;
|
|
74
|
+
} else {
|
|
75
|
+
return Stdlib_Option.map(getFieldTarget(param[1]), target => [
|
|
76
|
+
prefix + `.` + propName,
|
|
77
|
+
target
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
56
83
|
function toWithoutDcbTag(plugin, entity) {
|
|
57
84
|
return Semantic$Reventless.mark(Sury.string, Semantic$Reventless.Id.reference, {
|
|
58
85
|
TAG: "ReferenceTo",
|
|
@@ -67,6 +94,7 @@ export {
|
|
|
67
94
|
to_,
|
|
68
95
|
getTarget,
|
|
69
96
|
getFieldTarget,
|
|
97
|
+
collectFieldTargets,
|
|
70
98
|
toWithoutDcbTag,
|
|
71
99
|
}
|
|
72
100
|
/* sury Not a pure module */
|
|
@@ -350,6 +350,7 @@ let renderPluginStructureCall = (
|
|
|
350
350
|
~extensions: array<string>,
|
|
351
351
|
~extensionPoints: array<Pairing.extensionPointDef>,
|
|
352
352
|
~componentChapters: array<(string, string)>,
|
|
353
|
+
~hasLifecycleModel: bool,
|
|
353
354
|
): option<array<string>> => {
|
|
354
355
|
// The structure call carries the EP *mapping* files (one per Delegate
|
|
355
356
|
// connection), not the wrapped ExtensionPoint module — Plugin_Structure reads
|
|
@@ -428,6 +429,15 @@ let renderPluginStructureCall = (
|
|
|
428
429
|
)
|
|
429
430
|
ls->Array.push(" ~componentChapters=Dict.fromArray([" ++ entries->Array.join(", ") ++ "]),")
|
|
430
431
|
}
|
|
432
|
+
// What the plugin's own scenarios say about each command's lifecycle edge.
|
|
433
|
+
// Written by `check:lifecycle:update`, not here: the sidecars it harvests are
|
|
434
|
+
// produced by the build this generator runs *before*, so a generator-written
|
|
435
|
+
// model would always be one build stale and a cold clone would have none at
|
|
436
|
+
// all. Referenced only when the file is on disk, so a plugin that has never
|
|
437
|
+
// been harvested keeps a byte-identical generated Plugin.res.
|
|
438
|
+
if hasLifecycleModel {
|
|
439
|
+
ls->Array.push(" ~lifecycleModel=LifecycleModel.model,")
|
|
440
|
+
}
|
|
431
441
|
ls->Array.push(" )")
|
|
432
442
|
Some(ls)
|
|
433
443
|
}
|
|
@@ -595,6 +605,7 @@ let renderComposition = (
|
|
|
595
605
|
~config: Config.config,
|
|
596
606
|
~resolved: Pairing.resolved,
|
|
597
607
|
~componentChapters: array<(string, string)>,
|
|
608
|
+
~hasLifecycleModel: bool,
|
|
598
609
|
): string => {
|
|
599
610
|
let lines: array<string> = []
|
|
600
611
|
|
|
@@ -740,6 +751,7 @@ let renderComposition = (
|
|
|
740
751
|
~extensions=resolved.extensions,
|
|
741
752
|
~extensionPoints=resolved.extensionPoints,
|
|
742
753
|
~componentChapters,
|
|
754
|
+
~hasLifecycleModel,
|
|
743
755
|
)
|
|
744
756
|
let hasPluginStructure = pluginStructureLines->Option.isSome
|
|
745
757
|
switch pluginStructureLines {
|
|
@@ -833,6 +845,7 @@ let render = (
|
|
|
833
845
|
~config: Config.config,
|
|
834
846
|
~resolved: Pairing.resolved,
|
|
835
847
|
~discovered: array<Discovery.discoveredFile>,
|
|
848
|
+
~hasLifecycleModel: bool=false,
|
|
836
849
|
): string => {
|
|
837
850
|
validateUniqueSpecStems(~discovered)
|
|
838
851
|
validateSliceTargets(~resolved)
|
|
@@ -858,6 +871,6 @@ let render = (
|
|
|
858
871
|
)
|
|
859
872
|
switch config.variant {
|
|
860
873
|
| Aws({compositionNamespace}) => renderAwsWrapper(~compositionNamespace)
|
|
861
|
-
| Composition => renderComposition(~config, ~resolved, ~componentChapters)
|
|
874
|
+
| Composition => renderComposition(~config, ~resolved, ~componentChapters, ~hasLifecycleModel)
|
|
862
875
|
}
|
|
863
876
|
}
|
|
@@ -239,7 +239,7 @@ function renderTaskMakeParam(tasks) {
|
|
|
239
239
|
return " ~tasks=[" + entries.join(", ") + "],";
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
function renderPluginStructureCall(name, aggregates, readModels, stateViewSlices, stateViewSlicesStream, stateChangeSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, extensions, extensionPoints, componentChapters) {
|
|
242
|
+
function renderPluginStructureCall(name, aggregates, readModels, stateViewSlices, stateViewSlicesStream, stateChangeSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, extensions, extensionPoints, componentChapters, hasLifecycleModel) {
|
|
243
243
|
let epMappingStems = extensionPoints.flatMap(param => param.mappings);
|
|
244
244
|
let hasComponents = aggregates.length !== 0 || readModels.length !== 0 || stateViewSlices.length !== 0 || stateViewSlicesStream.length !== 0 || stateChangeSlices.length !== 0 || automationSlices.length !== 0 || outboundTranslationSlices.length !== 0 || inboundTranslationSlices.length !== 0 || extensions.length !== 0 || epMappingStems.length !== 0;
|
|
245
245
|
if (!hasComponents) {
|
|
@@ -295,6 +295,9 @@ function renderPluginStructureCall(name, aggregates, readModels, stateViewSlices
|
|
|
295
295
|
let entries$8 = componentChapters.map(param => "(\"" + param[0] + "\", \"" + param[1] + "\")");
|
|
296
296
|
ls.push(" ~componentChapters=Dict.fromArray([" + entries$8.join(", ") + "]),");
|
|
297
297
|
}
|
|
298
|
+
if (hasLifecycleModel) {
|
|
299
|
+
ls.push(" ~lifecycleModel=LifecycleModel.model,");
|
|
300
|
+
}
|
|
298
301
|
ls.push(" )");
|
|
299
302
|
return ls;
|
|
300
303
|
}
|
|
@@ -394,7 +397,7 @@ function renderAwsWrapper(compositionNamespace) {
|
|
|
394
397
|
].join("\n");
|
|
395
398
|
}
|
|
396
399
|
|
|
397
|
-
function renderComposition(config, resolved, componentChapters) {
|
|
400
|
+
function renderComposition(config, resolved, componentChapters, hasLifecycleModel) {
|
|
398
401
|
let lines = [];
|
|
399
402
|
lines.push("// AUTO-GENERATED — do not edit. Run `npm run generate` to update.");
|
|
400
403
|
lines.push("");
|
|
@@ -459,7 +462,7 @@ function renderComposition(config, resolved, componentChapters) {
|
|
|
459
462
|
lines.push(" // Extensions");
|
|
460
463
|
push(renderExtensions(resolved.extensions));
|
|
461
464
|
}
|
|
462
|
-
let pluginStructureLines = renderPluginStructureCall(config.name, resolved.aggregates, resolved.readModels, resolved.stateViewSlices, resolved.stateViewSlicesStream, resolved.stateChangeSlices, resolved.automationSlices, resolved.outboundTranslationSlices, resolved.inboundTranslationSlices, resolved.extensions, resolved.extensionPoints, componentChapters);
|
|
465
|
+
let pluginStructureLines = renderPluginStructureCall(config.name, resolved.aggregates, resolved.readModels, resolved.stateViewSlices, resolved.stateViewSlicesStream, resolved.stateChangeSlices, resolved.automationSlices, resolved.outboundTranslationSlices, resolved.inboundTranslationSlices, resolved.extensions, resolved.extensionPoints, componentChapters, hasLifecycleModel);
|
|
463
466
|
let hasPluginStructure = Stdlib_Option.isSome(pluginStructureLines);
|
|
464
467
|
if (pluginStructureLines !== undefined) {
|
|
465
468
|
pluginStructureLines.forEach(l => {
|
|
@@ -517,7 +520,8 @@ function renderComposition(config, resolved, componentChapters) {
|
|
|
517
520
|
return lines.join("\n");
|
|
518
521
|
}
|
|
519
522
|
|
|
520
|
-
function render(config, resolved, discovered) {
|
|
523
|
+
function render(config, resolved, discovered, hasLifecycleModelOpt) {
|
|
524
|
+
let hasLifecycleModel = hasLifecycleModelOpt !== undefined ? hasLifecycleModelOpt : false;
|
|
521
525
|
validateUniqueSpecStems(discovered);
|
|
522
526
|
validateSliceTargets(resolved);
|
|
523
527
|
let componentStems = [
|
|
@@ -533,7 +537,7 @@ function render(config, resolved, discovered) {
|
|
|
533
537
|
let componentChapters = Discovery$Reventless.chaptersByStem(discovered).filter(param => componentStems.includes(param[0]));
|
|
534
538
|
let match = config.variant;
|
|
535
539
|
if (typeof match !== "object") {
|
|
536
|
-
return renderComposition(config, resolved, componentChapters);
|
|
540
|
+
return renderComposition(config, resolved, componentChapters, hasLifecycleModel);
|
|
537
541
|
} else {
|
|
538
542
|
return renderAwsWrapper(match.compositionNamespace);
|
|
539
543
|
}
|
|
@@ -34,7 +34,10 @@ let () = {
|
|
|
34
34
|
let config = {...Config.read(~srcDir), variant}
|
|
35
35
|
let discovered = Discovery.scan(~srcDir, ~exclude=config.exclude)
|
|
36
36
|
let resolved = Pairing.resolve(discovered, ~srcDir)
|
|
37
|
-
|
|
37
|
+
// Sits at the src root rather than in a kind folder, so `Discovery` never
|
|
38
|
+
// sees it and it is not a component. Its presence is the whole question.
|
|
39
|
+
let hasLifecycleModel = NodePath.join([srcDir, "LifecycleModel.res"])->NodeFs.existsSync
|
|
40
|
+
let source = Codegen.render(~config, ~resolved, ~discovered, ~hasLifecycleModel)
|
|
38
41
|
|
|
39
42
|
let outputDir = switch variant {
|
|
40
43
|
| Config.Composition => srcDir
|
|
@@ -56,7 +56,8 @@ if (srcDirArg === "") {
|
|
|
56
56
|
};
|
|
57
57
|
let discovered = Discovery$Reventless.scan(srcDir, config_exclude);
|
|
58
58
|
let resolved = Pairing$Reventless.resolve(discovered, srcDir);
|
|
59
|
-
let
|
|
59
|
+
let hasLifecycleModel = Nodefs.existsSync(Nodepath.join(srcDir, "LifecycleModel.res"));
|
|
60
|
+
let source = Codegen$Reventless.render(config, resolved, discovered, hasLifecycleModel);
|
|
60
61
|
let outputDir;
|
|
61
62
|
outputDir = typeof variant !== "object" ? srcDir : Nodepath.join(process.cwd(), "src");
|
|
62
63
|
let pluginPath = Nodepath.join(outputDir, "Plugin.res");
|