@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,386 @@
|
|
|
1
|
+
// Applies pairing rules to discovered files.
|
|
2
|
+
// - Aggregates: pairs Foo + FooBehavior; finds Foo_EventMappings if present
|
|
3
|
+
// - ReadModels: pairs FooReadModel + FooProjections; extracts mapping module names from projections file
|
|
4
|
+
// - Slices (Plan 02): pair X + X_<Kind> where Kind is the implementation
|
|
5
|
+
// suffix matching the slice's folder. The slice arrays carry only spec
|
|
6
|
+
// stems; the corresponding impl stem is `<stem>_<implSuffix>`.
|
|
7
|
+
// * StateChangeSlice / StateViewSlice → Behavior / Projection
|
|
8
|
+
// * AutomationSlice → Automation
|
|
9
|
+
// * In/OutboundTranslationSlice → Translation
|
|
10
|
+
// - ExtensionPoints: groups by epGroup, counts mappings, selects Make variant
|
|
11
|
+
|
|
12
|
+
// Implementation-file suffix per slice kind. Used to filter impl files out
|
|
13
|
+
// of the slice arrays (they're paired with their spec stems via this
|
|
14
|
+
// suffix at codegen time) and to compute the impl module reference.
|
|
15
|
+
let implSuffixForStateChange = "_Behavior"
|
|
16
|
+
let implSuffixForStateView = "_Projection"
|
|
17
|
+
let implSuffixForAutomation = "_Automation"
|
|
18
|
+
let implSuffixForTranslation = "_Translation"
|
|
19
|
+
// Plan 04: AutomationSlice splits source-side concerns into a `_Mappings`
|
|
20
|
+
// sibling. Treated as an impl file so it doesn't surface as a slice spec.
|
|
21
|
+
let mappingsSuffixForAutomation = "_Mappings"
|
|
22
|
+
|
|
23
|
+
let isImplStem = (stem: string): bool =>
|
|
24
|
+
stem->String.endsWith(implSuffixForStateChange)
|
|
25
|
+
|| stem->String.endsWith(implSuffixForStateView)
|
|
26
|
+
|| stem->String.endsWith(implSuffixForAutomation)
|
|
27
|
+
|| stem->String.endsWith(implSuffixForTranslation)
|
|
28
|
+
|| stem->String.endsWith(mappingsSuffixForAutomation)
|
|
29
|
+
|
|
30
|
+
type aggregateDef = {spec: string, behavior: string, eventMappings: option<string>, isAsync: bool}
|
|
31
|
+
// readModelDef pairs a ReadModel spec with its sibling `_Projections.res`
|
|
32
|
+
// file. Codegen emits `Platform.ReadModel.Make(<spec>, <projections>)` and
|
|
33
|
+
// expects the projections file to declare `let mappings` via
|
|
34
|
+
// `@@reventless.mappings`. `stream: true` (the `ReadModelStream/` folder) flips
|
|
35
|
+
// the emitted factory to `Platform.ReadModelStream.Make` — a DynamoDB-Stream-
|
|
36
|
+
// backed QueryDb that drives AppSync Events (Source B) live updates. Stream and
|
|
37
|
+
// non-stream read models are otherwise identical downstream (resolvers, AutoUI
|
|
38
|
+
// manifest, pluginStructure, query field).
|
|
39
|
+
type readModelDef = {
|
|
40
|
+
readModel: string,
|
|
41
|
+
projections: string,
|
|
42
|
+
stream: bool,
|
|
43
|
+
}
|
|
44
|
+
type extensionPointDef = {group: option<string>, mappings: array<string>}
|
|
45
|
+
|
|
46
|
+
type resolved = {
|
|
47
|
+
stateChangeSlices: array<string>,
|
|
48
|
+
stateViewSlices: array<string>,
|
|
49
|
+
stateViewSlicesStream: array<string>,
|
|
50
|
+
automationSlices: array<string>,
|
|
51
|
+
inboundTranslationSlices: array<string>,
|
|
52
|
+
outboundTranslationSlices: array<string>,
|
|
53
|
+
// stem → declared targetName (None = not declared or explicitly None)
|
|
54
|
+
automationSliceTargets: Dict.t<option<string>>,
|
|
55
|
+
inboundTranslationSliceTargets: Dict.t<option<string>>,
|
|
56
|
+
outboundTranslationSliceTargets: Dict.t<option<string>>,
|
|
57
|
+
// StateChangeSlice stems carrying `@@reventless.async` — codegen emits
|
|
58
|
+
// `Platform.StateChangeSlice.MakeAsync` for these (CommandPending response).
|
|
59
|
+
asyncStateChangeSlices: Dict.t<bool>,
|
|
60
|
+
aggregates: array<aggregateDef>,
|
|
61
|
+
readModels: array<readModelDef>,
|
|
62
|
+
tasks: array<string>,
|
|
63
|
+
extensionPoints: array<extensionPointDef>,
|
|
64
|
+
extensions: array<string>,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Scan srcDir recursively for `<Agg>_EventMappings.res` and `<Agg>_Mappings.res`
|
|
68
|
+
// files and collect {AggName → mappingStem} dict. Supports the legacy flat
|
|
69
|
+
// layout (`srcDir/EventMappings/`), the per-entity layout
|
|
70
|
+
// (`srcDir/<Agg>/Aggregate/<Agg>_EventMappings.res`), AND the Phase-3-2
|
|
71
|
+
// post-rename form (`<Agg>_Mappings.res`). `Plugin/`, `tests/`, `lib/` are
|
|
72
|
+
// skipped. The new `_Mappings.res` form is only matched inside an
|
|
73
|
+
// `Aggregate/` (or `EventMappings/`) folder so AutomationSlice's own
|
|
74
|
+
// `_Mappings.res` siblings don't get scooped up.
|
|
75
|
+
let findEventMappings = (~srcDir: string): Dict.t<string> => {
|
|
76
|
+
let dict = Dict.make()
|
|
77
|
+
let parentDirIsAggregate = (relSegments: array<string>): bool =>
|
|
78
|
+
switch relSegments->Array.length {
|
|
79
|
+
| 0 => false
|
|
80
|
+
| n =>
|
|
81
|
+
let parent = relSegments->Array.getUnsafe(n - 1)
|
|
82
|
+
parent === "Aggregate" || parent === "EventMappings"
|
|
83
|
+
}
|
|
84
|
+
let rec walk = (dir: string, segments: array<string>) =>
|
|
85
|
+
Generator_Node.readDir(dir)->Array.forEach(entry => {
|
|
86
|
+
let name = entry->Generator_Node.direntName
|
|
87
|
+
if entry->Generator_Node.isDirectory {
|
|
88
|
+
if name !== "Plugin" && name !== "tests" && name !== "lib" {
|
|
89
|
+
walk(Generator_Node.join([dir, name]), Array.concat(segments, [name]))
|
|
90
|
+
}
|
|
91
|
+
} else if entry->Generator_Node.isFile {
|
|
92
|
+
if name->String.endsWith("_Mappings.res") && parentDirIsAggregate(segments) {
|
|
93
|
+
let stem = name->String.slice(~start=0, ~end=name->String.length - 4)
|
|
94
|
+
// "_Mappings" = 9 chars
|
|
95
|
+
let aggName = stem->String.slice(~start=0, ~end=stem->String.length - 9)
|
|
96
|
+
Dict.set(dict, aggName, stem)
|
|
97
|
+
} else if name->String.endsWith("_EventMappings.res") {
|
|
98
|
+
let stem = name->String.slice(~start=0, ~end=name->String.length - 4)
|
|
99
|
+
// "_EventMappings" = 14 chars
|
|
100
|
+
let aggName = stem->String.slice(~start=0, ~end=stem->String.length - 14)
|
|
101
|
+
// Don't overwrite a `_Mappings.res` discovered earlier.
|
|
102
|
+
switch Dict.get(dict, aggName) {
|
|
103
|
+
| Some(_) => ()
|
|
104
|
+
| None => Dict.set(dict, aggName, stem)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
if Generator_Node.existsSync(srcDir) {
|
|
110
|
+
walk(srcDir, [])
|
|
111
|
+
}
|
|
112
|
+
dict
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let sortedStems = (stems: array<string>): array<string> =>
|
|
116
|
+
stems->Array.toSorted((a, b) => if a < b {-1.0} else if a > b {1.0} else {0.0})
|
|
117
|
+
|
|
118
|
+
// Read a slice spec file and extract the value of `let targetName = ...`.
|
|
119
|
+
// Returns Some(name) for string literals (including inside Some("...")),
|
|
120
|
+
// or None for `let targetName = None` or when the line is absent.
|
|
121
|
+
let extractTargetName = (filePath: string): option<string> => {
|
|
122
|
+
try {
|
|
123
|
+
let content = Generator_Node.readFileSync(filePath)
|
|
124
|
+
let result = ref(None)
|
|
125
|
+
content->String.split("\n")->Array.forEach(line => {
|
|
126
|
+
let trimmed = line->String.trimStart
|
|
127
|
+
if trimmed->String.startsWith("let targetName = ") {
|
|
128
|
+
let firstQuote = trimmed->String.indexOf("\"")
|
|
129
|
+
let lastQuote = trimmed->String.lastIndexOf("\"")
|
|
130
|
+
if firstQuote >= 0 && lastQuote > firstQuote {
|
|
131
|
+
result := Some(trimmed->String.slice(~start=firstQuote + 1, ~end=lastQuote))
|
|
132
|
+
}
|
|
133
|
+
// `let targetName = None` → no quotes found → result stays None
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
result.contents
|
|
137
|
+
} catch {
|
|
138
|
+
| _ => None
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Returns true if the spec file declares `@@reventless.async` at the file
|
|
143
|
+
// level (Aggregate or StateChangeSlice opt-in to CommandPending response).
|
|
144
|
+
// Falls back to false on read errors so a transient FS failure can't flip
|
|
145
|
+
// component to async — the safe default is sync.
|
|
146
|
+
let hasAsyncAttribute = (filePath: string): bool => {
|
|
147
|
+
try {
|
|
148
|
+
let content = Generator_Node.readFileSync(filePath)
|
|
149
|
+
let found = ref(false)
|
|
150
|
+
content->String.split("\n")->Array.forEach(line => {
|
|
151
|
+
let trimmed = line->String.trimStart
|
|
152
|
+
// Match `@@reventless.async` and tolerate a payload arg in the future.
|
|
153
|
+
if (
|
|
154
|
+
trimmed->String.startsWith("@@reventless.async ") ||
|
|
155
|
+
trimmed->String.startsWith("@@reventless.async(") ||
|
|
156
|
+
trimmed === "@@reventless.async" ||
|
|
157
|
+
trimmed->String.startsWith("@@reventless.async\n")
|
|
158
|
+
) {
|
|
159
|
+
found := true
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
found.contents
|
|
163
|
+
} catch {
|
|
164
|
+
| _ => false
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): resolved => {
|
|
169
|
+
let eventMappings = findEventMappings(~srcDir)
|
|
170
|
+
|
|
171
|
+
let stateChangeSlices = []
|
|
172
|
+
let stateViewSlices = []
|
|
173
|
+
let stateViewSlicesStream = []
|
|
174
|
+
let automationSlices = []
|
|
175
|
+
let inboundTranslationSlices = []
|
|
176
|
+
let outboundTranslationSlices = []
|
|
177
|
+
// relPath dicts for slices that declare targetName
|
|
178
|
+
let automationSliceRelPaths: Dict.t<string> = Dict.make()
|
|
179
|
+
let inboundTranslationSliceRelPaths: Dict.t<string> = Dict.make()
|
|
180
|
+
let outboundTranslationSliceRelPaths: Dict.t<string> = Dict.make()
|
|
181
|
+
let aggregateSpecs: array<string> = []
|
|
182
|
+
let aggregateSpecRelPaths: Dict.t<string> = Dict.make()
|
|
183
|
+
let stateChangeSliceRelPaths: Dict.t<string> = Dict.make()
|
|
184
|
+
let aggregateBehaviors: array<string> = []
|
|
185
|
+
let readModelStems: array<string> = []
|
|
186
|
+
// Stream-enabled read model spec stems (the `ReadModelStream/` folder).
|
|
187
|
+
let readModelStreamStems: array<string> = []
|
|
188
|
+
// projectionsByRelPath: stem → relPath mapping for projections files (shared
|
|
189
|
+
// by ReadModel and ReadModelStream — the projection file shape is identical).
|
|
190
|
+
let projectionsByRelPath: Dict.t<string> = Dict.make()
|
|
191
|
+
let tasks = []
|
|
192
|
+
let epFiles: array<Discovery.discoveredFile> = []
|
|
193
|
+
let extensions = []
|
|
194
|
+
|
|
195
|
+
// Classify discovered files by component type. For the five slice
|
|
196
|
+
// families, impl files (X_Behavior / X_Projection / X_Automation /
|
|
197
|
+
// X_Translation) are filtered out: they're paired with their spec stems
|
|
198
|
+
// at codegen time via the `_<ImplKind>` suffix convention.
|
|
199
|
+
discovered->Array.forEach(({stem, componentType, epGroup, relPath}) => {
|
|
200
|
+
switch componentType {
|
|
201
|
+
| StateChangeSlice =>
|
|
202
|
+
if !isImplStem(stem) {
|
|
203
|
+
stateChangeSlices->Array.push(stem)
|
|
204
|
+
stateChangeSliceRelPaths->Dict.set(stem, relPath)
|
|
205
|
+
}
|
|
206
|
+
| StateViewSlice =>
|
|
207
|
+
if !isImplStem(stem) { stateViewSlices->Array.push(stem) }
|
|
208
|
+
| StateViewSliceStream =>
|
|
209
|
+
if !isImplStem(stem) { stateViewSlicesStream->Array.push(stem) }
|
|
210
|
+
| AutomationSlice =>
|
|
211
|
+
if !isImplStem(stem) {
|
|
212
|
+
automationSlices->Array.push(stem)
|
|
213
|
+
automationSliceRelPaths->Dict.set(stem, relPath)
|
|
214
|
+
}
|
|
215
|
+
| InboundTranslationSlice =>
|
|
216
|
+
if !isImplStem(stem) {
|
|
217
|
+
inboundTranslationSlices->Array.push(stem)
|
|
218
|
+
inboundTranslationSliceRelPaths->Dict.set(stem, relPath)
|
|
219
|
+
}
|
|
220
|
+
| OutboundTranslationSlice =>
|
|
221
|
+
if !isImplStem(stem) {
|
|
222
|
+
outboundTranslationSlices->Array.push(stem)
|
|
223
|
+
outboundTranslationSliceRelPaths->Dict.set(stem, relPath)
|
|
224
|
+
}
|
|
225
|
+
| Aggregate =>
|
|
226
|
+
if stem->String.endsWith("Behavior") {
|
|
227
|
+
aggregateBehaviors->Array.push(stem)
|
|
228
|
+
} else {
|
|
229
|
+
aggregateSpecs->Array.push(stem)
|
|
230
|
+
aggregateSpecRelPaths->Dict.set(stem, relPath)
|
|
231
|
+
}
|
|
232
|
+
| ReadModel =>
|
|
233
|
+
if stem->String.endsWith("Projections") {
|
|
234
|
+
Dict.set(projectionsByRelPath, stem, relPath)
|
|
235
|
+
} else {
|
|
236
|
+
readModelStems->Array.push(stem)
|
|
237
|
+
}
|
|
238
|
+
| ReadModelStream =>
|
|
239
|
+
if stem->String.endsWith("Projections") {
|
|
240
|
+
Dict.set(projectionsByRelPath, stem, relPath)
|
|
241
|
+
} else {
|
|
242
|
+
readModelStreamStems->Array.push(stem)
|
|
243
|
+
}
|
|
244
|
+
| Task => tasks->Array.push(stem)
|
|
245
|
+
| ExtensionPoint => epFiles->Array.push({stem, componentType, epGroup, relPath})
|
|
246
|
+
| Extension => extensions->Array.push(stem)
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
// ── Aggregate pairing ──────────────────────────────────────────────────────
|
|
251
|
+
// Tries the post-Phase-3.1 `<Spec>_Behavior` form first, then falls back
|
|
252
|
+
// to the legacy `<Spec>Behavior` (no underscore).
|
|
253
|
+
let aggregates =
|
|
254
|
+
aggregateSpecs
|
|
255
|
+
->sortedStems
|
|
256
|
+
->Array.filterMap(spec => {
|
|
257
|
+
let underscored = spec ++ "_Behavior"
|
|
258
|
+
let bare = spec ++ "Behavior"
|
|
259
|
+
let behaviorStem = if aggregateBehaviors->Array.includes(underscored) {
|
|
260
|
+
Some(underscored)
|
|
261
|
+
} else if aggregateBehaviors->Array.includes(bare) {
|
|
262
|
+
Some(bare)
|
|
263
|
+
} else {
|
|
264
|
+
None
|
|
265
|
+
}
|
|
266
|
+
switch behaviorStem {
|
|
267
|
+
| Some(behavior) =>
|
|
268
|
+
let isAsync = switch Dict.get(aggregateSpecRelPaths, spec) {
|
|
269
|
+
| None => false
|
|
270
|
+
| Some(relPath) => hasAsyncAttribute(Generator_Node.join([srcDir, relPath]))
|
|
271
|
+
}
|
|
272
|
+
Some({
|
|
273
|
+
spec,
|
|
274
|
+
behavior,
|
|
275
|
+
eventMappings: Dict.get(eventMappings, spec),
|
|
276
|
+
isAsync,
|
|
277
|
+
})
|
|
278
|
+
| None =>
|
|
279
|
+
Console.warn("Generator: Aggregate spec `" ++ spec ++ "` has no matching `" ++ underscored ++ "` or `" ++ bare ++ "` — skipping")
|
|
280
|
+
None
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
// ── ReadModel pairing ──────────────────────────────────────────────────────
|
|
285
|
+
// Pairs spec `<Plural>` with impl `<Plural>_Projections.res`. The legacy
|
|
286
|
+
// unpaired forms (`<Plural>ReadModel.res` / `<Plural>Projections.res`) are
|
|
287
|
+
// no longer recognised — see PR3-5 of the close-ppx-gaps-and-unify-naming
|
|
288
|
+
// plan for the migration; downstream codebases should rename to the
|
|
289
|
+
// underscored form.
|
|
290
|
+
let pairReadModel = (~stream: bool, rm: string): option<readModelDef> => {
|
|
291
|
+
// Strip optional ReadModel suffix to get the base plural name.
|
|
292
|
+
let baseName = if rm->String.endsWith("ReadModel") {
|
|
293
|
+
rm->String.slice(~start=0, ~end=rm->String.length - 9)
|
|
294
|
+
} else {
|
|
295
|
+
rm
|
|
296
|
+
}
|
|
297
|
+
let underscoredProj = baseName ++ "_Projections"
|
|
298
|
+
switch Dict.get(projectionsByRelPath, underscoredProj) {
|
|
299
|
+
| None =>
|
|
300
|
+
Console.warn("Generator: ReadModel `" ++ rm ++ "` has no matching `" ++ underscoredProj ++ ".res` — skipping")
|
|
301
|
+
None
|
|
302
|
+
| Some(_) =>
|
|
303
|
+
Some({readModel: rm, projections: underscoredProj, stream})
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
let readModels = Array.concat(
|
|
307
|
+
readModelStems->sortedStems->Array.filterMap(rm => pairReadModel(~stream=false, rm)),
|
|
308
|
+
readModelStreamStems->sortedStems->Array.filterMap(rm => pairReadModel(~stream=true, rm)),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
// ── ExtensionPoint grouping ────────────────────────────────────────────────
|
|
312
|
+
let epByGroup: Dict.t<array<string>> = Dict.make()
|
|
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
|
+
let arr = Dict.get(epByGroup, g)->Option.getOr({
|
|
320
|
+
let a: array<string> = []
|
|
321
|
+
Dict.set(epByGroup, g, a)
|
|
322
|
+
a
|
|
323
|
+
})
|
|
324
|
+
arr->Array.push(stem)
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
let extensionPoints: array<extensionPointDef> = []
|
|
329
|
+
|
|
330
|
+
if flatEpMappings->Array.length > 0 {
|
|
331
|
+
extensionPoints->Array.push({group: None, mappings: flatEpMappings->sortedStems})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
Dict.toArray(epByGroup)->Array.forEach(((group, mappings)) => {
|
|
335
|
+
extensionPoints->Array.push({group: Some(group), mappings: mappings->sortedStems})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// ── targetName extraction ──────────────────────────────────────────────────
|
|
339
|
+
let buildTargets = (stems: array<string>, relPaths: Dict.t<string>): Dict.t<option<string>> => {
|
|
340
|
+
let dict = Dict.make()
|
|
341
|
+
stems->Array.forEach(stem => {
|
|
342
|
+
let target = switch Dict.get(relPaths, stem) {
|
|
343
|
+
| None => None
|
|
344
|
+
| Some(relPath) => extractTargetName(Generator_Node.join([srcDir, relPath]))
|
|
345
|
+
}
|
|
346
|
+
dict->Dict.set(stem, target)
|
|
347
|
+
})
|
|
348
|
+
dict
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
{
|
|
352
|
+
stateChangeSlices: stateChangeSlices->sortedStems,
|
|
353
|
+
stateViewSlices: stateViewSlices->sortedStems,
|
|
354
|
+
stateViewSlicesStream: stateViewSlicesStream->sortedStems,
|
|
355
|
+
automationSlices: automationSlices->sortedStems,
|
|
356
|
+
inboundTranslationSlices: inboundTranslationSlices->sortedStems,
|
|
357
|
+
outboundTranslationSlices: outboundTranslationSlices->sortedStems,
|
|
358
|
+
automationSliceTargets: buildTargets(automationSlices, automationSliceRelPaths),
|
|
359
|
+
inboundTranslationSliceTargets: buildTargets(
|
|
360
|
+
inboundTranslationSlices,
|
|
361
|
+
inboundTranslationSliceRelPaths,
|
|
362
|
+
),
|
|
363
|
+
outboundTranslationSliceTargets: buildTargets(
|
|
364
|
+
outboundTranslationSlices,
|
|
365
|
+
outboundTranslationSliceRelPaths,
|
|
366
|
+
),
|
|
367
|
+
asyncStateChangeSlices: {
|
|
368
|
+
let d = Dict.make()
|
|
369
|
+
stateChangeSlices->Array.forEach(stem => {
|
|
370
|
+
switch Dict.get(stateChangeSliceRelPaths, stem) {
|
|
371
|
+
| None => ()
|
|
372
|
+
| Some(relPath) =>
|
|
373
|
+
if hasAsyncAttribute(Generator_Node.join([srcDir, relPath])) {
|
|
374
|
+
d->Dict.set(stem, true)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
d
|
|
379
|
+
},
|
|
380
|
+
aggregates,
|
|
381
|
+
readModels,
|
|
382
|
+
tasks: tasks->sortedStems,
|
|
383
|
+
extensionPoints,
|
|
384
|
+
extensions: extensions->sortedStems,
|
|
385
|
+
}
|
|
386
|
+
}
|