@reventlessdev/reventless-core 3.0.0-alpha.291 → 3.0.0-alpha.293
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/package.json +7 -7
- package/src/admin/Platform_Admin_Structure.res +1 -2
- package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
- package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res.mjs +2 -2
- package/src/components/AutomationSlice/AutomationSlice_Callback.res +25 -46
- package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +27 -46
- package/src/components/AutomationSlice/AutomationSlice_Route.res +50 -0
- package/src/components/AutomationSlice/AutomationSlice_Route.res.mjs +39 -0
- package/src/components/Dcb/Dcb_Builder.res +31 -56
- package/src/components/Dcb/Dcb_Builder.res.mjs +13 -29
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +2 -2
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res.mjs +2 -2
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +125 -164
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +121 -130
- package/src/plugin/component/Plugin_Builder.res +8 -18
- package/src/plugin/component/Plugin_Builder.res.mjs +2 -8
- package/src/plugin/component/Plugin_Helpers.res +16 -9
- package/src/plugin/component/Plugin_Helpers.res.mjs +1 -1
- package/src/plugin/component/Plugin_Structure.res +35 -28
- package/src/plugin/component/Plugin_Structure.res.mjs +18 -8
- package/src/plugin/connect/PluginConnectExtension_Mapping.res.mjs +1 -1
- package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +1 -1
- package/src/plugin/connect/PluginExtensionPoint_UiFragment.res +1 -1
- package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +1 -1
- package/src/plugin/lifecycle/PluginSpec.res.mjs +2 -2
- package/tests/aggregate/AggregateCacheTest.res.mjs +4 -4
- package/tests/aggregate/AggregateConflictTest.res.mjs +4 -4
- package/tests/aggregate/AggregateFixtures.res.mjs +4 -4
- package/tests/aggregate/AggregateSnapshotTest.res.mjs +6 -6
- package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +6 -6
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +3 -3
- package/tests/commandgenerator/OwnerActingTest.res.mjs +7 -7
- package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +1 -1
- package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +3 -3
- package/tests/dcb/DcbFixtures.res.mjs +2 -2
- package/tests/dcb/DcbStateChangeSliceTest.res.mjs +7 -7
- package/tests/eventmapper/EventMapperFixtures.res.mjs +4 -4
- package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +2 -2
- package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +2 -2
- package/tests/plugin/PluginStructureAccessTest.res +57 -0
- package/tests/plugin/PluginStructureAccessTest.res.mjs +91 -1
- package/tests/plugin/PluginStructureTest.res.mjs +10 -10
- package/tests/plugin/StateChange/PsAttachInvoice.res.mjs +2 -2
- package/tests/plugin/StateChange/PsChangePhoto.res.mjs +2 -2
- package/tests/plugin/StateChange/PsDispatchShipment.res.mjs +2 -2
- package/tests/plugin/StateChange/PsGatedCommands.res.mjs +3 -3
- package/tests/plugin/StateChange/PsPlaceOrder.res.mjs +2 -2
- package/tests/plugin/StateChange/PsReserveStock.res.mjs +2 -2
- package/tests/plugin/StateChange/PsShipOrder.res.mjs +2 -2
- package/tests/plugin/StateChange/PsTransitionSwitch.res.mjs +2 -2
- package/tests/plugin/StateChange/PsTypoStore.res.mjs +2 -2
- package/tests/plugin/StateChange/PsUploadAvatar.res.mjs +2 -2
- package/tests/plugin/StateChange/PsUploadImages.res.mjs +2 -2
- package/tests/util/CommandPublisherTest.res.mjs +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,27 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.293 (2026-10-10)
|
|
7
|
+
|
|
8
|
+
* feat!: a command's rule is looked up by its name, without a cast ([5f02c40](https://github.com/ReventlessDev/reventless-core/commit/5f02c4081d077bf4a6951d8c9b2b16553a8ca87b))
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* a spec that writes `commandAuthorization` by hand writes
|
|
13
|
+
`authorizationOf = (name: string) => …` instead; the PPX refuses the old name.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# 3.0.0-alpha.292 (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
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* **gwt:** automation and translation tests take the slice as written, and the sidecars read them ([93a0919](https://github.com/ReventlessDev/reventless-core/commit/93a0919a0d66730cf9bf89144ad8fba3cb025332))
|
|
25
|
+
|
|
26
|
+
|
|
6
27
|
# 3.0.0-alpha.291 (2026-10-08)
|
|
7
28
|
|
|
8
29
|
**Note:** Version bump only for package @reventlessdev/reventless-core
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-core",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.293",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -28,22 +28,22 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"sury": "11.0.0",
|
|
30
30
|
"uuid": "^13.0.0",
|
|
31
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
32
|
-
"@reventlessdev/rescript-fast-csv": "2.0.0-alpha.22",
|
|
33
31
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
32
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
34
33
|
"@reventlessdev/rescript-node": "2.0.0-alpha.22",
|
|
35
34
|
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.15",
|
|
35
|
+
"@reventlessdev/rescript-fast-csv": "2.0.0-alpha.22",
|
|
36
36
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.23",
|
|
37
|
-
"@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
|
|
38
37
|
"@reventlessdev/rescript-ssh2": "2.0.0-alpha.23",
|
|
39
|
-
"@reventlessdev/
|
|
38
|
+
"@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
|
|
40
39
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.39",
|
|
41
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
40
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.162",
|
|
41
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.190"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "12.3.1",
|
|
45
45
|
"sury-ppx": "11.0.0",
|
|
46
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
46
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.99"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"rescript": "^12.3.1"
|
|
@@ -37,8 +37,7 @@ let pluginCommands: array<commandDef> = Plugin_Structure.extractCommandDefs(
|
|
|
37
37
|
~isAggregate=true,
|
|
38
38
|
~mutationFieldFor=variantName =>
|
|
39
39
|
Api_Naming.adminField(~name=PluginSpec.name ++ "_" ++ variantName),
|
|
40
|
-
~
|
|
41
|
-
PluginSpec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
|
|
40
|
+
~authorizationOf=name => PluginSpec.authorizationOf(name)->Reventless.Authorization.named,
|
|
42
41
|
~commandTransition=PluginSpec.commandTransition->Obj.magic,
|
|
43
42
|
commandSchema,
|
|
44
43
|
)
|
|
@@ -8,7 +8,7 @@ import * as PluginsReadModelSpec$ReventlessCore from "../plugin/lifecycle/Plugin
|
|
|
8
8
|
|
|
9
9
|
let pluginId = "Platform";
|
|
10
10
|
|
|
11
|
-
let pluginCommands = Plugin_Structure$ReventlessCore.extractCommandDefs(undefined, true, undefined, variantName => Api_Naming$ReventlessCore.adminField(PluginSpec$ReventlessCore.name + "_" + variantName),
|
|
11
|
+
let pluginCommands = Plugin_Structure$ReventlessCore.extractCommandDefs(undefined, true, undefined, variantName => Api_Naming$ReventlessCore.adminField(PluginSpec$ReventlessCore.name + "_" + variantName), name => Authorization$Reventless.named(PluginSpec$ReventlessCore.authorizationOf(name)), PluginSpec$ReventlessCore.commandTransition, undefined, PluginSpec$ReventlessCore.commandSchema);
|
|
12
12
|
|
|
13
13
|
let pluginAggregate_producedEventTypes = [];
|
|
14
14
|
|
|
@@ -42,7 +42,7 @@ let eventSchema = Sury.union([
|
|
|
42
42
|
|
|
43
43
|
let commandSchema$1 = Api$ReventlessInfra.markNoApi(commandSchema);
|
|
44
44
|
|
|
45
|
-
function
|
|
45
|
+
function authorizationOf(param) {
|
|
46
46
|
return "AllowAuthenticated";
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -70,7 +70,7 @@ export {
|
|
|
70
70
|
consumedEventSchema,
|
|
71
71
|
commandSchema$1 as commandSchema,
|
|
72
72
|
moduleUrl,
|
|
73
|
-
|
|
73
|
+
authorizationOf,
|
|
74
74
|
readConsistency,
|
|
75
75
|
commandTransition,
|
|
76
76
|
traits,
|
|
@@ -166,50 +166,33 @@ module Make = (
|
|
|
166
166
|
None
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
type dispatch = {
|
|
171
|
-
sourceName: string,
|
|
172
|
-
handle: (JSON.t, ~sourceId: string, Reventless.AutomationSlice.context) => unit,
|
|
173
|
-
}
|
|
169
|
+
module Route = AutomationSlice_Route.Make(Spec, Automation)
|
|
174
170
|
|
|
175
|
-
let
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
| None =>
|
|
188
|
-
let row: todoRow = {
|
|
189
|
-
item: item->Reventless.Util_Sury.toJson(Spec.todoItemSchema),
|
|
190
|
-
status: Pending,
|
|
191
|
-
createdAt: now(),
|
|
192
|
-
retryCount: 0,
|
|
193
|
-
maxRetries: Spec.maxRetries,
|
|
194
|
-
}
|
|
195
|
-
todoItems->Dict.set(id, row)
|
|
196
|
-
}
|
|
197
|
-
})
|
|
198
|
-
// Resolve — mark a pending item as completed.
|
|
199
|
-
switch M.resolve(event) {
|
|
200
|
-
| Some(id) =>
|
|
201
|
-
todoItems
|
|
202
|
-
->Dict.get(id)
|
|
203
|
-
->Option.forEach(row => {
|
|
204
|
-
todoItems->Dict.set(id, {...row, status: Completed, completedAt: now()})
|
|
205
|
-
})
|
|
206
|
-
| None => ()
|
|
171
|
+
let apply = ({collected, resolved}: AutomationSlice_Route.routed<Spec.todoItem>) => {
|
|
172
|
+
// Collect — append new items keyed by ID; first writer wins (idempotent).
|
|
173
|
+
collected->Array.forEach(((id, item)) => {
|
|
174
|
+
switch todoItems->Dict.get(id) {
|
|
175
|
+
| Some(_) => () // Already exists — skip
|
|
176
|
+
| None =>
|
|
177
|
+
let row: todoRow = {
|
|
178
|
+
item: item->Reventless.Util_Sury.toJson(Spec.todoItemSchema),
|
|
179
|
+
status: Pending,
|
|
180
|
+
createdAt: now(),
|
|
181
|
+
retryCount: 0,
|
|
182
|
+
maxRetries: Spec.maxRetries,
|
|
207
183
|
}
|
|
208
|
-
|
|
184
|
+
todoItems->Dict.set(id, row)
|
|
209
185
|
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
186
|
+
})
|
|
187
|
+
// Resolve — mark a pending item as completed.
|
|
188
|
+
resolved->Option.forEach(id =>
|
|
189
|
+
todoItems
|
|
190
|
+
->Dict.get(id)
|
|
191
|
+
->Option.forEach(row => {
|
|
192
|
+
todoItems->Dict.set(id, {...row, status: Completed, completedAt: now()})
|
|
193
|
+
})
|
|
194
|
+
)
|
|
195
|
+
}
|
|
213
196
|
|
|
214
197
|
let phase1 = (events: array<JSON.t>, ctx: Reventless.AutomationSlice.context) => {
|
|
215
198
|
events->Array.forEach(json => {
|
|
@@ -223,11 +206,7 @@ module Make = (
|
|
|
223
206
|
| None => json
|
|
224
207
|
}
|
|
225
208
|
// Multiple mappings can share a sourceName; all matching dispatches run.
|
|
226
|
-
|
|
227
|
-
if d.sourceName == sourceName {
|
|
228
|
-
d.handle(eventPayload, ~sourceId=context.id, ctx)
|
|
229
|
-
}
|
|
230
|
-
})
|
|
209
|
+
Route.route(eventPayload, ~sourceName, ~sourceId=context.id, ctx)->Array.forEach(apply)
|
|
231
210
|
})
|
|
232
211
|
}
|
|
233
212
|
|
|
@@ -8,11 +8,11 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
8
8
|
import * as Effect from "effect/Effect";
|
|
9
9
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
10
10
|
import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
|
|
11
|
-
import * as DcbDecode$Reventless from "@reventlessdev/reventless-spec/src/components/DcbDecode.res.mjs";
|
|
12
11
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
13
12
|
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
14
13
|
import * as Message$ReventlessCore from "../../Message.res.mjs";
|
|
15
14
|
import * as EffectLogger$ReventlessCore from "../../util/EffectLogger.res.mjs";
|
|
15
|
+
import * as AutomationSlice_Route$ReventlessCore from "./AutomationSlice_Route.res.mjs";
|
|
16
16
|
|
|
17
17
|
let todoStatusSchema = Sury.union([
|
|
18
18
|
Sury.literal("Pending"),
|
|
@@ -55,59 +55,40 @@ function Make(Spec) {
|
|
|
55
55
|
newrecord.status = retryCount >= Spec.maxRetries ? "Abandoned" : "Failed";
|
|
56
56
|
return newrecord;
|
|
57
57
|
};
|
|
58
|
-
let
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
let
|
|
62
|
-
let
|
|
63
|
-
if (
|
|
58
|
+
let Route = AutomationSlice_Route$ReventlessCore.Make(Spec)(Automation);
|
|
59
|
+
let apply = param => {
|
|
60
|
+
param.collected.forEach(param => {
|
|
61
|
+
let id = param[0];
|
|
62
|
+
let match = todoItems[id];
|
|
63
|
+
if (match !== undefined) {
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
-
let
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let id = M.resolve(event$1);
|
|
86
|
-
if (id !== undefined) {
|
|
87
|
-
return Stdlib_Option.forEach(todoItems[id], row => {
|
|
88
|
-
let newrecord = {...row};
|
|
89
|
-
newrecord.completedAt = new Date().toISOString();
|
|
90
|
-
newrecord.status = "Completed";
|
|
91
|
-
todoItems[id] = newrecord;
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
return {
|
|
96
|
-
sourceName: M.sourceName,
|
|
97
|
-
handle: handle
|
|
98
|
-
};
|
|
99
|
-
});
|
|
66
|
+
let row_item = Util_Sury$Reventless.toJson(param[1], Spec.todoItemSchema);
|
|
67
|
+
let row_createdAt = new Date().toISOString();
|
|
68
|
+
let row_maxRetries = Spec.maxRetries;
|
|
69
|
+
let row = {
|
|
70
|
+
item: row_item,
|
|
71
|
+
status: "Pending",
|
|
72
|
+
createdAt: row_createdAt,
|
|
73
|
+
retryCount: 0,
|
|
74
|
+
maxRetries: row_maxRetries
|
|
75
|
+
};
|
|
76
|
+
todoItems[id] = row;
|
|
77
|
+
});
|
|
78
|
+
Stdlib_Option.forEach(param.resolved, id => Stdlib_Option.forEach(todoItems[id], row => {
|
|
79
|
+
let newrecord = {...row};
|
|
80
|
+
newrecord.completedAt = new Date().toISOString();
|
|
81
|
+
newrecord.status = "Completed";
|
|
82
|
+
todoItems[id] = newrecord;
|
|
83
|
+
}));
|
|
84
|
+
};
|
|
100
85
|
let phase1 = (events, ctx) => {
|
|
101
86
|
events.forEach(json => {
|
|
102
87
|
let context = Message$ReventlessCore.decode(json, Message$Reventless.contextSchema);
|
|
103
88
|
let sourceName = context.meta.service;
|
|
104
89
|
let dict = Stdlib_JSON.Decode.object(json);
|
|
105
90
|
let eventPayload = dict !== undefined ? Stdlib_Option.getOr(dict["event"], json) : json;
|
|
106
|
-
|
|
107
|
-
if (d.sourceName === sourceName) {
|
|
108
|
-
return d.handle(eventPayload, context.id, ctx);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
91
|
+
Route.route(eventPayload, sourceName, context.id, ctx).forEach(apply);
|
|
111
92
|
});
|
|
112
93
|
};
|
|
113
94
|
let phase2 = async publishJsons => {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Decode one source event and run its mappings' `collect`/`resolve`, with no
|
|
2
|
+
// side effects. Shared by the runtime callback, which applies the result to its
|
|
3
|
+
// to-do list, and by the GWT sweep, which must route exactly as the runtime does.
|
|
4
|
+
|
|
5
|
+
/** What one mapping made of one event. */
|
|
6
|
+
type routed<'item> = {
|
|
7
|
+
collected: array<(string, 'item)>,
|
|
8
|
+
resolved: option<string>,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module Make = (
|
|
12
|
+
Spec: Reventless.AutomationSlice.Spec,
|
|
13
|
+
Automation: Reventless.AutomationSlice.Automation with module Spec := Spec,
|
|
14
|
+
) => {
|
|
15
|
+
type dispatch = {
|
|
16
|
+
sourceName: string,
|
|
17
|
+
handle: (
|
|
18
|
+
JSON.t,
|
|
19
|
+
~sourceId: string,
|
|
20
|
+
Reventless.AutomationSlice.context,
|
|
21
|
+
) => option<routed<Spec.todoItem>>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Decoders are compiled once, per mapping.
|
|
25
|
+
let dispatches: array<
|
|
26
|
+
dispatch,
|
|
27
|
+
> = Automation.mappings->Array.map((module(M: Automation.Mapping)) => {
|
|
28
|
+
let decoder = Reventless.DcbDecode.makeDecoder(M.sourceEventSchema)
|
|
29
|
+
let handle = (json: JSON.t, ~sourceId: string, ctx: Reventless.AutomationSlice.context) => {
|
|
30
|
+
let (eventType, dataDict) = json->Message.splitMessage
|
|
31
|
+
decoder.decode(~eventType, ~data=dataDict)->Option.map(event => {
|
|
32
|
+
collected: M.collect(event, ~sourceId, ctx),
|
|
33
|
+
resolved: M.resolve(event),
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
{sourceName: M.sourceName, handle}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/** Every mapping registered for `sourceName` that understood the payload, in
|
|
40
|
+
registration order. `payload` is the inner event, not the envelope. */
|
|
41
|
+
let route = (
|
|
42
|
+
payload: JSON.t,
|
|
43
|
+
~sourceName: string,
|
|
44
|
+
~sourceId: string,
|
|
45
|
+
ctx: Reventless.AutomationSlice.context,
|
|
46
|
+
): array<routed<Spec.todoItem>> =>
|
|
47
|
+
dispatches->Array.filterMap(d =>
|
|
48
|
+
d.sourceName == sourceName ? d.handle(payload, ~sourceId, ctx) : None
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as DcbDecode$Reventless from "@reventlessdev/reventless-spec/src/components/DcbDecode.res.mjs";
|
|
6
|
+
import * as Message$ReventlessCore from "../../Message.res.mjs";
|
|
7
|
+
|
|
8
|
+
function Make(Spec) {
|
|
9
|
+
return Automation => {
|
|
10
|
+
let dispatches = Automation.mappings.map(M => {
|
|
11
|
+
let decoder = DcbDecode$Reventless.makeDecoder(M.sourceEventSchema);
|
|
12
|
+
let handle = (json, sourceId, ctx) => {
|
|
13
|
+
let match = Message$ReventlessCore.splitMessage(json);
|
|
14
|
+
return Stdlib_Option.map(decoder.decode(match[0], match[1]), event => ({
|
|
15
|
+
collected: M.collect(event, sourceId, ctx),
|
|
16
|
+
resolved: M.resolve(event)
|
|
17
|
+
}));
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
sourceName: M.sourceName,
|
|
21
|
+
handle: handle
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
let route = (payload, sourceName, sourceId, ctx) => Stdlib_Array.filterMap(dispatches, d => {
|
|
25
|
+
if (d.sourceName === sourceName) {
|
|
26
|
+
return d.handle(payload, sourceId, ctx);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
dispatches: dispatches,
|
|
31
|
+
route: route
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
Make,
|
|
38
|
+
}
|
|
39
|
+
/* DcbDecode-Reventless Not a pure module */
|
|
@@ -89,6 +89,16 @@ let emptyResult: dcbResult = {
|
|
|
89
89
|
eventLogEntries: [],
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// An inbound slice's door rule, read off its spec.
|
|
93
|
+
let inboundDoorPermission = (
|
|
94
|
+
commandSchema: S.t<'command>,
|
|
95
|
+
authorizationOf: string => Reventless.Authorization.rule<'role>,
|
|
96
|
+
) =>
|
|
97
|
+
Plugin_Structure.inboundDoorPermission(
|
|
98
|
+
~commandSchema=commandSchema->S.castToUnknown,
|
|
99
|
+
~authorizationOf=name => name->authorizationOf->Reventless.Authorization.named,
|
|
100
|
+
)
|
|
101
|
+
|
|
92
102
|
module Make = (
|
|
93
103
|
DcbEventLogStorage: DcbEventLog_Adapter.Storage,
|
|
94
104
|
DcbEventTopicPublisher: EventTopic_Adapter.Publisher,
|
|
@@ -534,8 +544,7 @@ module Make = (
|
|
|
534
544
|
~commandSchema,
|
|
535
545
|
)->Array.map(((f, _)) => f),
|
|
536
546
|
~commandSchema,
|
|
537
|
-
~
|
|
538
|
-
S.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
|
|
547
|
+
~authorizationOf=name => S.Spec.authorizationOf(name)->Reventless.Authorization.named,
|
|
539
548
|
)
|
|
540
549
|
}
|
|
541
550
|
})
|
|
@@ -550,8 +559,7 @@ module Make = (
|
|
|
550
559
|
~commandSchema,
|
|
551
560
|
)->Array.map(((f, _)) => f),
|
|
552
561
|
~commandSchema,
|
|
553
|
-
~
|
|
554
|
-
S.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
|
|
562
|
+
~authorizationOf=name => S.Spec.authorizationOf(name)->Reventless.Authorization.named,
|
|
555
563
|
)
|
|
556
564
|
}
|
|
557
565
|
})
|
|
@@ -749,6 +757,7 @@ module Make = (
|
|
|
749
757
|
registerResolver(
|
|
750
758
|
~fieldName,
|
|
751
759
|
~externalInputSchema=ITS.Spec.externalInputSchema->S.castToUnknown,
|
|
760
|
+
~permission=?inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.authorizationOf),
|
|
752
761
|
)
|
|
753
762
|
| None => ()
|
|
754
763
|
}
|
|
@@ -865,16 +874,14 @@ module Make = (
|
|
|
865
874
|
let raw: dict<JSON.t> = event->Obj.magic
|
|
866
875
|
switch raw->Dict.get("__inboundTranslation") {
|
|
867
876
|
| Some(_) =>
|
|
868
|
-
let
|
|
869
|
-
|
|
870
|
-
->Dict.get("fieldName")
|
|
871
|
-
->Option.flatMap(JSON.Decode.string)
|
|
872
|
-
->Option.getOr("")
|
|
873
|
-
let args = raw->Dict.get("arguments")->Option.getOr(JSON.Encode.null)
|
|
874
|
-
switch receivers->Dict.get(fieldName) {
|
|
877
|
+
let invocation: InboundTranslationSlice_Callback.doorInvocation = event->Obj.magic
|
|
878
|
+
switch receivers->Dict.get(invocation.fieldName) {
|
|
875
879
|
| Some(receiveFn) =>
|
|
876
880
|
Effect.promise(async () => {
|
|
877
|
-
let result = await receiveFn(
|
|
881
|
+
let result = await receiveFn(
|
|
882
|
+
invocation.arguments,
|
|
883
|
+
~caller=InboundTranslationSlice_Callback.doorCaller(invocation.identity),
|
|
884
|
+
)
|
|
878
885
|
let response =
|
|
879
886
|
result
|
|
880
887
|
->InboundTranslationSlice_Callback.receiveResultToOutcome
|
|
@@ -1034,33 +1041,9 @@ module Make = (
|
|
|
1034
1041
|
})
|
|
1035
1042
|
}
|
|
1036
1043
|
|
|
1037
|
-
// DCB-specific API schema entries
|
|
1038
|
-
//
|
|
1039
|
-
//
|
|
1040
|
-
// command type may declare multiple constructors with different
|
|
1041
|
-
// authorization rules. The Cognito group directive operates at field
|
|
1042
|
-
// granularity, so we read the auth for the first constructor (matching
|
|
1043
|
-
// the existing dcbTags convention at line 549 above). When all
|
|
1044
|
-
// constructors share the file-level default, this is exact; when they
|
|
1045
|
-
// differ, resolver-level enforcement still fires inside the per-slice
|
|
1046
|
-
// handler (the file-level rule applied here is the least-restrictive
|
|
1047
|
-
// bound).
|
|
1048
|
-
let permissionForFirstConstructor = (
|
|
1049
|
-
~commandSchema: S.t<unknown>,
|
|
1050
|
-
~commandAuthorization: unknown => Reventless.Authorization.permission,
|
|
1051
|
-
): option<Reventless.Authorization.permission> => {
|
|
1052
|
-
let names = Reventless.DcbTag.extractAllVariantNames(commandSchema->Obj.magic)
|
|
1053
|
-
switch names->Array.get(0) {
|
|
1054
|
-
| None => None
|
|
1055
|
-
| Some(first) =>
|
|
1056
|
-
let hasPayload = Reventless.DcbTag.isVariantPayloadBearing(
|
|
1057
|
-
commandSchema->Obj.magic,
|
|
1058
|
-
first,
|
|
1059
|
-
)
|
|
1060
|
-
let syntheticCmd: unknown = hasPayload ? {"TAG": first}->Obj.magic : first->Obj.magic
|
|
1061
|
-
Some(commandAuthorization(syntheticCmd))
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1044
|
+
// DCB-specific API schema entries. A state change slice has one field per
|
|
1045
|
+
// constructor, each gated by its own rule; an inbound slice has one field,
|
|
1046
|
+
// gated by `inboundDoorPermission`, and checks each command in `receive`.
|
|
1064
1047
|
let mutationEntriesFromSlices =
|
|
1065
1048
|
stateChangeSlices->Array.filterMap((module(S: StateChangeSlice.T)) => {
|
|
1066
1049
|
let commandSchema = S.Spec.commandSchema->Reventless.DcbTag.toUnknownSchema
|
|
@@ -1078,17 +1061,13 @@ module Make = (
|
|
|
1078
1061
|
)
|
|
1079
1062
|
// One SDL field per constructor, each with its own constructor's args and
|
|
1080
1063
|
// its own per-constructor authorization rule.
|
|
1081
|
-
let commandAuthorization: unknown => Reventless.Authorization.permission = command =>
|
|
1082
|
-
S.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named
|
|
1083
1064
|
let fieldPermissions = Dict.make()
|
|
1084
|
-
fieldSpecs->Array.forEach(((fieldName, ctor)) =>
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
ctor,
|
|
1065
|
+
fieldSpecs->Array.forEach(((fieldName, ctor)) =>
|
|
1066
|
+
fieldPermissions->Dict.set(
|
|
1067
|
+
fieldName,
|
|
1068
|
+
S.Spec.authorizationOf(ctor)->Reventless.Authorization.named,
|
|
1088
1069
|
)
|
|
1089
|
-
|
|
1090
|
-
fieldPermissions->Dict.set(fieldName, commandAuthorization(syntheticCmd))
|
|
1091
|
-
})
|
|
1070
|
+
)
|
|
1092
1071
|
Some({
|
|
1093
1072
|
ReventlessInfra.Api.fieldNames: fieldSpecs->Array.map(((f, _)) => f),
|
|
1094
1073
|
commandSchema,
|
|
@@ -1108,14 +1087,10 @@ module Make = (
|
|
|
1108
1087
|
inboundTranslationSlices->Array.map((module(ITS: InboundTranslationSlice.T)) => {
|
|
1109
1088
|
let fieldName = Api_Naming.sliceMutationField(~plugin=name, ~slice=ITS.Spec.name)
|
|
1110
1089
|
let fieldPermissions = Dict.make()
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
) {
|
|
1116
|
-
| Some(rule) => fieldPermissions->Dict.set(fieldName, rule)
|
|
1117
|
-
| None => ()
|
|
1118
|
-
}
|
|
1090
|
+
inboundDoorPermission(
|
|
1091
|
+
ITS.Spec.commandSchema,
|
|
1092
|
+
ITS.Spec.authorizationOf,
|
|
1093
|
+
)->Option.forEach(rule => fieldPermissions->Dict.set(fieldName, rule))
|
|
1119
1094
|
{
|
|
1120
1095
|
ReventlessInfra.Api.fieldNames: [fieldName],
|
|
1121
1096
|
commandSchema: ITS.Spec.externalInputSchema->S.castToUnknown,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Sury from "sury";
|
|
4
|
-
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
4
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
6
5
|
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
7
6
|
import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
|
|
@@ -119,6 +118,10 @@ let emptyResult = {
|
|
|
119
118
|
eventLogEntries: emptyResult_eventLogEntries
|
|
120
119
|
};
|
|
121
120
|
|
|
121
|
+
function inboundDoorPermission(commandSchema, authorizationOf) {
|
|
122
|
+
return Plugin_Structure$ReventlessCore.inboundDoorPermission(commandSchema, name => Authorization$Reventless.named(authorizationOf(name)));
|
|
123
|
+
}
|
|
124
|
+
|
|
122
125
|
function Make(DcbEventLogStorage) {
|
|
123
126
|
return DcbEventTopicPublisher => (DcbCommandTopicChannel => (DcbCommandTopicChannelAsync => (RuntimeBuilder => (HooksConfig => {
|
|
124
127
|
let construct = (name, childName, apiNamePrefixOpt, onAdminApiOpt, environmentOpt, platformNameOpt, aggregateEventTopicsOpt, aggregateProducedEventsOpt, publishToAggregatesOpt, stateChangeSlices, stateViewSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, systemCallableComponentsOpt, componentRuntimeOpt, pluginStructure, opts) => {
|
|
@@ -292,13 +295,13 @@ function Make(DcbEventLogStorage) {
|
|
|
292
295
|
syncSlices.forEach(S => {
|
|
293
296
|
let commandSchema = S.Spec.commandSchema;
|
|
294
297
|
if (!ApiNoApiHelpers$ReventlessCore.isNoApi(commandSchema)) {
|
|
295
|
-
return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema,
|
|
298
|
+
return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema, name => Authorization$Reventless.named(S.Spec.authorizationOf(name)));
|
|
296
299
|
}
|
|
297
300
|
});
|
|
298
301
|
asyncSlices.forEach(S => {
|
|
299
302
|
let commandSchema = S.Spec.commandSchema;
|
|
300
303
|
if (!ApiNoApiHelpers$ReventlessCore.isNoApi(commandSchema)) {
|
|
301
|
-
return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema,
|
|
304
|
+
return registerResolver("Dcb", Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema).map(param => param[0]), commandSchema, name => Authorization$Reventless.named(S.Spec.authorizationOf(name)));
|
|
302
305
|
}
|
|
303
306
|
});
|
|
304
307
|
}
|
|
@@ -394,7 +397,7 @@ function Make(DcbEventLogStorage) {
|
|
|
394
397
|
let fieldName = Api_Naming$ReventlessCore.sliceMutationField(name, ITS.Spec.name);
|
|
395
398
|
let registerResolver = HooksConfig.hooks.inboundMutationResolverHook;
|
|
396
399
|
if (registerResolver !== undefined) {
|
|
397
|
-
registerResolver(fieldName, ITS.Spec.externalInputSchema);
|
|
400
|
+
registerResolver(fieldName, ITS.Spec.externalInputSchema, inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.authorizationOf));
|
|
398
401
|
}
|
|
399
402
|
let bindReceive = HooksConfig.hooks.inboundMutationBindReceiveHook;
|
|
400
403
|
if (bindReceive !== undefined) {
|
|
@@ -464,12 +467,10 @@ function Make(DcbEventLogStorage) {
|
|
|
464
467
|
return (event, ctx) => {
|
|
465
468
|
let match = event["__inboundTranslation"];
|
|
466
469
|
if (match !== undefined) {
|
|
467
|
-
let
|
|
468
|
-
let args = Stdlib_Option.getOr(event["arguments"], null);
|
|
469
|
-
let receiveFn = receivers[fieldName];
|
|
470
|
+
let receiveFn = receivers[event.fieldName];
|
|
470
471
|
if (receiveFn !== undefined) {
|
|
471
472
|
return Effect.promise(async () => {
|
|
472
|
-
let result = await receiveFn(
|
|
473
|
+
let result = await receiveFn(event.arguments, InboundTranslationSlice_Callback$ReventlessCore.doorCaller(event.identity));
|
|
473
474
|
return CommandTopic$ReventlessCore.commandOutcomeToJson(InboundTranslationSlice_Callback$ReventlessCore.receiveResultToOutcome(result));
|
|
474
475
|
});
|
|
475
476
|
} else {
|
|
@@ -544,18 +545,6 @@ function Make(DcbEventLogStorage) {
|
|
|
544
545
|
RuntimeBuilder.forDcbCommandTopic(asyncDcbHandler, asyncDcbConnectFn, sliceMemoryFloor(asyncSlices), sliceTimeoutFloor(asyncSlices), asyncDcbCommandTopic);
|
|
545
546
|
});
|
|
546
547
|
};
|
|
547
|
-
let permissionForFirstConstructor = (commandSchema, commandAuthorization) => {
|
|
548
|
-
let names = DcbTag$Reventless.extractAllVariantNames(commandSchema);
|
|
549
|
-
let first = names[0];
|
|
550
|
-
if (first === undefined) {
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
let hasPayload = DcbTag$Reventless.isVariantPayloadBearing(commandSchema, first);
|
|
554
|
-
let syntheticCmd = hasPayload ? ({
|
|
555
|
-
TAG: first
|
|
556
|
-
}) : first;
|
|
557
|
-
return commandAuthorization(syntheticCmd);
|
|
558
|
-
};
|
|
559
548
|
let mutationEntriesFromSlices = Stdlib_Array.filterMap(stateChangeSlices, S => {
|
|
560
549
|
let commandSchema = S.Spec.commandSchema;
|
|
561
550
|
let fieldSpecs = Api_Naming$ReventlessCore.sliceMutationFields(apiNamePrefix, S.Spec.name, commandSchema);
|
|
@@ -565,12 +554,7 @@ function Make(DcbEventLogStorage) {
|
|
|
565
554
|
let sliceDef = Stdlib_Option.flatMap(pluginStructure, s => s.stateChangeSlices.find(d => d.name === S.Spec.name));
|
|
566
555
|
let fieldPermissions = {};
|
|
567
556
|
fieldSpecs.forEach(param => {
|
|
568
|
-
|
|
569
|
-
let hasPayload = DcbTag$Reventless.isVariantPayloadBearing(commandSchema, ctor);
|
|
570
|
-
let syntheticCmd = hasPayload ? ({
|
|
571
|
-
TAG: ctor
|
|
572
|
-
}) : ctor;
|
|
573
|
-
fieldPermissions[param[0]] = Authorization$Reventless.named(S.Spec.commandAuthorization(syntheticCmd));
|
|
557
|
+
fieldPermissions[param[0]] = Authorization$Reventless.named(S.Spec.authorizationOf(param[1]));
|
|
574
558
|
});
|
|
575
559
|
return {
|
|
576
560
|
fieldNames: fieldSpecs.map(param => param[0]),
|
|
@@ -585,10 +569,9 @@ function Make(DcbEventLogStorage) {
|
|
|
585
569
|
let mutationEntriesFromInboundSlices = inboundTranslationSlices.map(ITS => {
|
|
586
570
|
let fieldName = Api_Naming$ReventlessCore.sliceMutationField(name, ITS.Spec.name);
|
|
587
571
|
let fieldPermissions = {};
|
|
588
|
-
|
|
589
|
-
if (rule !== undefined) {
|
|
572
|
+
Stdlib_Option.forEach(inboundDoorPermission(ITS.Spec.commandSchema, ITS.Spec.authorizationOf), rule => {
|
|
590
573
|
fieldPermissions[fieldName] = rule;
|
|
591
|
-
}
|
|
574
|
+
});
|
|
592
575
|
return {
|
|
593
576
|
fieldNames: [fieldName],
|
|
594
577
|
commandSchema: ITS.Spec.externalInputSchema,
|
|
@@ -691,6 +674,7 @@ export {
|
|
|
691
674
|
variantTag,
|
|
692
675
|
mergedEventSchema,
|
|
693
676
|
emptyResult,
|
|
677
|
+
inboundDoorPermission,
|
|
694
678
|
Make,
|
|
695
679
|
}
|
|
696
680
|
/* log Not a pure module */
|
|
@@ -81,8 +81,8 @@ module Make = (
|
|
|
81
81
|
->Pulumi.Output.all2
|
|
82
82
|
->Pulumi.Output.apply(((queryDbOps, publishJsonsFn)) => {
|
|
83
83
|
let ops: InboundTranslationSlice.operations = {
|
|
84
|
-
receive: async inputJson => {
|
|
85
|
-
let result = await Callback.receive(publishJsonsFn, inputJson)
|
|
84
|
+
receive: async (inputJson, ~caller=?) => {
|
|
85
|
+
let result = await Callback.receive(publishJsonsFn, inputJson, ~caller?)
|
|
86
86
|
await syncToQueryDb(queryDbOps, result->InboundTranslationSlice_Callback.requestIdOf)
|
|
87
87
|
result
|
|
88
88
|
},
|