@reventlessdev/reventless-core 3.0.0-alpha.285 → 3.0.0-alpha.286

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 (42) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/package.json +6 -6
  3. package/src/Roles.res +3 -0
  4. package/src/Roles.res.mjs +2 -0
  5. package/src/admin/Platform_AdminGroup.res +12 -0
  6. package/src/admin/Platform_AdminGroup.res.mjs +23 -0
  7. package/src/admin/Platform_Admin_Structure.res +14 -10
  8. package/src/admin/Platform_Admin_Structure.res.mjs +22 -33
  9. package/src/components/AutomationSlice/AutomationSlice_Builder.res +2 -1
  10. package/src/components/Counter/Counter_Builder.res +4 -2
  11. package/src/components/Dcb/Dcb_Builder.res +9 -6
  12. package/src/components/Dcb/Dcb_Builder.res.mjs +6 -6
  13. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +2 -1
  14. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +2 -1
  15. package/src/components/QueryDb/QueryDb_Builder.res +1 -1
  16. package/src/components/QueryDb/QueryDb_Builder.res.mjs +3 -2
  17. package/src/components/StateViewSlice/StateViewSlice_Builder.res +1 -0
  18. package/src/plugin/api/PluginBaseFragment.res +1 -1
  19. package/src/plugin/api/PluginBaseFragment.res.mjs +2 -1
  20. package/src/plugin/component/Plugin_Builder.res +7 -3
  21. package/src/plugin/component/Plugin_Builder.res.mjs +6 -3
  22. package/src/plugin/component/Plugin_Helpers.res +2 -1
  23. package/src/plugin/component/Plugin_Helpers.res.mjs +2 -1
  24. package/src/plugin/component/Plugin_Structure.res +52 -19
  25. package/src/plugin/component/Plugin_Structure.res.mjs +40 -15
  26. package/src/plugin/lifecycle/PluginsReadModelSpec.res +1 -1
  27. package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +1 -1
  28. package/tests/admin/AdminApiSchemaDriftTest.res +9 -3
  29. package/tests/admin/AdminApiSchemaDriftTest.res.mjs +3 -3
  30. package/tests/admin/Platform_Admin_StructureTest.res +17 -14
  31. package/tests/admin/Platform_Admin_StructureTest.res.mjs +31 -19
  32. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +8 -72
  33. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +9 -39
  34. package/tests/api/GraphQL_FragmentGeneratorTest.res +1 -1
  35. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +1 -1
  36. package/tests/plugin/PluginStructureAccessTest.res +30 -1
  37. package/tests/plugin/PluginStructureAccessTest.res.mjs +28 -1
  38. package/tests/plugin/StateChange/PsGatedCommands.res +9 -1
  39. package/tests/plugin/StateChange/PsGatedCommands.res.mjs +4 -1
  40. package/tests/plugin/StateView/PsGatedView.res +1 -1
  41. package/tests/plugin/StateView/PsGatedView.res.mjs +1 -1
  42. package/tests/plugin/pluginDefinitionRequiredScalars.txt +5 -0
@@ -47,52 +47,20 @@ let publicRm = {
47
47
  requiredAccess: undefined
48
48
  };
49
49
 
50
- let internalRm_consumedEventTypes = publicRm_consumedEventTypes;
50
+ let newrecord = {...publicRm};
51
51
 
52
- let internalRm_linkedWriteSide = publicRm_linkedWriteSide;
52
+ newrecord.visibility = "Internal";
53
53
 
54
- let internalRm_searchableFields = publicRm_searchableFields;
54
+ newrecord.queryField = "Catalog_ProductAudit";
55
55
 
56
- let internalRm_labelFieldSource = "annotation";
57
-
58
- let internalRm_visibility = "Internal";
59
-
60
- let internalRm_chapter = "Inventory";
61
-
62
- let internalRm_singleQueryField = "Catalog_Product";
63
-
64
- let internalRm_idField = "productId";
65
-
66
- let internalRm_idFieldSource = "convention";
67
-
68
- let internalRm = {
69
- name: "ProductAudit",
70
- queryField: "Catalog_ProductAudit",
71
- schema: "{}",
72
- consumedEventTypes: internalRm_consumedEventTypes,
73
- linkedWriteSide: internalRm_linkedWriteSide,
74
- labelField: "displayName",
75
- searchableFields: internalRm_searchableFields,
76
- labelFieldSource: internalRm_labelFieldSource,
77
- lifecycleField: undefined,
78
- ownerField: undefined,
79
- retiredField: undefined,
80
- retiredValues: undefined,
81
- namedWhenRetired: undefined,
82
- visibility: internalRm_visibility,
83
- chapter: internalRm_chapter,
84
- singleQueryField: internalRm_singleQueryField,
85
- idField: internalRm_idField,
86
- idFieldSource: internalRm_idFieldSource,
87
- requiredAccess: undefined
88
- };
56
+ newrecord.name = "ProductAudit";
89
57
 
90
58
  let structure_readModels = [
91
59
  publicRm,
92
- internalRm
60
+ newrecord
93
61
  ];
94
62
 
95
- let structure_stateViewSlices = [internalRm];
63
+ let structure_stateViewSlices = [newrecord];
96
64
 
97
65
  let structure_stateChangeSlices = [];
98
66
 
@@ -248,6 +216,8 @@ globalThis.describe("absent optional collections", () => {
248
216
  });
249
217
  });
250
218
 
219
+ let internalRm = newrecord;
220
+
251
221
  export {
252
222
  publicRm,
253
223
  internalRm,
@@ -256,4 +226,4 @@ export {
256
226
  json,
257
227
  namesOf,
258
228
  }
259
- /* encoded Not a pure module */
229
+ /* Not a pure module */
@@ -853,7 +853,7 @@ describe("@resolves / @resolvesMany reach the SDL", () => {
853
853
  () =>
854
854
  orderType(
855
855
  ~resolvedFields=Some(ResolvedFields.bothFields),
856
- ~targetPermission=AllowGroups(["Admin"]),
856
+ ~targetPermission=AllowRoles([Reventless.Role.make("Admin")]),
857
857
  ),
858
858
  ),
859
859
  )->toBe(true)
@@ -710,7 +710,7 @@ globalThis.describe("@resolves / @resolvesMany reach the SDL", () => {
710
710
  });
711
711
  globalThis.test("a target guarded differently from the parent is refused", () => {
712
712
  globalThis.expect(refused(() => orderType(bothFields, {
713
- TAG: "AllowGroups",
713
+ TAG: "AllowRoles",
714
714
  _0: ["Admin"]
715
715
  }))).toBe(true);
716
716
  });
@@ -76,7 +76,7 @@ let viewNamed = name =>
76
76
  ->Option.flatMap(q => q.requiredAccess)
77
77
 
78
78
  describe("requiredAccess derived from the authorization rule", () => {
79
- testSync("an AllowGroups command publishes its groups", () =>
79
+ testSync("an AllowRoles command publishes its groups", () =>
80
80
  expect(commandNamed("Restock"))->toEqual(Some(["Admin", "Ops"]))
81
81
  )
82
82
 
@@ -108,4 +108,33 @@ describe("requiredAccess derived from the authorization rule", () => {
108
108
  json->String.includes(`"requiredAccess":[]`),
109
109
  ))->toEqual((true, true, false))
110
110
  })
111
+
112
+ // The access keys are compared with a token's groups, so they are the groups
113
+ // the roles map to; the roles themselves travel beside them for the deploy
114
+ // check.
115
+ testSync("a renamed role publishes its group as the key and its role as the role", () => {
116
+ Reventless.Role.clearGroups()
117
+ Reventless.Role.setGroups([(Reventless.Role.make("Ops"), "shop-ops")])
118
+ let renamed = Plugin_Structure.make(
119
+ ~name="AccessPlugin",
120
+ ~stateChangeSlices=[module(PsGatedCommandsSlice)],
121
+ )
122
+ Reventless.Role.clearGroups()
123
+ let restock =
124
+ renamed.stateChangeSlices
125
+ ->Array.flatMap(w => w.commands)
126
+ ->Array.find(c => c.name === "Restock")
127
+ expect(restock->Option.map(c => (c.requiredAccess, c.requiredRoles)))->toEqual(
128
+ Some((Some(["Admin", "shop-ops"]), Some(["Admin", "Ops"]))),
129
+ )
130
+ })
131
+
132
+ testSync("a command that names no role publishes no roles", () =>
133
+ expect(
134
+ structure.stateChangeSlices
135
+ ->Array.flatMap(w => w.commands)
136
+ ->Array.find(c => c.name === "RequestRestock")
137
+ ->Option.flatMap(c => c.requiredRoles),
138
+ )->toEqual(None)
139
+ )
111
140
  })
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
4
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
5
6
  import * as PsGatedView$ReventlessCore from "./StateView/PsGatedView.res.mjs";
6
7
  import * as PsOrdersView$ReventlessCore from "./StateView/PsOrdersView.res.mjs";
7
8
  import * as PsGatedCommands$ReventlessCore from "./StateChange/PsGatedCommands.res.mjs";
@@ -119,7 +120,7 @@ function viewNamed(name) {
119
120
  }
120
121
 
121
122
  globalThis.describe("requiredAccess derived from the authorization rule", () => {
122
- globalThis.test("an AllowGroups command publishes its groups", () => {
123
+ globalThis.test("an AllowRoles command publishes its groups", () => {
123
124
  globalThis.expect(commandNamed("Restock")).toEqual([
124
125
  "Admin",
125
126
  "Ops"
@@ -146,6 +147,32 @@ globalThis.describe("requiredAccess derived from the authorization rule", () =>
146
147
  false
147
148
  ]);
148
149
  });
150
+ globalThis.test("a renamed role publishes its group as the key and its role as the role", () => {
151
+ Role$Reventless.clearGroups();
152
+ Role$Reventless.setGroups([[
153
+ "Ops",
154
+ "shop-ops"
155
+ ]]);
156
+ let renamed = Plugin_Structure$ReventlessCore.make("AccessPlugin", undefined, undefined, undefined, [PsGatedCommandsSlice], undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
157
+ Role$Reventless.clearGroups();
158
+ let restock = renamed.stateChangeSlices.flatMap(w => w.commands).find(c => c.name === "Restock");
159
+ globalThis.expect(Stdlib_Option.map(restock, c => [
160
+ c.requiredAccess,
161
+ c.requiredRoles
162
+ ])).toEqual([
163
+ [
164
+ "Admin",
165
+ "shop-ops"
166
+ ],
167
+ [
168
+ "Admin",
169
+ "Ops"
170
+ ]
171
+ ]);
172
+ });
173
+ globalThis.test("a command that names no role publishes no roles", () => {
174
+ globalThis.expect(Stdlib_Option.flatMap(structure.stateChangeSlices.flatMap(w => w.commands).find(c => c.name === "RequestRestock"), c => c.requiredRoles)).toEqual(undefined);
175
+ });
149
176
  });
150
177
 
151
178
  export {
@@ -4,6 +4,14 @@
4
4
 
5
5
  @@reventless.spec("GatedCommands")
6
6
 
7
+ // Declared here rather than in the package's `Roles.res`: the rule resolves
8
+ // `Roles` by ordinary scoping, so a spec can name roles its package does not.
9
+ module Roles = {
10
+ type t =
11
+ | Admin
12
+ | Ops
13
+ }
14
+
7
15
  type state = bool
8
16
  let initialState = false
9
17
 
@@ -14,7 +22,7 @@ let evolve = (_state, _event) => true
14
22
 
15
23
  @schema
16
24
  type command =
17
- | @authorize(AllowGroups(["Admin", "Ops"])) Restock({productId: string})
25
+ | @authorize(AllowRoles([Admin, Ops])) Restock({productId: string})
18
26
  | RequestRestock({productId: string})
19
27
 
20
28
  @schema
@@ -3,6 +3,8 @@
3
3
  import * as Sury from "sury";
4
4
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
5
5
 
6
+ let Roles = {};
7
+
6
8
  let consumedEventSchema = Sury.$schema(s => ({
7
9
  TAG: "StockLow",
8
10
  productId: s.m(DcbTag$Reventless.string)
@@ -46,7 +48,7 @@ function decide(_state, _command) {
46
48
  function commandAuthorization(command) {
47
49
  if (command.TAG === "Restock") {
48
50
  return {
49
- TAG: "AllowGroups",
51
+ TAG: "AllowRoles",
50
52
  _0: [
51
53
  "Admin",
52
54
  "Ops"
@@ -76,6 +78,7 @@ let readConsistency = "EscalateOnRetry";
76
78
  export {
77
79
  name,
78
80
  Id,
81
+ Roles,
79
82
  initialState,
80
83
  consumedEventSchema,
81
84
  evolve,
@@ -1,7 +1,7 @@
1
1
  // Test fixture spec: a view whose module-level rule restricts who may read it.
2
2
 
3
3
  @@reventless.spec("GatedView")
4
- @@reventless.authorize(AllowGroups(["Admin"]))
4
+ @@reventless.authorize(AllowRoles([Admin]))
5
5
 
6
6
  @schema
7
7
  type consumedEvent = OrderPlaced({orderId: string, customerName: string})
@@ -31,7 +31,7 @@ function project(param) {
31
31
  let config = ReadModel$Reventless.config(undefined, undefined, undefined);
32
32
 
33
33
  let authorization = {
34
- TAG: "AllowGroups",
34
+ TAG: "AllowRoles",
35
35
  _0: ["Admin"]
36
36
  };
37
37
 
@@ -56,6 +56,7 @@
56
56
  .structure.aggregates[].commands[].references[].entity: string
57
57
  .structure.aggregates[].commands[].references[].fieldName: string
58
58
  .structure.aggregates[].commands[].requiredAccess[]: string
59
+ .structure.aggregates[].commands[].requiredRoles[]: string
59
60
  .structure.aggregates[].commands[].schema: string
60
61
  .structure.aggregates[].consumedEventTypes[]: string
61
62
  .structure.aggregates[].errors[].name: string
@@ -91,6 +92,7 @@
91
92
  .structure.extensions[].name: string
92
93
  .structure.inboundTranslationSlices[].commandTypes[]: string
93
94
  .structure.inboundTranslationSlices[].name: string
95
+ .structure.inboundTranslationSlices[].requiredRoles[]: string
94
96
  .structure.inboundTranslationSlices[].targetName: string
95
97
  .structure.outboundTranslationSlices[].consumedEventTypes[]: string
96
98
  # `consumedSources` is itself optional and is NEW, so no persisted payload
@@ -107,6 +109,7 @@
107
109
  .structure.readModels[].name: string
108
110
  .structure.readModels[].queryField: string
109
111
  .structure.readModels[].requiredAccess[]: string
112
+ .structure.readModels[].requiredRoles[]: string
110
113
  .structure.readModels[].retiredValues[]: string
111
114
  .structure.readModels[].schema: string
112
115
  .structure.readModels[].searchableFields[]: string
@@ -138,6 +141,7 @@
138
141
  .structure.stateChangeSlices[].commands[].references[].entity: string
139
142
  .structure.stateChangeSlices[].commands[].references[].fieldName: string
140
143
  .structure.stateChangeSlices[].commands[].requiredAccess[]: string
144
+ .structure.stateChangeSlices[].commands[].requiredRoles[]: string
141
145
  .structure.stateChangeSlices[].commands[].schema: string
142
146
  .structure.stateChangeSlices[].consumedEventTypes[]: string
143
147
  .structure.stateChangeSlices[].errors[].name: string
@@ -157,6 +161,7 @@
157
161
  .structure.stateViewSlices[].name: string
158
162
  .structure.stateViewSlices[].queryField: string
159
163
  .structure.stateViewSlices[].requiredAccess[]: string
164
+ .structure.stateViewSlices[].requiredRoles[]: string
160
165
  .structure.stateViewSlices[].retiredValues[]: string
161
166
  .structure.stateViewSlices[].schema: string
162
167
  .structure.stateViewSlices[].searchableFields[]: string