@reventlessdev/reventless-local 3.0.0-alpha.281 → 3.0.0-alpha.283
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 +18 -0
- package/package.json +9 -9
- package/src/Platform.res +5 -4
- package/src/Platform.res.mjs +12 -12
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +6 -25
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res.mjs +4 -20
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +34 -38
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res.mjs +14 -6
- package/src/adapter/PlatformGraphQL_Server.res +1 -1
- package/tests/adapter/CommandAuthorizationTest.res +7 -8
- package/tests/adapter/CommandAuthorizationTest.res.mjs +5 -5
- package/tests/adapter/InboundTranslationMutationTest.res +95 -7
- package/tests/adapter/InboundTranslationMutationTest.res.mjs +97 -9
- package/tests/components/aggregate/AggregateFixtures.res.mjs +3 -3
- package/tests/components/aggregate/AggregateSelfMappingFixtures.res.mjs +3 -3
- package/tests/components/automationslice/AutomationSliceFixtures.res.mjs +4 -4
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res.mjs +8 -8
- package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res.mjs +2 -2
- package/tests/components/commandgenerator/CommandGeneratorFixtures.res.mjs +3 -3
- package/tests/components/commandtopic/CommandTopicFixtures.res.mjs +2 -2
- package/tests/components/commandtopic/CommandTopicStreamFixtures.res.mjs +2 -2
- package/tests/components/dcb/DcbCrossPartitionFixtures.res.mjs +3 -3
- package/tests/components/dcb/DcbFixtures.res.mjs +3 -3
- package/tests/components/extensionpoint/ExtensionPointFixtures.res.mjs +5 -5
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +116 -8
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs +160 -19
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +58 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res.mjs +145 -7
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res.mjs +3 -3
- package/tests/components/readmodel/DcbReadModelE2EFixtures.res.mjs +3 -3
- package/tests/plugin/PluginBehavior_GWT.res.mjs +2 -2
- package/tests/plugin/UiFragmentRegistry_GWT.res.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
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.283 (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
|
+
|
|
17
|
+
# 3.0.0-alpha.282 (2026-10-10)
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* an inbound translation is gated alike on every door, and encodes its commands by schema ([6ec3ce6](https://github.com/ReventlessDev/reventless-core/commit/6ec3ce672e53e2c162c64a90d103be691391be5e))
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
# 3.0.0-alpha.281 (2026-10-08)
|
|
7
25
|
|
|
8
26
|
**Note:** Version bump only for package @reventlessdev/reventless-local
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-local",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.283",
|
|
4
4
|
"description": "Local platform for Reventless (in-memory or SQLite backend, for development and testing without AWS)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -37,21 +37,21 @@
|
|
|
37
37
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
38
38
|
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.29",
|
|
39
39
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
40
|
-
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.23",
|
|
41
40
|
"@reventlessdev/rescript-node": "2.0.0-alpha.22",
|
|
41
|
+
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.23",
|
|
42
42
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.23",
|
|
43
|
-
"@reventlessdev/reventless-
|
|
44
|
-
"@reventlessdev/reventless-
|
|
45
|
-
"@reventlessdev/reventless-gwt": "1.0.0-alpha.
|
|
46
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
47
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.155",
|
|
43
|
+
"@reventlessdev/reventless-graphql-server": "1.0.0-alpha.141",
|
|
44
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.293",
|
|
45
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.240",
|
|
46
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.190",
|
|
48
47
|
"@reventlessdev/reventless-seed": "1.0.0-alpha.33",
|
|
49
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
48
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.162",
|
|
49
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.157"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"rescript": "12.3.1",
|
|
53
53
|
"sury-ppx": "11.0.0",
|
|
54
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
54
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.99"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"rescript": "^12.3.1"
|
package/src/Platform.res
CHANGED
|
@@ -290,14 +290,14 @@ module MakeWithConfig = (
|
|
|
290
290
|
deployTarget: ref("Domain"),
|
|
291
291
|
// Phase 1: register SDL + resolver stub synchronously.
|
|
292
292
|
// Pass the resolved server so the correct target (domain or platform) receives the schema.
|
|
293
|
-
mutationResolverHook: (~kind, ~fields, ~commandSchema, ~
|
|
293
|
+
mutationResolverHook: (~kind, ~fields, ~commandSchema, ~authorizationOf) => {
|
|
294
294
|
let server = resolveTargetGraphQL()
|
|
295
295
|
switch kind {
|
|
296
296
|
| ReventlessCore.Plugin_Helpers.Aggregate =>
|
|
297
297
|
CommandGeneratorResolvers_GraphQL.register(
|
|
298
298
|
~fields,
|
|
299
299
|
~commandSchema,
|
|
300
|
-
~
|
|
300
|
+
~authorizationOf,
|
|
301
301
|
~server,
|
|
302
302
|
)
|
|
303
303
|
| Dcb =>
|
|
@@ -305,7 +305,7 @@ module MakeWithConfig = (
|
|
|
305
305
|
CommandGeneratorResolvers_GraphQL.registerDcb(
|
|
306
306
|
~fieldName=field,
|
|
307
307
|
~commandSchema,
|
|
308
|
-
~
|
|
308
|
+
~authorizationOf,
|
|
309
309
|
~server,
|
|
310
310
|
)
|
|
311
311
|
)
|
|
@@ -314,10 +314,11 @@ module MakeWithConfig = (
|
|
|
314
314
|
// Phase 2: bind generateCommand when Output.apply resolves.
|
|
315
315
|
mutationBindHook: CommandGeneratorResolvers_GraphQL.bindHandler,
|
|
316
316
|
// InboundTranslationSlice hooks — pass resolved server for correct target routing.
|
|
317
|
-
inboundMutationResolverHook: (~fieldName, ~externalInputSchema) =>
|
|
317
|
+
inboundMutationResolverHook: (~fieldName, ~externalInputSchema, ~permission=?) =>
|
|
318
318
|
InboundTranslationResolvers_GraphQL.register(
|
|
319
319
|
~fieldName,
|
|
320
320
|
~externalInputSchema,
|
|
321
|
+
~permission?,
|
|
321
322
|
~server=resolveTargetGraphQL(),
|
|
322
323
|
),
|
|
323
324
|
inboundMutationBindReceiveHook: InboundTranslationResolvers_GraphQL.bindReceive,
|
package/src/Platform.res.mjs
CHANGED
|
@@ -225,15 +225,15 @@ function MakeWithConfig(Config) {
|
|
|
225
225
|
}
|
|
226
226
|
};
|
|
227
227
|
let EventHistoryResolvers = EventHistoryResolvers_GraphQL$ReventlessLocal.Make(Bus);
|
|
228
|
-
let hooks_mutationResolverHook = (kind, fields, commandSchema,
|
|
228
|
+
let hooks_mutationResolverHook = (kind, fields, commandSchema, authorizationOf) => {
|
|
229
229
|
let server = resolveTargetGraphQL();
|
|
230
230
|
if (kind === "Aggregate") {
|
|
231
|
-
return CommandGeneratorResolvers_GraphQL$ReventlessLocal.register(fields, commandSchema,
|
|
231
|
+
return CommandGeneratorResolvers_GraphQL$ReventlessLocal.register(fields, commandSchema, authorizationOf, server);
|
|
232
232
|
}
|
|
233
|
-
fields.forEach(field => CommandGeneratorResolvers_GraphQL$ReventlessLocal.registerDcb(field, commandSchema,
|
|
233
|
+
fields.forEach(field => CommandGeneratorResolvers_GraphQL$ReventlessLocal.registerDcb(field, commandSchema, authorizationOf, server));
|
|
234
234
|
};
|
|
235
235
|
let hooks_mutationBindHook = CommandGeneratorResolvers_GraphQL$ReventlessLocal.bindHandler;
|
|
236
|
-
let hooks_inboundMutationResolverHook = (fieldName, externalInputSchema) => InboundTranslationResolvers_GraphQL$ReventlessLocal.register(fieldName, externalInputSchema, resolveTargetGraphQL());
|
|
236
|
+
let hooks_inboundMutationResolverHook = (fieldName, externalInputSchema, permission) => InboundTranslationResolvers_GraphQL$ReventlessLocal.register(fieldName, externalInputSchema, permission, resolveTargetGraphQL());
|
|
237
237
|
let hooks_inboundMutationBindReceiveHook = InboundTranslationResolvers_GraphQL$ReventlessLocal.bindReceive;
|
|
238
238
|
let hooks_schemaTypeRegistrationHook = sdlTypes => resolveTargetGraphQL().registerTypes(sdlTypes);
|
|
239
239
|
let hooks_mcpSchemaRegistrationHook = param => {
|
|
@@ -649,7 +649,7 @@ function MakeWithConfig(Config) {
|
|
|
649
649
|
errorSchema: UiFragmentRegistry$ReventlessCore.errorSchema,
|
|
650
650
|
eventSchema: UiFragmentRegistry$ReventlessCore.eventSchema,
|
|
651
651
|
commandSchema: UiFragmentRegistry$ReventlessCore.commandSchema,
|
|
652
|
-
|
|
652
|
+
authorizationOf: UiFragmentRegistry$ReventlessCore.authorizationOf,
|
|
653
653
|
commandTransition: UiFragmentRegistry$ReventlessCore.commandTransition,
|
|
654
654
|
traits: UiFragmentRegistry$ReventlessCore.traits,
|
|
655
655
|
readConsistency: UiFragmentRegistry$ReventlessCore.readConsistency
|
|
@@ -761,7 +761,7 @@ function MakeWithConfig(Config) {
|
|
|
761
761
|
errorSchema: PluginSpec$ReventlessCore.errorSchema,
|
|
762
762
|
commandSchema: PluginSpec$ReventlessCore.commandSchema,
|
|
763
763
|
moduleUrl: PluginSpec$ReventlessCore.moduleUrl,
|
|
764
|
-
|
|
764
|
+
authorizationOf: PluginSpec$ReventlessCore.authorizationOf,
|
|
765
765
|
commandTransition: PluginSpec$ReventlessCore.commandTransition,
|
|
766
766
|
traits: PluginSpec$ReventlessCore.traits
|
|
767
767
|
})({
|
|
@@ -1985,15 +1985,15 @@ function Make($star) {
|
|
|
1985
1985
|
}
|
|
1986
1986
|
};
|
|
1987
1987
|
let EventHistoryResolvers = EventHistoryResolvers_GraphQL$ReventlessLocal.Make(Bus);
|
|
1988
|
-
let hooks_mutationResolverHook = (kind, fields, commandSchema,
|
|
1988
|
+
let hooks_mutationResolverHook = (kind, fields, commandSchema, authorizationOf) => {
|
|
1989
1989
|
let server = resolveTargetGraphQL();
|
|
1990
1990
|
if (kind === "Aggregate") {
|
|
1991
|
-
return CommandGeneratorResolvers_GraphQL$ReventlessLocal.register(fields, commandSchema,
|
|
1991
|
+
return CommandGeneratorResolvers_GraphQL$ReventlessLocal.register(fields, commandSchema, authorizationOf, server);
|
|
1992
1992
|
}
|
|
1993
|
-
fields.forEach(field => CommandGeneratorResolvers_GraphQL$ReventlessLocal.registerDcb(field, commandSchema,
|
|
1993
|
+
fields.forEach(field => CommandGeneratorResolvers_GraphQL$ReventlessLocal.registerDcb(field, commandSchema, authorizationOf, server));
|
|
1994
1994
|
};
|
|
1995
1995
|
let hooks_mutationBindHook = CommandGeneratorResolvers_GraphQL$ReventlessLocal.bindHandler;
|
|
1996
|
-
let hooks_inboundMutationResolverHook = (fieldName, externalInputSchema) => InboundTranslationResolvers_GraphQL$ReventlessLocal.register(fieldName, externalInputSchema, resolveTargetGraphQL());
|
|
1996
|
+
let hooks_inboundMutationResolverHook = (fieldName, externalInputSchema, permission) => InboundTranslationResolvers_GraphQL$ReventlessLocal.register(fieldName, externalInputSchema, permission, resolveTargetGraphQL());
|
|
1997
1997
|
let hooks_inboundMutationBindReceiveHook = InboundTranslationResolvers_GraphQL$ReventlessLocal.bindReceive;
|
|
1998
1998
|
let hooks_schemaTypeRegistrationHook = sdlTypes => resolveTargetGraphQL().registerTypes(sdlTypes);
|
|
1999
1999
|
let hooks_mcpSchemaRegistrationHook = param => {
|
|
@@ -2409,7 +2409,7 @@ function Make($star) {
|
|
|
2409
2409
|
errorSchema: UiFragmentRegistry$ReventlessCore.errorSchema,
|
|
2410
2410
|
eventSchema: UiFragmentRegistry$ReventlessCore.eventSchema,
|
|
2411
2411
|
commandSchema: UiFragmentRegistry$ReventlessCore.commandSchema,
|
|
2412
|
-
|
|
2412
|
+
authorizationOf: UiFragmentRegistry$ReventlessCore.authorizationOf,
|
|
2413
2413
|
commandTransition: UiFragmentRegistry$ReventlessCore.commandTransition,
|
|
2414
2414
|
traits: UiFragmentRegistry$ReventlessCore.traits,
|
|
2415
2415
|
readConsistency: UiFragmentRegistry$ReventlessCore.readConsistency
|
|
@@ -2521,7 +2521,7 @@ function Make($star) {
|
|
|
2521
2521
|
errorSchema: PluginSpec$ReventlessCore.errorSchema,
|
|
2522
2522
|
commandSchema: PluginSpec$ReventlessCore.commandSchema,
|
|
2523
2523
|
moduleUrl: PluginSpec$ReventlessCore.moduleUrl,
|
|
2524
|
-
|
|
2524
|
+
authorizationOf: PluginSpec$ReventlessCore.authorizationOf,
|
|
2525
2525
|
commandTransition: PluginSpec$ReventlessCore.commandTransition,
|
|
2526
2526
|
traits: PluginSpec$ReventlessCore.traits
|
|
2527
2527
|
})({
|
|
@@ -83,17 +83,6 @@ let checkPluginStatus = (~field: string): option<JSON.t> =>
|
|
|
83
83
|
| None => None
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
// Build a synthetic command value sufficient for evaluating
|
|
87
|
-
// `commandAuthorization`. ReScript variants with record payloads compile to
|
|
88
|
-
// `{TAG: cname, ...payload}` and the PPX-generated switch matches `Ctor(_)`
|
|
89
|
-
// which only checks `command.TAG === cname`. Payload-less constructors
|
|
90
|
-
// compile to bare string literals and the switch matches them with
|
|
91
|
-
// `command === "Cname"`, so the synthetic value must be a bare string in
|
|
92
|
-
// that case — otherwise the wildcard branch (file-level default) wins
|
|
93
|
-
// instead of the per-constructor rule.
|
|
94
|
-
let syntheticCommand = (cname: string, ~hasPayload: bool): unknown =>
|
|
95
|
-
hasPayload ? {"TAG": cname}->Obj.magic : cname->Obj.magic
|
|
96
|
-
|
|
97
86
|
let capitalize = s => s->String.charAt(0)->String.toUpperCase ++ s->String.slice(~start=1)
|
|
98
87
|
|
|
99
88
|
// -- CommandResult SDL types --------------------------------------------------
|
|
@@ -139,7 +128,7 @@ let extractVariantSchema = (commandSchema: S.t<unknown>, ~index=0) =>
|
|
|
139
128
|
// stale `_0` payload arg into the SDL. Looking up by name avoids that.
|
|
140
129
|
let variantIndexForField = (commandSchema: S.t<unknown>, ~field: string): int => {
|
|
141
130
|
let cname = extractCommandName(field)
|
|
142
|
-
let allNames = Reventless.DcbTag.extractAllVariantNames(commandSchema
|
|
131
|
+
let allNames = Reventless.DcbTag.extractAllVariantNames(commandSchema)
|
|
143
132
|
allNames->Array.indexOf(cname)
|
|
144
133
|
}
|
|
145
134
|
|
|
@@ -188,7 +177,7 @@ let runCommand = async (
|
|
|
188
177
|
let register = (
|
|
189
178
|
~fields: array<string>,
|
|
190
179
|
~commandSchema: S.t<unknown>,
|
|
191
|
-
~
|
|
180
|
+
~authorizationOf: string => Reventless.Authorization.permission,
|
|
192
181
|
~server: ReventlessGraphqlServer.GraphQL_ServerInstance.t,
|
|
193
182
|
) => {
|
|
194
183
|
ensureCommandResultTypes(server)
|
|
@@ -210,13 +199,6 @@ let register = (
|
|
|
210
199
|
fields->Array.forEach(field => {
|
|
211
200
|
let handlerRef = ref(None)
|
|
212
201
|
handlerRefs->Dict.set(field, handlerRef)
|
|
213
|
-
// hasPayload is fixed per field — capture once at registration time so
|
|
214
|
-
// the resolver doesn't re-walk the schema per request.
|
|
215
|
-
let variantIndex = variantIndexForField(commandSchema, ~field)
|
|
216
|
-
let hasPayload = switch extractVariantSchema(commandSchema, ~index=variantIndex) {
|
|
217
|
-
| Object(_) => true
|
|
218
|
-
| _ => false
|
|
219
|
-
}
|
|
220
202
|
let commandName = extractCommandName(field)
|
|
221
203
|
let resolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (
|
|
222
204
|
_root,
|
|
@@ -229,7 +211,7 @@ let register = (
|
|
|
229
211
|
| Some(rejected) => rejected
|
|
230
212
|
| None =>
|
|
231
213
|
let identity = extractIdentity(ctx)
|
|
232
|
-
let rule =
|
|
214
|
+
let rule = authorizationOf(commandName)
|
|
233
215
|
if !Reventless.Authorization.isAllowed(rule, identity) {
|
|
234
216
|
rejectForbidden(~field)
|
|
235
217
|
} else {
|
|
@@ -262,7 +244,7 @@ let register = (
|
|
|
262
244
|
let registerDcb = (
|
|
263
245
|
~fieldName: string,
|
|
264
246
|
~commandSchema: S.t<unknown>,
|
|
265
|
-
~
|
|
247
|
+
~authorizationOf: string => Reventless.Authorization.permission,
|
|
266
248
|
~server: ReventlessGraphqlServer.GraphQL_ServerInstance.t,
|
|
267
249
|
) => {
|
|
268
250
|
ensureCommandResultTypes(server)
|
|
@@ -271,7 +253,7 @@ let registerDcb = (
|
|
|
271
253
|
// constructor (slice name == constructor); a multi-command slice's `Plugin_Ctor`
|
|
272
254
|
// resolves to `Ctor`. Position 0 was previously hardcoded, which dropped every
|
|
273
255
|
// non-first constructor of a multi-command slice.
|
|
274
|
-
let constructorNames = Reventless.DcbTag.extractAllVariantNames(commandSchema
|
|
256
|
+
let constructorNames = Reventless.DcbTag.extractAllVariantNames(commandSchema)
|
|
275
257
|
let variantIndex = variantIndexForField(commandSchema, ~field=fieldName)
|
|
276
258
|
let variantSchema = extractVariantSchema(
|
|
277
259
|
commandSchema,
|
|
@@ -284,7 +266,6 @@ let registerDcb = (
|
|
|
284
266
|
(
|
|
285
267
|
variantIndex >= 0 ? constructorNames->Array.get(variantIndex) : constructorNames->Array.get(0)
|
|
286
268
|
)->Option.getOr(fieldName)
|
|
287
|
-
let hasPayload = Reventless.DcbTag.isVariantPayloadBearing(commandSchema->Obj.magic, tag)
|
|
288
269
|
|
|
289
270
|
let handlerRef = ref(None)
|
|
290
271
|
handlerRefs->Dict.set(fieldName, handlerRef)
|
|
@@ -300,7 +281,7 @@ let registerDcb = (
|
|
|
300
281
|
| Some(rejected) => rejected
|
|
301
282
|
| None =>
|
|
302
283
|
let identity = extractIdentity(ctx)
|
|
303
|
-
let rule =
|
|
284
|
+
let rule = authorizationOf(tag)
|
|
304
285
|
if !Reventless.Authorization.isAllowed(rule, identity) {
|
|
305
286
|
rejectForbidden(~field=fieldName)
|
|
306
287
|
} else {
|
|
@@ -93,16 +93,6 @@ function checkPluginStatus(field) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
function syntheticCommand(cname, hasPayload) {
|
|
97
|
-
if (hasPayload) {
|
|
98
|
-
return {
|
|
99
|
-
TAG: cname
|
|
100
|
-
};
|
|
101
|
-
} else {
|
|
102
|
-
return cname;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
96
|
function capitalize(s) {
|
|
107
97
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
108
98
|
}
|
|
@@ -156,7 +146,7 @@ async function runCommand(generateCommand, payload) {
|
|
|
156
146
|
}
|
|
157
147
|
}
|
|
158
148
|
|
|
159
|
-
function register(fields, commandSchema,
|
|
149
|
+
function register(fields, commandSchema, authorizationOf, server) {
|
|
160
150
|
server.registerTypes([commandResultSdl]);
|
|
161
151
|
let sdlFields = fields.map(field => {
|
|
162
152
|
let variantIndex = variantIndexForField(commandSchema, field);
|
|
@@ -173,10 +163,6 @@ function register(fields, commandSchema, commandAuthorization, server) {
|
|
|
173
163
|
contents: undefined
|
|
174
164
|
};
|
|
175
165
|
handlerRefs[field] = handlerRef;
|
|
176
|
-
let variantIndex = variantIndexForField(commandSchema, field);
|
|
177
|
-
let match = extractVariantSchema(commandSchema, variantIndex);
|
|
178
|
-
let hasPayload;
|
|
179
|
-
hasPayload = match.type === "object";
|
|
180
166
|
let commandName = extractCommandName(field);
|
|
181
167
|
let resolver = async (_root, args, ctx) => {
|
|
182
168
|
let generateCommand = handlerRef.contents;
|
|
@@ -188,7 +174,7 @@ function register(fields, commandSchema, commandAuthorization, server) {
|
|
|
188
174
|
return rejected;
|
|
189
175
|
}
|
|
190
176
|
let identity = extractIdentity(ctx);
|
|
191
|
-
let rule =
|
|
177
|
+
let rule = authorizationOf(commandName);
|
|
192
178
|
if (!Authorization$Reventless.isAllowed(rule, identity)) {
|
|
193
179
|
return rejectForbidden(field);
|
|
194
180
|
}
|
|
@@ -210,14 +196,13 @@ function register(fields, commandSchema, commandAuthorization, server) {
|
|
|
210
196
|
server.registerMutations(sdlFields, resolvers);
|
|
211
197
|
}
|
|
212
198
|
|
|
213
|
-
function registerDcb(fieldName, commandSchema,
|
|
199
|
+
function registerDcb(fieldName, commandSchema, authorizationOf, server) {
|
|
214
200
|
server.registerTypes([commandResultSdl]);
|
|
215
201
|
let constructorNames = DcbTag$Reventless.extractAllVariantNames(commandSchema);
|
|
216
202
|
let variantIndex = variantIndexForField(commandSchema, fieldName);
|
|
217
203
|
let variantSchema = extractVariantSchema(commandSchema, variantIndex >= 0 ? variantIndex : 0);
|
|
218
204
|
let sdlFields = [deriveSdlField(fieldName, variantSchema)];
|
|
219
205
|
let tag = Stdlib_Option.getOr(variantIndex >= 0 ? constructorNames[variantIndex] : constructorNames[0], fieldName);
|
|
220
|
-
let hasPayload = DcbTag$Reventless.isVariantPayloadBearing(commandSchema, tag);
|
|
221
206
|
let handlerRef = {
|
|
222
207
|
contents: undefined
|
|
223
208
|
};
|
|
@@ -232,7 +217,7 @@ function registerDcb(fieldName, commandSchema, commandAuthorization, server) {
|
|
|
232
217
|
return rejected;
|
|
233
218
|
}
|
|
234
219
|
let identity = extractIdentity(ctx);
|
|
235
|
-
let rule =
|
|
220
|
+
let rule = authorizationOf(tag);
|
|
236
221
|
if (!Authorization$Reventless.isAllowed(rule, identity)) {
|
|
237
222
|
return rejectForbidden(fieldName);
|
|
238
223
|
}
|
|
@@ -296,7 +281,6 @@ export {
|
|
|
296
281
|
resetPluginStatusGate,
|
|
297
282
|
rejectPluginStatus,
|
|
298
283
|
checkPluginStatus,
|
|
299
|
-
syntheticCommand,
|
|
300
284
|
capitalize,
|
|
301
285
|
commandResultSdl,
|
|
302
286
|
ensureCommandResultTypes,
|
|
@@ -1,39 +1,35 @@
|
|
|
1
|
-
// GraphQL mutation resolvers for in-memory InboundTranslationSlices
|
|
2
|
-
//
|
|
3
|
-
// Each resolver calls `receive(argsJson)` directly — the InboundTranslationSlice
|
|
4
|
-
// handles parsing, validation, translation, and command publishing internally.
|
|
1
|
+
// GraphQL mutation resolvers for in-memory InboundTranslationSlices: one field per
|
|
2
|
+
// slice, gated by the door's rule and handing the caller on to `receive`.
|
|
5
3
|
|
|
6
4
|
@@warning("-44")
|
|
7
5
|
open ReventlessCore
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
type receive = (
|
|
8
|
+
JSON.t,
|
|
9
|
+
~caller: Reventless.Identity.t=?,
|
|
10
|
+
) => promise<ReventlessInfra.InboundTranslationSlice.receiveResult>
|
|
11
|
+
|
|
12
|
+
// fieldName → receive. Pre-populated with a queuing forwarder so a call made
|
|
13
|
+
// before `bindReceive` parks until it runs.
|
|
14
|
+
let receiveRegistry: dict<receive> = Dict.make()
|
|
16
15
|
|
|
17
16
|
type pendingCall = {
|
|
18
17
|
inputJson: JSON.t,
|
|
18
|
+
caller: option<Reventless.Identity.t>,
|
|
19
19
|
resolve: ReventlessInfra.InboundTranslationSlice.receiveResult => unit,
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
// Per-field pending-call queues — drained when bindReceive fires.
|
|
23
22
|
let pendingQueueRegistry: dict<ref<array<pendingCall>>> = Dict.make()
|
|
24
23
|
|
|
25
|
-
// Phase 1:
|
|
26
|
-
//
|
|
27
|
-
// invoke receive immediately; calls are parked until bindReceive drains them.
|
|
24
|
+
// Phase 1: register SDL + resolver stub synchronously (before server starts).
|
|
25
|
+
// `permission` is the door's rule; without one the field admits nobody.
|
|
28
26
|
let register = (
|
|
29
27
|
~fieldName: string,
|
|
30
28
|
~externalInputSchema: S.t<unknown>,
|
|
29
|
+
~permission: option<Reventless.Authorization.permission>=?,
|
|
31
30
|
~server: ReventlessGraphqlServer.GraphQL_ServerInstance.t,
|
|
32
31
|
) => {
|
|
33
|
-
//
|
|
34
|
-
// this scope's document. Registered here rather than relying on a plugin's
|
|
35
|
-
// command handlers having registered them first — a plugin whose only mutation
|
|
36
|
-
// is an inbound translation must not depend on registration order.
|
|
32
|
+
// Registered here, so a plugin whose only mutation is inbound has its result union.
|
|
37
33
|
CommandGeneratorResolvers_GraphQL.ensureCommandResultTypes(server)
|
|
38
34
|
|
|
39
35
|
let sdlFields = switch GraphQL_FragmentGenerator.deriveMutationFieldFromObject(
|
|
@@ -46,28 +42,32 @@ let register = (
|
|
|
46
42
|
| None => [` ${fieldName}: CommandResult!`]
|
|
47
43
|
}
|
|
48
44
|
|
|
49
|
-
// Queue of pending calls when receive is not yet bound.
|
|
50
45
|
let pendingQueue: ref<array<pendingCall>> = ref([])
|
|
51
46
|
pendingQueueRegistry->Dict.set(fieldName, pendingQueue)
|
|
52
47
|
|
|
53
|
-
|
|
54
|
-
let queuingReceive = (inputJson: JSON.t) =>
|
|
48
|
+
let queuingReceive: receive = (inputJson, ~caller=?) =>
|
|
55
49
|
Promise.make((resolve, _reject) => {
|
|
56
|
-
pendingQueue.contents->Array.push({inputJson, resolve})
|
|
50
|
+
pendingQueue.contents->Array.push({inputJson, caller, resolve})
|
|
57
51
|
})
|
|
58
52
|
receiveRegistry->Dict.set(fieldName, queuingReceive)
|
|
59
53
|
|
|
60
54
|
let resolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (
|
|
61
55
|
_root,
|
|
62
56
|
args,
|
|
63
|
-
|
|
57
|
+
ctx,
|
|
64
58
|
) => {
|
|
65
|
-
let
|
|
66
|
-
let
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
let caller = CommandGeneratorResolvers_GraphQL.extractIdentity(ctx)
|
|
60
|
+
let admitted =
|
|
61
|
+
permission->Option.mapOr(false, rule => Reventless.Authorization.isAllowed(rule, caller))
|
|
62
|
+
if !admitted {
|
|
63
|
+
CommandGeneratorResolvers_GraphQL.rejectForbidden(~field=fieldName)
|
|
64
|
+
} else {
|
|
65
|
+
let receive = receiveRegistry->Dict.getUnsafe(fieldName)
|
|
66
|
+
let result = await receive(args->Obj.magic, ~caller)
|
|
67
|
+
result
|
|
68
|
+
->InboundTranslationSlice_Callback.receiveResultToOutcome
|
|
69
|
+
->CommandTopic.commandOutcomeToJson
|
|
70
|
+
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
let resolvers = Dict.make()
|
|
@@ -75,19 +75,15 @@ let register = (
|
|
|
75
75
|
server.registerMutations(~sdlFields, ~resolvers)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
// Phase 2:
|
|
79
|
-
|
|
80
|
-
let bindReceive = (
|
|
81
|
-
~fieldName: string,
|
|
82
|
-
~receive: JSON.t => promise<ReventlessInfra.InboundTranslationSlice.receiveResult>,
|
|
83
|
-
) => {
|
|
78
|
+
// Phase 2: bind the real receive, replacing the forwarder and draining its queue.
|
|
79
|
+
let bindReceive = (~fieldName: string, ~receive: receive) => {
|
|
84
80
|
receiveRegistry->Dict.set(fieldName, receive)
|
|
85
81
|
switch pendingQueueRegistry->Dict.get(fieldName) {
|
|
86
82
|
| Some(pendingQueue) =>
|
|
87
83
|
let pending = pendingQueue.contents
|
|
88
84
|
pendingQueue.contents = []
|
|
89
|
-
pending->Array.forEach(({inputJson, resolve}) => {
|
|
90
|
-
let _ = receive(inputJson)->Promise.thenResolve(result => resolve(result))
|
|
85
|
+
pending->Array.forEach(({inputJson, caller, resolve}) => {
|
|
86
|
+
let _ = receive(inputJson, ~caller?)->Promise.thenResolve(result => resolve(result))
|
|
91
87
|
})
|
|
92
88
|
| None => ()
|
|
93
89
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as Authorization$Reventless from "@reventlessdev/reventless-spec/src/types/Authorization.res.mjs";
|
|
3
5
|
import * as CommandTopic$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic.res.mjs";
|
|
4
6
|
import * as GraphQL_FragmentGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
|
|
5
7
|
import * as InboundTranslationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs";
|
|
@@ -9,7 +11,7 @@ let receiveRegistry = {};
|
|
|
9
11
|
|
|
10
12
|
let pendingQueueRegistry = {};
|
|
11
13
|
|
|
12
|
-
function register(fieldName, externalInputSchema, server) {
|
|
14
|
+
function register(fieldName, externalInputSchema, permission, server) {
|
|
13
15
|
CommandGeneratorResolvers_GraphQL$ReventlessLocal.ensureCommandResultTypes(server);
|
|
14
16
|
let field = GraphQL_FragmentGenerator$ReventlessCore.deriveMutationFieldFromObject(fieldName, [], new Set(), externalInputSchema);
|
|
15
17
|
let sdlFields = field !== undefined ? [field] : [` ` + fieldName + `: CommandResult!`];
|
|
@@ -17,16 +19,22 @@ function register(fieldName, externalInputSchema, server) {
|
|
|
17
19
|
contents: []
|
|
18
20
|
};
|
|
19
21
|
pendingQueueRegistry[fieldName] = pendingQueue;
|
|
20
|
-
let queuingReceive = inputJson => new Promise((resolve, _reject) => {
|
|
22
|
+
let queuingReceive = (inputJson, caller) => new Promise((resolve, _reject) => {
|
|
21
23
|
pendingQueue.contents.push({
|
|
22
24
|
inputJson: inputJson,
|
|
25
|
+
caller: caller,
|
|
23
26
|
resolve: resolve
|
|
24
27
|
});
|
|
25
28
|
});
|
|
26
29
|
receiveRegistry[fieldName] = queuingReceive;
|
|
27
|
-
let resolver = async (_root, args,
|
|
30
|
+
let resolver = async (_root, args, ctx) => {
|
|
31
|
+
let caller = CommandGeneratorResolvers_GraphQL$ReventlessLocal.extractIdentity(ctx);
|
|
32
|
+
let admitted = Stdlib_Option.mapOr(permission, false, rule => Authorization$Reventless.isAllowed(rule, caller));
|
|
33
|
+
if (!admitted) {
|
|
34
|
+
return CommandGeneratorResolvers_GraphQL$ReventlessLocal.rejectForbidden(fieldName);
|
|
35
|
+
}
|
|
28
36
|
let receive = receiveRegistry[fieldName];
|
|
29
|
-
let result = await receive(args);
|
|
37
|
+
let result = await receive(args, caller);
|
|
30
38
|
return CommandTopic$ReventlessCore.commandOutcomeToJson(InboundTranslationSlice_Callback$ReventlessCore.receiveResultToOutcome(result));
|
|
31
39
|
};
|
|
32
40
|
let resolvers = {};
|
|
@@ -44,7 +52,7 @@ function bindReceive(fieldName, receive) {
|
|
|
44
52
|
pendingQueue.contents = [];
|
|
45
53
|
pending.forEach(param => {
|
|
46
54
|
let resolve = param.resolve;
|
|
47
|
-
receive(param.inputJson).then(result => resolve(result));
|
|
55
|
+
receive(param.inputJson, param.caller).then(result => resolve(result));
|
|
48
56
|
});
|
|
49
57
|
}
|
|
50
58
|
|
|
@@ -54,4 +62,4 @@ export {
|
|
|
54
62
|
register,
|
|
55
63
|
bindReceive,
|
|
56
64
|
}
|
|
57
|
-
/*
|
|
65
|
+
/* Authorization-Reventless Not a pure module */
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// failure, and cannot afford to read it as a session that has ended.
|
|
22
22
|
//
|
|
23
23
|
// Mutations are wrapped (in addition to their per-command
|
|
24
|
-
// `
|
|
24
|
+
// `authorizationOf` rule inside `CommandGeneratorResolvers_GraphQL.register`)
|
|
25
25
|
// because the per-command rule defaults to `AllowAuthenticated` and the
|
|
26
26
|
// in-memory adapter falls back to `defaultUser` on missing-bearer requests
|
|
27
27
|
// — without the wrapper, anonymous callers could trigger admin mutations
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// constructor `Add({name})` defaulting to AllowAuthenticated, plus a
|
|
4
4
|
// payload-less `Archive` constrained to AllowRoles([Admin]).
|
|
5
5
|
//
|
|
6
|
-
// The PPX emits
|
|
6
|
+
// The PPX emits an `authorizationOf` switch at compile time (see
|
|
7
7
|
// examples/online-shop-hybrid/catalog/.../Category.res.mjs). This test hand-
|
|
8
8
|
// writes an equivalent switch and feeds it through the live resolver path
|
|
9
9
|
// (CommandGeneratorResolvers_GraphQL.register), so the test covers the same
|
|
@@ -25,11 +25,10 @@ type command =
|
|
|
25
25
|
|
|
26
26
|
// Matches the shape the PPX emits for `@authorize(AllowRoles([Admin]))` on
|
|
27
27
|
// the payload-less `Archive` constructor with a file-level default.
|
|
28
|
-
let
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
AllowAuthenticated
|
|
28
|
+
let authorizationOf = (name: string): Reventless.Authorization.permission =>
|
|
29
|
+
switch name {
|
|
30
|
+
| "Archive" => AllowRoles([Reventless.Role.make("Admin")])
|
|
31
|
+
| _ => AllowAuthenticated
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
// ── Identity fixtures ────────────────────────────────────────────────────────
|
|
@@ -84,7 +83,7 @@ let buildFixture = (~namespace: string) => {
|
|
|
84
83
|
CommandGeneratorResolvers_GraphQL.register(
|
|
85
84
|
~fields=[addField, renameField, archiveField],
|
|
86
85
|
~commandSchema=commandSchema->S.castToUnknown,
|
|
87
|
-
~
|
|
86
|
+
~authorizationOf,
|
|
88
87
|
~server,
|
|
89
88
|
)
|
|
90
89
|
|
|
@@ -242,7 +241,7 @@ let failingFixture = (~namespace: string, ~failWith: unit => unit) => {
|
|
|
242
241
|
CommandGeneratorResolvers_GraphQL.register(
|
|
243
242
|
~fields=[field],
|
|
244
243
|
~commandSchema=commandSchema->S.castToUnknown,
|
|
245
|
-
~
|
|
244
|
+
~authorizationOf,
|
|
246
245
|
~server,
|
|
247
246
|
)
|
|
248
247
|
let generateCommand: ReventlessCore.CommandGenerator.commandGenerator = _payload =>
|
|
@@ -28,8 +28,8 @@ let commandSchema = Sury.union([
|
|
|
28
28
|
Sury.literal("Archive")
|
|
29
29
|
]);
|
|
30
30
|
|
|
31
|
-
function
|
|
32
|
-
if (
|
|
31
|
+
function authorizationOf(name) {
|
|
32
|
+
if (name === "Archive") {
|
|
33
33
|
return {
|
|
34
34
|
TAG: "AllowRoles",
|
|
35
35
|
_0: ["Admin"]
|
|
@@ -91,7 +91,7 @@ function buildFixture(namespace) {
|
|
|
91
91
|
addField,
|
|
92
92
|
renameField,
|
|
93
93
|
archiveField
|
|
94
|
-
], commandSchema,
|
|
94
|
+
], commandSchema, authorizationOf, DomainGraphQL_Server$ReventlessLocal.asInterface);
|
|
95
95
|
let stubGenerateCommand = payload => Effect.promise(() => {
|
|
96
96
|
calls.contents.push(payload.command);
|
|
97
97
|
return Promise.resolve({
|
|
@@ -203,7 +203,7 @@ globalThis.describe("CommandGeneratorResolvers_GraphQL — per-constructor autho
|
|
|
203
203
|
|
|
204
204
|
function failingFixture(namespace, failWith) {
|
|
205
205
|
let field = namespace + `_Add`;
|
|
206
|
-
CommandGeneratorResolvers_GraphQL$ReventlessLocal.register([field], commandSchema,
|
|
206
|
+
CommandGeneratorResolvers_GraphQL$ReventlessLocal.register([field], commandSchema, authorizationOf, DomainGraphQL_Server$ReventlessLocal.asInterface);
|
|
207
207
|
let generateCommand = _payload => Effect.promise(() => {
|
|
208
208
|
failWith();
|
|
209
209
|
return Promise.resolve({
|
|
@@ -260,7 +260,7 @@ globalThis.describe("CommandGeneratorResolvers_GraphQL — a failure the caller
|
|
|
260
260
|
|
|
261
261
|
export {
|
|
262
262
|
commandSchema,
|
|
263
|
-
|
|
263
|
+
authorizationOf,
|
|
264
264
|
adminIdentity,
|
|
265
265
|
userIdentity,
|
|
266
266
|
ctxFor,
|