@reventlessdev/reventless-core 3.0.0-alpha.224 → 3.0.0-alpha.226

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/package.json +6 -6
  3. package/src/admin/Platform_Admin_Structure.res +6 -0
  4. package/src/admin/Platform_Admin_Structure.res.mjs +9 -3
  5. package/src/admin/Platform_BakedManifest.res +154 -0
  6. package/src/admin/Platform_BakedManifest.res.mjs +156 -0
  7. package/src/admin/Platform_ComponentDefinitionsApi.res +9 -2
  8. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +18 -2
  9. package/src/components/Api/OwnerScopeDiagnostics.res +54 -0
  10. package/src/components/Api/OwnerScopeDiagnostics.res.mjs +27 -0
  11. package/src/components/Api/QueryDbListQuery.res +25 -1
  12. package/src/components/Api/QueryDbListQuery.res.mjs +10 -3
  13. package/src/components/Api/SuryToJsonSchema.res +19 -0
  14. package/src/components/Api/SuryToJsonSchema.res.mjs +18 -4
  15. package/src/components/CommandGenerator/CommandGenerator_Callback.res +48 -0
  16. package/src/components/CommandGenerator/CommandGenerator_Callback.res.mjs +27 -1
  17. package/src/plugin/component/Plugin_Structure.res +59 -3
  18. package/src/plugin/component/Plugin_Structure.res.mjs +29 -9
  19. package/tests/admin/Platform_BakedManifestTest.res +244 -0
  20. package/tests/admin/Platform_BakedManifestTest.res.mjs +294 -0
  21. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +10 -0
  22. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +24 -8
  23. package/tests/admin/Platform_PluginStructuresApiTest.res +2 -0
  24. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +6 -2
  25. package/tests/api/Api_IdsTest.res.mjs +1 -1
  26. package/tests/api/OwnerScopeDiagnosticsTest.res +109 -0
  27. package/tests/api/OwnerScopeDiagnosticsTest.res.mjs +91 -0
  28. package/tests/api/SuryToJsonSchemaTest.res +45 -0
  29. package/tests/api/SuryToJsonSchemaTest.res.mjs +23 -0
  30. package/tests/commandgenerator/OwnerStampingTest.res +174 -0
  31. package/tests/commandgenerator/OwnerStampingTest.res.mjs +195 -0
  32. package/tests/plugin/PluginStructureAccessTest.res +110 -0
  33. package/tests/plugin/PluginStructureAccessTest.res.mjs +144 -0
  34. package/tests/plugin/StateChangeSlice/PsGatedCommands.res +28 -0
  35. package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +84 -0
  36. package/tests/plugin/StateViewSlice/PsGatedView.res +15 -0
  37. package/tests/plugin/StateViewSlice/PsGatedView.res.mjs +60 -0
  38. package/tests/plugin/pluginDefinitionRequiredScalars.txt +4 -0
@@ -0,0 +1,244 @@
1
+ // The baked manifest is curation, so what matters is exactly which components
2
+ // survive an include-list — and, decisively, that an entry which includes
3
+ // everything is byte-identical to what the served query returns. Anything less
4
+ // and a shell reading the file is reading a second, quietly different contract.
5
+
6
+ open JestGlobals
7
+ open Reventless.Plugin
8
+
9
+ let queryable = (~name, ~visibility=?, ~ownerField=?, ()): queryableDef => {
10
+ name,
11
+ queryField: `Ordering_${name}`,
12
+ schema: "{}",
13
+ consumedEventTypes: [],
14
+ linkedWriteSide: [],
15
+ labelField: "displayName",
16
+ searchableFields: ["displayName"],
17
+ labelFieldSource: None,
18
+ statusField: None,
19
+ visibility,
20
+ chapter: None,
21
+ singleQueryField: Some(`Ordering_${name}Single`),
22
+ idField: Some("id"),
23
+ idFieldSource: Some("convention"),
24
+ requiredAccess: None,
25
+ ownerField,
26
+ }
27
+
28
+ let command = (~name, ~references=[], ~ownerField=?, ()): commandDef => {
29
+ name,
30
+ schema: "{}",
31
+ level: Collection,
32
+ aggregateIdField: None,
33
+ mutationField: name->String.toLowerCase,
34
+ references,
35
+ allowedStates: None,
36
+ targetState: None,
37
+ apiExposed: Some(true),
38
+ requiredAccess: None,
39
+ ownerField,
40
+ }
41
+
42
+ let writable = (~name, ~commands): writableDef => {
43
+ name,
44
+ commands,
45
+ linkedViews: [],
46
+ consistencyRead: None,
47
+ producedEventTypes: [],
48
+ consumedEventTypes: [],
49
+ events: [],
50
+ errors: [],
51
+ chapter: None,
52
+ }
53
+
54
+ // Mirrors the hybrid example's shape: a public order list, an Internal
55
+ // denormalised product mirror a command @refs, one end-user command and one
56
+ // operator command.
57
+ let structure: pluginStructure = {
58
+ readModels: [queryable(~name="Customers", ())],
59
+ stateViewSlices: [
60
+ queryable(~name="Orders", ~ownerField="customerId", ()),
61
+ queryable(~name="AvailableProducts", ~visibility="Internal", ()),
62
+ ],
63
+ stateChangeSlices: [
64
+ writable(
65
+ ~name="PlaceOrder",
66
+ ~commands=[
67
+ command(
68
+ ~name="PlaceOrder",
69
+ ~references=[{fieldName: "productIds", entity: "AvailableProducts", plugin: None}],
70
+ ~ownerField="customerId",
71
+ (),
72
+ ),
73
+ ],
74
+ ),
75
+ writable(~name="ImportOrders", ~commands=[command(~name="ImportOrders", ())]),
76
+ ],
77
+ aggregates: [],
78
+ automationSlices: [],
79
+ outboundTranslationSlices: [],
80
+ inboundTranslationSlices: [],
81
+ extensions: [],
82
+ extensionPoints: None,
83
+ requiredStores: None,
84
+ requiredStoreDeclarations: None,
85
+ }
86
+
87
+ let structures = [("Ordering@1.0.0", structure)]
88
+
89
+ let bake = selections =>
90
+ Platform_BakedManifest.curate(~structures, ~selections)
91
+
92
+ let entries = (json: JSON.t) => json->JSON.Decode.array->Option.getOr([])
93
+
94
+ let names = (entry: JSON.t, field: string) =>
95
+ entry
96
+ ->JSON.Decode.object
97
+ ->Option.flatMap(d => d->Dict.get(field))
98
+ ->Option.flatMap(JSON.Decode.array)
99
+ ->Option.getOr([])
100
+ ->Array.filterMap(v =>
101
+ v->JSON.Decode.object->Option.flatMap(d => d->Dict.get("name"))->Option.flatMap(JSON.Decode.string)
102
+ )
103
+
104
+ let firstEntry = (r: result<JSON.t, Platform_BakedManifest.error>) =>
105
+ switch r {
106
+ | Ok(json) => json->entries->Array.getUnsafe(0)
107
+ | Error(e) => JsError.throwWithMessage(Platform_BakedManifest.describe(e))
108
+ }
109
+
110
+ describe("curate", () => {
111
+ testSync("selects exactly the named components", () => {
112
+ let entry =
113
+ bake([{plugin: "Ordering", views: Some(["Orders"]), commands: Some(["PlaceOrder"])}])
114
+ ->firstEntry
115
+ expect((entry->names("stateViewSlices"), entry->names("readModels")))->toEqual((["Orders"], []))
116
+ })
117
+
118
+ testSync("drops a write side whose commands were all excluded", () => {
119
+ let entry =
120
+ bake([{plugin: "Ordering", views: Some([]), commands: Some(["PlaceOrder"])}])->firstEntry
121
+ expect(entry->names("stateChangeSlices"))->toEqual(["PlaceOrder"])
122
+ })
123
+
124
+ testSync("unset views and commands include every public component", () => {
125
+ let entry = bake([{plugin: "Ordering", views: None, commands: None}])->firstEntry
126
+ expect((
127
+ entry->names("readModels"),
128
+ entry->names("stateViewSlices"),
129
+ entry->names("stateChangeSlices"),
130
+ ))->toEqual((["Customers"], ["Orders"], ["PlaceOrder", "ImportOrders"]))
131
+ })
132
+
133
+ // A shell reading a baked file has no admin API to recover a reference target
134
+ // from, so an Internal view a kept command points at has to survive the bake.
135
+ testSync("carries an Internal view referenced by an included command", () => {
136
+ let entry =
137
+ bake([{plugin: "Ordering", views: Some(["Orders"]), commands: Some(["PlaceOrder"])}])
138
+ ->firstEntry
139
+ expect(entry->names("internalQueryables"))->toEqual(["AvailableProducts"])
140
+ })
141
+
142
+ testSync("omits an Internal view no included command references", () => {
143
+ let entry =
144
+ bake([{plugin: "Ordering", views: Some(["Orders"]), commands: Some(["ImportOrders"])}])
145
+ ->firstEntry
146
+ expect(entry->names("internalQueryables"))->toEqual([])
147
+ })
148
+
149
+ testSync("an Internal view is never selectable as a view", () =>
150
+ expect(
151
+ bake([{plugin: "Ordering", views: Some(["AvailableProducts"]), commands: None}]),
152
+ )->toEqual(
153
+ Error(Platform_BakedManifest.UnknownView({plugin: "Ordering", view: "AvailableProducts"})),
154
+ )
155
+ )
156
+
157
+ testSync("fails naming an unknown plugin", () =>
158
+ expect(bake([{plugin: "Shipping", views: None, commands: None}]))->toEqual(
159
+ Error(Platform_BakedManifest.UnknownPlugin("Shipping")),
160
+ )
161
+ )
162
+
163
+ testSync("fails naming an unknown command", () =>
164
+ expect(bake([{plugin: "Ordering", views: None, commands: Some(["CancelOrder"])}]))->toEqual(
165
+ Error(Platform_BakedManifest.UnknownCommand({plugin: "Ordering", command: "CancelOrder"})),
166
+ )
167
+ )
168
+
169
+ // The anti-drift assertion: include everything and the baked entry IS the
170
+ // served entry. Any future divergence in the encoder shows up here first.
171
+ testSync("an all-inclusive bake is byte-identical to the served entry", () => {
172
+ let baked = bake([{plugin: "Ordering", views: None, commands: None}])->firstEntry
173
+ let served = Platform_ComponentDefinitionsApi.encodePluginStructureEntry(
174
+ ~pluginId="Ordering@1.0.0",
175
+ structure,
176
+ )
177
+ expect(baked->JSON.stringify)->toEqual(served->JSON.stringify)
178
+ })
179
+
180
+ testSync("one entry per selection, in declaration order", () => {
181
+ let json =
182
+ Platform_BakedManifest.curate(
183
+ ~structures=[("Ordering@1.0.0", structure), ("Catalog@1.0.0", structure)],
184
+ ~selections=[
185
+ {plugin: "Catalog", views: Some([]), commands: Some([])},
186
+ {plugin: "Ordering", views: Some([]), commands: Some([])},
187
+ ],
188
+ )
189
+ let ids =
190
+ switch json {
191
+ | Ok(j) =>
192
+ j
193
+ ->entries
194
+ ->Array.filterMap(e =>
195
+ e
196
+ ->JSON.Decode.object
197
+ ->Option.flatMap(d => d->Dict.get("pluginId"))
198
+ ->Option.flatMap(JSON.Decode.string)
199
+ )
200
+ | Error(_) => []
201
+ }
202
+ expect(ids)->toEqual(["Catalog", "Ordering"])
203
+ })
204
+
205
+ // A shell reading a baked manifest has no admin API to ask instead, so a field
206
+ // the curation step forgot to copy is a field that view can never learn about.
207
+ // `curate` reuses the served encoder rather than writing its own, which is what
208
+ // makes this hold — the assertion guards that property, not a second code path.
209
+ testSync("a curated entry carries the owner field the served entry carries", () => {
210
+ let entry =
211
+ bake([{plugin: "Ordering", views: Some(["Orders"]), commands: Some(["PlaceOrder"])}])
212
+ ->firstEntry
213
+ let ownerOf = (key, name) =>
214
+ entry
215
+ ->JSON.Decode.object
216
+ ->Option.flatMap(d => d->Dict.get(key))
217
+ ->Option.flatMap(JSON.Decode.array)
218
+ ->Option.getOr([])
219
+ ->Array.filterMap(JSON.Decode.object)
220
+ ->Array.find(d =>
221
+ d->Dict.get("name")->Option.flatMap(JSON.Decode.string) == Some(name)
222
+ )
223
+ ->Option.flatMap(d => d->Dict.get("ownerField"))
224
+ ->Option.flatMap(JSON.Decode.string)
225
+ let commandOwner =
226
+ entry
227
+ ->JSON.Decode.object
228
+ ->Option.flatMap(d => d->Dict.get("stateChangeSlices"))
229
+ ->Option.flatMap(JSON.Decode.array)
230
+ ->Option.getOr([])
231
+ ->Array.filterMap(JSON.Decode.object)
232
+ ->Array.flatMap(w =>
233
+ w->Dict.get("commands")->Option.flatMap(JSON.Decode.array)->Option.getOr([])
234
+ )
235
+ ->Array.filterMap(JSON.Decode.object)
236
+ ->Array.get(0)
237
+ ->Option.flatMap(d => d->Dict.get("ownerField"))
238
+ ->Option.flatMap(JSON.Decode.string)
239
+ expect((ownerOf("stateViewSlices", "Orders"), commandOwner))->toEqual((
240
+ Some("customerId"),
241
+ Some("customerId"),
242
+ ))
243
+ })
244
+ })
@@ -0,0 +1,294 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
4
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
7
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
8
+ import * as Platform_BakedManifest$ReventlessCore from "../../src/admin/Platform_BakedManifest.res.mjs";
9
+ import * as Platform_ComponentDefinitionsApi$ReventlessCore from "../../src/admin/Platform_ComponentDefinitionsApi.res.mjs";
10
+
11
+ function queryable(name, visibility, ownerField, param) {
12
+ return {
13
+ name: name,
14
+ queryField: `Ordering_` + name,
15
+ schema: "{}",
16
+ consumedEventTypes: [],
17
+ linkedWriteSide: [],
18
+ labelField: "displayName",
19
+ searchableFields: ["displayName"],
20
+ labelFieldSource: undefined,
21
+ statusField: undefined,
22
+ ownerField: ownerField,
23
+ visibility: visibility,
24
+ chapter: undefined,
25
+ singleQueryField: `Ordering_` + name + `Single`,
26
+ idField: "id",
27
+ idFieldSource: "convention",
28
+ requiredAccess: undefined
29
+ };
30
+ }
31
+
32
+ function command(name, referencesOpt, ownerField, param) {
33
+ let references = referencesOpt !== undefined ? referencesOpt : [];
34
+ return {
35
+ name: name,
36
+ schema: "{}",
37
+ level: "Collection",
38
+ aggregateIdField: undefined,
39
+ mutationField: name.toLowerCase(),
40
+ references: references,
41
+ allowedStates: undefined,
42
+ targetState: undefined,
43
+ apiExposed: true,
44
+ requiredAccess: undefined,
45
+ ownerField: ownerField
46
+ };
47
+ }
48
+
49
+ function writable(name, commands) {
50
+ return {
51
+ name: name,
52
+ commands: commands,
53
+ producedEventTypes: [],
54
+ consumedEventTypes: [],
55
+ linkedViews: [],
56
+ consistencyRead: undefined,
57
+ events: [],
58
+ errors: [],
59
+ chapter: undefined
60
+ };
61
+ }
62
+
63
+ let structure_readModels = [queryable("Customers", undefined, undefined, undefined)];
64
+
65
+ let structure_stateViewSlices = [
66
+ queryable("Orders", undefined, "customerId", undefined),
67
+ queryable("AvailableProducts", "Internal", undefined, undefined)
68
+ ];
69
+
70
+ let structure_stateChangeSlices = [
71
+ writable("PlaceOrder", [command("PlaceOrder", [{
72
+ fieldName: "productIds",
73
+ entity: "AvailableProducts",
74
+ plugin: undefined
75
+ }], "customerId", undefined)]),
76
+ writable("ImportOrders", [command("ImportOrders", undefined, undefined, undefined)])
77
+ ];
78
+
79
+ let structure_aggregates = [];
80
+
81
+ let structure_automationSlices = [];
82
+
83
+ let structure_outboundTranslationSlices = [];
84
+
85
+ let structure_inboundTranslationSlices = [];
86
+
87
+ let structure_extensions = [];
88
+
89
+ let structure = {
90
+ readModels: structure_readModels,
91
+ stateViewSlices: structure_stateViewSlices,
92
+ stateChangeSlices: structure_stateChangeSlices,
93
+ aggregates: structure_aggregates,
94
+ automationSlices: structure_automationSlices,
95
+ outboundTranslationSlices: structure_outboundTranslationSlices,
96
+ inboundTranslationSlices: structure_inboundTranslationSlices,
97
+ extensions: structure_extensions,
98
+ extensionPoints: undefined,
99
+ requiredStores: undefined,
100
+ requiredStoreDeclarations: undefined
101
+ };
102
+
103
+ let structures = [[
104
+ "Ordering@1.0.0",
105
+ structure
106
+ ]];
107
+
108
+ function bake(selections) {
109
+ return Platform_BakedManifest$ReventlessCore.curate(structures, selections);
110
+ }
111
+
112
+ function entries(json) {
113
+ return Stdlib_Option.getOr(Stdlib_JSON.Decode.array(json), []);
114
+ }
115
+
116
+ function names(entry, field) {
117
+ return Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(entry), d => d[field]), Stdlib_JSON.Decode.array), []), v => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(v), d => d["name"]), Stdlib_JSON.Decode.string));
118
+ }
119
+
120
+ function firstEntry(r) {
121
+ if (r.TAG === "Ok") {
122
+ return entries(r._0)[0];
123
+ } else {
124
+ return Stdlib_JsError.throwWithMessage(Platform_BakedManifest$ReventlessCore.describe(r._0));
125
+ }
126
+ }
127
+
128
+ globalThis.describe("curate", () => {
129
+ globalThis.test("selects exactly the named components", () => {
130
+ let entry = firstEntry(Platform_BakedManifest$ReventlessCore.curate(structures, [{
131
+ plugin: "Ordering",
132
+ views: ["Orders"],
133
+ commands: ["PlaceOrder"]
134
+ }]));
135
+ globalThis.expect([
136
+ names(entry, "stateViewSlices"),
137
+ names(entry, "readModels")
138
+ ]).toEqual([
139
+ ["Orders"],
140
+ []
141
+ ]);
142
+ });
143
+ globalThis.test("drops a write side whose commands were all excluded", () => {
144
+ let entry = firstEntry(Platform_BakedManifest$ReventlessCore.curate(structures, [{
145
+ plugin: "Ordering",
146
+ views: [],
147
+ commands: ["PlaceOrder"]
148
+ }]));
149
+ globalThis.expect(names(entry, "stateChangeSlices")).toEqual(["PlaceOrder"]);
150
+ });
151
+ globalThis.test("unset views and commands include every public component", () => {
152
+ let entry = firstEntry(Platform_BakedManifest$ReventlessCore.curate(structures, [{
153
+ plugin: "Ordering",
154
+ views: undefined,
155
+ commands: undefined
156
+ }]));
157
+ globalThis.expect([
158
+ names(entry, "readModels"),
159
+ names(entry, "stateViewSlices"),
160
+ names(entry, "stateChangeSlices")
161
+ ]).toEqual([
162
+ ["Customers"],
163
+ ["Orders"],
164
+ [
165
+ "PlaceOrder",
166
+ "ImportOrders"
167
+ ]
168
+ ]);
169
+ });
170
+ globalThis.test("carries an Internal view referenced by an included command", () => {
171
+ let entry = firstEntry(Platform_BakedManifest$ReventlessCore.curate(structures, [{
172
+ plugin: "Ordering",
173
+ views: ["Orders"],
174
+ commands: ["PlaceOrder"]
175
+ }]));
176
+ globalThis.expect(names(entry, "internalQueryables")).toEqual(["AvailableProducts"]);
177
+ });
178
+ globalThis.test("omits an Internal view no included command references", () => {
179
+ let entry = firstEntry(Platform_BakedManifest$ReventlessCore.curate(structures, [{
180
+ plugin: "Ordering",
181
+ views: ["Orders"],
182
+ commands: ["ImportOrders"]
183
+ }]));
184
+ globalThis.expect(names(entry, "internalQueryables")).toEqual([]);
185
+ });
186
+ globalThis.test("an Internal view is never selectable as a view", () => {
187
+ globalThis.expect(Platform_BakedManifest$ReventlessCore.curate(structures, [{
188
+ plugin: "Ordering",
189
+ views: ["AvailableProducts"],
190
+ commands: undefined
191
+ }])).toEqual({
192
+ TAG: "Error",
193
+ _0: {
194
+ TAG: "UnknownView",
195
+ plugin: "Ordering",
196
+ view: "AvailableProducts"
197
+ }
198
+ });
199
+ });
200
+ globalThis.test("fails naming an unknown plugin", () => {
201
+ globalThis.expect(Platform_BakedManifest$ReventlessCore.curate(structures, [{
202
+ plugin: "Shipping",
203
+ views: undefined,
204
+ commands: undefined
205
+ }])).toEqual({
206
+ TAG: "Error",
207
+ _0: {
208
+ TAG: "UnknownPlugin",
209
+ _0: "Shipping"
210
+ }
211
+ });
212
+ });
213
+ globalThis.test("fails naming an unknown command", () => {
214
+ globalThis.expect(Platform_BakedManifest$ReventlessCore.curate(structures, [{
215
+ plugin: "Ordering",
216
+ views: undefined,
217
+ commands: ["CancelOrder"]
218
+ }])).toEqual({
219
+ TAG: "Error",
220
+ _0: {
221
+ TAG: "UnknownCommand",
222
+ plugin: "Ordering",
223
+ command: "CancelOrder"
224
+ }
225
+ });
226
+ });
227
+ globalThis.test("an all-inclusive bake is byte-identical to the served entry", () => {
228
+ let baked = firstEntry(Platform_BakedManifest$ReventlessCore.curate(structures, [{
229
+ plugin: "Ordering",
230
+ views: undefined,
231
+ commands: undefined
232
+ }]));
233
+ let served = Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Ordering@1.0.0", structure);
234
+ globalThis.expect(JSON.stringify(baked)).toEqual(JSON.stringify(served));
235
+ });
236
+ globalThis.test("one entry per selection, in declaration order", () => {
237
+ let json = Platform_BakedManifest$ReventlessCore.curate([
238
+ [
239
+ "Ordering@1.0.0",
240
+ structure
241
+ ],
242
+ [
243
+ "Catalog@1.0.0",
244
+ structure
245
+ ]
246
+ ], [
247
+ {
248
+ plugin: "Catalog",
249
+ views: [],
250
+ commands: []
251
+ },
252
+ {
253
+ plugin: "Ordering",
254
+ views: [],
255
+ commands: []
256
+ }
257
+ ]);
258
+ let ids;
259
+ ids = json.TAG === "Ok" ? Stdlib_Array.filterMap(entries(json._0), e => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(e), d => d["pluginId"]), Stdlib_JSON.Decode.string)) : [];
260
+ globalThis.expect(ids).toEqual([
261
+ "Catalog",
262
+ "Ordering"
263
+ ]);
264
+ });
265
+ globalThis.test("a curated entry carries the owner field the served entry carries", () => {
266
+ let entry = firstEntry(Platform_BakedManifest$ReventlessCore.curate(structures, [{
267
+ plugin: "Ordering",
268
+ views: ["Orders"],
269
+ commands: ["PlaceOrder"]
270
+ }]));
271
+ let ownerOf = (key, name) => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(entry), d => d[key]), Stdlib_JSON.Decode.array), []), Stdlib_JSON.Decode.object).find(d => Primitive_object.equal(Stdlib_Option.flatMap(d["name"], Stdlib_JSON.Decode.string), name)), d => d["ownerField"]), Stdlib_JSON.Decode.string);
272
+ let commandOwner = Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Array.filterMap(Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(entry), d => d["stateChangeSlices"]), Stdlib_JSON.Decode.array), []), Stdlib_JSON.Decode.object).flatMap(w => Stdlib_Option.getOr(Stdlib_Option.flatMap(w["commands"], Stdlib_JSON.Decode.array), [])), Stdlib_JSON.Decode.object)[0], d => d["ownerField"]), Stdlib_JSON.Decode.string);
273
+ globalThis.expect([
274
+ ownerOf("stateViewSlices", "Orders"),
275
+ commandOwner
276
+ ]).toEqual([
277
+ "customerId",
278
+ "customerId"
279
+ ]);
280
+ });
281
+ });
282
+
283
+ export {
284
+ queryable,
285
+ command,
286
+ writable,
287
+ structure,
288
+ structures,
289
+ bake,
290
+ entries,
291
+ names,
292
+ firstEntry,
293
+ }
294
+ /* structure Not a pure module */
@@ -17,6 +17,8 @@ let cmd: commandDef = {
17
17
  allowedStates: None,
18
18
  targetState: None,
19
19
  apiExposed: Some(true),
20
+ requiredAccess: None,
21
+ ownerField: None,
20
22
  }
21
23
 
22
24
  let qbl: queryableDef = {
@@ -34,6 +36,8 @@ let qbl: queryableDef = {
34
36
  singleQueryField: Some("Catalog_Product"),
35
37
  idField: Some("productId"),
36
38
  idFieldSource: Some("convention"),
39
+ requiredAccess: None,
40
+ ownerField: None,
37
41
  }
38
42
 
39
43
  let wbl: writableDef = {
@@ -258,6 +262,8 @@ describe("visibility filtering (deployed AutoUI hides Internal)", () => {
258
262
  singleQueryField: Some("Ordering_AvailableProduct"),
259
263
  idField: Some("productId"),
260
264
  idFieldSource: Some("sole"),
265
+ requiredAccess: None,
266
+ ownerField: None,
261
267
  }
262
268
  // A distinct name per source array: the complement has to be fed by both, and
263
269
  // reusing one def would hide a version that only walks `readModels`.
@@ -334,6 +340,8 @@ describe("allowedStates + statusField populated", () => {
334
340
  allowedStates: Some(["Inactive"]),
335
341
  targetState: Some("Active"),
336
342
  apiExposed: Some(true),
343
+ requiredAccess: None,
344
+ ownerField: None,
337
345
  }
338
346
 
339
347
  let qblWithStatus: queryableDef = {
@@ -351,6 +359,8 @@ describe("allowedStates + statusField populated", () => {
351
359
  singleQueryField: Some("Platform_Plugin"),
352
360
  idField: None,
353
361
  idFieldSource: None,
362
+ requiredAccess: None,
363
+ ownerField: None,
354
364
  }
355
365
 
356
366
  let wblWithStates: writableDef = {
@@ -23,7 +23,9 @@ let cmd = {
23
23
  references: cmd_references,
24
24
  allowedStates: undefined,
25
25
  targetState: undefined,
26
- apiExposed: cmd_apiExposed
26
+ apiExposed: cmd_apiExposed,
27
+ requiredAccess: undefined,
28
+ ownerField: undefined
27
29
  };
28
30
 
29
31
  let qbl_consumedEventTypes = ["ProductAdded"];
@@ -50,11 +52,13 @@ let qbl = {
50
52
  searchableFields: qbl_searchableFields,
51
53
  labelFieldSource: qbl_labelFieldSource,
52
54
  statusField: undefined,
55
+ ownerField: undefined,
53
56
  visibility: undefined,
54
57
  chapter: undefined,
55
58
  singleQueryField: qbl_singleQueryField,
56
59
  idField: qbl_idField,
57
- idFieldSource: qbl_idFieldSource
60
+ idFieldSource: qbl_idFieldSource,
61
+ requiredAccess: undefined
58
62
  };
59
63
 
60
64
  let wbl_commands = [cmd];
@@ -279,11 +283,13 @@ globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", ()
279
283
  searchableFields: internalQbl_searchableFields,
280
284
  labelFieldSource: internalQbl_labelFieldSource,
281
285
  statusField: undefined,
286
+ ownerField: undefined,
282
287
  visibility: internalQbl_visibility,
283
288
  chapter: undefined,
284
289
  singleQueryField: internalQbl_singleQueryField,
285
290
  idField: internalQbl_idField,
286
- idFieldSource: internalQbl_idFieldSource
291
+ idFieldSource: internalQbl_idFieldSource,
292
+ requiredAccess: undefined
287
293
  };
288
294
  let internalSlice_consumedEventTypes = internalQbl_consumedEventTypes;
289
295
  let internalSlice_linkedWriteSide = internalQbl_linkedWriteSide;
@@ -303,11 +309,13 @@ globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", ()
303
309
  searchableFields: internalSlice_searchableFields,
304
310
  labelFieldSource: internalSlice_labelFieldSource,
305
311
  statusField: undefined,
312
+ ownerField: undefined,
306
313
  visibility: internalSlice_visibility,
307
314
  chapter: undefined,
308
315
  singleQueryField: internalSlice_singleQueryField,
309
316
  idField: internalSlice_idField,
310
- idFieldSource: internalSlice_idFieldSource
317
+ idFieldSource: internalSlice_idFieldSource,
318
+ requiredAccess: undefined
311
319
  };
312
320
  let mixed_readModels = [
313
321
  qbl,
@@ -385,7 +393,9 @@ globalThis.describe("allowedStates + statusField populated", () => {
385
393
  references: cmdWithStates_references,
386
394
  allowedStates: cmdWithStates_allowedStates,
387
395
  targetState: cmdWithStates_targetState,
388
- apiExposed: cmdWithStates_apiExposed
396
+ apiExposed: cmdWithStates_apiExposed,
397
+ requiredAccess: undefined,
398
+ ownerField: undefined
389
399
  };
390
400
  let qblWithStatus_consumedEventTypes = [];
391
401
  let qblWithStatus_linkedWriteSide = ["Plugin"];
@@ -403,11 +413,13 @@ globalThis.describe("allowedStates + statusField populated", () => {
403
413
  searchableFields: qblWithStatus_searchableFields,
404
414
  labelFieldSource: qblWithStatus_labelFieldSource,
405
415
  statusField: qblWithStatus_statusField,
416
+ ownerField: undefined,
406
417
  visibility: undefined,
407
418
  chapter: undefined,
408
419
  singleQueryField: qblWithStatus_singleQueryField,
409
420
  idField: undefined,
410
- idFieldSource: undefined
421
+ idFieldSource: undefined,
422
+ requiredAccess: undefined
411
423
  };
412
424
  let wblWithStates_commands = [cmdWithStates];
413
425
  let wblWithStates_producedEventTypes = [];
@@ -480,11 +492,13 @@ globalThis.describe("singleQueryField reaches the caller", () => {
480
492
  searchableFields: qbl_searchableFields,
481
493
  labelFieldSource: "annotation",
482
494
  statusField: undefined,
495
+ ownerField: undefined,
483
496
  visibility: undefined,
484
497
  chapter: undefined,
485
498
  singleQueryField: undefined,
486
499
  idField: "productId",
487
- idFieldSource: "convention"
500
+ idFieldSource: "convention",
501
+ requiredAccess: undefined
488
502
  }],
489
503
  stateViewSlices: structure_stateViewSlices,
490
504
  stateChangeSlices: structure_stateChangeSlices,
@@ -523,11 +537,13 @@ globalThis.describe("idField / idFieldSource reach the caller", () => {
523
537
  searchableFields: qbl_searchableFields,
524
538
  labelFieldSource: "annotation",
525
539
  statusField: undefined,
540
+ ownerField: undefined,
526
541
  visibility: undefined,
527
542
  chapter: undefined,
528
543
  singleQueryField: "Catalog_Product",
529
544
  idField: undefined,
530
- idFieldSource: undefined
545
+ idFieldSource: undefined,
546
+ requiredAccess: undefined
531
547
  }],
532
548
  stateViewSlices: structure_stateViewSlices,
533
549
  stateChangeSlices: structure_stateChangeSlices,
@@ -22,6 +22,8 @@ let publicRm: queryableDef = {
22
22
  singleQueryField: Some("Catalog_Product"),
23
23
  idField: Some("productId"),
24
24
  idFieldSource: Some("convention"),
25
+ requiredAccess: None,
26
+ ownerField: None,
25
27
  }
26
28
 
27
29
  // The component `Platform_ComponentDefinitions` drops and this query must keep.