@reventlessdev/reventless-core 3.0.0-alpha.251 → 3.0.0-alpha.253

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 (73) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/package.json +8 -8
  3. package/src/admin/Platform_Admin_Structure.res +4 -1
  4. package/src/admin/Platform_Admin_Structure.res.mjs +4 -2
  5. package/src/admin/Platform_BakedManifest.res.mjs +3 -1
  6. package/src/admin/Platform_PluginStructuresApi.res +43 -7
  7. package/src/admin/Platform_PluginStructuresApi.res.mjs +47 -1
  8. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +8 -0
  9. package/src/components/Api/ApiNoApiHelpers.res +37 -3
  10. package/src/components/Api/ApiNoApiHelpers.res.mjs +29 -2
  11. package/src/components/AutomationSlice/AutomationSlice_Callback.res +4 -4
  12. package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +3 -3
  13. package/src/plugin/api/PluginBaseFragment.res +3 -3
  14. package/src/plugin/component/Plugin_Structure.res +158 -25
  15. package/src/plugin/component/Plugin_Structure.res.mjs +82 -18
  16. package/src/plugin/connect/PluginConnectExtension_Mapping.res.mjs +3 -1
  17. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +3 -1
  18. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +3 -1
  19. package/src/plugin/lifecycle/PluginBehavior.res +11 -11
  20. package/src/plugin/lifecycle/PluginBehavior.res.mjs +11 -11
  21. package/src/plugin/lifecycle/PluginSpec.res +39 -16
  22. package/src/plugin/lifecycle/PluginSpec.res.mjs +54 -56
  23. package/tests/admin/AdminApiSchemaDriftTest.res +27 -1
  24. package/tests/admin/AdminApiSchemaDriftTest.res.mjs +22 -2
  25. package/tests/admin/Platform_Admin_StructureTest.res +8 -3
  26. package/tests/admin/Platform_Admin_StructureTest.res.mjs +8 -2
  27. package/tests/admin/Platform_BakedManifestTest.res +2 -0
  28. package/tests/admin/Platform_BakedManifestTest.res.mjs +3 -1
  29. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +4 -0
  30. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +21 -7
  31. package/tests/admin/Platform_PluginStructuresApiTest.res +16 -0
  32. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +17 -3
  33. package/tests/aggregate/AggregateCacheTest.res.mjs +15 -3
  34. package/tests/aggregate/AggregateConflictTest.res.mjs +15 -3
  35. package/tests/aggregate/AggregateFixtures.res.mjs +15 -3
  36. package/tests/aggregate/AggregateSnapshotTest.res.mjs +21 -5
  37. package/tests/api/ApiNoApiTest.res +46 -0
  38. package/tests/api/ApiNoApiTest.res.mjs +49 -1
  39. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +27 -3
  40. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +12 -2
  41. package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +2 -0
  42. package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +12 -2
  43. package/tests/dcb/DcbFixtures.res.mjs +8 -0
  44. package/tests/dcb/DcbStateChangeSliceTest.res.mjs +18 -0
  45. package/tests/eventmapper/EventMapperFixtures.res.mjs +15 -3
  46. package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +9 -1
  47. package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +9 -1
  48. package/tests/message/MessageTest.res +2 -0
  49. package/tests/message/MessageTest.res.mjs +3 -1
  50. package/tests/plugin/PluginStructureAccessTest.res.mjs +2 -0
  51. package/tests/plugin/PluginStructureTest.res +191 -12
  52. package/tests/plugin/PluginStructureTest.res.mjs +246 -6
  53. package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +8 -0
  54. package/tests/plugin/StateChangeSlice/PsChangePhoto.res.mjs +8 -0
  55. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res +17 -7
  56. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res.mjs +12 -11
  57. package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +8 -0
  58. package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +8 -0
  59. package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +8 -0
  60. package/tests/plugin/StateChangeSlice/PsShipOrder.res +17 -13
  61. package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +17 -11
  62. package/tests/plugin/StateChangeSlice/PsTransitionSwitch.res +54 -0
  63. package/tests/plugin/StateChangeSlice/PsTransitionSwitch.res.mjs +104 -0
  64. package/tests/plugin/StateChangeSlice/PsTypoStore.res.mjs +8 -0
  65. package/tests/plugin/StateChangeSlice/PsUploadAvatar.res.mjs +8 -0
  66. package/tests/plugin/StateChangeSlice/PsUploadImages.res.mjs +8 -0
  67. package/tests/plugin/StateViewSlice/PsShipmentsView.res +1 -1
  68. package/tests/plugin/pluginDefinitionRequiredScalars.txt +18 -0
  69. package/tests/util/CommandPublisherTest.res.mjs +9 -1
  70. package/src/components/Api/ApiAllowedStatesHelpers.res +0 -18
  71. package/src/components/Api/ApiAllowedStatesHelpers.res.mjs +0 -16
  72. package/src/components/Api/ApiTargetStateHelpers.res +0 -21
  73. package/src/components/Api/ApiTargetStateHelpers.res.mjs +0 -16
@@ -2,8 +2,9 @@
2
2
 
3
3
  open Reventless.Plugin
4
4
 
5
- // Alias, not `open`: the PPX strips `@transition` before the typechecker, so the
6
- // state constructors never reach it and an `open` would warn as unused.
5
+ // Alias, not `open`: the lifecycle states are named qualified in
6
+ // `commandTransition` below, and an `open` would pull the whole view's namespace
7
+ // in for four constructors.
7
8
  module Plugins = PluginsReadModelSpec
8
9
 
9
10
  // The Plugin aggregate is keyed by plugin **name** (not name@version). One
@@ -15,10 +16,7 @@ module Plugins = PluginsReadModelSpec
15
16
  // more same-shaped ones, so the `pluginDefinition` pair must lead (DZakh/sury#392).
16
17
  @schema
17
18
  type command =
18
- // The handshake brings the version's row into being, so it runs from no state
19
- // — `decide` accepts it against an unknown version and against every status
20
- // but `Connected`, where it is a definition refresh rather than a move.
21
- | @noApi @transition(() => Plugins.Connected) Connect(pluginDefinition)
19
+ | @noApi Connect(pluginDefinition)
22
20
  // Records a protocol-version incompatibility without changing connection state.
23
21
  | @noApi ReportIncompatibility(pluginDefinition)
24
22
  | @noApi Heartbeat(version)
@@ -26,20 +24,20 @@ type command =
26
24
  // re-run for an already-connected version so its definition is refreshed on the
27
25
  // row — unlike Heartbeat, which no-ops a connected keep-alive.
28
26
  | @noApi Redetect(version)
29
- // A heartbeat timing out is the only way a row reaches `Disconnected`, and
30
- // `decide` moves the row for exactly one status — anything else is a stray
31
- // disconnect it tolerates without emitting.
32
- | @noApi @transition(([Plugins.Connected]) => Plugins.Disconnected) Disconnect(version)
27
+ | @noApi Disconnect(version)
33
28
  // Admin lifecycle commands — API-exposed (auto-derived admin mutations,
34
29
  // Cognito group-gated). `version` selects which known version to act on.
35
- // The `@transition` edges must agree with `decide` below; `Platform_Admin_Structure`
36
- // reads them off this schema rather than restating them.
37
- | @transition(([Plugins.Inactive, Plugins.Retired]) => Plugins.Connected) Activate(version)
38
- | @transition(([Plugins.Connected, Plugins.Disconnected]) => Plugins.Inactive) Deactivate(version)
30
+ //
31
+ // Inline records, unlike the `@noApi` commands above, and the difference is the
32
+ // API. A positional payload publishes its argument as `_0`, which names nothing:
33
+ // a caller cannot match it against a field it holds, and a coercion failure says
34
+ // `Variable '_0'` rather than what was missing. These three are the ones a person
35
+ // calls, so their argument carries its own name.
36
+ | Activate({version: version})
37
+ | Deactivate({version: version})
39
38
  // Manual admin retirement of a specific version (archive). Distinct from the
40
39
  // automatic `Superseded` transition (which is decided, not commanded).
41
- | @transition(([Plugins.Connected, Plugins.Disconnected, Plugins.Inactive]) => Plugins.Retired)
42
- Retire(version)
40
+ | Retire({version: version})
43
41
 
44
42
  // Carries both the superseded and the superseding version's full definition —
45
43
  // a deterministic trigger for version-to-version schema/data migrations
@@ -83,3 +81,28 @@ type error =
83
81
  | AlreadyConnected
84
82
  | IsDisconnected
85
83
  | IsInactive
84
+
85
+ // The edges must agree with `decide`; `Platform_Admin_Structure` reads them off
86
+ // the assembled structure rather than restating them.
87
+ type lifecycleState = Plugins.status
88
+
89
+ let commandTransition = (command: command): Reventless.Transition.t<lifecycleState> => {
90
+ open Reventless.Transition
91
+ switch command {
92
+ // The handshake brings the version's row into being, so it runs from no state
93
+ // — `decide` accepts it against an unknown version and against every status
94
+ // but `Connected`, where it is a definition refresh rather than a move.
95
+ | Connect(_) => Creates(Plugins.Connected)
96
+ // Neither records a move: an incompatibility is noted beside the row, and a
97
+ // heartbeat or a re-detect refreshes one that is already where it belongs.
98
+ | ReportIncompatibility(_) | Heartbeat(_) | Redetect(_) => Unrestricted
99
+ // A heartbeat timing out is the only way a row reaches `Disconnected`, and
100
+ // `decide` moves the row for exactly one status — anything else is a stray
101
+ // disconnect it tolerates without emitting.
102
+ | Disconnect(_) => Moves([Plugins.Connected], Plugins.Disconnected)
103
+ | Activate(_) => Moves([Plugins.Inactive, Plugins.Retired], Plugins.Connected)
104
+ | Deactivate(_) => Moves([Plugins.Connected, Plugins.Disconnected], Plugins.Inactive)
105
+ | Retire(_) =>
106
+ Moves([Plugins.Connected, Plugins.Disconnected, Plugins.Inactive], Plugins.Retired)
107
+ }
108
+ }
@@ -27,15 +27,15 @@ let commandSchema = Sury.union([
27
27
  })),
28
28
  Sury.$schema(s => ({
29
29
  TAG: "Activate",
30
- _0: s.m(Plugin$Reventless.versionSchema)
30
+ version: s.m(Plugin$Reventless.versionSchema)
31
31
  })),
32
32
  Sury.$schema(s => ({
33
33
  TAG: "Deactivate",
34
- _0: s.m(Plugin$Reventless.versionSchema)
34
+ version: s.m(Plugin$Reventless.versionSchema)
35
35
  })),
36
36
  Sury.$schema(s => ({
37
37
  TAG: "Retire",
38
- _0: s.m(Plugin$Reventless.versionSchema)
38
+ version: s.m(Plugin$Reventless.versionSchema)
39
39
  }))
40
40
  ]);
41
41
 
@@ -92,6 +92,52 @@ let errorSchema = Sury.union([
92
92
  Sury.literal("IsInactive")
93
93
  ]);
94
94
 
95
+ function commandTransition(command) {
96
+ switch (command.TAG) {
97
+ case "Connect" :
98
+ return {
99
+ TAG: "Creates",
100
+ _0: "Connected"
101
+ };
102
+ case "Disconnect" :
103
+ return {
104
+ TAG: "Moves",
105
+ _0: ["Connected"],
106
+ _1: "Disconnected"
107
+ };
108
+ case "Activate" :
109
+ return {
110
+ TAG: "Moves",
111
+ _0: [
112
+ "Inactive",
113
+ "Retired"
114
+ ],
115
+ _1: "Connected"
116
+ };
117
+ case "Deactivate" :
118
+ return {
119
+ TAG: "Moves",
120
+ _0: [
121
+ "Connected",
122
+ "Disconnected"
123
+ ],
124
+ _1: "Inactive"
125
+ };
126
+ case "Retire" :
127
+ return {
128
+ TAG: "Moves",
129
+ _0: [
130
+ "Connected",
131
+ "Disconnected",
132
+ "Inactive"
133
+ ],
134
+ _1: "Retired"
135
+ };
136
+ default:
137
+ return "Unrestricted";
138
+ }
139
+ }
140
+
95
141
  let commandSchema$1 = Api$ReventlessInfra.markNoApiVariants(commandSchema, [
96
142
  "Connect",
97
143
  "ReportIncompatibility",
@@ -100,62 +146,12 @@ let commandSchema$1 = Api$ReventlessInfra.markNoApiVariants(commandSchema, [
100
146
  "Disconnect"
101
147
  ]);
102
148
 
103
- let commandSchema$2 = Api$ReventlessInfra.markAllowedStates(commandSchema$1, [
104
- [
105
- "Disconnect",
106
- ["Connected"]
107
- ],
108
- [
109
- "Activate",
110
- [
111
- "Inactive",
112
- "Retired"
113
- ]
114
- ],
115
- [
116
- "Deactivate",
117
- [
118
- "Connected",
119
- "Disconnected"
120
- ]
121
- ],
122
- [
123
- "Retire",
124
- [
125
- "Connected",
126
- "Disconnected",
127
- "Inactive"
128
- ]
129
- ]
130
- ]);
131
-
132
- let commandSchema$3 = Api$ReventlessInfra.markTargetState(commandSchema$2, [
133
- [
134
- "Connect",
135
- "Connected"
136
- ],
137
- [
138
- "Disconnect",
139
- "Disconnected"
140
- ],
141
- [
142
- "Activate",
143
- "Connected"
144
- ],
145
- [
146
- "Deactivate",
147
- "Inactive"
148
- ],
149
- [
150
- "Retire",
151
- "Retired"
152
- ]
153
- ]);
154
-
155
149
  function commandAuthorization(param) {
156
150
  return "AllowAuthenticated";
157
151
  }
158
152
 
153
+ let traits = [];
154
+
159
155
  let name = "Plugin";
160
156
 
161
157
  let Id;
@@ -171,8 +167,10 @@ export {
171
167
  versionSupersededDataSchema,
172
168
  eventSchema,
173
169
  errorSchema,
174
- commandSchema$3 as commandSchema,
170
+ commandTransition,
171
+ commandSchema$1 as commandSchema,
175
172
  moduleUrl,
176
173
  commandAuthorization,
174
+ traits,
177
175
  }
178
176
  /* commandSchema Not a pure module */
@@ -165,6 +165,11 @@ let storeDecl: requiredStoreDeclaration = {
165
165
  annotation: Some("images"),
166
166
  }
167
167
 
168
+ let capabilityDecl: requiredCapabilityDeclaration = {
169
+ capability: "Geocoding",
170
+ component: "GeocodeAddress",
171
+ }
172
+
168
173
  let structure: pluginStructure = {
169
174
  readModels: [queryable],
170
175
  stateViewSlices: [],
@@ -177,6 +182,8 @@ let structure: pluginStructure = {
177
182
  extensionPoints: Some([extensionPoint]),
178
183
  requiredStores: Some(["catalog.images"]),
179
184
  requiredStoreDeclarations: Some([storeDecl]),
185
+ requiredCapabilities: Some([capabilityDecl]),
186
+ traitDeclarations: None,
180
187
  }
181
188
 
182
189
  let panel: panelManifestEntry = {
@@ -338,7 +345,16 @@ let guards: array<guard> = [
338
345
  wireOnly: ["pluginId", "internalQueryables"],
339
346
  // Producer-side and store fields belong to Platform_PluginStructures — the
340
347
  // deployed AutoUI has no use for either.
341
- recordOnly: ["extensionPoints", "requiredStores", "requiredStoreDeclarations"],
348
+ recordOnly: [
349
+ "extensionPoints",
350
+ "requiredStores",
351
+ "requiredStoreDeclarations",
352
+ "requiredCapabilities",
353
+ // Structure-level provenance, like the three above: AutoUI renders
354
+ // components, and where one came from is the map's question rather than a
355
+ // generated page's. It reaches the wire on `Platform_PluginStructures`.
356
+ "traitDeclarations",
357
+ ],
342
358
  },
343
359
  {
344
360
  gqlType: "Platform_PublishedEventDef",
@@ -364,6 +380,16 @@ let guards: array<guard> = [
364
380
  wireOnly: [],
365
381
  recordOnly: [],
366
382
  },
383
+ {
384
+ gqlType: "Platform_RequiredCapabilityDeclaration",
385
+ sdlTypes: structuresSdl,
386
+ recordFields: schemaKeys(requiredCapabilityDeclarationSchema),
387
+ wireFields: jsonKeys(
388
+ Platform_PluginStructuresApi.encodeRequiredCapabilityDeclaration(capabilityDecl),
389
+ ),
390
+ wireOnly: [],
391
+ recordOnly: [],
392
+ },
367
393
  {
368
394
  gqlType: "Platform_PluginStructureEntry",
369
395
  sdlTypes: structuresSdl,
@@ -279,6 +279,11 @@ let storeDecl = {
279
279
  annotation: "images"
280
280
  };
281
281
 
282
+ let capabilityDecl = {
283
+ capability: "Geocoding",
284
+ component: "GeocodeAddress"
285
+ };
286
+
282
287
  let structure_readModels = [queryable];
283
288
 
284
289
  let structure_stateViewSlices = [];
@@ -301,6 +306,8 @@ let structure_requiredStores = ["catalog.images"];
301
306
 
302
307
  let structure_requiredStoreDeclarations = [storeDecl];
303
308
 
309
+ let structure_requiredCapabilities = [capabilityDecl];
310
+
304
311
  let structure = {
305
312
  readModels: structure_readModels,
306
313
  stateViewSlices: structure_stateViewSlices,
@@ -312,7 +319,9 @@ let structure = {
312
319
  extensions: structure_extensions,
313
320
  extensionPoints: structure_extensionPoints,
314
321
  requiredStores: structure_requiredStores,
315
- requiredStoreDeclarations: structure_requiredStoreDeclarations
322
+ requiredStoreDeclarations: structure_requiredStoreDeclarations,
323
+ requiredCapabilities: structure_requiredCapabilities,
324
+ traitDeclarations: undefined
316
325
  };
317
326
 
318
327
  let panel_positions = ["platform-summary"];
@@ -461,7 +470,9 @@ let guards = [
461
470
  recordOnly: [
462
471
  "extensionPoints",
463
472
  "requiredStores",
464
- "requiredStoreDeclarations"
473
+ "requiredStoreDeclarations",
474
+ "requiredCapabilities",
475
+ "traitDeclarations"
465
476
  ]
466
477
  },
467
478
  {
@@ -488,6 +499,14 @@ let guards = [
488
499
  wireOnly: [],
489
500
  recordOnly: []
490
501
  },
502
+ {
503
+ gqlType: "Platform_RequiredCapabilityDeclaration",
504
+ sdlTypes: Platform_PluginStructuresApi$ReventlessCore.sdlTypes,
505
+ recordFields: schemaKeys(Plugin$Reventless.requiredCapabilityDeclarationSchema),
506
+ wireFields: jsonKeys(Platform_PluginStructuresApi$ReventlessCore.encodeRequiredCapabilityDeclaration(capabilityDecl)),
507
+ wireOnly: [],
508
+ recordOnly: []
509
+ },
491
510
  {
492
511
  gqlType: "Platform_PluginStructureEntry",
493
512
  sdlTypes: Platform_PluginStructuresApi$ReventlessCore.sdlTypes,
@@ -584,6 +603,7 @@ export {
584
603
  extension,
585
604
  extensionPoint,
586
605
  storeDecl,
606
+ capabilityDecl,
587
607
  structure,
588
608
  panel,
589
609
  menu,
@@ -111,13 +111,18 @@ describe("the admin structure's declared transitions are checked", () => {
111
111
 
112
112
  // The drift that made deriving these defs worth doing rather than tidy. The
113
113
  // hand-written arg schema described only `id`, while the SDL generated off the
114
- // same `commandSchema` declares `Platform_Plugin_Activate(_0: String!, id: ID!)`.
114
+ // same `commandSchema` declares `Platform_Plugin_Activate(id: ID!, version: String!)`.
115
115
  // AutoUI builds its mutation document from the published schema, so it sent a
116
116
  // document missing a required argument and every admin row action was rejected at
117
117
  // GraphQL validation, before the aggregate saw it.
118
118
  describe("the Plugin command defs carry every argument the SDL requires", () => {
119
- testSync("the version argument is published", () =>
120
- expect(activate().schema->String.includes(`"_0"`))->toEqual(true)
119
+ // Named, not positional. A caller can match `version` against a field it holds;
120
+ // `_0` is a name only the encoder ever chose.
121
+ testSync("the version argument is published, under its own name", () =>
122
+ expect((
123
+ activate().schema->String.includes(`"version"`),
124
+ activate().schema->String.includes(`"_0"`),
125
+ ))->toEqual((true, false))
121
126
  )
122
127
 
123
128
  testSync("and so is the aggregate id", () => {
@@ -101,8 +101,14 @@ globalThis.describe("the admin structure's declared transitions are checked", ()
101
101
  });
102
102
 
103
103
  globalThis.describe("the Plugin command defs carry every argument the SDL requires", () => {
104
- globalThis.test("the version argument is published", () => {
105
- globalThis.expect(commandNamed("Activate").schema.includes(`"_0"`)).toEqual(true);
104
+ globalThis.test("the version argument is published, under its own name", () => {
105
+ globalThis.expect([
106
+ commandNamed("Activate").schema.includes(`"version"`),
107
+ commandNamed("Activate").schema.includes(`"_0"`)
108
+ ]).toEqual([
109
+ true,
110
+ false
111
+ ]);
106
112
  });
107
113
  globalThis.test("and so is the aggregate id", () => {
108
114
  globalThis.expect(commandNamed("Activate").aggregateIdField).toEqual(undefined);
@@ -85,6 +85,8 @@ let structure: pluginStructure = {
85
85
  extensionPoints: None,
86
86
  requiredStores: None,
87
87
  requiredStoreDeclarations: None,
88
+ requiredCapabilities: None,
89
+ traitDeclarations: None,
88
90
  }
89
91
 
90
92
  let structures = [("Ordering@1.0.0", structure)]
@@ -100,7 +100,9 @@ let structure = {
100
100
  extensions: structure_extensions,
101
101
  extensionPoints: undefined,
102
102
  requiredStores: undefined,
103
- requiredStoreDeclarations: undefined
103
+ requiredStoreDeclarations: undefined,
104
+ requiredCapabilities: undefined,
105
+ traitDeclarations: undefined
104
106
  };
105
107
 
106
108
  let structures = [[
@@ -105,6 +105,8 @@ let structure: pluginStructure = {
105
105
  extensionPoints: None,
106
106
  requiredStores: None,
107
107
  requiredStoreDeclarations: None,
108
+ requiredCapabilities: None,
109
+ traitDeclarations: None,
108
110
  }
109
111
 
110
112
  let encoded = Platform_ComponentDefinitionsApi.encodePluginStructureEntry(
@@ -402,6 +404,8 @@ describe("allowedStates + lifecycleField populated", () => {
402
404
  extensionPoints: None,
403
405
  requiredStores: None,
404
406
  requiredStoreDeclarations: None,
407
+ requiredCapabilities: None,
408
+ traitDeclarations: None,
405
409
  }
406
410
 
407
411
  let json =
@@ -155,7 +155,9 @@ let structure = {
155
155
  extensions: structure_extensions,
156
156
  extensionPoints: undefined,
157
157
  requiredStores: undefined,
158
- requiredStoreDeclarations: undefined
158
+ requiredStoreDeclarations: undefined,
159
+ requiredCapabilities: undefined,
160
+ traitDeclarations: undefined
159
161
  };
160
162
 
161
163
  let encoded = Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Catalog", undefined, structure);
@@ -264,7 +266,9 @@ globalThis.describe("translation-slice externalSystem round-trip", () => {
264
266
  extensions: externalStructure_extensions,
265
267
  extensionPoints: undefined,
266
268
  requiredStores: undefined,
267
- requiredStoreDeclarations: undefined
269
+ requiredStoreDeclarations: undefined,
270
+ requiredCapabilities: undefined,
271
+ traitDeclarations: undefined
268
272
  };
269
273
  let externalJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Catalog", undefined, externalStructure));
270
274
  globalThis.test("surfaces Some outbound externalSystem as its inner string", () => {
@@ -356,7 +360,9 @@ globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", ()
356
360
  extensions: mixed_extensions,
357
361
  extensionPoints: undefined,
358
362
  requiredStores: undefined,
359
- requiredStoreDeclarations: undefined
363
+ requiredStoreDeclarations: undefined,
364
+ requiredCapabilities: undefined,
365
+ traitDeclarations: undefined
360
366
  };
361
367
  let mixedEntry = Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Ordering", undefined, mixed);
362
368
  let namesAt = (entry, key) => Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(entry), o => o[key]), Stdlib_JSON.Decode.array), []), c => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(c), o => o["name"]), Stdlib_JSON.Decode.string));
@@ -385,7 +391,9 @@ globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", ()
385
391
  extensions: structure_extensions,
386
392
  extensionPoints: undefined,
387
393
  requiredStores: undefined,
388
- requiredStoreDeclarations: undefined
394
+ requiredStoreDeclarations: undefined,
395
+ requiredCapabilities: undefined,
396
+ traitDeclarations: undefined
389
397
  });
390
398
  globalThis.expect(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(entry), o => o["internalQueryables"])).toEqual([]);
391
399
  });
@@ -477,7 +485,9 @@ globalThis.describe("allowedStates + lifecycleField populated", () => {
477
485
  extensions: structureWithStates_extensions,
478
486
  extensionPoints: undefined,
479
487
  requiredStores: undefined,
480
- requiredStoreDeclarations: undefined
488
+ requiredStoreDeclarations: undefined,
489
+ requiredCapabilities: undefined,
490
+ traitDeclarations: undefined
481
491
  };
482
492
  let json = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Platform", undefined, structureWithStates));
483
493
  globalThis.test("encodes populated allowedStates as a JSON array", () => {
@@ -532,7 +542,9 @@ globalThis.describe("singleQueryField reaches the caller", () => {
532
542
  extensions: structure_extensions,
533
543
  extensionPoints: undefined,
534
544
  requiredStores: undefined,
535
- requiredStoreDeclarations: undefined
545
+ requiredStoreDeclarations: undefined,
546
+ requiredCapabilities: undefined,
547
+ traitDeclarations: undefined
536
548
  }));
537
549
  globalThis.expect(unstated.includes("\"singleQueryField\":null")).toEqual(true);
538
550
  });
@@ -580,7 +592,9 @@ globalThis.describe("idField / idFieldSource reach the caller", () => {
580
592
  extensions: structure_extensions,
581
593
  extensionPoints: undefined,
582
594
  requiredStores: undefined,
583
- requiredStoreDeclarations: undefined
595
+ requiredStoreDeclarations: undefined,
596
+ requiredCapabilities: undefined,
597
+ traitDeclarations: undefined
584
598
  }));
585
599
  globalThis.expect(unresolved.includes("\"idField\":null")).toEqual(true);
586
600
  globalThis.expect(unresolved.includes("\"idFieldSource\":null")).toEqual(true);
@@ -67,6 +67,8 @@ let structure: pluginStructure = {
67
67
  requiredStoreDeclarations: Some([
68
68
  {store: "Catalog.images", component: "Product", field: "image", annotation: Some("images")},
69
69
  ]),
70
+ requiredCapabilities: Some([{capability: "Geocoding", component: "GeocodeAddress"}]),
71
+ traitDeclarations: None,
70
72
  }
71
73
 
72
74
  let encoded = Platform_PluginStructuresApi.encodePluginStructureEntry(
@@ -98,10 +100,12 @@ describe("Platform_PluginStructures entry", () => {
98
100
  "outboundTranslationSlices",
99
101
  "pluginId",
100
102
  "readModels",
103
+ "requiredCapabilities",
101
104
  "requiredStoreDeclarations",
102
105
  "requiredStores",
103
106
  "stateChangeSlices",
104
107
  "stateViewSlices",
108
+ "traitDeclarations",
105
109
  ])
106
110
  })
107
111
 
@@ -142,6 +146,16 @@ describe("Platform_PluginStructures entry", () => {
142
146
  testSync("encodes required stores with their provenance", () =>
143
147
  expect(json->String.includes("\"requiredStores\":[\"Catalog.images\"]"))->toEqual(true)
144
148
  )
149
+
150
+ // No `field`: a capability a slice declares is not a field's need, which is why
151
+ // it travels beside `requiredStores` rather than inside it.
152
+ testSync("encodes required capabilities with their declaring component", () =>
153
+ expect(
154
+ json->String.includes(
155
+ "\"requiredCapabilities\":[{\"capability\":\"Geocoding\",\"component\":\"GeocodeAddress\"}]",
156
+ ),
157
+ )->toEqual(true)
158
+ )
145
159
  })
146
160
 
147
161
  // A structure persisted before these fields existed decodes as None, and null is the
@@ -153,6 +167,8 @@ describe("absent optional collections", () => {
153
167
  extensionPoints: None,
154
168
  requiredStores: None,
155
169
  requiredStoreDeclarations: None,
170
+ requiredCapabilities: None,
171
+ traitDeclarations: None,
156
172
  }
157
173
  let bareJson =
158
174
  Platform_PluginStructuresApi.encodePluginStructureEntry(
@@ -127,6 +127,11 @@ let structure_requiredStoreDeclarations = [{
127
127
  annotation: "images"
128
128
  }];
129
129
 
130
+ let structure_requiredCapabilities = [{
131
+ capability: "Geocoding",
132
+ component: "GeocodeAddress"
133
+ }];
134
+
130
135
  let structure = {
131
136
  readModels: structure_readModels,
132
137
  stateViewSlices: structure_stateViewSlices,
@@ -138,7 +143,9 @@ let structure = {
138
143
  extensions: structure_extensions,
139
144
  extensionPoints: structure_extensionPoints,
140
145
  requiredStores: structure_requiredStores,
141
- requiredStoreDeclarations: structure_requiredStoreDeclarations
146
+ requiredStoreDeclarations: structure_requiredStoreDeclarations,
147
+ requiredCapabilities: structure_requiredCapabilities,
148
+ traitDeclarations: undefined
142
149
  };
143
150
 
144
151
  let encoded = Platform_PluginStructuresApi$ReventlessCore.encodePluginStructureEntry("Catalog", structure);
@@ -162,10 +169,12 @@ globalThis.describe("Platform_PluginStructures entry", () => {
162
169
  "outboundTranslationSlices",
163
170
  "pluginId",
164
171
  "readModels",
172
+ "requiredCapabilities",
165
173
  "requiredStoreDeclarations",
166
174
  "requiredStores",
167
175
  "stateChangeSlices",
168
- "stateViewSlices"
176
+ "stateViewSlices",
177
+ "traitDeclarations"
169
178
  ]);
170
179
  });
171
180
  globalThis.test("keeps Internal read models", () => {
@@ -192,6 +201,9 @@ globalThis.describe("Platform_PluginStructures entry", () => {
192
201
  globalThis.test("encodes required stores with their provenance", () => {
193
202
  globalThis.expect(json.includes("\"requiredStores\":[\"Catalog.images\"]")).toEqual(true);
194
203
  });
204
+ globalThis.test("encodes required capabilities with their declaring component", () => {
205
+ globalThis.expect(json.includes("\"requiredCapabilities\":[{\"capability\":\"Geocoding\",\"component\":\"GeocodeAddress\"}]")).toEqual(true);
206
+ });
195
207
  });
196
208
 
197
209
  globalThis.describe("absent optional collections", () => {
@@ -214,7 +226,9 @@ globalThis.describe("absent optional collections", () => {
214
226
  extensions: bare_extensions,
215
227
  extensionPoints: undefined,
216
228
  requiredStores: undefined,
217
- requiredStoreDeclarations: undefined
229
+ requiredStoreDeclarations: undefined,
230
+ requiredCapabilities: undefined,
231
+ traitDeclarations: undefined
218
232
  };
219
233
  let bareJson = JSON.stringify(Platform_PluginStructuresApi$ReventlessCore.encodePluginStructureEntry("Catalog", bare));
220
234
  globalThis.test("encodes absent extensionPoints as null", () => {
@@ -37,6 +37,12 @@ function commandAuthorization(param) {
37
37
  return "AllowAuthenticated";
38
38
  }
39
39
 
40
+ function commandTransition(param) {
41
+ return "Unrestricted";
42
+ }
43
+
44
+ let traits = [];
45
+
40
46
  let AggSpec = {
41
47
  Id: undefined,
42
48
  name: name,
@@ -44,7 +50,9 @@ let AggSpec = {
44
50
  eventSchema: eventSchema,
45
51
  errorSchema: errorSchema,
46
52
  moduleUrl: moduleUrl,
47
- commandAuthorization: commandAuthorization
53
+ commandAuthorization: commandAuthorization,
54
+ commandTransition: commandTransition,
55
+ traits: traits
48
56
  };
49
57
 
50
58
  let initialState = {
@@ -237,7 +245,9 @@ let TestHandler = Aggregate_Callback$ReventlessCore.Make({
237
245
  errorSchema: errorSchema,
238
246
  commandSchema: commandSchema,
239
247
  moduleUrl: moduleUrl,
240
- commandAuthorization: commandAuthorization
248
+ commandAuthorization: commandAuthorization,
249
+ commandTransition: commandTransition,
250
+ traits: traits
241
251
  })({
242
252
  initialState: initialState,
243
253
  evolve: evolve,
@@ -258,7 +268,9 @@ let TestHandler = Aggregate_Callback$ReventlessCore.Make({
258
268
  errorSchema: errorSchema,
259
269
  commandSchema: commandSchema,
260
270
  moduleUrl: moduleUrl,
261
- commandAuthorization: commandAuthorization
271
+ commandAuthorization: commandAuthorization,
272
+ commandTransition: commandTransition,
273
+ traits: traits
262
274
  },
263
275
  EventLog: {
264
276
  Spec: {