@reventlessdev/reventless-spec 3.0.0-alpha.135 → 3.0.0-alpha.137
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 +28 -0
- package/package.json +2 -2
- package/src/components/DcbScopeInference.res +224 -73
- package/src/components/DcbScopeInference.res.mjs +187 -49
- package/src/components/DcbTag.res +142 -186
- package/src/components/DcbTag.res.mjs +137 -128
- package/src/components/DcbValidation.res +50 -0
- package/src/components/DcbValidation.res.mjs +51 -0
- package/src/generator/Codegen.res +2 -1
- package/src/generator/Codegen.res.mjs +1 -1
- package/src/lifecycle/CheckLifecycleModel.res +114 -4
- package/src/lifecycle/CheckLifecycleModel.res.mjs +148 -4
- package/src/semantic/Semantic.res +44 -0
- package/src/semantic/Semantic.res.mjs +100 -12
- package/src/types/AccountsManifest.res +6 -0
- package/src/types/AccountsManifest.res.mjs +2 -1
|
@@ -489,6 +489,109 @@ function deriveCommands(component, observations, labelled) {
|
|
|
489
489
|
});
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
+
function shownOutcomeOf(s) {
|
|
493
|
+
let match = s.thenKind;
|
|
494
|
+
switch (match) {
|
|
495
|
+
case "error" :
|
|
496
|
+
case "event" :
|
|
497
|
+
break;
|
|
498
|
+
case "noEvent" :
|
|
499
|
+
return [
|
|
500
|
+
"noEvent",
|
|
501
|
+
[]
|
|
502
|
+
];
|
|
503
|
+
default:
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
return [
|
|
507
|
+
s.thenKind,
|
|
508
|
+
s.thenElements.map(e => e.name)
|
|
509
|
+
];
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function byPluginComponentCommand(xs) {
|
|
513
|
+
return xs.toSorted((a, b) => {
|
|
514
|
+
let c = Primitive_string.compare(a.plugin, b.plugin);
|
|
515
|
+
if (c !== 0) {
|
|
516
|
+
return c;
|
|
517
|
+
}
|
|
518
|
+
let c$1 = Primitive_string.compare(a.component, b.component);
|
|
519
|
+
if (c$1 !== 0) {
|
|
520
|
+
return c$1;
|
|
521
|
+
} else {
|
|
522
|
+
return Primitive_string.compare(a.command, b.command);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function commandOutcomes(plugin, corpora) {
|
|
528
|
+
let entries = [];
|
|
529
|
+
corpora.forEach(c => {
|
|
530
|
+
c.scenarios.forEach(s => {
|
|
531
|
+
let match = s.whenKind;
|
|
532
|
+
let match$1 = s.whenElements[0];
|
|
533
|
+
let match$2 = shownOutcomeOf(s);
|
|
534
|
+
if (match !== "command") {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (match$1 === undefined) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
if (match$2 === undefined) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
let names = match$2[1];
|
|
544
|
+
let kind = match$2[0];
|
|
545
|
+
let e = entries.find(e => {
|
|
546
|
+
if (e.component === c.component) {
|
|
547
|
+
return e.command === match$1.name;
|
|
548
|
+
} else {
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
let entry;
|
|
553
|
+
if (e !== undefined) {
|
|
554
|
+
entry = e;
|
|
555
|
+
} else {
|
|
556
|
+
let e_component = c.component;
|
|
557
|
+
let e_command = match$1.name;
|
|
558
|
+
let e_outcomes = [];
|
|
559
|
+
let e$1 = {
|
|
560
|
+
plugin: plugin,
|
|
561
|
+
component: e_component,
|
|
562
|
+
command: e_command,
|
|
563
|
+
outcomes: e_outcomes
|
|
564
|
+
};
|
|
565
|
+
entries.push(e$1);
|
|
566
|
+
entry = e$1;
|
|
567
|
+
}
|
|
568
|
+
let o = entry.outcomes.find(o => {
|
|
569
|
+
if (o.kind === kind) {
|
|
570
|
+
return Primitive_object.equal(o.names, names);
|
|
571
|
+
} else {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
if (o !== undefined) {
|
|
576
|
+
if (!o.scenarios.includes(s.title)) {
|
|
577
|
+
o.scenarios.push(s.title);
|
|
578
|
+
return;
|
|
579
|
+
} else {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
entry.outcomes.push({
|
|
584
|
+
kind: kind,
|
|
585
|
+
names: names,
|
|
586
|
+
scenarios: [s.title]
|
|
587
|
+
});
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
return byPluginComponentCommand(entries);
|
|
593
|
+
}
|
|
594
|
+
|
|
492
595
|
function allUnverified(cmd, add, why) {
|
|
493
596
|
let states = cmd.allowedStates;
|
|
494
597
|
if (states !== undefined && states.length !== 0) {
|
|
@@ -659,7 +762,7 @@ function isWritablePath(path) {
|
|
|
659
762
|
}
|
|
660
763
|
}
|
|
661
764
|
|
|
662
|
-
async function runPlugin(plugin, pluginDir, findings, opaque) {
|
|
765
|
+
async function runPlugin(plugin, pluginDir, findings, opaque, outcomes) {
|
|
663
766
|
let msg = await readDeclared(pluginDir);
|
|
664
767
|
if (msg.TAG !== "Ok") {
|
|
665
768
|
return {
|
|
@@ -669,6 +772,7 @@ async function runPlugin(plugin, pluginDir, findings, opaque) {
|
|
|
669
772
|
}
|
|
670
773
|
let declared = msg._0;
|
|
671
774
|
let corpora = Stdlib_Array.filterMap(filesUnder(Nodepath.join(pluginDir, "tests"), ".gwt.json"), readCorpus);
|
|
775
|
+
outcomes.push(...commandOutcomes(plugin, corpora));
|
|
672
776
|
corpora.forEach(c => {
|
|
673
777
|
let unreadable = c.scenarios.filter(s => s.whenElements.length === 0);
|
|
674
778
|
if (unreadable.length !== 0) {
|
|
@@ -805,7 +909,7 @@ function goldenPath(root) {
|
|
|
805
909
|
return Nodepath.join(root.dir, "schema", "lifecycle-model.json");
|
|
806
910
|
}
|
|
807
911
|
|
|
808
|
-
function reportJson(findings, opaque, derived, failures) {
|
|
912
|
+
function reportJson(findings, opaque, derived, outcomes, failures) {
|
|
809
913
|
let findingJson = f => Object.fromEntries([
|
|
810
914
|
[
|
|
811
915
|
"verdict",
|
|
@@ -891,6 +995,38 @@ function reportJson(findings, opaque, derived, failures) {
|
|
|
891
995
|
o.unreadable
|
|
892
996
|
]
|
|
893
997
|
]);
|
|
998
|
+
let shownOutcomeJson = o => Object.fromEntries([
|
|
999
|
+
[
|
|
1000
|
+
"kind",
|
|
1001
|
+
o.kind
|
|
1002
|
+
],
|
|
1003
|
+
[
|
|
1004
|
+
"names",
|
|
1005
|
+
o.names.map(prim => prim)
|
|
1006
|
+
],
|
|
1007
|
+
[
|
|
1008
|
+
"scenarios",
|
|
1009
|
+
o.scenarios.map(prim => prim)
|
|
1010
|
+
]
|
|
1011
|
+
]);
|
|
1012
|
+
let commandOutcomesJson = e => Object.fromEntries([
|
|
1013
|
+
[
|
|
1014
|
+
"plugin",
|
|
1015
|
+
e.plugin
|
|
1016
|
+
],
|
|
1017
|
+
[
|
|
1018
|
+
"component",
|
|
1019
|
+
e.component
|
|
1020
|
+
],
|
|
1021
|
+
[
|
|
1022
|
+
"command",
|
|
1023
|
+
e.command
|
|
1024
|
+
],
|
|
1025
|
+
[
|
|
1026
|
+
"outcomes",
|
|
1027
|
+
e.outcomes.map(shownOutcomeJson)
|
|
1028
|
+
]
|
|
1029
|
+
]);
|
|
894
1030
|
return JSON.stringify(Object.fromEntries([
|
|
895
1031
|
[
|
|
896
1032
|
"version",
|
|
@@ -904,6 +1040,10 @@ function reportJson(findings, opaque, derived, failures) {
|
|
|
904
1040
|
"commands",
|
|
905
1041
|
derived.map(commandJson)
|
|
906
1042
|
],
|
|
1043
|
+
[
|
|
1044
|
+
"outcomes",
|
|
1045
|
+
byPluginComponentCommand(outcomes).map(commandOutcomesJson)
|
|
1046
|
+
],
|
|
907
1047
|
[
|
|
908
1048
|
"opaque",
|
|
909
1049
|
opaque.map(opaqueJson)
|
|
@@ -995,6 +1135,7 @@ function writeOrCompare(path, actual, label, drifted) {
|
|
|
995
1135
|
async function main() {
|
|
996
1136
|
let findings = [];
|
|
997
1137
|
let opaque = [];
|
|
1138
|
+
let outcomes = [];
|
|
998
1139
|
let failures = [];
|
|
999
1140
|
let drifted = [];
|
|
1000
1141
|
let allDerived = [];
|
|
@@ -1024,7 +1165,7 @@ async function main() {
|
|
|
1024
1165
|
if (pluginDir !== undefined) {
|
|
1025
1166
|
let plugin = Nodepath.basename(pluginDir);
|
|
1026
1167
|
let qualified = example + `/` + plugin;
|
|
1027
|
-
let commands = await runPlugin(qualified, pluginDir, findings, opaque);
|
|
1168
|
+
let commands = await runPlugin(qualified, pluginDir, findings, opaque, outcomes);
|
|
1028
1169
|
if (commands.TAG === "Ok") {
|
|
1029
1170
|
let commands$1 = commands._0;
|
|
1030
1171
|
commands$1.forEach(c => {
|
|
@@ -1057,7 +1198,7 @@ async function main() {
|
|
|
1057
1198
|
let of_ = severity => findings.filter(f => f.severity === severity);
|
|
1058
1199
|
let contradicted = of_("contradicted");
|
|
1059
1200
|
if (json) {
|
|
1060
|
-
console.log(reportJson(findings, opaque, allDerived, failures));
|
|
1201
|
+
console.log(reportJson(findings, opaque, allDerived, outcomes, failures));
|
|
1061
1202
|
} else {
|
|
1062
1203
|
[
|
|
1063
1204
|
"contradicted",
|
|
@@ -1131,6 +1272,9 @@ export {
|
|
|
1131
1272
|
sameRow,
|
|
1132
1273
|
observe,
|
|
1133
1274
|
deriveCommands,
|
|
1275
|
+
shownOutcomeOf,
|
|
1276
|
+
byPluginComponentCommand,
|
|
1277
|
+
commandOutcomes,
|
|
1134
1278
|
allUnverified,
|
|
1135
1279
|
compare,
|
|
1136
1280
|
pluginDirsIn,
|
|
@@ -113,6 +113,50 @@ module Id = {
|
|
|
113
113
|
let geolocation = "geolocation"
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/** One transparent-string semantic: a `semantic/` module whose `type t` is a
|
|
117
|
+
bare `string`. */
|
|
118
|
+
type brandedString = {
|
|
119
|
+
moduleName: string,
|
|
120
|
+
/** The `Id` values of this module carry. Not derivable from the module name —
|
|
121
|
+
`CalendarDate` carries `date`. */
|
|
122
|
+
id: string,
|
|
123
|
+
/** Whether the module exposes the `let schema` that sury-ppx resolves `X.t`
|
|
124
|
+
to by convention. The `false`s build theirs from a function taking
|
|
125
|
+
arguments (`forStore` / `forField` / `forCollection`), so such a field
|
|
126
|
+
always carries an explicit `@s.matches` and there is no name a pass could
|
|
127
|
+
derive. Offering one as a plain type pick emits code that does not
|
|
128
|
+
compile. */
|
|
129
|
+
hasDerivableSchema: bool,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Every transparent-string semantic, so a consumer can ask what they are
|
|
133
|
+
instead of transcribing the set.
|
|
134
|
+
|
|
135
|
+
A check written against the literal `string` keyword sees only the brand and
|
|
136
|
+
refuses the field, which would make declaring a semantic cost the field
|
|
137
|
+
whatever that check gates — so a pass that must leave these alone needs the
|
|
138
|
+
set, not a guess. It cannot be guessed from the names: `Money.t` is a record,
|
|
139
|
+
`Duration.t` an int, `Percent.t` and `Bytes.t` floats, and all four are
|
|
140
|
+
deliberately absent.
|
|
141
|
+
|
|
142
|
+
Both facts here are computable from the sources, and `SemanticBrandedStringsTest`
|
|
143
|
+
recomputes them rather than trusting this list — so adding a module without
|
|
144
|
+
adding it here fails, and so does the ppx's copy drifting from either. */
|
|
145
|
+
let brandedStrings: array<brandedString> = [
|
|
146
|
+
{moduleName: "DateTime", id: Id.dateTime, hasDerivableSchema: true},
|
|
147
|
+
{moduleName: "CalendarDate", id: Id.date, hasDerivableSchema: true},
|
|
148
|
+
{moduleName: "Email", id: Id.email, hasDerivableSchema: true},
|
|
149
|
+
{moduleName: "Phone", id: Id.phone, hasDerivableSchema: true},
|
|
150
|
+
{moduleName: "Url", id: Id.url, hasDerivableSchema: true},
|
|
151
|
+
{moduleName: "Color", id: Id.color, hasDerivableSchema: true},
|
|
152
|
+
{moduleName: "FileRef", id: Id.fileRef, hasDerivableSchema: true},
|
|
153
|
+
{moduleName: "ImageRef", id: Id.imageRef, hasDerivableSchema: true},
|
|
154
|
+
{moduleName: "MemberRef", id: Id.memberRef, hasDerivableSchema: false},
|
|
155
|
+
{moduleName: "StorageRef", id: Id.storageRef, hasDerivableSchema: false},
|
|
156
|
+
{moduleName: "UploadableFile", id: Id.uploadableFile, hasDerivableSchema: false},
|
|
157
|
+
{moduleName: "UploadableImage", id: Id.uploadableImage, hasDerivableSchema: false},
|
|
158
|
+
]
|
|
159
|
+
|
|
116
160
|
let semanticId: S.Metadata.Id.t<t> = S.Metadata.Id.make(~namespace="reventless", ~name="semantic")
|
|
117
161
|
|
|
118
162
|
/** Mark a schema as carrying a semantic. */
|
|
@@ -4,25 +4,49 @@ import * as S from "sury/src/S.res.mjs";
|
|
|
4
4
|
import * as Sury from "sury";
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
6
|
|
|
7
|
+
let dateTime = "dateTime";
|
|
8
|
+
|
|
9
|
+
let date = "date";
|
|
10
|
+
|
|
11
|
+
let storageRef = "storageRef";
|
|
12
|
+
|
|
13
|
+
let uploadableImage = "uploadableImage";
|
|
14
|
+
|
|
15
|
+
let uploadableFile = "uploadableFile";
|
|
16
|
+
|
|
17
|
+
let imageRef = "imageRef";
|
|
18
|
+
|
|
19
|
+
let fileRef = "fileRef";
|
|
20
|
+
|
|
21
|
+
let memberRef = "memberRef";
|
|
22
|
+
|
|
23
|
+
let email = "email";
|
|
24
|
+
|
|
25
|
+
let phone = "phone";
|
|
26
|
+
|
|
27
|
+
let url = "url";
|
|
28
|
+
|
|
29
|
+
let color = "color";
|
|
30
|
+
|
|
7
31
|
let Id = {
|
|
8
|
-
dateTime:
|
|
9
|
-
date:
|
|
32
|
+
dateTime: dateTime,
|
|
33
|
+
date: date,
|
|
10
34
|
reference: "reference",
|
|
11
|
-
storageRef:
|
|
35
|
+
storageRef: storageRef,
|
|
12
36
|
offload: "offload",
|
|
13
|
-
uploadableImage:
|
|
14
|
-
uploadableFile:
|
|
15
|
-
imageRef:
|
|
16
|
-
fileRef:
|
|
37
|
+
uploadableImage: uploadableImage,
|
|
38
|
+
uploadableFile: uploadableFile,
|
|
39
|
+
imageRef: imageRef,
|
|
40
|
+
fileRef: fileRef,
|
|
17
41
|
captionedImage: "captionedImage",
|
|
18
|
-
memberRef:
|
|
19
|
-
email:
|
|
20
|
-
phone:
|
|
21
|
-
url:
|
|
42
|
+
memberRef: memberRef,
|
|
43
|
+
email: email,
|
|
44
|
+
phone: phone,
|
|
45
|
+
url: url,
|
|
22
46
|
percent: "percent",
|
|
23
47
|
bytes: "bytes",
|
|
24
48
|
duration: "duration",
|
|
25
|
-
color:
|
|
49
|
+
color: color,
|
|
26
50
|
money: "money",
|
|
27
51
|
dateRange: "dateRange",
|
|
28
52
|
geoPoint: "geoPoint",
|
|
@@ -30,6 +54,69 @@ let Id = {
|
|
|
30
54
|
geolocation: "geolocation"
|
|
31
55
|
};
|
|
32
56
|
|
|
57
|
+
let brandedStrings = [
|
|
58
|
+
{
|
|
59
|
+
moduleName: "DateTime",
|
|
60
|
+
id: dateTime,
|
|
61
|
+
hasDerivableSchema: true
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
moduleName: "CalendarDate",
|
|
65
|
+
id: date,
|
|
66
|
+
hasDerivableSchema: true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
moduleName: "Email",
|
|
70
|
+
id: email,
|
|
71
|
+
hasDerivableSchema: true
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
moduleName: "Phone",
|
|
75
|
+
id: phone,
|
|
76
|
+
hasDerivableSchema: true
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
moduleName: "Url",
|
|
80
|
+
id: url,
|
|
81
|
+
hasDerivableSchema: true
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
moduleName: "Color",
|
|
85
|
+
id: color,
|
|
86
|
+
hasDerivableSchema: true
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
moduleName: "FileRef",
|
|
90
|
+
id: fileRef,
|
|
91
|
+
hasDerivableSchema: true
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
moduleName: "ImageRef",
|
|
95
|
+
id: imageRef,
|
|
96
|
+
hasDerivableSchema: true
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
moduleName: "MemberRef",
|
|
100
|
+
id: memberRef,
|
|
101
|
+
hasDerivableSchema: false
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
moduleName: "StorageRef",
|
|
105
|
+
id: storageRef,
|
|
106
|
+
hasDerivableSchema: false
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
moduleName: "UploadableFile",
|
|
110
|
+
id: uploadableFile,
|
|
111
|
+
hasDerivableSchema: false
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
moduleName: "UploadableImage",
|
|
115
|
+
id: uploadableImage,
|
|
116
|
+
hasDerivableSchema: false
|
|
117
|
+
}
|
|
118
|
+
];
|
|
119
|
+
|
|
33
120
|
let semanticId = Sury.$Metadata_Id_make("reventless", "semantic");
|
|
34
121
|
|
|
35
122
|
function mark(schema, id, payloadOpt) {
|
|
@@ -129,6 +216,7 @@ function has(fieldSchema, id) {
|
|
|
129
216
|
|
|
130
217
|
export {
|
|
131
218
|
Id,
|
|
219
|
+
brandedStrings,
|
|
132
220
|
semanticId,
|
|
133
221
|
mark,
|
|
134
222
|
refined,
|
|
@@ -27,6 +27,11 @@ provisioned" on both platforms.
|
|
|
27
27
|
`userId` is what the platform stamps on rows this account writes. Locally it
|
|
28
28
|
defaults to the username; on AWS it is the `sub` the pool minted, so it can only
|
|
29
29
|
be filled in after the account exists.
|
|
30
|
+
|
|
31
|
+
`demoOwner` names the person this account plays in a demo's data, for a seed to
|
|
32
|
+
find it by. It exists because the name a pool accepts is not always the demo's:
|
|
33
|
+
a pool that signs in on an email address takes `shopper@example.com`, not
|
|
34
|
+
`shopper`.
|
|
30
35
|
*/
|
|
31
36
|
@schema
|
|
32
37
|
type entry = {
|
|
@@ -34,6 +39,7 @@ type entry = {
|
|
|
34
39
|
password: string,
|
|
35
40
|
groups: array<string>,
|
|
36
41
|
userId?: string,
|
|
42
|
+
demoOwner?: string,
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
let entriesSchema = S.array(entrySchema)
|
|
@@ -15,7 +15,8 @@ let entrySchema = Sury.$schema(s => ({
|
|
|
15
15
|
username: s.m(Sury.string),
|
|
16
16
|
password: s.m(Sury.string),
|
|
17
17
|
groups: s.m(Sury.array(Sury.string)),
|
|
18
|
-
userId: s.m(Sury.$option(Sury.string))
|
|
18
|
+
userId: s.m(Sury.$option(Sury.string)),
|
|
19
|
+
demoOwner: s.m(Sury.$option(Sury.string))
|
|
19
20
|
}));
|
|
20
21
|
|
|
21
22
|
let entriesSchema = Sury.array(entrySchema);
|