@reventlessdev/reventless-spec 3.0.0-alpha.61

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 (86) hide show
  1. package/CHANGELOG.md +625 -0
  2. package/LICENSE +202 -0
  3. package/README.md +114 -0
  4. package/package.json +34 -0
  5. package/rescript.json +25 -0
  6. package/run-generator.mjs +2 -0
  7. package/src/AnsiStyle.res +40 -0
  8. package/src/AnsiStyle.res.mjs +54 -0
  9. package/src/LogPrefix.res +168 -0
  10. package/src/LogPrefix.res.mjs +148 -0
  11. package/src/PackageVersion.res +73 -0
  12. package/src/PackageVersion.res.mjs +81 -0
  13. package/src/components/Aggregate.res +64 -0
  14. package/src/components/Aggregate.res.mjs +2 -0
  15. package/src/components/AutomationSlice.res +279 -0
  16. package/src/components/AutomationSlice.res.mjs +30 -0
  17. package/src/components/Counter.res +24 -0
  18. package/src/components/Counter.res.mjs +2 -0
  19. package/src/components/DcbDecode.res +128 -0
  20. package/src/components/DcbDecode.res.mjs +117 -0
  21. package/src/components/DcbTag.res +1256 -0
  22. package/src/components/DcbTag.res.mjs +869 -0
  23. package/src/components/DcbValidation.res +362 -0
  24. package/src/components/DcbValidation.res.mjs +360 -0
  25. package/src/components/DisplayName.res +40 -0
  26. package/src/components/DisplayName.res.mjs +26 -0
  27. package/src/components/ExtensionPoint.res +27 -0
  28. package/src/components/ExtensionPoint.res.mjs +2 -0
  29. package/src/components/InboundTranslationSlice.res +79 -0
  30. package/src/components/InboundTranslationSlice.res.mjs +2 -0
  31. package/src/components/OutboundTranslationSlice.res +108 -0
  32. package/src/components/OutboundTranslationSlice.res.mjs +2 -0
  33. package/src/components/Plugin.res +373 -0
  34. package/src/components/Plugin.res.mjs +249 -0
  35. package/src/components/ReadModel.res +199 -0
  36. package/src/components/ReadModel.res.mjs +18 -0
  37. package/src/components/Reference.res +51 -0
  38. package/src/components/Reference.res.mjs +37 -0
  39. package/src/components/StateAnnotations.res +65 -0
  40. package/src/components/StateAnnotations.res.mjs +15 -0
  41. package/src/components/StateChangeSlice.res +131 -0
  42. package/src/components/StateChangeSlice.res.mjs +2 -0
  43. package/src/components/StateViewSlice.res +99 -0
  44. package/src/components/StateViewSlice.res.mjs +2 -0
  45. package/src/components/Task.res +62 -0
  46. package/src/components/Task.res.mjs +2 -0
  47. package/src/generator/Codegen.res +703 -0
  48. package/src/generator/Codegen.res.mjs +485 -0
  49. package/src/generator/Config.res +85 -0
  50. package/src/generator/Config.res.mjs +78 -0
  51. package/src/generator/Discovery.res +204 -0
  52. package/src/generator/Discovery.res.mjs +213 -0
  53. package/src/generator/Generator_Node.res +49 -0
  54. package/src/generator/Generator_Node.res.mjs +18 -0
  55. package/src/generator/Pairing.res +386 -0
  56. package/src/generator/Pairing.res.mjs +353 -0
  57. package/src/generator/PluginGenerator.res +53 -0
  58. package/src/generator/PluginGenerator.res.mjs +70 -0
  59. package/src/types/Authorization.res +23 -0
  60. package/src/types/Authorization.res.mjs +33 -0
  61. package/src/types/Behavior.res +78 -0
  62. package/src/types/Behavior.res.mjs +2 -0
  63. package/src/types/EventMapping.res +100 -0
  64. package/src/types/EventMapping.res.mjs +15 -0
  65. package/src/types/Handler.res +30 -0
  66. package/src/types/Handler.res.mjs +2 -0
  67. package/src/types/Id.res +75 -0
  68. package/src/types/Id.res.mjs +37 -0
  69. package/src/types/Identity.res +46 -0
  70. package/src/types/Identity.res.mjs +51 -0
  71. package/src/types/Message.res +197 -0
  72. package/src/types/Message.res.mjs +108 -0
  73. package/src/types/Projection.res +220 -0
  74. package/src/types/Projection.res.mjs +44 -0
  75. package/src/types/QueryEngine.res +123 -0
  76. package/src/types/QueryEngine.res.mjs +12 -0
  77. package/src/types/ReadConsistency.res +38 -0
  78. package/src/types/ReadConsistency.res.mjs +29 -0
  79. package/src/types/Schedule.res +65 -0
  80. package/src/types/Schedule.res.mjs +68 -0
  81. package/src/types/SideEffect.res +45 -0
  82. package/src/types/SideEffect.res.mjs +2 -0
  83. package/src/types/StoredEvent.res +46 -0
  84. package/src/types/StoredEvent.res.mjs +32 -0
  85. package/src/types/Visibility.res +24 -0
  86. package/src/types/Visibility.res.mjs +25 -0
@@ -0,0 +1,353 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Nodefs from "node:fs";
4
+ import * as Nodepath from "node:path";
5
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
+ import * as Generator_Node$Reventless from "./Generator_Node.res.mjs";
8
+
9
+ let implSuffixForStateChange = "_Behavior";
10
+
11
+ let implSuffixForStateView = "_Projection";
12
+
13
+ let implSuffixForAutomation = "_Automation";
14
+
15
+ let implSuffixForTranslation = "_Translation";
16
+
17
+ let mappingsSuffixForAutomation = "_Mappings";
18
+
19
+ function isImplStem(stem) {
20
+ if (stem.endsWith(implSuffixForStateChange) || stem.endsWith(implSuffixForStateView) || stem.endsWith(implSuffixForAutomation) || stem.endsWith(implSuffixForTranslation)) {
21
+ return true;
22
+ } else {
23
+ return stem.endsWith(mappingsSuffixForAutomation);
24
+ }
25
+ }
26
+
27
+ function findEventMappings(srcDir) {
28
+ let dict = {};
29
+ let parentDirIsAggregate = relSegments => {
30
+ let n = relSegments.length;
31
+ if (n === 0) {
32
+ return false;
33
+ }
34
+ let parent = relSegments[n - 1 | 0];
35
+ if (parent === "Aggregate") {
36
+ return true;
37
+ } else {
38
+ return parent === "EventMappings";
39
+ }
40
+ };
41
+ let walk = (dir, segments) => {
42
+ Generator_Node$Reventless.readDir(dir).forEach(entry => {
43
+ let name = entry.name;
44
+ if (entry.isDirectory()) {
45
+ if (name !== "Plugin" && name !== "tests" && name !== "lib") {
46
+ return walk(Nodepath.join(dir, name), segments.concat([name]));
47
+ } else {
48
+ return;
49
+ }
50
+ }
51
+ if (!entry.isFile()) {
52
+ return;
53
+ }
54
+ if (name.endsWith("_Mappings.res") && parentDirIsAggregate(segments)) {
55
+ let stem = name.slice(0, name.length - 4 | 0);
56
+ let aggName = stem.slice(0, stem.length - 9 | 0);
57
+ dict[aggName] = stem;
58
+ return;
59
+ }
60
+ if (!name.endsWith("_EventMappings.res")) {
61
+ return;
62
+ }
63
+ let stem$1 = name.slice(0, name.length - 4 | 0);
64
+ let aggName$1 = stem$1.slice(0, stem$1.length - 14 | 0);
65
+ let match = dict[aggName$1];
66
+ if (match !== undefined) {
67
+ return;
68
+ } else {
69
+ dict[aggName$1] = stem$1;
70
+ return;
71
+ }
72
+ });
73
+ };
74
+ if (Nodefs.existsSync(srcDir)) {
75
+ walk(srcDir, []);
76
+ }
77
+ return dict;
78
+ }
79
+
80
+ function sortedStems(stems) {
81
+ return stems.toSorted((a, b) => {
82
+ if (a < b) {
83
+ return -1.0;
84
+ } else if (a > b) {
85
+ return 1.0;
86
+ } else {
87
+ return 0.0;
88
+ }
89
+ });
90
+ }
91
+
92
+ function extractTargetName(filePath) {
93
+ try {
94
+ let content = Nodefs.readFileSync(filePath, "utf8");
95
+ let result = {
96
+ contents: undefined
97
+ };
98
+ content.split("\n").forEach(line => {
99
+ let trimmed = line.trimStart();
100
+ if (!trimmed.startsWith("let targetName = ")) {
101
+ return;
102
+ }
103
+ let firstQuote = trimmed.indexOf("\"");
104
+ let lastQuote = trimmed.lastIndexOf("\"");
105
+ if (firstQuote >= 0 && lastQuote > firstQuote) {
106
+ result.contents = trimmed.slice(firstQuote + 1 | 0, lastQuote);
107
+ return;
108
+ }
109
+ });
110
+ return result.contents;
111
+ } catch (exn) {
112
+ return;
113
+ }
114
+ }
115
+
116
+ function hasAsyncAttribute(filePath) {
117
+ try {
118
+ let content = Nodefs.readFileSync(filePath, "utf8");
119
+ let found = {
120
+ contents: false
121
+ };
122
+ content.split("\n").forEach(line => {
123
+ let trimmed = line.trimStart();
124
+ if (trimmed.startsWith("@@reventless.async ") || trimmed.startsWith("@@reventless.async(") || trimmed === "@@reventless.async" || trimmed.startsWith("@@reventless.async\n")) {
125
+ found.contents = true;
126
+ return;
127
+ }
128
+ });
129
+ return found.contents;
130
+ } catch (exn) {
131
+ return false;
132
+ }
133
+ }
134
+
135
+ function resolve(discovered, srcDir) {
136
+ let eventMappings = findEventMappings(srcDir);
137
+ let stateChangeSlices = [];
138
+ let stateViewSlices = [];
139
+ let stateViewSlicesStream = [];
140
+ let automationSlices = [];
141
+ let inboundTranslationSlices = [];
142
+ let outboundTranslationSlices = [];
143
+ let automationSliceRelPaths = {};
144
+ let inboundTranslationSliceRelPaths = {};
145
+ let outboundTranslationSliceRelPaths = {};
146
+ let aggregateSpecs = [];
147
+ let aggregateSpecRelPaths = {};
148
+ let stateChangeSliceRelPaths = {};
149
+ let aggregateBehaviors = [];
150
+ let readModelStems = [];
151
+ let readModelStreamStems = [];
152
+ let projectionsByRelPath = {};
153
+ let tasks = [];
154
+ let epFiles = [];
155
+ let extensions = [];
156
+ discovered.forEach(param => {
157
+ let relPath = param.relPath;
158
+ let componentType = param.componentType;
159
+ let stem = param.stem;
160
+ switch (componentType) {
161
+ case "StateChangeSlice" :
162
+ if (!isImplStem(stem)) {
163
+ stateChangeSlices.push(stem);
164
+ stateChangeSliceRelPaths[stem] = relPath;
165
+ return;
166
+ } else {
167
+ return;
168
+ }
169
+ case "StateViewSlice" :
170
+ if (!isImplStem(stem)) {
171
+ stateViewSlices.push(stem);
172
+ return;
173
+ } else {
174
+ return;
175
+ }
176
+ case "StateViewSliceStream" :
177
+ if (!isImplStem(stem)) {
178
+ stateViewSlicesStream.push(stem);
179
+ return;
180
+ } else {
181
+ return;
182
+ }
183
+ case "AutomationSlice" :
184
+ if (!isImplStem(stem)) {
185
+ automationSlices.push(stem);
186
+ automationSliceRelPaths[stem] = relPath;
187
+ return;
188
+ } else {
189
+ return;
190
+ }
191
+ case "InboundTranslationSlice" :
192
+ if (!isImplStem(stem)) {
193
+ inboundTranslationSlices.push(stem);
194
+ inboundTranslationSliceRelPaths[stem] = relPath;
195
+ return;
196
+ } else {
197
+ return;
198
+ }
199
+ case "OutboundTranslationSlice" :
200
+ if (!isImplStem(stem)) {
201
+ outboundTranslationSlices.push(stem);
202
+ outboundTranslationSliceRelPaths[stem] = relPath;
203
+ return;
204
+ } else {
205
+ return;
206
+ }
207
+ case "Aggregate" :
208
+ if (stem.endsWith("Behavior")) {
209
+ aggregateBehaviors.push(stem);
210
+ } else {
211
+ aggregateSpecs.push(stem);
212
+ aggregateSpecRelPaths[stem] = relPath;
213
+ }
214
+ return;
215
+ case "ReadModel" :
216
+ if (stem.endsWith("Projections")) {
217
+ projectionsByRelPath[stem] = relPath;
218
+ } else {
219
+ readModelStems.push(stem);
220
+ }
221
+ return;
222
+ case "ReadModelStream" :
223
+ if (stem.endsWith("Projections")) {
224
+ projectionsByRelPath[stem] = relPath;
225
+ } else {
226
+ readModelStreamStems.push(stem);
227
+ }
228
+ return;
229
+ case "Task" :
230
+ tasks.push(stem);
231
+ return;
232
+ case "ExtensionPoint" :
233
+ epFiles.push({
234
+ stem: stem,
235
+ componentType: componentType,
236
+ epGroup: param.epGroup,
237
+ relPath: relPath
238
+ });
239
+ return;
240
+ case "Extension" :
241
+ extensions.push(stem);
242
+ return;
243
+ }
244
+ });
245
+ let aggregates = Stdlib_Array.filterMap(sortedStems(aggregateSpecs), spec => {
246
+ let underscored = spec + "_Behavior";
247
+ let bare = spec + "Behavior";
248
+ let behaviorStem = aggregateBehaviors.includes(underscored) ? underscored : (
249
+ aggregateBehaviors.includes(bare) ? bare : undefined
250
+ );
251
+ if (behaviorStem !== undefined) {
252
+ let relPath = aggregateSpecRelPaths[spec];
253
+ let isAsync = relPath !== undefined ? hasAsyncAttribute(Nodepath.join(srcDir, relPath)) : false;
254
+ return {
255
+ spec: spec,
256
+ behavior: behaviorStem,
257
+ eventMappings: eventMappings[spec],
258
+ isAsync: isAsync
259
+ };
260
+ }
261
+ console.warn("Generator: Aggregate spec `" + spec + "` has no matching `" + underscored + "` or `" + bare + "` — skipping");
262
+ });
263
+ let pairReadModel = (stream, rm) => {
264
+ let baseName = rm.endsWith("ReadModel") ? rm.slice(0, rm.length - 9 | 0) : rm;
265
+ let underscoredProj = baseName + "_Projections";
266
+ let match = projectionsByRelPath[underscoredProj];
267
+ if (match !== undefined) {
268
+ return {
269
+ readModel: rm,
270
+ projections: underscoredProj,
271
+ stream: stream
272
+ };
273
+ } else {
274
+ console.warn("Generator: ReadModel `" + rm + "` has no matching `" + underscoredProj + ".res` — skipping");
275
+ return;
276
+ }
277
+ };
278
+ let readModels = Stdlib_Array.filterMap(sortedStems(readModelStems), rm => pairReadModel(false, rm)).concat(Stdlib_Array.filterMap(sortedStems(readModelStreamStems), rm => pairReadModel(true, rm)));
279
+ let epByGroup = {};
280
+ let flatEpMappings = [];
281
+ epFiles.forEach(param => {
282
+ let epGroup = param.epGroup;
283
+ let stem = param.stem;
284
+ if (epGroup !== undefined) {
285
+ let a = [];
286
+ let arr = Stdlib_Option.getOr(epByGroup[epGroup], (epByGroup[epGroup] = a, a));
287
+ arr.push(stem);
288
+ return;
289
+ }
290
+ flatEpMappings.push(stem);
291
+ });
292
+ let extensionPoints = [];
293
+ if (flatEpMappings.length !== 0) {
294
+ extensionPoints.push({
295
+ group: undefined,
296
+ mappings: sortedStems(flatEpMappings)
297
+ });
298
+ }
299
+ Object.entries(epByGroup).forEach(param => {
300
+ extensionPoints.push({
301
+ group: param[0],
302
+ mappings: sortedStems(param[1])
303
+ });
304
+ });
305
+ let buildTargets = (stems, relPaths) => {
306
+ let dict = {};
307
+ stems.forEach(stem => {
308
+ let relPath = relPaths[stem];
309
+ let target = relPath !== undefined ? extractTargetName(Nodepath.join(srcDir, relPath)) : undefined;
310
+ dict[stem] = target;
311
+ });
312
+ return dict;
313
+ };
314
+ let d = {};
315
+ return {
316
+ stateChangeSlices: sortedStems(stateChangeSlices),
317
+ stateViewSlices: sortedStems(stateViewSlices),
318
+ stateViewSlicesStream: sortedStems(stateViewSlicesStream),
319
+ automationSlices: sortedStems(automationSlices),
320
+ inboundTranslationSlices: sortedStems(inboundTranslationSlices),
321
+ outboundTranslationSlices: sortedStems(outboundTranslationSlices),
322
+ automationSliceTargets: buildTargets(automationSlices, automationSliceRelPaths),
323
+ inboundTranslationSliceTargets: buildTargets(inboundTranslationSlices, inboundTranslationSliceRelPaths),
324
+ outboundTranslationSliceTargets: buildTargets(outboundTranslationSlices, outboundTranslationSliceRelPaths),
325
+ asyncStateChangeSlices: (stateChangeSlices.forEach(stem => {
326
+ let relPath = stateChangeSliceRelPaths[stem];
327
+ if (relPath !== undefined && hasAsyncAttribute(Nodepath.join(srcDir, relPath))) {
328
+ d[stem] = true;
329
+ return;
330
+ }
331
+ }), d),
332
+ aggregates: aggregates,
333
+ readModels: readModels,
334
+ tasks: sortedStems(tasks),
335
+ extensionPoints: extensionPoints,
336
+ extensions: sortedStems(extensions)
337
+ };
338
+ }
339
+
340
+ export {
341
+ implSuffixForStateChange,
342
+ implSuffixForStateView,
343
+ implSuffixForAutomation,
344
+ implSuffixForTranslation,
345
+ mappingsSuffixForAutomation,
346
+ isImplStem,
347
+ findEventMappings,
348
+ sortedStems,
349
+ extractTargetName,
350
+ hasAsyncAttribute,
351
+ resolve,
352
+ }
353
+ /* node:fs Not a pure module */
@@ -0,0 +1,53 @@
1
+ // Entry point for the plugin generator.
2
+ // Usage: generate-plugin <srcDir>
3
+ // generate-plugin --aws <Namespace> <srcDir>
4
+
5
+ let () = {
6
+ let argv2 = Generator_Node.argv->Array.get(2)->Option.getOr("")
7
+ let argv3 = Generator_Node.argv->Array.get(3)->Option.getOr("")
8
+ let argv4 = Generator_Node.argv->Array.get(4)->Option.getOr("")
9
+
10
+ let (variant, srcDirArg) = if argv2 === "--aws" {
11
+ if argv3 !== "" && argv4 !== "" {
12
+ (Config.Aws({compositionNamespace: argv3}), argv4)
13
+ } else {
14
+ Console.error("Usage: generate-plugin --aws <Namespace> <srcDir>")
15
+ (Config.Composition, "")
16
+ }
17
+ } else {
18
+ (Config.Composition, argv2)
19
+ }
20
+
21
+ if srcDirArg === "" {
22
+ if argv2 !== "--aws" {
23
+ Console.error("Usage: generate-plugin <srcDir>")
24
+ Console.error(" generate-plugin --aws <Namespace> <srcDir>")
25
+ }
26
+ } else {
27
+ // Resolve to absolute path (handles relative paths and trailing slashes)
28
+ let srcDir = Generator_Node.resolve([srcDirArg])
29
+
30
+ let config = {...Config.read(~srcDir), variant}
31
+ let discovered = Discovery.scan(~srcDir, ~exclude=config.exclude)
32
+ let resolved = Pairing.resolve(discovered, ~srcDir)
33
+ let source = Codegen.render(~config, ~resolved, ~discovered)
34
+
35
+ let outputDir = switch variant {
36
+ | Config.Composition => srcDir
37
+ | Config.Aws(_) => Generator_Node.join([Generator_Node.cwd(), "src"])
38
+ }
39
+
40
+ let pluginPath = Generator_Node.join([outputDir, "Plugin.res"])
41
+ Generator_Node.writeFileSync(pluginPath, source)
42
+ Console.log("Generated: " ++ pluginPath)
43
+
44
+ switch variant {
45
+ | Config.Composition => ()
46
+ | Config.Aws(_) =>
47
+ let mainSource = Codegen.renderMain(~config)
48
+ let mainPath = Generator_Node.join([outputDir, "Main.res"])
49
+ Generator_Node.writeFileSync(mainPath, mainSource)
50
+ Console.log("Generated: " ++ mainPath)
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,70 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Nodefs from "node:fs";
4
+ import * as Nodepath from "node:path";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Config$Reventless from "./Config.res.mjs";
7
+ import * as Codegen$Reventless from "./Codegen.res.mjs";
8
+ import * as Pairing$Reventless from "./Pairing.res.mjs";
9
+ import * as Discovery$Reventless from "./Discovery.res.mjs";
10
+
11
+ let argv2 = Stdlib_Option.getOr(process.argv[2], "");
12
+
13
+ let argv3 = Stdlib_Option.getOr(process.argv[3], "");
14
+
15
+ let argv4 = Stdlib_Option.getOr(process.argv[4], "");
16
+
17
+ let match = argv2 === "--aws" ? (
18
+ argv3 !== "" && argv4 !== "" ? [
19
+ {
20
+ TAG: "Aws",
21
+ compositionNamespace: argv3
22
+ },
23
+ argv4
24
+ ] : (console.error("Usage: generate-plugin --aws <Namespace> <srcDir>"), [
25
+ "Composition",
26
+ ""
27
+ ])
28
+ ) : [
29
+ "Composition",
30
+ argv2
31
+ ];
32
+
33
+ let srcDirArg = match[1];
34
+
35
+ let variant = match[0];
36
+
37
+ if (srcDirArg === "") {
38
+ if (argv2 !== "--aws") {
39
+ console.error("Usage: generate-plugin <srcDir>");
40
+ console.error(" generate-plugin --aws <Namespace> <srcDir>");
41
+ }
42
+ } else {
43
+ let srcDir = Nodepath.resolve(srcDirArg);
44
+ let init = Config$Reventless.read(srcDir);
45
+ let config_name = init.name;
46
+ let config_heartbeatInterval = init.heartbeatInterval;
47
+ let config_exclude = init.exclude;
48
+ let config = {
49
+ name: config_name,
50
+ heartbeatInterval: config_heartbeatInterval,
51
+ exclude: config_exclude,
52
+ variant: variant
53
+ };
54
+ let discovered = Discovery$Reventless.scan(srcDir, config_exclude);
55
+ let resolved = Pairing$Reventless.resolve(discovered, srcDir);
56
+ let source = Codegen$Reventless.render(config, resolved, discovered);
57
+ let outputDir;
58
+ outputDir = typeof variant !== "object" ? srcDir : Nodepath.join(process.cwd(), "src");
59
+ let pluginPath = Nodepath.join(outputDir, "Plugin.res");
60
+ Nodefs.writeFileSync(pluginPath, source, "utf8");
61
+ console.log("Generated: " + pluginPath);
62
+ if (typeof variant === "object") {
63
+ let mainSource = Codegen$Reventless.renderMain(config);
64
+ let mainPath = Nodepath.join(outputDir, "Main.res");
65
+ Nodefs.writeFileSync(mainPath, mainSource, "utf8");
66
+ console.log("Generated: " + mainPath);
67
+ }
68
+ }
69
+
70
+ /* argv2 Not a pure module */
@@ -0,0 +1,23 @@
1
+ // Provider-agnostic authorization rules. Evaluated against the
2
+ // `Identity.t` resolved by an `Auth_Adapter.Provider.authenticate` call.
3
+ //
4
+ // `array<string>` for groups (not a parameterised variant) keeps the
5
+ // framework decoupled from any application's specific group set —
6
+ // applications can define their own typed `group` variant and convert via
7
+ // a thin helper, see docs/analysis/authentication-authorization.md §4.2.
8
+
9
+ @schema
10
+ type permission =
11
+ | AllowGroups(array<string>)
12
+ | AllowAuthenticated
13
+ | AllowAnonymous
14
+ | DenyAll
15
+
16
+ let isAllowed = (rule: permission, identity: Identity.t): bool =>
17
+ switch rule {
18
+ | DenyAll => false
19
+ | AllowAnonymous => true
20
+ | AllowAuthenticated => identity.userId !== "anonymous"
21
+ | AllowGroups(groups) =>
22
+ groups->Array.some(group => identity.groups->Array.includes(group))
23
+ }
@@ -0,0 +1,33 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+
5
+ let permissionSchema = S.union([
6
+ S.schema(s => ({
7
+ TAG: "AllowGroups",
8
+ _0: s.m(S.array(S.string))
9
+ })),
10
+ S.literal("AllowAuthenticated"),
11
+ S.literal("AllowAnonymous"),
12
+ S.literal("DenyAll")
13
+ ]);
14
+
15
+ function isAllowed(rule, identity) {
16
+ if (typeof rule === "object") {
17
+ return rule._0.some(group => identity.groups.includes(group));
18
+ }
19
+ switch (rule) {
20
+ case "AllowAuthenticated" :
21
+ return identity.userId !== "anonymous";
22
+ case "AllowAnonymous" :
23
+ return true;
24
+ case "DenyAll" :
25
+ return false;
26
+ }
27
+ }
28
+
29
+ export {
30
+ permissionSchema,
31
+ isAllowed,
32
+ }
33
+ /* permissionSchema Not a pure module */
@@ -0,0 +1,78 @@
1
+ // Behavior module type for aggregate business logic.
2
+ // Lives in reventless-spec so application domain code can define behaviors
3
+ // without importing the reventless implementation package.
4
+
5
+ /**
6
+ Module type that application code must implement to define aggregate business logic.
7
+
8
+ A `Behavior.T` module contains the `Spec` (command / event / error types), the
9
+ aggregate `state`, and two pure functions plus an initial state value:
10
+
11
+ - `initialState` — the starting state before any events (represents "not yet created")
12
+ - `evolve` — fold an event into the current state
13
+ - `decide` — handle a command on the current state, returning `Ok(events)` or `Error(error)`
14
+
15
+ @example
16
+ ```rescript
17
+ // CategoryBehavior.res
18
+ module Spec = Category
19
+
20
+ @schema
21
+ type state = NotCreated | Active({name: string}) | Archived
22
+
23
+ let initialState = NotCreated
24
+
25
+ let evolve = (state, event) => switch (state, event) {
26
+ | (NotCreated, CategoryAdded({name})) => Active({name})
27
+ | (Active(_), CategoryRenamed({name})) => Active({name})
28
+ | (Active(_), CategoryArchived(_)) => Archived
29
+ | _ => state
30
+ }
31
+
32
+ let decide = (state, cmd) => switch (state, cmd) {
33
+ | (NotCreated, AddCategory({categoryId, name})) => Ok([CategoryAdded({categoryId, name})])
34
+ | (NotCreated, _) => Error(CategoryNotFound)
35
+ | (Active(_), AddCategory(_)) => Error(CategoryAlreadyExists)
36
+ | (Active(_), RenameCategory({categoryId, name})) => Ok([CategoryRenamed({categoryId, name})])
37
+ | (Active(_), ArchiveCategory({categoryId})) => Ok([CategoryArchived({categoryId})])
38
+ | (Archived, _) => Error(CategoryAlreadyArchived)
39
+ }
40
+ ```
41
+ */
42
+ module type T = {
43
+ module Spec: {
44
+ /** The command type. Must carry a `@schema` attribute. */
45
+ @schema
46
+ type command
47
+
48
+ /** The event type. Must carry a `@schema` attribute. */
49
+ @schema
50
+ type event
51
+
52
+ /** The error type returned when business rules are violated. */
53
+ @schema
54
+ type error
55
+ }
56
+
57
+ /** The aggregate's internal state type. Not persisted; rebuilt by replaying events. */
58
+ type state
59
+
60
+ /** The starting state before any events have been applied. */
61
+ let initialState: state
62
+
63
+ /**
64
+ Fold one event into the current state. Called for every event during replay.
65
+ Must be a pure function — no side effects.
66
+ */
67
+ let evolve: (state, Spec.event) => state
68
+
69
+ /**
70
+ Handle a command on the current state.
71
+ Return `Ok(events)` to emit events, or `Error(error)` to reject the command.
72
+ */
73
+ let decide: (state, Spec.command) => result<array<Spec.event>, Spec.error>
74
+
75
+ /** File URL of this module (`import.meta.url`). Used by AWS builders to derive
76
+ the npm specifier for runtime dynamic imports. */
77
+ let moduleUrl: string
78
+ }
@@ -0,0 +1,2 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+ /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */