@reventlessdev/reventless-core 3.0.0-alpha.194 → 3.0.0-alpha.195
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 +7 -0
- package/package.json +7 -7
- package/src/components/Api/SchemaType.res +36 -0
- package/src/components/Api/SchemaType.res.mjs +22 -0
- package/src/components/Api/SuryToJsonSchema.res +18 -2
- package/src/components/Api/SuryToJsonSchema.res.mjs +8 -4
- package/src/plugin/component/Plugin_Structure.res +11 -2
- package/src/plugin/component/Plugin_Structure.res.mjs +2 -3
- package/tests/plugin/PluginStructureTest.res +49 -16
- package/tests/plugin/PluginStructureTest.res.mjs +27 -16
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.195 (2026-07-30)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **core:** a command's field markers reach the wire, and its optional fields stay optional ([f1c1112](https://github.com/ReventlessDev/reventless-core/commit/f1c1112e9baa6b06e50097a5a618f49c9301cd0a))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 3.0.0-alpha.194 (2026-07-30)
|
|
7
14
|
|
|
8
15
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-core",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.195",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -29,16 +29,16 @@
|
|
|
29
29
|
"sury": "11.0.0-alpha.4",
|
|
30
30
|
"uuid": "^13.0.0",
|
|
31
31
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.31",
|
|
32
|
-
"@reventlessdev/rescript-fast-csv": "1.2.0-alpha.14",
|
|
33
32
|
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.13",
|
|
34
|
-
"@reventlessdev/rescript-
|
|
33
|
+
"@reventlessdev/rescript-fast-csv": "1.2.0-alpha.14",
|
|
35
34
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.9",
|
|
36
|
-
"@reventlessdev/rescript-
|
|
37
|
-
"@reventlessdev/rescript-uuid": "1.1.0-alpha.17",
|
|
38
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.112",
|
|
35
|
+
"@reventlessdev/rescript-node-streams": "1.1.0-alpha.13",
|
|
39
36
|
"@reventlessdev/rescript-ssh2": "1.1.0-alpha.14",
|
|
37
|
+
"@reventlessdev/rescript-uuid": "1.1.0-alpha.17",
|
|
38
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
|
|
40
39
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.29",
|
|
41
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
40
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.88",
|
|
41
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.113"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "12.3.0",
|
|
@@ -141,6 +141,42 @@ and shapeOf = (~parentName: string, ~fieldName: string, schema: S.t<unknown>): s
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
The names of an object's optional properties, read from sury rather than from
|
|
146
|
+
the IR.
|
|
147
|
+
|
|
148
|
+
The IR cannot answer this for every field. `shapeOf` classifies a DCB-tagged or
|
|
149
|
+
`@ref` field as `EntityId` before it ever looks at the union sury wraps an
|
|
150
|
+
`option<…>` in — correctly, since those markers now describe the field through
|
|
151
|
+
that wrapper — so an optional reference reaches the IR as a plain `EntityId`
|
|
152
|
+
with its optionality spent. Asking the source schema keeps the two questions
|
|
153
|
+
apart: what a field *is*, and whether it has to be there.
|
|
154
|
+
|
|
155
|
+
Top-level properties only. A nested object's own fields are emitted by the
|
|
156
|
+
`ObjectRef` branch, which has no `required` of its own today.
|
|
157
|
+
*/
|
|
158
|
+
let optionalFieldNames = (schema: S.t<unknown>): array<string> =>
|
|
159
|
+
switch schema {
|
|
160
|
+
| Object({properties}) =>
|
|
161
|
+
properties
|
|
162
|
+
->Dict.toArray
|
|
163
|
+
->Array.filterMap(((propName, propSchema)) =>
|
|
164
|
+
switch propSchema {
|
|
165
|
+
| Union({anyOf}) =>
|
|
166
|
+
anyOf->Array.some(v =>
|
|
167
|
+
switch v {
|
|
168
|
+
| Null(_) | Undefined(_) => true
|
|
169
|
+
| _ => false
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
? Some(propName)
|
|
173
|
+
: None
|
|
174
|
+
| _ => None
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
| _ => []
|
|
178
|
+
}
|
|
179
|
+
|
|
144
180
|
let fromSuryObject = (~typeName: string, schema: S.t<unknown>): option<dict<schemaType>> =>
|
|
145
181
|
switch schema {
|
|
146
182
|
| Object({properties}) =>
|
|
@@ -150,6 +150,27 @@ function shapeOf(parentName, fieldName, schema) {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
function optionalFieldNames(schema) {
|
|
154
|
+
if (schema.type === "object") {
|
|
155
|
+
return Stdlib_Array.filterMap(Object.entries(schema.properties), param => {
|
|
156
|
+
let propSchema = param[1];
|
|
157
|
+
if (propSchema.type === "union" && propSchema.anyOf.some(v => {
|
|
158
|
+
switch (v.type) {
|
|
159
|
+
case "null" :
|
|
160
|
+
case "undefined" :
|
|
161
|
+
return true;
|
|
162
|
+
default:
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
})) {
|
|
166
|
+
return param[0];
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
153
174
|
function fromSuryObject(typeName, schema) {
|
|
154
175
|
if (schema.type !== "object") {
|
|
155
176
|
return;
|
|
@@ -176,6 +197,7 @@ export {
|
|
|
176
197
|
isIdsFieldName,
|
|
177
198
|
fromSury,
|
|
178
199
|
shapeOf,
|
|
200
|
+
optionalFieldNames,
|
|
179
201
|
fromSuryObject,
|
|
180
202
|
}
|
|
181
203
|
/* DcbTag-Reventless Not a pure module */
|
|
@@ -179,8 +179,18 @@ and withSemantic = (fieldSchema: JSON.t, sem: Reventless.Semantic.t): JSON.t =>
|
|
|
179
179
|
JSON.Encode.object(obj)
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
// `optional` names the fields that may be absent; everything else is required.
|
|
183
|
+
// It comes from the sury schema (`SchemaType.optionalFieldNames`) rather than
|
|
184
|
+
// from the IR, which cannot answer for a reference or a DCB-tagged field — both
|
|
185
|
+
// classify as `EntityId` before the nullable wrapper is ever examined.
|
|
186
|
+
//
|
|
187
|
+
// Defaulting to "all required" mattered nowhere while only read-model state came
|
|
188
|
+
// through here: nothing validates a read schema. A command schema is the input
|
|
189
|
+
// of a form, and a field listed as required is one the form refuses to submit
|
|
190
|
+
// without — which turned `imageUrl?` into a picture every product had to have.
|
|
182
191
|
and objectRefToJsonSchema = (
|
|
183
192
|
~annotations: option<Reventless.StateAnnotations.stateAnnotationSpec>=?,
|
|
193
|
+
~optional: array<string>=[],
|
|
184
194
|
fields: dict<SchemaType.schemaType>,
|
|
185
195
|
): JSON.t => {
|
|
186
196
|
let props = Dict.make()
|
|
@@ -194,7 +204,9 @@ and objectRefToJsonSchema = (
|
|
|
194
204
|
| None => baseSchema
|
|
195
205
|
}
|
|
196
206
|
props->Dict.set(fieldName, withAnnotations)
|
|
197
|
-
|
|
207
|
+
if !(optional->Array.includes(fieldName)) {
|
|
208
|
+
required->Array.push(fieldName)
|
|
209
|
+
}
|
|
198
210
|
})
|
|
199
211
|
let entries: array<(string, JSON.t)> = [
|
|
200
212
|
("type", str("object")),
|
|
@@ -212,7 +224,11 @@ let deriveObjectSchema = (schema: S.t<unknown>): JSON.t =>
|
|
|
212
224
|
switch SchemaType.fromSuryObject(~typeName="", schema) {
|
|
213
225
|
| Some(fields) =>
|
|
214
226
|
let annotations = Reventless.StateAnnotations.getSpec(schema)
|
|
215
|
-
let objSchema = objectRefToJsonSchema(
|
|
227
|
+
let objSchema = objectRefToJsonSchema(
|
|
228
|
+
~annotations?,
|
|
229
|
+
~optional=SchemaType.optionalFieldNames(schema),
|
|
230
|
+
fields,
|
|
231
|
+
)
|
|
216
232
|
// Surface component-level visibility on the top-level object schema.
|
|
217
233
|
// Omitted entirely for the default `Public` (None) so schemas stay compact.
|
|
218
234
|
switch annotations {
|
|
@@ -177,7 +177,7 @@ function fromSchemaType(st) {
|
|
|
177
177
|
]
|
|
178
178
|
]);
|
|
179
179
|
case "ObjectRef" :
|
|
180
|
-
return objectRefToJsonSchema(undefined, st._1);
|
|
180
|
+
return objectRefToJsonSchema(undefined, undefined, st._1);
|
|
181
181
|
case "Enum" :
|
|
182
182
|
return Object.fromEntries([
|
|
183
183
|
[
|
|
@@ -221,7 +221,8 @@ function withSemantic(fieldSchema, sem) {
|
|
|
221
221
|
return obj;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
function objectRefToJsonSchema(annotations, fields) {
|
|
224
|
+
function objectRefToJsonSchema(annotations, optionalOpt, fields) {
|
|
225
|
+
let optional = optionalOpt !== undefined ? optionalOpt : [];
|
|
225
226
|
let props = {};
|
|
226
227
|
let required = [];
|
|
227
228
|
Object.entries(fields).forEach(param => {
|
|
@@ -229,7 +230,10 @@ function objectRefToJsonSchema(annotations, fields) {
|
|
|
229
230
|
let baseSchema = fromSchemaType(param[1]);
|
|
230
231
|
let withAnnotations = annotations !== undefined ? mergeAnnotations(baseSchema, fieldName, annotations) : baseSchema;
|
|
231
232
|
props[fieldName] = withAnnotations;
|
|
232
|
-
|
|
233
|
+
if (!optional.includes(fieldName)) {
|
|
234
|
+
required.push(fieldName);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
233
237
|
});
|
|
234
238
|
let entries = [
|
|
235
239
|
[
|
|
@@ -259,7 +263,7 @@ function deriveObjectSchema(schema) {
|
|
|
259
263
|
]]);
|
|
260
264
|
}
|
|
261
265
|
let annotations = StateAnnotations$Reventless.getSpec(schema);
|
|
262
|
-
let objSchema = objectRefToJsonSchema(annotations, fields);
|
|
266
|
+
let objSchema = objectRefToJsonSchema(annotations, SchemaType$ReventlessCore.optionalFieldNames(schema), fields);
|
|
263
267
|
if (annotations === undefined) {
|
|
264
268
|
return objSchema;
|
|
265
269
|
}
|
|
@@ -269,7 +269,14 @@ let make = (
|
|
|
269
269
|
}
|
|
270
270
|
({
|
|
271
271
|
Reventless.Plugin.name: variantName,
|
|
272
|
-
schema:
|
|
272
|
+
// The derived schema, not sury's raw one: `S.toJSONSchema` carries the
|
|
273
|
+
// shape and drops every `x-reventless-*` marker the PPX put on the
|
|
274
|
+
// fields, so a command's `@storageRef`/`@semantic`/`@ref` reached the
|
|
275
|
+
// wire on the read side and nowhere on the write side. A reader that
|
|
276
|
+
// matches a field against its setter — or picks the upload endpoint of
|
|
277
|
+
// the store a command argument declares — then has nothing to match on.
|
|
278
|
+
// `MCP_SchemaGenerator` already derives these same variant schemas.
|
|
279
|
+
schema: v->SuryToJsonSchema.deriveObjectSchema->JSON.stringify,
|
|
273
280
|
level,
|
|
274
281
|
aggregateIdField,
|
|
275
282
|
// A non-exposed (`@noApi`) variant has no callable mutation field. For a
|
|
@@ -343,7 +350,9 @@ let make = (
|
|
|
343
350
|
)
|
|
344
351
|
({
|
|
345
352
|
Reventless.Plugin.name: variantName,
|
|
346
|
-
schema
|
|
353
|
+
// Derived for the same reason as `commandDef.schema` above: an event's
|
|
354
|
+
// field markers are the write side's half of the same vocabulary.
|
|
355
|
+
schema: v->SuryToJsonSchema.deriveObjectSchema->JSON.stringify,
|
|
347
356
|
references,
|
|
348
357
|
}: Reventless.Plugin.eventDef)
|
|
349
358
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as S from "sury/src/S.res.mjs";
|
|
4
3
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
4
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
6
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
@@ -229,7 +228,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
229
228
|
}
|
|
230
229
|
return {
|
|
231
230
|
name: variantName,
|
|
232
|
-
schema: JSON.stringify(
|
|
231
|
+
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(v)),
|
|
233
232
|
level: match[0],
|
|
234
233
|
aggregateIdField: match[1],
|
|
235
234
|
mutationField: apiExposed ? mutationFieldFor(variantName) : "",
|
|
@@ -281,7 +280,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
281
280
|
});
|
|
282
281
|
return {
|
|
283
282
|
name: variantName,
|
|
284
|
-
schema: JSON.stringify(
|
|
283
|
+
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(v)),
|
|
285
284
|
references: references
|
|
286
285
|
};
|
|
287
286
|
};
|
|
@@ -549,26 +549,28 @@ describe("Plugin_Structure.make — Phase 2 graph fields", () => {
|
|
|
549
549
|
})
|
|
550
550
|
})
|
|
551
551
|
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
| None => None
|
|
560
|
-
}
|
|
552
|
+
// Shared by the read-side and write-side schema cases below, which ask the
|
|
553
|
+
// same question of the same emitter.
|
|
554
|
+
let getProperty = (json: JSON.t, key: string): option<JSON.t> =>
|
|
555
|
+
switch json->JSON.Decode.object {
|
|
556
|
+
| Some(obj) => obj->Dict.get(key)
|
|
557
|
+
| None => None
|
|
558
|
+
}
|
|
561
559
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
| None => None
|
|
568
|
-
}
|
|
560
|
+
let getPropertyOf = (json: JSON.t, fieldName: string): option<JSON.t> =>
|
|
561
|
+
switch getProperty(json, "properties") {
|
|
562
|
+
| Some(props) =>
|
|
563
|
+
switch props->JSON.Decode.object {
|
|
564
|
+
| Some(obj) => obj->Dict.get(fieldName)
|
|
569
565
|
| None => None
|
|
570
566
|
}
|
|
567
|
+
| None => None
|
|
568
|
+
}
|
|
571
569
|
|
|
570
|
+
// Phase 4: queryableDef.schema must carry x-reventless-* extension keys for
|
|
571
|
+
// annotated state types. Plugin_Structure now uses SuryToJsonSchema.deriveObjectSchema
|
|
572
|
+
// (annotation-aware) instead of S.toJSONSchema (metadata-blind).
|
|
573
|
+
describe("queryableDef.schema propagates x-reventless-* annotations", () => {
|
|
572
574
|
let parseSchema = (svs: Reventless.Plugin.queryableDef): JSON.t =>
|
|
573
575
|
svs.schema->JSON.parseOrThrow
|
|
574
576
|
|
|
@@ -826,6 +828,37 @@ describe("Plugin_Structure.make — Phase 2 graph fields", () => {
|
|
|
826
828
|
]
|
|
827
829
|
expect(cmd.references)->toEqual(expected)
|
|
828
830
|
})
|
|
831
|
+
|
|
832
|
+
// What `requiredStores` knows, the wire has to carry. A reader deciding
|
|
833
|
+
// which command fills a storage-ref field, or which store's endpoint an
|
|
834
|
+
// upload goes to, has only the command schema to go on.
|
|
835
|
+
testSync("the command schema carries the field's storage-ref marker", () => {
|
|
836
|
+
let cmd = (withOptional.stateChangeSlices->Array.getUnsafe(0)).commands->Array.getUnsafe(0)
|
|
837
|
+
let target =
|
|
838
|
+
cmd.schema
|
|
839
|
+
->JSON.parseOrThrow
|
|
840
|
+
->getPropertyOf("avatarUrl")
|
|
841
|
+
->Option.flatMap(s => getProperty(s, "x-reventless-semantic-target"))
|
|
842
|
+
->Option.flatMap(t => getProperty(t, "store"))
|
|
843
|
+
->Option.flatMap(JSON.Decode.string)
|
|
844
|
+
expect(target)->toBe(Some("avatars"))
|
|
845
|
+
})
|
|
846
|
+
|
|
847
|
+
// A form submits what a schema says is required. `avatarUrl?` is optional in
|
|
848
|
+
// the spec, so listing it would make the picture mandatory in every UI that
|
|
849
|
+
// renders the command — the failure this pairs with, since carrying the
|
|
850
|
+
// marker is worth nothing if the field cannot be left empty.
|
|
851
|
+
testSync("an optional command argument is not required", () => {
|
|
852
|
+
let cmd = (withOptional.stateChangeSlices->Array.getUnsafe(0)).commands->Array.getUnsafe(0)
|
|
853
|
+
let required =
|
|
854
|
+
cmd.schema
|
|
855
|
+
->JSON.parseOrThrow
|
|
856
|
+
->getProperty("required")
|
|
857
|
+
->Option.flatMap(JSON.Decode.array)
|
|
858
|
+
->Option.getOr([])
|
|
859
|
+
->Array.filterMap(JSON.Decode.string)
|
|
860
|
+
expect(required)->toEqual(["customerId"])
|
|
861
|
+
})
|
|
829
862
|
})
|
|
830
863
|
|
|
831
864
|
// Declarations are authoritative; the name heuristics are a lint. A
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as S from "sury/src/S.res.mjs";
|
|
4
4
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
6
|
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
6
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
8
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
@@ -676,23 +677,23 @@ globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
|
|
|
676
677
|
globalThis.expect(Plugin_Structure$ReventlessCore.statusFieldFromStateSchema("Test", schema)).toEqual(undefined);
|
|
677
678
|
});
|
|
678
679
|
});
|
|
680
|
+
let getProperty = (json, key) => {
|
|
681
|
+
let obj = Stdlib_JSON.Decode.object(json);
|
|
682
|
+
if (obj !== undefined) {
|
|
683
|
+
return obj[key];
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
let getPropertyOf = (json, fieldName) => {
|
|
687
|
+
let props = getProperty(json, "properties");
|
|
688
|
+
if (props === undefined) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
let obj = Stdlib_JSON.Decode.object(props);
|
|
692
|
+
if (obj !== undefined) {
|
|
693
|
+
return obj[fieldName];
|
|
694
|
+
}
|
|
695
|
+
};
|
|
679
696
|
globalThis.describe("queryableDef.schema propagates x-reventless-* annotations", () => {
|
|
680
|
-
let getProperty = (json, key) => {
|
|
681
|
-
let obj = Stdlib_JSON.Decode.object(json);
|
|
682
|
-
if (obj !== undefined) {
|
|
683
|
-
return obj[key];
|
|
684
|
-
}
|
|
685
|
-
};
|
|
686
|
-
let getPropertyOf = (json, fieldName) => {
|
|
687
|
-
let props = getProperty(json, "properties");
|
|
688
|
-
if (props === undefined) {
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
let obj = Stdlib_JSON.Decode.object(props);
|
|
692
|
-
if (obj !== undefined) {
|
|
693
|
-
return obj[fieldName];
|
|
694
|
-
}
|
|
695
|
-
};
|
|
696
697
|
let annotatedSchema = JSON.parse(structure.stateViewSlices[3].schema);
|
|
697
698
|
globalThis.test("itemId carries x-reventless-id", () => {
|
|
698
699
|
globalThis.expect(Stdlib_Option.flatMap(Stdlib_Option.flatMap(getPropertyOf(annotatedSchema, "itemId"), s => getProperty(s, "x-reventless-id")), Stdlib_JSON.Decode.bool)).toBe(true);
|
|
@@ -872,6 +873,16 @@ globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
|
|
|
872
873
|
}];
|
|
873
874
|
globalThis.expect(cmd.references).toEqual(expected);
|
|
874
875
|
});
|
|
876
|
+
globalThis.test("the command schema carries the field's storage-ref marker", () => {
|
|
877
|
+
let cmd = withOptional.stateChangeSlices[0].commands[0];
|
|
878
|
+
let target = Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(getPropertyOf(JSON.parse(cmd.schema), "avatarUrl"), s => getProperty(s, "x-reventless-semantic-target")), t => getProperty(t, "store")), Stdlib_JSON.Decode.string);
|
|
879
|
+
globalThis.expect(target).toBe("avatars");
|
|
880
|
+
});
|
|
881
|
+
globalThis.test("an optional command argument is not required", () => {
|
|
882
|
+
let cmd = withOptional.stateChangeSlices[0].commands[0];
|
|
883
|
+
let required = Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(getProperty(JSON.parse(cmd.schema), "required"), Stdlib_JSON.Decode.array), []), Stdlib_JSON.Decode.string);
|
|
884
|
+
globalThis.expect(required).toEqual(["customerId"]);
|
|
885
|
+
});
|
|
875
886
|
});
|
|
876
887
|
globalThis.describe("capability inference", () => {
|
|
877
888
|
let commandWarnings = Capability_Inference$ReventlessCore.scanSchema("ChangePhoto", PsChangePhoto$ReventlessCore.commandSchema);
|