@reventlessdev/reventless-core 3.0.0-alpha.255 → 3.0.0-alpha.257
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 +58 -0
- package/package.json +9 -9
- package/src/adapter/Monitoring/Monitoring.res +79 -5
- package/src/adapter/Monitoring/Monitoring.res.mjs +42 -2
- package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
- package/src/admin/Platform_ComponentDefinitionsApi.res +5 -1
- package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +5 -1
- package/src/admin/Platform_UiSlots.res +21 -0
- package/src/admin/Platform_UiSlots.res.mjs +15 -0
- package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +1 -1
- package/src/components/Aggregate/Aggregate_Callback.res +13 -4
- package/src/components/Aggregate/Aggregate_Callback.res.mjs +6 -5
- package/src/components/Api/GraphQL_FragmentGenerator.res +2 -0
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +4 -0
- package/src/components/Api/MCP_SchemaGenerator.res.mjs +2 -2
- package/src/components/Api/SchemaType.res +8 -0
- package/src/components/Api/SchemaType.res.mjs +15 -5
- package/src/components/Api/SuryToJsonSchema.res +70 -12
- package/src/components/Api/SuryToJsonSchema.res.mjs +43 -16
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +8 -3
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +1 -1
- package/src/plugin/component/Plugin.res +1 -0
- package/src/plugin/component/Plugin_Builder.res +2 -0
- package/src/plugin/component/Plugin_Builder.res.mjs +3 -2
- package/src/plugin/component/Plugin_Structure.res +105 -10
- package/src/plugin/component/Plugin_Structure.res.mjs +71 -27
- package/src/plugin/lifecycle/PluginsReadModelSpec.res +1 -1
- package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +1 -1
- package/src/util/Validation.res +1 -1
- package/src/util/Validation.res.mjs +2 -2
- package/tests/adapter/MonitoringTest.res +46 -7
- package/tests/adapter/MonitoringTest.res.mjs +47 -5
- package/tests/admin/AdminApiSchemaDriftTest.res +1 -0
- package/tests/admin/AdminApiSchemaDriftTest.res.mjs +3 -0
- package/tests/admin/Platform_Admin_StructureTest.res.mjs +2 -14
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +1 -0
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +2 -0
- package/tests/admin/Platform_PluginStructuresApiTest.res +8 -14
- package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +3 -3
- package/tests/aggregate/AggregateCacheTest.res.mjs +1 -1
- package/tests/aggregate/AggregateConflictTest.res.mjs +1 -1
- package/tests/aggregate/AggregateFixtures.res.mjs +1 -1
- package/tests/aggregate/AggregateSnapshotTest.res.mjs +1 -1
- package/tests/api/GraphQL_FragmentGeneratorTest.res +6 -4
- package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +5 -5
- package/tests/api/SuryToJsonSchemaTest.res.mjs +57 -57
- package/tests/api/TaggedUnionSchemaTest.res.mjs +1 -1
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +1 -1
- package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +1 -1
- package/tests/dcb/DcbFixtures.res.mjs +1 -1
- package/tests/eventmapper/EventMapperFixtures.res.mjs +1 -1
- package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +1 -1
- package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +1 -1
- package/tests/fixtures/plugin-lifecycle/2026-07-22-versionconnected-no-requiredstores.json +1 -1
- package/tests/fixtures/plugin-lifecycle/2026-07-22-versionsuperseded-nested-definitions.json +1 -1
- package/tests/fixtures/plugin-lifecycle/2026-07-28-versionconnected-requiredstores-only.json +1 -1
- package/tests/fixtures/plugin-lifecycle/2026-07-30-versionconnected-declarations-without-annotation.json +1 -1
- package/tests/fixtures/plugin-lifecycle/2026-07-30-versiondisconnected-declarations-without-annotation.json +1 -1
- package/tests/fixtures/plugin-lifecycle/README.md +12 -0
- package/tests/logger/LogFormatTest.res +1 -1
- package/tests/logger/LogFormatTest.res.mjs +1 -1
- package/tests/plugin/ManifestVisibilityTest.res.mjs +1 -1
- package/tests/plugin/PluginDefinitionRequiredScalarsTest.res +1 -1
- package/tests/plugin/PluginDefinitionScalars.res +13 -8
- package/tests/plugin/PluginDefinitionScalars.res.mjs +13 -5
- package/tests/plugin/PluginStructureAccessTest.res.mjs +1 -1
- package/tests/plugin/PluginStructureTest.res +184 -0
- package/tests/plugin/PluginStructureTest.res.mjs +142 -17
- package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsChangePhoto.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsReserveStock.res +14 -1
- package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +9 -2
- package/tests/plugin/StateChangeSlice/PsTypoStore.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsUploadAvatar.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsUploadImages.res.mjs +1 -1
- package/tests/plugin/pluginDefinitionRequiredScalars.txt +13 -14
- package/tests/util/CommandPublisherTest.res.mjs +1 -1
|
@@ -176,33 +176,72 @@ let rec isNullableType = (st: SchemaType.schemaType): bool =>
|
|
|
176
176
|
| _ => false
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
// The GraphQL **input** type name an object is declared under, written onto the
|
|
180
|
+
// object's own JSON Schema wherever it occurs.
|
|
181
|
+
//
|
|
182
|
+
// A client that assembles a mutation must name its variables' types, and for a
|
|
183
|
+
// *top-level* argument `x-reventless-graphql-type` already publishes the rendered
|
|
184
|
+
// reference (`[Ordering_PlaceOrderLineItems!]!`, wrappers and all). That one is
|
|
185
|
+
// keyed by argument name and so stops at the first level; this one travels with
|
|
186
|
+
// the object, which is what a reader reaching an element schema inside a list has.
|
|
187
|
+
//
|
|
188
|
+
// The name is taken from the `ObjectRef` the SDL emitter names the type from, so
|
|
189
|
+
// there is one source and the two cannot drift — but only when the caller rooted
|
|
190
|
+
// the walk at the same prefix the emitter uses. That is what `inputNames` gates:
|
|
191
|
+
// a schema walked from an unknown root would publish a name the SDL never
|
|
192
|
+
// declares, and a plausible wrong name is worse than none.
|
|
193
|
+
let withGraphqlInput = (schema: JSON.t, name: string): JSON.t =>
|
|
194
|
+
switch schema->JSON.Decode.object {
|
|
195
|
+
| None => schema
|
|
196
|
+
| Some(obj) =>
|
|
197
|
+
obj->Dict.set("x-reventless-graphql-input", str(name))
|
|
198
|
+
JSON.Encode.object(obj)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let rec fromSchemaType = (~inputNames: bool=false, st: SchemaType.schemaType): JSON.t =>
|
|
180
202
|
switch st {
|
|
181
203
|
| ScalarString => jsonObject([("type", str("string"))])
|
|
182
204
|
| ScalarNumber => jsonObject([("type", str("number"))])
|
|
205
|
+
| ScalarInt => jsonObject([("type", str("integer"))])
|
|
183
206
|
| ScalarBoolean => jsonObject([("type", str("boolean"))])
|
|
184
207
|
| ScalarBigInt => jsonObject([("type", str("integer"))])
|
|
185
208
|
| EntityId => jsonObject([("type", str("string")), ("format", str("uuid"))])
|
|
186
209
|
| DateTime => jsonObject([("type", str("string")), ("format", str("date-time"))])
|
|
187
210
|
| Nullable(inner) =>
|
|
188
|
-
let innerSchema = fromSchemaType(inner)
|
|
211
|
+
let innerSchema = fromSchemaType(~inputNames, inner)
|
|
189
212
|
jsonObject([("oneOf", JSON.Encode.array([innerSchema, jsonObject([("type", str("null"))])]))])
|
|
190
213
|
| ArrayOf(item) =>
|
|
191
|
-
jsonObject([("type", str("array")), ("items", fromSchemaType(item))])
|
|
192
|
-
| ObjectRef(
|
|
214
|
+
jsonObject([("type", str("array")), ("items", fromSchemaType(~inputNames, item))])
|
|
215
|
+
| ObjectRef(name, fields) =>
|
|
216
|
+
let base = objectRefToJsonSchema(~inputNames, fields)
|
|
217
|
+
inputNames ? base->withGraphqlInput(name) : base
|
|
193
218
|
| Enum(_, values) =>
|
|
194
219
|
jsonObject([
|
|
195
220
|
("type", str("string")),
|
|
196
221
|
("enum", JSON.Encode.array(values->Array.map(JSON.Encode.string))),
|
|
197
222
|
])
|
|
198
|
-
|
|
223
|
+
// A semantic composite is one named type wherever it appears, and GraphQL
|
|
224
|
+
// forbids one name serving as both an object and an input — so in input
|
|
225
|
+
// position it is the `Input`-suffixed name that exists in the SDL, which is
|
|
226
|
+
// the one a client can paste into a variable declaration. Positionally-named
|
|
227
|
+
// objects take no suffix, so the `ObjectRef` branch above already published
|
|
228
|
+
// the right string for them.
|
|
229
|
+
| Semantic(sem, inner) =>
|
|
230
|
+
let base = fromSchemaType(~inputNames, inner)
|
|
231
|
+
let base = switch (inputNames, SchemaType.canonicalName(sem.id), inner) {
|
|
232
|
+
| (true, Some(name), ObjectRef(_, _)) => base->withGraphqlInput(name ++ "Input")
|
|
233
|
+
| _ => base
|
|
234
|
+
}
|
|
235
|
+
base->withSemantic(sem)
|
|
236
|
+
// A union has no input form — see the fragment generator, which emits `String`
|
|
237
|
+
// and says so — hence no name to publish on either arm.
|
|
199
238
|
// A union is `oneOf` its arms, each an object carrying the `TAG` const it is
|
|
200
239
|
// discriminated by. The discriminator is what tells a union apart from a
|
|
201
240
|
// nullable object, which is also a `oneOf` of objects — a reader that misses
|
|
202
241
|
// it selects one arm's fields as though they were the field's own and produces
|
|
203
242
|
// a query that looks plausible and is invalid.
|
|
204
243
|
| TaggedUnion(name, arms) =>
|
|
205
|
-
let members = arms->Array.map(((tag, armType)) => armToJsonSchema(~tag, armType))
|
|
244
|
+
let members = arms->Array.map(((tag, armType)) => armToJsonSchema(~inputNames, ~tag, armType))
|
|
206
245
|
jsonObject([("oneOf", JSON.Encode.array(members)), ("x-reventless-union", str(name))])
|
|
207
246
|
| Unknown => jsonObject([("type", str("string"))])
|
|
208
247
|
}
|
|
@@ -212,10 +251,14 @@ let rec fromSchemaType = (st: SchemaType.schemaType): JSON.t =>
|
|
|
212
251
|
// the payload that says which arm this is. The GraphQL member type name rides
|
|
213
252
|
// alongside so a reader mapping a raw payload to a selection does not have to
|
|
214
253
|
// re-derive the naming rule in a second repo.
|
|
215
|
-
and armToJsonSchema = (
|
|
254
|
+
and armToJsonSchema = (
|
|
255
|
+
~inputNames: bool=false,
|
|
256
|
+
~tag: string,
|
|
257
|
+
armType: SchemaType.schemaType,
|
|
258
|
+
): JSON.t =>
|
|
216
259
|
switch armType {
|
|
217
260
|
| ObjectRef(memberName, fields) =>
|
|
218
|
-
let base = objectRefToJsonSchema(fields)
|
|
261
|
+
let base = objectRefToJsonSchema(~inputNames, fields)
|
|
219
262
|
switch base->JSON.Decode.object {
|
|
220
263
|
| Some(obj) =>
|
|
221
264
|
switch obj->Dict.get("properties")->Option.flatMap(JSON.Decode.object) {
|
|
@@ -232,7 +275,7 @@ and armToJsonSchema = (~tag: string, armType: SchemaType.schemaType): JSON.t =>
|
|
|
232
275
|
JSON.Encode.object(obj)
|
|
233
276
|
| None => base
|
|
234
277
|
}
|
|
235
|
-
| other => fromSchemaType(other)
|
|
278
|
+
| other => fromSchemaType(~inputNames, other)
|
|
236
279
|
}
|
|
237
280
|
|
|
238
281
|
// Attach a type-carried semantic to a field's JSON Schema.
|
|
@@ -313,11 +356,17 @@ and withSemantic = (fieldSchema: JSON.t, sem: Reventless.Semantic.t): JSON.t =>
|
|
|
313
356
|
// way — off the sury schema by the caller. What it marks is a field whose value
|
|
314
357
|
// must not be rendered into content somebody receives, which is a property of
|
|
315
358
|
// the domain model rather than of the field's shape.
|
|
359
|
+
// `inputNames` says which side of the wire this schema describes. It changes only
|
|
360
|
+
// the name a semantic composite is published under — `MoneyInput` where a command
|
|
361
|
+
// takes one, `Money` where a view returns one — because GraphQL forbids one name
|
|
362
|
+
// serving as both. Emitting the input name on a view would name a type that need
|
|
363
|
+
// not exist in the SDL at all.
|
|
316
364
|
and objectRefToJsonSchema = (
|
|
317
365
|
~annotations: option<Reventless.StateAnnotations.stateAnnotationSpec>=?,
|
|
318
366
|
~optional: array<string>=[],
|
|
319
367
|
~owners: array<string>=[],
|
|
320
368
|
~sensitive: array<string>=[],
|
|
369
|
+
~inputNames: bool=false,
|
|
321
370
|
fields: dict<SchemaType.schemaType>,
|
|
322
371
|
): JSON.t => {
|
|
323
372
|
let props = Dict.make()
|
|
@@ -336,7 +385,7 @@ and objectRefToJsonSchema = (
|
|
|
336
385
|
->Dict.toArray
|
|
337
386
|
->Array.filter(((fieldName, _)) => !(internal->Array.includes(fieldName)))
|
|
338
387
|
->Array.forEach(((fieldName, fieldType)) => {
|
|
339
|
-
let baseSchema = fromSchemaType(fieldType)
|
|
388
|
+
let baseSchema = fromSchemaType(~inputNames, fieldType)
|
|
340
389
|
let withAnnotations = switch annotations {
|
|
341
390
|
| Some(spec) => mergeAnnotations(baseSchema, fieldName, spec)
|
|
342
391
|
| None => baseSchema
|
|
@@ -394,8 +443,16 @@ and objectRefToJsonSchema = (
|
|
|
394
443
|
|
|
395
444
|
// ── Public API (sury → JSON Schema via SchemaType) ───────────────────────
|
|
396
445
|
|
|
397
|
-
|
|
398
|
-
|
|
446
|
+
// `typeName` roots the walk where the SDL emitter roots its own — the mutation
|
|
447
|
+
// field name for a command — so the nested type names this publishes are the
|
|
448
|
+
// ones the SDL declares. It is only consulted when `inputNames` asks for those
|
|
449
|
+
// names; every other caller leaves both alone and gets today's schema exactly.
|
|
450
|
+
let deriveObjectSchema = (
|
|
451
|
+
~inputNames: bool=false,
|
|
452
|
+
~typeName: string="",
|
|
453
|
+
schema: S.t<unknown>,
|
|
454
|
+
): JSON.t =>
|
|
455
|
+
switch SchemaType.fromSuryObject(~typeName, schema) {
|
|
399
456
|
| Some(fields) =>
|
|
400
457
|
let annotations = Reventless.StateAnnotations.getSpec(schema)
|
|
401
458
|
let objSchema = objectRefToJsonSchema(
|
|
@@ -403,6 +460,7 @@ let deriveObjectSchema = (schema: S.t<unknown>): JSON.t =>
|
|
|
403
460
|
~optional=SchemaType.optionalFieldNames(schema),
|
|
404
461
|
~owners=Reventless.Owner.fieldNames(schema),
|
|
405
462
|
~sensitive=Reventless.Sensitive.fieldNames(schema),
|
|
463
|
+
~inputNames,
|
|
406
464
|
fields,
|
|
407
465
|
)
|
|
408
466
|
// Surface component-level hints on the top-level object schema.
|
|
@@ -160,7 +160,18 @@ function isNullableType(_st) {
|
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
function
|
|
163
|
+
function withGraphqlInput(schema, name) {
|
|
164
|
+
let obj = Stdlib_JSON.Decode.object(schema);
|
|
165
|
+
if (obj !== undefined) {
|
|
166
|
+
obj["x-reventless-graphql-input"] = name;
|
|
167
|
+
return obj;
|
|
168
|
+
} else {
|
|
169
|
+
return schema;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function fromSchemaType(inputNamesOpt, st) {
|
|
174
|
+
let inputNames = inputNamesOpt !== undefined ? inputNamesOpt : false;
|
|
164
175
|
if (typeof st !== "object") {
|
|
165
176
|
switch (st) {
|
|
166
177
|
case "ScalarNumber" :
|
|
@@ -173,6 +184,7 @@ function fromSchemaType(st) {
|
|
|
173
184
|
"type",
|
|
174
185
|
"boolean"
|
|
175
186
|
]]);
|
|
187
|
+
case "ScalarInt" :
|
|
176
188
|
case "ScalarBigInt" :
|
|
177
189
|
return Object.fromEntries([[
|
|
178
190
|
"type",
|
|
@@ -210,7 +222,7 @@ function fromSchemaType(st) {
|
|
|
210
222
|
} else {
|
|
211
223
|
switch (st.TAG) {
|
|
212
224
|
case "Nullable" :
|
|
213
|
-
let innerSchema = fromSchemaType(st._0);
|
|
225
|
+
let innerSchema = fromSchemaType(inputNames, st._0);
|
|
214
226
|
return Object.fromEntries([[
|
|
215
227
|
"oneOf",
|
|
216
228
|
[
|
|
@@ -229,11 +241,16 @@ function fromSchemaType(st) {
|
|
|
229
241
|
],
|
|
230
242
|
[
|
|
231
243
|
"items",
|
|
232
|
-
fromSchemaType(st._0)
|
|
244
|
+
fromSchemaType(inputNames, st._0)
|
|
233
245
|
]
|
|
234
246
|
]);
|
|
235
247
|
case "ObjectRef" :
|
|
236
|
-
|
|
248
|
+
let base = objectRefToJsonSchema(undefined, undefined, undefined, undefined, inputNames, st._1);
|
|
249
|
+
if (inputNames) {
|
|
250
|
+
return withGraphqlInput(base, st._0);
|
|
251
|
+
} else {
|
|
252
|
+
return base;
|
|
253
|
+
}
|
|
237
254
|
case "Enum" :
|
|
238
255
|
return Object.fromEntries([
|
|
239
256
|
[
|
|
@@ -246,9 +263,14 @@ function fromSchemaType(st) {
|
|
|
246
263
|
]
|
|
247
264
|
]);
|
|
248
265
|
case "Semantic" :
|
|
249
|
-
|
|
266
|
+
let inner = st._1;
|
|
267
|
+
let sem = st._0;
|
|
268
|
+
let base$1 = fromSchemaType(inputNames, inner);
|
|
269
|
+
let match = SchemaType$ReventlessCore.canonicalName(sem.id);
|
|
270
|
+
let base$2 = inputNames && match !== undefined && typeof inner === "object" && inner.TAG === "ObjectRef" ? withGraphqlInput(base$1, match + "Input") : base$1;
|
|
271
|
+
return withSemantic(base$2, sem);
|
|
250
272
|
case "TaggedUnion" :
|
|
251
|
-
let members = st._1.map(param => armToJsonSchema(param[0], param[1]));
|
|
273
|
+
let members = st._1.map(param => armToJsonSchema(inputNames, param[0], param[1]));
|
|
252
274
|
return Object.fromEntries([
|
|
253
275
|
[
|
|
254
276
|
"oneOf",
|
|
@@ -263,14 +285,15 @@ function fromSchemaType(st) {
|
|
|
263
285
|
}
|
|
264
286
|
}
|
|
265
287
|
|
|
266
|
-
function armToJsonSchema(tag, armType) {
|
|
288
|
+
function armToJsonSchema(inputNamesOpt, tag, armType) {
|
|
289
|
+
let inputNames = inputNamesOpt !== undefined ? inputNamesOpt : false;
|
|
267
290
|
if (typeof armType !== "object") {
|
|
268
|
-
return fromSchemaType(armType);
|
|
291
|
+
return fromSchemaType(inputNames, armType);
|
|
269
292
|
}
|
|
270
293
|
if (armType.TAG !== "ObjectRef") {
|
|
271
|
-
return fromSchemaType(armType);
|
|
294
|
+
return fromSchemaType(inputNames, armType);
|
|
272
295
|
}
|
|
273
|
-
let base = objectRefToJsonSchema(undefined, undefined, undefined, undefined, armType._1);
|
|
296
|
+
let base = objectRefToJsonSchema(undefined, undefined, undefined, undefined, inputNames, armType._1);
|
|
274
297
|
let obj = Stdlib_JSON.Decode.object(base);
|
|
275
298
|
if (obj === undefined) {
|
|
276
299
|
return base;
|
|
@@ -341,17 +364,18 @@ function withSemantic(fieldSchema, sem) {
|
|
|
341
364
|
return obj;
|
|
342
365
|
}
|
|
343
366
|
|
|
344
|
-
function objectRefToJsonSchema(annotations, optionalOpt, ownersOpt, sensitiveOpt, fields) {
|
|
367
|
+
function objectRefToJsonSchema(annotations, optionalOpt, ownersOpt, sensitiveOpt, inputNamesOpt, fields) {
|
|
345
368
|
let optional = optionalOpt !== undefined ? optionalOpt : [];
|
|
346
369
|
let owners = ownersOpt !== undefined ? ownersOpt : [];
|
|
347
370
|
let sensitive = sensitiveOpt !== undefined ? sensitiveOpt : [];
|
|
371
|
+
let inputNames = inputNamesOpt !== undefined ? inputNamesOpt : false;
|
|
348
372
|
let props = {};
|
|
349
373
|
let required = [];
|
|
350
374
|
let internal = Stdlib_Option.getOr(Stdlib_Option.flatMap(annotations, spec => spec.internal), []);
|
|
351
375
|
Object.entries(fields).filter(param => !internal.includes(param[0])).forEach(param => {
|
|
352
376
|
let fieldType = param[1];
|
|
353
377
|
let fieldName = param[0];
|
|
354
|
-
let baseSchema = fromSchemaType(fieldType);
|
|
378
|
+
let baseSchema = fromSchemaType(inputNames, fieldType);
|
|
355
379
|
let withAnnotations = annotations !== undefined ? mergeAnnotations(baseSchema, fieldName, annotations) : baseSchema;
|
|
356
380
|
let withAnnotations$1;
|
|
357
381
|
if (owners.includes(fieldName)) {
|
|
@@ -401,8 +425,10 @@ function objectRefToJsonSchema(annotations, optionalOpt, ownersOpt, sensitiveOpt
|
|
|
401
425
|
return Object.fromEntries(entries);
|
|
402
426
|
}
|
|
403
427
|
|
|
404
|
-
function deriveObjectSchema(schema) {
|
|
405
|
-
let
|
|
428
|
+
function deriveObjectSchema(inputNamesOpt, typeNameOpt, schema) {
|
|
429
|
+
let inputNames = inputNamesOpt !== undefined ? inputNamesOpt : false;
|
|
430
|
+
let typeName = typeNameOpt !== undefined ? typeNameOpt : "";
|
|
431
|
+
let fields = SchemaType$ReventlessCore.fromSuryObject(typeName, schema);
|
|
406
432
|
if (fields === undefined) {
|
|
407
433
|
return Object.fromEntries([[
|
|
408
434
|
"type",
|
|
@@ -410,7 +436,7 @@ function deriveObjectSchema(schema) {
|
|
|
410
436
|
]]);
|
|
411
437
|
}
|
|
412
438
|
let annotations = StateAnnotations$Reventless.getSpec(schema);
|
|
413
|
-
let objSchema = objectRefToJsonSchema(annotations, SchemaType$ReventlessCore.optionalFieldNames(schema), Owner$Reventless.fieldNames(schema), Sensitive$Reventless.fieldNames(schema), fields);
|
|
439
|
+
let objSchema = objectRefToJsonSchema(annotations, SchemaType$ReventlessCore.optionalFieldNames(schema), Owner$Reventless.fieldNames(schema), Sensitive$Reventless.fieldNames(schema), inputNames, fields);
|
|
414
440
|
if (annotations === undefined) {
|
|
415
441
|
return objSchema;
|
|
416
442
|
}
|
|
@@ -434,7 +460,7 @@ function deriveObjectSchema(schema) {
|
|
|
434
460
|
}
|
|
435
461
|
|
|
436
462
|
function toJsonSchema(schema) {
|
|
437
|
-
return fromSchemaType(SchemaType$ReventlessCore.fromSury("", "", schema));
|
|
463
|
+
return fromSchemaType(undefined, SchemaType$ReventlessCore.fromSury("", "", schema));
|
|
438
464
|
}
|
|
439
465
|
|
|
440
466
|
export {
|
|
@@ -444,6 +470,7 @@ export {
|
|
|
444
470
|
withOptionalPlugin,
|
|
445
471
|
mergeAnnotations,
|
|
446
472
|
isNullableType,
|
|
473
|
+
withGraphqlInput,
|
|
447
474
|
fromSchemaType,
|
|
448
475
|
armToJsonSchema,
|
|
449
476
|
withSemantic,
|
|
@@ -37,8 +37,8 @@ module Make = (
|
|
|
37
37
|
// Encode through the event schema (like the Aggregate path's Message.encode),
|
|
38
38
|
// NOT JSON.stringifyAny: the runtime representation drops `None` option
|
|
39
39
|
// fields entirely, while the consumer side (DcbDecode) parses with the sury
|
|
40
|
-
// schema —
|
|
41
|
-
// event would be dropped as schema drift.
|
|
40
|
+
// schema — an encoding that writes `null` for an absent field would reject the
|
|
41
|
+
// missing key and the event would be dropped as schema drift.
|
|
42
42
|
let json = event->Reventless.Util_Sury.toJson(Spec.eventSchema)
|
|
43
43
|
let (eventType, data) = json->Message.splitMessage
|
|
44
44
|
// Use `extractTagsExpanded` (not `extractTags`) so per-element tags on
|
|
@@ -313,9 +313,14 @@ module Make = (
|
|
|
313
313
|
)
|
|
314
314
|
})
|
|
315
315
|
->Effect.flatMap(((state, headPosition, _)) =>
|
|
316
|
+
// Identity, not content: the decision model folds the whole matched
|
|
317
|
+
// history, so serialising it here makes the line's size a function of
|
|
318
|
+
// how long the entity has been in use.
|
|
316
319
|
EffectLogger.logDebug(
|
|
317
320
|
~comp,
|
|
318
|
-
`deciding
|
|
321
|
+
`deciding: id=${entityId->Option.getOr("-")} head=${headPosition->Option.getOr(
|
|
322
|
+
"-",
|
|
323
|
+
)} cmd=${cmdJson->LogFormat.cmdName}`,
|
|
319
324
|
)->Effect.flatMap(_ =>
|
|
320
325
|
switch Behavior.decide(state, command'.command) {
|
|
321
326
|
| Ok(newEvents) if newEvents->Array.length == 0 =>
|
|
@@ -164,7 +164,7 @@ function Make(Spec) {
|
|
|
164
164
|
}), param => {
|
|
165
165
|
let headPosition = param[1];
|
|
166
166
|
let state = param[0];
|
|
167
|
-
return Effect.flatMap(EffectLogger$ReventlessCore.logDebug(comp, undefined, `deciding
|
|
167
|
+
return Effect.flatMap(EffectLogger$ReventlessCore.logDebug(comp, undefined, `deciding: id=` + Stdlib_Option.getOr(entityId, "-") + ` head=` + Stdlib_Option.getOr(headPosition, "-") + ` cmd=` + LogFormat$ReventlessCore.cmdName(cmdJson)), () => {
|
|
168
168
|
let newEvents = Behavior.decide(state, command$p.command);
|
|
169
169
|
if (newEvents.TAG === "Ok") {
|
|
170
170
|
let newEvents$1 = newEvents._0;
|
|
@@ -52,6 +52,7 @@ module type T = {
|
|
|
52
52
|
~extensions: array<module(ReventlessInfra.Extension.Blueprint)>=?,
|
|
53
53
|
~extensionPoints: array<module(ReventlessInfra.ExtensionPointMapping.Mapping)>=?,
|
|
54
54
|
~componentChapters: dict<string>=?,
|
|
55
|
+
~lifecycleModel: array<Reventless.Plugin.derivedEdge>=?,
|
|
55
56
|
) => Reventless.Plugin.pluginStructure
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -1104,6 +1104,7 @@ module Make = (
|
|
|
1104
1104
|
~extensions: array<module(ReventlessInfra.Extension.Blueprint)>=[],
|
|
1105
1105
|
~extensionPoints: array<module(ReventlessInfra.ExtensionPointMapping.Mapping)>=[],
|
|
1106
1106
|
~componentChapters: dict<string>=Dict.make(),
|
|
1107
|
+
~lifecycleModel: array<Reventless.Plugin.derivedEdge>=[],
|
|
1107
1108
|
): Reventless.Plugin.pluginStructure =>
|
|
1108
1109
|
Plugin_Structure.make(
|
|
1109
1110
|
~name,
|
|
@@ -1117,6 +1118,7 @@ module Make = (
|
|
|
1117
1118
|
~extensions,
|
|
1118
1119
|
~extensionPoints,
|
|
1119
1120
|
~componentChapters,
|
|
1121
|
+
~lifecycleModel,
|
|
1120
1122
|
)
|
|
1121
1123
|
|
|
1122
1124
|
let make = (
|
|
@@ -95,7 +95,7 @@ function Make(Spec) {
|
|
|
95
95
|
pages: pages
|
|
96
96
|
};
|
|
97
97
|
};
|
|
98
|
-
let makePluginDefinition = (name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChangeSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, extensionsOpt, extensionPointsOpt, componentChaptersOpt) => {
|
|
98
|
+
let makePluginDefinition = (name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChangeSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, extensionsOpt, extensionPointsOpt, componentChaptersOpt, lifecycleModelOpt) => {
|
|
99
99
|
let aggregates = aggregatesOpt !== undefined ? aggregatesOpt : [];
|
|
100
100
|
let readModels = readModelsOpt !== undefined ? readModelsOpt : [];
|
|
101
101
|
let stateViewSlices = stateViewSlicesOpt !== undefined ? stateViewSlicesOpt : [];
|
|
@@ -106,7 +106,8 @@ function Make(Spec) {
|
|
|
106
106
|
let extensions = extensionsOpt !== undefined ? extensionsOpt : [];
|
|
107
107
|
let extensionPoints = extensionPointsOpt !== undefined ? extensionPointsOpt : [];
|
|
108
108
|
let componentChapters = componentChaptersOpt !== undefined ? componentChaptersOpt : ({});
|
|
109
|
-
|
|
109
|
+
let lifecycleModel = lifecycleModelOpt !== undefined ? lifecycleModelOpt : [];
|
|
110
|
+
return Plugin_Structure$ReventlessCore.make(name, aggregates, readModels, stateViewSlices, stateChangeSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, extensions, extensionPoints, componentChapters, lifecycleModel);
|
|
110
111
|
};
|
|
111
112
|
let make = (name, heartbeatInterval, extensionPointsOpt, extensionsOpt, aggregatesOpt, readModelsOpt, tasksOpt, stateChangeSlicesOpt, stateViewSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, systemCallableComponentsOpt, componentRuntimeOpt, uiFragments, pluginStructure, opts) => {
|
|
112
113
|
let extensionPoints = extensionPointsOpt !== undefined ? extensionPointsOpt : [];
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
let log = Logger.fromEnv()
|
|
6
6
|
|
|
7
|
+
// Set by `check:lifecycle`, and by nothing else. See the note where it is read.
|
|
8
|
+
@val external _declaredTransitionsOnly: option<string> = "process.env.REVENTLESS_DECLARED_TRANSITIONS_ONLY"
|
|
9
|
+
let declaredTransitionsOnly = _declaredTransitionsOnly->Option.isSome
|
|
10
|
+
|
|
7
11
|
// Whether a field can name a record, stated over `SchemaType`'s IR so every
|
|
8
12
|
// shape it grows is excluded before it exists. `Nullable` unwraps; a `Semantic`
|
|
9
13
|
// wrapper is refused — a bucket key or URL is not prose.
|
|
@@ -413,6 +417,15 @@ let checkDeclaredTransitions = (
|
|
|
413
417
|
if Array.length(known) == 0 {
|
|
414
418
|
unvalidated := unvalidated.contents + 1
|
|
415
419
|
} else {
|
|
420
|
+
// Said, because the two are fixed differently: an authored edge is
|
|
421
|
+
// edited, a harvested one is re-derived. A harvested state the view no
|
|
422
|
+
// longer declares means the committed model went stale, and stopping is
|
|
423
|
+
// right — published, it would offer the command on no row at all.
|
|
424
|
+
let hint = switch cmd.allowedStatesSource {
|
|
425
|
+
| Some("derived") =>
|
|
426
|
+
" This edge came from the component's own scenarios, so the committed lifecycle model is stale."
|
|
427
|
+
| _ => ""
|
|
428
|
+
}
|
|
416
429
|
declared
|
|
417
430
|
->Array.filter(state => !(known->Array.includes(state)))
|
|
418
431
|
->Array.forEach(state =>
|
|
@@ -421,7 +434,7 @@ let checkDeclaredTransitions = (
|
|
|
421
434
|
`${w.name}.${cmd.name} declares state "${state}", which none of its ` ++
|
|
422
435
|
`linked views declare — ${w.linkedViews->Array.join(
|
|
423
436
|
", ",
|
|
424
|
-
)} know ${known->Array.join(", ")}
|
|
437
|
+
)} know ${known->Array.join(", ")}.${hint}`,
|
|
425
438
|
)
|
|
426
439
|
->ignore
|
|
427
440
|
)
|
|
@@ -443,7 +456,7 @@ let checkDeclaredTransitions = (
|
|
|
443
456
|
|
|
444
457
|
if Array.length(failures) > 0 {
|
|
445
458
|
JsError.throwWithMessage(
|
|
446
|
-
`${pluginName}: a
|
|
459
|
+
`${pluginName}: a transition names states that do not exist.\n` ++
|
|
447
460
|
failures->Array.join("\n"),
|
|
448
461
|
)
|
|
449
462
|
}
|
|
@@ -580,10 +593,18 @@ let extractReferences = (properties: dict<S.t<unknown>>): array<
|
|
|
580
593
|
> =>
|
|
581
594
|
properties
|
|
582
595
|
->Dict.toArray
|
|
583
|
-
->Array.
|
|
584
|
-
|
|
596
|
+
->Array.flatMap(((fieldName, fieldSchema)) =>
|
|
597
|
+
// `collectFieldTargets`, not `getFieldTarget`: a reference declared on a field
|
|
598
|
+
// of a record the field holds (an order line's `productId`) is one this
|
|
599
|
+
// command declares, and it arrives named by its own path. Commands and events
|
|
600
|
+
// both come through here, which is what stops one of them learning about
|
|
601
|
+
// nesting and the other not.
|
|
602
|
+
Reventless.Reference.collectFieldTargets(fieldName, fieldSchema)->Array.map(((
|
|
603
|
+
path,
|
|
604
|
+
target,
|
|
605
|
+
)) => (
|
|
585
606
|
{
|
|
586
|
-
Reventless.Plugin.fieldName,
|
|
607
|
+
Reventless.Plugin.fieldName: path,
|
|
587
608
|
entity: target.entity,
|
|
588
609
|
plugin: target.plugin,
|
|
589
610
|
}: Reventless.Plugin.fieldReference
|
|
@@ -637,6 +658,10 @@ let extractErrorDefs = (errorSchema: S.t<unknown>): array<Reventless.Plugin.erro
|
|
|
637
658
|
|
|
638
659
|
// Aggregate commands that initialize a new aggregate instance are Collection-level
|
|
639
660
|
// (shown as table-top buttons); all others are Instance-level (shown per-row).
|
|
661
|
+
//
|
|
662
|
+
// A guess from the command's name stem, and it misreads `Enroll`, `Provision`,
|
|
663
|
+
// `Onboard`. It answers only where the harvested model does not: a plugin with no
|
|
664
|
+
// corpus, or one whose linked views declare no lifecycle to label a history with.
|
|
640
665
|
let isCreateCommandName = name =>
|
|
641
666
|
["Add", "Create", "Register", "Open", "Initialize", "Submit", "Start", "Place"]->Array.some(p =>
|
|
642
667
|
name->String.startsWith(p)
|
|
@@ -724,13 +749,17 @@ let toCommandDef = (
|
|
|
724
749
|
// (see the call sites below), so `Transition` itself asserts nothing about
|
|
725
750
|
// representation and stays parameterised all the way down.
|
|
726
751
|
~commandTransition: unknown => Reventless.Transition.t<string>,
|
|
752
|
+
// What this component's own scenarios say about each command, harvested at
|
|
753
|
+
// build time and committed beside the plugin. `None` for a command no corpus
|
|
754
|
+
// covers, which is most of them in a plugin that ships no tests.
|
|
755
|
+
~derivedEdgeFor: string => option<Reventless.Plugin.derivedEdge>,
|
|
727
756
|
v: S.t<unknown>,
|
|
728
757
|
): option<Reventless.Plugin.commandDef> => {
|
|
729
758
|
// Build a commandDef for one variant. `properties` is the variant's field dict —
|
|
730
759
|
// empty for a payload-less variant (e.g. `| Archive`), which compiles to a bare
|
|
731
760
|
// `S.literal("Archive")` string rather than an `{TAG, ...}` object.
|
|
732
761
|
let mkDef = (~variantName, ~properties) => {
|
|
733
|
-
let (
|
|
762
|
+
let (guessedLevel, aggregateIdField) = commandLevelAndId(~isAggregate, ~variantName, properties)
|
|
734
763
|
let references = extractReferences(properties)
|
|
735
764
|
// Evaluated against a synthetic value per constructor, the same shape the
|
|
736
765
|
// resolver builds at call time: a payload-bearing variant compiles to
|
|
@@ -742,12 +771,57 @@ let toCommandDef = (
|
|
|
742
771
|
// The spec's own switch, which is exhaustive — so it also speaks for a
|
|
743
772
|
// constructor the host did not declare but spliced from a trait.
|
|
744
773
|
//
|
|
745
|
-
// An edge is ONE declaration, so the two fields are read off it together.
|
|
746
774
|
// `targetState: None` ⇒ AutoUI's board resolver falls back to its name-stem
|
|
747
775
|
// heuristic.
|
|
748
776
|
let declared = commandTransition(syntheticCommand)
|
|
749
|
-
let
|
|
750
|
-
|
|
777
|
+
let derived = derivedEdgeFor(variantName)
|
|
778
|
+
|
|
779
|
+
// Where the scenarios answer, they answer; where they are silent, the
|
|
780
|
+
// annotation stands. The halves are resolved separately because they are
|
|
781
|
+
// silent separately — a corpus routinely shows a command taking effect
|
|
782
|
+
// without ever showing where it lands — and because an empty derivation is
|
|
783
|
+
// not a derivation: `Some([])` matches no row's lifecycle tag, so publishing
|
|
784
|
+
// one offers the command on no row at all while the annotation that would
|
|
785
|
+
// have been right sits unread beside it.
|
|
786
|
+
//
|
|
787
|
+
// The one claim the scenarios may not answer for is `Unrestricted`: it says
|
|
788
|
+
// the command is legal in EVERY state, and a corpus only covers the states
|
|
789
|
+
// somebody wrote a scenario for, so deriving a from-set there would shrink a
|
|
790
|
+
// deliberate claim to an accident of coverage and withdraw the command from
|
|
791
|
+
// the rows nobody tested. `Undeclared` — the ppx's injected default — erases
|
|
792
|
+
// to the same pair of `None`s and is silence, which the scenarios may answer.
|
|
793
|
+
let unrestricted = Reventless.Transition.isUnrestricted(declared)
|
|
794
|
+
let (allowedStates, allowedStatesSource) = switch (
|
|
795
|
+
unrestricted
|
|
796
|
+
? None
|
|
797
|
+
: derived->Option.flatMap(d =>
|
|
798
|
+
Array.length(d.allowedStates) > 0 ? Some(d.allowedStates) : None
|
|
799
|
+
),
|
|
800
|
+
Reventless.Transition.allowedStates(declared),
|
|
801
|
+
) {
|
|
802
|
+
| (Some(observed), _) => (Some(observed), Some("derived"))
|
|
803
|
+
| (None, Some(states)) => (Some(states), Some("declared"))
|
|
804
|
+
| (None, None) => (None, unrestricted ? Some("unrestricted") : None)
|
|
805
|
+
}
|
|
806
|
+
// The target is not protected the same way, because the two halves fail
|
|
807
|
+
// differently: a from-set RESTRICTS, and narrowing one wrongly takes a
|
|
808
|
+
// command away from rows it belongs on, while a target only tells a diagram
|
|
809
|
+
// where an edge lands. Supplying one the author left unstated adds
|
|
810
|
+
// information and withdraws nothing — which is what `Customer.Register`'s
|
|
811
|
+
// "the row's status is the view's to derive" asks for.
|
|
812
|
+
let targetState = switch derived->Option.flatMap(d =>
|
|
813
|
+
// Two observed targets are an edge `targetState` cannot express. Reported
|
|
814
|
+
// as a contradiction by the harvest; here the declaration is left to speak
|
|
815
|
+
// rather than one of the two picked.
|
|
816
|
+
switch d.targets {
|
|
817
|
+
| [only] => Some(only)
|
|
818
|
+
| _ => None
|
|
819
|
+
}
|
|
820
|
+
) {
|
|
821
|
+
| Some(observed) => Some(observed)
|
|
822
|
+
| None => Reventless.Transition.targetState(declared)
|
|
823
|
+
}
|
|
824
|
+
let level = derived->Option.flatMap(d => d.level)->Option.getOr(guessedLevel)
|
|
751
825
|
// API-exposed iff the whole command isn't @noApi and this variant
|
|
752
826
|
// isn't in its @noApi-variants set — mirrors the API-generation filter
|
|
753
827
|
// (Plugin_Helpers / PluginBaseFragment). Drives the event-graph API badge.
|
|
@@ -763,7 +837,12 @@ let toCommandDef = (
|
|
|
763
837
|
// argument type names are composed *from* that field name, so there is
|
|
764
838
|
// nothing to publish for it either.
|
|
765
839
|
let mutationField = apiExposed ? mutationFieldFor(variantName) : ""
|
|
766
|
-
|
|
840
|
+
// Rooted at the mutation field, and asking for the input type names, so an
|
|
841
|
+
// object nested inside an argument (an order line inside `lineItems`) carries
|
|
842
|
+
// the name the SDL declares it under — which `x-reventless-graphql-type`, keyed
|
|
843
|
+
// by top-level argument, has no place to put.
|
|
844
|
+
let jsonSchema =
|
|
845
|
+
v->SuryToJsonSchema.deriveObjectSchema(~inputNames=apiExposed, ~typeName=mutationField)
|
|
767
846
|
let annotatedSchema = if apiExposed {
|
|
768
847
|
GraphQL_FragmentGenerator.mutationArgTypes(~fieldName=mutationField, v)->Option.mapOr(
|
|
769
848
|
jsonSchema,
|
|
@@ -785,6 +864,7 @@ let toCommandDef = (
|
|
|
785
864
|
references,
|
|
786
865
|
allowedStates,
|
|
787
866
|
targetState,
|
|
867
|
+
allowedStatesSource: ?allowedStatesSource,
|
|
788
868
|
apiExposed: Some(apiExposed),
|
|
789
869
|
requiredAccess,
|
|
790
870
|
// Resolved from this constructor's own properties, not the union's: two
|
|
@@ -817,6 +897,7 @@ let extractCommandDefs = (
|
|
|
817
897
|
~mutationFieldFor: string => string,
|
|
818
898
|
~commandAuthorization: unknown => Reventless.Authorization.permission,
|
|
819
899
|
~commandTransition: unknown => Reventless.Transition.t<string>,
|
|
900
|
+
~derivedEdgeFor: string => option<Reventless.Plugin.derivedEdge>=_ => None,
|
|
820
901
|
commandSchema: S.t<unknown>,
|
|
821
902
|
): array<Reventless.Plugin.commandDef> =>
|
|
822
903
|
switch commandSchema {
|
|
@@ -828,6 +909,7 @@ let extractCommandDefs = (
|
|
|
828
909
|
~parentSchema=commandSchema,
|
|
829
910
|
~commandAuthorization,
|
|
830
911
|
~commandTransition,
|
|
912
|
+
~derivedEdgeFor,
|
|
831
913
|
v,
|
|
832
914
|
)
|
|
833
915
|
)
|
|
@@ -839,6 +921,7 @@ let extractCommandDefs = (
|
|
|
839
921
|
~parentSchema=commandSchema,
|
|
840
922
|
~commandAuthorization,
|
|
841
923
|
~commandTransition,
|
|
924
|
+
~derivedEdgeFor,
|
|
842
925
|
commandSchema,
|
|
843
926
|
)->Option.mapOr([], def => [def])
|
|
844
927
|
}
|
|
@@ -911,8 +994,18 @@ let make = (
|
|
|
911
994
|
// Component name → chapter, captured from each component's source folder by the
|
|
912
995
|
// plugin generator. Keyed by `Spec.name`; no entry renders flat.
|
|
913
996
|
~componentChapters: dict<string>=Dict.make(),
|
|
997
|
+
// What the plugin's own scenarios say about each command's lifecycle edge,
|
|
998
|
+
// harvested by `check:lifecycle` and committed as `src/LifecycleModel.res`.
|
|
999
|
+
~lifecycleModel: array<Reventless.Plugin.derivedEdge>=[],
|
|
914
1000
|
): Reventless.Plugin.pluginStructure => {
|
|
915
1001
|
let chapterOf = (compName: string): option<string> => componentChapters->Dict.get(compName)
|
|
1002
|
+
// The harvest compares its own reading of the corpus with the DECLARATION, so
|
|
1003
|
+
// it needs a structure the model has not already been folded into. Reading a
|
|
1004
|
+
// derived value back as though it were the claim would confirm every edge and
|
|
1005
|
+
// hide exactly the disagreements the check exists to find.
|
|
1006
|
+
let model = declaredTransitionsOnly ? [] : lifecycleModel
|
|
1007
|
+
let derivedEdgeFor = (~component: string, command: string) =>
|
|
1008
|
+
model->Array.find(e => e.component == component && e.command == command)
|
|
916
1009
|
// Payload-less variants dropped: the graph must not claim an edge a DCB lookup
|
|
917
1010
|
// cannot WHERE-clause on.
|
|
918
1011
|
let eventVariantNames = schema => Reventless.DcbTag.extractVariantNames(schema)
|
|
@@ -1391,6 +1484,7 @@ let make = (
|
|
|
1391
1484
|
),
|
|
1392
1485
|
~commandAuthorization=SCS.Spec.commandAuthorization->Obj.magic,
|
|
1393
1486
|
~commandTransition=SCS.Spec.commandTransition->Obj.magic,
|
|
1487
|
+
~derivedEdgeFor=derivedEdgeFor(~component=SCS.Spec.name, ...),
|
|
1394
1488
|
SCS.Spec.commandSchema->S.castToUnknown,
|
|
1395
1489
|
),
|
|
1396
1490
|
producedEventTypes: produced,
|
|
@@ -1416,6 +1510,7 @@ let make = (
|
|
|
1416
1510
|
~mutationFieldFor=variantName => Api_Naming.aggregateMutationField(~plugin=name, ~aggregate=A.Spec.name, ~command=variantName),
|
|
1417
1511
|
~commandAuthorization=A.Spec.commandAuthorization->Obj.magic,
|
|
1418
1512
|
~commandTransition=A.Spec.commandTransition->Obj.magic,
|
|
1513
|
+
~derivedEdgeFor=derivedEdgeFor(~component=A.Spec.name, ...),
|
|
1419
1514
|
A.Spec.commandSchema->S.castToUnknown,
|
|
1420
1515
|
),
|
|
1421
1516
|
producedEventTypes: produced,
|