@reventlessdev/reventless-core 3.0.0-alpha.292 → 3.0.0-alpha.293
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 +11 -0
- package/package.json +9 -9
- package/src/admin/Platform_Admin_Structure.res +1 -2
- package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
- package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res.mjs +2 -2
- package/src/components/Dcb/Dcb_Builder.res +11 -20
- package/src/components/Dcb/Dcb_Builder.res.mjs +7 -12
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +27 -24
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +4 -4
- package/src/plugin/component/Plugin_Builder.res +8 -18
- package/src/plugin/component/Plugin_Builder.res.mjs +2 -8
- package/src/plugin/component/Plugin_Helpers.res +5 -6
- package/src/plugin/component/Plugin_Helpers.res.mjs +1 -1
- package/src/plugin/component/Plugin_Structure.res +22 -28
- package/src/plugin/component/Plugin_Structure.res.mjs +12 -12
- package/src/plugin/connect/PluginConnectExtension_Mapping.res.mjs +1 -1
- package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +1 -1
- package/src/plugin/connect/PluginExtensionPoint_UiFragment.res +1 -1
- package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +1 -1
- package/src/plugin/lifecycle/PluginSpec.res.mjs +2 -2
- package/tests/aggregate/AggregateCacheTest.res.mjs +4 -4
- package/tests/aggregate/AggregateConflictTest.res.mjs +4 -4
- package/tests/aggregate/AggregateFixtures.res.mjs +4 -4
- package/tests/aggregate/AggregateSnapshotTest.res.mjs +6 -6
- package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +6 -6
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +3 -3
- package/tests/commandgenerator/OwnerActingTest.res.mjs +7 -7
- package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +1 -1
- package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +3 -3
- package/tests/dcb/DcbFixtures.res.mjs +2 -2
- package/tests/dcb/DcbStateChangeSliceTest.res.mjs +7 -7
- package/tests/eventmapper/EventMapperFixtures.res.mjs +4 -4
- package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +2 -2
- package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +2 -2
- package/tests/plugin/PluginStructureAccessTest.res +6 -5
- package/tests/plugin/PluginStructureAccessTest.res.mjs +17 -14
- package/tests/plugin/PluginStructureTest.res.mjs +10 -10
- package/tests/plugin/StateChange/PsAttachInvoice.res.mjs +2 -2
- package/tests/plugin/StateChange/PsChangePhoto.res.mjs +2 -2
- package/tests/plugin/StateChange/PsDispatchShipment.res.mjs +2 -2
- package/tests/plugin/StateChange/PsGatedCommands.res.mjs +3 -3
- package/tests/plugin/StateChange/PsPlaceOrder.res.mjs +2 -2
- package/tests/plugin/StateChange/PsReserveStock.res.mjs +2 -2
- package/tests/plugin/StateChange/PsShipOrder.res.mjs +2 -2
- package/tests/plugin/StateChange/PsTransitionSwitch.res.mjs +2 -2
- package/tests/plugin/StateChange/PsTypoStore.res.mjs +2 -2
- package/tests/plugin/StateChange/PsUploadAvatar.res.mjs +2 -2
- package/tests/plugin/StateChange/PsUploadImages.res.mjs +2 -2
- package/tests/util/CommandPublisherTest.res.mjs +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.293 (2026-10-10)
|
|
7
|
+
|
|
8
|
+
* feat!: a command's rule is looked up by its name, without a cast ([5f02c40](https://github.com/ReventlessDev/reventless-core/commit/5f02c4081d077bf4a6951d8c9b2b16553a8ca87b))
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* a spec that writes `commandAuthorization` by hand writes
|
|
13
|
+
`authorizationOf = (name: string) => …` instead; the PPX refuses the old name.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
# 3.0.0-alpha.292 (2026-10-10)
|
|
7
18
|
|
|
8
19
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-core",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.293",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -28,22 +28,22 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"sury": "11.0.0",
|
|
30
30
|
"uuid": "^13.0.0",
|
|
31
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
31
32
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
32
|
-
"@reventlessdev/rescript-fast-csv": "2.0.0-alpha.22",
|
|
33
|
-
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.15",
|
|
34
33
|
"@reventlessdev/rescript-node": "2.0.0-alpha.22",
|
|
35
|
-
"@reventlessdev/rescript-
|
|
36
|
-
"@reventlessdev/rescript-
|
|
34
|
+
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.15",
|
|
35
|
+
"@reventlessdev/rescript-fast-csv": "2.0.0-alpha.22",
|
|
37
36
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.23",
|
|
38
|
-
"@reventlessdev/
|
|
37
|
+
"@reventlessdev/rescript-ssh2": "2.0.0-alpha.23",
|
|
38
|
+
"@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
|
|
39
39
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.39",
|
|
40
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
41
|
-
"@reventlessdev/
|
|
40
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.162",
|
|
41
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.190"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "12.3.1",
|
|
45
45
|
"sury-ppx": "11.0.0",
|
|
46
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
46
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.99"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"rescript": "^12.3.1"
|
|
@@ -37,8 +37,7 @@ let pluginCommands: array<commandDef> = Plugin_Structure.extractCommandDefs(
|
|
|
37
37
|
~isAggregate=true,
|
|
38
38
|
~mutationFieldFor=variantName =>
|
|
39
39
|
Api_Naming.adminField(~name=PluginSpec.name ++ "_" ++ variantName),
|
|
40
|
-
~
|
|
41
|
-
PluginSpec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
|
|
40
|
+
~authorizationOf=name => PluginSpec.authorizationOf(name)->Reventless.Authorization.named,
|
|
42
41
|
~commandTransition=PluginSpec.commandTransition->Obj.magic,
|
|
43
42
|
commandSchema,
|
|
44
43
|
)
|
|
@@ -8,7 +8,7 @@ import * as PluginsReadModelSpec$ReventlessCore from "../plugin/lifecycle/Plugin
|
|
|
8
8
|
|
|
9
9
|
let pluginId = "Platform";
|
|
10
10
|
|
|
11
|
-
let pluginCommands = Plugin_Structure$ReventlessCore.extractCommandDefs(undefined, true, undefined, variantName => Api_Naming$ReventlessCore.adminField(PluginSpec$ReventlessCore.name + "_" + variantName),
|
|
11
|
+
let pluginCommands = Plugin_Structure$ReventlessCore.extractCommandDefs(undefined, true, undefined, variantName => Api_Naming$ReventlessCore.adminField(PluginSpec$ReventlessCore.name + "_" + variantName), name => Authorization$Reventless.named(PluginSpec$ReventlessCore.authorizationOf(name)), PluginSpec$ReventlessCore.commandTransition, undefined, PluginSpec$ReventlessCore.commandSchema);
|
|
12
12
|
|
|
13
13
|
let pluginAggregate_producedEventTypes = [];
|
|
14
14
|
|
|
@@ -42,7 +42,7 @@ let eventSchema = Sury.union([
|
|
|
42
42
|
|
|
43
43
|
let commandSchema$1 = Api$ReventlessInfra.markNoApi(commandSchema);
|
|
44
44
|
|
|
45
|
-
function
|
|
45
|
+
function authorizationOf(param) {
|
|
46
46
|
return "AllowAuthenticated";
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -70,7 +70,7 @@ export {
|
|
|
70
70
|
consumedEventSchema,
|
|
71
71
|
commandSchema$1 as commandSchema,
|
|
72
72
|
moduleUrl,
|
|
73
|
-
|
|
73
|
+
authorizationOf,
|
|
74
74
|
readConsistency,
|
|
75
75
|
commandTransition,
|
|
76
76
|
traits,
|
|
@@ -92,11 +92,11 @@ let emptyResult: dcbResult = {
|
|
|
92
92
|
// An inbound slice's door rule, read off its spec.
|
|
93
93
|
let inboundDoorPermission = (
|
|
94
94
|
commandSchema: S.t<'command>,
|
|
95
|
-
|
|
95
|
+
authorizationOf: string => Reventless.Authorization.rule<'role>,
|
|
96
96
|
) =>
|
|
97
97
|
Plugin_Structure.inboundDoorPermission(
|
|
98
98
|
~commandSchema=commandSchema->S.castToUnknown,
|
|
99
|
-
~
|
|
99
|
+
~authorizationOf=name => name->authorizationOf->Reventless.Authorization.named,
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
module Make = (
|
|
@@ -544,8 +544,7 @@ module Make = (
|
|
|
544
544
|
~commandSchema,
|
|
545
545
|
)->Array.map(((f, _)) => f),
|
|
546
546
|
~commandSchema,
|
|
547
|
-
~
|
|
548
|
-
S.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
|
|
547
|
+
~authorizationOf=name => S.Spec.authorizationOf(name)->Reventless.Authorization.named,
|
|
549
548
|
)
|
|
550
549
|
}
|
|
551
550
|
})
|
|
@@ -560,8 +559,7 @@ module Make = (
|
|
|
560
559
|
~commandSchema,
|
|
561
560
|
)->Array.map(((f, _)) => f),
|
|
562
561
|
~commandSchema,
|
|
563
|
-
~
|
|
564
|
-
S.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
|
|
562
|
+
~authorizationOf=name => S.Spec.authorizationOf(name)->Reventless.Authorization.named,
|
|
565
563
|
)
|
|
566
564
|
}
|
|
567
565
|
})
|
|
@@ -759,10 +757,7 @@ module Make = (
|
|
|
759
757
|
registerResolver(
|
|
760
758
|
~fieldName,
|
|
761
759
|
~externalInputSchema=ITS.Spec.externalInputSchema->S.castToUnknown,
|
|
762
|
-
~permission=?inboundDoorPermission(
|
|
763
|
-
ITS.Spec.commandSchema,
|
|
764
|
-
ITS.Spec.commandAuthorization,
|
|
765
|
-
),
|
|
760
|
+
~permission=?inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.authorizationOf),
|
|
766
761
|
)
|
|
767
762
|
| None => ()
|
|
768
763
|
}
|
|
@@ -1066,17 +1061,13 @@ module Make = (
|
|
|
1066
1061
|
)
|
|
1067
1062
|
// One SDL field per constructor, each with its own constructor's args and
|
|
1068
1063
|
// its own per-constructor authorization rule.
|
|
1069
|
-
let commandAuthorization: unknown => Reventless.Authorization.permission = command =>
|
|
1070
|
-
S.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named
|
|
1071
1064
|
let fieldPermissions = Dict.make()
|
|
1072
|
-
fieldSpecs->Array.forEach(((fieldName, ctor)) =>
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
ctor,
|
|
1065
|
+
fieldSpecs->Array.forEach(((fieldName, ctor)) =>
|
|
1066
|
+
fieldPermissions->Dict.set(
|
|
1067
|
+
fieldName,
|
|
1068
|
+
S.Spec.authorizationOf(ctor)->Reventless.Authorization.named,
|
|
1076
1069
|
)
|
|
1077
|
-
|
|
1078
|
-
fieldPermissions->Dict.set(fieldName, commandAuthorization(syntheticCmd))
|
|
1079
|
-
})
|
|
1070
|
+
)
|
|
1080
1071
|
Some({
|
|
1081
1072
|
ReventlessInfra.Api.fieldNames: fieldSpecs->Array.map(((f, _)) => f),
|
|
1082
1073
|
commandSchema,
|
|
@@ -1098,7 +1089,7 @@ module Make = (
|
|
|
1098
1089
|
let fieldPermissions = Dict.make()
|
|
1099
1090
|
inboundDoorPermission(
|
|
1100
1091
|
ITS.Spec.commandSchema,
|
|
1101
|
-
ITS.Spec.
|
|
1092
|
+
ITS.Spec.authorizationOf,
|
|
1102
1093
|
)->Option.forEach(rule => fieldPermissions->Dict.set(fieldName, rule))
|
|
1103
1094
|
{
|
|
1104
1095
|
ReventlessInfra.Api.fieldNames: [fieldName],
|
|
@@ -118,8 +118,8 @@ let emptyResult = {
|
|
|
118
118
|
eventLogEntries: emptyResult_eventLogEntries
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
function inboundDoorPermission(commandSchema,
|
|
122
|
-
return Plugin_Structure$ReventlessCore.inboundDoorPermission(commandSchema,
|
|
121
|
+
function inboundDoorPermission(commandSchema, authorizationOf) {
|
|
122
|
+
return Plugin_Structure$ReventlessCore.inboundDoorPermission(commandSchema, name => Authorization$Reventless.named(authorizationOf(name)));
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
function Make(DcbEventLogStorage) {
|
|
@@ -295,13 +295,13 @@ function Make(DcbEventLogStorage) {
|
|
|
295
295
|
syncSlices.forEach(S => {
|
|
296
296
|
let commandSchema = S.Spec.commandSchema;
|
|
297
297
|
if (!ApiNoApiHelpers$ReventlessCore.isNoApi(commandSchema)) {
|
|
298
|
-
return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema,
|
|
298
|
+
return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema, name => Authorization$Reventless.named(S.Spec.authorizationOf(name)));
|
|
299
299
|
}
|
|
300
300
|
});
|
|
301
301
|
asyncSlices.forEach(S => {
|
|
302
302
|
let commandSchema = S.Spec.commandSchema;
|
|
303
303
|
if (!ApiNoApiHelpers$ReventlessCore.isNoApi(commandSchema)) {
|
|
304
|
-
return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema,
|
|
304
|
+
return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema, name => Authorization$Reventless.named(S.Spec.authorizationOf(name)));
|
|
305
305
|
}
|
|
306
306
|
});
|
|
307
307
|
}
|
|
@@ -397,7 +397,7 @@ function Make(DcbEventLogStorage) {
|
|
|
397
397
|
let fieldName = Api_Naming$ReventlessCore.sliceMutationField(name, ITS.Spec.name);
|
|
398
398
|
let registerResolver = HooksConfig.hooks.inboundMutationResolverHook;
|
|
399
399
|
if (registerResolver !== undefined) {
|
|
400
|
-
registerResolver(fieldName, ITS.Spec.externalInputSchema, inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.
|
|
400
|
+
registerResolver(fieldName, ITS.Spec.externalInputSchema, inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.authorizationOf));
|
|
401
401
|
}
|
|
402
402
|
let bindReceive = HooksConfig.hooks.inboundMutationBindReceiveHook;
|
|
403
403
|
if (bindReceive !== undefined) {
|
|
@@ -554,12 +554,7 @@ function Make(DcbEventLogStorage) {
|
|
|
554
554
|
let sliceDef = Stdlib_Option.flatMap(pluginStructure, s => s.stateChangeSlices.find(d => d.name === S.Spec.name));
|
|
555
555
|
let fieldPermissions = {};
|
|
556
556
|
fieldSpecs.forEach(param => {
|
|
557
|
-
|
|
558
|
-
let hasPayload = DcbTag$Reventless.isVariantPayloadBearing(commandSchema, ctor);
|
|
559
|
-
let syntheticCmd = hasPayload ? ({
|
|
560
|
-
TAG: ctor
|
|
561
|
-
}) : ctor;
|
|
562
|
-
fieldPermissions[param[0]] = Authorization$Reventless.named(S.Spec.commandAuthorization(syntheticCmd));
|
|
557
|
+
fieldPermissions[param[0]] = Authorization$Reventless.named(S.Spec.authorizationOf(param[1]));
|
|
563
558
|
});
|
|
564
559
|
return {
|
|
565
560
|
fieldNames: fieldSpecs.map(param => param[0]),
|
|
@@ -574,7 +569,7 @@ function Make(DcbEventLogStorage) {
|
|
|
574
569
|
let mutationEntriesFromInboundSlices = inboundTranslationSlices.map(ITS => {
|
|
575
570
|
let fieldName = Api_Naming$ReventlessCore.sliceMutationField(name, ITS.Spec.name);
|
|
576
571
|
let fieldPermissions = {};
|
|
577
|
-
Stdlib_Option.forEach(inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.
|
|
572
|
+
Stdlib_Option.forEach(inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.authorizationOf), rule => {
|
|
578
573
|
fieldPermissions[fieldName] = rule;
|
|
579
574
|
});
|
|
580
575
|
return {
|
|
@@ -168,34 +168,37 @@ module Make = (
|
|
|
168
168
|
switch Translation.translate(input) {
|
|
169
169
|
| Error(msg) => fail(msg)
|
|
170
170
|
| Ok(pairs) =>
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
|
|
171
|
+
switch pairs->Array.reduce(Ok([]), (acc, pair) =>
|
|
172
|
+
switch (acc, encode(pair)) {
|
|
173
|
+
| (Ok(msgs), Ok(msg)) => Ok(msgs->Array.concat([msg]))
|
|
174
|
+
| (Error(_) as failed, _) => failed
|
|
175
|
+
| (_, Error(msg)) => Error(msg)
|
|
176
|
+
}
|
|
177
|
+
) {
|
|
178
|
+
| Error(msg) => fail(msg)
|
|
179
|
+
// Each command against its own rule, read by the name it encodes to; one
|
|
180
|
+
// refused refuses the message.
|
|
181
|
+
| Ok(msgs)
|
|
182
|
+
if msgs->Array.some(({commandJson}) =>
|
|
183
|
+
!callerAdmits(
|
|
184
|
+
commandJson
|
|
185
|
+
->Reventless.Message.variantNameOfJson
|
|
186
|
+
->Spec.authorizationOf
|
|
187
|
+
->Reventless.Authorization.named,
|
|
188
|
+
caller,
|
|
189
|
+
)
|
|
190
|
+
) =>
|
|
178
191
|
fail(
|
|
179
192
|
~errorCode="Forbidden",
|
|
180
193
|
`${Spec.name}: the caller is not authorized for every command this input translates into`,
|
|
181
194
|
)
|
|
182
|
-
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
) {
|
|
190
|
-
| Error(msg) => fail(msg)
|
|
191
|
-
| Ok([]) => succeed(~requestId, ~inputJson, [])
|
|
192
|
-
| Ok(msgs) =>
|
|
193
|
-
try {
|
|
194
|
-
await publishJsons(msgs)
|
|
195
|
-
succeed(~requestId, ~inputJson, pairs->Array.map(((targetId, _)) => targetId))
|
|
196
|
-
} catch {
|
|
197
|
-
| exn => fail(messageOf(exn, "publish failed"))
|
|
198
|
-
}
|
|
195
|
+
| Ok([]) => succeed(~requestId, ~inputJson, [])
|
|
196
|
+
| Ok(msgs) =>
|
|
197
|
+
try {
|
|
198
|
+
await publishJsons(msgs)
|
|
199
|
+
succeed(~requestId, ~inputJson, pairs->Array.map(((targetId, _)) => targetId))
|
|
200
|
+
} catch {
|
|
201
|
+
| exn => fail(messageOf(exn, "publish failed"))
|
|
199
202
|
}
|
|
200
203
|
}
|
|
201
204
|
}
|
|
@@ -8,6 +8,7 @@ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
|
8
8
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
9
9
|
import * as Effect from "effect/Effect";
|
|
10
10
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
11
|
+
import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
|
|
11
12
|
import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
|
|
12
13
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
13
14
|
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
@@ -167,10 +168,6 @@ function Make(Spec) {
|
|
|
167
168
|
return fail(undefined, msg._0);
|
|
168
169
|
}
|
|
169
170
|
let pairs = msg._0;
|
|
170
|
-
let refused = pairs.find(param => !callerAdmits(Authorization$Reventless.named(Spec.commandAuthorization(param[1])), caller));
|
|
171
|
-
if (refused !== undefined) {
|
|
172
|
-
return fail("Forbidden", Spec.name + `: the caller is not authorized for every command this input translates into`);
|
|
173
|
-
}
|
|
174
171
|
let msg$1 = Stdlib_Array.reduce(pairs, {
|
|
175
172
|
TAG: "Ok",
|
|
176
173
|
_0: []
|
|
@@ -196,6 +193,9 @@ function Make(Spec) {
|
|
|
196
193
|
return fail(undefined, msg$1._0);
|
|
197
194
|
}
|
|
198
195
|
let msgs = msg$1._0;
|
|
196
|
+
if (msgs.some(param => !callerAdmits(Authorization$Reventless.named(Spec.authorizationOf(Message$Reventless.variantNameOfJson(param.commandJson))), caller))) {
|
|
197
|
+
return fail("Forbidden", Spec.name + `: the caller is not authorized for every command this input translates into`);
|
|
198
|
+
}
|
|
199
199
|
if (msgs.length === 0) {
|
|
200
200
|
return succeed(requestId, inputJson, []);
|
|
201
201
|
}
|
|
@@ -214,32 +214,22 @@ module Make = (
|
|
|
214
214
|
~kind=Aggregate,
|
|
215
215
|
~fields=fieldNames,
|
|
216
216
|
~commandSchema,
|
|
217
|
-
~
|
|
218
|
-
M.Spec.
|
|
217
|
+
~authorizationOf=name =>
|
|
218
|
+
M.Spec.authorizationOf(name)->Reventless.Authorization.named,
|
|
219
219
|
)
|
|
220
220
|
)
|
|
221
221
|
let aggDef =
|
|
222
222
|
pluginStructure->Option.flatMap(s =>
|
|
223
223
|
s.aggregates->Array.find(d => d.name == M.Spec.name)
|
|
224
224
|
)
|
|
225
|
-
//
|
|
226
|
-
// evaluating the PPX-generated `commandAuthorization` against a
|
|
227
|
-
// synthetic command value per constructor. Mirrors the resolver-time
|
|
228
|
-
// shape from `CommandGeneratorResolvers_GraphQL.syntheticCommand` —
|
|
229
|
-
// payload-bearing variants compile to `{TAG, ...}`, payload-less to
|
|
230
|
-
// bare strings.
|
|
225
|
+
// Each field gated by its own constructor's rule.
|
|
231
226
|
let fieldPermissions = Dict.make()
|
|
232
|
-
filteredConstructorNames->Array.forEachWithIndex((cname, idx) =>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
M.Spec.
|
|
236
|
-
cname,
|
|
227
|
+
filteredConstructorNames->Array.forEachWithIndex((cname, idx) =>
|
|
228
|
+
fieldPermissions->Dict.set(
|
|
229
|
+
fieldNames->Array.getUnsafe(idx),
|
|
230
|
+
M.Spec.authorizationOf(cname)->Reventless.Authorization.named,
|
|
237
231
|
)
|
|
238
|
-
|
|
239
|
-
let rule =
|
|
240
|
-
M.Spec.commandAuthorization(syntheticCmd->Obj.magic)->Reventless.Authorization.named
|
|
241
|
-
fieldPermissions->Dict.set(fieldName, rule)
|
|
242
|
-
})
|
|
232
|
+
)
|
|
243
233
|
[
|
|
244
234
|
{
|
|
245
235
|
ReventlessInfra.Api.fieldNames,
|
|
@@ -176,17 +176,11 @@ function Make(Spec) {
|
|
|
176
176
|
if (fieldNames.length === 0) {
|
|
177
177
|
return [];
|
|
178
178
|
}
|
|
179
|
-
Stdlib_Option.forEach(Spec.hooks.mutationResolverHook, registerResolver => registerResolver("Aggregate", fieldNames, commandSchema,
|
|
179
|
+
Stdlib_Option.forEach(Spec.hooks.mutationResolverHook, registerResolver => registerResolver("Aggregate", fieldNames, commandSchema, name => Authorization$Reventless.named(M.Spec.authorizationOf(name))));
|
|
180
180
|
let aggDef = Stdlib_Option.flatMap(pluginStructure, s => s.aggregates.find(d => d.name === M.Spec.name));
|
|
181
181
|
let fieldPermissions = {};
|
|
182
182
|
filteredConstructorNames.forEach((cname, idx) => {
|
|
183
|
-
|
|
184
|
-
let hasPayload = DcbTag$Reventless.isVariantPayloadBearing(M.Spec.commandSchema, cname);
|
|
185
|
-
let syntheticCmd = hasPayload ? ({
|
|
186
|
-
TAG: cname
|
|
187
|
-
}) : cname;
|
|
188
|
-
let rule = Authorization$Reventless.named(M.Spec.commandAuthorization(syntheticCmd));
|
|
189
|
-
fieldPermissions[fieldName] = rule;
|
|
183
|
+
fieldPermissions[fieldNames[idx]] = Authorization$Reventless.named(M.Spec.authorizationOf(cname));
|
|
190
184
|
});
|
|
191
185
|
return [{
|
|
192
186
|
fieldNames: fieldNames,
|
|
@@ -1006,14 +1006,14 @@ type subscriptionInfraParams = {
|
|
|
1006
1006
|
type platformHooks = {
|
|
1007
1007
|
// ── In-memory GraphQL mutation registration ────────────────────────────
|
|
1008
1008
|
// Phase 1: register SDL + resolver stub synchronously.
|
|
1009
|
-
// `
|
|
1010
|
-
//
|
|
1011
|
-
//
|
|
1009
|
+
// `authorizationOf` is the aggregate / slice Spec's rule by constructor name;
|
|
1010
|
+
// the resolver evaluates it per-request against `ctx.identity` to enforce
|
|
1011
|
+
// per-constructor authorization rules.
|
|
1012
1012
|
mutationResolverHook?: (
|
|
1013
1013
|
~kind: mutationKind,
|
|
1014
1014
|
~fields: array<string>,
|
|
1015
1015
|
~commandSchema: S.t<unknown>,
|
|
1016
|
-
~
|
|
1016
|
+
~authorizationOf: string => Reventless.Authorization.permission,
|
|
1017
1017
|
) => unit,
|
|
1018
1018
|
// Phase 2: bind generateCommand inside Output.apply.
|
|
1019
1019
|
mutationBindHook?: (~field: string, ~generateCommand: CommandGenerator.commandGenerator) => unit,
|
|
@@ -1158,8 +1158,7 @@ let registerAdminAggregateMutations = (
|
|
|
1158
1158
|
~kind=Aggregate,
|
|
1159
1159
|
~fields=fieldNames,
|
|
1160
1160
|
~commandSchema,
|
|
1161
|
-
~
|
|
1162
|
-
M.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
|
|
1161
|
+
~authorizationOf=name => M.Spec.authorizationOf(name)->Reventless.Authorization.named,
|
|
1163
1162
|
)
|
|
1164
1163
|
)
|
|
1165
1164
|
}
|
|
@@ -541,7 +541,7 @@ function registerAdminAggregateMutations(aggregates, hooks) {
|
|
|
541
541
|
let fieldNames = filteredConstructorNames.map(cname => Api_Naming$ReventlessCore.adminField(M.Spec.name + "_" + cname));
|
|
542
542
|
aggregateMutationFieldsRegistry[M.Spec.name] = fieldNames;
|
|
543
543
|
if (fieldNames.length !== 0) {
|
|
544
|
-
return Stdlib_Option.forEach(hooks.mutationResolverHook, registerResolver => registerResolver("Aggregate", fieldNames, commandSchema,
|
|
544
|
+
return Stdlib_Option.forEach(hooks.mutationResolverHook, registerResolver => registerResolver("Aggregate", fieldNames, commandSchema, name => Authorization$Reventless.named(M.Spec.authorizationOf(name))));
|
|
545
545
|
}
|
|
546
546
|
});
|
|
547
547
|
}
|
|
@@ -810,28 +810,25 @@ let nonEmpty = (xs: array<string>): option<array<string>> => xs->Array.length >
|
|
|
810
810
|
let rolesFor = (rule: Reventless.Authorization.permission): option<array<string>> =>
|
|
811
811
|
rule->Reventless.Authorization.rolesOf->Array.map(role => (role :> string))->nonEmpty
|
|
812
812
|
|
|
813
|
-
// A stand-in for one constructor
|
|
814
|
-
//
|
|
815
|
-
//
|
|
816
|
-
// the constructor alone.
|
|
813
|
+
// A stand-in for one constructor: a payload-bearing variant compiles to
|
|
814
|
+
// `{TAG, ...}`, a payload-less one to a bare string. Enough for a spec's
|
|
815
|
+
// `commandTransition`, whose switch matches on the constructor alone.
|
|
817
816
|
let syntheticCommand = (~schema: S.t<unknown>, variantName: string): 'command =>
|
|
818
817
|
Reventless.DcbTag.isVariantPayloadBearing(schema, variantName)
|
|
819
818
|
? {"TAG": variantName}->Obj.magic
|
|
820
819
|
: variantName->Obj.magic
|
|
821
820
|
|
|
822
|
-
// Each constructor's rule
|
|
821
|
+
// Each constructor's rule.
|
|
823
822
|
let constructorPermissions = (
|
|
824
823
|
~commandSchema: S.t<unknown>,
|
|
825
|
-
~
|
|
824
|
+
~authorizationOf: string => Reventless.Authorization.permission,
|
|
826
825
|
): array<Reventless.Authorization.permission> =>
|
|
827
|
-
Reventless.DcbTag.extractAllVariantNames(commandSchema)->Array.map(
|
|
828
|
-
syntheticCommand(~schema=commandSchema, variantName)->commandAuthorization
|
|
829
|
-
)
|
|
826
|
+
Reventless.DcbTag.extractAllVariantNames(commandSchema)->Array.map(authorizationOf)
|
|
830
827
|
|
|
831
828
|
// An inbound slice's gate where no command exists yet: any constructor's rule.
|
|
832
829
|
// Both the deployed gate and the local resolver apply this one.
|
|
833
|
-
let inboundDoorPermission = (~commandSchema, ~
|
|
834
|
-
constructorPermissions(~commandSchema, ~
|
|
830
|
+
let inboundDoorPermission = (~commandSchema, ~authorizationOf) =>
|
|
831
|
+
constructorPermissions(~commandSchema, ~authorizationOf)->Reventless.Authorization.anyOf
|
|
835
832
|
|
|
836
833
|
// Each mutation argument's GraphQL type onto its property, so a consumer declares
|
|
837
834
|
// the variable the server expects. Mutates the freshly derived schema in place.
|
|
@@ -860,13 +857,13 @@ let toCommandDef = (
|
|
|
860
857
|
~partitionKey: option<string>=?,
|
|
861
858
|
~mutationFieldFor: string => string,
|
|
862
859
|
~parentSchema: S.t<unknown>,
|
|
863
|
-
// The
|
|
864
|
-
// one aggregate carries commands with very different audiences,
|
|
865
|
-
// component-level shortcut would gate `AddProduct` and `PlaceOrder` alike.
|
|
866
|
-
~
|
|
867
|
-
// The spec's `command => Transition.t<_>`, evaluated per variant
|
|
868
|
-
//
|
|
869
|
-
//
|
|
860
|
+
// The spec's `authorizationOf`, by constructor name. Per VARIANT, not per
|
|
861
|
+
// component: one aggregate carries commands with very different audiences,
|
|
862
|
+
// and a component-level shortcut would gate `AddProduct` and `PlaceOrder` alike.
|
|
863
|
+
~authorizationOf: string => Reventless.Authorization.permission,
|
|
864
|
+
// The spec's `command => Transition.t<_>`, evaluated per variant against a
|
|
865
|
+
// stand-in, and the only declaration of an edge there is — the PPX injects
|
|
866
|
+
// `Undeclared` for a spec that writes no switch.
|
|
870
867
|
//
|
|
871
868
|
// Read at `t<string>` because this is the erasure boundary: the spec declares
|
|
872
869
|
// its edges over the linked view's own lifecycle enum, whose arms are
|
|
@@ -961,7 +958,7 @@ let toCommandDef = (
|
|
|
961
958
|
| Some(excluded) => !(excluded->Set.has(variantName))
|
|
962
959
|
| None => true
|
|
963
960
|
}
|
|
964
|
-
let rule =
|
|
961
|
+
let rule = authorizationOf(variantName)
|
|
965
962
|
let requiredAccess = accessKeysFor(rule)
|
|
966
963
|
// See the note on the record's `mutationField` for why a non-exposed
|
|
967
964
|
// variant gets the empty sentinel. It has no callable field, and the
|
|
@@ -1032,7 +1029,7 @@ let extractCommandDefs = (
|
|
|
1032
1029
|
~isAggregate,
|
|
1033
1030
|
~partitionKey: option<string>=?,
|
|
1034
1031
|
~mutationFieldFor: string => string,
|
|
1035
|
-
~
|
|
1032
|
+
~authorizationOf: string => Reventless.Authorization.permission,
|
|
1036
1033
|
~commandTransition: unknown => Reventless.Transition.t<string>,
|
|
1037
1034
|
~derivedEdgeFor: string => option<Reventless.Plugin.derivedEdge>=_ => None,
|
|
1038
1035
|
commandSchema: S.t<unknown>,
|
|
@@ -1046,7 +1043,7 @@ let extractCommandDefs = (
|
|
|
1046
1043
|
~partitionKey?,
|
|
1047
1044
|
~mutationFieldFor,
|
|
1048
1045
|
~parentSchema=commandSchema,
|
|
1049
|
-
~
|
|
1046
|
+
~authorizationOf,
|
|
1050
1047
|
~commandTransition,
|
|
1051
1048
|
~derivedEdgeFor,
|
|
1052
1049
|
v,
|
|
@@ -1060,7 +1057,7 @@ let extractCommandDefs = (
|
|
|
1060
1057
|
~partitionKey?,
|
|
1061
1058
|
~mutationFieldFor,
|
|
1062
1059
|
~parentSchema=commandSchema,
|
|
1063
|
-
~
|
|
1060
|
+
~authorizationOf,
|
|
1064
1061
|
~commandTransition,
|
|
1065
1062
|
~derivedEdgeFor,
|
|
1066
1063
|
commandSchema,
|
|
@@ -1719,8 +1716,7 @@ let make = (
|
|
|
1719
1716
|
~commandSchema=SCS.Spec.commandSchema->S.castToUnknown,
|
|
1720
1717
|
~variant=variantName,
|
|
1721
1718
|
),
|
|
1722
|
-
~
|
|
1723
|
-
SCS.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
|
|
1719
|
+
~authorizationOf=name => SCS.Spec.authorizationOf(name)->Reventless.Authorization.named,
|
|
1724
1720
|
~commandTransition=SCS.Spec.commandTransition->Obj.magic,
|
|
1725
1721
|
~derivedEdgeFor=derivedEdgeFor(~component=SCS.Spec.name, ...),
|
|
1726
1722
|
SCS.Spec.commandSchema->S.castToUnknown,
|
|
@@ -1754,8 +1750,7 @@ let make = (
|
|
|
1754
1750
|
~aggregate=A.Spec.name,
|
|
1755
1751
|
~command=variantName,
|
|
1756
1752
|
),
|
|
1757
|
-
~
|
|
1758
|
-
A.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
|
|
1753
|
+
~authorizationOf=name => A.Spec.authorizationOf(name)->Reventless.Authorization.named,
|
|
1759
1754
|
~commandTransition=A.Spec.commandTransition->Obj.magic,
|
|
1760
1755
|
~derivedEdgeFor=derivedEdgeFor(~component=A.Spec.name, ...),
|
|
1761
1756
|
A.Spec.commandSchema->S.castToUnknown,
|
|
@@ -1834,8 +1829,7 @@ let make = (
|
|
|
1834
1829
|
requiredRoles: ?(
|
|
1835
1830
|
constructorPermissions(
|
|
1836
1831
|
~commandSchema=ITS.Spec.commandSchema->S.castToUnknown,
|
|
1837
|
-
~
|
|
1838
|
-
ITS.Spec.commandAuthorization(cmd)->Reventless.Authorization.named,
|
|
1832
|
+
~authorizationOf=name => ITS.Spec.authorizationOf(name)->Reventless.Authorization.named,
|
|
1839
1833
|
)
|
|
1840
1834
|
->Array.flatMap(Reventless.Authorization.rolesOf)
|
|
1841
1835
|
->Array.map(role => (role :> string))
|
|
@@ -644,12 +644,12 @@ function syntheticCommand(schema, variantName) {
|
|
|
644
644
|
}
|
|
645
645
|
}
|
|
646
646
|
|
|
647
|
-
function constructorPermissions(commandSchema,
|
|
648
|
-
return DcbTag$Reventless.extractAllVariantNames(commandSchema).map(
|
|
647
|
+
function constructorPermissions(commandSchema, authorizationOf) {
|
|
648
|
+
return DcbTag$Reventless.extractAllVariantNames(commandSchema).map(authorizationOf);
|
|
649
649
|
}
|
|
650
650
|
|
|
651
|
-
function inboundDoorPermission(commandSchema,
|
|
652
|
-
return Authorization$Reventless.anyOf(
|
|
651
|
+
function inboundDoorPermission(commandSchema, authorizationOf) {
|
|
652
|
+
return Authorization$Reventless.anyOf(DcbTag$Reventless.extractAllVariantNames(commandSchema).map(authorizationOf));
|
|
653
653
|
}
|
|
654
654
|
|
|
655
655
|
function annotateArgTypes(schema, argTypes) {
|
|
@@ -666,7 +666,7 @@ function annotateArgTypes(schema, argTypes) {
|
|
|
666
666
|
return schema;
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
-
function toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, parentSchema,
|
|
669
|
+
function toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, parentSchema, authorizationOf, commandTransition, derivedEdgeFor, v) {
|
|
670
670
|
let mkDef = (variantName, properties) => {
|
|
671
671
|
let match = commandLevelAndId(isAggregate, partitionKey, variantName, properties);
|
|
672
672
|
let aggregateIdField = match[1];
|
|
@@ -708,7 +708,7 @@ function toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor
|
|
|
708
708
|
let excluded = ApiNoApiHelpers$ReventlessCore.getExcludedVariants(parentSchema);
|
|
709
709
|
apiExposed = excluded !== undefined ? !excluded.has(variantName) : true;
|
|
710
710
|
}
|
|
711
|
-
let rule =
|
|
711
|
+
let rule = authorizationOf(variantName);
|
|
712
712
|
let requiredAccess = accessKeysFor(rule);
|
|
713
713
|
let mutationField = apiExposed ? mutationFieldFor(variantName) : "";
|
|
714
714
|
let jsonSchema = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(apiExposed, mutationField, v);
|
|
@@ -753,12 +753,12 @@ function toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor
|
|
|
753
753
|
}
|
|
754
754
|
}
|
|
755
755
|
|
|
756
|
-
function extractCommandDefs(identityViews, isAggregate, partitionKey, mutationFieldFor,
|
|
756
|
+
function extractCommandDefs(identityViews, isAggregate, partitionKey, mutationFieldFor, authorizationOf, commandTransition, derivedEdgeForOpt, commandSchema) {
|
|
757
757
|
let derivedEdgeFor = derivedEdgeForOpt !== undefined ? derivedEdgeForOpt : param => {};
|
|
758
758
|
if (commandSchema.type === "anyOf") {
|
|
759
|
-
return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema,
|
|
759
|
+
return Stdlib_Array.filterMap(commandSchema.anyOf, v => toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema, authorizationOf, commandTransition, derivedEdgeFor, v));
|
|
760
760
|
} else {
|
|
761
|
-
return Stdlib_Option.mapOr(toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema,
|
|
761
|
+
return Stdlib_Option.mapOr(toCommandDef(identityViews, isAggregate, partitionKey, mutationFieldFor, commandSchema, authorizationOf, commandTransition, derivedEdgeFor, commandSchema), [], def => [def]);
|
|
762
762
|
}
|
|
763
763
|
}
|
|
764
764
|
|
|
@@ -1226,7 +1226,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1226
1226
|
let consumed = match$1[1];
|
|
1227
1227
|
return {
|
|
1228
1228
|
name: SCS.Spec.name,
|
|
1229
|
-
commands: extractCommandDefs(identityViews, false, partitionBySlice[SCS.Spec.name], variantName => Api_Naming$ReventlessCore.sliceMutationFieldFor(name, SCS.Spec.name, SCS.Spec.commandSchema, variantName),
|
|
1229
|
+
commands: extractCommandDefs(identityViews, false, partitionBySlice[SCS.Spec.name], variantName => Api_Naming$ReventlessCore.sliceMutationFieldFor(name, SCS.Spec.name, SCS.Spec.commandSchema, variantName), name => Authorization$Reventless.named(SCS.Spec.authorizationOf(name)), SCS.Spec.commandTransition, extra => derivedEdgeFor(SCS.Spec.name, extra), SCS.Spec.commandSchema),
|
|
1230
1230
|
producedEventTypes: produced,
|
|
1231
1231
|
consumedEventTypes: consumed,
|
|
1232
1232
|
linkedViews: linkedSvsFor(produced),
|
|
@@ -1241,7 +1241,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1241
1241
|
let produced = match[1];
|
|
1242
1242
|
return {
|
|
1243
1243
|
name: A.Spec.name,
|
|
1244
|
-
commands: extractCommandDefs(identityViews, true, undefined, variantName => Api_Naming$ReventlessCore.aggregateMutationField(name, A.Spec.name, variantName),
|
|
1244
|
+
commands: extractCommandDefs(identityViews, true, undefined, variantName => Api_Naming$ReventlessCore.aggregateMutationField(name, A.Spec.name, variantName), name => Authorization$Reventless.named(A.Spec.authorizationOf(name)), A.Spec.commandTransition, extra => derivedEdgeFor(A.Spec.name, extra), A.Spec.commandSchema),
|
|
1245
1245
|
producedEventTypes: produced,
|
|
1246
1246
|
consumedEventTypes: [],
|
|
1247
1247
|
linkedViews: linkedSvsFor(produced).concat(linkedReadModelsFor(A.Spec.name)),
|
|
@@ -1276,7 +1276,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
1276
1276
|
targetName: ITS.Spec.targetName,
|
|
1277
1277
|
externalSystem: ITS.Spec.externalSystem,
|
|
1278
1278
|
chapter: componentChapters[ITS.Spec.name],
|
|
1279
|
-
requiredRoles: nonEmpty(Array.from(new Set(
|
|
1279
|
+
requiredRoles: nonEmpty(Array.from(new Set(DcbTag$Reventless.extractAllVariantNames(ITS.Spec.commandSchema).map(name => Authorization$Reventless.named(ITS.Spec.authorizationOf(name))).flatMap(Authorization$Reventless.rolesOf).map(role => role)).values()))
|
|
1280
1280
|
}));
|
|
1281
1281
|
let tableFailures = [];
|
|
1282
1282
|
let tableWarnings = [];
|