@reventlessdev/reventless-core 3.0.0-alpha.250 → 3.0.0-alpha.252

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 (70) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/package.json +7 -7
  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_ComponentDefinitionsApi.res +31 -1
  7. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +29 -1
  8. package/src/admin/Platform_PluginStructuresApi.res +80 -8
  9. package/src/admin/Platform_PluginStructuresApi.res.mjs +76 -2
  10. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +8 -0
  11. package/src/components/Api/ApiNoApiHelpers.res +37 -3
  12. package/src/components/Api/ApiNoApiHelpers.res.mjs +29 -2
  13. package/src/components/AutomationSlice/AutomationSlice_Callback.res +4 -4
  14. package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +3 -3
  15. package/src/plugin/api/PluginBaseFragment.res +3 -3
  16. package/src/plugin/component/Plugin_Structure.res +787 -384
  17. package/src/plugin/component/Plugin_Structure.res.mjs +430 -20
  18. package/src/plugin/connect/PluginConnectExtension_Mapping.res +3 -6
  19. package/src/plugin/connect/PluginConnectExtension_Mapping.res.mjs +14 -2
  20. package/src/plugin/connect/PluginExtensionPoint_Plugin.res +0 -1
  21. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +63 -3
  22. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +22 -3
  23. package/src/plugin/lifecycle/PluginBehavior.res +11 -11
  24. package/src/plugin/lifecycle/PluginBehavior.res.mjs +11 -11
  25. package/src/plugin/lifecycle/PluginSpec.res +11 -3
  26. package/src/plugin/lifecycle/PluginSpec.res.mjs +11 -3
  27. package/tests/admin/AdminApiSchemaDriftTest.res +486 -0
  28. package/tests/admin/AdminApiSchemaDriftTest.res.mjs +619 -0
  29. package/tests/admin/Platform_Admin_StructureTest.res +8 -3
  30. package/tests/admin/Platform_Admin_StructureTest.res.mjs +8 -2
  31. package/tests/admin/Platform_BakedManifestTest.res +2 -0
  32. package/tests/admin/Platform_BakedManifestTest.res.mjs +3 -1
  33. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +10 -0
  34. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +30 -8
  35. package/tests/admin/Platform_PluginStructuresApiTest.res +28 -1
  36. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +27 -5
  37. package/tests/aggregate/AggregateCacheTest.res.mjs +15 -3
  38. package/tests/aggregate/AggregateConflictTest.res.mjs +15 -3
  39. package/tests/aggregate/AggregateFixtures.res.mjs +15 -3
  40. package/tests/aggregate/AggregateSnapshotTest.res.mjs +21 -5
  41. package/tests/api/ApiNoApiTest.res +46 -0
  42. package/tests/api/ApiNoApiTest.res.mjs +49 -1
  43. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +27 -3
  44. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +12 -2
  45. package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +2 -0
  46. package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +12 -2
  47. package/tests/dcb/DcbFixtures.res.mjs +8 -0
  48. package/tests/dcb/DcbStateChangeSliceTest.res.mjs +18 -0
  49. package/tests/eventmapper/EventMapperFixtures.res.mjs +15 -3
  50. package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +9 -1
  51. package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +9 -1
  52. package/tests/message/MessageTest.res +2 -0
  53. package/tests/message/MessageTest.res.mjs +3 -1
  54. package/tests/plugin/PluginStructureAccessTest.res.mjs +2 -0
  55. package/tests/plugin/PluginStructureTest.res +294 -1
  56. package/tests/plugin/PluginStructureTest.res.mjs +373 -3
  57. package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +8 -0
  58. package/tests/plugin/StateChangeSlice/PsChangePhoto.res.mjs +8 -0
  59. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res.mjs +8 -0
  60. package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +8 -0
  61. package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +8 -0
  62. package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +8 -0
  63. package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +8 -0
  64. package/tests/plugin/StateChangeSlice/PsTransitionSwitch.res +59 -0
  65. package/tests/plugin/StateChangeSlice/PsTransitionSwitch.res.mjs +115 -0
  66. package/tests/plugin/StateChangeSlice/PsTypoStore.res.mjs +8 -0
  67. package/tests/plugin/StateChangeSlice/PsUploadAvatar.res.mjs +8 -0
  68. package/tests/plugin/StateChangeSlice/PsUploadImages.res.mjs +8 -0
  69. package/tests/plugin/pluginDefinitionRequiredScalars.txt +26 -0
  70. package/tests/util/CommandPublisherTest.res.mjs +9 -1
@@ -94,11 +94,19 @@ let structure: pluginStructure = {
94
94
  delegateNames: ["onAdded"],
95
95
  eventTypes: ["ProductAdded"],
96
96
  commandTypes: [],
97
+ handledEvents: Some([
98
+ {name: "Catalog.Products.ProductAdded", toCommandTypes: ["Catalog.Watch"]},
99
+ ]),
100
+ issuedCommands: Some([
101
+ {name: "Catalog.Products.Acknowledge", fromEventTypes: ["Catalog.Watched"]},
102
+ ]),
97
103
  },
98
104
  ],
99
105
  extensionPoints: None,
100
106
  requiredStores: None,
101
107
  requiredStoreDeclarations: None,
108
+ requiredCapabilities: None,
109
+ traitDeclarations: None,
102
110
  }
103
111
 
104
112
  let encoded = Platform_ComponentDefinitionsApi.encodePluginStructureEntry(
@@ -396,6 +404,8 @@ describe("allowedStates + lifecycleField populated", () => {
396
404
  extensionPoints: None,
397
405
  requiredStores: None,
398
406
  requiredStoreDeclarations: None,
407
+ requiredCapabilities: None,
408
+ traitDeclarations: None,
399
409
  }
400
410
 
401
411
  let json =
@@ -133,7 +133,15 @@ let structure_extensions = [{
133
133
  name: "Catalog.Products.Watcher",
134
134
  delegateNames: ["onAdded"],
135
135
  eventTypes: ["ProductAdded"],
136
- commandTypes: []
136
+ commandTypes: [],
137
+ handledEvents: [{
138
+ name: "Catalog.Products.ProductAdded",
139
+ toCommandTypes: ["Catalog.Watch"]
140
+ }],
141
+ issuedCommands: [{
142
+ name: "Catalog.Products.Acknowledge",
143
+ fromEventTypes: ["Catalog.Watched"]
144
+ }]
137
145
  }];
138
146
 
139
147
  let structure = {
@@ -147,7 +155,9 @@ let structure = {
147
155
  extensions: structure_extensions,
148
156
  extensionPoints: undefined,
149
157
  requiredStores: undefined,
150
- requiredStoreDeclarations: undefined
158
+ requiredStoreDeclarations: undefined,
159
+ requiredCapabilities: undefined,
160
+ traitDeclarations: undefined
151
161
  };
152
162
 
153
163
  let encoded = Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Catalog", undefined, structure);
@@ -256,7 +266,9 @@ globalThis.describe("translation-slice externalSystem round-trip", () => {
256
266
  extensions: externalStructure_extensions,
257
267
  extensionPoints: undefined,
258
268
  requiredStores: undefined,
259
- requiredStoreDeclarations: undefined
269
+ requiredStoreDeclarations: undefined,
270
+ requiredCapabilities: undefined,
271
+ traitDeclarations: undefined
260
272
  };
261
273
  let externalJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Catalog", undefined, externalStructure));
262
274
  globalThis.test("surfaces Some outbound externalSystem as its inner string", () => {
@@ -348,7 +360,9 @@ globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", ()
348
360
  extensions: mixed_extensions,
349
361
  extensionPoints: undefined,
350
362
  requiredStores: undefined,
351
- requiredStoreDeclarations: undefined
363
+ requiredStoreDeclarations: undefined,
364
+ requiredCapabilities: undefined,
365
+ traitDeclarations: undefined
352
366
  };
353
367
  let mixedEntry = Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Ordering", undefined, mixed);
354
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));
@@ -377,7 +391,9 @@ globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", ()
377
391
  extensions: structure_extensions,
378
392
  extensionPoints: undefined,
379
393
  requiredStores: undefined,
380
- requiredStoreDeclarations: undefined
394
+ requiredStoreDeclarations: undefined,
395
+ requiredCapabilities: undefined,
396
+ traitDeclarations: undefined
381
397
  });
382
398
  globalThis.expect(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(entry), o => o["internalQueryables"])).toEqual([]);
383
399
  });
@@ -469,7 +485,9 @@ globalThis.describe("allowedStates + lifecycleField populated", () => {
469
485
  extensions: structureWithStates_extensions,
470
486
  extensionPoints: undefined,
471
487
  requiredStores: undefined,
472
- requiredStoreDeclarations: undefined
488
+ requiredStoreDeclarations: undefined,
489
+ requiredCapabilities: undefined,
490
+ traitDeclarations: undefined
473
491
  };
474
492
  let json = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Platform", undefined, structureWithStates));
475
493
  globalThis.test("encodes populated allowedStates as a JSON array", () => {
@@ -524,7 +542,9 @@ globalThis.describe("singleQueryField reaches the caller", () => {
524
542
  extensions: structure_extensions,
525
543
  extensionPoints: undefined,
526
544
  requiredStores: undefined,
527
- requiredStoreDeclarations: undefined
545
+ requiredStoreDeclarations: undefined,
546
+ requiredCapabilities: undefined,
547
+ traitDeclarations: undefined
528
548
  }));
529
549
  globalThis.expect(unstated.includes("\"singleQueryField\":null")).toEqual(true);
530
550
  });
@@ -572,7 +592,9 @@ globalThis.describe("idField / idFieldSource reach the caller", () => {
572
592
  extensions: structure_extensions,
573
593
  extensionPoints: undefined,
574
594
  requiredStores: undefined,
575
- requiredStoreDeclarations: undefined
595
+ requiredStoreDeclarations: undefined,
596
+ requiredCapabilities: undefined,
597
+ traitDeclarations: undefined
576
598
  }));
577
599
  globalThis.expect(unresolved.includes("\"idField\":null")).toEqual(true);
578
600
  globalThis.expect(unresolved.includes("\"idFieldSource\":null")).toEqual(true);
@@ -52,12 +52,23 @@ let structure: pluginStructure = {
52
52
  delegateNames: ["onAdded"],
53
53
  sourceEventTypes: ["Catalog.ProductAdded"],
54
54
  commandTypes: None,
55
+ publishedEvents: Some([
56
+ {
57
+ name: "Catalog.Products.ProductBecameAvailable",
58
+ fromEventTypes: ["Catalog.ProductAdded"],
59
+ },
60
+ ]),
61
+ acceptedCommands: Some([
62
+ {name: "Catalog.Products.Reserve", toCommandTypes: ["Catalog.ReserveStock"]},
63
+ ]),
55
64
  },
56
65
  ]),
57
66
  requiredStores: Some(["Catalog.images"]),
58
67
  requiredStoreDeclarations: Some([
59
68
  {store: "Catalog.images", component: "Product", field: "image", annotation: Some("images")},
60
69
  ]),
70
+ requiredCapabilities: Some([{capability: "Geocoding", component: "GeocodeAddress"}]),
71
+ traitDeclarations: None,
61
72
  }
62
73
 
63
74
  let encoded = Platform_PluginStructuresApi.encodePluginStructureEntry(
@@ -89,10 +100,12 @@ describe("Platform_PluginStructures entry", () => {
89
100
  "outboundTranslationSlices",
90
101
  "pluginId",
91
102
  "readModels",
103
+ "requiredCapabilities",
92
104
  "requiredStoreDeclarations",
93
105
  "requiredStores",
94
106
  "stateChangeSlices",
95
107
  "stateViewSlices",
108
+ "traitDeclarations",
96
109
  ])
97
110
  })
98
111
 
@@ -116,7 +129,9 @@ describe("Platform_PluginStructures entry", () => {
116
129
  testSync("encodes extension points with their source event types", () =>
117
130
  expect(
118
131
  json->String.includes(
119
- "\"extensionPoints\":[{\"name\":\"Catalog.Products\",\"delegateNames\":[\"onAdded\"],\"sourceEventTypes\":[\"Catalog.ProductAdded\"],\"commandTypes\":null}]",
132
+ "\"extensionPoints\":[{\"name\":\"Catalog.Products\",\"delegateNames\":[\"onAdded\"],\"sourceEventTypes\":[\"Catalog.ProductAdded\"],\"commandTypes\":null," ++
133
+ "\"publishedEvents\":[{\"name\":\"Catalog.Products.ProductBecameAvailable\",\"fromEventTypes\":[\"Catalog.ProductAdded\"]}]," ++
134
+ "\"acceptedCommands\":[{\"name\":\"Catalog.Products.Reserve\",\"toCommandTypes\":[\"Catalog.ReserveStock\"]}]}]",
120
135
  ),
121
136
  )->toEqual(true)
122
137
  )
@@ -131,6 +146,16 @@ describe("Platform_PluginStructures entry", () => {
131
146
  testSync("encodes required stores with their provenance", () =>
132
147
  expect(json->String.includes("\"requiredStores\":[\"Catalog.images\"]"))->toEqual(true)
133
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
+ )
134
159
  })
135
160
 
136
161
  // A structure persisted before these fields existed decodes as None, and null is the
@@ -142,6 +167,8 @@ describe("absent optional collections", () => {
142
167
  extensionPoints: None,
143
168
  requiredStores: None,
144
169
  requiredStoreDeclarations: None,
170
+ requiredCapabilities: None,
171
+ traitDeclarations: None,
145
172
  }
146
173
  let bareJson =
147
174
  Platform_PluginStructuresApi.encodePluginStructureEntry(
@@ -107,7 +107,15 @@ let structure_extensionPoints = [{
107
107
  name: "Catalog.Products",
108
108
  delegateNames: ["onAdded"],
109
109
  sourceEventTypes: ["Catalog.ProductAdded"],
110
- commandTypes: undefined
110
+ commandTypes: undefined,
111
+ publishedEvents: [{
112
+ name: "Catalog.Products.ProductBecameAvailable",
113
+ fromEventTypes: ["Catalog.ProductAdded"]
114
+ }],
115
+ acceptedCommands: [{
116
+ name: "Catalog.Products.Reserve",
117
+ toCommandTypes: ["Catalog.ReserveStock"]
118
+ }]
111
119
  }];
112
120
 
113
121
  let structure_requiredStores = ["Catalog.images"];
@@ -119,6 +127,11 @@ let structure_requiredStoreDeclarations = [{
119
127
  annotation: "images"
120
128
  }];
121
129
 
130
+ let structure_requiredCapabilities = [{
131
+ capability: "Geocoding",
132
+ component: "GeocodeAddress"
133
+ }];
134
+
122
135
  let structure = {
123
136
  readModels: structure_readModels,
124
137
  stateViewSlices: structure_stateViewSlices,
@@ -130,7 +143,9 @@ let structure = {
130
143
  extensions: structure_extensions,
131
144
  extensionPoints: structure_extensionPoints,
132
145
  requiredStores: structure_requiredStores,
133
- requiredStoreDeclarations: structure_requiredStoreDeclarations
146
+ requiredStoreDeclarations: structure_requiredStoreDeclarations,
147
+ requiredCapabilities: structure_requiredCapabilities,
148
+ traitDeclarations: undefined
134
149
  };
135
150
 
136
151
  let encoded = Platform_PluginStructuresApi$ReventlessCore.encodePluginStructureEntry("Catalog", structure);
@@ -154,10 +169,12 @@ globalThis.describe("Platform_PluginStructures entry", () => {
154
169
  "outboundTranslationSlices",
155
170
  "pluginId",
156
171
  "readModels",
172
+ "requiredCapabilities",
157
173
  "requiredStoreDeclarations",
158
174
  "requiredStores",
159
175
  "stateChangeSlices",
160
- "stateViewSlices"
176
+ "stateViewSlices",
177
+ "traitDeclarations"
161
178
  ]);
162
179
  });
163
180
  globalThis.test("keeps Internal read models", () => {
@@ -176,7 +193,7 @@ globalThis.describe("Platform_PluginStructures entry", () => {
176
193
  globalThis.expect(json.includes("\"chapter\":\"Inventory\"")).toEqual(true);
177
194
  });
178
195
  globalThis.test("encodes extension points with their source event types", () => {
179
- globalThis.expect(json.includes("\"extensionPoints\":[{\"name\":\"Catalog.Products\",\"delegateNames\":[\"onAdded\"],\"sourceEventTypes\":[\"Catalog.ProductAdded\"],\"commandTypes\":null}]")).toEqual(true);
196
+ globalThis.expect(json.includes("\"extensionPoints\":[{\"name\":\"Catalog.Products\",\"delegateNames\":[\"onAdded\"],\"sourceEventTypes\":[\"Catalog.ProductAdded\"],\"commandTypes\":null,\"publishedEvents\":[{\"name\":\"Catalog.Products.ProductBecameAvailable\",\"fromEventTypes\":[\"Catalog.ProductAdded\"]}],\"acceptedCommands\":[{\"name\":\"Catalog.Products.Reserve\",\"toCommandTypes\":[\"Catalog.ReserveStock\"]}]}]")).toEqual(true);
180
197
  });
181
198
  globalThis.test("encodes the singular query field beside the list one", () => {
182
199
  globalThis.expect(json.includes("\"singleQueryField\":\"Catalog_Product\"")).toEqual(true);
@@ -184,6 +201,9 @@ globalThis.describe("Platform_PluginStructures entry", () => {
184
201
  globalThis.test("encodes required stores with their provenance", () => {
185
202
  globalThis.expect(json.includes("\"requiredStores\":[\"Catalog.images\"]")).toEqual(true);
186
203
  });
204
+ globalThis.test("encodes required capabilities with their declaring component", () => {
205
+ globalThis.expect(json.includes("\"requiredCapabilities\":[{\"capability\":\"Geocoding\",\"component\":\"GeocodeAddress\"}]")).toEqual(true);
206
+ });
187
207
  });
188
208
 
189
209
  globalThis.describe("absent optional collections", () => {
@@ -206,7 +226,9 @@ globalThis.describe("absent optional collections", () => {
206
226
  extensions: bare_extensions,
207
227
  extensionPoints: undefined,
208
228
  requiredStores: undefined,
209
- requiredStoreDeclarations: undefined
229
+ requiredStoreDeclarations: undefined,
230
+ requiredCapabilities: undefined,
231
+ traitDeclarations: undefined
210
232
  };
211
233
  let bareJson = JSON.stringify(Platform_PluginStructuresApi$ReventlessCore.encodePluginStructureEntry("Catalog", bare));
212
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: {
@@ -26,6 +26,12 @@ function commandAuthorization(param) {
26
26
  return "AllowAuthenticated";
27
27
  }
28
28
 
29
+ function commandTransition(param) {
30
+ return "Unrestricted";
31
+ }
32
+
33
+ let traits = [];
34
+
29
35
  let AggSpec = {
30
36
  Id: undefined,
31
37
  name: name,
@@ -33,7 +39,9 @@ let AggSpec = {
33
39
  eventSchema: eventSchema,
34
40
  errorSchema: errorSchema,
35
41
  moduleUrl: moduleUrl,
36
- commandAuthorization: commandAuthorization
42
+ commandAuthorization: commandAuthorization,
43
+ commandTransition: commandTransition,
44
+ traits: traits
37
45
  };
38
46
 
39
47
  let initialState = {
@@ -194,7 +202,9 @@ let TestHandler = Aggregate_Callback$ReventlessCore.Make({
194
202
  errorSchema: errorSchema,
195
203
  commandSchema: commandSchema,
196
204
  moduleUrl: moduleUrl,
197
- commandAuthorization: commandAuthorization
205
+ commandAuthorization: commandAuthorization,
206
+ commandTransition: commandTransition,
207
+ traits: traits
198
208
  })({
199
209
  initialState: initialState,
200
210
  evolve: evolve,
@@ -215,7 +225,9 @@ let TestHandler = Aggregate_Callback$ReventlessCore.Make({
215
225
  errorSchema: errorSchema,
216
226
  commandSchema: commandSchema,
217
227
  moduleUrl: moduleUrl,
218
- commandAuthorization: commandAuthorization
228
+ commandAuthorization: commandAuthorization,
229
+ commandTransition: commandTransition,
230
+ traits: traits
219
231
  },
220
232
  EventLog: {
221
233
  Spec: {
@@ -40,6 +40,12 @@ function commandAuthorization(param) {
40
40
  return "AllowAuthenticated";
41
41
  }
42
42
 
43
+ function commandTransition(param) {
44
+ return "Unrestricted";
45
+ }
46
+
47
+ let traits = [];
48
+
43
49
  let AggSpec = {
44
50
  Id: undefined,
45
51
  name: name,
@@ -47,7 +53,9 @@ let AggSpec = {
47
53
  eventSchema: eventSchema,
48
54
  errorSchema: errorSchema,
49
55
  moduleUrl: moduleUrl,
50
- commandAuthorization: commandAuthorization
56
+ commandAuthorization: commandAuthorization,
57
+ commandTransition: commandTransition,
58
+ traits: traits
51
59
  };
52
60
 
53
61
  let stateSchema = Sury.union([
@@ -229,7 +237,9 @@ let TestHandler = Aggregate_Callback$ReventlessCore.Make({
229
237
  errorSchema: errorSchema,
230
238
  commandSchema: commandSchema,
231
239
  moduleUrl: moduleUrl,
232
- commandAuthorization: commandAuthorization
240
+ commandAuthorization: commandAuthorization,
241
+ commandTransition: commandTransition,
242
+ traits: traits
233
243
  })({
234
244
  initialState: "NotCreated",
235
245
  evolve: evolve,
@@ -250,7 +260,9 @@ let TestHandler = Aggregate_Callback$ReventlessCore.Make({
250
260
  errorSchema: errorSchema,
251
261
  commandSchema: commandSchema,
252
262
  moduleUrl: moduleUrl,
253
- commandAuthorization: commandAuthorization
263
+ commandAuthorization: commandAuthorization,
264
+ commandTransition: commandTransition,
265
+ traits: traits
254
266
  },
255
267
  EventLog: {
256
268
  Spec: {
@@ -32,6 +32,12 @@ function commandAuthorization(param) {
32
32
  return "AllowAuthenticated";
33
33
  }
34
34
 
35
+ function commandTransition(param) {
36
+ return "Unrestricted";
37
+ }
38
+
39
+ let traits = [];
40
+
35
41
  let AggSpec = {
36
42
  Id: undefined,
37
43
  name: name,
@@ -39,7 +45,9 @@ let AggSpec = {
39
45
  eventSchema: eventSchema,
40
46
  errorSchema: errorSchema,
41
47
  moduleUrl: moduleUrl,
42
- commandAuthorization: commandAuthorization
48
+ commandAuthorization: commandAuthorization,
49
+ commandTransition: commandTransition,
50
+ traits: traits
43
51
  };
44
52
 
45
53
  let stateSchema = Sury.$schema(s => ({
@@ -284,7 +292,9 @@ let TestHandler = Aggregate_Callback$ReventlessCore.Make({
284
292
  errorSchema: errorSchema,
285
293
  commandSchema: commandSchema,
286
294
  moduleUrl: moduleUrl,
287
- commandAuthorization: commandAuthorization
295
+ commandAuthorization: commandAuthorization,
296
+ commandTransition: commandTransition,
297
+ traits: traits
288
298
  })({
289
299
  initialState: initialState,
290
300
  evolve: evolve,
@@ -305,7 +315,9 @@ let TestHandler = Aggregate_Callback$ReventlessCore.Make({
305
315
  errorSchema: errorSchema,
306
316
  commandSchema: commandSchema,
307
317
  moduleUrl: moduleUrl,
308
- commandAuthorization: commandAuthorization
318
+ commandAuthorization: commandAuthorization,
319
+ commandTransition: commandTransition,
320
+ traits: traits
309
321
  },
310
322
  EventLog: {
311
323
  Spec: {
@@ -426,7 +438,9 @@ let PlainHandler = Aggregate_Callback$ReventlessCore.Make({
426
438
  errorSchema: errorSchema,
427
439
  commandSchema: commandSchema,
428
440
  moduleUrl: moduleUrl,
429
- commandAuthorization: commandAuthorization
441
+ commandAuthorization: commandAuthorization,
442
+ commandTransition: commandTransition,
443
+ traits: traits
430
444
  })({
431
445
  initialState: initialState$1,
432
446
  evolve: evolve$1,
@@ -447,7 +461,9 @@ let PlainHandler = Aggregate_Callback$ReventlessCore.Make({
447
461
  errorSchema: errorSchema,
448
462
  commandSchema: commandSchema,
449
463
  moduleUrl: moduleUrl,
450
- commandAuthorization: commandAuthorization
464
+ commandAuthorization: commandAuthorization,
465
+ commandTransition: commandTransition,
466
+ traits: traits
451
467
  },
452
468
  EventLog: {
453
469
  Spec: {
@@ -1,6 +1,52 @@
1
1
  open JestGlobals
2
2
  open ApiNoApiHelpers
3
3
 
4
+ // The exclusion has to survive a variant spread. Splicing copies a union's
5
+ // members, not its metadata, so an exclusion recorded only on the union vanishes
6
+ // on the way into the host — and the host publishes as a mutation and a
7
+ // subscription a command whose author marked it internal. No error, no warning.
8
+ //
9
+ // Built the way the PPX builds it (`markNoApiVariants` on a freshly made union),
10
+ // then spliced the way the compiler splices it (concatenating `anyOf`), so this
11
+ // exercises the real path rather than a hand-stamped stand-in.
12
+ let member = name =>
13
+ S.object(o => {
14
+ let _ = o.field("TAG", S.literal(name))
15
+ })->S.castToUnknown
16
+
17
+ let sourceUnion =
18
+ S.union([member("Public"), member("Internal")])->ReventlessInfra.Api.markNoApiVariants([
19
+ "Internal",
20
+ ])
21
+
22
+ let spliced = switch sourceUnion {
23
+ | AnyOf({anyOf}) => S.union(Array.concat([member("HostOwn")], anyOf))
24
+ | _ => sourceUnion
25
+ }
26
+
27
+ describe("a spread carries its exclusions", () => {
28
+ testSync("the source union still reports its own exclusion", () =>
29
+ expect(getExcludedVariants(sourceUnion)->Option.map(Set.toArray))->toEqual(Some(["Internal"]))
30
+ )
31
+
32
+ testSync("a host that splices the members inherits the exclusion", () =>
33
+ expect(getExcludedVariants(spliced)->Option.map(Set.toArray))->toEqual(Some(["Internal"]))
34
+ )
35
+
36
+ // The failure this closes, stated as the thing it must not do: the spliced
37
+ // internal variant must not appear among the host's callable fields.
38
+ testSync("the spliced internal variant is filtered out of a host's fields", () =>
39
+ expect(filterNoApiVariants(["HostOwn", "Public", "Internal"], spliced))->toEqual([
40
+ "HostOwn",
41
+ "Public",
42
+ ])
43
+ )
44
+
45
+ testSync("nothing else is filtered", () =>
46
+ expect(filterNoApiVariants(["HostOwn", "Public"], spliced))->toEqual(["HostOwn", "Public"])
47
+ )
48
+ })
49
+
4
50
  describe("ApiNoApiHelpers:", () => {
5
51
  testSync("isNoApi returns false for schema without noApi metadata", () => {
6
52
  let schema = S.string->S.castToUnknown
@@ -5,6 +5,49 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
5
  import * as Api$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/Api.res.mjs";
6
6
  import * as ApiNoApiHelpers$ReventlessCore from "../../src/components/Api/ApiNoApiHelpers.res.mjs";
7
7
 
8
+ function member(name) {
9
+ return Sury.object(o => {
10
+ o.f("TAG", Sury.literal(name));
11
+ });
12
+ }
13
+
14
+ let sourceUnion = Api$ReventlessInfra.markNoApiVariants(Sury.union([
15
+ member("Public"),
16
+ member("Internal")
17
+ ]), ["Internal"]);
18
+
19
+ let spliced;
20
+
21
+ spliced = sourceUnion.type === "anyOf" ? Sury.union([member("HostOwn")].concat(sourceUnion.anyOf)) : sourceUnion;
22
+
23
+ globalThis.describe("a spread carries its exclusions", () => {
24
+ globalThis.test("the source union still reports its own exclusion", () => {
25
+ globalThis.expect(Stdlib_Option.map(ApiNoApiHelpers$ReventlessCore.getExcludedVariants(sourceUnion), prim => Array.from(prim))).toEqual(["Internal"]);
26
+ });
27
+ globalThis.test("a host that splices the members inherits the exclusion", () => {
28
+ globalThis.expect(Stdlib_Option.map(ApiNoApiHelpers$ReventlessCore.getExcludedVariants(spliced), prim => Array.from(prim))).toEqual(["Internal"]);
29
+ });
30
+ globalThis.test("the spliced internal variant is filtered out of a host's fields", () => {
31
+ globalThis.expect(ApiNoApiHelpers$ReventlessCore.filterNoApiVariants([
32
+ "HostOwn",
33
+ "Public",
34
+ "Internal"
35
+ ], spliced)).toEqual([
36
+ "HostOwn",
37
+ "Public"
38
+ ]);
39
+ });
40
+ globalThis.test("nothing else is filtered", () => {
41
+ globalThis.expect(ApiNoApiHelpers$ReventlessCore.filterNoApiVariants([
42
+ "HostOwn",
43
+ "Public"
44
+ ], spliced)).toEqual([
45
+ "HostOwn",
46
+ "Public"
47
+ ]);
48
+ });
49
+ });
50
+
8
51
  globalThis.describe("ApiNoApiHelpers:", () => {
9
52
  globalThis.test("isNoApi returns false for schema without noApi metadata", () => {
10
53
  let schema = Sury.string;
@@ -95,4 +138,9 @@ globalThis.describe("ApiNoApiHelpers:", () => {
95
138
  });
96
139
  });
97
140
 
98
- /* Not a pure module */
141
+ export {
142
+ member,
143
+ sourceUnion,
144
+ spliced,
145
+ }
146
+ /* sourceUnion Not a pure module */
@@ -24,9 +24,17 @@ function commandAuthorization(param) {
24
24
  return "AllowAuthenticated";
25
25
  }
26
26
 
27
+ function commandTransition(param) {
28
+ return "Unrestricted";
29
+ }
30
+
31
+ let traits = [];
32
+
27
33
  let OrderSliceCmd = {
28
34
  commandSchema: commandSchema,
29
- commandAuthorization: commandAuthorization
35
+ commandAuthorization: commandAuthorization,
36
+ commandTransition: commandTransition,
37
+ traits: traits
30
38
  };
31
39
 
32
40
  function mutationFor(fragment, needle) {
@@ -196,9 +204,17 @@ function commandAuthorization$1(param) {
196
204
  return "AllowAuthenticated";
197
205
  }
198
206
 
207
+ function commandTransition$1(param) {
208
+ return "Unrestricted";
209
+ }
210
+
211
+ let traits$1 = [];
212
+
199
213
  let AddProductCmd = {
200
214
  commandSchema: commandSchema$1,
201
- commandAuthorization: commandAuthorization$1
215
+ commandAuthorization: commandAuthorization$1,
216
+ commandTransition: commandTransition$1,
217
+ traits: traits$1
202
218
  };
203
219
 
204
220
  let commandSchema$2 = Sury.union([
@@ -217,9 +233,17 @@ function commandAuthorization$2(param) {
217
233
  return "AllowAuthenticated";
218
234
  }
219
235
 
236
+ function commandTransition$2(param) {
237
+ return "Unrestricted";
238
+ }
239
+
240
+ let traits$2 = [];
241
+
220
242
  let ChangePriceCmd = {
221
243
  commandSchema: commandSchema$2,
222
- commandAuthorization: commandAuthorization$2
244
+ commandAuthorization: commandAuthorization$2,
245
+ commandTransition: commandTransition$2,
246
+ traits: traits$2
223
247
  };
224
248
 
225
249
  globalThis.describe("semantic composites are named once, not once per field", () => {
@@ -32,6 +32,12 @@ function commandAuthorization(param) {
32
32
  return "AllowAuthenticated";
33
33
  }
34
34
 
35
+ function commandTransition(param) {
36
+ return "Unrestricted";
37
+ }
38
+
39
+ let traits = [];
40
+
35
41
  let CmdGenAggSpec = {
36
42
  Id: undefined,
37
43
  name: name,
@@ -39,7 +45,9 @@ let CmdGenAggSpec = {
39
45
  eventSchema: eventSchema,
40
46
  errorSchema: errorSchema,
41
47
  moduleUrl: moduleUrl,
42
- commandAuthorization: commandAuthorization
48
+ commandAuthorization: commandAuthorization,
49
+ commandTransition: commandTransition,
50
+ traits: traits
43
51
  };
44
52
 
45
53
  let moduleUrl$1 = import.meta.url;
@@ -106,7 +114,9 @@ let TestGenerator = CommandGenerator_Callback$ReventlessCore.Make(MockPublishSpe
106
114
  errorSchema: errorSchema,
107
115
  commandSchema: commandSchema,
108
116
  moduleUrl: moduleUrl,
109
- commandAuthorization: commandAuthorization
117
+ commandAuthorization: commandAuthorization,
118
+ commandTransition: commandTransition,
119
+ traits: traits
110
120
  });
111
121
 
112
122
  let singleTagCommandSchema = Sury.$schema(s => ({