@reventlessdev/reventless-spec 3.0.0-alpha.62 → 3.0.0-alpha.64
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 +15 -0
- package/package.json +15 -3
- package/rescript.json +6 -1
- package/src/components/ComponentKind.res +99 -0
- package/src/components/ComponentKind.res.mjs +125 -0
- package/src/components/DcbDecode.res +11 -21
- package/src/components/DcbDecode.res.mjs +8 -25
- package/src/components/DcbTag.res +31 -104
- package/src/components/DcbTag.res.mjs +33 -120
- package/src/components/DcbValidation.res +32 -13
- package/src/components/DcbValidation.res.mjs +45 -13
- package/src/components/PluginName.res +39 -0
- package/src/components/PluginName.res.mjs +45 -0
- package/src/components/Snapshot.res +26 -0
- package/src/components/Snapshot.res.mjs +2 -0
- package/src/generator/Config.res +9 -26
- package/src/generator/Config.res.mjs +5 -30
- package/src/generator/Discovery.res +5 -26
- package/src/generator/Discovery.res.mjs +4 -54
- package/src/generator/Pairing.res +33 -25
- package/src/generator/Pairing.res.mjs +35 -27
- package/src/generator/PluginGenerator.res +5 -0
- package/src/generator/PluginGenerator.res.mjs +1 -0
- package/src/types/Behavior.res +8 -0
|
@@ -130,6 +130,19 @@ function extractTagsFromProperties(properties, jsonDict) {
|
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
function variantTagName(items) {
|
|
134
|
+
return Stdlib_Option.flatMap(items.find(item => item.location === "TAG"), item => {
|
|
135
|
+
let match = item.schema;
|
|
136
|
+
if (match.type !== "string") {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
let $$const = match.const;
|
|
140
|
+
if ($$const !== undefined) {
|
|
141
|
+
return $$const;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
133
146
|
function extractTagsFromJson(schema, json) {
|
|
134
147
|
switch (schema.type) {
|
|
135
148
|
case "object" :
|
|
@@ -152,22 +165,12 @@ function extractTagsFromJson(schema, json) {
|
|
|
152
165
|
return Stdlib_Array.reduce(schema.anyOf, [], (acc, variantSchema) => {
|
|
153
166
|
if (acc.length !== 0) {
|
|
154
167
|
return acc;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
let match = item.schema;
|
|
161
|
-
if (match.type !== "string") {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
let $$const = match.const;
|
|
165
|
-
if ($$const !== undefined) {
|
|
166
|
-
return $$const;
|
|
168
|
+
} else if (variantSchema.type === "object") {
|
|
169
|
+
if (Primitive_object.equal(variantTagName(variantSchema.items), jsonTag)) {
|
|
170
|
+
return extractTagsFromProperties(variantSchema.properties, jsonDict$1);
|
|
171
|
+
} else {
|
|
172
|
+
return [];
|
|
167
173
|
}
|
|
168
|
-
});
|
|
169
|
-
if (Primitive_object.equal(variantTag, jsonTag)) {
|
|
170
|
-
return extractTagsFromProperties(variantSchema.properties, jsonDict$1);
|
|
171
174
|
} else {
|
|
172
175
|
return [];
|
|
173
176
|
}
|
|
@@ -185,29 +188,11 @@ function extractTags(schema, value) {
|
|
|
185
188
|
function extractVariantNames(schema) {
|
|
186
189
|
switch (schema.type) {
|
|
187
190
|
case "object" :
|
|
188
|
-
return Stdlib_Option.
|
|
189
|
-
let match = item.schema;
|
|
190
|
-
if (match.type !== "string") {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
let $$const = match.const;
|
|
194
|
-
if ($$const !== undefined) {
|
|
195
|
-
return [$$const];
|
|
196
|
-
}
|
|
197
|
-
}), []);
|
|
191
|
+
return Stdlib_Option.mapOr(variantTagName(schema.items), [], t => [t]);
|
|
198
192
|
case "union" :
|
|
199
193
|
return Stdlib_Array.filterMap(schema.anyOf, variantSchema => {
|
|
200
194
|
if (variantSchema.type === "object") {
|
|
201
|
-
return
|
|
202
|
-
let match = item.schema;
|
|
203
|
-
if (match.type !== "string") {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
let $$const = match.const;
|
|
207
|
-
if ($$const !== undefined) {
|
|
208
|
-
return $$const;
|
|
209
|
-
}
|
|
210
|
-
});
|
|
195
|
+
return variantTagName(variantSchema.items);
|
|
211
196
|
}
|
|
212
197
|
});
|
|
213
198
|
default:
|
|
@@ -225,16 +210,7 @@ function extractAllVariantNames(schema) {
|
|
|
225
210
|
return [];
|
|
226
211
|
}
|
|
227
212
|
case "object" :
|
|
228
|
-
return Stdlib_Option.
|
|
229
|
-
let match = item.schema;
|
|
230
|
-
if (match.type !== "string") {
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
let $$const = match.const;
|
|
234
|
-
if ($$const !== undefined) {
|
|
235
|
-
return [$$const];
|
|
236
|
-
}
|
|
237
|
-
}), []);
|
|
213
|
+
return Stdlib_Option.mapOr(variantTagName(schema.items), [], t => [t]);
|
|
238
214
|
case "union" :
|
|
239
215
|
return Stdlib_Array.filterMap(schema.anyOf, variantSchema => {
|
|
240
216
|
switch (variantSchema.type) {
|
|
@@ -246,16 +222,7 @@ function extractAllVariantNames(schema) {
|
|
|
246
222
|
return;
|
|
247
223
|
}
|
|
248
224
|
case "object" :
|
|
249
|
-
return
|
|
250
|
-
let match = item.schema;
|
|
251
|
-
if (match.type !== "string") {
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
let $$const = match.const;
|
|
255
|
-
if ($$const !== undefined) {
|
|
256
|
-
return $$const;
|
|
257
|
-
}
|
|
258
|
-
});
|
|
225
|
+
return variantTagName(variantSchema.items);
|
|
259
226
|
default:
|
|
260
227
|
return;
|
|
261
228
|
}
|
|
@@ -272,29 +239,11 @@ function isVariantPayloadBearing(schema, name) {
|
|
|
272
239
|
}
|
|
273
240
|
switch (schema.type) {
|
|
274
241
|
case "object" :
|
|
275
|
-
return Stdlib_Option.
|
|
276
|
-
let match = item.schema;
|
|
277
|
-
if (match.type !== "string") {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
let $$const = match.const;
|
|
281
|
-
if ($$const !== undefined) {
|
|
282
|
-
return $$const === name;
|
|
283
|
-
}
|
|
284
|
-
}), false);
|
|
242
|
+
return Stdlib_Option.mapOr(variantTagName(schema.items), false, t => t === name);
|
|
285
243
|
case "union" :
|
|
286
244
|
return schema.anyOf.some(variantSchema => {
|
|
287
245
|
if (variantSchema.type === "object") {
|
|
288
|
-
return Stdlib_Option.
|
|
289
|
-
let match = item.schema;
|
|
290
|
-
if (match.type !== "string") {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
let $$const = match.const;
|
|
294
|
-
if ($$const !== undefined) {
|
|
295
|
-
return $$const === name;
|
|
296
|
-
}
|
|
297
|
-
}), false);
|
|
246
|
+
return Stdlib_Option.mapOr(variantTagName(variantSchema.items), false, t => t === name);
|
|
298
247
|
} else {
|
|
299
248
|
return false;
|
|
300
249
|
}
|
|
@@ -359,22 +308,12 @@ function extractTagsFromJsonExpanded(schema, json) {
|
|
|
359
308
|
return Stdlib_Array.reduce(schema.anyOf, [], (acc, variantSchema) => {
|
|
360
309
|
if (acc.length !== 0) {
|
|
361
310
|
return acc;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
let match = item.schema;
|
|
368
|
-
if (match.type !== "string") {
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
let $$const = match.const;
|
|
372
|
-
if ($$const !== undefined) {
|
|
373
|
-
return $$const;
|
|
311
|
+
} else if (variantSchema.type === "object") {
|
|
312
|
+
if (Primitive_object.equal(variantTagName(variantSchema.items), jsonTag)) {
|
|
313
|
+
return extractTagsFromPropertiesExpanded(variantSchema.properties, jsonDict$1);
|
|
314
|
+
} else {
|
|
315
|
+
return [];
|
|
374
316
|
}
|
|
375
|
-
});
|
|
376
|
-
if (Primitive_object.equal(variantTag, jsonTag)) {
|
|
377
|
-
return extractTagsFromPropertiesExpanded(variantSchema.properties, jsonDict$1);
|
|
378
317
|
} else {
|
|
379
318
|
return [];
|
|
380
319
|
}
|
|
@@ -427,16 +366,7 @@ function extractTagKeysByEventType(schema) {
|
|
|
427
366
|
return;
|
|
428
367
|
}
|
|
429
368
|
let properties = variantSchema.properties;
|
|
430
|
-
Stdlib_Option.forEach(
|
|
431
|
-
let match = item.schema;
|
|
432
|
-
if (match.type !== "string") {
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
let $$const = match.const;
|
|
436
|
-
if ($$const !== undefined) {
|
|
437
|
-
return $$const;
|
|
438
|
-
}
|
|
439
|
-
}), eventType => {
|
|
369
|
+
Stdlib_Option.forEach(variantTagName(variantSchema.items), eventType => {
|
|
440
370
|
result[eventType] = tagKeysOfProperties(properties);
|
|
441
371
|
});
|
|
442
372
|
};
|
|
@@ -608,16 +538,7 @@ function eventShapesOfSchema(schema) {
|
|
|
608
538
|
}
|
|
609
539
|
case "object" :
|
|
610
540
|
let properties = variantSchema.properties;
|
|
611
|
-
return Stdlib_Option.map(
|
|
612
|
-
let match = item.schema;
|
|
613
|
-
if (match.type !== "string") {
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
let $$const = match.const;
|
|
617
|
-
if ($$const !== undefined) {
|
|
618
|
-
return $$const;
|
|
619
|
-
}
|
|
620
|
-
}), eventType => ({
|
|
541
|
+
return Stdlib_Option.map(variantTagName(variantSchema.items), eventType => ({
|
|
621
542
|
eventType: eventType,
|
|
622
543
|
idFields: idFieldsOfProperties(properties)
|
|
623
544
|
}));
|
|
@@ -702,16 +623,7 @@ function findMultiTagVariantNames(schema) {
|
|
|
702
623
|
}
|
|
703
624
|
let tagCount = Object.entries(variantSchema.properties).filter(param => isTagged(param[1])).length;
|
|
704
625
|
if (tagCount > 1) {
|
|
705
|
-
return Stdlib_Option.getOr(
|
|
706
|
-
let match = item.schema;
|
|
707
|
-
if (match.type !== "string") {
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
let $$const = match.const;
|
|
711
|
-
if ($$const !== undefined) {
|
|
712
|
-
return $$const;
|
|
713
|
-
}
|
|
714
|
-
}), "(unknown)");
|
|
626
|
+
return Stdlib_Option.getOr(variantTagName(variantSchema.items), "(unknown)");
|
|
715
627
|
}
|
|
716
628
|
};
|
|
717
629
|
if (schema.type === "union") {
|
|
@@ -909,6 +821,7 @@ export {
|
|
|
909
821
|
resolveArrayTagKey,
|
|
910
822
|
jsonValueToString,
|
|
911
823
|
extractTagsFromProperties,
|
|
824
|
+
variantTagName,
|
|
912
825
|
extractTagsFromJson,
|
|
913
826
|
extractTags,
|
|
914
827
|
extractVariantNames,
|
|
@@ -13,16 +13,7 @@ type variantInfo = {
|
|
|
13
13
|
let extractVariantInfo = (variantSchema: S.t<unknown>): option<variantInfo> =>
|
|
14
14
|
switch variantSchema {
|
|
15
15
|
| Object({items, properties}) =>
|
|
16
|
-
|
|
17
|
-
items
|
|
18
|
-
->Array.find(item => item.location == "TAG")
|
|
19
|
-
->Option.flatMap(item =>
|
|
20
|
-
switch item.schema {
|
|
21
|
-
| String({const}) => Some(const)
|
|
22
|
-
| _ => None
|
|
23
|
-
}
|
|
24
|
-
)
|
|
25
|
-
tagName->Option.map(tagName => {
|
|
16
|
+
DcbTag.variantTagName(items)->Option.map(tagName => {
|
|
26
17
|
let taggedFields =
|
|
27
18
|
properties
|
|
28
19
|
->Dict.toArray
|
|
@@ -56,7 +47,10 @@ let extractAllVariants = (schema: S.t<unknown>): array<variantInfo> =>
|
|
|
56
47
|
// Check if a variant has no payload fields (only TAG)
|
|
57
48
|
let isPayloadLess = (info: variantInfo): bool => info.fields->Dict.toArray->Array.length == 0
|
|
58
49
|
|
|
59
|
-
// Compare two sury schema types for structural compatibility
|
|
50
|
+
// Compare two sury schema types for structural compatibility. Objects and
|
|
51
|
+
// unions recurse: previously any (Object, Object) / (Union, Union) pair was
|
|
52
|
+
// declared compatible, so nested payload drift (a renamed/retyped nested field,
|
|
53
|
+
// a changed variant) passed validation undetected.
|
|
60
54
|
let rec schemasAreCompatible = (a: S.t<unknown>, b: S.t<unknown>): bool =>
|
|
61
55
|
switch (a, b) {
|
|
62
56
|
| (String(_), String(_)) => true
|
|
@@ -65,10 +59,35 @@ let rec schemasAreCompatible = (a: S.t<unknown>, b: S.t<unknown>): bool =>
|
|
|
65
59
|
| (BigInt(_), BigInt(_)) => true
|
|
66
60
|
| (Array({additionalItems: Schema(aItem)}), Array({additionalItems: Schema(bItem)})) =>
|
|
67
61
|
schemasAreCompatible(aItem, bItem)
|
|
68
|
-
| (
|
|
69
|
-
| (
|
|
62
|
+
| (Object({properties: aProps}), Object({properties: bProps})) => propsCompatible(aProps, bProps)
|
|
63
|
+
| (Union(_), Union(_)) =>
|
|
64
|
+
// Same set of variant tags, and each shared variant's payload compatible.
|
|
65
|
+
let av = extractAllVariants(a)
|
|
66
|
+
let bv = extractAllVariants(b)
|
|
67
|
+
let at = av->Array.map(v => v.tagName)->Array.toSorted(String.compare)
|
|
68
|
+
let bt = bv->Array.map(v => v.tagName)->Array.toSorted(String.compare)
|
|
69
|
+
at == bt &&
|
|
70
|
+
at->Array.every(tag =>
|
|
71
|
+
switch (av->Array.find(v => v.tagName == tag), bv->Array.find(v => v.tagName == tag)) {
|
|
72
|
+
| (Some(x), Some(y)) => propsCompatible(x.fields, y.fields)
|
|
73
|
+
| _ => false
|
|
74
|
+
}
|
|
75
|
+
)
|
|
70
76
|
| _ => false
|
|
71
77
|
}
|
|
78
|
+
// Two property maps are compatible when they carry the same field names and each
|
|
79
|
+
// field's schema is compatible.
|
|
80
|
+
and propsCompatible = (aProps: dict<S.t<unknown>>, bProps: dict<S.t<unknown>>): bool => {
|
|
81
|
+
let aKeys = aProps->Dict.keysToArray->Array.toSorted(String.compare)
|
|
82
|
+
let bKeys = bProps->Dict.keysToArray->Array.toSorted(String.compare)
|
|
83
|
+
aKeys == bKeys &&
|
|
84
|
+
aKeys->Array.every(k =>
|
|
85
|
+
switch (aProps->Dict.get(k), bProps->Dict.get(k)) {
|
|
86
|
+
| (Some(av), Some(bv)) => schemasAreCompatible(av, bv)
|
|
87
|
+
| _ => false
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
}
|
|
72
91
|
|
|
73
92
|
// Get a human-readable type name for a schema
|
|
74
93
|
let schemaTypeName = (schema: S.t<unknown>): string =>
|
|
@@ -11,17 +11,7 @@ function extractVariantInfo(variantSchema) {
|
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
let properties = variantSchema.properties;
|
|
14
|
-
|
|
15
|
-
let match = item.schema;
|
|
16
|
-
if (match.type !== "string") {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
let $$const = match.const;
|
|
20
|
-
if ($$const !== undefined) {
|
|
21
|
-
return $$const;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
return Stdlib_Option.map(tagName, tagName => {
|
|
14
|
+
return Stdlib_Option.map(DcbTag$Reventless.variantTagName(variantSchema.items), tagName => {
|
|
25
15
|
let taggedFields = Stdlib_Array.filterMap(Object.entries(properties), param => {
|
|
26
16
|
if (DcbTag$Reventless.isTagged(param[1])) {
|
|
27
17
|
return param[0];
|
|
@@ -95,15 +85,56 @@ function schemasAreCompatible(_a, _b) {
|
|
|
95
85
|
_a = aItem;
|
|
96
86
|
continue;
|
|
97
87
|
case "object" :
|
|
98
|
-
|
|
88
|
+
if (b.type === "object") {
|
|
89
|
+
return propsCompatible(a.properties, b.properties);
|
|
90
|
+
} else {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
99
93
|
case "union" :
|
|
100
|
-
|
|
94
|
+
if (b.type !== "union") {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
let av = extractAllVariants(a);
|
|
98
|
+
let bv = extractAllVariants(b);
|
|
99
|
+
let at = av.map(v => v.tagName).toSorted(Primitive_string.compare);
|
|
100
|
+
let bt = bv.map(v => v.tagName).toSorted(Primitive_string.compare);
|
|
101
|
+
if (Primitive_object.equal(at, bt)) {
|
|
102
|
+
return at.every(tag => {
|
|
103
|
+
let match = av.find(v => v.tagName === tag);
|
|
104
|
+
let match$1 = bv.find(v => v.tagName === tag);
|
|
105
|
+
if (match !== undefined && match$1 !== undefined) {
|
|
106
|
+
return propsCompatible(match.fields, match$1.fields);
|
|
107
|
+
} else {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
101
114
|
default:
|
|
102
115
|
return false;
|
|
103
116
|
}
|
|
104
117
|
};
|
|
105
118
|
}
|
|
106
119
|
|
|
120
|
+
function propsCompatible(aProps, bProps) {
|
|
121
|
+
let aKeys = Object.keys(aProps).toSorted(Primitive_string.compare);
|
|
122
|
+
let bKeys = Object.keys(bProps).toSorted(Primitive_string.compare);
|
|
123
|
+
if (Primitive_object.equal(aKeys, bKeys)) {
|
|
124
|
+
return aKeys.every(k => {
|
|
125
|
+
let match = aProps[k];
|
|
126
|
+
let match$1 = bProps[k];
|
|
127
|
+
if (match !== undefined && match$1 !== undefined) {
|
|
128
|
+
return schemasAreCompatible(match, match$1);
|
|
129
|
+
} else {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
107
138
|
function schemaTypeName(schema) {
|
|
108
139
|
switch (schema.type) {
|
|
109
140
|
case "never" :
|
|
@@ -381,6 +412,7 @@ export {
|
|
|
381
412
|
extractAllVariants,
|
|
382
413
|
isPayloadLess,
|
|
383
414
|
schemasAreCompatible,
|
|
415
|
+
propsCompatible,
|
|
384
416
|
schemaTypeName,
|
|
385
417
|
dedupeKeys,
|
|
386
418
|
validateCompositeReads,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Single source of truth for deriving a plugin's PascalCase name from its npm
|
|
2
|
+
// package name, and for resolving the final plugin name from the (optional)
|
|
3
|
+
// plugin.json `name` and package.json `name`. The plugin generator
|
|
4
|
+
// (`generator/Config`) and the external tooling that reconstructs the domain
|
|
5
|
+
// graph (`reventless-gwt`'s `LocalHost`) both derive from this, so the two can't
|
|
6
|
+
// drift — a drift here silently breaks graph plugin keys (the graph would key
|
|
7
|
+
// plugins under names that don't match the ones the generator bakes into
|
|
8
|
+
// `Plugin.res`). See [[reference_ppx_spec_name_single_strip]] for the sibling
|
|
9
|
+
// spec-name derivation.
|
|
10
|
+
|
|
11
|
+
// "@scope/my-catalog" → "MyCatalog", "online-shop" → "OnlineShop".
|
|
12
|
+
let fromPackageName = (packageName: string): string => {
|
|
13
|
+
let base = switch packageName->String.split("/") {
|
|
14
|
+
| [_scope, local] => local
|
|
15
|
+
| [local] => local
|
|
16
|
+
| parts => parts->Array.getUnsafe(parts->Array.length - 1)
|
|
17
|
+
}
|
|
18
|
+
base
|
|
19
|
+
->String.split("-")
|
|
20
|
+
->Array.flatMap(part => part->String.split("_"))
|
|
21
|
+
->Array.map(word =>
|
|
22
|
+
if word === "" {
|
|
23
|
+
""
|
|
24
|
+
} else {
|
|
25
|
+
word->String.slice(~start=0, ~end=1)->String.toUpperCase ++
|
|
26
|
+
word->String.slice(~start=1, ~end=word->String.length)
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
->Array.join("")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// plugin.json `name` wins; else PascalCase(package.json `name`); else "Plugin".
|
|
33
|
+
// The two callers each read the raw fields with their own node bindings, then
|
|
34
|
+
// share this precedence so the resolution can never diverge.
|
|
35
|
+
let resolve = (~pluginJsonName: option<string>, ~packageJsonName: option<string>): string =>
|
|
36
|
+
switch pluginJsonName {
|
|
37
|
+
| Some(name) => name
|
|
38
|
+
| None => packageJsonName->Option.map(fromPackageName)->Option.getOr("Plugin")
|
|
39
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
|
|
5
|
+
function fromPackageName(packageName) {
|
|
6
|
+
let parts = packageName.split("/");
|
|
7
|
+
let len = parts.length;
|
|
8
|
+
let base;
|
|
9
|
+
if (len >= 3) {
|
|
10
|
+
base = parts[parts.length - 1 | 0];
|
|
11
|
+
} else {
|
|
12
|
+
switch (len) {
|
|
13
|
+
case 0 :
|
|
14
|
+
base = parts[parts.length - 1 | 0];
|
|
15
|
+
break;
|
|
16
|
+
case 1 :
|
|
17
|
+
base = parts[0];
|
|
18
|
+
break;
|
|
19
|
+
case 2 :
|
|
20
|
+
base = parts[1];
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return base.split("-").flatMap(part => part.split("_")).map(word => {
|
|
25
|
+
if (word === "") {
|
|
26
|
+
return "";
|
|
27
|
+
} else {
|
|
28
|
+
return word.slice(0, 1).toUpperCase() + word.slice(1, word.length);
|
|
29
|
+
}
|
|
30
|
+
}).join("");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolve(pluginJsonName, packageJsonName) {
|
|
34
|
+
if (pluginJsonName !== undefined) {
|
|
35
|
+
return pluginJsonName;
|
|
36
|
+
} else {
|
|
37
|
+
return Stdlib_Option.getOr(Stdlib_Option.map(packageJsonName, fromPackageName), "Plugin");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
fromPackageName,
|
|
43
|
+
resolve,
|
|
44
|
+
}
|
|
45
|
+
/* No side effect */
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Per-aggregate persisted-snapshot configuration
|
|
2
|
+
// (docs/plans/aggregate-snapshotting.md).
|
|
3
|
+
//
|
|
4
|
+
// Snapshotting is opt-in and lives on the Behavior module (where `state` is
|
|
5
|
+
// defined): `Behavior.T.snapshot = None` (the default, auto-injected by
|
|
6
|
+
// `@@reventless.behavior`) keeps full replay; `Some(config)` makes the
|
|
7
|
+
// aggregate write a keep-one snapshot every `interval` events and seed cold
|
|
8
|
+
// replays from the latest one. Snapshots are a read optimization only — the
|
|
9
|
+
// OCC append remains the sole consistency primitive, and a missing, corrupt,
|
|
10
|
+
// or schema-drifted snapshot degrades to full replay.
|
|
11
|
+
//
|
|
12
|
+
// Enable via the file-level attribute on a behavior file:
|
|
13
|
+
//
|
|
14
|
+
// @@reventless.snapshots(100)
|
|
15
|
+
//
|
|
16
|
+
// which injects `let snapshot = Some({interval: 100, stateSchema})` (requires
|
|
17
|
+
// `@schema type state` so sury generates the schema), or hand-write the
|
|
18
|
+
// binding for full control.
|
|
19
|
+
|
|
20
|
+
type config<'state> = {
|
|
21
|
+
/** Write a snapshot after every N appended events. Required — no default. */
|
|
22
|
+
interval: int,
|
|
23
|
+
/** Sury schema for the behavior's state — serializes the persisted snapshot
|
|
24
|
+
and (hashed) gates staleness across state-shape changes. */
|
|
25
|
+
stateSchema: S.t<'state>,
|
|
26
|
+
}
|
package/src/generator/Config.res
CHANGED
|
@@ -12,25 +12,9 @@ type config = {
|
|
|
12
12
|
|
|
13
13
|
// Convert npm package name to PascalCase plugin name.
|
|
14
14
|
// "@scope/my-catalog" → "MyCatalog", "online-shop" → "OnlineShop"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
| [local] => local
|
|
19
|
-
| parts => parts->Array.getUnsafe(parts->Array.length - 1)
|
|
20
|
-
}
|
|
21
|
-
base
|
|
22
|
-
->String.split("-")
|
|
23
|
-
->Array.flatMap(part => part->String.split("_"))
|
|
24
|
-
->Array.map(word =>
|
|
25
|
-
if word === "" {
|
|
26
|
-
""
|
|
27
|
-
} else {
|
|
28
|
-
word->String.slice(~start=0, ~end=1)->String.toUpperCase ++
|
|
29
|
-
word->String.slice(~start=1, ~end=word->String.length)
|
|
30
|
-
}
|
|
31
|
-
)
|
|
32
|
-
->Array.join("")
|
|
33
|
-
}
|
|
15
|
+
// Single-sourced with reventless-gwt's LocalHost via `PluginName` so the two
|
|
16
|
+
// can't drift (a drift silently breaks graph plugin keys).
|
|
17
|
+
let packageNameToPluginName = PluginName.fromPackageName
|
|
34
18
|
|
|
35
19
|
let readJson = (path: string): option<JSON.t> =>
|
|
36
20
|
try Some(Generator_Node.readFileSync(path)->JSON.parseOrThrow) catch {
|
|
@@ -58,13 +42,9 @@ let getStrArrayField = (json: JSON.t, key: string): option<array<string>> =>
|
|
|
58
42
|
->Option.map(arr => arr->Array.filterMap(JSON.Decode.string))
|
|
59
43
|
|
|
60
44
|
let read = (~srcDir: string): config => {
|
|
61
|
-
//
|
|
45
|
+
// Read package.json `name` from the parent of srcDir
|
|
62
46
|
let packageJsonPath = Generator_Node.join([Generator_Node.dirname(srcDir), "package.json"])
|
|
63
|
-
let
|
|
64
|
-
readJson(packageJsonPath)
|
|
65
|
-
->Option.flatMap(j => getStrField(j, "name"))
|
|
66
|
-
->Option.map(packageNameToPluginName)
|
|
67
|
-
->Option.getOr("Plugin")
|
|
47
|
+
let packageJsonName = readJson(packageJsonPath)->Option.flatMap(j => getStrField(j, "name"))
|
|
68
48
|
|
|
69
49
|
// Read plugin.json from srcDir (optional)
|
|
70
50
|
let pluginJsonPath = Generator_Node.join([srcDir, "plugin.json"])
|
|
@@ -75,7 +55,10 @@ let read = (~srcDir: string): config => {
|
|
|
75
55
|
}
|
|
76
56
|
|
|
77
57
|
{
|
|
78
|
-
name:
|
|
58
|
+
name: PluginName.resolve(
|
|
59
|
+
~pluginJsonName=pluginJson->Option.flatMap(j => getStrField(j, "name")),
|
|
60
|
+
~packageJsonName,
|
|
61
|
+
),
|
|
79
62
|
heartbeatInterval: pluginJson
|
|
80
63
|
->Option.flatMap(j => getIntField(j, "heartbeatInterval"))
|
|
81
64
|
->Option.getOr(5),
|
|
@@ -5,34 +5,7 @@ import * as Nodepath from "node:path";
|
|
|
5
5
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
6
6
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
7
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
8
|
-
|
|
9
|
-
function packageNameToPluginName(packageName) {
|
|
10
|
-
let parts = packageName.split("/");
|
|
11
|
-
let len = parts.length;
|
|
12
|
-
let base;
|
|
13
|
-
if (len >= 3) {
|
|
14
|
-
base = parts[parts.length - 1 | 0];
|
|
15
|
-
} else {
|
|
16
|
-
switch (len) {
|
|
17
|
-
case 0 :
|
|
18
|
-
base = parts[parts.length - 1 | 0];
|
|
19
|
-
break;
|
|
20
|
-
case 1 :
|
|
21
|
-
base = parts[0];
|
|
22
|
-
break;
|
|
23
|
-
case 2 :
|
|
24
|
-
base = parts[1];
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return base.split("-").flatMap(part => part.split("_")).map(word => {
|
|
29
|
-
if (word === "") {
|
|
30
|
-
return "";
|
|
31
|
-
} else {
|
|
32
|
-
return word.slice(0, 1).toUpperCase() + word.slice(1, word.length);
|
|
33
|
-
}
|
|
34
|
-
}).join("");
|
|
35
|
-
}
|
|
8
|
+
import * as PluginName$Reventless from "../components/PluginName.res.mjs";
|
|
36
9
|
|
|
37
10
|
function readJson(path) {
|
|
38
11
|
try {
|
|
@@ -56,17 +29,19 @@ function getStrArrayField(json, key) {
|
|
|
56
29
|
|
|
57
30
|
function read(srcDir) {
|
|
58
31
|
let packageJsonPath = Nodepath.join(Nodepath.dirname(srcDir), "package.json");
|
|
59
|
-
let
|
|
32
|
+
let packageJsonName = Stdlib_Option.flatMap(readJson(packageJsonPath), j => getStrField(j, "name"));
|
|
60
33
|
let pluginJsonPath = Nodepath.join(srcDir, "plugin.json");
|
|
61
34
|
let pluginJson = Nodefs.existsSync(pluginJsonPath) ? readJson(pluginJsonPath) : undefined;
|
|
62
35
|
return {
|
|
63
|
-
name:
|
|
36
|
+
name: PluginName$Reventless.resolve(Stdlib_Option.flatMap(pluginJson, j => getStrField(j, "name")), packageJsonName),
|
|
64
37
|
heartbeatInterval: Stdlib_Option.getOr(Stdlib_Option.flatMap(pluginJson, j => getIntField(j, "heartbeatInterval")), 5),
|
|
65
38
|
exclude: Stdlib_Option.getOr(Stdlib_Option.flatMap(pluginJson, j => getStrArrayField(j, "exclude")), []),
|
|
66
39
|
variant: "Composition"
|
|
67
40
|
};
|
|
68
41
|
}
|
|
69
42
|
|
|
43
|
+
let packageNameToPluginName = PluginName$Reventless.fromPackageName;
|
|
44
|
+
|
|
70
45
|
export {
|
|
71
46
|
packageNameToPluginName,
|
|
72
47
|
readJson,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Directory scanner: walks src/, classifies .res files by parent folder name.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Re-export the single-source `ComponentKind.t` (same constructors, so the bare
|
|
4
|
+
// pattern matches in `Pairing` keep resolving via type inference) and derive the
|
|
5
|
+
// folder classifier from it — one vocabulary shared with the gwt discovery.
|
|
6
|
+
type componentType = ComponentKind.t =
|
|
4
7
|
| StateChangeSlice
|
|
5
8
|
| StateViewSlice
|
|
6
9
|
| StateViewSliceStream
|
|
@@ -17,31 +20,7 @@ type componentType =
|
|
|
17
20
|
// relPath: path relative to srcDir (e.g. "ReadModel/ProductsProjections.res")
|
|
18
21
|
type discoveredFile = {stem: string, componentType: componentType, epGroup: option<string>, relPath: string}
|
|
19
22
|
|
|
20
|
-
let folderToComponentType =
|
|
21
|
-
switch folder {
|
|
22
|
-
| "StateChange" | "StateChanges" | "StateChangeSlice" | "StateChangeSlices" =>
|
|
23
|
-
Some(StateChangeSlice)
|
|
24
|
-
| "StateView" | "StateViews" | "StateViewSlice" | "StateViewSlices" => Some(StateViewSlice)
|
|
25
|
-
| "StateViewSliceStream" | "StateViewSliceStreams" => Some(StateViewSliceStream)
|
|
26
|
-
| "Automation" | "Automations" | "AutomationSlice" | "AutomationSlices" => Some(AutomationSlice)
|
|
27
|
-
| "InboundTranslation"
|
|
28
|
-
| "InboundTranslations"
|
|
29
|
-
| "InboundTranslationSlice"
|
|
30
|
-
| "InboundTranslationSlices" =>
|
|
31
|
-
Some(InboundTranslationSlice)
|
|
32
|
-
| "OutboundTranslation"
|
|
33
|
-
| "OutboundTranslations"
|
|
34
|
-
| "OutboundTranslationSlice"
|
|
35
|
-
| "OutboundTranslationSlices" =>
|
|
36
|
-
Some(OutboundTranslationSlice)
|
|
37
|
-
| "Aggregate" | "Aggregates" => Some(Aggregate)
|
|
38
|
-
| "ReadModel" | "ReadModels" => Some(ReadModel)
|
|
39
|
-
| "ReadModelStream" | "ReadModelStreams" => Some(ReadModelStream)
|
|
40
|
-
| "Task" | "Tasks" => Some(Task)
|
|
41
|
-
| "ExtensionPoint" | "ExtensionPoints" => Some(ExtensionPoint)
|
|
42
|
-
| "Extension" | "Extensions" => Some(Extension)
|
|
43
|
-
| _ => None
|
|
44
|
-
}
|
|
23
|
+
let folderToComponentType = ComponentKind.folderToKind
|
|
45
24
|
|
|
46
25
|
let isAlwaysExcludedDir = (name: string): bool =>
|
|
47
26
|
name === "Plugin" || name === "tests" || name === "lib"
|