@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.
@@ -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: "dateTime",
9
- date: "date",
32
+ dateTime: dateTime,
33
+ date: date,
10
34
  reference: "reference",
11
- storageRef: "storageRef",
35
+ storageRef: storageRef,
12
36
  offload: "offload",
13
- uploadableImage: "uploadableImage",
14
- uploadableFile: "uploadableFile",
15
- imageRef: "imageRef",
16
- fileRef: "fileRef",
37
+ uploadableImage: uploadableImage,
38
+ uploadableFile: uploadableFile,
39
+ imageRef: imageRef,
40
+ fileRef: fileRef,
17
41
  captionedImage: "captionedImage",
18
- memberRef: "memberRef",
19
- email: "email",
20
- phone: "phone",
21
- url: "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: "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);