@reventlessdev/reventless-spec 3.0.0-alpha.63 → 3.0.0-alpha.65
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 +16 -0
- package/package.json +15 -3
- package/rescript.json +6 -1
- package/src/components/ComponentKind.res +99 -0
- package/src/components/ComponentKind.res.mjs +125 -0
- package/src/components/DcbDecode.res +2 -20
- package/src/components/DcbDecode.res.mjs +7 -25
- package/src/components/DcbTag.res +34 -104
- package/src/components/DcbTag.res.mjs +56 -120
- package/src/components/DcbValidation.res +1 -10
- package/src/components/DcbValidation.res.mjs +1 -11
- package/src/components/PluginName.res +39 -0
- package/src/components/PluginName.res.mjs +45 -0
- package/src/components/Snapshot.res +26 -0
- package/src/components/Snapshot.res.mjs +2 -0
- package/src/generator/Codegen.res +10 -0
- package/src/generator/Codegen.res.mjs +2 -1
- package/src/generator/Config.res +9 -26
- package/src/generator/Config.res.mjs +5 -30
- package/src/generator/Discovery.res +5 -26
- package/src/generator/Discovery.res.mjs +4 -54
- package/src/generator/Pairing.res +111 -41
- package/src/generator/Pairing.res.mjs +93 -33
- package/src/types/Behavior.res +8 -0
|
@@ -1,61 +1,9 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Nodepath from "node:path";
|
|
4
|
+
import * as ComponentKind$Reventless from "../components/ComponentKind.res.mjs";
|
|
4
5
|
import * as Generator_Node$Reventless from "./Generator_Node.res.mjs";
|
|
5
6
|
|
|
6
|
-
function folderToComponentType(folder) {
|
|
7
|
-
switch (folder) {
|
|
8
|
-
case "Aggregate" :
|
|
9
|
-
case "Aggregates" :
|
|
10
|
-
return "Aggregate";
|
|
11
|
-
case "Automation" :
|
|
12
|
-
case "AutomationSlice" :
|
|
13
|
-
case "AutomationSlices" :
|
|
14
|
-
case "Automations" :
|
|
15
|
-
return "AutomationSlice";
|
|
16
|
-
case "ExtensionPoint" :
|
|
17
|
-
case "ExtensionPoints" :
|
|
18
|
-
return "ExtensionPoint";
|
|
19
|
-
case "Extension" :
|
|
20
|
-
case "Extensions" :
|
|
21
|
-
return "Extension";
|
|
22
|
-
case "InboundTranslation" :
|
|
23
|
-
case "InboundTranslationSlice" :
|
|
24
|
-
case "InboundTranslationSlices" :
|
|
25
|
-
case "InboundTranslations" :
|
|
26
|
-
return "InboundTranslationSlice";
|
|
27
|
-
case "OutboundTranslation" :
|
|
28
|
-
case "OutboundTranslationSlice" :
|
|
29
|
-
case "OutboundTranslationSlices" :
|
|
30
|
-
case "OutboundTranslations" :
|
|
31
|
-
return "OutboundTranslationSlice";
|
|
32
|
-
case "ReadModelStream" :
|
|
33
|
-
case "ReadModelStreams" :
|
|
34
|
-
return "ReadModelStream";
|
|
35
|
-
case "ReadModel" :
|
|
36
|
-
case "ReadModels" :
|
|
37
|
-
return "ReadModel";
|
|
38
|
-
case "StateChange" :
|
|
39
|
-
case "StateChangeSlice" :
|
|
40
|
-
case "StateChangeSlices" :
|
|
41
|
-
case "StateChanges" :
|
|
42
|
-
return "StateChangeSlice";
|
|
43
|
-
case "StateViewSliceStream" :
|
|
44
|
-
case "StateViewSliceStreams" :
|
|
45
|
-
return "StateViewSliceStream";
|
|
46
|
-
case "StateView" :
|
|
47
|
-
case "StateViewSlice" :
|
|
48
|
-
case "StateViewSlices" :
|
|
49
|
-
case "StateViews" :
|
|
50
|
-
return "StateViewSlice";
|
|
51
|
-
case "Task" :
|
|
52
|
-
case "Tasks" :
|
|
53
|
-
return "Task";
|
|
54
|
-
default:
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
7
|
function isAlwaysExcludedDir(name) {
|
|
60
8
|
if (name === "Plugin" || name === "tests") {
|
|
61
9
|
return true;
|
|
@@ -146,7 +94,7 @@ function walkDir(dir, relDir, parentComponentType, epGroup, exclude, acc) {
|
|
|
146
94
|
if (isAlwaysExcludedDir(entryName)) {
|
|
147
95
|
return;
|
|
148
96
|
}
|
|
149
|
-
let ct =
|
|
97
|
+
let ct = ComponentKind$Reventless.folderToKind(entryName);
|
|
150
98
|
if (ct === undefined) {
|
|
151
99
|
return walkDir(Nodepath.join(dir, entryName), relPath, parentComponentType, epGroup, exclude, acc);
|
|
152
100
|
}
|
|
@@ -199,6 +147,8 @@ function scan(srcDir, exclude) {
|
|
|
199
147
|
return acc;
|
|
200
148
|
}
|
|
201
149
|
|
|
150
|
+
let folderToComponentType = ComponentKind$Reventless.folderToKind;
|
|
151
|
+
|
|
202
152
|
export {
|
|
203
153
|
folderToComponentType,
|
|
204
154
|
isAlwaysExcludedDir,
|
|
@@ -57,6 +57,12 @@ type resolved = {
|
|
|
57
57
|
// StateChangeSlice stems carrying `@@reventless.async` — codegen emits
|
|
58
58
|
// `Platform.StateChangeSlice.MakeAsync` for these (CommandPending response).
|
|
59
59
|
asyncStateChangeSlices: Dict.t<bool>,
|
|
60
|
+
// Spec names of StateChangeSlices / StateViewSlices (incl. Stream) carrying
|
|
61
|
+
// `@@reventless.systemCallable` — codegen threads them as
|
|
62
|
+
// `~systemCallableComponents` on `Platform.Plugin.make` so the AWS provider
|
|
63
|
+
// emits the dual-auth `@aws_cognito_user_pools(...) @aws_iam` directive on
|
|
64
|
+
// their GraphQL fields (deploy-time SigV4 system callers). Sorted.
|
|
65
|
+
systemCallableComponents: array<string>,
|
|
60
66
|
aggregates: array<aggregateDef>,
|
|
61
67
|
readModels: array<readModelDef>,
|
|
62
68
|
tasks: array<string>,
|
|
@@ -139,22 +145,21 @@ let extractTargetName = (filePath: string): option<string> => {
|
|
|
139
145
|
}
|
|
140
146
|
}
|
|
141
147
|
|
|
142
|
-
// Returns true if the spec file declares
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
let
|
|
148
|
+
// Returns true if the spec file declares the given file-level attribute
|
|
149
|
+
// (e.g. `@@reventless.async`, `@@reventless.systemCallable`). Falls back to
|
|
150
|
+
// false on read errors so a transient FS failure can't flip a component
|
|
151
|
+
// into the opt-in behavior — the safe default is the attribute being absent.
|
|
152
|
+
let hasFileAttribute = (filePath: string, ~attr: string): bool => {
|
|
147
153
|
try {
|
|
148
154
|
let content = Generator_Node.readFileSync(filePath)
|
|
149
155
|
let found = ref(false)
|
|
150
156
|
content->String.split("\n")->Array.forEach(line => {
|
|
151
157
|
let trimmed = line->String.trimStart
|
|
152
|
-
// Match
|
|
158
|
+
// Match the bare attribute and tolerate a payload arg in the future.
|
|
153
159
|
if (
|
|
154
|
-
trimmed->String.startsWith("
|
|
155
|
-
trimmed->String.startsWith("
|
|
156
|
-
trimmed ===
|
|
157
|
-
trimmed->String.startsWith("@@reventless.async\n")
|
|
160
|
+
trimmed->String.startsWith(attr ++ " ") ||
|
|
161
|
+
trimmed->String.startsWith(attr ++ "(") ||
|
|
162
|
+
trimmed === attr
|
|
158
163
|
) {
|
|
159
164
|
found := true
|
|
160
165
|
}
|
|
@@ -165,6 +170,73 @@ let hasAsyncAttribute = (filePath: string): bool => {
|
|
|
165
170
|
}
|
|
166
171
|
}
|
|
167
172
|
|
|
173
|
+
// `@@reventless.async` — Aggregate or StateChangeSlice opt-in to async
|
|
174
|
+
// command dispatch (CommandPending response).
|
|
175
|
+
let hasAsyncAttribute = (filePath: string): bool =>
|
|
176
|
+
hasFileAttribute(filePath, ~attr="@@reventless.async")
|
|
177
|
+
|
|
178
|
+
// `@@reventless.systemCallable` — StateChangeSlice / StateViewSlice opt-in to
|
|
179
|
+
// deploy-time IAM (SigV4) invocation of the component's GraphQL fields.
|
|
180
|
+
let hasSystemCallableAttribute = (filePath: string): bool =>
|
|
181
|
+
hasFileAttribute(filePath, ~attr="@@reventless.systemCallable")
|
|
182
|
+
|
|
183
|
+
// The effective spec name of a file: the explicit `@@reventless.spec("Name")`
|
|
184
|
+
// payload when present, the filename stem otherwise (mirroring the PPX's name
|
|
185
|
+
// derivation). Needed because `systemCallableComponents` is matched against
|
|
186
|
+
// `Spec.name` at deploy time — a stem-only value would silently miss specs
|
|
187
|
+
// with an explicit name override.
|
|
188
|
+
let effectiveSpecName = (filePath: string, ~stem: string): string => {
|
|
189
|
+
try {
|
|
190
|
+
let content = Generator_Node.readFileSync(filePath)
|
|
191
|
+
let name = ref(None)
|
|
192
|
+
content->String.split("\n")->Array.forEach(line => {
|
|
193
|
+
let trimmed = line->String.trimStart
|
|
194
|
+
if trimmed->String.startsWith("@@reventless.spec(") {
|
|
195
|
+
let firstQuote = trimmed->String.indexOf("\"")
|
|
196
|
+
let lastQuote = trimmed->String.lastIndexOf("\"")
|
|
197
|
+
if firstQuote >= 0 && lastQuote > firstQuote {
|
|
198
|
+
name := Some(trimmed->String.slice(~start=firstQuote + 1, ~end=lastQuote))
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
name.contents->Option.getOr(stem)
|
|
203
|
+
} catch {
|
|
204
|
+
| _ => stem
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Partition the ExtensionPoint mapping files into flat (ungrouped) and grouped
|
|
209
|
+
// `extensionPointDef`s: every mapping sharing an `epGroup` collects into one
|
|
210
|
+
// grouped entry; the ungrouped ones share a single flat (`group: None`) entry.
|
|
211
|
+
// Pure (no I/O) so the grouping is unit-testable — in particular the A8 fix:
|
|
212
|
+
// `Option.getOr`'s eager default previously re-created the group's array every
|
|
213
|
+
// iteration, so a group with ≥2 mappings collapsed to `[]` and the generator
|
|
214
|
+
// emitted a `Plugin.res` referencing a never-generated module.
|
|
215
|
+
let groupExtensionPoints = (epFiles: array<Discovery.discoveredFile>): array<extensionPointDef> => {
|
|
216
|
+
let epByGroup: Dict.t<array<string>> = Dict.make()
|
|
217
|
+
let flatEpMappings: array<string> = []
|
|
218
|
+
|
|
219
|
+
epFiles->Array.forEach(({stem, epGroup}) =>
|
|
220
|
+
switch epGroup {
|
|
221
|
+
| None => flatEpMappings->Array.push(stem)
|
|
222
|
+
| Some(g) =>
|
|
223
|
+
switch Dict.get(epByGroup, g) {
|
|
224
|
+
| Some(arr) => arr->Array.push(stem)
|
|
225
|
+
| None => Dict.set(epByGroup, g, [stem])
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
let extensionPoints: array<extensionPointDef> = []
|
|
231
|
+
if flatEpMappings->Array.length > 0 {
|
|
232
|
+
extensionPoints->Array.push({group: None, mappings: flatEpMappings->sortedStems})
|
|
233
|
+
}
|
|
234
|
+
Dict.toArray(epByGroup)->Array.forEach(((group, mappings)) =>
|
|
235
|
+
extensionPoints->Array.push({group: Some(group), mappings: mappings->sortedStems})
|
|
236
|
+
)
|
|
237
|
+
extensionPoints
|
|
238
|
+
}
|
|
239
|
+
|
|
168
240
|
let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): resolved => {
|
|
169
241
|
let eventMappings = findEventMappings(~srcDir)
|
|
170
242
|
|
|
@@ -181,6 +253,8 @@ let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): re
|
|
|
181
253
|
let aggregateSpecs: array<string> = []
|
|
182
254
|
let aggregateSpecRelPaths: Dict.t<string> = Dict.make()
|
|
183
255
|
let stateChangeSliceRelPaths: Dict.t<string> = Dict.make()
|
|
256
|
+
// Shared by StateViewSlice and StateViewSliceStream (stems are unique).
|
|
257
|
+
let stateViewSliceRelPaths: Dict.t<string> = Dict.make()
|
|
184
258
|
let aggregateBehaviors: array<string> = []
|
|
185
259
|
let readModelStems: array<string> = []
|
|
186
260
|
// Stream-enabled read model spec stems (the `ReadModelStream/` folder).
|
|
@@ -204,9 +278,15 @@ let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): re
|
|
|
204
278
|
stateChangeSliceRelPaths->Dict.set(stem, relPath)
|
|
205
279
|
}
|
|
206
280
|
| StateViewSlice =>
|
|
207
|
-
if !isImplStem(stem) {
|
|
281
|
+
if !isImplStem(stem) {
|
|
282
|
+
stateViewSlices->Array.push(stem)
|
|
283
|
+
stateViewSliceRelPaths->Dict.set(stem, relPath)
|
|
284
|
+
}
|
|
208
285
|
| StateViewSliceStream =>
|
|
209
|
-
if !isImplStem(stem) {
|
|
286
|
+
if !isImplStem(stem) {
|
|
287
|
+
stateViewSlicesStream->Array.push(stem)
|
|
288
|
+
stateViewSliceRelPaths->Dict.set(stem, relPath)
|
|
289
|
+
}
|
|
210
290
|
| AutomationSlice =>
|
|
211
291
|
if !isImplStem(stem) {
|
|
212
292
|
automationSlices->Array.push(stem)
|
|
@@ -309,35 +389,7 @@ let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): re
|
|
|
309
389
|
)
|
|
310
390
|
|
|
311
391
|
// ── ExtensionPoint grouping ────────────────────────────────────────────────
|
|
312
|
-
let
|
|
313
|
-
let flatEpMappings: array<string> = []
|
|
314
|
-
|
|
315
|
-
epFiles->Array.forEach(({stem, epGroup}) => {
|
|
316
|
-
switch epGroup {
|
|
317
|
-
| None => flatEpMappings->Array.push(stem)
|
|
318
|
-
| Some(g) =>
|
|
319
|
-
// `Option.getOr`'s default is evaluated eagerly, so the previous shape
|
|
320
|
-
// re-created and re-set a fresh empty array every iteration — overwriting
|
|
321
|
-
// the group's real entry and pushing onto a now-detached array. A group
|
|
322
|
-
// with ≥2 mappings ended up empty, emitting a Plugin.res that referenced a
|
|
323
|
-
// never-generated module (`Array.getUnsafe(0)` on `[]`). Use a switch so
|
|
324
|
-
// the create branch runs only when the group is genuinely absent.
|
|
325
|
-
switch Dict.get(epByGroup, g) {
|
|
326
|
-
| Some(arr) => arr->Array.push(stem)
|
|
327
|
-
| None => Dict.set(epByGroup, g, [stem])
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
let extensionPoints: array<extensionPointDef> = []
|
|
333
|
-
|
|
334
|
-
if flatEpMappings->Array.length > 0 {
|
|
335
|
-
extensionPoints->Array.push({group: None, mappings: flatEpMappings->sortedStems})
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
Dict.toArray(epByGroup)->Array.forEach(((group, mappings)) => {
|
|
339
|
-
extensionPoints->Array.push({group: Some(group), mappings: mappings->sortedStems})
|
|
340
|
-
})
|
|
392
|
+
let extensionPoints = groupExtensionPoints(epFiles)
|
|
341
393
|
|
|
342
394
|
// ── targetName extraction ──────────────────────────────────────────────────
|
|
343
395
|
let buildTargets = (stems: array<string>, relPaths: Dict.t<string>): Dict.t<option<string>> => {
|
|
@@ -381,6 +433,24 @@ let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): re
|
|
|
381
433
|
})
|
|
382
434
|
d
|
|
383
435
|
},
|
|
436
|
+
systemCallableComponents: {
|
|
437
|
+
let names = []
|
|
438
|
+
let collect = (stems: array<string>, relPaths: Dict.t<string>) =>
|
|
439
|
+
stems->Array.forEach(stem => {
|
|
440
|
+
switch Dict.get(relPaths, stem) {
|
|
441
|
+
| None => ()
|
|
442
|
+
| Some(relPath) =>
|
|
443
|
+
let filePath = Generator_Node.join([srcDir, relPath])
|
|
444
|
+
if hasSystemCallableAttribute(filePath) {
|
|
445
|
+
names->Array.push(effectiveSpecName(filePath, ~stem))
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
})
|
|
449
|
+
collect(stateChangeSlices, stateChangeSliceRelPaths)
|
|
450
|
+
collect(stateViewSlices, stateViewSliceRelPaths)
|
|
451
|
+
collect(stateViewSlicesStream, stateViewSliceRelPaths)
|
|
452
|
+
names->sortedStems
|
|
453
|
+
},
|
|
384
454
|
aggregates,
|
|
385
455
|
readModels,
|
|
386
456
|
tasks: tasks->sortedStems,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as Nodefs from "node:fs";
|
|
4
4
|
import * as Nodepath from "node:path";
|
|
5
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";
|
|
6
7
|
import * as Generator_Node$Reventless from "./Generator_Node.res.mjs";
|
|
7
8
|
|
|
8
9
|
let implSuffixForStateChange = "_Behavior";
|
|
@@ -112,7 +113,7 @@ function extractTargetName(filePath) {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
function
|
|
116
|
+
function hasFileAttribute(filePath, attr) {
|
|
116
117
|
try {
|
|
117
118
|
let content = Nodefs.readFileSync(filePath, "utf8");
|
|
118
119
|
let found = {
|
|
@@ -120,7 +121,7 @@ function hasAsyncAttribute(filePath) {
|
|
|
120
121
|
};
|
|
121
122
|
content.split("\n").forEach(line => {
|
|
122
123
|
let trimmed = line.trimStart();
|
|
123
|
-
if (trimmed.startsWith("
|
|
124
|
+
if (trimmed.startsWith(attr + " ") || trimmed.startsWith(attr + "(") || trimmed === attr) {
|
|
124
125
|
found.contents = true;
|
|
125
126
|
return;
|
|
126
127
|
}
|
|
@@ -131,6 +132,71 @@ function hasAsyncAttribute(filePath) {
|
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
function hasAsyncAttribute(filePath) {
|
|
136
|
+
return hasFileAttribute(filePath, "@@reventless.async");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function hasSystemCallableAttribute(filePath) {
|
|
140
|
+
return hasFileAttribute(filePath, "@@reventless.systemCallable");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function effectiveSpecName(filePath, stem) {
|
|
144
|
+
try {
|
|
145
|
+
let content = Nodefs.readFileSync(filePath, "utf8");
|
|
146
|
+
let name = {
|
|
147
|
+
contents: undefined
|
|
148
|
+
};
|
|
149
|
+
content.split("\n").forEach(line => {
|
|
150
|
+
let trimmed = line.trimStart();
|
|
151
|
+
if (!trimmed.startsWith("@@reventless.spec(")) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let firstQuote = trimmed.indexOf("\"");
|
|
155
|
+
let lastQuote = trimmed.lastIndexOf("\"");
|
|
156
|
+
if (firstQuote >= 0 && lastQuote > firstQuote) {
|
|
157
|
+
name.contents = trimmed.slice(firstQuote + 1 | 0, lastQuote);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return Stdlib_Option.getOr(name.contents, stem);
|
|
162
|
+
} catch (exn) {
|
|
163
|
+
return stem;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function groupExtensionPoints(epFiles) {
|
|
168
|
+
let epByGroup = {};
|
|
169
|
+
let flatEpMappings = [];
|
|
170
|
+
epFiles.forEach(param => {
|
|
171
|
+
let epGroup = param.epGroup;
|
|
172
|
+
let stem = param.stem;
|
|
173
|
+
if (epGroup !== undefined) {
|
|
174
|
+
let arr = epByGroup[epGroup];
|
|
175
|
+
if (arr !== undefined) {
|
|
176
|
+
arr.push(stem);
|
|
177
|
+
} else {
|
|
178
|
+
epByGroup[epGroup] = [stem];
|
|
179
|
+
}
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
flatEpMappings.push(stem);
|
|
183
|
+
});
|
|
184
|
+
let extensionPoints = [];
|
|
185
|
+
if (flatEpMappings.length !== 0) {
|
|
186
|
+
extensionPoints.push({
|
|
187
|
+
group: undefined,
|
|
188
|
+
mappings: sortedStems(flatEpMappings)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
Object.entries(epByGroup).forEach(param => {
|
|
192
|
+
extensionPoints.push({
|
|
193
|
+
group: param[0],
|
|
194
|
+
mappings: sortedStems(param[1])
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
return extensionPoints;
|
|
198
|
+
}
|
|
199
|
+
|
|
134
200
|
function resolve(discovered, srcDir) {
|
|
135
201
|
let eventMappings = findEventMappings(srcDir);
|
|
136
202
|
let stateChangeSlices = [];
|
|
@@ -145,6 +211,7 @@ function resolve(discovered, srcDir) {
|
|
|
145
211
|
let aggregateSpecs = [];
|
|
146
212
|
let aggregateSpecRelPaths = {};
|
|
147
213
|
let stateChangeSliceRelPaths = {};
|
|
214
|
+
let stateViewSliceRelPaths = {};
|
|
148
215
|
let aggregateBehaviors = [];
|
|
149
216
|
let readModelStems = [];
|
|
150
217
|
let readModelStreamStems = [];
|
|
@@ -168,6 +235,7 @@ function resolve(discovered, srcDir) {
|
|
|
168
235
|
case "StateViewSlice" :
|
|
169
236
|
if (!isImplStem(stem)) {
|
|
170
237
|
stateViewSlices.push(stem);
|
|
238
|
+
stateViewSliceRelPaths[stem] = relPath;
|
|
171
239
|
return;
|
|
172
240
|
} else {
|
|
173
241
|
return;
|
|
@@ -175,6 +243,7 @@ function resolve(discovered, srcDir) {
|
|
|
175
243
|
case "StateViewSliceStream" :
|
|
176
244
|
if (!isImplStem(stem)) {
|
|
177
245
|
stateViewSlicesStream.push(stem);
|
|
246
|
+
stateViewSliceRelPaths[stem] = relPath;
|
|
178
247
|
return;
|
|
179
248
|
} else {
|
|
180
249
|
return;
|
|
@@ -249,7 +318,7 @@ function resolve(discovered, srcDir) {
|
|
|
249
318
|
);
|
|
250
319
|
if (behaviorStem !== undefined) {
|
|
251
320
|
let relPath = aggregateSpecRelPaths[spec];
|
|
252
|
-
let isAsync = relPath !== undefined ?
|
|
321
|
+
let isAsync = relPath !== undefined ? hasFileAttribute(Nodepath.join(srcDir, relPath), "@@reventless.async") : false;
|
|
253
322
|
return {
|
|
254
323
|
spec: spec,
|
|
255
324
|
behavior: behaviorStem,
|
|
@@ -275,35 +344,7 @@ function resolve(discovered, srcDir) {
|
|
|
275
344
|
}
|
|
276
345
|
};
|
|
277
346
|
let readModels = Stdlib_Array.filterMap(sortedStems(readModelStems), rm => pairReadModel(false, rm)).concat(Stdlib_Array.filterMap(sortedStems(readModelStreamStems), rm => pairReadModel(true, rm)));
|
|
278
|
-
let
|
|
279
|
-
let flatEpMappings = [];
|
|
280
|
-
epFiles.forEach(param => {
|
|
281
|
-
let epGroup = param.epGroup;
|
|
282
|
-
let stem = param.stem;
|
|
283
|
-
if (epGroup !== undefined) {
|
|
284
|
-
let arr = epByGroup[epGroup];
|
|
285
|
-
if (arr !== undefined) {
|
|
286
|
-
arr.push(stem);
|
|
287
|
-
} else {
|
|
288
|
-
epByGroup[epGroup] = [stem];
|
|
289
|
-
}
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
flatEpMappings.push(stem);
|
|
293
|
-
});
|
|
294
|
-
let extensionPoints = [];
|
|
295
|
-
if (flatEpMappings.length !== 0) {
|
|
296
|
-
extensionPoints.push({
|
|
297
|
-
group: undefined,
|
|
298
|
-
mappings: sortedStems(flatEpMappings)
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
Object.entries(epByGroup).forEach(param => {
|
|
302
|
-
extensionPoints.push({
|
|
303
|
-
group: param[0],
|
|
304
|
-
mappings: sortedStems(param[1])
|
|
305
|
-
});
|
|
306
|
-
});
|
|
347
|
+
let extensionPoints = groupExtensionPoints(epFiles);
|
|
307
348
|
let buildTargets = (stems, relPaths) => {
|
|
308
349
|
let dict = {};
|
|
309
350
|
stems.forEach(stem => {
|
|
@@ -314,6 +355,20 @@ function resolve(discovered, srcDir) {
|
|
|
314
355
|
return dict;
|
|
315
356
|
};
|
|
316
357
|
let d = {};
|
|
358
|
+
let names = [];
|
|
359
|
+
let collect = (stems, relPaths) => {
|
|
360
|
+
stems.forEach(stem => {
|
|
361
|
+
let relPath = relPaths[stem];
|
|
362
|
+
if (relPath === undefined) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
let filePath = Nodepath.join(srcDir, relPath);
|
|
366
|
+
if (hasFileAttribute(filePath, "@@reventless.systemCallable")) {
|
|
367
|
+
names.push(effectiveSpecName(filePath, stem));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
};
|
|
317
372
|
return {
|
|
318
373
|
stateChangeSlices: sortedStems(stateChangeSlices),
|
|
319
374
|
stateViewSlices: sortedStems(stateViewSlices),
|
|
@@ -326,11 +381,12 @@ function resolve(discovered, srcDir) {
|
|
|
326
381
|
outboundTranslationSliceTargets: buildTargets(outboundTranslationSlices, outboundTranslationSliceRelPaths),
|
|
327
382
|
asyncStateChangeSlices: (stateChangeSlices.forEach(stem => {
|
|
328
383
|
let relPath = stateChangeSliceRelPaths[stem];
|
|
329
|
-
if (relPath !== undefined &&
|
|
384
|
+
if (relPath !== undefined && hasFileAttribute(Nodepath.join(srcDir, relPath), "@@reventless.async")) {
|
|
330
385
|
d[stem] = true;
|
|
331
386
|
return;
|
|
332
387
|
}
|
|
333
388
|
}), d),
|
|
389
|
+
systemCallableComponents: (collect(stateChangeSlices, stateChangeSliceRelPaths), collect(stateViewSlices, stateViewSliceRelPaths), collect(stateViewSlicesStream, stateViewSliceRelPaths), sortedStems(names)),
|
|
334
390
|
aggregates: aggregates,
|
|
335
391
|
readModels: readModels,
|
|
336
392
|
tasks: sortedStems(tasks),
|
|
@@ -349,7 +405,11 @@ export {
|
|
|
349
405
|
findEventMappings,
|
|
350
406
|
sortedStems,
|
|
351
407
|
extractTargetName,
|
|
408
|
+
hasFileAttribute,
|
|
352
409
|
hasAsyncAttribute,
|
|
410
|
+
hasSystemCallableAttribute,
|
|
411
|
+
effectiveSpecName,
|
|
412
|
+
groupExtensionPoints,
|
|
353
413
|
resolve,
|
|
354
414
|
}
|
|
355
415
|
/* node:fs Not a pure module */
|
package/src/types/Behavior.res
CHANGED
|
@@ -72,6 +72,14 @@ module type T = {
|
|
|
72
72
|
*/
|
|
73
73
|
let decide: (state, Spec.command) => result<array<Spec.event>, Spec.error>
|
|
74
74
|
|
|
75
|
+
/** Persisted-snapshot configuration (docs/plans/aggregate-snapshotting.md).
|
|
76
|
+
`None` — the default, auto-injected by `@@reventless.behavior` — keeps
|
|
77
|
+
full replay; `Some({interval, stateSchema})` writes a keep-one snapshot
|
|
78
|
+
every `interval` events and seeds cold replays from the latest one.
|
|
79
|
+
Enable with `@@reventless.snapshots(<interval>)` (requires
|
|
80
|
+
`@schema type state`) or a manual binding. */
|
|
81
|
+
let snapshot: option<Snapshot.config<state>>
|
|
82
|
+
|
|
75
83
|
/** File URL of this module (`import.meta.url`). Used by AWS builders to derive
|
|
76
84
|
the npm specifier for runtime dynamic imports. */
|
|
77
85
|
let moduleUrl: string
|