@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.
Files changed (55) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/package.json +7 -7
  3. package/src/admin/Platform_Admin_Structure.res +1 -2
  4. package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
  5. package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res.mjs +2 -2
  6. package/src/components/AutomationSlice/AutomationSlice_Callback.res +25 -46
  7. package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +27 -46
  8. package/src/components/AutomationSlice/AutomationSlice_Route.res +50 -0
  9. package/src/components/AutomationSlice/AutomationSlice_Route.res.mjs +39 -0
  10. package/src/components/Dcb/Dcb_Builder.res +31 -56
  11. package/src/components/Dcb/Dcb_Builder.res.mjs +13 -29
  12. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +2 -2
  13. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res.mjs +2 -2
  14. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +125 -164
  15. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +121 -130
  16. package/src/plugin/component/Plugin_Builder.res +8 -18
  17. package/src/plugin/component/Plugin_Builder.res.mjs +2 -8
  18. package/src/plugin/component/Plugin_Helpers.res +16 -9
  19. package/src/plugin/component/Plugin_Helpers.res.mjs +1 -1
  20. package/src/plugin/component/Plugin_Structure.res +35 -28
  21. package/src/plugin/component/Plugin_Structure.res.mjs +18 -8
  22. package/src/plugin/connect/PluginConnectExtension_Mapping.res.mjs +1 -1
  23. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +1 -1
  24. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res +1 -1
  25. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +1 -1
  26. package/src/plugin/lifecycle/PluginSpec.res.mjs +2 -2
  27. package/tests/aggregate/AggregateCacheTest.res.mjs +4 -4
  28. package/tests/aggregate/AggregateConflictTest.res.mjs +4 -4
  29. package/tests/aggregate/AggregateFixtures.res.mjs +4 -4
  30. package/tests/aggregate/AggregateSnapshotTest.res.mjs +6 -6
  31. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +6 -6
  32. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +3 -3
  33. package/tests/commandgenerator/OwnerActingTest.res.mjs +7 -7
  34. package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +1 -1
  35. package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +3 -3
  36. package/tests/dcb/DcbFixtures.res.mjs +2 -2
  37. package/tests/dcb/DcbStateChangeSliceTest.res.mjs +7 -7
  38. package/tests/eventmapper/EventMapperFixtures.res.mjs +4 -4
  39. package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +2 -2
  40. package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +2 -2
  41. package/tests/plugin/PluginStructureAccessTest.res +57 -0
  42. package/tests/plugin/PluginStructureAccessTest.res.mjs +91 -1
  43. package/tests/plugin/PluginStructureTest.res.mjs +10 -10
  44. package/tests/plugin/StateChange/PsAttachInvoice.res.mjs +2 -2
  45. package/tests/plugin/StateChange/PsChangePhoto.res.mjs +2 -2
  46. package/tests/plugin/StateChange/PsDispatchShipment.res.mjs +2 -2
  47. package/tests/plugin/StateChange/PsGatedCommands.res.mjs +3 -3
  48. package/tests/plugin/StateChange/PsPlaceOrder.res.mjs +2 -2
  49. package/tests/plugin/StateChange/PsReserveStock.res.mjs +2 -2
  50. package/tests/plugin/StateChange/PsShipOrder.res.mjs +2 -2
  51. package/tests/plugin/StateChange/PsTransitionSwitch.res.mjs +2 -2
  52. package/tests/plugin/StateChange/PsTypoStore.res.mjs +2 -2
  53. package/tests/plugin/StateChange/PsUploadAvatar.res.mjs +2 -2
  54. package/tests/plugin/StateChange/PsUploadImages.res.mjs +2 -2
  55. 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.291",
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/reventless-infra": "3.0.0-alpha.188",
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.160"
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.98"
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
- ~commandAuthorization=command =>
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), command => Authorization$Reventless.named(PluginSpec$ReventlessCore.commandAuthorization(command)), PluginSpec$ReventlessCore.commandTransition, undefined, PluginSpec$ReventlessCore.commandSchema);
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 commandAuthorization(param) {
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
- commandAuthorization,
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
- // Per-source erased dispatch — pre-compile decoders once at module init.
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 dispatches: array<
176
- dispatch,
177
- > = Automation.mappings->Array.map((module(M: Automation.Mapping)) => {
178
- let decoder = Reventless.DcbDecode.makeDecoder(M.sourceEventSchema)
179
- let handle = (json: JSON.t, ~sourceId: string, ctx: Reventless.AutomationSlice.context) => {
180
- let (eventType, dataDict) = json->Message.splitMessage
181
- switch decoder.decode(~eventType, ~data=dataDict) {
182
- | Some(event) =>
183
- // Collect — append new items keyed by ID; first writer wins (idempotent).
184
- M.collect(event, ~sourceId, ctx)->Array.forEach(((id, item)) => {
185
- switch todoItems->Dict.get(id) {
186
- | Some(_) => () // Already exists — skip
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
- | None => ()
184
+ todoItems->Dict.set(id, row)
209
185
  }
210
- }
211
- {sourceName: M.sourceName, handle}
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
- dispatches->Array.forEach(d => {
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 dispatches = Automation.mappings.map(M => {
59
- let decoder = DcbDecode$Reventless.makeDecoder(M.sourceEventSchema);
60
- let handle = (json, sourceId, ctx) => {
61
- let match = Message$ReventlessCore.splitMessage(json);
62
- let event = decoder.decode(match[0], match[1]);
63
- if (event === undefined) {
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 event$1 = Primitive_option.valFromOption(event);
67
- M.collect(event$1, sourceId, ctx).forEach(param => {
68
- let id = param[0];
69
- let match = todoItems[id];
70
- if (match !== undefined) {
71
- return;
72
- }
73
- let row_item = Util_Sury$Reventless.toJson(param[1], Spec.todoItemSchema);
74
- let row_createdAt = new Date().toISOString();
75
- let row_maxRetries = Spec.maxRetries;
76
- let row = {
77
- item: row_item,
78
- status: "Pending",
79
- createdAt: row_createdAt,
80
- retryCount: 0,
81
- maxRetries: row_maxRetries
82
- };
83
- todoItems[id] = row;
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
- dispatches.forEach(d => {
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
- ~commandAuthorization=command =>
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
- ~commandAuthorization=command =>
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 fieldName =
869
- raw
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(args)
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
- // Stage E2: a DCB StateChangeSlice has a single GraphQL field but its
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
- let hasPayload = Reventless.DcbTag.isVariantPayloadBearing(
1086
- commandSchema->Obj.magic,
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
- let syntheticCmd: unknown = hasPayload ? {"TAG": ctor}->Obj.magic : ctor->Obj.magic
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
- switch permissionForFirstConstructor(
1112
- ~commandSchema=ITS.Spec.commandSchema->Reventless.DcbTag.toUnknownSchema,
1113
- ~commandAuthorization=command =>
1114
- ITS.Spec.commandAuthorization(command->Obj.magic)->Reventless.Authorization.named,
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, command => Authorization$Reventless.named(S.Spec.commandAuthorization(command)));
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, command => Authorization$Reventless.named(S.Spec.commandAuthorization(command)));
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 fieldName = Stdlib_Option.getOr(Stdlib_Option.flatMap(event["fieldName"], Stdlib_JSON.Decode.string), "");
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(args);
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
- let ctor = param[1];
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
- let rule = permissionForFirstConstructor(ITS.Spec.commandSchema, command => Authorization$Reventless.named(ITS.Spec.commandAuthorization(command)));
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
  },