@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.
- package/CHANGELOG.md +625 -0
- package/LICENSE +202 -0
- package/README.md +114 -0
- package/package.json +34 -0
- package/rescript.json +25 -0
- package/run-generator.mjs +2 -0
- package/src/AnsiStyle.res +40 -0
- package/src/AnsiStyle.res.mjs +54 -0
- package/src/LogPrefix.res +168 -0
- package/src/LogPrefix.res.mjs +148 -0
- package/src/PackageVersion.res +73 -0
- package/src/PackageVersion.res.mjs +81 -0
- package/src/components/Aggregate.res +64 -0
- package/src/components/Aggregate.res.mjs +2 -0
- package/src/components/AutomationSlice.res +279 -0
- package/src/components/AutomationSlice.res.mjs +30 -0
- package/src/components/Counter.res +24 -0
- package/src/components/Counter.res.mjs +2 -0
- package/src/components/DcbDecode.res +128 -0
- package/src/components/DcbDecode.res.mjs +117 -0
- package/src/components/DcbTag.res +1256 -0
- package/src/components/DcbTag.res.mjs +869 -0
- package/src/components/DcbValidation.res +362 -0
- package/src/components/DcbValidation.res.mjs +360 -0
- package/src/components/DisplayName.res +40 -0
- package/src/components/DisplayName.res.mjs +26 -0
- package/src/components/ExtensionPoint.res +27 -0
- package/src/components/ExtensionPoint.res.mjs +2 -0
- package/src/components/InboundTranslationSlice.res +79 -0
- package/src/components/InboundTranslationSlice.res.mjs +2 -0
- package/src/components/OutboundTranslationSlice.res +108 -0
- package/src/components/OutboundTranslationSlice.res.mjs +2 -0
- package/src/components/Plugin.res +373 -0
- package/src/components/Plugin.res.mjs +249 -0
- package/src/components/ReadModel.res +199 -0
- package/src/components/ReadModel.res.mjs +18 -0
- package/src/components/Reference.res +51 -0
- package/src/components/Reference.res.mjs +37 -0
- package/src/components/StateAnnotations.res +65 -0
- package/src/components/StateAnnotations.res.mjs +15 -0
- package/src/components/StateChangeSlice.res +131 -0
- package/src/components/StateChangeSlice.res.mjs +2 -0
- package/src/components/StateViewSlice.res +99 -0
- package/src/components/StateViewSlice.res.mjs +2 -0
- package/src/components/Task.res +62 -0
- package/src/components/Task.res.mjs +2 -0
- package/src/generator/Codegen.res +703 -0
- package/src/generator/Codegen.res.mjs +485 -0
- package/src/generator/Config.res +85 -0
- package/src/generator/Config.res.mjs +78 -0
- package/src/generator/Discovery.res +204 -0
- package/src/generator/Discovery.res.mjs +213 -0
- package/src/generator/Generator_Node.res +49 -0
- package/src/generator/Generator_Node.res.mjs +18 -0
- package/src/generator/Pairing.res +386 -0
- package/src/generator/Pairing.res.mjs +353 -0
- package/src/generator/PluginGenerator.res +53 -0
- package/src/generator/PluginGenerator.res.mjs +70 -0
- package/src/types/Authorization.res +23 -0
- package/src/types/Authorization.res.mjs +33 -0
- package/src/types/Behavior.res +78 -0
- package/src/types/Behavior.res.mjs +2 -0
- package/src/types/EventMapping.res +100 -0
- package/src/types/EventMapping.res.mjs +15 -0
- package/src/types/Handler.res +30 -0
- package/src/types/Handler.res.mjs +2 -0
- package/src/types/Id.res +75 -0
- package/src/types/Id.res.mjs +37 -0
- package/src/types/Identity.res +46 -0
- package/src/types/Identity.res.mjs +51 -0
- package/src/types/Message.res +197 -0
- package/src/types/Message.res.mjs +108 -0
- package/src/types/Projection.res +220 -0
- package/src/types/Projection.res.mjs +44 -0
- package/src/types/QueryEngine.res +123 -0
- package/src/types/QueryEngine.res.mjs +12 -0
- package/src/types/ReadConsistency.res +38 -0
- package/src/types/ReadConsistency.res.mjs +29 -0
- package/src/types/Schedule.res +65 -0
- package/src/types/Schedule.res.mjs +68 -0
- package/src/types/SideEffect.res +45 -0
- package/src/types/SideEffect.res.mjs +2 -0
- package/src/types/StoredEvent.res +46 -0
- package/src/types/StoredEvent.res.mjs +32 -0
- package/src/types/Visibility.res +24 -0
- 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
|
+
}
|