@reventlessdev/reventless-spec 3.0.0-alpha.63 → 3.0.0-alpha.64

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.
@@ -165,6 +165,38 @@ let hasAsyncAttribute = (filePath: string): bool => {
165
165
  }
166
166
  }
167
167
 
168
+ // Partition the ExtensionPoint mapping files into flat (ungrouped) and grouped
169
+ // `extensionPointDef`s: every mapping sharing an `epGroup` collects into one
170
+ // grouped entry; the ungrouped ones share a single flat (`group: None`) entry.
171
+ // Pure (no I/O) so the grouping is unit-testable — in particular the A8 fix:
172
+ // `Option.getOr`'s eager default previously re-created the group's array every
173
+ // iteration, so a group with ≥2 mappings collapsed to `[]` and the generator
174
+ // emitted a `Plugin.res` referencing a never-generated module.
175
+ let groupExtensionPoints = (epFiles: array<Discovery.discoveredFile>): array<extensionPointDef> => {
176
+ let epByGroup: Dict.t<array<string>> = Dict.make()
177
+ let flatEpMappings: array<string> = []
178
+
179
+ epFiles->Array.forEach(({stem, epGroup}) =>
180
+ switch epGroup {
181
+ | None => flatEpMappings->Array.push(stem)
182
+ | Some(g) =>
183
+ switch Dict.get(epByGroup, g) {
184
+ | Some(arr) => arr->Array.push(stem)
185
+ | None => Dict.set(epByGroup, g, [stem])
186
+ }
187
+ }
188
+ )
189
+
190
+ let extensionPoints: array<extensionPointDef> = []
191
+ if flatEpMappings->Array.length > 0 {
192
+ extensionPoints->Array.push({group: None, mappings: flatEpMappings->sortedStems})
193
+ }
194
+ Dict.toArray(epByGroup)->Array.forEach(((group, mappings)) =>
195
+ extensionPoints->Array.push({group: Some(group), mappings: mappings->sortedStems})
196
+ )
197
+ extensionPoints
198
+ }
199
+
168
200
  let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): resolved => {
169
201
  let eventMappings = findEventMappings(~srcDir)
170
202
 
@@ -309,35 +341,7 @@ let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): re
309
341
  )
310
342
 
311
343
  // ── 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
- // `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
- })
344
+ let extensionPoints = groupExtensionPoints(epFiles)
341
345
 
342
346
  // ── targetName extraction ──────────────────────────────────────────────────
343
347
  let buildTargets = (stems: array<string>, relPaths: Dict.t<string>): Dict.t<option<string>> => {
@@ -131,6 +131,39 @@ function hasAsyncAttribute(filePath) {
131
131
  }
132
132
  }
133
133
 
134
+ function groupExtensionPoints(epFiles) {
135
+ let epByGroup = {};
136
+ let flatEpMappings = [];
137
+ epFiles.forEach(param => {
138
+ let epGroup = param.epGroup;
139
+ let stem = param.stem;
140
+ if (epGroup !== undefined) {
141
+ let arr = epByGroup[epGroup];
142
+ if (arr !== undefined) {
143
+ arr.push(stem);
144
+ } else {
145
+ epByGroup[epGroup] = [stem];
146
+ }
147
+ return;
148
+ }
149
+ flatEpMappings.push(stem);
150
+ });
151
+ let extensionPoints = [];
152
+ if (flatEpMappings.length !== 0) {
153
+ extensionPoints.push({
154
+ group: undefined,
155
+ mappings: sortedStems(flatEpMappings)
156
+ });
157
+ }
158
+ Object.entries(epByGroup).forEach(param => {
159
+ extensionPoints.push({
160
+ group: param[0],
161
+ mappings: sortedStems(param[1])
162
+ });
163
+ });
164
+ return extensionPoints;
165
+ }
166
+
134
167
  function resolve(discovered, srcDir) {
135
168
  let eventMappings = findEventMappings(srcDir);
136
169
  let stateChangeSlices = [];
@@ -275,35 +308,7 @@ function resolve(discovered, srcDir) {
275
308
  }
276
309
  };
277
310
  let readModels = Stdlib_Array.filterMap(sortedStems(readModelStems), rm => pairReadModel(false, rm)).concat(Stdlib_Array.filterMap(sortedStems(readModelStreamStems), rm => pairReadModel(true, rm)));
278
- let epByGroup = {};
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
- });
311
+ let extensionPoints = groupExtensionPoints(epFiles);
307
312
  let buildTargets = (stems, relPaths) => {
308
313
  let dict = {};
309
314
  stems.forEach(stem => {
@@ -350,6 +355,7 @@ export {
350
355
  sortedStems,
351
356
  extractTargetName,
352
357
  hasAsyncAttribute,
358
+ groupExtensionPoints,
353
359
  resolve,
354
360
  }
355
361
  /* node:fs Not a pure module */
@@ -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