@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.
- package/CHANGELOG.md +15 -0
- package/package.json +6 -6
- package/src/Roles.res +3 -0
- package/src/Roles.res.mjs +2 -0
- package/src/admin/Platform_AdminGroup.res +12 -0
- package/src/admin/Platform_AdminGroup.res.mjs +23 -0
- package/src/admin/Platform_Admin_Structure.res +14 -10
- package/src/admin/Platform_Admin_Structure.res.mjs +22 -33
- package/src/components/AutomationSlice/AutomationSlice_Builder.res +2 -1
- package/src/components/Counter/Counter_Builder.res +4 -2
- package/src/components/Dcb/Dcb_Builder.res +9 -6
- package/src/components/Dcb/Dcb_Builder.res.mjs +6 -6
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +2 -1
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +2 -1
- package/src/components/QueryDb/QueryDb_Builder.res +1 -1
- package/src/components/QueryDb/QueryDb_Builder.res.mjs +3 -2
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +1 -0
- package/src/plugin/api/PluginBaseFragment.res +1 -1
- package/src/plugin/api/PluginBaseFragment.res.mjs +2 -1
- package/src/plugin/component/Plugin_Builder.res +7 -3
- package/src/plugin/component/Plugin_Builder.res.mjs +6 -3
- package/src/plugin/component/Plugin_Helpers.res +2 -1
- package/src/plugin/component/Plugin_Helpers.res.mjs +2 -1
- package/src/plugin/component/Plugin_Structure.res +52 -19
- package/src/plugin/component/Plugin_Structure.res.mjs +40 -15
- package/src/plugin/lifecycle/PluginsReadModelSpec.res +1 -1
- package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +1 -1
- package/tests/admin/AdminApiSchemaDriftTest.res +9 -3
- package/tests/admin/AdminApiSchemaDriftTest.res.mjs +3 -3
- package/tests/admin/Platform_Admin_StructureTest.res +17 -14
- package/tests/admin/Platform_Admin_StructureTest.res.mjs +31 -19
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +8 -72
- package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +9 -39
- package/tests/api/GraphQL_FragmentGeneratorTest.res +1 -1
- package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +1 -1
- package/tests/plugin/PluginStructureAccessTest.res +30 -1
- package/tests/plugin/PluginStructureAccessTest.res.mjs +28 -1
- package/tests/plugin/StateChange/PsGatedCommands.res +9 -1
- package/tests/plugin/StateChange/PsGatedCommands.res.mjs +4 -1
- package/tests/plugin/StateView/PsGatedView.res +1 -1
- package/tests/plugin/StateView/PsGatedView.res.mjs +1 -1
- package/tests/plugin/pluginDefinitionRequiredScalars.txt +5 -0
|
@@ -47,52 +47,20 @@ let publicRm = {
|
|
|
47
47
|
requiredAccess: undefined
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
let
|
|
50
|
+
let newrecord = {...publicRm};
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
newrecord.visibility = "Internal";
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
newrecord.queryField = "Catalog_ProductAudit";
|
|
55
55
|
|
|
56
|
-
|
|
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
|
-
|
|
60
|
+
newrecord
|
|
93
61
|
];
|
|
94
62
|
|
|
95
|
-
let structure_stateViewSlices = [
|
|
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
|
-
/*
|
|
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=
|
|
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: "
|
|
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
|
|
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
|
|
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(
|
|
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: "
|
|
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(
|
|
4
|
+
@@reventless.authorize(AllowRoles([Admin]))
|
|
5
5
|
|
|
6
6
|
@schema
|
|
7
7
|
type consumedEvent = OrderPlaced({orderId: string, customerName: string})
|
|
@@ -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
|