@reventlessdev/reventless-core 3.0.0-alpha.222 → 3.0.0-alpha.224
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 +32 -0
- package/package.json +7 -7
- package/src/admin/Platform_Admin_Structure.res +10 -0
- package/src/admin/Platform_Admin_Structure.res.mjs +6 -1
- package/src/admin/Platform_ComponentDefinitionsApi.res +33 -2
- package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +18 -2
- package/src/components/Api/Api_Ids.res +63 -0
- package/src/components/Api/Api_Ids.res.mjs +43 -0
- package/src/components/Api/GraphQL_FragmentGenerator.res +121 -28
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +97 -27
- package/src/components/Api/QueryDbListQuery.res +13 -5
- package/src/components/Api/QueryDbListQuery.res.mjs +5 -3
- package/src/components/Api/SchemaType.res +36 -2
- package/src/components/Api/SchemaType.res.mjs +31 -10
- package/src/plugin/component/Plugin_Structure.res +41 -24
- package/src/plugin/component/Plugin_Structure.res.mjs +25 -18
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +118 -7
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +168 -7
- package/tests/admin/Platform_PluginStructuresApiTest.res +10 -0
- package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +23 -2
- package/tests/api/Api_IdsTest.res +143 -0
- package/tests/api/Api_IdsTest.res.mjs +97 -0
- package/tests/api/GraphQL_FragmentGeneratorTest.res +169 -0
- package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +179 -0
- package/tests/plugin/PluginStructureTest.res +159 -0
- package/tests/plugin/PluginStructureTest.res.mjs +160 -6
- package/tests/plugin/StateChangeSlice/PsReserveStock.res +44 -0
- package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +67 -0
- package/tests/plugin/StateViewSlice/PsCategoriesView.res +17 -0
- package/tests/plugin/StateViewSlice/PsCategoriesView.res.mjs +57 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
5
|
import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
6
|
+
import * as Api_Naming$ReventlessCore from "./Api_Naming.res.mjs";
|
|
6
7
|
import * as SchemaType$ReventlessCore from "./SchemaType.res.mjs";
|
|
7
8
|
import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
|
|
8
9
|
import * as GraphQL_Stitcher$ReventlessCore from "./GraphQL_Stitcher.res.mjs";
|
|
@@ -72,7 +73,20 @@ function fromSchemaType(_required, _asInputOpt, _st, collectedTypes, seenTypes)
|
|
|
72
73
|
}
|
|
73
74
|
return name$1 + bang;
|
|
74
75
|
case "Semantic" :
|
|
75
|
-
|
|
76
|
+
let inner = st._1;
|
|
77
|
+
if (typeof inner === "object" && inner.TAG === "ObjectRef") {
|
|
78
|
+
let name$2 = inner._0;
|
|
79
|
+
if (Stdlib_Option.isSome(SchemaType$ReventlessCore.canonicalName(st._0.id))) {
|
|
80
|
+
let typeName = asInput ? name$2 + "Input" : name$2;
|
|
81
|
+
if (!seenTypes.has(typeName)) {
|
|
82
|
+
seenTypes.add(typeName);
|
|
83
|
+
let typeDef$1 = objectRefToGraphQL(asInput, typeName, inner._1, collectedTypes, seenTypes);
|
|
84
|
+
collectedTypes.push(typeDef$1);
|
|
85
|
+
}
|
|
86
|
+
return typeName + bang;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
_st = inner;
|
|
76
90
|
_asInputOpt = asInput;
|
|
77
91
|
continue;
|
|
78
92
|
}
|
|
@@ -172,11 +186,55 @@ function scalarOfSchemaType(_st) {
|
|
|
172
186
|
};
|
|
173
187
|
}
|
|
174
188
|
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
189
|
+
function isKeyFieldName(name) {
|
|
190
|
+
if (name.length > 2) {
|
|
191
|
+
return name.endsWith("Id");
|
|
192
|
+
} else {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function resolveKeyField(entityName, schema) {
|
|
198
|
+
let match = StateAnnotations$Reventless.getSpec(schema);
|
|
199
|
+
let declared = match !== undefined ? match.ids[0] : undefined;
|
|
200
|
+
if (declared !== undefined) {
|
|
201
|
+
return [
|
|
202
|
+
declared,
|
|
203
|
+
"annotation"
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
let candidates = Object.keys(Stdlib_Option.getOr(SchemaType$ReventlessCore.fromSuryObject("", schema), {})).filter(isKeyFieldName);
|
|
207
|
+
let singular = Api_Naming$ReventlessCore.singularize(Api_Naming$ReventlessCore.stripViewSuffix(entityName));
|
|
208
|
+
let conventional = singular.slice(0, 1).toLowerCase() + singular.slice(1, singular.length) + "Id";
|
|
209
|
+
if (candidates.includes(conventional)) {
|
|
210
|
+
return [
|
|
211
|
+
conventional,
|
|
212
|
+
"convention"
|
|
213
|
+
];
|
|
214
|
+
} else if (candidates.length === 1) {
|
|
215
|
+
return [
|
|
216
|
+
candidates[0],
|
|
217
|
+
"sole"
|
|
218
|
+
];
|
|
219
|
+
} else {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function entityNameOf(entry) {
|
|
225
|
+
let n = entry.specName;
|
|
226
|
+
if (n !== undefined) {
|
|
227
|
+
return n;
|
|
228
|
+
}
|
|
229
|
+
let i = entry.returnTypeName.lastIndexOf("_");
|
|
230
|
+
if (i !== -1) {
|
|
231
|
+
return entry.returnTypeName.slice(i + 1 | 0, entry.returnTypeName.length);
|
|
232
|
+
} else {
|
|
233
|
+
return entry.returnTypeName;
|
|
179
234
|
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function deriveServerCapability(entityName, schema) {
|
|
180
238
|
let fieldTypes = Stdlib_Option.getOr(SchemaType$ReventlessCore.fromSuryObject("", schema), {});
|
|
181
239
|
let scalarOf = fieldName => Stdlib_Option.mapOr(fieldTypes[fieldName], "String", scalarOfSchemaType);
|
|
182
240
|
let filterFields = [];
|
|
@@ -201,26 +259,35 @@ function deriveServerCapability(schema) {
|
|
|
201
259
|
return;
|
|
202
260
|
}
|
|
203
261
|
};
|
|
204
|
-
spec.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
pushFilter(name,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
262
|
+
let spec = StateAnnotations$Reventless.getSpec(schema);
|
|
263
|
+
if (spec !== undefined) {
|
|
264
|
+
spec.ids.forEach(name => {
|
|
265
|
+
pushFilter(name, false);
|
|
266
|
+
pushSort(name);
|
|
267
|
+
});
|
|
268
|
+
spec.compositeIds.forEach(name => pushFilter(name, false));
|
|
269
|
+
spec.subIds.forEach(name => {
|
|
270
|
+
pushFilter(name, true);
|
|
271
|
+
pushSort(name);
|
|
272
|
+
});
|
|
273
|
+
spec.compositeSubIds.forEach(name => {
|
|
274
|
+
pushFilter(name, true);
|
|
275
|
+
pushSort(name);
|
|
276
|
+
});
|
|
277
|
+
spec.indexes.forEach(param => {
|
|
278
|
+
let name = param[0];
|
|
279
|
+
pushFilter(name, false);
|
|
280
|
+
pushSort(name);
|
|
281
|
+
});
|
|
282
|
+
spec.scan.forEach(name => pushFilter(name, false));
|
|
283
|
+
spec.scanSort.forEach(pushSort);
|
|
284
|
+
}
|
|
285
|
+
let match = resolveKeyField(entityName, schema);
|
|
286
|
+
if (match !== undefined) {
|
|
287
|
+
let field = match[0];
|
|
288
|
+
pushFilter(field, false);
|
|
289
|
+
pushSort(field);
|
|
290
|
+
}
|
|
224
291
|
return {
|
|
225
292
|
filterFields: filterFields,
|
|
226
293
|
sortFields: sortFields
|
|
@@ -435,7 +502,7 @@ function generate(mutationEntries, queryEntries) {
|
|
|
435
502
|
types.push(t);
|
|
436
503
|
});
|
|
437
504
|
}
|
|
438
|
-
let capability = deriveServerCapability(entry.stateSchema);
|
|
505
|
+
let capability = deriveServerCapability(entityNameOf(entry), entry.stateSchema);
|
|
439
506
|
let connectionFilterTypeName = entry.returnTypeName + "Filter";
|
|
440
507
|
if (!seenTypes.has(connectionFilterTypeName)) {
|
|
441
508
|
seenTypes.add(connectionFilterTypeName);
|
|
@@ -491,6 +558,9 @@ export {
|
|
|
491
558
|
deriveSubIdFilterType,
|
|
492
559
|
emptyCapability,
|
|
493
560
|
scalarOfSchemaType,
|
|
561
|
+
isKeyFieldName,
|
|
562
|
+
resolveKeyField,
|
|
563
|
+
entityNameOf,
|
|
494
564
|
deriveServerCapability,
|
|
495
565
|
validateScanSortAlignment,
|
|
496
566
|
deriveConnectionFilterType,
|
|
@@ -503,4 +573,4 @@ export {
|
|
|
503
573
|
deriveMutationFieldFromObject,
|
|
504
574
|
generate,
|
|
505
575
|
}
|
|
506
|
-
/*
|
|
576
|
+
/* Api_Naming-ReventlessCore Not a pure module */
|
|
@@ -71,15 +71,16 @@ let buildConnection = (
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// Compute the Relay connection response (edges + pageInfo) for `items` under the
|
|
74
|
-
// GraphQL args. `decodeLocalId` maps a possibly-Relay-encoded id to its local id
|
|
75
|
-
//
|
|
76
|
-
//
|
|
74
|
+
// GraphQL args. `decodeLocalId` maps a possibly-Relay-encoded id to its local id;
|
|
75
|
+
// it defaults to the shared rule so every caller applies the same one — the AWS
|
|
76
|
+
// Lambda used to pass a no-op here, which silently made `filter.ids` reject a
|
|
77
|
+
// global id on that provider only. Everything else is pure over the item array.
|
|
77
78
|
let run = (
|
|
78
79
|
~items: array<JSON.t>,
|
|
79
80
|
~argsDict: Dict.t<JSON.t>,
|
|
80
81
|
~capability: GraphQL_FragmentGenerator.serverCapability,
|
|
81
82
|
~labelField: string,
|
|
82
|
-
~decodeLocalId: string => option<string
|
|
83
|
+
~decodeLocalId: string => option<string>=Api_Ids.toLocalId,
|
|
83
84
|
): JSON.t => {
|
|
84
85
|
let filterDict =
|
|
85
86
|
argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
|
|
@@ -145,11 +146,18 @@ let run = (
|
|
|
145
146
|
getLabel(item)->String.toLowerCase->String.startsWith(p->String.toLowerCase)
|
|
146
147
|
| _ => true
|
|
147
148
|
}
|
|
149
|
+
// Either id form matches, in both directions: the argument may be a Relay
|
|
150
|
+
// global id while the row carries its storage key (the normal case — every
|
|
151
|
+
// door but `node` reports the storage key), and the row may carry a global id
|
|
152
|
+
// where some caller stamped one. Decoding only the row's id, as this did,
|
|
153
|
+
// covered just the second.
|
|
148
154
|
let passIds = switch ids {
|
|
149
155
|
| Some(idList) if idList->Array.length > 0 =>
|
|
150
156
|
let itemId = getId(item)
|
|
151
157
|
let itemLocalId = decodeLocalId(itemId)
|
|
152
|
-
idList->Array.some(i =>
|
|
158
|
+
idList->Array.some(i =>
|
|
159
|
+
i == itemId || itemLocalId == Some(i) || decodeLocalId(i) == Some(itemId)
|
|
160
|
+
)
|
|
153
161
|
| _ => true
|
|
154
162
|
}
|
|
155
163
|
let passPerField = perFieldChecks->Array.every(check => check(item))
|
|
@@ -5,6 +5,7 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
6
|
import * as Stdlib_Nullable from "@rescript/runtime/lib/es6/Stdlib_Nullable.js";
|
|
7
7
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
8
|
+
import * as Api_Ids$ReventlessCore from "./Api_Ids.res.mjs";
|
|
8
9
|
|
|
9
10
|
function encodeCursor(value) {
|
|
10
11
|
return btoa(value);
|
|
@@ -47,7 +48,8 @@ function buildConnection(pageItems, hasNextPage, hasPreviousPage, cursorValueOf)
|
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function run(items, argsDict, capability, labelField,
|
|
51
|
+
function run(items, argsDict, capability, labelField, decodeLocalIdOpt) {
|
|
52
|
+
let decodeLocalId = decodeLocalIdOpt !== undefined ? decodeLocalIdOpt : Api_Ids$ReventlessCore.toLocalId;
|
|
51
53
|
let filterDict = Stdlib_Option.getOr(Stdlib_Option.flatMap(argsDict["filter"], Stdlib_JSON.Decode.object), {});
|
|
52
54
|
let search = Stdlib_Option.flatMap(filterDict["search"], Stdlib_JSON.Decode.string);
|
|
53
55
|
let searchPrefix = Stdlib_Option.flatMap(filterDict["searchPrefix"], Stdlib_JSON.Decode.string);
|
|
@@ -83,10 +85,10 @@ function run(items, argsDict, capability, labelField, decodeLocalId) {
|
|
|
83
85
|
let itemId = getId(item);
|
|
84
86
|
let itemLocalId = decodeLocalId(itemId);
|
|
85
87
|
passIds = ids.some(i => {
|
|
86
|
-
if (i === itemId) {
|
|
88
|
+
if (i === itemId || Primitive_object.equal(itemLocalId, i)) {
|
|
87
89
|
return true;
|
|
88
90
|
} else {
|
|
89
|
-
return Primitive_object.equal(
|
|
91
|
+
return Primitive_object.equal(decodeLocalId(i), itemId);
|
|
90
92
|
}
|
|
91
93
|
});
|
|
92
94
|
} else {
|
|
@@ -32,8 +32,34 @@ let isIdsFieldName = (name: string): bool => {
|
|
|
32
32
|
len > 3 && String.endsWith(lower, "ids")
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
The GraphQL type name a semantic **composite** is emitted under.
|
|
37
|
+
|
|
38
|
+
The branded scalars are absent on purpose: they refine a `string` or a number
|
|
39
|
+
without changing its shape, so each is already a `String` or a `Float` in GraphQL
|
|
40
|
+
and has no definition that could be duplicated. Only the semantics that turn a
|
|
41
|
+
field into an object or an enum need a name of their own — without one, the same
|
|
42
|
+
type is emitted once per *field* that uses it, under the field path that reached
|
|
43
|
+
it (`Catalog_AddProductPriceCurrency`, `Ordering_SyncNewProductPriceCurrency`, …
|
|
44
|
+
six copies of ISO 4217 in one example schema).
|
|
45
|
+
|
|
46
|
+
Naming them is what makes the definitions identical across plugins, which is the
|
|
47
|
+
property merged-API composition needs: AppSync unions same-named types from
|
|
48
|
+
different source APIs, and a union of identical definitions is that definition.
|
|
49
|
+
The `Node` / `PageInfo` / `SortOrder` base types already rely on exactly this.
|
|
50
|
+
*/
|
|
51
|
+
let semanticCompositeNames = [
|
|
52
|
+
(Reventless.Semantic.Id.money, "Money"),
|
|
53
|
+
(Reventless.Semantic.Id.dateRange, "DateRange"),
|
|
54
|
+
(Reventless.Semantic.Id.geoPoint, "GeoPoint"),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
let canonicalName = (id: string): option<string> =>
|
|
58
|
+
semanticCompositeNames
|
|
59
|
+
->Array.find(((semanticId, _)) => semanticId == id)
|
|
60
|
+
->Option.map(((_, name)) => name)
|
|
61
|
+
|
|
35
62
|
let rec fromSury = (~parentName: string, ~fieldName: string, schema: S.t<unknown>): schemaType => {
|
|
36
|
-
let shape = shapeOf(~parentName, ~fieldName, schema)
|
|
37
63
|
// Semantics the IR already has a dedicated shape for keep it: `dateTime` and
|
|
38
64
|
// `reference` are read below via `isDateTime` / `getTarget`, both of which now
|
|
39
65
|
// consult the generic marker, and their `format` output is a published
|
|
@@ -42,8 +68,16 @@ let rec fromSury = (~parentName: string, ~fieldName: string, schema: S.t<unknown
|
|
|
42
68
|
switch Reventless.Semantic.get(schema) {
|
|
43
69
|
| Some({id} as sem)
|
|
44
70
|
if id !== Reventless.Semantic.Id.dateTime && id !== Reventless.Semantic.Id.reference =>
|
|
71
|
+
// A composite is one type wherever it appears, so its shape is walked under
|
|
72
|
+
// its own name rather than under the field path that reached it. That
|
|
73
|
+
// renames what is nested inside it too — `Money`'s currency enum becomes
|
|
74
|
+
// `MoneyCurrency` for every field, rather than one enum per price field.
|
|
75
|
+
let shape = switch canonicalName(id) {
|
|
76
|
+
| Some(name) => shapeOf(~parentName=name, ~fieldName="", schema)
|
|
77
|
+
| None => shapeOf(~parentName, ~fieldName, schema)
|
|
78
|
+
}
|
|
45
79
|
Semantic(sem, shape)
|
|
46
|
-
| _ =>
|
|
80
|
+
| _ => shapeOf(~parentName, ~fieldName, schema)
|
|
47
81
|
}
|
|
48
82
|
}
|
|
49
83
|
|
|
@@ -27,22 +27,41 @@ function isIdsFieldName(name) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
let semanticCompositeNames = [
|
|
31
|
+
[
|
|
32
|
+
Semantic$Reventless.Id.money,
|
|
33
|
+
"Money"
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
Semantic$Reventless.Id.dateRange,
|
|
37
|
+
"DateRange"
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
Semantic$Reventless.Id.geoPoint,
|
|
41
|
+
"GeoPoint"
|
|
42
|
+
]
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
function canonicalName(id) {
|
|
46
|
+
return Stdlib_Option.map(semanticCompositeNames.find(param => param[0] === id), param => param[1]);
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
function fromSury(parentName, fieldName, schema) {
|
|
31
|
-
let shape = shapeOf(parentName, fieldName, schema);
|
|
32
50
|
let sem = Semantic$Reventless.get(schema);
|
|
33
51
|
if (sem === undefined) {
|
|
34
|
-
return
|
|
52
|
+
return shapeOf(parentName, fieldName, schema);
|
|
35
53
|
}
|
|
36
54
|
let id = sem.id;
|
|
37
|
-
if (id
|
|
38
|
-
return
|
|
39
|
-
TAG: "Semantic",
|
|
40
|
-
_0: sem,
|
|
41
|
-
_1: shape
|
|
42
|
-
};
|
|
43
|
-
} else {
|
|
44
|
-
return shape;
|
|
55
|
+
if (id === Semantic$Reventless.Id.dateTime || id === Semantic$Reventless.Id.reference) {
|
|
56
|
+
return shapeOf(parentName, fieldName, schema);
|
|
45
57
|
}
|
|
58
|
+
let name = canonicalName(id);
|
|
59
|
+
let shape = name !== undefined ? shapeOf(name, "", schema) : shapeOf(parentName, fieldName, schema);
|
|
60
|
+
return {
|
|
61
|
+
TAG: "Semantic",
|
|
62
|
+
_0: sem,
|
|
63
|
+
_1: shape
|
|
64
|
+
};
|
|
46
65
|
}
|
|
47
66
|
|
|
48
67
|
function shapeOf(parentName, fieldName, schema) {
|
|
@@ -195,6 +214,8 @@ export {
|
|
|
195
214
|
isDateTime,
|
|
196
215
|
isIdFieldName,
|
|
197
216
|
isIdsFieldName,
|
|
217
|
+
semanticCompositeNames,
|
|
218
|
+
canonicalName,
|
|
198
219
|
fromSury,
|
|
199
220
|
shapeOf,
|
|
200
221
|
optionalFieldNames,
|
|
@@ -164,20 +164,29 @@ let labelFieldsFromStateSchema = (
|
|
|
164
164
|
// because its components never pass through `make` — has to derive its defs the
|
|
165
165
|
// same way, and a second copy of this walk would be free to drift from this one.
|
|
166
166
|
|
|
167
|
+
// A variant's declared cross-entity references. Shared by the command and event
|
|
168
|
+
// walks because the two ask the identical question of identical field dicts, and
|
|
169
|
+
// the question is `getFieldTarget` rather than `getTarget`: a reference declared
|
|
170
|
+
// on an `array<string>` field sits on the element schema. Collecting it in one
|
|
171
|
+
// place is what stops one walk from being taught that and the other not.
|
|
172
|
+
let extractReferences = (properties: dict<S.t<unknown>>): array<
|
|
173
|
+
Reventless.Plugin.fieldReference,
|
|
174
|
+
> =>
|
|
175
|
+
properties
|
|
176
|
+
->Dict.toArray
|
|
177
|
+
->Array.filterMap(((fieldName, fieldSchema)) =>
|
|
178
|
+
Reventless.Reference.getFieldTarget(fieldSchema)->Option.map(target => (
|
|
179
|
+
{
|
|
180
|
+
Reventless.Plugin.fieldName,
|
|
181
|
+
entity: target.entity,
|
|
182
|
+
plugin: target.plugin,
|
|
183
|
+
}: Reventless.Plugin.fieldReference
|
|
184
|
+
))
|
|
185
|
+
)
|
|
186
|
+
|
|
167
187
|
let toEventDef = (v: S.t<unknown>): option<Reventless.Plugin.eventDef> => {
|
|
168
188
|
let mkDef = (~variantName, ~properties) => {
|
|
169
|
-
let references =
|
|
170
|
-
properties
|
|
171
|
-
->Dict.toArray
|
|
172
|
-
->Array.filterMap(((fieldName, fieldSchema)) =>
|
|
173
|
-
Reventless.Reference.getTarget(fieldSchema)->Option.map(target => (
|
|
174
|
-
{
|
|
175
|
-
Reventless.Plugin.fieldName,
|
|
176
|
-
entity: target.entity,
|
|
177
|
-
plugin: target.plugin,
|
|
178
|
-
}: Reventless.Plugin.fieldReference
|
|
179
|
-
))
|
|
180
|
-
)
|
|
189
|
+
let references = extractReferences(properties)
|
|
181
190
|
({
|
|
182
191
|
Reventless.Plugin.name: variantName,
|
|
183
192
|
// Derived for the same reason as `commandDef.schema`: an event's field
|
|
@@ -301,18 +310,7 @@ let make = (
|
|
|
301
310
|
// `S.literal("Archive")` string rather than an `{TAG, ...}` object.
|
|
302
311
|
let mkDef = (~variantName, ~properties) => {
|
|
303
312
|
let (level, aggregateIdField) = commandLevelAndId(~isAggregate, ~variantName, properties)
|
|
304
|
-
let references =
|
|
305
|
-
properties
|
|
306
|
-
->Dict.toArray
|
|
307
|
-
->Array.filterMap(((fieldName, fieldSchema)) =>
|
|
308
|
-
Reventless.Reference.getTarget(fieldSchema)->Option.map(target => (
|
|
309
|
-
{
|
|
310
|
-
Reventless.Plugin.fieldName,
|
|
311
|
-
entity: target.entity,
|
|
312
|
-
plugin: target.plugin,
|
|
313
|
-
}: Reventless.Plugin.fieldReference
|
|
314
|
-
))
|
|
315
|
-
)
|
|
313
|
+
let references = extractReferences(properties)
|
|
316
314
|
// Per-variant `allowedStates` lives on the *parent* command schema
|
|
317
315
|
// (the PPX attaches a single dict<variantName, [|states|]> via
|
|
318
316
|
// markAllowedStates). Look it up by variant name; back-compat
|
|
@@ -652,6 +650,12 @@ let make = (
|
|
|
652
650
|
let qf = Api_Naming.queryFieldNamesForReadModel(~plugin=name, ~name=R.Spec.name)
|
|
653
651
|
let stateSchema = R.Spec.stateSchema->S.castToUnknown
|
|
654
652
|
let label = labelFieldsFromStateSchema(~entityName=R.Spec.name, stateSchema)
|
|
653
|
+
// The same call the capability deriver makes, so the published key and the
|
|
654
|
+
// key the generated filter/order-by is built from cannot disagree.
|
|
655
|
+
let keyField = GraphQL_FragmentGenerator.resolveKeyField(
|
|
656
|
+
~entityName=R.Spec.name,
|
|
657
|
+
stateSchema,
|
|
658
|
+
)
|
|
655
659
|
// The events this read model projects from, qualified to the plugin's event ids so
|
|
656
660
|
// they match the producers' nodes in the graph. Was empty — which dropped projection
|
|
657
661
|
// edges for any event reaching the read model via a DCB-log-sourced mapping (a classic
|
|
@@ -669,6 +673,12 @@ let make = (
|
|
|
669
673
|
statusField: statusFieldFromStateSchema(~entityName=R.Spec.name, stateSchema),
|
|
670
674
|
visibility: visibilityTag(R.Spec.visibility),
|
|
671
675
|
chapter: chapterOf(R.Spec.name),
|
|
676
|
+
// Taken from the `qf` record, never re-derived: `Api_Naming` is the only
|
|
677
|
+
// place that decides how `Products`/`Categories` singularise, and the
|
|
678
|
+
// point of publishing the name is that a consumer stops guessing it.
|
|
679
|
+
singleQueryField: Some(qf.singleFieldName),
|
|
680
|
+
idField: keyField->Option.map(((f, _)) => f),
|
|
681
|
+
idFieldSource: keyField->Option.map(((_, rung)) => rung),
|
|
672
682
|
}: Reventless.Plugin.queryableDef)
|
|
673
683
|
})
|
|
674
684
|
|
|
@@ -678,6 +688,10 @@ let make = (
|
|
|
678
688
|
let (_, consumed) = svsConsumed->Array.getUnsafe(i)
|
|
679
689
|
let stateSchema = SVS.Spec.stateSchema->S.castToUnknown
|
|
680
690
|
let label = labelFieldsFromStateSchema(~entityName=SVS.Spec.name, stateSchema)
|
|
691
|
+
let keyField = GraphQL_FragmentGenerator.resolveKeyField(
|
|
692
|
+
~entityName=SVS.Spec.name,
|
|
693
|
+
stateSchema,
|
|
694
|
+
)
|
|
681
695
|
({
|
|
682
696
|
Reventless.Plugin.name: SVS.Spec.name,
|
|
683
697
|
queryField: qf.listFieldName,
|
|
@@ -690,6 +704,9 @@ let make = (
|
|
|
690
704
|
statusField: statusFieldFromStateSchema(~entityName=SVS.Spec.name, stateSchema),
|
|
691
705
|
visibility: visibilityTag(SVS.Spec.visibility),
|
|
692
706
|
chapter: chapterOf(SVS.Spec.name),
|
|
707
|
+
singleQueryField: Some(qf.singleFieldName),
|
|
708
|
+
idField: keyField->Option.map(((f, _)) => f),
|
|
709
|
+
idFieldSource: keyField->Option.map(((_, rung)) => rung),
|
|
693
710
|
}: Reventless.Plugin.queryableDef)
|
|
694
711
|
})
|
|
695
712
|
|
|
@@ -19,6 +19,7 @@ import * as SuryToJsonSchema$ReventlessCore from "../../components/Api/SuryToJso
|
|
|
19
19
|
import * as Capability_Inference$ReventlessCore from "./Capability_Inference.res.mjs";
|
|
20
20
|
import * as ApiTargetStateHelpers$ReventlessCore from "../../components/Api/ApiTargetStateHelpers.res.mjs";
|
|
21
21
|
import * as ApiAllowedStatesHelpers$ReventlessCore from "../../components/Api/ApiAllowedStatesHelpers.res.mjs";
|
|
22
|
+
import * as GraphQL_FragmentGenerator$ReventlessCore from "../../components/Api/GraphQL_FragmentGenerator.res.mjs";
|
|
22
23
|
|
|
23
24
|
let log = Logger$ReventlessCore.fromEnv();
|
|
24
25
|
|
|
@@ -140,16 +141,20 @@ function labelFieldsFromStateSchema(entityName, stateSchema) {
|
|
|
140
141
|
};
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
function extractReferences(properties) {
|
|
145
|
+
return Stdlib_Array.filterMap(Object.entries(properties), param => {
|
|
146
|
+
let fieldName = param[0];
|
|
147
|
+
return Stdlib_Option.map(Reference$Reventless.getFieldTarget(param[1]), target => ({
|
|
148
|
+
fieldName: fieldName,
|
|
149
|
+
entity: target.entity,
|
|
150
|
+
plugin: target.plugin
|
|
151
|
+
}));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
143
155
|
function toEventDef(v) {
|
|
144
156
|
let mkDef = (variantName, properties) => {
|
|
145
|
-
let references =
|
|
146
|
-
let fieldName = param[0];
|
|
147
|
-
return Stdlib_Option.map(Reference$Reventless.getTarget(param[1]), target => ({
|
|
148
|
-
fieldName: fieldName,
|
|
149
|
-
entity: target.entity,
|
|
150
|
-
plugin: target.plugin
|
|
151
|
-
}));
|
|
152
|
-
});
|
|
157
|
+
let references = extractReferences(properties);
|
|
153
158
|
return {
|
|
154
159
|
name: variantName,
|
|
155
160
|
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(v)),
|
|
@@ -267,14 +272,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
267
272
|
let toCommandDef = (isAggregate, mutationFieldFor, parentSchema, v) => {
|
|
268
273
|
let mkDef = (variantName, properties) => {
|
|
269
274
|
let match = commandLevelAndId(isAggregate, variantName, properties);
|
|
270
|
-
let references =
|
|
271
|
-
let fieldName = param[0];
|
|
272
|
-
return Stdlib_Option.map(Reference$Reventless.getTarget(param[1]), target => ({
|
|
273
|
-
fieldName: fieldName,
|
|
274
|
-
entity: target.entity,
|
|
275
|
-
plugin: target.plugin
|
|
276
|
-
}));
|
|
277
|
-
});
|
|
275
|
+
let references = extractReferences(properties);
|
|
278
276
|
let allowedStates = ApiAllowedStatesHelpers$ReventlessCore.getAllowedStates(parentSchema, variantName);
|
|
279
277
|
let targetState = ApiTargetStateHelpers$ReventlessCore.getTargetState(parentSchema, variantName);
|
|
280
278
|
let apiExposed = false;
|
|
@@ -503,6 +501,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
503
501
|
let qf = Api_Naming$ReventlessCore.queryFieldNamesForReadModel(name, R.Spec.name, undefined);
|
|
504
502
|
let stateSchema = R.Spec.stateSchema;
|
|
505
503
|
let label = labelFieldsFromStateSchema(R.Spec.name, stateSchema);
|
|
504
|
+
let keyField = GraphQL_FragmentGenerator$ReventlessCore.resolveKeyField(R.Spec.name, stateSchema);
|
|
506
505
|
let consumed = qualify(name, R.consumedEventNames);
|
|
507
506
|
return {
|
|
508
507
|
name: R.Spec.name,
|
|
@@ -515,7 +514,10 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
515
514
|
labelFieldSource: labelFieldSourceToString(label.source),
|
|
516
515
|
statusField: statusFieldFromStateSchema(R.Spec.name, stateSchema),
|
|
517
516
|
visibility: visibilityTag(R.Spec.visibility),
|
|
518
|
-
chapter: componentChapters[R.Spec.name]
|
|
517
|
+
chapter: componentChapters[R.Spec.name],
|
|
518
|
+
singleQueryField: qf.singleFieldName,
|
|
519
|
+
idField: Stdlib_Option.map(keyField, param => param[0]),
|
|
520
|
+
idFieldSource: Stdlib_Option.map(keyField, param => param[1])
|
|
519
521
|
};
|
|
520
522
|
});
|
|
521
523
|
let stateViewDefs = stateViewSlices.map((SVS, i) => {
|
|
@@ -524,6 +526,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
524
526
|
let consumed = match[1];
|
|
525
527
|
let stateSchema = SVS.Spec.stateSchema;
|
|
526
528
|
let label = labelFieldsFromStateSchema(SVS.Spec.name, stateSchema);
|
|
529
|
+
let keyField = GraphQL_FragmentGenerator$ReventlessCore.resolveKeyField(SVS.Spec.name, stateSchema);
|
|
527
530
|
return {
|
|
528
531
|
name: SVS.Spec.name,
|
|
529
532
|
queryField: qf.listFieldName,
|
|
@@ -535,7 +538,10 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
535
538
|
labelFieldSource: labelFieldSourceToString(label.source),
|
|
536
539
|
statusField: statusFieldFromStateSchema(SVS.Spec.name, stateSchema),
|
|
537
540
|
visibility: visibilityTag(SVS.Spec.visibility),
|
|
538
|
-
chapter: componentChapters[SVS.Spec.name]
|
|
541
|
+
chapter: componentChapters[SVS.Spec.name],
|
|
542
|
+
singleQueryField: qf.singleFieldName,
|
|
543
|
+
idField: Stdlib_Option.map(keyField, param => param[0]),
|
|
544
|
+
idFieldSource: Stdlib_Option.map(keyField, param => param[1])
|
|
539
545
|
};
|
|
540
546
|
});
|
|
541
547
|
let stateChangeDefs = stateChangeSlices.map((SCS, i) => {
|
|
@@ -653,6 +659,7 @@ export {
|
|
|
653
659
|
statusFieldFromStateSchema,
|
|
654
660
|
labelFieldSourceToString,
|
|
655
661
|
labelFieldsFromStateSchema,
|
|
662
|
+
extractReferences,
|
|
656
663
|
toEventDef,
|
|
657
664
|
extractEventDefs,
|
|
658
665
|
extractErrorDefs,
|