@reventlessdev/reventless-local 3.0.0-alpha.280 → 3.0.0-alpha.282
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 +9 -9
- package/src/Platform.res +2 -1
- package/src/Platform.res.mjs +2 -2
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +34 -38
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res.mjs +14 -6
- package/tests/adapter/InboundTranslationMutationTest.res +95 -7
- package/tests/adapter/InboundTranslationMutationTest.res.mjs +97 -9
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +114 -6
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs +158 -17
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +57 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res.mjs +140 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
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.282 (2026-10-10)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* an inbound translation is gated alike on every door, and encodes its commands by schema ([6ec3ce6](https://github.com/ReventlessDev/reventless-core/commit/6ec3ce672e53e2c162c64a90d103be691391be5e))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.281 (2026-10-08)
|
|
14
|
+
|
|
15
|
+
**Note:** Version bump only for package @reventlessdev/reventless-local
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 3.0.0-alpha.280 (2026-10-08)
|
|
7
22
|
|
|
8
23
|
**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.282",
|
|
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": {
|
|
@@ -38,15 +38,15 @@
|
|
|
38
38
|
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.29",
|
|
39
39
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
40
40
|
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.23",
|
|
41
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.
|
|
41
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.22",
|
|
42
42
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.23",
|
|
43
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.
|
|
44
|
-
"@reventlessdev/reventless-graphql-server": "1.0.0-alpha.
|
|
45
|
-
"@reventlessdev/reventless-gwt": "1.0.0-alpha.
|
|
46
|
-
"@reventlessdev/reventless-
|
|
47
|
-
"@reventlessdev/reventless-
|
|
48
|
-
"@reventlessdev/reventless-
|
|
49
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
43
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.292",
|
|
44
|
+
"@reventlessdev/reventless-graphql-server": "1.0.0-alpha.140",
|
|
45
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.239",
|
|
46
|
+
"@reventlessdev/reventless-seed": "1.0.0-alpha.33",
|
|
47
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.189",
|
|
48
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.156",
|
|
49
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.161"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"rescript": "12.3.1",
|
package/src/Platform.res
CHANGED
|
@@ -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
|
@@ -233,7 +233,7 @@ function MakeWithConfig(Config) {
|
|
|
233
233
|
fields.forEach(field => CommandGeneratorResolvers_GraphQL$ReventlessLocal.registerDcb(field, commandSchema, commandAuthorization, 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 => {
|
|
@@ -1993,7 +1993,7 @@ function Make($star) {
|
|
|
1993
1993
|
fields.forEach(field => CommandGeneratorResolvers_GraphQL$ReventlessLocal.registerDcb(field, commandSchema, commandAuthorization, 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 => {
|
|
@@ -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 */
|
|
@@ -44,16 +44,36 @@ let selection = `__typename
|
|
|
44
44
|
... on CommandAccepted { msgId entityId eventCount }
|
|
45
45
|
... on CommandRejected { msgId errorCode errorDetail }`
|
|
46
46
|
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
let signedIn = (groups): Reventless.Identity.t => {
|
|
48
|
+
userId: "u-1",
|
|
49
|
+
username: "alice",
|
|
50
|
+
groups,
|
|
51
|
+
provider: InMemory,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let contextOf = (identity: option<Reventless.Identity.t>): JSON.t =>
|
|
55
|
+
switch identity {
|
|
56
|
+
| Some(identity) =>
|
|
57
|
+
Dict.fromArray([
|
|
58
|
+
("identity", identity->Reventless.Util_Sury.toJson(Reventless.Identity.schema)),
|
|
59
|
+
])->JSON.Encode.object
|
|
60
|
+
| None => JSON.Encode.null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let execute = async (~caller, source) =>
|
|
51
64
|
await GraphqlYoga.graphql({
|
|
52
65
|
"schema": DomainGraphQL_Server.composeSchema(),
|
|
53
66
|
"source": source,
|
|
54
|
-
"contextValue":
|
|
67
|
+
"contextValue": contextOf(caller),
|
|
55
68
|
})
|
|
56
|
-
|
|
69
|
+
|
|
70
|
+
let runMutation = async (~status: string) =>
|
|
71
|
+
await execute(
|
|
72
|
+
~caller=Some(signedIn([])),
|
|
73
|
+
`mutation {
|
|
74
|
+
r: ${fieldName}(paymentId: "pay-1", orderId: "ord-1", status: "${status}") { ${selection} }
|
|
75
|
+
}`,
|
|
76
|
+
)
|
|
57
77
|
|
|
58
78
|
let errorMessages = (result: GraphqlYoga.executionResult): array<string> =>
|
|
59
79
|
result.errors
|
|
@@ -86,13 +106,14 @@ describe("InboundTranslationSlice mutation — response envelope", () => {
|
|
|
86
106
|
InboundTranslationResolvers_GraphQL.register(
|
|
87
107
|
~fieldName,
|
|
88
108
|
~externalInputSchema=PaymentWebhookSpec.externalInputSchema->S.castToUnknown,
|
|
109
|
+
~permission=AllowAuthenticated,
|
|
89
110
|
~server=DomainGraphQL_Server.asInterface,
|
|
90
111
|
)
|
|
91
112
|
DomainGraphQL_Server.resetScope()
|
|
92
113
|
|
|
93
114
|
InboundTranslationResolvers_GraphQL.bindReceive(
|
|
94
115
|
~fieldName,
|
|
95
|
-
~receive=inputJson => Callback.receive(publishJsons, inputJson),
|
|
116
|
+
~receive=(inputJson, ~caller=?) => Callback.receive(publishJsons, inputJson, ~caller?),
|
|
96
117
|
)
|
|
97
118
|
})
|
|
98
119
|
|
|
@@ -140,3 +161,70 @@ describe("InboundTranslationSlice mutation — response envelope", () => {
|
|
|
140
161
|
expect(fieldLine->Option.map(l => l->String.endsWith(": CommandResult!")))->toEqual(Some(true))
|
|
141
162
|
})
|
|
142
163
|
})
|
|
164
|
+
|
|
165
|
+
module CatalogFeedTranslation = {
|
|
166
|
+
let translate = CatalogFeedSpec.translate
|
|
167
|
+
let moduleUrl = CatalogFeedSpec.moduleUrl
|
|
168
|
+
}
|
|
169
|
+
module CatalogFeedCallback = ReventlessCore.InboundTranslationSlice_Callback.Make(
|
|
170
|
+
CatalogFeedSpec,
|
|
171
|
+
CatalogFeedTranslation,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
let feedField = "Catalog_CatalogFeed"
|
|
175
|
+
|
|
176
|
+
let runFeed = async (~caller, ~kind) =>
|
|
177
|
+
await execute(
|
|
178
|
+
~caller,
|
|
179
|
+
`mutation { r: ${feedField}(kind: "${kind}", id: "x-1") { ${selection} } }`,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
describe("InboundTranslationSlice mutation — the door", () => {
|
|
183
|
+
beforeEach(() => {
|
|
184
|
+
DomainGraphQL_Server.asInterface.reset()
|
|
185
|
+
published.contents = []
|
|
186
|
+
|
|
187
|
+
DomainGraphQL_Server.setScope("Catalog")
|
|
188
|
+
InboundTranslationResolvers_GraphQL.register(
|
|
189
|
+
~fieldName=feedField,
|
|
190
|
+
~externalInputSchema=CatalogFeedSpec.externalInputSchema->S.castToUnknown,
|
|
191
|
+
~permission=?ReventlessCore.Dcb_Builder.inboundDoorPermission(
|
|
192
|
+
CatalogFeedSpec.commandSchema,
|
|
193
|
+
CatalogFeedSpec.commandAuthorization,
|
|
194
|
+
),
|
|
195
|
+
~server=DomainGraphQL_Server.asInterface,
|
|
196
|
+
)
|
|
197
|
+
DomainGraphQL_Server.resetScope()
|
|
198
|
+
|
|
199
|
+
InboundTranslationResolvers_GraphQL.bindReceive(
|
|
200
|
+
~fieldName=feedField,
|
|
201
|
+
~receive=(inputJson, ~caller=?) =>
|
|
202
|
+
CatalogFeedCallback.receive(publishJsons, inputJson, ~caller?),
|
|
203
|
+
)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
testPromise("a caller holding none of the commands' roles is refused", async () => {
|
|
207
|
+
let node = payload(await runFeed(~caller=Some(signedIn(["Ops"])), ~kind="product"))
|
|
208
|
+
expect(node->str("errorCode"))->toEqual(Some("Forbidden"))
|
|
209
|
+
expect(published.contents->Array.length)->toBe(0)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
testPromise("an unauthenticated caller is refused", async () => {
|
|
213
|
+
let node = payload(await runFeed(~caller=None, ~kind="product"))
|
|
214
|
+
expect(node->str("errorCode"))->toEqual(Some("Forbidden"))
|
|
215
|
+
expect(published.contents->Array.length)->toBe(0)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
testPromise("a caller holding a command's role is let through", async () => {
|
|
219
|
+
let node = payload(await runFeed(~caller=Some(signedIn(["Merchandiser"])), ~kind="product"))
|
|
220
|
+
expect(node->str("__typename"))->toEqual(Some("CommandAccepted"))
|
|
221
|
+
expect(published.contents->Array.length)->toBe(1)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// The caller reaches `receive`, which refuses the command the door could not see.
|
|
225
|
+
testPromise("the caller reaches the per-command check", async () => {
|
|
226
|
+
let node = payload(await runFeed(~caller=Some(signedIn(["Merchandiser"])), ~kind="both"))
|
|
227
|
+
expect(node->str("errorCode"))->toEqual(Some("Forbidden"))
|
|
228
|
+
expect(published.contents->Array.length)->toBe(0)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
@@ -5,6 +5,9 @@ import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
|
5
5
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
6
6
|
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
7
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
8
|
+
import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
|
|
9
|
+
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
10
|
+
import * as Dcb_Builder$ReventlessCore from "@reventlessdev/reventless-core/src/components/Dcb/Dcb_Builder.res.mjs";
|
|
8
11
|
import * as DomainGraphQL_Server$ReventlessLocal from "../../src/adapter/DomainGraphQL_Server.res.mjs";
|
|
9
12
|
import * as InboundTranslationSliceFixtures$ReventlessLocal from "../components/inboundtranslationslice/InboundTranslationSliceFixtures.res.mjs";
|
|
10
13
|
import * as InboundTranslationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs";
|
|
@@ -26,8 +29,7 @@ let Callback = InboundTranslationSlice_Callback$ReventlessCore.Make({
|
|
|
26
29
|
commandSchema: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.commandSchema,
|
|
27
30
|
targetName: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.targetName,
|
|
28
31
|
externalSystem: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.externalSystem,
|
|
29
|
-
commandAuthorization: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.commandAuthorization
|
|
30
|
-
commandTransition: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.commandTransition
|
|
32
|
+
commandAuthorization: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.commandAuthorization
|
|
31
33
|
})(PaymentWebhookTranslation);
|
|
32
34
|
|
|
33
35
|
let scope = "Payments";
|
|
@@ -46,17 +48,40 @@ let selection = `__typename
|
|
|
46
48
|
... on CommandAccepted { msgId entityId eventCount }
|
|
47
49
|
... on CommandRejected { msgId errorCode errorDetail }`;
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
function signedIn(groups) {
|
|
52
|
+
return {
|
|
53
|
+
userId: "u-1",
|
|
54
|
+
username: "alice",
|
|
55
|
+
groups: groups,
|
|
56
|
+
provider: "InMemory"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function contextOf(identity) {
|
|
61
|
+
if (identity !== undefined) {
|
|
62
|
+
return Object.fromEntries([[
|
|
63
|
+
"identity",
|
|
64
|
+
Util_Sury$Reventless.toJson(identity, Identity$Reventless.schema)
|
|
65
|
+
]]);
|
|
66
|
+
} else {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function execute(caller, source) {
|
|
53
72
|
return await Graphql.graphql({
|
|
54
73
|
schema: DomainGraphQL_Server$ReventlessLocal.composeSchema(),
|
|
55
74
|
source: source,
|
|
56
|
-
contextValue:
|
|
75
|
+
contextValue: contextOf(caller)
|
|
57
76
|
});
|
|
58
77
|
}
|
|
59
78
|
|
|
79
|
+
async function runMutation(status) {
|
|
80
|
+
return await execute(signedIn([]), `mutation {
|
|
81
|
+
r: ` + fieldName + `(paymentId: "pay-1", orderId: "ord-1", status: "` + status + `") { ` + selection + ` }
|
|
82
|
+
}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
60
85
|
function errorMessages(result) {
|
|
61
86
|
return Stdlib_Option.getOr(result.errors, []).map(e => Stdlib_Option.getOr(Stdlib_JsExn.message(e), "unknown error"));
|
|
62
87
|
}
|
|
@@ -79,9 +104,9 @@ globalThis.describe("InboundTranslationSlice mutation — response envelope", ()
|
|
|
79
104
|
published.contents = [];
|
|
80
105
|
Object.keys(Callback.auditLog).forEach(k => Stdlib_Dict.$$delete(Callback.auditLog, k));
|
|
81
106
|
DomainGraphQL_Server$ReventlessLocal.setScope(scope);
|
|
82
|
-
InboundTranslationResolvers_GraphQL$ReventlessLocal.register(fieldName, InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.externalInputSchema, DomainGraphQL_Server$ReventlessLocal.asInterface);
|
|
107
|
+
InboundTranslationResolvers_GraphQL$ReventlessLocal.register(fieldName, InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.externalInputSchema, "AllowAuthenticated", DomainGraphQL_Server$ReventlessLocal.asInterface);
|
|
83
108
|
DomainGraphQL_Server$ReventlessLocal.resetScope();
|
|
84
|
-
InboundTranslationResolvers_GraphQL$ReventlessLocal.bindReceive(fieldName, inputJson => Callback.receive(publishJsons, inputJson));
|
|
109
|
+
InboundTranslationResolvers_GraphQL$ReventlessLocal.bindReceive(fieldName, (inputJson, caller) => Callback.receive(publishJsons, inputJson, caller));
|
|
85
110
|
});
|
|
86
111
|
globalThis.test("a successful translation resolves as CommandAccepted, with no errors", async () => {
|
|
87
112
|
let result = await runMutation("completed");
|
|
@@ -116,6 +141,62 @@ globalThis.describe("InboundTranslationSlice mutation — response envelope", ()
|
|
|
116
141
|
});
|
|
117
142
|
});
|
|
118
143
|
|
|
144
|
+
let translate$1 = InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.translate;
|
|
145
|
+
|
|
146
|
+
let moduleUrl$1 = InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.moduleUrl;
|
|
147
|
+
|
|
148
|
+
let CatalogFeedTranslation = {
|
|
149
|
+
translate: translate$1,
|
|
150
|
+
moduleUrl: moduleUrl$1
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
let CatalogFeedCallback = InboundTranslationSlice_Callback$ReventlessCore.Make({
|
|
154
|
+
name: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.name,
|
|
155
|
+
moduleUrl: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.moduleUrl,
|
|
156
|
+
externalInputSchema: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.externalInputSchema,
|
|
157
|
+
commandSchema: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.commandSchema,
|
|
158
|
+
targetName: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.targetName,
|
|
159
|
+
externalSystem: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.externalSystem,
|
|
160
|
+
commandAuthorization: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.commandAuthorization
|
|
161
|
+
})(CatalogFeedTranslation);
|
|
162
|
+
|
|
163
|
+
let feedField = "Catalog_CatalogFeed";
|
|
164
|
+
|
|
165
|
+
async function runFeed(caller, kind) {
|
|
166
|
+
return await execute(caller, `mutation { r: ` + feedField + `(kind: "` + kind + `", id: "x-1") { ` + selection + ` } }`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
globalThis.describe("InboundTranslationSlice mutation — the door", () => {
|
|
170
|
+
globalThis.beforeEach(() => {
|
|
171
|
+
DomainGraphQL_Server$ReventlessLocal.asInterface.reset();
|
|
172
|
+
published.contents = [];
|
|
173
|
+
DomainGraphQL_Server$ReventlessLocal.setScope("Catalog");
|
|
174
|
+
InboundTranslationResolvers_GraphQL$ReventlessLocal.register(feedField, InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.externalInputSchema, Dcb_Builder$ReventlessCore.inboundDoorPermission(InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.commandSchema, InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.commandAuthorization), DomainGraphQL_Server$ReventlessLocal.asInterface);
|
|
175
|
+
DomainGraphQL_Server$ReventlessLocal.resetScope();
|
|
176
|
+
InboundTranslationResolvers_GraphQL$ReventlessLocal.bindReceive(feedField, (inputJson, caller) => CatalogFeedCallback.receive(publishJsons, inputJson, caller));
|
|
177
|
+
});
|
|
178
|
+
globalThis.test("a caller holding none of the commands' roles is refused", async () => {
|
|
179
|
+
let node = payload(await runFeed(signedIn(["Ops"]), "product"));
|
|
180
|
+
globalThis.expect(str(node, "errorCode")).toEqual("Forbidden");
|
|
181
|
+
globalThis.expect(published.contents.length).toBe(0);
|
|
182
|
+
});
|
|
183
|
+
globalThis.test("an unauthenticated caller is refused", async () => {
|
|
184
|
+
let node = payload(await runFeed(undefined, "product"));
|
|
185
|
+
globalThis.expect(str(node, "errorCode")).toEqual("Forbidden");
|
|
186
|
+
globalThis.expect(published.contents.length).toBe(0);
|
|
187
|
+
});
|
|
188
|
+
globalThis.test("a caller holding a command's role is let through", async () => {
|
|
189
|
+
let node = payload(await runFeed(signedIn(["Merchandiser"]), "product"));
|
|
190
|
+
globalThis.expect(str(node, "__typename")).toEqual("CommandAccepted");
|
|
191
|
+
globalThis.expect(published.contents.length).toBe(1);
|
|
192
|
+
});
|
|
193
|
+
globalThis.test("the caller reaches the per-command check", async () => {
|
|
194
|
+
let node = payload(await runFeed(signedIn(["Merchandiser"]), "both"));
|
|
195
|
+
globalThis.expect(str(node, "errorCode")).toEqual("Forbidden");
|
|
196
|
+
globalThis.expect(published.contents.length).toBe(0);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
119
200
|
export {
|
|
120
201
|
PaymentWebhookTranslation,
|
|
121
202
|
Callback,
|
|
@@ -124,10 +205,17 @@ export {
|
|
|
124
205
|
published,
|
|
125
206
|
publishJsons,
|
|
126
207
|
selection,
|
|
208
|
+
signedIn,
|
|
209
|
+
contextOf,
|
|
210
|
+
execute,
|
|
127
211
|
runMutation,
|
|
128
212
|
errorMessages,
|
|
129
213
|
payload,
|
|
130
214
|
str,
|
|
131
215
|
num,
|
|
216
|
+
CatalogFeedTranslation,
|
|
217
|
+
CatalogFeedCallback,
|
|
218
|
+
feedField,
|
|
219
|
+
runFeed,
|
|
132
220
|
}
|
|
133
221
|
/* Callback Not a pure module */
|
|
@@ -226,9 +226,6 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
226
226
|
|
|
227
227
|
let commandAuthorization = (_: command): Reventless.Authorization.rule<role> =>
|
|
228
228
|
AllowAuthenticated
|
|
229
|
-
type lifecycleState = unit
|
|
230
|
-
let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> =>
|
|
231
|
-
Unrestricted
|
|
232
229
|
}
|
|
233
230
|
|
|
234
231
|
module MultiTranslation = {
|
|
@@ -295,9 +292,6 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
295
292
|
|
|
296
293
|
let commandAuthorization = (_: command): Reventless.Authorization.rule<role> =>
|
|
297
294
|
AllowAuthenticated
|
|
298
|
-
type lifecycleState = unit
|
|
299
|
-
let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> =>
|
|
300
|
-
Unrestricted
|
|
301
295
|
}
|
|
302
296
|
|
|
303
297
|
module EmptyTranslation = {
|
|
@@ -329,3 +323,117 @@ describe("InboundTranslationSlice Callback", () => {
|
|
|
329
323
|
)
|
|
330
324
|
})
|
|
331
325
|
})
|
|
326
|
+
|
|
327
|
+
module CatalogFeedTranslation = {
|
|
328
|
+
let translate = CatalogFeedSpec.translate
|
|
329
|
+
let moduleUrl = CatalogFeedSpec.moduleUrl
|
|
330
|
+
}
|
|
331
|
+
module CatalogFeedCallback = ReventlessCore.InboundTranslationSlice_Callback.Make(
|
|
332
|
+
CatalogFeedSpec,
|
|
333
|
+
CatalogFeedTranslation,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
let caller = (groups): Reventless.Identity.t => {
|
|
337
|
+
userId: "u-1",
|
|
338
|
+
username: "alice",
|
|
339
|
+
groups,
|
|
340
|
+
provider: InMemory,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let catalogInput = (kind): JSON.t =>
|
|
344
|
+
Dict.fromArray([
|
|
345
|
+
("kind", JSON.Encode.string(kind)),
|
|
346
|
+
("id", JSON.Encode.string("x-1")),
|
|
347
|
+
])->JSON.Encode.object
|
|
348
|
+
|
|
349
|
+
describe("receive checks each command against its own rule", () => {
|
|
350
|
+
let published = ref([])
|
|
351
|
+
let publish: ReventlessInfra.CommandTopic.publishJsons = async cmds =>
|
|
352
|
+
published := published.contents->Array.concat(cmds)
|
|
353
|
+
let _ = beforeEach(() => published := [])
|
|
354
|
+
|
|
355
|
+
testPromise(
|
|
356
|
+
"a caller lacking one command's role is refused, and nothing is published",
|
|
357
|
+
async () => {
|
|
358
|
+
let result = await CatalogFeedCallback.receive(
|
|
359
|
+
publish,
|
|
360
|
+
catalogInput("both"),
|
|
361
|
+
~caller=caller(["Merchandiser"]),
|
|
362
|
+
)
|
|
363
|
+
switch result {
|
|
364
|
+
| Error({requestId, ?errorCode}) =>
|
|
365
|
+
expect(errorCode)->toEqual(Some("Forbidden"))
|
|
366
|
+
let row = CatalogFeedCallback.auditLog->Dict.get(requestId)
|
|
367
|
+
expect(row->Option.map(r => r.status))->toEqual(
|
|
368
|
+
Some(ReventlessCore.InboundTranslationSlice_Callback.Failure),
|
|
369
|
+
)
|
|
370
|
+
| Ok(_) => expect(true)->toBe(false)
|
|
371
|
+
}
|
|
372
|
+
expect(published.contents->Array.length)->toBe(0)
|
|
373
|
+
},
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
testPromise("a caller holding every command's role publishes them all", async () => {
|
|
377
|
+
let result = await CatalogFeedCallback.receive(
|
|
378
|
+
publish,
|
|
379
|
+
catalogInput("both"),
|
|
380
|
+
~caller=caller(["Merchandiser", "Admin"]),
|
|
381
|
+
)
|
|
382
|
+
expect(result->Result.isOk)->toBe(true)
|
|
383
|
+
expect(published.contents->Array.length)->toBe(2)
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
testPromise("the role one command needs is enough when the input yields only it", async () => {
|
|
387
|
+
let result = await CatalogFeedCallback.receive(
|
|
388
|
+
publish,
|
|
389
|
+
catalogInput("product"),
|
|
390
|
+
~caller=caller(["Merchandiser"]),
|
|
391
|
+
)
|
|
392
|
+
expect(result->Result.isOk)->toBe(true)
|
|
393
|
+
expect(published.contents->Array.length)->toBe(1)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
testPromise("no caller is the platform itself, and publishes", async () => {
|
|
397
|
+
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"))
|
|
398
|
+
expect(result->Result.isOk)->toBe(true)
|
|
399
|
+
expect(published.contents->Array.length)->toBe(2)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
testPromise("a refusal answers CommandRejected with Forbidden", async () => {
|
|
403
|
+
let result = await CatalogFeedCallback.receive(
|
|
404
|
+
publish,
|
|
405
|
+
catalogInput("both"),
|
|
406
|
+
~caller=Reventless.Identity.anonymous,
|
|
407
|
+
)
|
|
408
|
+
switch result->ReventlessCore.InboundTranslationSlice_Callback.receiveResultToOutcome {
|
|
409
|
+
| Rejected({errorCode}) => expect(errorCode)->toBe("Forbidden")
|
|
410
|
+
| _ => expect(true)->toBe(false)
|
|
411
|
+
}
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
module NoteFeedTranslation = {
|
|
416
|
+
let translate = NoteFeedSpec.translate
|
|
417
|
+
let moduleUrl = NoteFeedSpec.moduleUrl
|
|
418
|
+
}
|
|
419
|
+
module NoteFeedCallback = ReventlessCore.InboundTranslationSlice_Callback.Make(
|
|
420
|
+
NoteFeedSpec,
|
|
421
|
+
NoteFeedTranslation,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
describe("receive encodes commands with their schema", () => {
|
|
425
|
+
testPromise("an absent option the schema writes as null decodes at the target", async () => {
|
|
426
|
+
let published = ref([])
|
|
427
|
+
let publish: ReventlessInfra.CommandTopic.publishJsons = async cmds => published := cmds
|
|
428
|
+
let _ = await NoteFeedCallback.receive(
|
|
429
|
+
publish,
|
|
430
|
+
Dict.fromArray([("orderId", JSON.Encode.string("ord-1"))])->JSON.Encode.object,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
let commandJson = (published.contents->Array.getUnsafe(0)).commandJson
|
|
434
|
+
// The target decodes with the same schema; the runtime value would omit `note`.
|
|
435
|
+
let decoded = commandJson->S.parseOrThrow(~to=NoteFeedSpec.commandSchema)
|
|
436
|
+
expect(decoded)->toEqual(NoteFeedSpec.Annotate({orderId: "ord-1", note: None}))
|
|
437
|
+
expect(commandJson->JSON.stringify->String.includes(`"note":null`))->toBe(true)
|
|
438
|
+
})
|
|
439
|
+
})
|
package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
import * as Sury from "sury";
|
|
4
4
|
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
6
7
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
7
8
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
9
|
+
import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
|
|
8
10
|
import * as InboundTranslationSliceFixtures$ReventlessLocal from "./InboundTranslationSliceFixtures.res.mjs";
|
|
9
11
|
import * as InboundTranslationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs";
|
|
10
12
|
|
|
@@ -24,8 +26,7 @@ let Callback = InboundTranslationSlice_Callback$ReventlessCore.Make({
|
|
|
24
26
|
commandSchema: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.commandSchema,
|
|
25
27
|
targetName: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.targetName,
|
|
26
28
|
externalSystem: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.externalSystem,
|
|
27
|
-
commandAuthorization: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.commandAuthorization
|
|
28
|
-
commandTransition: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.commandTransition
|
|
29
|
+
commandAuthorization: InboundTranslationSliceFixtures$ReventlessLocal.PaymentWebhookSpec.commandAuthorization
|
|
29
30
|
})(PaymentWebhookTranslation);
|
|
30
31
|
|
|
31
32
|
globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
@@ -45,7 +46,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
45
46
|
orderId: "ord-1",
|
|
46
47
|
status: "completed"
|
|
47
48
|
};
|
|
48
|
-
let result = await Callback.receive(mockPublish, inputJson);
|
|
49
|
+
let result = await Callback.receive(mockPublish, inputJson, undefined);
|
|
49
50
|
if (result.TAG === "Ok") {
|
|
50
51
|
let match = result._0;
|
|
51
52
|
globalThis.expect(match.targetIds).toEqual(["ord-1"]);
|
|
@@ -76,7 +77,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
76
77
|
orderId: "ord-1",
|
|
77
78
|
status: "pending"
|
|
78
79
|
};
|
|
79
|
-
let result = await Callback.receive(mockPublish, inputJson);
|
|
80
|
+
let result = await Callback.receive(mockPublish, inputJson, undefined);
|
|
80
81
|
if (result.TAG === "Ok") {
|
|
81
82
|
globalThis.expect(true).toBe(false);
|
|
82
83
|
} else {
|
|
@@ -93,7 +94,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
93
94
|
let inputJson = {
|
|
94
95
|
unexpected: "data"
|
|
95
96
|
};
|
|
96
|
-
let result = await Callback.receive(mockPublish, inputJson);
|
|
97
|
+
let result = await Callback.receive(mockPublish, inputJson, undefined);
|
|
97
98
|
if (result.TAG === "Ok") {
|
|
98
99
|
globalThis.expect(true).toBe(false);
|
|
99
100
|
} else {
|
|
@@ -111,7 +112,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
111
112
|
orderId: "ord-1",
|
|
112
113
|
status: "completed"
|
|
113
114
|
};
|
|
114
|
-
let result = await Callback.receive(failingPublish, inputJson);
|
|
115
|
+
let result = await Callback.receive(failingPublish, inputJson, undefined);
|
|
115
116
|
if (result.TAG === "Ok") {
|
|
116
117
|
globalThis.expect(true).toBe(false);
|
|
117
118
|
} else {
|
|
@@ -130,7 +131,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
130
131
|
paymentId: "pay-1",
|
|
131
132
|
orderId: "ord-1",
|
|
132
133
|
status: "completed"
|
|
133
|
-
});
|
|
134
|
+
}, undefined);
|
|
134
135
|
let firstId = InboundTranslationSlice_Callback$ReventlessCore.requestIdOf(first);
|
|
135
136
|
globalThis.expect(Stdlib_Option.isSome(InboundTranslationSlice_Callback$ReventlessCore.takeAuditRow(Callback.auditLog, firstId))).toBe(true);
|
|
136
137
|
globalThis.expect(Object.entries(Callback.auditLog).length).toBe(0);
|
|
@@ -138,7 +139,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
138
139
|
paymentId: "pay-1",
|
|
139
140
|
orderId: "ord-1",
|
|
140
141
|
status: "completed"
|
|
141
|
-
});
|
|
142
|
+
}, undefined);
|
|
142
143
|
let secondId = InboundTranslationSlice_Callback$ReventlessCore.requestIdOf(second);
|
|
143
144
|
globalThis.expect(secondId === firstId).toBe(false);
|
|
144
145
|
globalThis.expect(Object.entries(Callback.auditLog).length).toBe(1);
|
|
@@ -151,7 +152,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
151
152
|
paymentId: "pay-1",
|
|
152
153
|
orderId: "ord-1",
|
|
153
154
|
status: "pending"
|
|
154
|
-
});
|
|
155
|
+
}, undefined);
|
|
155
156
|
let id = InboundTranslationSlice_Callback$ReventlessCore.requestIdOf(result);
|
|
156
157
|
let row = InboundTranslationSlice_Callback$ReventlessCore.takeAuditRow(Callback.auditLog, id);
|
|
157
158
|
if (row !== undefined) {
|
|
@@ -186,7 +187,6 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
186
187
|
])
|
|
187
188
|
});
|
|
188
189
|
let commandAuthorization = param => "AllowAuthenticated";
|
|
189
|
-
let commandTransition = param => "Unrestricted";
|
|
190
190
|
let MultiTranslation = {
|
|
191
191
|
translate: translate,
|
|
192
192
|
moduleUrl: moduleUrl
|
|
@@ -198,8 +198,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
198
198
|
commandSchema: commandSchema,
|
|
199
199
|
targetName: "ConfirmPayment",
|
|
200
200
|
externalSystem: undefined,
|
|
201
|
-
commandAuthorization: commandAuthorization
|
|
202
|
-
commandTransition: commandTransition
|
|
201
|
+
commandAuthorization: commandAuthorization
|
|
203
202
|
})(MultiTranslation);
|
|
204
203
|
let publishedCommands = {
|
|
205
204
|
contents: []
|
|
@@ -215,7 +214,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
215
214
|
"pay-3"
|
|
216
215
|
]
|
|
217
216
|
};
|
|
218
|
-
let result = await MultiCallback.receive(mockPublish, inputJson);
|
|
217
|
+
let result = await MultiCallback.receive(mockPublish, inputJson, undefined);
|
|
219
218
|
if (result.TAG === "Ok") {
|
|
220
219
|
let match = result._0;
|
|
221
220
|
globalThis.expect(match.targetIds).toEqual([
|
|
@@ -250,7 +249,6 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
250
249
|
_0: []
|
|
251
250
|
});
|
|
252
251
|
let commandAuthorization = param => "AllowAuthenticated";
|
|
253
|
-
let commandTransition = param => "Unrestricted";
|
|
254
252
|
let EmptyTranslation = {
|
|
255
253
|
translate: translate,
|
|
256
254
|
moduleUrl: moduleUrl
|
|
@@ -262,8 +260,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
262
260
|
commandSchema: commandSchema,
|
|
263
261
|
targetName: "ConfirmPayment",
|
|
264
262
|
externalSystem: undefined,
|
|
265
|
-
commandAuthorization: commandAuthorization
|
|
266
|
-
commandTransition: commandTransition
|
|
263
|
+
commandAuthorization: commandAuthorization
|
|
267
264
|
})(EmptyTranslation);
|
|
268
265
|
let publishedCommands = {
|
|
269
266
|
contents: []
|
|
@@ -274,7 +271,7 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
274
271
|
let inputJson = {
|
|
275
272
|
orderId: "ord-1"
|
|
276
273
|
};
|
|
277
|
-
let result = await EmptyCallback.receive(mockPublish, inputJson);
|
|
274
|
+
let result = await EmptyCallback.receive(mockPublish, inputJson, undefined);
|
|
278
275
|
if (result.TAG === "Ok") {
|
|
279
276
|
let match = result._0;
|
|
280
277
|
globalThis.expect(match.targetIds).toEqual([]);
|
|
@@ -287,8 +284,152 @@ globalThis.describe("InboundTranslationSlice Callback", () => {
|
|
|
287
284
|
});
|
|
288
285
|
});
|
|
289
286
|
|
|
287
|
+
let translate$1 = InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.translate;
|
|
288
|
+
|
|
289
|
+
let moduleUrl$1 = InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.moduleUrl;
|
|
290
|
+
|
|
291
|
+
let CatalogFeedTranslation = {
|
|
292
|
+
translate: translate$1,
|
|
293
|
+
moduleUrl: moduleUrl$1
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
let CatalogFeedCallback = InboundTranslationSlice_Callback$ReventlessCore.Make({
|
|
297
|
+
name: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.name,
|
|
298
|
+
moduleUrl: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.moduleUrl,
|
|
299
|
+
externalInputSchema: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.externalInputSchema,
|
|
300
|
+
commandSchema: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.commandSchema,
|
|
301
|
+
targetName: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.targetName,
|
|
302
|
+
externalSystem: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.externalSystem,
|
|
303
|
+
commandAuthorization: InboundTranslationSliceFixtures$ReventlessLocal.CatalogFeedSpec.commandAuthorization
|
|
304
|
+
})(CatalogFeedTranslation);
|
|
305
|
+
|
|
306
|
+
function caller(groups) {
|
|
307
|
+
return {
|
|
308
|
+
userId: "u-1",
|
|
309
|
+
username: "alice",
|
|
310
|
+
groups: groups,
|
|
311
|
+
provider: "InMemory"
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function catalogInput(kind) {
|
|
316
|
+
return Object.fromEntries([
|
|
317
|
+
[
|
|
318
|
+
"kind",
|
|
319
|
+
kind
|
|
320
|
+
],
|
|
321
|
+
[
|
|
322
|
+
"id",
|
|
323
|
+
"x-1"
|
|
324
|
+
]
|
|
325
|
+
]);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
globalThis.describe("receive checks each command against its own rule", () => {
|
|
329
|
+
let published = {
|
|
330
|
+
contents: []
|
|
331
|
+
};
|
|
332
|
+
let publish = async cmds => {
|
|
333
|
+
published.contents = published.contents.concat(cmds);
|
|
334
|
+
};
|
|
335
|
+
globalThis.beforeEach(() => {
|
|
336
|
+
published.contents = [];
|
|
337
|
+
});
|
|
338
|
+
globalThis.test("a caller lacking one command's role is refused, and nothing is published", async () => {
|
|
339
|
+
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), caller(["Merchandiser"]));
|
|
340
|
+
if (result.TAG === "Ok") {
|
|
341
|
+
globalThis.expect(true).toBe(false);
|
|
342
|
+
} else {
|
|
343
|
+
let match = result._0;
|
|
344
|
+
globalThis.expect(match.errorCode).toEqual("Forbidden");
|
|
345
|
+
let row = CatalogFeedCallback.auditLog[match.requestId];
|
|
346
|
+
globalThis.expect(Stdlib_Option.map(row, r => r.status)).toEqual("Failure");
|
|
347
|
+
}
|
|
348
|
+
globalThis.expect(published.contents.length).toBe(0);
|
|
349
|
+
});
|
|
350
|
+
globalThis.test("a caller holding every command's role publishes them all", async () => {
|
|
351
|
+
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), caller([
|
|
352
|
+
"Merchandiser",
|
|
353
|
+
"Admin"
|
|
354
|
+
]));
|
|
355
|
+
globalThis.expect(Stdlib_Result.isOk(result)).toBe(true);
|
|
356
|
+
globalThis.expect(published.contents.length).toBe(2);
|
|
357
|
+
});
|
|
358
|
+
globalThis.test("the role one command needs is enough when the input yields only it", async () => {
|
|
359
|
+
let result = await CatalogFeedCallback.receive(publish, catalogInput("product"), caller(["Merchandiser"]));
|
|
360
|
+
globalThis.expect(Stdlib_Result.isOk(result)).toBe(true);
|
|
361
|
+
globalThis.expect(published.contents.length).toBe(1);
|
|
362
|
+
});
|
|
363
|
+
globalThis.test("no caller is the platform itself, and publishes", async () => {
|
|
364
|
+
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), undefined);
|
|
365
|
+
globalThis.expect(Stdlib_Result.isOk(result)).toBe(true);
|
|
366
|
+
globalThis.expect(published.contents.length).toBe(2);
|
|
367
|
+
});
|
|
368
|
+
globalThis.test("a refusal answers CommandRejected with Forbidden", async () => {
|
|
369
|
+
let result = await CatalogFeedCallback.receive(publish, catalogInput("both"), Identity$Reventless.anonymous);
|
|
370
|
+
let match = InboundTranslationSlice_Callback$ReventlessCore.receiveResultToOutcome(result);
|
|
371
|
+
switch (match.TAG) {
|
|
372
|
+
case "Rejected" :
|
|
373
|
+
globalThis.expect(match.errorCode).toBe("Forbidden");
|
|
374
|
+
return;
|
|
375
|
+
case "Accepted" :
|
|
376
|
+
case "Pending" :
|
|
377
|
+
globalThis.expect(true).toBe(false);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
let translate$2 = InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.translate;
|
|
384
|
+
|
|
385
|
+
let moduleUrl$2 = InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.moduleUrl;
|
|
386
|
+
|
|
387
|
+
let NoteFeedTranslation = {
|
|
388
|
+
translate: translate$2,
|
|
389
|
+
moduleUrl: moduleUrl$2
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
let NoteFeedCallback = InboundTranslationSlice_Callback$ReventlessCore.Make({
|
|
393
|
+
name: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.name,
|
|
394
|
+
moduleUrl: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.moduleUrl,
|
|
395
|
+
externalInputSchema: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.externalInputSchema,
|
|
396
|
+
commandSchema: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.commandSchema,
|
|
397
|
+
targetName: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.targetName,
|
|
398
|
+
externalSystem: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.externalSystem,
|
|
399
|
+
commandAuthorization: InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.commandAuthorization
|
|
400
|
+
})(NoteFeedTranslation);
|
|
401
|
+
|
|
402
|
+
globalThis.describe("receive encodes commands with their schema", () => {
|
|
403
|
+
globalThis.test("an absent option the schema writes as null decodes at the target", async () => {
|
|
404
|
+
let published = {
|
|
405
|
+
contents: []
|
|
406
|
+
};
|
|
407
|
+
let publish = async cmds => {
|
|
408
|
+
published.contents = cmds;
|
|
409
|
+
};
|
|
410
|
+
await NoteFeedCallback.receive(publish, Object.fromEntries([[
|
|
411
|
+
"orderId",
|
|
412
|
+
"ord-1"
|
|
413
|
+
]]), undefined);
|
|
414
|
+
let commandJson = published.contents[0].commandJson;
|
|
415
|
+
let decoded = Sury.parseOrThrow(commandJson, InboundTranslationSliceFixtures$ReventlessLocal.NoteFeedSpec.commandSchema);
|
|
416
|
+
globalThis.expect(decoded).toEqual({
|
|
417
|
+
TAG: "Annotate",
|
|
418
|
+
orderId: "ord-1",
|
|
419
|
+
note: undefined
|
|
420
|
+
});
|
|
421
|
+
globalThis.expect(JSON.stringify(commandJson).includes(`"note":null`)).toBe(true);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
290
425
|
export {
|
|
291
426
|
PaymentWebhookTranslation,
|
|
292
427
|
Callback,
|
|
428
|
+
CatalogFeedTranslation,
|
|
429
|
+
CatalogFeedCallback,
|
|
430
|
+
caller,
|
|
431
|
+
catalogInput,
|
|
432
|
+
NoteFeedTranslation,
|
|
433
|
+
NoteFeedCallback,
|
|
293
434
|
}
|
|
294
435
|
/* Callback Not a pure module */
|
|
@@ -36,3 +36,60 @@ module PaymentWebhookSpec = {
|
|
|
36
36
|
| status => Error("Unknown payment status: " ++ status)
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
// Two constructors under different roles; `kind` picks which the input becomes.
|
|
41
|
+
module CatalogFeedSpec = {
|
|
42
|
+
let name = "CatalogFeed"
|
|
43
|
+
let moduleUrl: string = %raw(`import.meta.url`)
|
|
44
|
+
|
|
45
|
+
@schema
|
|
46
|
+
type externalInput = {kind: string, id: string}
|
|
47
|
+
|
|
48
|
+
@schema
|
|
49
|
+
type command =
|
|
50
|
+
| AddProduct({productId: @s.matches(Reventless.DcbTag.string) string})
|
|
51
|
+
| AddCategory({categoryId: @s.matches(Reventless.DcbTag.string) string})
|
|
52
|
+
|
|
53
|
+
let targetName = "AddProduct"
|
|
54
|
+
let externalSystem = None
|
|
55
|
+
|
|
56
|
+
type role = Reventless.Role.name
|
|
57
|
+
let merchandiser = Reventless.Role.make("Merchandiser")
|
|
58
|
+
let admin = Reventless.Role.make("Admin")
|
|
59
|
+
let commandAuthorization = (command: command): Reventless.Authorization.rule<role> =>
|
|
60
|
+
switch command {
|
|
61
|
+
| AddProduct(_) => AllowRoles([merchandiser])
|
|
62
|
+
| AddCategory(_) => AllowRoles([admin])
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let translate = (input: externalInput) =>
|
|
66
|
+
switch input.kind {
|
|
67
|
+
| "product" => Ok([(input.id, AddProduct({productId: input.id}))])
|
|
68
|
+
| "both" =>
|
|
69
|
+
Ok([
|
|
70
|
+
(input.id, AddProduct({productId: input.id})),
|
|
71
|
+
(input.id, AddCategory({categoryId: input.id})),
|
|
72
|
+
])
|
|
73
|
+
| kind => Error("Unknown kind: " ++ kind)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// A command whose schema writes an absent note as `null`, which the runtime value does not.
|
|
78
|
+
module NoteFeedSpec = {
|
|
79
|
+
let name = "NoteFeed"
|
|
80
|
+
let moduleUrl: string = %raw(`import.meta.url`)
|
|
81
|
+
|
|
82
|
+
@schema
|
|
83
|
+
type externalInput = {orderId: string}
|
|
84
|
+
|
|
85
|
+
@schema
|
|
86
|
+
type command =
|
|
87
|
+
Annotate({orderId: @s.matches(Reventless.DcbTag.string) string, note: @s.null option<string>})
|
|
88
|
+
|
|
89
|
+
let targetName = "Annotate"
|
|
90
|
+
let externalSystem = None
|
|
91
|
+
|
|
92
|
+
let translate = (input: externalInput) => Ok([
|
|
93
|
+
(input.orderId, Annotate({orderId: input.orderId, note: None})),
|
|
94
|
+
])
|
|
95
|
+
}
|
|
@@ -56,10 +56,6 @@ function commandAuthorization(param) {
|
|
|
56
56
|
return "AllowAuthenticated";
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function commandTransition(param) {
|
|
60
|
-
return "Undeclared";
|
|
61
|
-
}
|
|
62
|
-
|
|
63
59
|
let traits = [];
|
|
64
60
|
|
|
65
61
|
let PaymentWebhookSpec = {
|
|
@@ -71,12 +67,151 @@ let PaymentWebhookSpec = {
|
|
|
71
67
|
externalSystem: undefined,
|
|
72
68
|
translate: translate,
|
|
73
69
|
commandAuthorization: commandAuthorization,
|
|
74
|
-
commandTransition: commandTransition,
|
|
75
70
|
traits: traits
|
|
76
71
|
};
|
|
77
72
|
|
|
73
|
+
let moduleUrl$2 = import.meta.url;
|
|
74
|
+
|
|
75
|
+
let externalInputSchema$1 = Sury.$schema(s => ({
|
|
76
|
+
kind: s.m(Sury.string),
|
|
77
|
+
id: s.m(Sury.string)
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
let commandSchema$1 = Sury.union([
|
|
81
|
+
Sury.$schema(s => ({
|
|
82
|
+
TAG: "AddProduct",
|
|
83
|
+
productId: s.m(DcbTag$Reventless.string)
|
|
84
|
+
})),
|
|
85
|
+
Sury.$schema(s => ({
|
|
86
|
+
TAG: "AddCategory",
|
|
87
|
+
categoryId: s.m(DcbTag$Reventless.string)
|
|
88
|
+
}))
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
let merchandiser = "Merchandiser";
|
|
92
|
+
|
|
93
|
+
let admin = "Admin";
|
|
94
|
+
|
|
95
|
+
function commandAuthorization$1(command) {
|
|
96
|
+
if (command.TAG === "AddProduct") {
|
|
97
|
+
return {
|
|
98
|
+
TAG: "AllowRoles",
|
|
99
|
+
_0: [merchandiser]
|
|
100
|
+
};
|
|
101
|
+
} else {
|
|
102
|
+
return {
|
|
103
|
+
TAG: "AllowRoles",
|
|
104
|
+
_0: [admin]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function translate$1(input) {
|
|
110
|
+
let kind = input.kind;
|
|
111
|
+
switch (kind) {
|
|
112
|
+
case "both" :
|
|
113
|
+
return {
|
|
114
|
+
TAG: "Ok",
|
|
115
|
+
_0: [
|
|
116
|
+
[
|
|
117
|
+
input.id,
|
|
118
|
+
{
|
|
119
|
+
TAG: "AddProduct",
|
|
120
|
+
productId: input.id
|
|
121
|
+
}
|
|
122
|
+
],
|
|
123
|
+
[
|
|
124
|
+
input.id,
|
|
125
|
+
{
|
|
126
|
+
TAG: "AddCategory",
|
|
127
|
+
categoryId: input.id
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
]
|
|
131
|
+
};
|
|
132
|
+
case "product" :
|
|
133
|
+
return {
|
|
134
|
+
TAG: "Ok",
|
|
135
|
+
_0: [[
|
|
136
|
+
input.id,
|
|
137
|
+
{
|
|
138
|
+
TAG: "AddProduct",
|
|
139
|
+
productId: input.id
|
|
140
|
+
}
|
|
141
|
+
]]
|
|
142
|
+
};
|
|
143
|
+
default:
|
|
144
|
+
return {
|
|
145
|
+
TAG: "Error",
|
|
146
|
+
_0: "Unknown kind: " + kind
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let traits$1 = [];
|
|
152
|
+
|
|
153
|
+
let CatalogFeedSpec = {
|
|
154
|
+
name: "CatalogFeed",
|
|
155
|
+
moduleUrl: moduleUrl$2,
|
|
156
|
+
externalInputSchema: externalInputSchema$1,
|
|
157
|
+
commandSchema: commandSchema$1,
|
|
158
|
+
targetName: "AddProduct",
|
|
159
|
+
externalSystem: undefined,
|
|
160
|
+
merchandiser: merchandiser,
|
|
161
|
+
admin: admin,
|
|
162
|
+
commandAuthorization: commandAuthorization$1,
|
|
163
|
+
translate: translate$1,
|
|
164
|
+
traits: traits$1
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
let moduleUrl$3 = import.meta.url;
|
|
168
|
+
|
|
169
|
+
let externalInputSchema$2 = Sury.$schema(s => ({
|
|
170
|
+
orderId: s.m(Sury.string)
|
|
171
|
+
}));
|
|
172
|
+
|
|
173
|
+
let commandSchema$2 = Sury.$schema(s => ({
|
|
174
|
+
TAG: "Annotate",
|
|
175
|
+
orderId: s.m(DcbTag$Reventless.string),
|
|
176
|
+
note: s.m(Sury.$nullAsOption(Sury.string))
|
|
177
|
+
}));
|
|
178
|
+
|
|
179
|
+
function translate$2(input) {
|
|
180
|
+
return {
|
|
181
|
+
TAG: "Ok",
|
|
182
|
+
_0: [[
|
|
183
|
+
input.orderId,
|
|
184
|
+
{
|
|
185
|
+
TAG: "Annotate",
|
|
186
|
+
orderId: input.orderId,
|
|
187
|
+
note: undefined
|
|
188
|
+
}
|
|
189
|
+
]]
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function commandAuthorization$2(param) {
|
|
194
|
+
return "AllowAuthenticated";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let traits$2 = [];
|
|
198
|
+
|
|
199
|
+
let NoteFeedSpec = {
|
|
200
|
+
name: "NoteFeed",
|
|
201
|
+
moduleUrl: moduleUrl$3,
|
|
202
|
+
externalInputSchema: externalInputSchema$2,
|
|
203
|
+
commandSchema: commandSchema$2,
|
|
204
|
+
targetName: "Annotate",
|
|
205
|
+
externalSystem: undefined,
|
|
206
|
+
translate: translate$2,
|
|
207
|
+
commandAuthorization: commandAuthorization$2,
|
|
208
|
+
traits: traits$2
|
|
209
|
+
};
|
|
210
|
+
|
|
78
211
|
export {
|
|
79
212
|
OrderEventLog,
|
|
80
213
|
PaymentWebhookSpec,
|
|
214
|
+
CatalogFeedSpec,
|
|
215
|
+
NoteFeedSpec,
|
|
81
216
|
}
|
|
82
217
|
/* moduleUrl Not a pure module */
|