@reventlessdev/reventless-spec 3.0.0-alpha.64 → 3.0.0-alpha.66
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 +15 -0
- package/package.json +1 -1
- package/src/components/DcbTag.res +7 -0
- package/src/components/DcbTag.res.mjs +23 -0
- package/src/generator/Codegen.res +10 -0
- package/src/generator/Codegen.res.mjs +2 -1
- package/src/generator/Pairing.res +78 -12
- package/src/generator/Pairing.res.mjs +58 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.66 (2026-07-05)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **spec:** republish DcbTag to ship [@schema-generated](https://github.com/schema-generated) derivedPartitionTagSchema ([c110c26](https://github.com/ReventlessDev/reventless-core/commit/c110c26ba54617a8455e205ed77aeba5212f6170))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.65 (2026-07-05)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* @[@reventless](https://github.com/reventless).systemCallable slice opt-in for deploy-time system callers ([c5ed537](https://github.com/ReventlessDev/reventless-core/commit/c5ed537309f8e4b7d4a4d4af1ed1ec83d060aea3))
|
|
18
|
+
* **reventless-aws:** Postgres change-feed relay auto-wiring (B2.3d) ([62b430f](https://github.com/ReventlessDev/reventless-core/commit/62b430f1e6d8ec1f172de0cca324f7f26aaf5fcb))
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 3.0.0-alpha.64 (2026-07-03)
|
|
7
22
|
|
|
8
23
|
**Note:** Version bump only for package @reventlessdev/reventless-spec
|
package/package.json
CHANGED
|
@@ -23,6 +23,7 @@ type tag = {key: string, value: string}
|
|
|
23
23
|
Identifies which tag field is used as the storage partition key.
|
|
24
24
|
The partition key determines how events are distributed across DynamoDB partitions.
|
|
25
25
|
*/
|
|
26
|
+
@schema
|
|
26
27
|
type partitionTag = {key: string}
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -238,12 +239,18 @@ let isCompositePartitionMember = (fieldSchema: S.t<unknown>): bool =>
|
|
|
238
239
|
Composite partition key specification — the ordered field names and inter-field separators.
|
|
239
240
|
`seps[i]` is placed between `keys[i]` and `keys[i+1]`; length is always `keys.length - 1`.
|
|
240
241
|
*/
|
|
242
|
+
@schema
|
|
241
243
|
type compositePartitionSpec = {
|
|
242
244
|
keys: array<string>,
|
|
243
245
|
seps: array<string>,
|
|
244
246
|
}
|
|
245
247
|
|
|
248
|
+
// Republish note: alpha.65 shipped a stale compiled interface that omitted the
|
|
249
|
+
// @schema-generated `derivedPartitionTagSchema`, breaking downstream
|
|
250
|
+
// `Reventless.DcbTag.derivedPartitionTagSchema` references (e.g. reventless-aws
|
|
251
|
+
// PgChangeFeedRelay). This forces a clean rebuild + republish.
|
|
246
252
|
/** Union of simple and composite partition tag strategies. */
|
|
253
|
+
@schema
|
|
247
254
|
type derivedPartitionTag =
|
|
248
255
|
| Simple(partitionTag)
|
|
249
256
|
| Composite(compositePartitionSpec)
|
|
@@ -14,6 +14,10 @@ let tagSchema = S.schema(s => ({
|
|
|
14
14
|
value: s.m(S.string)
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
|
+
let partitionTagSchema = S.schema(s => ({
|
|
18
|
+
key: s.m(S.string)
|
|
19
|
+
}));
|
|
20
|
+
|
|
17
21
|
let dcbTagId = S.Metadata.Id.make("dcb", "tag");
|
|
18
22
|
|
|
19
23
|
let dcbPartitionTagId = S.Metadata.Id.make("dcb", "partitionTag");
|
|
@@ -48,6 +52,22 @@ function isCompositePartitionMember(fieldSchema) {
|
|
|
48
52
|
return Stdlib_Option.isSome(S.Metadata.get(fieldSchema, dcbCompositePartitionMemberId));
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
let compositePartitionSpecSchema = S.schema(s => ({
|
|
56
|
+
keys: s.m(S.array(S.string)),
|
|
57
|
+
seps: s.m(S.array(S.string))
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
let derivedPartitionTagSchema = S.union([
|
|
61
|
+
S.schema(s => ({
|
|
62
|
+
TAG: "Simple",
|
|
63
|
+
_0: s.m(partitionTagSchema)
|
|
64
|
+
})),
|
|
65
|
+
S.schema(s => ({
|
|
66
|
+
TAG: "Composite",
|
|
67
|
+
_0: s.m(compositePartitionSpecSchema)
|
|
68
|
+
}))
|
|
69
|
+
]);
|
|
70
|
+
|
|
51
71
|
function isTagged(fieldSchema) {
|
|
52
72
|
return Stdlib_Option.isSome(S.Metadata.get(fieldSchema, dcbTagId));
|
|
53
73
|
}
|
|
@@ -800,6 +820,7 @@ function getPartitionTagValue(query, pt) {
|
|
|
800
820
|
|
|
801
821
|
export {
|
|
802
822
|
tagSchema,
|
|
823
|
+
partitionTagSchema,
|
|
803
824
|
dcbTagId,
|
|
804
825
|
dcbPartitionTagId,
|
|
805
826
|
dcbCrossPartitionId,
|
|
@@ -812,6 +833,8 @@ export {
|
|
|
812
833
|
crossPartition,
|
|
813
834
|
compositePartitionMember,
|
|
814
835
|
isCompositePartitionMember,
|
|
836
|
+
compositePartitionSpecSchema,
|
|
837
|
+
derivedPartitionTagSchema,
|
|
815
838
|
isTagged,
|
|
816
839
|
isTaggedArray,
|
|
817
840
|
isPartitionTag,
|
|
@@ -672,6 +672,16 @@ let renderComposition = (~config: Config.config, ~resolved: Pairing.resolved): s
|
|
|
672
672
|
~moduleSuffix="Slice",
|
|
673
673
|
),
|
|
674
674
|
hasPluginStructure ? Some(" ~pluginStructure=pluginStructure,") : None,
|
|
675
|
+
// Components carrying `@@reventless.systemCallable` — only emitted when at
|
|
676
|
+
// least one opts in, so existing generated Plugin.res files stay identical.
|
|
677
|
+
resolved.systemCallableComponents->Array.length > 0
|
|
678
|
+
? Some(
|
|
679
|
+
" ~systemCallableComponents=[" ++
|
|
680
|
+
resolved.systemCallableComponents
|
|
681
|
+
->Array.map(n => "\"" ++ n ++ "\"")
|
|
682
|
+
->Array.join(", ") ++ "],",
|
|
683
|
+
)
|
|
684
|
+
: None,
|
|
675
685
|
]
|
|
676
686
|
|
|
677
687
|
makeParams->Array.filterMap(x => x)->Array.forEach(line => lines->Array.push(line))
|
|
@@ -428,7 +428,8 @@ function renderComposition(config, resolved) {
|
|
|
428
428
|
renderMakeParam("automationSlices", resolved.automationSlices, "Slice"),
|
|
429
429
|
renderMakeParam("outboundTranslationSlices", resolved.outboundTranslationSlices, "Slice"),
|
|
430
430
|
renderMakeParam("inboundTranslationSlices", resolved.inboundTranslationSlices, "Slice"),
|
|
431
|
-
hasPluginStructure ? " ~pluginStructure=pluginStructure," : undefined
|
|
431
|
+
hasPluginStructure ? " ~pluginStructure=pluginStructure," : undefined,
|
|
432
|
+
resolved.systemCallableComponents.length !== 0 ? " ~systemCallableComponents=[" + resolved.systemCallableComponents.map(n => "\"" + n + "\"").join(", ") + "]," : undefined
|
|
432
433
|
];
|
|
433
434
|
Stdlib_Array.filterMap(makeParams, x => x).forEach(line => {
|
|
434
435
|
lines.push(line);
|
|
@@ -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,41 @@ 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
|
+
|
|
168
208
|
// Partition the ExtensionPoint mapping files into flat (ungrouped) and grouped
|
|
169
209
|
// `extensionPointDef`s: every mapping sharing an `epGroup` collects into one
|
|
170
210
|
// grouped entry; the ungrouped ones share a single flat (`group: None`) entry.
|
|
@@ -213,6 +253,8 @@ let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): re
|
|
|
213
253
|
let aggregateSpecs: array<string> = []
|
|
214
254
|
let aggregateSpecRelPaths: Dict.t<string> = Dict.make()
|
|
215
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()
|
|
216
258
|
let aggregateBehaviors: array<string> = []
|
|
217
259
|
let readModelStems: array<string> = []
|
|
218
260
|
// Stream-enabled read model spec stems (the `ReadModelStream/` folder).
|
|
@@ -236,9 +278,15 @@ let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): re
|
|
|
236
278
|
stateChangeSliceRelPaths->Dict.set(stem, relPath)
|
|
237
279
|
}
|
|
238
280
|
| StateViewSlice =>
|
|
239
|
-
if !isImplStem(stem) {
|
|
281
|
+
if !isImplStem(stem) {
|
|
282
|
+
stateViewSlices->Array.push(stem)
|
|
283
|
+
stateViewSliceRelPaths->Dict.set(stem, relPath)
|
|
284
|
+
}
|
|
240
285
|
| StateViewSliceStream =>
|
|
241
|
-
if !isImplStem(stem) {
|
|
286
|
+
if !isImplStem(stem) {
|
|
287
|
+
stateViewSlicesStream->Array.push(stem)
|
|
288
|
+
stateViewSliceRelPaths->Dict.set(stem, relPath)
|
|
289
|
+
}
|
|
242
290
|
| AutomationSlice =>
|
|
243
291
|
if !isImplStem(stem) {
|
|
244
292
|
automationSlices->Array.push(stem)
|
|
@@ -385,6 +433,24 @@ let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): re
|
|
|
385
433
|
})
|
|
386
434
|
d
|
|
387
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
|
+
},
|
|
388
454
|
aggregates,
|
|
389
455
|
readModels,
|
|
390
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,38 @@ 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
|
+
|
|
134
167
|
function groupExtensionPoints(epFiles) {
|
|
135
168
|
let epByGroup = {};
|
|
136
169
|
let flatEpMappings = [];
|
|
@@ -178,6 +211,7 @@ function resolve(discovered, srcDir) {
|
|
|
178
211
|
let aggregateSpecs = [];
|
|
179
212
|
let aggregateSpecRelPaths = {};
|
|
180
213
|
let stateChangeSliceRelPaths = {};
|
|
214
|
+
let stateViewSliceRelPaths = {};
|
|
181
215
|
let aggregateBehaviors = [];
|
|
182
216
|
let readModelStems = [];
|
|
183
217
|
let readModelStreamStems = [];
|
|
@@ -201,6 +235,7 @@ function resolve(discovered, srcDir) {
|
|
|
201
235
|
case "StateViewSlice" :
|
|
202
236
|
if (!isImplStem(stem)) {
|
|
203
237
|
stateViewSlices.push(stem);
|
|
238
|
+
stateViewSliceRelPaths[stem] = relPath;
|
|
204
239
|
return;
|
|
205
240
|
} else {
|
|
206
241
|
return;
|
|
@@ -208,6 +243,7 @@ function resolve(discovered, srcDir) {
|
|
|
208
243
|
case "StateViewSliceStream" :
|
|
209
244
|
if (!isImplStem(stem)) {
|
|
210
245
|
stateViewSlicesStream.push(stem);
|
|
246
|
+
stateViewSliceRelPaths[stem] = relPath;
|
|
211
247
|
return;
|
|
212
248
|
} else {
|
|
213
249
|
return;
|
|
@@ -282,7 +318,7 @@ function resolve(discovered, srcDir) {
|
|
|
282
318
|
);
|
|
283
319
|
if (behaviorStem !== undefined) {
|
|
284
320
|
let relPath = aggregateSpecRelPaths[spec];
|
|
285
|
-
let isAsync = relPath !== undefined ?
|
|
321
|
+
let isAsync = relPath !== undefined ? hasFileAttribute(Nodepath.join(srcDir, relPath), "@@reventless.async") : false;
|
|
286
322
|
return {
|
|
287
323
|
spec: spec,
|
|
288
324
|
behavior: behaviorStem,
|
|
@@ -319,6 +355,20 @@ function resolve(discovered, srcDir) {
|
|
|
319
355
|
return dict;
|
|
320
356
|
};
|
|
321
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
|
+
};
|
|
322
372
|
return {
|
|
323
373
|
stateChangeSlices: sortedStems(stateChangeSlices),
|
|
324
374
|
stateViewSlices: sortedStems(stateViewSlices),
|
|
@@ -331,11 +381,12 @@ function resolve(discovered, srcDir) {
|
|
|
331
381
|
outboundTranslationSliceTargets: buildTargets(outboundTranslationSlices, outboundTranslationSliceRelPaths),
|
|
332
382
|
asyncStateChangeSlices: (stateChangeSlices.forEach(stem => {
|
|
333
383
|
let relPath = stateChangeSliceRelPaths[stem];
|
|
334
|
-
if (relPath !== undefined &&
|
|
384
|
+
if (relPath !== undefined && hasFileAttribute(Nodepath.join(srcDir, relPath), "@@reventless.async")) {
|
|
335
385
|
d[stem] = true;
|
|
336
386
|
return;
|
|
337
387
|
}
|
|
338
388
|
}), d),
|
|
389
|
+
systemCallableComponents: (collect(stateChangeSlices, stateChangeSliceRelPaths), collect(stateViewSlices, stateViewSliceRelPaths), collect(stateViewSlicesStream, stateViewSliceRelPaths), sortedStems(names)),
|
|
339
390
|
aggregates: aggregates,
|
|
340
391
|
readModels: readModels,
|
|
341
392
|
tasks: sortedStems(tasks),
|
|
@@ -354,7 +405,10 @@ export {
|
|
|
354
405
|
findEventMappings,
|
|
355
406
|
sortedStems,
|
|
356
407
|
extractTargetName,
|
|
408
|
+
hasFileAttribute,
|
|
357
409
|
hasAsyncAttribute,
|
|
410
|
+
hasSystemCallableAttribute,
|
|
411
|
+
effectiveSpecName,
|
|
358
412
|
groupExtensionPoints,
|
|
359
413
|
resolve,
|
|
360
414
|
}
|