@reventlessdev/reventless-spec 3.0.0-alpha.103 → 3.0.0-alpha.104
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
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.104 (2026-08-09)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **spec:** see payload-less variants inside a mixed union, and say what differs ([e82e742](https://github.com/ReventlessDev/reventless-core/commit/e82e742201941f978bfde809eeb2581fdda453c5))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 3.0.0-alpha.103 (2026-08-09)
|
|
7
14
|
|
|
8
15
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -26,22 +26,28 @@ let extractVariantInfo = (variantSchema: S.t<unknown>): option<variantInfo> =>
|
|
|
26
26
|
)
|
|
27
27
|
{tagName, fields: properties, taggedFields}
|
|
28
28
|
})
|
|
29
|
+
// Payload-less variant — sury compiles a constructor with no payload to
|
|
30
|
+
// String({const}), whether it stands alone (`type t = Foo`) or sits beside
|
|
31
|
+
// payload-carrying constructors in one union (`Foo | Bar(string)`). Matching it
|
|
32
|
+
// here rather than only at the top level is what makes it visible inside a
|
|
33
|
+
// mixed union: the Union branch below filters members through this function, so
|
|
34
|
+
// while this case lived only in `extractAllVariants` every payload-less arm of a
|
|
35
|
+
// mixed union was silently dropped — `NoPriorType | PriorType(string)` extracted
|
|
36
|
+
// as just `[PriorType]`, and a tag-set comparison could not tell it apart from a
|
|
37
|
+
// union that genuinely has only that one arm.
|
|
38
|
+
| String({const: ?Some(name)}) => Some({tagName: name, fields: Dict.make(), taggedFields: []})
|
|
29
39
|
| _ => None
|
|
30
40
|
}
|
|
31
41
|
|
|
32
|
-
// Extract all variant infos from a schema (handles Union and single
|
|
42
|
+
// Extract all variant infos from a schema (handles Union and single variants)
|
|
33
43
|
let extractAllVariants = (schema: S.t<unknown>): array<variantInfo> =>
|
|
34
44
|
switch schema {
|
|
35
45
|
| Union({anyOf}) => anyOf->Array.filterMap(extractVariantInfo)
|
|
36
|
-
|
|
|
46
|
+
| _ =>
|
|
37
47
|
switch extractVariantInfo(schema) {
|
|
38
48
|
| Some(info) => [info]
|
|
39
49
|
| None => []
|
|
40
50
|
}
|
|
41
|
-
| String({const: ?Some(name)}) =>
|
|
42
|
-
// Single payload-less variant — sury compiles `type t = Foo` to String({const: "Foo"})
|
|
43
|
-
[{tagName: name, fields: Dict.make(), taggedFields: []}]
|
|
44
|
-
| _ => []
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
// Check if a variant has no payload fields (only TAG)
|
|
@@ -105,6 +111,31 @@ let schemaTypeName = (schema: S.t<unknown>): string =>
|
|
|
105
111
|
| _ => "unknown"
|
|
106
112
|
}
|
|
107
113
|
|
|
114
|
+
// One level deeper than `schemaTypeName`, for the mismatch messages.
|
|
115
|
+
//
|
|
116
|
+
// `schemaTypeName` alone makes an incompatibility unreadable whenever both sides
|
|
117
|
+
// share a top-level kind: `schemasAreCompatible` rejects two unions on their
|
|
118
|
+
// variant tags or a variant's payload, but both stringify to "union", so the
|
|
119
|
+
// message read "consumes X as union but producer declares it as union" and named
|
|
120
|
+
// nothing a reader could act on. Listing the members turns that into the actual
|
|
121
|
+
// difference — e.g. `union[string | unknown]` against
|
|
122
|
+
// `union[NoPriorType | PriorType]`, which names the drift outright.
|
|
123
|
+
//
|
|
124
|
+
// A union of *tagged* variants is described by its tag names; one whose members
|
|
125
|
+
// carry no TAG (what `option<t>` compiles to) falls back to the member kinds,
|
|
126
|
+
// since that is all there is to say about it.
|
|
127
|
+
let describeSchema = (schema: S.t<unknown>): string =>
|
|
128
|
+
switch schema {
|
|
129
|
+
| Union({anyOf}) =>
|
|
130
|
+
let tags = extractAllVariants(schema)->Array.map(v => v.tagName)
|
|
131
|
+
let members = tags->Array.length > 0 ? tags : anyOf->Array.map(schemaTypeName)
|
|
132
|
+
`union[${members->Array.join(" | ")}]`
|
|
133
|
+
| Object({properties}) =>
|
|
134
|
+
`object{${properties->Dict.keysToArray->Array.toSorted(String.compare)->Array.join(", ")}}`
|
|
135
|
+
| Array({additionalItems: Schema(item)}) => `array<${schemaTypeName(item)}>`
|
|
136
|
+
| _ => schemaTypeName(schema)
|
|
137
|
+
}
|
|
138
|
+
|
|
108
139
|
type producerEntry = {
|
|
109
140
|
sliceName: string,
|
|
110
141
|
variant: variantInfo,
|
|
@@ -314,7 +345,7 @@ let validateProducedAndConsumed = (
|
|
|
314
345
|
let _ =
|
|
315
346
|
errors->Array.push({
|
|
316
347
|
sliceName,
|
|
317
|
-
message: `Slice '${sliceName}' consumes '${variant.tagName}.${fieldName}' as ${
|
|
348
|
+
message: `Slice '${sliceName}' consumes '${variant.tagName}.${fieldName}' as ${describeSchema(consumedFieldSchema)} but producer '${producer.sliceName}' declares it as ${describeSchema(producedFieldSchema)}`,
|
|
318
349
|
})
|
|
319
350
|
}
|
|
320
351
|
}
|
|
@@ -7,48 +7,46 @@ import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js
|
|
|
7
7
|
import * as DcbTag$Reventless from "./DcbTag.res.mjs";
|
|
8
8
|
|
|
9
9
|
function extractVariantInfo(variantSchema) {
|
|
10
|
-
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
let properties = variantSchema.properties;
|
|
14
|
-
return Stdlib_Option.map(DcbTag$Reventless.variantTagName(variantSchema.items), tagName => {
|
|
15
|
-
let taggedFields = Stdlib_Array.filterMap(Object.entries(properties), param => {
|
|
16
|
-
if (DcbTag$Reventless.isTagged(param[1])) {
|
|
17
|
-
return param[0];
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
return {
|
|
21
|
-
tagName: tagName,
|
|
22
|
-
fields: properties,
|
|
23
|
-
taggedFields: taggedFields
|
|
24
|
-
};
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function extractAllVariants(schema) {
|
|
29
|
-
switch (schema.type) {
|
|
10
|
+
switch (variantSchema.type) {
|
|
30
11
|
case "string" :
|
|
31
|
-
let name =
|
|
12
|
+
let name = variantSchema.const;
|
|
32
13
|
if (name !== undefined) {
|
|
33
|
-
return
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
14
|
+
return {
|
|
15
|
+
tagName: name,
|
|
16
|
+
fields: {},
|
|
17
|
+
taggedFields: []
|
|
18
|
+
};
|
|
38
19
|
} else {
|
|
39
|
-
return
|
|
20
|
+
return;
|
|
40
21
|
}
|
|
41
22
|
case "object" :
|
|
42
|
-
let
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
23
|
+
let properties = variantSchema.properties;
|
|
24
|
+
return Stdlib_Option.map(DcbTag$Reventless.variantTagName(variantSchema.items), tagName => {
|
|
25
|
+
let taggedFields = Stdlib_Array.filterMap(Object.entries(properties), param => {
|
|
26
|
+
if (DcbTag$Reventless.isTagged(param[1])) {
|
|
27
|
+
return param[0];
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
tagName: tagName,
|
|
32
|
+
fields: properties,
|
|
33
|
+
taggedFields: taggedFields
|
|
34
|
+
};
|
|
35
|
+
});
|
|
50
36
|
default:
|
|
51
|
-
return
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function extractAllVariants(schema) {
|
|
42
|
+
if (schema.type === "union") {
|
|
43
|
+
return Stdlib_Array.filterMap(schema.anyOf, extractVariantInfo);
|
|
44
|
+
}
|
|
45
|
+
let info = extractVariantInfo(schema);
|
|
46
|
+
if (info !== undefined) {
|
|
47
|
+
return [info];
|
|
48
|
+
} else {
|
|
49
|
+
return [];
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
52
|
|
|
@@ -160,6 +158,26 @@ function schemaTypeName(schema) {
|
|
|
160
158
|
}
|
|
161
159
|
}
|
|
162
160
|
|
|
161
|
+
function describeSchema(schema) {
|
|
162
|
+
switch (schema.type) {
|
|
163
|
+
case "array" :
|
|
164
|
+
let item = schema.additionalItems;
|
|
165
|
+
if (item === "strip" || item === "strict") {
|
|
166
|
+
return schemaTypeName(schema);
|
|
167
|
+
} else {
|
|
168
|
+
return `array<` + schemaTypeName(item) + `>`;
|
|
169
|
+
}
|
|
170
|
+
case "object" :
|
|
171
|
+
return `object{` + Object.keys(schema.properties).toSorted(Primitive_string.compare).join(", ") + `}`;
|
|
172
|
+
case "union" :
|
|
173
|
+
let tags = extractAllVariants(schema).map(v => v.tagName);
|
|
174
|
+
let members = tags.length !== 0 ? tags : schema.anyOf.map(schemaTypeName);
|
|
175
|
+
return `union[` + members.join(" | ") + `]`;
|
|
176
|
+
default:
|
|
177
|
+
return schemaTypeName(schema);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
163
181
|
function dedupeKeys(keys) {
|
|
164
182
|
let seen = new Set();
|
|
165
183
|
return keys.filter(k => {
|
|
@@ -298,7 +316,7 @@ function validateProducedAndConsumed(produced, consumed) {
|
|
|
298
316
|
if (!schemasAreCompatible(consumedFieldSchema, producedFieldSchema)) {
|
|
299
317
|
errors.push({
|
|
300
318
|
sliceName: sliceName,
|
|
301
|
-
message: `Slice '` + sliceName + `' consumes '` + variant.tagName + `.` + fieldName + `' as ` +
|
|
319
|
+
message: `Slice '` + sliceName + `' consumes '` + variant.tagName + `.` + fieldName + `' as ` + describeSchema(consumedFieldSchema) + ` but producer '` + producer.sliceName + `' declares it as ` + describeSchema(producedFieldSchema)
|
|
302
320
|
});
|
|
303
321
|
return;
|
|
304
322
|
} else {
|
|
@@ -414,6 +432,7 @@ export {
|
|
|
414
432
|
schemasAreCompatible,
|
|
415
433
|
propsCompatible,
|
|
416
434
|
schemaTypeName,
|
|
435
|
+
describeSchema,
|
|
417
436
|
dedupeKeys,
|
|
418
437
|
validateCompositeReads,
|
|
419
438
|
validateProducedAndConsumed,
|