@reventlessdev/reventless-core 3.0.0-alpha.224 → 3.0.0-alpha.225

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 (38) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/package.json +6 -6
  3. package/src/admin/Platform_Admin_Structure.res +6 -0
  4. package/src/admin/Platform_Admin_Structure.res.mjs +9 -3
  5. package/src/admin/Platform_BakedManifest.res +154 -0
  6. package/src/admin/Platform_BakedManifest.res.mjs +156 -0
  7. package/src/admin/Platform_ComponentDefinitionsApi.res +9 -2
  8. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +18 -2
  9. package/src/components/Api/OwnerScopeDiagnostics.res +54 -0
  10. package/src/components/Api/OwnerScopeDiagnostics.res.mjs +27 -0
  11. package/src/components/Api/QueryDbListQuery.res +25 -1
  12. package/src/components/Api/QueryDbListQuery.res.mjs +10 -3
  13. package/src/components/Api/SuryToJsonSchema.res +19 -0
  14. package/src/components/Api/SuryToJsonSchema.res.mjs +18 -4
  15. package/src/components/CommandGenerator/CommandGenerator_Callback.res +48 -0
  16. package/src/components/CommandGenerator/CommandGenerator_Callback.res.mjs +27 -1
  17. package/src/plugin/component/Plugin_Structure.res +59 -3
  18. package/src/plugin/component/Plugin_Structure.res.mjs +29 -9
  19. package/tests/admin/Platform_BakedManifestTest.res +244 -0
  20. package/tests/admin/Platform_BakedManifestTest.res.mjs +294 -0
  21. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +10 -0
  22. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +24 -8
  23. package/tests/admin/Platform_PluginStructuresApiTest.res +2 -0
  24. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +6 -2
  25. package/tests/api/Api_IdsTest.res.mjs +1 -1
  26. package/tests/api/OwnerScopeDiagnosticsTest.res +109 -0
  27. package/tests/api/OwnerScopeDiagnosticsTest.res.mjs +91 -0
  28. package/tests/api/SuryToJsonSchemaTest.res +45 -0
  29. package/tests/api/SuryToJsonSchemaTest.res.mjs +23 -0
  30. package/tests/commandgenerator/OwnerStampingTest.res +174 -0
  31. package/tests/commandgenerator/OwnerStampingTest.res.mjs +195 -0
  32. package/tests/plugin/PluginStructureAccessTest.res +110 -0
  33. package/tests/plugin/PluginStructureAccessTest.res.mjs +144 -0
  34. package/tests/plugin/StateChangeSlice/PsGatedCommands.res +28 -0
  35. package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +84 -0
  36. package/tests/plugin/StateViewSlice/PsGatedView.res +15 -0
  37. package/tests/plugin/StateViewSlice/PsGatedView.res.mjs +60 -0
  38. package/tests/plugin/pluginDefinitionRequiredScalars.txt +4 -0
@@ -0,0 +1,195 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Effect from "effect/Effect";
7
+ import * as Owner$Reventless from "@reventlessdev/reventless-spec/src/components/Owner.res.mjs";
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";
10
+ import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
11
+ import * as CommandGenerator_Callback$ReventlessCore from "../../src/components/CommandGenerator/CommandGenerator_Callback.res.mjs";
12
+
13
+ S.enableJson();
14
+
15
+ let commandSchema = S.union([
16
+ S.schema(s => ({
17
+ TAG: "PlaceOrder",
18
+ orderId: s.m(DcbTag$Reventless.string),
19
+ customerId: s.m(Owner$Reventless.string),
20
+ note: s.m(S.string)
21
+ })),
22
+ S.schema(s => ({
23
+ TAG: "ImportProducts",
24
+ batchId: s.m(DcbTag$Reventless.string),
25
+ source: s.m(S.string)
26
+ }))
27
+ ]);
28
+
29
+ let published = {
30
+ contents: []
31
+ };
32
+
33
+ let generate = CommandGenerator_Callback$ReventlessCore.makeGenerateCommand(async cmds => {
34
+ published.contents = published.contents.concat(cmds);
35
+ }, undefined, "Ordering", commandSchema, "StateChangeSlice", false);
36
+
37
+ function cognito(userId, groups) {
38
+ return {
39
+ userId: userId,
40
+ username: userId,
41
+ groups: groups,
42
+ provider: "Cognito"
43
+ };
44
+ }
45
+
46
+ let iam = {
47
+ userArn: "arn:aws:sts::1:assumed-role/Ingester",
48
+ accountId: "1",
49
+ username: "Ingester",
50
+ provider: "IAM"
51
+ };
52
+
53
+ function payload(command, args, identity) {
54
+ return {
55
+ command: command,
56
+ arguments: args,
57
+ meta: {
58
+ ip: ["127.0.0.1"],
59
+ user: "test",
60
+ info: ""
61
+ },
62
+ identity: identity
63
+ };
64
+ }
65
+
66
+ function str(prim) {
67
+ return prim;
68
+ }
69
+
70
+ function placeOrder(customerId, identity) {
71
+ return payload("PlaceOrder", Object.fromEntries([
72
+ [
73
+ "orderId",
74
+ "o-1"
75
+ ],
76
+ [
77
+ "customerId",
78
+ customerId
79
+ ],
80
+ [
81
+ "note",
82
+ "gift wrap"
83
+ ]
84
+ ]), identity);
85
+ }
86
+
87
+ function fieldOf(cmd, name) {
88
+ return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(cmd.commandJson), o => o[name]);
89
+ }
90
+
91
+ function lastPublished() {
92
+ return published.contents[published.contents.length - 1 | 0];
93
+ }
94
+
95
+ globalThis.beforeEach(() => {
96
+ published.contents = [];
97
+ OwnerScope$Reventless.setElevatedGroups(["Admin"]);
98
+ });
99
+
100
+ globalThis.describe("@owner stamping on the command path:", () => {
101
+ globalThis.test("an omitted owner field is filled from the caller", async () => {
102
+ await Effect.runPromise(generate(payload("PlaceOrder", Object.fromEntries([
103
+ [
104
+ "orderId",
105
+ "o-1"
106
+ ],
107
+ [
108
+ "note",
109
+ ""
110
+ ]
111
+ ]), cognito("cust-A", ["User"]))));
112
+ globalThis.expect(fieldOf(published.contents[published.contents.length - 1 | 0], "customerId")).toEqual("cust-A");
113
+ });
114
+ globalThis.test("a FORGED owner field is overwritten, not respected", async () => {
115
+ await Effect.runPromise(generate(placeOrder("cust-B", cognito("cust-A", ["User"]))));
116
+ globalThis.expect(fieldOf(published.contents[published.contents.length - 1 | 0], "customerId")).toEqual("cust-A");
117
+ });
118
+ globalThis.test("non-owner fields of the same command are untouched", async () => {
119
+ await Effect.runPromise(generate(placeOrder("cust-B", cognito("cust-A", ["User"]))));
120
+ globalThis.expect(fieldOf(published.contents[published.contents.length - 1 | 0], "note")).toEqual("gift wrap");
121
+ });
122
+ globalThis.test("a command in the same union with no owner field is left alone", async () => {
123
+ await Effect.runPromise(generate(payload("ImportProducts", Object.fromEntries([
124
+ [
125
+ "batchId",
126
+ "b-1"
127
+ ],
128
+ [
129
+ "source",
130
+ "acme"
131
+ ]
132
+ ]), cognito("cust-A", ["User"]))));
133
+ globalThis.expect([
134
+ fieldOf(published.contents[published.contents.length - 1 | 0], "source"),
135
+ fieldOf(published.contents[published.contents.length - 1 | 0], "customerId")
136
+ ]).toEqual([
137
+ "acme",
138
+ undefined
139
+ ]);
140
+ });
141
+ globalThis.describe("exempt callers keep the value they sent:", () => {
142
+ globalThis.test("an elevated caller's chosen customerId survives", async () => {
143
+ await Effect.runPromise(generate(placeOrder("cust-B", cognito("ops-1", ["Admin"]))));
144
+ globalThis.expect(fieldOf(published.contents[published.contents.length - 1 | 0], "customerId")).toEqual("cust-B");
145
+ });
146
+ globalThis.test("an IAM system caller's chosen customerId survives", async () => {
147
+ await Effect.runPromise(generate(placeOrder("cust-B", iam)));
148
+ globalThis.expect(fieldOf(published.contents[published.contents.length - 1 | 0], "customerId")).toEqual("cust-B");
149
+ });
150
+ });
151
+ globalThis.describe("a caller that cannot be identified is refused:", () => {
152
+ let refused = async p => {
153
+ try {
154
+ await Effect.runPromise(generate(p));
155
+ return false;
156
+ } catch (exn) {
157
+ return true;
158
+ }
159
+ };
160
+ globalThis.test("an anonymous caller cannot place an owned command", async () => {
161
+ globalThis.expect(await refused(placeOrder("cust-B", Identity$Reventless.anonymous))).toBe(true);
162
+ });
163
+ globalThis.test("nothing is published when the caller is refused", async () => {
164
+ await refused(placeOrder("cust-B", Identity$Reventless.anonymous));
165
+ globalThis.expect(published.contents.length).toBe(0);
166
+ });
167
+ globalThis.test("an anonymous caller may still issue an unowned command", async () => {
168
+ await Effect.runPromise(generate(payload("ImportProducts", Object.fromEntries([
169
+ [
170
+ "batchId",
171
+ "b-1"
172
+ ],
173
+ [
174
+ "source",
175
+ "acme"
176
+ ]
177
+ ]), Identity$Reventless.anonymous)));
178
+ globalThis.expect(fieldOf(published.contents[published.contents.length - 1 | 0], "source")).toEqual("acme");
179
+ });
180
+ });
181
+ });
182
+
183
+ export {
184
+ commandSchema,
185
+ published,
186
+ generate,
187
+ cognito,
188
+ iam,
189
+ payload,
190
+ str,
191
+ placeOrder,
192
+ fieldOf,
193
+ lastPublished,
194
+ }
195
+ /* Not a pure module */
@@ -0,0 +1,110 @@
1
+ // `requiredAccess` is derived from the authorization rule the server enforces, so
2
+ // what these assert is that the manifest cannot describe a different policy from
3
+ // the one the resolver applies. A client uses the keys to stop offering what it
4
+ // would be refused; it never decides anything with them.
5
+
6
+ open JestGlobals
7
+
8
+ type scsComponent = Component.t<
9
+ ReventlessInfra.StateChangeSlice.t,
10
+ ReventlessInfra.StateChangeSlice.outputs,
11
+ ReventlessInfra.StateChangeSlice.operations,
12
+ >
13
+ type svsComponent = Component.t<
14
+ ReventlessInfra.StateViewSlice.t,
15
+ ReventlessInfra.StateViewSlice.outputs,
16
+ ReventlessInfra.StateViewSlice.operations,
17
+ >
18
+
19
+ module PsGatedCommandsSlice: ReventlessInfra.StateChangeSlice.T = {
20
+ module Spec = PsGatedCommands
21
+ module Behavior = {
22
+ type state = PsGatedCommands.state
23
+ let initialState = PsGatedCommands.initialState
24
+ let evolve = PsGatedCommands.evolve
25
+ let decide = PsGatedCommands.decide
26
+ let moduleUrl = PsGatedCommands.moduleUrl
27
+ }
28
+ let isAsync = false
29
+ type component = scsComponent
30
+ let make = (
31
+ ~dcbEventLog as _,
32
+ ~publishJsons as _,
33
+ ~tagKeysByEventType as _=?,
34
+ ~crossPartitionTagKeys as _=?,
35
+ ~runtime as _=?,
36
+ ~opts as _=?,
37
+ ): component => Obj.magic(0)
38
+ }
39
+
40
+ module PsGatedViewSlice: ReventlessInfra.StateViewSlice.T = {
41
+ module Spec = PsGatedView
42
+ module Projection = {
43
+ let project = PsGatedView.project
44
+ let moduleUrl = PsGatedView.moduleUrl
45
+ }
46
+ type component = svsComponent
47
+ let make = (~dcbEventLog as _, ~runtime as _=?, ~opts as _=?): component => Obj.magic(0)
48
+ }
49
+
50
+ module PsOrdersViewSlice: ReventlessInfra.StateViewSlice.T = {
51
+ module Spec = PsOrdersView
52
+ module Projection = {
53
+ let project = PsOrdersView.project
54
+ let moduleUrl = PsOrdersView.moduleUrl
55
+ }
56
+ type component = svsComponent
57
+ let make = (~dcbEventLog as _, ~runtime as _=?, ~opts as _=?): component => Obj.magic(0)
58
+ }
59
+
60
+ let structure = Plugin_Structure.make(
61
+ ~name="AccessPlugin",
62
+ ~stateChangeSlices=[module(PsGatedCommandsSlice)],
63
+ ~stateViewSlices=[module(PsGatedViewSlice), module(PsOrdersViewSlice)],
64
+ )
65
+
66
+ let commandNamed = name =>
67
+ structure.stateChangeSlices
68
+ ->Array.flatMap(w => w.commands)
69
+ ->Array.find(c => c.name === name)
70
+ ->Option.flatMap(c => c.requiredAccess)
71
+
72
+ let viewNamed = name =>
73
+ structure.stateViewSlices
74
+ ->Array.find(q => q.name === name)
75
+ ->Option.flatMap(q => q.requiredAccess)
76
+
77
+ describe("requiredAccess derived from the authorization rule", () => {
78
+ testSync("an AllowGroups command publishes its groups", () =>
79
+ expect(commandNamed("Restock"))->toEqual(Some(["Admin", "Ops"]))
80
+ )
81
+
82
+ // The reason the rule is evaluated per constructor rather than per component:
83
+ // one slice, two audiences, and a component-level shortcut would gate both.
84
+ testSync("a sibling command in the same slice publishes nothing", () =>
85
+ expect(commandNamed("RequestRestock"))->toEqual(None)
86
+ )
87
+
88
+ testSync("a module-level rule reaches the view it guards", () =>
89
+ expect(viewNamed("GatedView"))->toEqual(Some(["Admin"]))
90
+ )
91
+
92
+ testSync("an unannotated view publishes nothing", () =>
93
+ expect(viewNamed("Orders"))->toEqual(None)
94
+ )
95
+
96
+ // Null rather than `[]` on the wire: "no key gates this" is one answer, and a
97
+ // def written before the field existed has to read as the same thing.
98
+ testSync("nothing to require encodes as null, not an empty list", () => {
99
+ let json =
100
+ Platform_ComponentDefinitionsApi.encodePluginStructureEntry(
101
+ ~pluginId="AccessPlugin",
102
+ structure,
103
+ )->JSON.stringify
104
+ expect((
105
+ json->String.includes(`"requiredAccess":null`),
106
+ json->String.includes(`"requiredAccess":["Admin","Ops"]`),
107
+ json->String.includes(`"requiredAccess":[]`),
108
+ ))->toEqual((true, true, false))
109
+ })
110
+ })
@@ -0,0 +1,144 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as PsGatedView$ReventlessCore from "./StateViewSlice/PsGatedView.res.mjs";
6
+ import * as PsOrdersView$ReventlessCore from "./StateViewSlice/PsOrdersView.res.mjs";
7
+ import * as PsGatedCommands$ReventlessCore from "./StateChangeSlice/PsGatedCommands.res.mjs";
8
+ import * as Plugin_Structure$ReventlessCore from "../../src/plugin/component/Plugin_Structure.res.mjs";
9
+ import * as Platform_ComponentDefinitionsApi$ReventlessCore from "../../src/admin/Platform_ComponentDefinitionsApi.res.mjs";
10
+
11
+ let Behavior = {
12
+ initialState: false,
13
+ evolve: PsGatedCommands$ReventlessCore.evolve,
14
+ decide: PsGatedCommands$ReventlessCore.decide,
15
+ moduleUrl: PsGatedCommands$ReventlessCore.moduleUrl
16
+ };
17
+
18
+ function make(param, param$1, param$2, param$3, param$4, param$5) {
19
+ return 0;
20
+ }
21
+
22
+ let PsGatedCommandsSlice_Spec = {
23
+ name: PsGatedCommands$ReventlessCore.name,
24
+ moduleUrl: PsGatedCommands$ReventlessCore.moduleUrl,
25
+ Id: Id$Reventless.$$String,
26
+ consumedEventSchema: PsGatedCommands$ReventlessCore.consumedEventSchema,
27
+ errorSchema: PsGatedCommands$ReventlessCore.errorSchema,
28
+ eventSchema: PsGatedCommands$ReventlessCore.eventSchema,
29
+ commandSchema: PsGatedCommands$ReventlessCore.commandSchema,
30
+ commandAuthorization: PsGatedCommands$ReventlessCore.commandAuthorization,
31
+ readConsistency: PsGatedCommands$ReventlessCore.readConsistency
32
+ };
33
+
34
+ let PsGatedCommandsSlice = {
35
+ Spec: PsGatedCommandsSlice_Spec,
36
+ Behavior: Behavior,
37
+ isAsync: false,
38
+ make: make
39
+ };
40
+
41
+ let Projection = {
42
+ project: PsGatedView$ReventlessCore.project,
43
+ moduleUrl: PsGatedView$ReventlessCore.moduleUrl
44
+ };
45
+
46
+ function make$1(param, param$1, param$2) {
47
+ return 0;
48
+ }
49
+
50
+ let PsGatedViewSlice_Spec = {
51
+ name: PsGatedView$ReventlessCore.name,
52
+ moduleUrl: PsGatedView$ReventlessCore.moduleUrl,
53
+ stateSchema: PsGatedView$ReventlessCore.stateSchema,
54
+ consumedEventSchema: PsGatedView$ReventlessCore.consumedEventSchema,
55
+ config: PsGatedView$ReventlessCore.config,
56
+ subIdConfig: undefined,
57
+ authorization: PsGatedView$ReventlessCore.authorization,
58
+ visibility: PsGatedView$ReventlessCore.visibility
59
+ };
60
+
61
+ let PsGatedViewSlice = {
62
+ Spec: PsGatedViewSlice_Spec,
63
+ Projection: Projection,
64
+ make: make$1
65
+ };
66
+
67
+ let Projection$1 = {
68
+ project: PsOrdersView$ReventlessCore.project,
69
+ moduleUrl: PsOrdersView$ReventlessCore.moduleUrl
70
+ };
71
+
72
+ function make$2(param, param$1, param$2) {
73
+ return 0;
74
+ }
75
+
76
+ let PsOrdersViewSlice_Spec = {
77
+ name: PsOrdersView$ReventlessCore.name,
78
+ moduleUrl: PsOrdersView$ReventlessCore.moduleUrl,
79
+ stateSchema: PsOrdersView$ReventlessCore.stateSchema,
80
+ consumedEventSchema: PsOrdersView$ReventlessCore.consumedEventSchema,
81
+ config: PsOrdersView$ReventlessCore.config,
82
+ subIdConfig: undefined,
83
+ authorization: PsOrdersView$ReventlessCore.authorization,
84
+ visibility: PsOrdersView$ReventlessCore.visibility
85
+ };
86
+
87
+ let PsOrdersViewSlice = {
88
+ Spec: PsOrdersViewSlice_Spec,
89
+ Projection: Projection$1,
90
+ make: make$2
91
+ };
92
+
93
+ let structure = Plugin_Structure$ReventlessCore.make("AccessPlugin", undefined, undefined, [
94
+ PsGatedViewSlice,
95
+ PsOrdersViewSlice
96
+ ], [PsGatedCommandsSlice], undefined, undefined, undefined, undefined, undefined, undefined);
97
+
98
+ function commandNamed(name) {
99
+ return Stdlib_Option.flatMap(structure.stateChangeSlices.flatMap(w => w.commands).find(c => c.name === name), c => c.requiredAccess);
100
+ }
101
+
102
+ function viewNamed(name) {
103
+ return Stdlib_Option.flatMap(structure.stateViewSlices.find(q => q.name === name), q => q.requiredAccess);
104
+ }
105
+
106
+ globalThis.describe("requiredAccess derived from the authorization rule", () => {
107
+ globalThis.test("an AllowGroups command publishes its groups", () => {
108
+ globalThis.expect(commandNamed("Restock")).toEqual([
109
+ "Admin",
110
+ "Ops"
111
+ ]);
112
+ });
113
+ globalThis.test("a sibling command in the same slice publishes nothing", () => {
114
+ globalThis.expect(commandNamed("RequestRestock")).toEqual(undefined);
115
+ });
116
+ globalThis.test("a module-level rule reaches the view it guards", () => {
117
+ globalThis.expect(viewNamed("GatedView")).toEqual(["Admin"]);
118
+ });
119
+ globalThis.test("an unannotated view publishes nothing", () => {
120
+ globalThis.expect(viewNamed("Orders")).toEqual(undefined);
121
+ });
122
+ globalThis.test("nothing to require encodes as null, not an empty list", () => {
123
+ let json = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("AccessPlugin", structure));
124
+ globalThis.expect([
125
+ json.includes(`"requiredAccess":null`),
126
+ json.includes(`"requiredAccess":["Admin","Ops"]`),
127
+ json.includes(`"requiredAccess":[]`)
128
+ ]).toEqual([
129
+ true,
130
+ true,
131
+ false
132
+ ]);
133
+ });
134
+ });
135
+
136
+ export {
137
+ PsGatedCommandsSlice,
138
+ PsGatedViewSlice,
139
+ PsOrdersViewSlice,
140
+ structure,
141
+ commandNamed,
142
+ viewNamed,
143
+ }
144
+ /* structure Not a pure module */
@@ -0,0 +1,28 @@
1
+ // Test fixture spec: one slice carrying commands with different audiences.
2
+ // `Restock` is operator-only, `RequestRestock` is open to anyone signed in —
3
+ // which is the case a component-level access rule would get wrong.
4
+
5
+ @@reventless.spec("GatedCommands")
6
+
7
+ type state = bool
8
+ let initialState = false
9
+
10
+ @schema
11
+ type consumedEvent = StockLow({productId: string})
12
+
13
+ let evolve = (_state, _event) => true
14
+
15
+ @schema
16
+ type command =
17
+ | @authorize(AllowGroups(["Admin", "Ops"])) Restock({productId: string})
18
+ | RequestRestock({productId: string})
19
+
20
+ @schema
21
+ type error = UnknownProduct
22
+
23
+ @schema
24
+ type event =
25
+ | Restocked({productId: string})
26
+ | RestockRequested({productId: string})
27
+
28
+ let decide = (_state, _command): result<array<event>, error> => Ok([])
@@ -0,0 +1,84 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
5
+
6
+ let consumedEventSchema = S.schema(s => ({
7
+ TAG: "StockLow",
8
+ productId: s.m(DcbTag$Reventless.string)
9
+ }));
10
+
11
+ function evolve(_state, _event) {
12
+ return true;
13
+ }
14
+
15
+ let commandSchema = S.union([
16
+ S.schema(s => ({
17
+ TAG: "Restock",
18
+ productId: s.m(DcbTag$Reventless.string)
19
+ })),
20
+ S.schema(s => ({
21
+ TAG: "RequestRestock",
22
+ productId: s.m(DcbTag$Reventless.string)
23
+ }))
24
+ ]);
25
+
26
+ let errorSchema = S.literal("UnknownProduct");
27
+
28
+ let eventSchema = S.union([
29
+ S.schema(s => ({
30
+ TAG: "Restocked",
31
+ productId: s.m(DcbTag$Reventless.string)
32
+ })),
33
+ S.schema(s => ({
34
+ TAG: "RestockRequested",
35
+ productId: s.m(DcbTag$Reventless.string)
36
+ }))
37
+ ]);
38
+
39
+ function decide(_state, _command) {
40
+ return {
41
+ TAG: "Ok",
42
+ _0: []
43
+ };
44
+ }
45
+
46
+ function commandAuthorization(command) {
47
+ if (command.TAG === "Restock") {
48
+ return {
49
+ TAG: "AllowGroups",
50
+ _0: [
51
+ "Admin",
52
+ "Ops"
53
+ ]
54
+ };
55
+ } else {
56
+ return "AllowAuthenticated";
57
+ }
58
+ }
59
+
60
+ let name = "GatedCommands";
61
+
62
+ let Id;
63
+
64
+ let initialState = false;
65
+
66
+ let moduleUrl = "@reventlessdev/reventless-core/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs";
67
+
68
+ let readConsistency = "EscalateOnRetry";
69
+
70
+ export {
71
+ name,
72
+ Id,
73
+ initialState,
74
+ consumedEventSchema,
75
+ evolve,
76
+ commandSchema,
77
+ errorSchema,
78
+ eventSchema,
79
+ decide,
80
+ moduleUrl,
81
+ commandAuthorization,
82
+ readConsistency,
83
+ }
84
+ /* consumedEventSchema Not a pure module */
@@ -0,0 +1,15 @@
1
+ // Test fixture spec: a view whose module-level rule restricts who may read it.
2
+
3
+ @@reventless.spec("GatedView")
4
+ @@reventless.authorize(AllowGroups(["Admin"]))
5
+
6
+ @schema
7
+ type consumedEvent = OrderPlaced({orderId: string, customerName: string})
8
+
9
+ @schema
10
+ type state = {orderId: string, customerName: string}
11
+
12
+ let project = ({event}: Reventless.StateViewSlice.consumed<consumedEvent>) =>
13
+ switch event {
14
+ | OrderPlaced({orderId, customerName}) => [Set(orderId, {orderId, customerName})]
15
+ }
@@ -0,0 +1,60 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
5
+ import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
6
+
7
+ let consumedEventSchema = S.schema(s => ({
8
+ TAG: "OrderPlaced",
9
+ orderId: s.m(DcbTag$Reventless.string),
10
+ customerName: s.m(S.string)
11
+ }));
12
+
13
+ let stateSchema = S.schema(s => ({
14
+ orderId: s.m(S.string),
15
+ customerName: s.m(S.string)
16
+ }));
17
+
18
+ function project(param) {
19
+ let event = param.event;
20
+ let orderId = event.orderId;
21
+ return [{
22
+ TAG: "Set",
23
+ _0: orderId,
24
+ _1: {
25
+ orderId: orderId,
26
+ customerName: event.customerName
27
+ }
28
+ }];
29
+ }
30
+
31
+ let config = ReadModel$Reventless.config(undefined, undefined, undefined);
32
+
33
+ let authorization = {
34
+ TAG: "AllowGroups",
35
+ _0: ["Admin"]
36
+ };
37
+
38
+ let name = "GatedView";
39
+
40
+ let Id;
41
+
42
+ let subIdConfig;
43
+
44
+ let moduleUrl = "@reventlessdev/reventless-core/tests/plugin/StateViewSlice/PsGatedView.res.mjs";
45
+
46
+ let visibility = "Public";
47
+
48
+ export {
49
+ name,
50
+ Id,
51
+ consumedEventSchema,
52
+ stateSchema,
53
+ project,
54
+ config,
55
+ subIdConfig,
56
+ moduleUrl,
57
+ authorization,
58
+ visibility,
59
+ }
60
+ /* consumedEventSchema Not a pure module */
@@ -41,6 +41,7 @@
41
41
  .structure.aggregates[].commands[].name: string
42
42
  .structure.aggregates[].commands[].references[].entity: string
43
43
  .structure.aggregates[].commands[].references[].fieldName: string
44
+ .structure.aggregates[].commands[].requiredAccess[]: string
44
45
  .structure.aggregates[].commands[].schema: string
45
46
  .structure.aggregates[].consumedEventTypes[]: string
46
47
  .structure.aggregates[].errors[].name: string
@@ -77,6 +78,7 @@
77
78
  .structure.readModels[].linkedWriteSide[]: string
78
79
  .structure.readModels[].name: string
79
80
  .structure.readModels[].queryField: string
81
+ .structure.readModels[].requiredAccess[]: string
80
82
  .structure.readModels[].schema: string
81
83
  .structure.readModels[].searchableFields[]: string
82
84
  .structure.requiredStoreDeclarations[].component: string
@@ -88,6 +90,7 @@
88
90
  .structure.stateChangeSlices[].commands[].name: string
89
91
  .structure.stateChangeSlices[].commands[].references[].entity: string
90
92
  .structure.stateChangeSlices[].commands[].references[].fieldName: string
93
+ .structure.stateChangeSlices[].commands[].requiredAccess[]: string
91
94
  .structure.stateChangeSlices[].commands[].schema: string
92
95
  .structure.stateChangeSlices[].consumedEventTypes[]: string
93
96
  .structure.stateChangeSlices[].errors[].name: string
@@ -106,6 +109,7 @@
106
109
  .structure.stateViewSlices[].linkedWriteSide[]: string
107
110
  .structure.stateViewSlices[].name: string
108
111
  .structure.stateViewSlices[].queryField: string
112
+ .structure.stateViewSlices[].requiredAccess[]: string
109
113
  .structure.stateViewSlices[].schema: string
110
114
  .structure.stateViewSlices[].searchableFields[]: string
111
115
  .version: string