@reventlessdev/reventless-spec 3.0.0-alpha.133 → 3.0.0-alpha.135
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 +33 -0
- package/package.json +4 -3
- package/run-check-lifecycle.mjs +2 -1
- package/run-prepare-accounts.mjs +3 -0
- package/src/AnsiStyle.res +0 -1
- package/src/LogPrefix.res +4 -13
- package/src/components/AutomationSlice.res +1 -5
- package/src/components/CapabilityManifest.res +6 -3
- package/src/components/CapabilityManifest.res.mjs +13 -2
- package/src/components/DcbDecode.res +6 -2
- package/src/components/DcbScopeInference.res +16 -12
- package/src/components/DcbTag.res +74 -46
- package/src/components/DcbValidation.res +114 -89
- package/src/components/DisplayName.res +4 -2
- package/src/components/ExtensionPoint.res +0 -1
- package/src/components/FieldDefault.res +2 -4
- package/src/components/InboundTranslationSlice.res +0 -2
- package/src/components/OutboundTranslationSlice.res +10 -8
- package/src/components/Owner.res +4 -4
- package/src/components/Plugin.res +10 -4
- package/src/components/ReadModel.res +0 -1
- package/src/components/Reference.res +2 -8
- package/src/components/Sensitive.res +4 -4
- package/src/components/StateAnnotations.res +4 -2
- package/src/components/StateChangeSlice.res +0 -2
- package/src/components/StateViewSlice.res +0 -2
- package/src/components/TaggedUnion.res +1 -2
- package/src/components/Task.res +0 -1
- package/src/components/TraitCertificate.res +2 -2
- package/src/components/TraitManifest.res +1 -1
- package/src/generator/CertifyTrait.res +2 -4
- package/src/generator/Codegen.res +77 -68
- package/src/generator/Discovery.res +29 -12
- package/src/generator/GraftTrait.res +17 -9
- package/src/generator/Pairing.res +39 -13
- package/src/generator/PlatformCodegen.res +14 -11
- package/src/generator/PlatformCodegen.res.mjs +5 -0
- package/src/generator/PlatformManifests.res +1 -3
- package/src/generator/PluginGenerator.res +0 -1
- package/src/generator/TraitManifestCli.res +7 -7
- package/src/lifecycle/CheckLifecycleModel.res +206 -148
- package/src/lifecycle/CheckLifecycleModel.res.mjs +23 -5
- package/src/semantic/Bytes.res +0 -1
- package/src/semantic/CalendarDate.res +2 -2
- package/src/semantic/Capabilities.res +21 -6
- package/src/semantic/Capabilities.res.mjs +18 -12
- package/src/semantic/CapabilityNeed.res +16 -5
- package/src/semantic/CapabilityNeed.res.mjs +9 -4
- package/src/semantic/CaptionedImage.res +0 -1
- package/src/semantic/Color.res +0 -1
- package/src/semantic/Currency.res +189 -170
- package/src/semantic/DateRange.res +3 -4
- package/src/semantic/DateTime.res +5 -5
- package/src/semantic/Duration.res +2 -2
- package/src/semantic/Email.res +0 -1
- package/src/semantic/FileRef.res +2 -2
- package/src/semantic/GeoPoint.res +17 -21
- package/src/semantic/Geocoding.res +8 -9
- package/src/semantic/Geolocation.res +6 -9
- package/src/semantic/IdentityProvider.res +127 -0
- package/src/semantic/IdentityProvider.res.mjs +65 -0
- package/src/semantic/ImageRef.res +2 -2
- package/src/semantic/MemberRef.res +0 -1
- package/src/semantic/Messaging.res +141 -21
- package/src/semantic/Messaging.res.mjs +51 -8
- package/src/semantic/Money.res +23 -20
- package/src/semantic/Offload.res +6 -4
- package/src/semantic/Percent.res +3 -3
- package/src/semantic/Phone.res +0 -1
- package/src/semantic/RowImage.res +4 -3
- package/src/semantic/Secrets.res +76 -0
- package/src/semantic/Secrets.res.mjs +49 -0
- package/src/semantic/Semantic.res +8 -11
- package/src/semantic/StorageRef.res +17 -17
- package/src/semantic/Template.res +5 -7
- package/src/semantic/UploadableFile.res +0 -1
- package/src/semantic/UploadableImage.res +0 -1
- package/src/semantic/Url.res +3 -3
- package/src/types/AccountsManifest.res +281 -0
- package/src/types/AccountsManifest.res.mjs +311 -0
- package/src/types/AdminGroup.res +27 -0
- package/src/types/AdminGroup.res.mjs +9 -0
- package/src/types/Authorization.res +1 -2
- package/src/types/Handler.res +0 -1
- package/src/types/Identity.res +1 -3
- package/src/types/Lifecycle.res +7 -6
- package/src/types/Message.res +6 -4
- package/src/types/OwnerScope.res +28 -3
- package/src/types/OwnerScope.res.mjs +12 -0
- package/src/types/PrepareAccounts.res +119 -0
- package/src/types/PrepareAccounts.res.mjs +148 -0
- package/src/types/Projection.res +9 -2
- package/src/types/StoredEvent.res +2 -4
- package/src/types/Transition.res +8 -4
- package/src/util/Util_Password.res +60 -0
- package/src/util/Util_Password.res.mjs +53 -0
- package/src/util/Util_Sury.res +1 -4
|
@@ -20,40 +20,35 @@ let renderSlices = (
|
|
|
20
20
|
stems: array<string>,
|
|
21
21
|
): array<string> =>
|
|
22
22
|
stems->Array.map(stem =>
|
|
23
|
-
" module "
|
|
24
|
-
++
|
|
25
|
-
++
|
|
26
|
-
|
|
27
|
-
++
|
|
28
|
-
|
|
29
|
-
++
|
|
30
|
-
|
|
31
|
-
++
|
|
32
|
-
++
|
|
33
|
-
++ ")"
|
|
23
|
+
" module " ++
|
|
24
|
+
stem ++
|
|
25
|
+
suffix ++
|
|
26
|
+
" = Platform." ++
|
|
27
|
+
platformFactory ++
|
|
28
|
+
".Make(" ++
|
|
29
|
+
stem ++
|
|
30
|
+
", " ++
|
|
31
|
+
stem ++
|
|
32
|
+
implSuffix ++ ")"
|
|
34
33
|
)
|
|
35
34
|
|
|
36
35
|
// StateChangeSlices opt into async dispatch via `@@reventless.async` on the
|
|
37
36
|
// spec file; the rendered factory becomes MakeAsync (CommandPending response).
|
|
38
|
-
let renderStateChangeSlices = (
|
|
39
|
-
stems: array<string>,
|
|
40
|
-
asyncStems: Dict.t<bool>,
|
|
41
|
-
): array<string> =>
|
|
37
|
+
let renderStateChangeSlices = (stems: array<string>, asyncStems: Dict.t<bool>): array<string> =>
|
|
42
38
|
stems->Array.map(stem => {
|
|
43
39
|
let factory = switch Dict.get(asyncStems, stem) {
|
|
44
40
|
| Some(true) => "MakeAsync"
|
|
45
41
|
| _ => "Make"
|
|
46
42
|
}
|
|
47
|
-
" module "
|
|
48
|
-
++
|
|
49
|
-
|
|
50
|
-
++
|
|
51
|
-
|
|
52
|
-
++
|
|
53
|
-
|
|
54
|
-
++
|
|
55
|
-
|
|
56
|
-
++ ")"
|
|
43
|
+
" module " ++
|
|
44
|
+
stem ++
|
|
45
|
+
"Slice = Platform.StateChangeSlice." ++
|
|
46
|
+
factory ++
|
|
47
|
+
"(" ++
|
|
48
|
+
stem ++
|
|
49
|
+
", " ++
|
|
50
|
+
stem ++
|
|
51
|
+
Pairing.implSuffixForStateChange ++ ")"
|
|
57
52
|
})
|
|
58
53
|
|
|
59
54
|
// AutomationSlice — 2-arg form. The merged `_Automation.res` shape exposes
|
|
@@ -63,13 +58,12 @@ let renderStateChangeSlices = (
|
|
|
63
58
|
// from the sibling `_Mappings.res`.
|
|
64
59
|
let renderAutomationSlices = (stems: array<string>): array<string> =>
|
|
65
60
|
stems->Array.map(stem =>
|
|
66
|
-
" module "
|
|
67
|
-
++
|
|
68
|
-
|
|
69
|
-
++
|
|
70
|
-
|
|
71
|
-
++
|
|
72
|
-
++ "_Automation)"
|
|
61
|
+
" module " ++
|
|
62
|
+
stem ++
|
|
63
|
+
"Slice = Platform.AutomationSlice.Make(" ++
|
|
64
|
+
stem ++
|
|
65
|
+
", " ++
|
|
66
|
+
stem ++ "_Automation)"
|
|
73
67
|
)
|
|
74
68
|
|
|
75
69
|
let renderAggregates = (aggregates: array<Pairing.aggregateDef>): array<string> =>
|
|
@@ -99,14 +93,19 @@ let renderReadModels = (readModels: array<Pairing.readModelDef>): array<string>
|
|
|
99
93
|
// platform wires a StateTopic Lambda for AppSync Events (Source B) live updates.
|
|
100
94
|
let factory = stream ? "Platform.ReadModelStream.Make" : "Platform.ReadModel.Make"
|
|
101
95
|
[
|
|
102
|
-
" module " ++
|
|
96
|
+
" module " ++
|
|
97
|
+
wrapperName ++
|
|
98
|
+
" = " ++
|
|
99
|
+
factory ++
|
|
100
|
+
"(" ++
|
|
101
|
+
readModel ++
|
|
102
|
+
", " ++
|
|
103
|
+
projections ++ ")",
|
|
103
104
|
]
|
|
104
105
|
})
|
|
105
106
|
|
|
106
107
|
let renderTasks = (tasks: array<string>): array<string> =>
|
|
107
|
-
tasks->Array.map(stem =>
|
|
108
|
-
" module " ++ stem ++ "Task = Platform.Task.Make(" ++ stem ++ ")"
|
|
109
|
-
)
|
|
108
|
+
tasks->Array.map(stem => " module " ++ stem ++ "Task = Platform.Task.Make(" ++ stem ++ ")")
|
|
110
109
|
|
|
111
110
|
let renderExtensionPoints = (extensionPoints: array<Pairing.extensionPointDef>): array<string> =>
|
|
112
111
|
extensionPoints->Array.flatMap(({group, mappings}) => {
|
|
@@ -118,8 +117,7 @@ let renderExtensionPoints = (extensionPoints: array<Pairing.extensionPointDef>):
|
|
|
118
117
|
}
|
|
119
118
|
switch count {
|
|
120
119
|
| 0 => []
|
|
121
|
-
| 1 =>
|
|
122
|
-
[" module " ++ moduleName ++ " = Platform.ExtensionPoint.Make(" ++ firstMapping ++ ")"]
|
|
120
|
+
| 1 => [" module " ++ moduleName ++ " = Platform.ExtensionPoint.Make(" ++ firstMapping ++ ")"]
|
|
123
121
|
| 2 =>
|
|
124
122
|
let m2 = mappings->Array.getUnsafe(1)
|
|
125
123
|
[
|
|
@@ -140,8 +138,7 @@ let renderExtensionPoints = (extensionPoints: array<Pairing.extensionPointDef>):
|
|
|
140
138
|
]
|
|
141
139
|
| _ =>
|
|
142
140
|
// MakeMulti — inline module expression
|
|
143
|
-
let mappingLines =
|
|
144
|
-
mappings->Array.map(m => " module(" ++ m ++ "),")
|
|
141
|
+
let mappingLines = mappings->Array.map(m => " module(" ++ m ++ "),")
|
|
145
142
|
Array.flat([
|
|
146
143
|
[
|
|
147
144
|
" module " ++ moduleName ++ " = Platform.ExtensionPoint.MakeMulti({",
|
|
@@ -152,10 +149,7 @@ let renderExtensionPoints = (extensionPoints: array<Pairing.extensionPointDef>):
|
|
|
152
149
|
" let mappings: array<module(Mapping)> = [",
|
|
153
150
|
],
|
|
154
151
|
mappingLines,
|
|
155
|
-
[
|
|
156
|
-
" ]",
|
|
157
|
-
" })",
|
|
158
|
-
],
|
|
152
|
+
[" ]", " })"],
|
|
159
153
|
])
|
|
160
154
|
}
|
|
161
155
|
})
|
|
@@ -172,11 +166,9 @@ let renderExtensions = (extensions: array<string>): array<string> =>
|
|
|
172
166
|
|
|
173
167
|
// ── make() call ──────────────────────────────────────────────────────────────
|
|
174
168
|
|
|
175
|
-
let renderMakeParam = (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
~moduleSuffix: string,
|
|
179
|
-
): option<string> =>
|
|
169
|
+
let renderMakeParam = (~param: string, ~items: array<string>, ~moduleSuffix: string): option<
|
|
170
|
+
string,
|
|
171
|
+
> =>
|
|
180
172
|
if items->Array.length === 0 {
|
|
181
173
|
None
|
|
182
174
|
} else {
|
|
@@ -184,10 +176,9 @@ let renderMakeParam = (
|
|
|
184
176
|
Some(" ~" ++ param ++ "=[" ++ entries->Array.join(", ") ++ "],")
|
|
185
177
|
}
|
|
186
178
|
|
|
187
|
-
let renderStateViewSlicesMakeParam = (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
): option<string> => {
|
|
179
|
+
let renderStateViewSlicesMakeParam = (slices: array<string>, streamSlices: array<string>): option<
|
|
180
|
+
string,
|
|
181
|
+
> => {
|
|
191
182
|
let entries = Array.flat([
|
|
192
183
|
slices->Array.map(s => "module(" ++ s ++ "Slice)"),
|
|
193
184
|
streamSlices->Array.map(s => "module(" ++ s ++ "StreamSlice)"),
|
|
@@ -314,9 +305,7 @@ let validateComponentRuntimeKeys = (
|
|
|
314
305
|
)
|
|
315
306
|
}
|
|
316
307
|
|
|
317
|
-
let renderComponentRuntimeParam = (
|
|
318
|
-
componentRuntime: dict<Config.runtimeHints>,
|
|
319
|
-
): option<string> => {
|
|
308
|
+
let renderComponentRuntimeParam = (componentRuntime: dict<Config.runtimeHints>): option<string> => {
|
|
320
309
|
let entries = componentRuntime->Dict.toArray
|
|
321
310
|
if entries->Array.length === 0 {
|
|
322
311
|
None
|
|
@@ -419,6 +408,7 @@ let renderPluginStructureCall = (
|
|
|
419
408
|
let entries = epMappingStems->Array.map(s => "module(" ++ s ++ ")")
|
|
420
409
|
ls->Array.push(" ~extensionPoints=[" ++ entries->Array.join(", ") ++ "],")
|
|
421
410
|
}
|
|
411
|
+
|
|
422
412
|
// Chapter grouping bands per component, captured from the source folder layout.
|
|
423
413
|
// Only emitted when at least one component lives under a chapter folder, so
|
|
424
414
|
// plugins with a flat `src/` keep a byte-identical generated Plugin.res.
|
|
@@ -427,8 +417,11 @@ let renderPluginStructureCall = (
|
|
|
427
417
|
componentChapters->Array.map(((stem, chapter)) =>
|
|
428
418
|
"(\"" ++ stem ++ "\", \"" ++ chapter ++ "\")"
|
|
429
419
|
)
|
|
430
|
-
ls->Array.push(
|
|
420
|
+
ls->Array.push(
|
|
421
|
+
" ~componentChapters=Dict.fromArray([" ++ entries->Array.join(", ") ++ "]),",
|
|
422
|
+
)
|
|
431
423
|
}
|
|
424
|
+
|
|
432
425
|
// What the plugin's own scenarios say about each command's lifecycle edge.
|
|
433
426
|
// Written by `check:lifecycle:update`, not here: the sidecars it harvests are
|
|
434
427
|
// produced by the build this generator runs *before*, so a generator-written
|
|
@@ -463,7 +456,18 @@ let validateUniqueSpecStems = (~discovered: array<Discovery.discoveredFile>) =>
|
|
|
463
456
|
->Dict.toArray
|
|
464
457
|
->Array.forEach(((stem, paths)) => {
|
|
465
458
|
if paths->Array.length > 1 {
|
|
466
|
-
let pathList =
|
|
459
|
+
let pathList =
|
|
460
|
+
paths
|
|
461
|
+
->Array.toSorted((a, b) =>
|
|
462
|
+
if a < b {
|
|
463
|
+
-1.0
|
|
464
|
+
} else if a > b {
|
|
465
|
+
1.0
|
|
466
|
+
} else {
|
|
467
|
+
0.0
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
->Array.join("\n - ")
|
|
467
471
|
JsError.throwWithMessage(
|
|
468
472
|
"Generator: stem `" ++
|
|
469
473
|
stem ++
|
|
@@ -493,7 +497,7 @@ let validateSliceTargets = (~resolved: Pairing.resolved) => {
|
|
|
493
497
|
"` is missing `let targetName = \"...\"`. Valid targets: " ++
|
|
494
498
|
knownReceivers->Array.join(", "),
|
|
495
499
|
)
|
|
496
|
-
| Some(t)
|
|
500
|
+
| Some(t) if !(knownReceivers->Array.includes(t)) =>
|
|
497
501
|
JsError.throwWithMessage(
|
|
498
502
|
"Generator: " ++
|
|
499
503
|
kind ++
|
|
@@ -526,7 +530,7 @@ let validateSliceTargets = (~resolved: Pairing.resolved) => {
|
|
|
526
530
|
// OutboundTranslationSlice: only validate when targetName = Some(name) (None = fire-and-forget)
|
|
527
531
|
resolved.outboundTranslationSlices->Array.forEach(stem => {
|
|
528
532
|
switch resolved.outboundTranslationSliceTargets->Dict.get(stem)->Option.getOr(None) {
|
|
529
|
-
| Some(t)
|
|
533
|
+
| Some(t) if !(knownReceivers->Array.includes(t)) =>
|
|
530
534
|
JsError.throwWithMessage(
|
|
531
535
|
"Generator: OutboundTranslationSlice `" ++
|
|
532
536
|
stem ++
|
|
@@ -616,9 +620,9 @@ let renderComposition = (
|
|
|
616
620
|
lines->Array.push("")
|
|
617
621
|
lines->Array.push(
|
|
618
622
|
"@val external uiBundleUrl: option<string> = \"process.env." ++
|
|
619
|
-
pluginNameToEnvBase(config.name) ++
|
|
620
|
-
"_UI_BUNDLE_URL\"",
|
|
623
|
+
pluginNameToEnvBase(config.name) ++ "_UI_BUNDLE_URL\"",
|
|
621
624
|
)
|
|
625
|
+
|
|
622
626
|
// The DCB boundary's slices as schemas, outside `Make` because the scope they
|
|
623
627
|
// determine is a property of the specs and not of any platform. Emitted rather
|
|
624
628
|
// than hand-listed so a slice added tomorrow is in it: this is the only value
|
|
@@ -651,10 +655,7 @@ let renderComposition = (
|
|
|
651
655
|
// StateChangeSlices — per-stem MakeAsync opt-in via @@reventless.async
|
|
652
656
|
if resolved.stateChangeSlices->Array.length > 0 {
|
|
653
657
|
lines->Array.push(" // StateChangeSlices")
|
|
654
|
-
renderStateChangeSlices(
|
|
655
|
-
resolved.stateChangeSlices,
|
|
656
|
-
resolved.asyncStateChangeSlices,
|
|
657
|
-
)->push
|
|
658
|
+
renderStateChangeSlices(resolved.stateChangeSlices, resolved.asyncStateChangeSlices)->push
|
|
658
659
|
}
|
|
659
660
|
|
|
660
661
|
// StateViewSlices
|
|
@@ -794,9 +795,17 @@ let renderComposition = (
|
|
|
794
795
|
renderAggregateMakeParam(resolved.aggregates),
|
|
795
796
|
renderReadModelMakeParam(resolved.readModels),
|
|
796
797
|
renderTaskMakeParam(resolved.tasks),
|
|
797
|
-
renderMakeParam(
|
|
798
|
+
renderMakeParam(
|
|
799
|
+
~param="stateChangeSlices",
|
|
800
|
+
~items=resolved.stateChangeSlices,
|
|
801
|
+
~moduleSuffix="Slice",
|
|
802
|
+
),
|
|
798
803
|
renderStateViewSlicesMakeParam(resolved.stateViewSlices, resolved.stateViewSlicesStream),
|
|
799
|
-
renderMakeParam(
|
|
804
|
+
renderMakeParam(
|
|
805
|
+
~param="automationSlices",
|
|
806
|
+
~items=resolved.automationSlices,
|
|
807
|
+
~moduleSuffix="Slice",
|
|
808
|
+
),
|
|
800
809
|
renderMakeParam(
|
|
801
810
|
~param="outboundTranslationSlices",
|
|
802
811
|
~items=resolved.outboundTranslationSlices,
|
|
@@ -18,7 +18,12 @@ type componentType = ComponentKind.t =
|
|
|
18
18
|
| Extension
|
|
19
19
|
|
|
20
20
|
// relPath: path relative to srcDir (e.g. "ReadModel/ProductsProjections.res")
|
|
21
|
-
type discoveredFile = {
|
|
21
|
+
type discoveredFile = {
|
|
22
|
+
stem: string,
|
|
23
|
+
componentType: componentType,
|
|
24
|
+
epGroup: option<string>,
|
|
25
|
+
relPath: string,
|
|
26
|
+
}
|
|
22
27
|
|
|
23
28
|
let folderToComponentType = ComponentKind.folderToKind
|
|
24
29
|
|
|
@@ -32,6 +37,7 @@ let folderToComponentType = ComponentKind.folderToKind
|
|
|
32
37
|
// docs/plans/done/deployed-chapter-grouping.md.
|
|
33
38
|
let chapterOf = (relPath: string): option<string> => {
|
|
34
39
|
let segments = relPath->String.split("/")
|
|
40
|
+
|
|
35
41
|
// Need at least one directory segment before the filename.
|
|
36
42
|
if segments->Array.length < 2 {
|
|
37
43
|
None
|
|
@@ -76,7 +82,7 @@ let isAlwaysExcludedDir = (name: string): bool =>
|
|
|
76
82
|
let matchesGlob = (path: string, pattern: string): bool =>
|
|
77
83
|
if pattern === "**" {
|
|
78
84
|
true
|
|
79
|
-
} else if
|
|
85
|
+
} else if !(pattern->String.includes("*")) {
|
|
80
86
|
// Exact file match or directory prefix
|
|
81
87
|
path === pattern || path->String.startsWith(pattern ++ "/")
|
|
82
88
|
} else if pattern->String.endsWith("/**") {
|
|
@@ -85,9 +91,11 @@ let matchesGlob = (path: string, pattern: string): bool =>
|
|
|
85
91
|
} else if pattern->String.endsWith("/*") {
|
|
86
92
|
let prefix = pattern->String.slice(~start=0, ~end=pattern->String.length - 2)
|
|
87
93
|
path->String.startsWith(prefix ++ "/") &&
|
|
88
|
-
!(
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
!(
|
|
95
|
+
path
|
|
96
|
+
->String.slice(~start=prefix->String.length + 1, ~end=path->String.length)
|
|
97
|
+
->String.includes("/")
|
|
98
|
+
)
|
|
91
99
|
} else {
|
|
92
100
|
path === pattern
|
|
93
101
|
}
|
|
@@ -103,14 +111,14 @@ let stemOf = (filename: string): option<string> =>
|
|
|
103
111
|
}
|
|
104
112
|
|
|
105
113
|
let isSkipped = (stem: string): bool =>
|
|
106
|
-
stem->String.endsWith("Test")
|
|
107
|
-
|
|
114
|
+
stem->String.endsWith("Test") ||
|
|
115
|
+
stem->String.endsWith("Fixtures") ||
|
|
108
116
|
// `_EventMappings` and `_Mappings` files in per-entity Aggregate/ folders
|
|
109
117
|
// are picked up by the dedicated [Pairing.findEventMappings] walker;
|
|
110
118
|
// treating them as Aggregate specs would hunt for a non-existent
|
|
111
119
|
// `_EventMappingsBehavior` / `_MappingsBehavior`.
|
|
112
|
-
|
|
113
|
-
|
|
120
|
+
stem->String.endsWith("_EventMappings") ||
|
|
121
|
+
stem->String.endsWith("_Mappings")
|
|
114
122
|
|
|
115
123
|
// Collect .res files directly in dir (non-recursive) into acc.
|
|
116
124
|
let collectFiles = (
|
|
@@ -124,7 +132,11 @@ let collectFiles = (
|
|
|
124
132
|
Generator_Node.readDir(dir)->Array.forEach(entry => {
|
|
125
133
|
if entry->NodeFs.isFile {
|
|
126
134
|
let filename = entry->NodeFs.direntName
|
|
127
|
-
let relPath = if relDir === "" {
|
|
135
|
+
let relPath = if relDir === "" {
|
|
136
|
+
filename
|
|
137
|
+
} else {
|
|
138
|
+
relDir ++ "/" ++ filename
|
|
139
|
+
}
|
|
128
140
|
if !isExcluded(relPath, exclude) {
|
|
129
141
|
switch stemOf(filename) {
|
|
130
142
|
| Some(stem) if !isSkipped(stem) => acc->Array.push({stem, componentType, epGroup, relPath})
|
|
@@ -145,7 +157,11 @@ let rec walkDir = (
|
|
|
145
157
|
): unit =>
|
|
146
158
|
Generator_Node.readDir(dir)->Array.forEach(entry => {
|
|
147
159
|
let entryName = entry->NodeFs.direntName
|
|
148
|
-
let relPath = if relDir === "" {
|
|
160
|
+
let relPath = if relDir === "" {
|
|
161
|
+
entryName
|
|
162
|
+
} else {
|
|
163
|
+
relDir ++ "/" ++ entryName
|
|
164
|
+
}
|
|
149
165
|
|
|
150
166
|
if isExcluded(relPath, exclude) {
|
|
151
167
|
()
|
|
@@ -215,7 +231,8 @@ let rec walkDir = (
|
|
|
215
231
|
let filename = entryName
|
|
216
232
|
if !isExcluded(relPath, exclude) {
|
|
217
233
|
switch stemOf(filename) {
|
|
218
|
-
| Some(stem) if !isSkipped(stem) =>
|
|
234
|
+
| Some(stem) if !isSkipped(stem) =>
|
|
235
|
+
acc->Array.push({stem, componentType: ct, epGroup, relPath})
|
|
219
236
|
| _ => ()
|
|
220
237
|
}
|
|
221
238
|
}
|
|
@@ -157,8 +157,7 @@ let main = async () => {
|
|
|
157
157
|
| _ =>
|
|
158
158
|
fail(
|
|
159
159
|
`${traitPackage} ships no scaffold (looked for ${specifier}).\n` ++
|
|
160
|
-
` Not every trait has one — a trait whose graft is all patches has nothing to write.\n` ++
|
|
161
|
-
` Write the graft by hand instead; its README says what the host must declare.`,
|
|
160
|
+
` Not every trait has one — a trait whose graft is all patches has nothing to write.\n` ++ ` Write the graft by hand instead; its README says what the host must declare.`,
|
|
162
161
|
)
|
|
163
162
|
%raw(`undefined`)
|
|
164
163
|
}
|
|
@@ -177,11 +176,14 @@ let main = async () => {
|
|
|
177
176
|
)
|
|
178
177
|
["into", "tests", "dry-run"]->Array.forEach(k => raw->Dict.delete(k))
|
|
179
178
|
|
|
180
|
-
let config = try raw
|
|
179
|
+
let config = try raw
|
|
180
|
+
->JSON.Encode.object
|
|
181
|
+
->Util_Sury.fromJson(scaffold.configSchema) catch {
|
|
181
182
|
| exn => {
|
|
182
183
|
fail(
|
|
183
|
-
`${traitPackage} refused this config: ${Util_Sury.exnMessage(
|
|
184
|
-
|
|
184
|
+
`${traitPackage} refused this config: ${Util_Sury.exnMessage(
|
|
185
|
+
exn,
|
|
186
|
+
)}\n` ++ ` Every --key is a field of the trait's own config; it decides what it needs.`,
|
|
185
187
|
)
|
|
186
188
|
%raw(`undefined`)
|
|
187
189
|
}
|
|
@@ -191,7 +193,12 @@ let main = async () => {
|
|
|
191
193
|
|
|
192
194
|
files->Array.forEach(({path, contents}) =>
|
|
193
195
|
if dryRun {
|
|
194
|
-
Console.log(
|
|
196
|
+
Console.log(
|
|
197
|
+
`Would write: ${path} (${contents
|
|
198
|
+
->String.split("\n")
|
|
199
|
+
->Array.length
|
|
200
|
+
->Int.toString} lines)`,
|
|
201
|
+
)
|
|
195
202
|
} else if NodeFs.existsSync(path) {
|
|
196
203
|
// Never overwrite. From the moment a graft lands it is the host's
|
|
197
204
|
// source, edited with the policy the trait deliberately does not
|
|
@@ -207,7 +214,9 @@ let main = async () => {
|
|
|
207
214
|
|
|
208
215
|
if patches->Array.length > 0 {
|
|
209
216
|
Console.log("")
|
|
210
|
-
Console.log(
|
|
217
|
+
Console.log(
|
|
218
|
+
"── Paste these; they go into files you already own ──────────────",
|
|
219
|
+
)
|
|
211
220
|
patches->Array.forEach(({into, at, contents}) => {
|
|
212
221
|
Console.log("")
|
|
213
222
|
Console.log(`# ${into} — ${at}`)
|
|
@@ -216,8 +225,7 @@ let main = async () => {
|
|
|
216
225
|
Console.log("")
|
|
217
226
|
Console.log(
|
|
218
227
|
"Printed rather than written: placing an arm in an existing ordered `switch`\n" ++
|
|
219
|
-
"is an AST operation, and a text splice into the wrong arm is a bug the\n" ++
|
|
220
|
-
"compiler cannot see.",
|
|
228
|
+
"is an AST operation, and a text splice into the wrong arm is a bug the\n" ++ "compiler cannot see.",
|
|
221
229
|
)
|
|
222
230
|
}
|
|
223
231
|
|
|
@@ -21,11 +21,11 @@ let implSuffixForTranslation = "_Translation"
|
|
|
21
21
|
let mappingsSuffixForAutomation = "_Mappings"
|
|
22
22
|
|
|
23
23
|
let isImplStem = (stem: string): bool =>
|
|
24
|
-
stem->String.endsWith(implSuffixForStateChange)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
29
|
|
|
30
30
|
type aggregateDef = {spec: string, behavior: string, eventMappings: option<string>, isAsync: bool}
|
|
31
31
|
// readModelDef pairs a ReadModel spec with its sibling `_Projections.res`
|
|
@@ -119,7 +119,15 @@ let findEventMappings = (~srcDir: string): Dict.t<string> => {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
let sortedStems = (stems: array<string>): array<string> =>
|
|
122
|
-
stems->Array.toSorted((a, b) =>
|
|
122
|
+
stems->Array.toSorted((a, b) =>
|
|
123
|
+
if a < b {
|
|
124
|
+
-1.0
|
|
125
|
+
} else if a > b {
|
|
126
|
+
1.0
|
|
127
|
+
} else {
|
|
128
|
+
0.0
|
|
129
|
+
}
|
|
130
|
+
)
|
|
123
131
|
|
|
124
132
|
// Read a slice spec file and extract the value of `let targetName = ...`.
|
|
125
133
|
// Returns Some(name) for string literals (including inside Some("...")),
|
|
@@ -128,7 +136,9 @@ let extractTargetName = (filePath: string): option<string> => {
|
|
|
128
136
|
try {
|
|
129
137
|
let content = NodeFs.readFileSync(filePath)
|
|
130
138
|
let result = ref(None)
|
|
131
|
-
content
|
|
139
|
+
content
|
|
140
|
+
->String.split("\n")
|
|
141
|
+
->Array.forEach(line => {
|
|
132
142
|
let trimmed = line->String.trimStart
|
|
133
143
|
if trimmed->String.startsWith("let targetName = ") {
|
|
134
144
|
let firstQuote = trimmed->String.indexOf("\"")
|
|
@@ -153,8 +163,11 @@ let hasFileAttribute = (filePath: string, ~attr: string): bool => {
|
|
|
153
163
|
try {
|
|
154
164
|
let content = NodeFs.readFileSync(filePath)
|
|
155
165
|
let found = ref(false)
|
|
156
|
-
content
|
|
166
|
+
content
|
|
167
|
+
->String.split("\n")
|
|
168
|
+
->Array.forEach(line => {
|
|
157
169
|
let trimmed = line->String.trimStart
|
|
170
|
+
|
|
158
171
|
// Match the bare attribute and tolerate a payload arg in the future.
|
|
159
172
|
if (
|
|
160
173
|
trimmed->String.startsWith(attr ++ " ") ||
|
|
@@ -189,7 +202,9 @@ let effectiveSpecName = (filePath: string, ~stem: string): string => {
|
|
|
189
202
|
try {
|
|
190
203
|
let content = NodeFs.readFileSync(filePath)
|
|
191
204
|
let name = ref(None)
|
|
192
|
-
content
|
|
205
|
+
content
|
|
206
|
+
->String.split("\n")
|
|
207
|
+
->Array.forEach(line => {
|
|
193
208
|
let trimmed = line->String.trimStart
|
|
194
209
|
if trimmed->String.startsWith("@@reventless.spec(") {
|
|
195
210
|
let firstQuote = trimmed->String.indexOf("\"")
|
|
@@ -356,7 +371,14 @@ let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): re
|
|
|
356
371
|
isAsync,
|
|
357
372
|
})
|
|
358
373
|
| None =>
|
|
359
|
-
Console.warn(
|
|
374
|
+
Console.warn(
|
|
375
|
+
"Generator: Aggregate spec `" ++
|
|
376
|
+
spec ++
|
|
377
|
+
"` has no matching `" ++
|
|
378
|
+
underscored ++
|
|
379
|
+
"` or `" ++
|
|
380
|
+
bare ++ "` — skipping",
|
|
381
|
+
)
|
|
360
382
|
None
|
|
361
383
|
}
|
|
362
384
|
})
|
|
@@ -377,10 +399,14 @@ let resolve = (discovered: array<Discovery.discoveredFile>, ~srcDir: string): re
|
|
|
377
399
|
let underscoredProj = baseName ++ "_Projections"
|
|
378
400
|
switch Dict.get(projectionsByRelPath, underscoredProj) {
|
|
379
401
|
| None =>
|
|
380
|
-
Console.warn(
|
|
402
|
+
Console.warn(
|
|
403
|
+
"Generator: ReadModel `" ++
|
|
404
|
+
rm ++
|
|
405
|
+
"` has no matching `" ++
|
|
406
|
+
underscoredProj ++ ".res` — skipping",
|
|
407
|
+
)
|
|
381
408
|
None
|
|
382
|
-
| Some(_) =>
|
|
383
|
-
Some({readModel: rm, projections: underscoredProj, stream})
|
|
409
|
+
| Some(_) => Some({readModel: rm, projections: underscoredProj, stream})
|
|
384
410
|
}
|
|
385
411
|
}
|
|
386
412
|
let readModels = Array.concat(
|
|
@@ -44,16 +44,20 @@ let union = (manifests: array<pluginManifest>): array<unionEntry> => {
|
|
|
44
44
|
let byKey: dict<unionEntry> = Dict.make()
|
|
45
45
|
manifests->Array.forEach(({pluginName, manifest}) =>
|
|
46
46
|
manifest.capabilities->Array.forEach(entry => {
|
|
47
|
-
let sites =
|
|
48
|
-
|
|
47
|
+
let sites = entry.declaredBy->Array.map(
|
|
48
|
+
site => {
|
|
49
49
|
pluginName,
|
|
50
50
|
component: site.component,
|
|
51
51
|
field: site.field,
|
|
52
52
|
annotation: site.annotation,
|
|
53
|
-
}
|
|
53
|
+
},
|
|
54
|
+
)
|
|
54
55
|
switch byKey->Dict.get(entry.key) {
|
|
55
56
|
| Some(existing) =>
|
|
56
|
-
byKey->Dict.set(
|
|
57
|
+
byKey->Dict.set(
|
|
58
|
+
entry.key,
|
|
59
|
+
{...existing, declaredBy: existing.declaredBy->Array.concat(sites)},
|
|
60
|
+
)
|
|
57
61
|
| None => byKey->Dict.set(entry.key, {kind: entry.kind, key: entry.key, declaredBy: sites})
|
|
58
62
|
}
|
|
59
63
|
})
|
|
@@ -71,10 +75,7 @@ let quote = (s: string): string => s->JSON.Encode.string->JSON.stringify
|
|
|
71
75
|
let splitKey = (key: string): option<(string, string)> =>
|
|
72
76
|
key
|
|
73
77
|
->String.indexOfOpt(".")
|
|
74
|
-
->Option.map(i => (
|
|
75
|
-
key->String.slice(~start=0, ~end=i),
|
|
76
|
-
key->String.slice(~start=i + 1),
|
|
77
|
-
))
|
|
78
|
+
->Option.map(i => (key->String.slice(~start=0, ~end=i), key->String.slice(~start=i + 1)))
|
|
78
79
|
|
|
79
80
|
// The provenance comment names the store as the field spells it — taken from
|
|
80
81
|
// the manifest, which recorded it at the one place the owning plugin was
|
|
@@ -109,7 +110,9 @@ let renderEntry = (entry: unionEntry): result<array<string>, string> =>
|
|
|
109
110
|
switch splitKey(entry.key) {
|
|
110
111
|
| None =>
|
|
111
112
|
Error(
|
|
112
|
-
`malformed capability key ${quote(
|
|
113
|
+
`malformed capability key ${quote(
|
|
114
|
+
entry.key,
|
|
115
|
+
)} — expected "{plugin}.{store}" (is the plugin's capabilities.json hand-edited?)`,
|
|
113
116
|
)
|
|
114
117
|
| Some((plugin, store)) =>
|
|
115
118
|
Ok(
|
|
@@ -122,6 +125,7 @@ let renderEntry = (entry: unionEntry): result<array<string>, string> =>
|
|
|
122
125
|
// declared it, with every declaring slice kept as provenance.
|
|
123
126
|
| Geocoding => Ok(renderComments(entry)->Array.concat([" Geocoding,"]))
|
|
124
127
|
| Messaging => Ok(renderComments(entry)->Array.concat([" Messaging,"]))
|
|
128
|
+
| IdentityProvider => Ok(renderComments(entry)->Array.concat([" IdentityProvider,"]))
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
/**
|
|
@@ -176,8 +180,7 @@ let duplicatePluginMessage = ((plugin, claimants): (string, array<string>)): str
|
|
|
176
180
|
)}.\n` ++
|
|
177
181
|
` A platform keys its plugin registry by name, so the second registration is read as a new ` ++
|
|
178
182
|
`VERSION of the first and supersedes it.\n` ++
|
|
179
|
-
` Give each deployable's plugin a distinct name (plugin.json), or — if these were meant to be ` ++
|
|
180
|
-
`one plugin — deploy only one of them.`
|
|
183
|
+
` Give each deployable's plugin a distinct name (plugin.json), or — if these were meant to be ` ++ `one plugin — deploy only one of them.`
|
|
181
184
|
|
|
182
185
|
let header = [
|
|
183
186
|
"// AUTO-GENERATED — do not edit. Run `pnpm run generate:platform` to update.",
|
|
@@ -75,9 +75,7 @@ let scriptsOf = (pkg: dict<JSON.t>): array<string> =>
|
|
|
75
75
|
pkg
|
|
76
76
|
->Dict.get("scripts")
|
|
77
77
|
->Option.flatMap(asObject)
|
|
78
|
-
->Option.mapOr([], scripts =>
|
|
79
|
-
scripts->Dict.valuesToArray->Array.filterMap(JSON.Decode.string)
|
|
80
|
-
)
|
|
78
|
+
->Option.mapOr([], scripts => scripts->Dict.valuesToArray->Array.filterMap(JSON.Decode.string))
|
|
81
79
|
|
|
82
80
|
// The composition `src/` an `-aws` root is generated from, read off the script
|
|
83
81
|
// that performs the generation.
|