@reventlessdev/reventless-core 3.0.0-alpha.152 → 3.0.0-alpha.153
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 +8 -0
- package/package.json +5 -5
- package/src/admin/Platform_Admin_Structure.res +2 -0
- package/src/admin/Platform_Admin_Structure.res.mjs +4 -2
- package/src/admin/Platform_ComponentDefinitionsApi.res +11 -0
- package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +28 -0
- package/src/plugin/component/Plugin.res +1 -0
- package/src/plugin/component/Plugin_Builder.res +40 -1
- package/src/plugin/component/Plugin_Builder.res.mjs +37 -9
- package/src/plugin/component/Plugin_BuiltHook.res +11 -0
- package/src/plugin/component/Plugin_Structure.res +15 -0
- package/src/plugin/component/Plugin_Structure.res.mjs +16 -8
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +69 -1
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +64 -8
- package/tests/plugin/ManifestVisibilityTest.res.mjs +1 -1
- package/tests/plugin/PluginStructureTest.res +34 -0
- package/tests/plugin/PluginStructureTest.res.mjs +32 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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.153 (2026-07-10)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **plugin-structure:** capture per-component chapter grouping for the deployed graph ([f9c88a9](https://github.com/ReventlessDev/reventless-core/commit/f9c88a9a48d8c032ffe23f9e5277caf12c29e85c))
|
|
11
|
+
* **plugin-structure:** carry translation-slice externalSystem into the deployed structure ([80c0cda](https://github.com/ReventlessDev/reventless-core/commit/80c0cda3b6d5fc6625d4b285f41505028f1dd06f))
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
# 3.0.0-alpha.152 (2026-07-10)
|
|
7
15
|
|
|
8
16
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-core",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.153",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.25",
|
|
32
32
|
"@reventlessdev/rescript-fast-csv": "1.2.0-alpha.11",
|
|
33
33
|
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.10",
|
|
34
|
+
"@reventlessdev/rescript-node-streams": "1.1.0-alpha.10",
|
|
34
35
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.6",
|
|
35
36
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
|
|
36
|
-
"@reventlessdev/rescript-node-streams": "1.1.0-alpha.10",
|
|
37
37
|
"@reventlessdev/rescript-ssh2": "1.1.0-alpha.11",
|
|
38
38
|
"@reventlessdev/rescript-uuid": "1.1.0-alpha.14",
|
|
39
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
40
|
-
"@reventlessdev/reventless-
|
|
41
|
-
"@reventlessdev/reventless-
|
|
39
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.94",
|
|
40
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.24",
|
|
41
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.72"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "^12.3.0",
|
|
@@ -96,6 +96,7 @@ let pluginAggregate: writableDef = {
|
|
|
96
96
|
linkedViews: ["Plugins"],
|
|
97
97
|
consistencyRead: None,
|
|
98
98
|
events: [],
|
|
99
|
+
chapter: None,
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
let pluginReadModel: queryableDef = {
|
|
@@ -110,6 +111,7 @@ let pluginReadModel: queryableDef = {
|
|
|
110
111
|
searchableFields: ["name"],
|
|
111
112
|
statusField: Some("status"),
|
|
112
113
|
visibility: None,
|
|
114
|
+
chapter: None,
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
let structure: pluginStructure = {
|
|
@@ -110,7 +110,8 @@ let pluginAggregate = {
|
|
|
110
110
|
consumedEventTypes: pluginAggregate_consumedEventTypes,
|
|
111
111
|
linkedViews: pluginAggregate_linkedViews,
|
|
112
112
|
consistencyRead: undefined,
|
|
113
|
-
events: pluginAggregate_events
|
|
113
|
+
events: pluginAggregate_events,
|
|
114
|
+
chapter: undefined
|
|
114
115
|
};
|
|
115
116
|
|
|
116
117
|
let pluginReadModel_queryField = Api_Naming$ReventlessCore.adminField("Plugins");
|
|
@@ -134,7 +135,8 @@ let pluginReadModel = {
|
|
|
134
135
|
labelField: "name",
|
|
135
136
|
searchableFields: pluginReadModel_searchableFields,
|
|
136
137
|
statusField: pluginReadModel_statusField,
|
|
137
|
-
visibility: undefined
|
|
138
|
+
visibility: undefined,
|
|
139
|
+
chapter: undefined
|
|
138
140
|
};
|
|
139
141
|
|
|
140
142
|
let structure_readModels = [pluginReadModel];
|
|
@@ -72,6 +72,7 @@ let encodeQueryableDef = (r: queryableDef): JSON.t =>
|
|
|
72
72
|
("labelField", JSON.Encode.string(r.labelField)),
|
|
73
73
|
("searchableFields", encodeStrings(r.searchableFields)),
|
|
74
74
|
("statusField", r.statusField->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
75
|
+
("chapter", r.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
75
76
|
])->JSON.Encode.object
|
|
76
77
|
|
|
77
78
|
let encodeEventDef = (e: eventDef): JSON.t =>
|
|
@@ -94,6 +95,7 @@ let encodeWritableDef = (w: writableDef): JSON.t =>
|
|
|
94
95
|
("consumedEventTypes", encodeStrings(w.consumedEventTypes)),
|
|
95
96
|
// Phase 6.3: emitted-event field schemas (None → [] on the wire).
|
|
96
97
|
("events", w.events->Array.map(encodeEventDef)->JSON.Encode.array),
|
|
98
|
+
("chapter", w.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
97
99
|
])->JSON.Encode.object
|
|
98
100
|
|
|
99
101
|
let encodeAutomationSliceDef = (a: automationSliceDef): JSON.t =>
|
|
@@ -101,6 +103,7 @@ let encodeAutomationSliceDef = (a: automationSliceDef): JSON.t =>
|
|
|
101
103
|
("name", JSON.Encode.string(a.name)),
|
|
102
104
|
("consumedEventTypes", encodeStrings(a.consumedEventTypes)),
|
|
103
105
|
("producedCommandTypes", encodeStrings(a.producedCommandTypes)),
|
|
106
|
+
("chapter", a.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
104
107
|
])->JSON.Encode.object
|
|
105
108
|
|
|
106
109
|
let encodeOutboundTranslationSliceDef = (o: outboundTranslationSliceDef): JSON.t =>
|
|
@@ -108,12 +111,20 @@ let encodeOutboundTranslationSliceDef = (o: outboundTranslationSliceDef): JSON.t
|
|
|
108
111
|
("name", JSON.Encode.string(o.name)),
|
|
109
112
|
("consumedEventTypes", encodeStrings(o.consumedEventTypes)),
|
|
110
113
|
("inboundCommandTypes", encodeStrings(o.inboundCommandTypes)),
|
|
114
|
+
// Foreign system this slice publishes to — lets a deployed-graph consumer draw
|
|
115
|
+
// the external-system boundary box without workspace access. None → null.
|
|
116
|
+
("externalSystem", o.externalSystem->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
117
|
+
("chapter", o.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
111
118
|
])->JSON.Encode.object
|
|
112
119
|
|
|
113
120
|
let encodeInboundTranslationSliceDef = (i: inboundTranslationSliceDef): JSON.t =>
|
|
114
121
|
Dict.fromArray([
|
|
115
122
|
("name", JSON.Encode.string(i.name)),
|
|
116
123
|
("commandTypes", encodeStrings(i.commandTypes)),
|
|
124
|
+
// Foreign system this slice receives from — lets a deployed-graph consumer draw
|
|
125
|
+
// the external-system boundary box without workspace access. None → null.
|
|
126
|
+
("externalSystem", i.externalSystem->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
127
|
+
("chapter", i.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
117
128
|
])->JSON.Encode.object
|
|
118
129
|
|
|
119
130
|
let encodeExtensionDef = (e: extensionDef): JSON.t =>
|
|
@@ -115,6 +115,10 @@ function encodeQueryableDef(r) {
|
|
|
115
115
|
[
|
|
116
116
|
"statusField",
|
|
117
117
|
Stdlib_Option.mapOr(r.statusField, null, prim => prim)
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
"chapter",
|
|
121
|
+
Stdlib_Option.mapOr(r.chapter, null, prim => prim)
|
|
118
122
|
]
|
|
119
123
|
]);
|
|
120
124
|
}
|
|
@@ -165,6 +169,10 @@ function encodeWritableDef(w) {
|
|
|
165
169
|
[
|
|
166
170
|
"events",
|
|
167
171
|
w.events.map(encodeEventDef)
|
|
172
|
+
],
|
|
173
|
+
[
|
|
174
|
+
"chapter",
|
|
175
|
+
Stdlib_Option.mapOr(w.chapter, null, prim => prim)
|
|
168
176
|
]
|
|
169
177
|
]);
|
|
170
178
|
}
|
|
@@ -182,6 +190,10 @@ function encodeAutomationSliceDef(a) {
|
|
|
182
190
|
[
|
|
183
191
|
"producedCommandTypes",
|
|
184
192
|
a.producedCommandTypes.map(prim => prim)
|
|
193
|
+
],
|
|
194
|
+
[
|
|
195
|
+
"chapter",
|
|
196
|
+
Stdlib_Option.mapOr(a.chapter, null, prim => prim)
|
|
185
197
|
]
|
|
186
198
|
]);
|
|
187
199
|
}
|
|
@@ -199,6 +211,14 @@ function encodeOutboundTranslationSliceDef(o) {
|
|
|
199
211
|
[
|
|
200
212
|
"inboundCommandTypes",
|
|
201
213
|
o.inboundCommandTypes.map(prim => prim)
|
|
214
|
+
],
|
|
215
|
+
[
|
|
216
|
+
"externalSystem",
|
|
217
|
+
Stdlib_Option.mapOr(o.externalSystem, null, prim => prim)
|
|
218
|
+
],
|
|
219
|
+
[
|
|
220
|
+
"chapter",
|
|
221
|
+
Stdlib_Option.mapOr(o.chapter, null, prim => prim)
|
|
202
222
|
]
|
|
203
223
|
]);
|
|
204
224
|
}
|
|
@@ -212,6 +232,14 @@ function encodeInboundTranslationSliceDef(i) {
|
|
|
212
232
|
[
|
|
213
233
|
"commandTypes",
|
|
214
234
|
i.commandTypes.map(prim => prim)
|
|
235
|
+
],
|
|
236
|
+
[
|
|
237
|
+
"externalSystem",
|
|
238
|
+
Stdlib_Option.mapOr(i.externalSystem, null, prim => prim)
|
|
239
|
+
],
|
|
240
|
+
[
|
|
241
|
+
"chapter",
|
|
242
|
+
Stdlib_Option.mapOr(i.chapter, null, prim => prim)
|
|
215
243
|
]
|
|
216
244
|
]);
|
|
217
245
|
}
|
|
@@ -46,6 +46,7 @@ module type T = {
|
|
|
46
46
|
~inboundTranslationSlices: array<module(ReventlessInfra.InboundTranslationSlice.T)>=?,
|
|
47
47
|
~extensions: array<module(ReventlessInfra.Extension.Blueprint)>=?,
|
|
48
48
|
~extensionPoints: array<module(ReventlessInfra.ExtensionPointMapping.Mapping)>=?,
|
|
49
|
+
~componentChapters: dict<string>=?,
|
|
49
50
|
) => Reventless.Plugin.pluginStructure
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -368,6 +368,30 @@ module Make = (
|
|
|
368
368
|
// so only aggregates, read models, and DCB slice names are included.
|
|
369
369
|
|
|
370
370
|
let extractTypes = schema => Reventless.DcbTag.extractAllVariantNames(schema)
|
|
371
|
+
|
|
372
|
+
// Chapter (intra-plugin grouping band) per component name, mirrored from the
|
|
373
|
+
// static pluginStructure so the hook surface reaches parity with it. The
|
|
374
|
+
// structure is the single source — it was populated by the generator's
|
|
375
|
+
// componentChapters map at makePluginDefinition time.
|
|
376
|
+
let chapterByName: Dict.t<string> = Dict.make()
|
|
377
|
+
switch pluginStructure {
|
|
378
|
+
| Some(ps) =>
|
|
379
|
+
let put = (n, c) =>
|
|
380
|
+
switch c {
|
|
381
|
+
| Some(ch) => chapterByName->Dict.set(n, ch)
|
|
382
|
+
| None => ()
|
|
383
|
+
}
|
|
384
|
+
ps.aggregates->Array.forEach(d => put(d.name, d.chapter))
|
|
385
|
+
ps.readModels->Array.forEach(d => put(d.name, d.chapter))
|
|
386
|
+
ps.stateChangeSlices->Array.forEach(d => put(d.name, d.chapter))
|
|
387
|
+
ps.stateViewSlices->Array.forEach(d => put(d.name, d.chapter))
|
|
388
|
+
ps.automationSlices->Array.forEach(d => put(d.name, d.chapter))
|
|
389
|
+
ps.outboundTranslationSlices->Array.forEach(d => put(d.name, d.chapter))
|
|
390
|
+
ps.inboundTranslationSlices->Array.forEach(d => put(d.name, d.chapter))
|
|
391
|
+
| None => ()
|
|
392
|
+
}
|
|
393
|
+
let chapterFor = (n: string): option<string> => chapterByName->Dict.get(n)
|
|
394
|
+
|
|
371
395
|
// Build per-component schema data and register it for the deployed hook.
|
|
372
396
|
let aggregateComponents = aggregates->Array.map((
|
|
373
397
|
module(M: ReventlessInfra.Aggregate.T with type api = api),
|
|
@@ -382,6 +406,7 @@ module Make = (
|
|
|
382
406
|
eventSchemas: [
|
|
383
407
|
SchemaWalker.walk(M.Spec.name ++ ".event", M.Spec.eventSchema),
|
|
384
408
|
],
|
|
409
|
+
chapter: ?chapterFor(M.Spec.name),
|
|
385
410
|
}
|
|
386
411
|
Plugin_Helpers.componentSchemaRegistry->Dict.set(M.Spec.name, schema)
|
|
387
412
|
({name: M.Spec.name, kind: "Aggregate", schema}: Plugin_Helpers.pluginBuiltComponent)
|
|
@@ -393,6 +418,7 @@ module Make = (
|
|
|
393
418
|
let qn = Api_Naming.queryFieldNamesForReadModel(~plugin=name, ~name=R.Spec.name)
|
|
394
419
|
let schema: Plugin_Helpers.pluginDeployedSchema = {
|
|
395
420
|
queryFields: [qn.singleFieldName, qn.listFieldName],
|
|
421
|
+
chapter: ?chapterFor(R.Spec.name),
|
|
396
422
|
}
|
|
397
423
|
Plugin_Helpers.componentSchemaRegistry->Dict.set(R.Spec.name, schema)
|
|
398
424
|
({name: R.Spec.name, kind: "ReadModel", schema}: Plugin_Helpers.pluginBuiltComponent)
|
|
@@ -421,6 +447,7 @@ module Make = (
|
|
|
421
447
|
consumedEventTypes,
|
|
422
448
|
producedCommandTypes: extractTypes(As.Spec.commandSchema),
|
|
423
449
|
targetName: As.Spec.targetName,
|
|
450
|
+
chapter: ?chapterFor(As.Spec.name),
|
|
424
451
|
},
|
|
425
452
|
)
|
|
426
453
|
})
|
|
@@ -434,6 +461,9 @@ module Make = (
|
|
|
434
461
|
commandTypes: extractTypes(Ots.Spec.inboundCommandSchema),
|
|
435
462
|
// Outbound slices may have no target (option); pass it through.
|
|
436
463
|
targetName: ?Ots.Spec.targetName,
|
|
464
|
+
// Foreign system this slice publishes to (option); drives the external box.
|
|
465
|
+
externalSystem: ?Ots.Spec.externalSystem,
|
|
466
|
+
chapter: ?chapterFor(Ots.Spec.name),
|
|
437
467
|
},
|
|
438
468
|
)
|
|
439
469
|
)
|
|
@@ -445,6 +475,9 @@ module Make = (
|
|
|
445
475
|
{
|
|
446
476
|
commandTypes: extractTypes(Its.Spec.commandSchema),
|
|
447
477
|
targetName: Its.Spec.targetName,
|
|
478
|
+
// Foreign system this slice receives from (option); drives the external box.
|
|
479
|
+
externalSystem: ?Its.Spec.externalSystem,
|
|
480
|
+
chapter: ?chapterFor(Its.Spec.name),
|
|
448
481
|
},
|
|
449
482
|
)
|
|
450
483
|
)
|
|
@@ -464,13 +497,17 @@ module Make = (
|
|
|
464
497
|
commandTypes: extractTypes(Scs.Spec.commandSchema),
|
|
465
498
|
eventTypes: extractEventTypes(Scs.Spec.eventSchema->S.castToUnknown),
|
|
466
499
|
consumedEventTypes: extractEventTypes(Scs.Spec.consumedEventSchema->S.castToUnknown),
|
|
500
|
+
chapter: ?chapterFor(Scs.Spec.name),
|
|
467
501
|
},
|
|
468
502
|
)
|
|
469
503
|
)
|
|
470
504
|
stateViewSlices->Array.forEach((module(Svs: ReventlessInfra.StateViewSlice.T)) =>
|
|
471
505
|
stateSchemas->Dict.set(
|
|
472
506
|
Svs.Spec.name,
|
|
473
|
-
{
|
|
507
|
+
{
|
|
508
|
+
consumedEventTypes: extractEventTypes(Svs.Spec.consumedEventSchema->S.castToUnknown),
|
|
509
|
+
chapter: ?chapterFor(Svs.Spec.name),
|
|
510
|
+
},
|
|
474
511
|
)
|
|
475
512
|
)
|
|
476
513
|
|
|
@@ -993,6 +1030,7 @@ module Make = (
|
|
|
993
1030
|
~inboundTranslationSlices: array<module(ReventlessInfra.InboundTranslationSlice.T)>=[],
|
|
994
1031
|
~extensions: array<module(ReventlessInfra.Extension.Blueprint)>=[],
|
|
995
1032
|
~extensionPoints: array<module(ReventlessInfra.ExtensionPointMapping.Mapping)>=[],
|
|
1033
|
+
~componentChapters: dict<string>=Dict.make(),
|
|
996
1034
|
): Reventless.Plugin.pluginStructure =>
|
|
997
1035
|
Plugin_Structure.make(
|
|
998
1036
|
~name,
|
|
@@ -1005,6 +1043,7 @@ module Make = (
|
|
|
1005
1043
|
~inboundTranslationSlices,
|
|
1006
1044
|
~extensions,
|
|
1007
1045
|
~extensionPoints,
|
|
1046
|
+
~componentChapters,
|
|
1008
1047
|
)
|
|
1009
1048
|
|
|
1010
1049
|
let make = (
|
|
@@ -89,7 +89,7 @@ function Make(Spec) {
|
|
|
89
89
|
pages: pages
|
|
90
90
|
};
|
|
91
91
|
};
|
|
92
|
-
let makePluginDefinition = (name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChangeSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, extensionsOpt, extensionPointsOpt) => {
|
|
92
|
+
let makePluginDefinition = (name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChangeSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, extensionsOpt, extensionPointsOpt, componentChaptersOpt) => {
|
|
93
93
|
let aggregates = aggregatesOpt !== undefined ? aggregatesOpt : [];
|
|
94
94
|
let readModels = readModelsOpt !== undefined ? readModelsOpt : [];
|
|
95
95
|
let stateViewSlices = stateViewSlicesOpt !== undefined ? stateViewSlicesOpt : [];
|
|
@@ -99,7 +99,8 @@ function Make(Spec) {
|
|
|
99
99
|
let inboundTranslationSlices = inboundTranslationSlicesOpt !== undefined ? inboundTranslationSlicesOpt : [];
|
|
100
100
|
let extensions = extensionsOpt !== undefined ? extensionsOpt : [];
|
|
101
101
|
let extensionPoints = extensionPointsOpt !== undefined ? extensionPointsOpt : [];
|
|
102
|
-
|
|
102
|
+
let componentChapters = componentChaptersOpt !== undefined ? componentChaptersOpt : ({});
|
|
103
|
+
return Plugin_Structure$ReventlessCore.make(name, aggregates, readModels, stateViewSlices, stateChangeSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, extensions, extensionPoints, componentChapters);
|
|
103
104
|
};
|
|
104
105
|
let make = (name, heartbeatInterval, extensionPointsOpt, extensionsOpt, aggregatesOpt, readModelsOpt, tasksOpt, stateChangeSlicesOpt, stateViewSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, systemCallableComponentsOpt, uiFragments, pluginStructure, opts) => {
|
|
105
106
|
let extensionPoints = extensionPointsOpt !== undefined ? extensionPointsOpt : [];
|
|
@@ -264,18 +265,36 @@ function Make(Spec) {
|
|
|
264
265
|
allQueryDbs[param[0] + "Todo"] = param[1].queryDb;
|
|
265
266
|
});
|
|
266
267
|
let queryEngine = QueryEngineAdapter.make(allQueryDbs);
|
|
268
|
+
let chapterByName = {};
|
|
269
|
+
if (pluginStructure !== undefined) {
|
|
270
|
+
let put = (n, c) => {
|
|
271
|
+
if (c !== undefined) {
|
|
272
|
+
chapterByName[n] = c;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
pluginStructure.aggregates.forEach(d => put(d.name, d.chapter));
|
|
277
|
+
pluginStructure.readModels.forEach(d => put(d.name, d.chapter));
|
|
278
|
+
pluginStructure.stateChangeSlices.forEach(d => put(d.name, d.chapter));
|
|
279
|
+
pluginStructure.stateViewSlices.forEach(d => put(d.name, d.chapter));
|
|
280
|
+
pluginStructure.automationSlices.forEach(d => put(d.name, d.chapter));
|
|
281
|
+
pluginStructure.outboundTranslationSlices.forEach(d => put(d.name, d.chapter));
|
|
282
|
+
pluginStructure.inboundTranslationSlices.forEach(d => put(d.name, d.chapter));
|
|
283
|
+
}
|
|
267
284
|
let aggregateComponents = aggregates.map(M => {
|
|
268
285
|
let schema_commandTypes = DcbTag$Reventless.extractAllVariantNames(M.Spec.commandSchema);
|
|
269
286
|
let schema_eventTypes = DcbTag$Reventless.extractAllVariantNames(M.Spec.eventSchema);
|
|
270
287
|
let schema_errorTypes = DcbTag$Reventless.extractAllVariantNames(M.Spec.errorSchema);
|
|
271
288
|
let schema_commandSchemas = [SchemaWalker$ReventlessCore.walk(M.Spec.name + ".command", M.Spec.commandSchema)];
|
|
272
289
|
let schema_eventSchemas = [SchemaWalker$ReventlessCore.walk(M.Spec.name + ".event", M.Spec.eventSchema)];
|
|
290
|
+
let schema_chapter = chapterByName[M.Spec.name];
|
|
273
291
|
let schema = {
|
|
274
292
|
commandTypes: schema_commandTypes,
|
|
275
293
|
eventTypes: schema_eventTypes,
|
|
276
294
|
errorTypes: schema_errorTypes,
|
|
277
295
|
commandSchemas: schema_commandSchemas,
|
|
278
|
-
eventSchemas: schema_eventSchemas
|
|
296
|
+
eventSchemas: schema_eventSchemas,
|
|
297
|
+
chapter: schema_chapter
|
|
279
298
|
};
|
|
280
299
|
Plugin_Helpers$ReventlessCore.componentSchemaRegistry[M.Spec.name] = schema;
|
|
281
300
|
return {
|
|
@@ -290,8 +309,10 @@ function Make(Spec) {
|
|
|
290
309
|
qn.singleFieldName,
|
|
291
310
|
qn.listFieldName
|
|
292
311
|
];
|
|
312
|
+
let schema_chapter = chapterByName[R.Spec.name];
|
|
293
313
|
let schema = {
|
|
294
|
-
queryFields: schema_queryFields
|
|
314
|
+
queryFields: schema_queryFields,
|
|
315
|
+
chapter: schema_chapter
|
|
295
316
|
};
|
|
296
317
|
Plugin_Helpers$ReventlessCore.componentSchemaRegistry[R.Spec.name] = schema;
|
|
297
318
|
return {
|
|
@@ -306,20 +327,25 @@ function Make(Spec) {
|
|
|
306
327
|
routingSchemas[As.Spec.name] = {
|
|
307
328
|
consumedEventTypes: consumedEventTypes,
|
|
308
329
|
producedCommandTypes: DcbTag$Reventless.extractAllVariantNames(As.Spec.commandSchema),
|
|
309
|
-
targetName: As.Spec.targetName
|
|
330
|
+
targetName: As.Spec.targetName,
|
|
331
|
+
chapter: chapterByName[As.Spec.name]
|
|
310
332
|
};
|
|
311
333
|
});
|
|
312
334
|
outboundTranslationSlices.forEach(Ots => {
|
|
313
335
|
routingSchemas[Ots.Spec.name] = {
|
|
314
336
|
commandTypes: DcbTag$Reventless.extractAllVariantNames(Ots.Spec.inboundCommandSchema),
|
|
315
337
|
consumedEventTypes: DcbTag$Reventless.extractVariantNames(Ots.Spec.consumedEventSchema),
|
|
316
|
-
targetName: Ots.Spec.targetName
|
|
338
|
+
targetName: Ots.Spec.targetName,
|
|
339
|
+
externalSystem: Ots.Spec.externalSystem,
|
|
340
|
+
chapter: chapterByName[Ots.Spec.name]
|
|
317
341
|
};
|
|
318
342
|
});
|
|
319
343
|
inboundTranslationSlices.forEach(Its => {
|
|
320
344
|
routingSchemas[Its.Spec.name] = {
|
|
321
345
|
commandTypes: DcbTag$Reventless.extractAllVariantNames(Its.Spec.commandSchema),
|
|
322
|
-
targetName: Its.Spec.targetName
|
|
346
|
+
targetName: Its.Spec.targetName,
|
|
347
|
+
externalSystem: Its.Spec.externalSystem,
|
|
348
|
+
chapter: chapterByName[Its.Spec.name]
|
|
323
349
|
};
|
|
324
350
|
});
|
|
325
351
|
let stateSchemas = {};
|
|
@@ -327,12 +353,14 @@ function Make(Spec) {
|
|
|
327
353
|
stateSchemas[Scs.Spec.name] = {
|
|
328
354
|
commandTypes: DcbTag$Reventless.extractAllVariantNames(Scs.Spec.commandSchema),
|
|
329
355
|
eventTypes: DcbTag$Reventless.extractVariantNames(Scs.Spec.eventSchema),
|
|
330
|
-
consumedEventTypes: DcbTag$Reventless.extractVariantNames(Scs.Spec.consumedEventSchema)
|
|
356
|
+
consumedEventTypes: DcbTag$Reventless.extractVariantNames(Scs.Spec.consumedEventSchema),
|
|
357
|
+
chapter: chapterByName[Scs.Spec.name]
|
|
331
358
|
};
|
|
332
359
|
});
|
|
333
360
|
stateViewSlices.forEach(Svs => {
|
|
334
361
|
stateSchemas[Svs.Spec.name] = {
|
|
335
|
-
consumedEventTypes: DcbTag$Reventless.extractVariantNames(Svs.Spec.consumedEventSchema)
|
|
362
|
+
consumedEventTypes: DcbTag$Reventless.extractVariantNames(Svs.Spec.consumedEventSchema),
|
|
363
|
+
chapter: chapterByName[Svs.Spec.name]
|
|
336
364
|
};
|
|
337
365
|
});
|
|
338
366
|
let enrichedComponents = (d, kind, schemas) => Object.keys(d).map(name => {
|
|
@@ -78,6 +78,12 @@ type pluginDeployedSchema = {
|
|
|
78
78
|
/** For routing slices (automation / inbound + outbound translation): the name
|
|
79
79
|
of the writable component this slice routes its commands to. */
|
|
80
80
|
targetName?: string,
|
|
81
|
+
/** For inbound / outbound translation slices: the foreign system this slice
|
|
82
|
+
integrates with, mirrored from the static `pluginStructure` for hook parity.
|
|
83
|
+
Drives the deployed graph's external-system boundary box. Absent when the slice
|
|
84
|
+
names no external system. See the translation-slice defs on
|
|
85
|
+
`Reventless.Plugin.pluginStructure`. */
|
|
86
|
+
externalSystem?: string,
|
|
81
87
|
sharedBy?: array<string>,
|
|
82
88
|
extensionPointName?: string,
|
|
83
89
|
providerPlugin?: string,
|
|
@@ -88,6 +94,11 @@ type pluginDeployedSchema = {
|
|
|
88
94
|
eventSchemas?: array<typeSchema>,
|
|
89
95
|
/** Field-level schema for the state type. */
|
|
90
96
|
stateSchema?: typeSchema,
|
|
97
|
+
/** Intra-plugin grouping band (the "chapter") this component belongs to, mirrored
|
|
98
|
+
from the static `pluginStructure` for parity on the hook surface. Absent when the
|
|
99
|
+
component carries no chapter (lives directly under a kind-folder). See
|
|
100
|
+
`Reventless.Plugin.queryableDef.chapter`. */
|
|
101
|
+
chapter?: string,
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
// ---------------------------------------------------------------------------
|
|
@@ -86,7 +86,15 @@ let make = (
|
|
|
86
86
|
~inboundTranslationSlices: array<module(ReventlessInfra.InboundTranslationSlice.T)>=[],
|
|
87
87
|
~extensions: array<module(ReventlessInfra.Extension.Blueprint)>=[],
|
|
88
88
|
~extensionPoints: array<module(ReventlessInfra.ExtensionPointMapping.Mapping)>=[],
|
|
89
|
+
// Component name → chapter (intra-plugin grouping band), captured at build time
|
|
90
|
+
// from each component's source folder by the plugin generator. A component whose
|
|
91
|
+
// spec name has no entry (or lives directly under a kind-folder) carries no chapter
|
|
92
|
+
// and renders flat. Keyed by `Spec.name`, which equals the source filename stem for
|
|
93
|
+
// every graph-node kind, so the generator can build this map from the discovered
|
|
94
|
+
// file paths. See `Codegen.chapterOf` and docs/plans/deployed-chapter-grouping.md.
|
|
95
|
+
~componentChapters: dict<string>=Dict.make(),
|
|
89
96
|
): Reventless.Plugin.pluginStructure => {
|
|
97
|
+
let chapterOf = (compName: string): option<string> => componentChapters->Dict.get(compName)
|
|
90
98
|
// Event schemas: filter out payload-less variants — DCB event-type lookups
|
|
91
99
|
// can't WHERE-clause on bare-string events, so the plugin graph mustn't
|
|
92
100
|
// claim cross-component edges that the runtime can't honour.
|
|
@@ -405,6 +413,7 @@ let make = (
|
|
|
405
413
|
searchableFields,
|
|
406
414
|
statusField: statusFieldFromStateSchema(stateSchema),
|
|
407
415
|
visibility: visibilityTag(R.Spec.visibility),
|
|
416
|
+
chapter: chapterOf(R.Spec.name),
|
|
408
417
|
}: Reventless.Plugin.queryableDef)
|
|
409
418
|
})
|
|
410
419
|
|
|
@@ -427,6 +436,7 @@ let make = (
|
|
|
427
436
|
searchableFields,
|
|
428
437
|
statusField: statusFieldFromStateSchema(stateSchema),
|
|
429
438
|
visibility: visibilityTag(SVS.Spec.visibility),
|
|
439
|
+
chapter: chapterOf(SVS.Spec.name),
|
|
430
440
|
}: Reventless.Plugin.queryableDef)
|
|
431
441
|
})
|
|
432
442
|
|
|
@@ -454,6 +464,7 @@ let make = (
|
|
|
454
464
|
linkedViews: linkedSvsFor(produced),
|
|
455
465
|
consistencyRead: consistencyReadFor(consumed),
|
|
456
466
|
events: extractEventDefs(SCS.Spec.eventSchema->S.castToUnknown),
|
|
467
|
+
chapter: chapterOf(SCS.Spec.name),
|
|
457
468
|
}: Reventless.Plugin.writableDef)
|
|
458
469
|
})
|
|
459
470
|
|
|
@@ -475,6 +486,7 @@ let make = (
|
|
|
475
486
|
linkedViews: Array.concat(linkedSvsFor(produced), linkedReadModelsFor(A.Spec.name)),
|
|
476
487
|
consistencyRead: None,
|
|
477
488
|
events: extractEventDefs(A.Spec.eventSchema->S.castToUnknown),
|
|
489
|
+
chapter: chapterOf(A.Spec.name),
|
|
478
490
|
}: Reventless.Plugin.writableDef)
|
|
479
491
|
})
|
|
480
492
|
|
|
@@ -497,6 +509,7 @@ let make = (
|
|
|
497
509
|
consumedEventTypes: qualify(~prefix=name, allConsumedVariants),
|
|
498
510
|
producedCommandTypes: qualify(~prefix=name, commandVariantNames(AS.Spec.commandSchema)),
|
|
499
511
|
targetName: AS.Spec.targetName,
|
|
512
|
+
chapter: chapterOf(AS.Spec.name),
|
|
500
513
|
}: Reventless.Plugin.automationSliceDef)
|
|
501
514
|
})
|
|
502
515
|
|
|
@@ -509,6 +522,7 @@ let make = (
|
|
|
509
522
|
inboundCommandTypes: qualify(~prefix=name, commandVariantNames(OTS.Spec.inboundCommandSchema)),
|
|
510
523
|
targetName: OTS.Spec.targetName,
|
|
511
524
|
externalSystem: OTS.Spec.externalSystem,
|
|
525
|
+
chapter: chapterOf(OTS.Spec.name),
|
|
512
526
|
}: Reventless.Plugin.outboundTranslationSliceDef))
|
|
513
527
|
|
|
514
528
|
// ── Inbound translation slices ────────────────────────────────────────────
|
|
@@ -519,6 +533,7 @@ let make = (
|
|
|
519
533
|
commandTypes: qualify(~prefix=name, commandVariantNames(ITS.Spec.commandSchema)),
|
|
520
534
|
targetName: ITS.Spec.targetName,
|
|
521
535
|
externalSystem: ITS.Spec.externalSystem,
|
|
536
|
+
chapter: chapterOf(ITS.Spec.name),
|
|
522
537
|
}: Reventless.Plugin.inboundTranslationSliceDef))
|
|
523
538
|
|
|
524
539
|
// ── Extensions ───────────────────────────────────────────────────────────
|
|
@@ -67,7 +67,7 @@ function labelFieldsFromStateSchema(entityName, stateSchema) {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChangeSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, extensionsOpt, extensionPointsOpt) {
|
|
70
|
+
function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChangeSlicesOpt, automationSlicesOpt, outboundTranslationSlicesOpt, inboundTranslationSlicesOpt, extensionsOpt, extensionPointsOpt, componentChaptersOpt) {
|
|
71
71
|
let aggregates = aggregatesOpt !== undefined ? aggregatesOpt : [];
|
|
72
72
|
let readModels = readModelsOpt !== undefined ? readModelsOpt : [];
|
|
73
73
|
let stateViewSlices = stateViewSlicesOpt !== undefined ? stateViewSlicesOpt : [];
|
|
@@ -77,6 +77,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
77
77
|
let inboundTranslationSlices = inboundTranslationSlicesOpt !== undefined ? inboundTranslationSlicesOpt : [];
|
|
78
78
|
let extensions = extensionsOpt !== undefined ? extensionsOpt : [];
|
|
79
79
|
let extensionPoints = extensionPointsOpt !== undefined ? extensionPointsOpt : [];
|
|
80
|
+
let componentChapters = componentChaptersOpt !== undefined ? componentChaptersOpt : ({});
|
|
80
81
|
let qualify = (prefix, names) => names.map(n => prefix + "." + n);
|
|
81
82
|
let isCreateCommandName = name => [
|
|
82
83
|
"Add",
|
|
@@ -333,7 +334,8 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
333
334
|
labelField: match[0],
|
|
334
335
|
searchableFields: match[1],
|
|
335
336
|
statusField: statusFieldFromStateSchema(stateSchema),
|
|
336
|
-
visibility: visibilityTag(R.Spec.visibility)
|
|
337
|
+
visibility: visibilityTag(R.Spec.visibility),
|
|
338
|
+
chapter: componentChapters[R.Spec.name]
|
|
337
339
|
};
|
|
338
340
|
});
|
|
339
341
|
let stateViewDefs = stateViewSlices.map((SVS, i) => {
|
|
@@ -351,7 +353,8 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
351
353
|
labelField: match$1[0],
|
|
352
354
|
searchableFields: match$1[1],
|
|
353
355
|
statusField: statusFieldFromStateSchema(stateSchema),
|
|
354
|
-
visibility: visibilityTag(SVS.Spec.visibility)
|
|
356
|
+
visibility: visibilityTag(SVS.Spec.visibility),
|
|
357
|
+
chapter: componentChapters[SVS.Spec.name]
|
|
355
358
|
};
|
|
356
359
|
});
|
|
357
360
|
let stateChangeDefs = stateChangeSlices.map((SCS, i) => {
|
|
@@ -366,7 +369,8 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
366
369
|
consumedEventTypes: consumed,
|
|
367
370
|
linkedViews: linkedSvsFor(produced),
|
|
368
371
|
consistencyRead: consistencyReadFor(consumed),
|
|
369
|
-
events: extractEventDefs(SCS.Spec.eventSchema)
|
|
372
|
+
events: extractEventDefs(SCS.Spec.eventSchema),
|
|
373
|
+
chapter: componentChapters[SCS.Spec.name]
|
|
370
374
|
};
|
|
371
375
|
});
|
|
372
376
|
let aggregateDefs = aggregates.map((A, i) => {
|
|
@@ -379,7 +383,8 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
379
383
|
consumedEventTypes: [],
|
|
380
384
|
linkedViews: linkedSvsFor(produced).concat(linkedReadModelsFor(A.Spec.name)),
|
|
381
385
|
consistencyRead: undefined,
|
|
382
|
-
events: extractEventDefs(A.Spec.eventSchema)
|
|
386
|
+
events: extractEventDefs(A.Spec.eventSchema),
|
|
387
|
+
chapter: componentChapters[A.Spec.name]
|
|
383
388
|
};
|
|
384
389
|
});
|
|
385
390
|
let automationSliceDefs = automationSlices.map(AS => {
|
|
@@ -388,7 +393,8 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
388
393
|
name: AS.Spec.name,
|
|
389
394
|
consumedEventTypes: qualify(name, allConsumedVariants),
|
|
390
395
|
producedCommandTypes: qualify(name, DcbTag$Reventless.extractAllVariantNames(AS.Spec.commandSchema)),
|
|
391
|
-
targetName: AS.Spec.targetName
|
|
396
|
+
targetName: AS.Spec.targetName,
|
|
397
|
+
chapter: componentChapters[AS.Spec.name]
|
|
392
398
|
};
|
|
393
399
|
});
|
|
394
400
|
let outboundTranslationSliceDefs = outboundTranslationSlices.map(OTS => ({
|
|
@@ -396,13 +402,15 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
396
402
|
consumedEventTypes: qualify(name, DcbTag$Reventless.extractVariantNames(OTS.Spec.consumedEventSchema)),
|
|
397
403
|
inboundCommandTypes: qualify(name, DcbTag$Reventless.extractAllVariantNames(OTS.Spec.inboundCommandSchema)),
|
|
398
404
|
targetName: OTS.Spec.targetName,
|
|
399
|
-
externalSystem: OTS.Spec.externalSystem
|
|
405
|
+
externalSystem: OTS.Spec.externalSystem,
|
|
406
|
+
chapter: componentChapters[OTS.Spec.name]
|
|
400
407
|
}));
|
|
401
408
|
let inboundTranslationSliceDefs = inboundTranslationSlices.map(ITS => ({
|
|
402
409
|
name: ITS.Spec.name,
|
|
403
410
|
commandTypes: qualify(name, DcbTag$Reventless.extractAllVariantNames(ITS.Spec.commandSchema)),
|
|
404
411
|
targetName: ITS.Spec.targetName,
|
|
405
|
-
externalSystem: ITS.Spec.externalSystem
|
|
412
|
+
externalSystem: ITS.Spec.externalSystem,
|
|
413
|
+
chapter: componentChapters[ITS.Spec.name]
|
|
406
414
|
}));
|
|
407
415
|
let extensionDefs = extensions.map(E => {
|
|
408
416
|
let delegateNames = E.mappings.map(M => M.delegateName);
|
|
@@ -28,6 +28,7 @@ let qbl: queryableDef = {
|
|
|
28
28
|
searchableFields: ["displayName"],
|
|
29
29
|
statusField: None,
|
|
30
30
|
visibility: None,
|
|
31
|
+
chapter: None,
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
let wbl: writableDef = {
|
|
@@ -38,6 +39,7 @@ let wbl: writableDef = {
|
|
|
38
39
|
producedEventTypes: ["ProductAdded"],
|
|
39
40
|
consumedEventTypes: [],
|
|
40
41
|
events: [{name: "ProductAdded", schema: "{}", references: []}],
|
|
42
|
+
chapter: None,
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
let structure: pluginStructure = {
|
|
@@ -51,6 +53,7 @@ let structure: pluginStructure = {
|
|
|
51
53
|
consumedEventTypes: ["StockLow"],
|
|
52
54
|
producedCommandTypes: ["Restock"],
|
|
53
55
|
targetName: "Product",
|
|
56
|
+
chapter: None,
|
|
54
57
|
},
|
|
55
58
|
],
|
|
56
59
|
outboundTranslationSlices: [
|
|
@@ -60,10 +63,17 @@ let structure: pluginStructure = {
|
|
|
60
63
|
inboundCommandTypes: ["Ship"],
|
|
61
64
|
targetName: None,
|
|
62
65
|
externalSystem: None,
|
|
66
|
+
chapter: None,
|
|
63
67
|
},
|
|
64
68
|
],
|
|
65
69
|
inboundTranslationSlices: [
|
|
66
|
-
{
|
|
70
|
+
{
|
|
71
|
+
name: "FromBilling",
|
|
72
|
+
commandTypes: ["RecordPayment"],
|
|
73
|
+
targetName: "Order",
|
|
74
|
+
externalSystem: None,
|
|
75
|
+
chapter: None,
|
|
76
|
+
},
|
|
67
77
|
],
|
|
68
78
|
extensions: [
|
|
69
79
|
{
|
|
@@ -143,6 +153,61 @@ describe("encodePluginStructureEntry", () => {
|
|
|
143
153
|
testSync("encodes writableDef events", () =>
|
|
144
154
|
expect(json->String.includes("\"events\":[{\"name\":\"ProductAdded\""))->toEqual(true)
|
|
145
155
|
)
|
|
156
|
+
|
|
157
|
+
// Translation-slice externalSystem: absent on the fixture above (both None), so it
|
|
158
|
+
// must serialize as null — the "no external box" case.
|
|
159
|
+
testSync("encodes None outbound externalSystem as null", () =>
|
|
160
|
+
expect(json->String.includes("\"inboundCommandTypes\":[\"Ship\"],\"externalSystem\":null"))->toEqual(
|
|
161
|
+
true,
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
testSync("encodes None inbound externalSystem as null", () =>
|
|
166
|
+
expect(json->String.includes("\"commandTypes\":[\"RecordPayment\"],\"externalSystem\":null"))->toEqual(
|
|
167
|
+
true,
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// A deployed-graph consumer draws the external-system boundary box off this field, so
|
|
173
|
+
// a slice whose spec sets `externalSystem = Some("X")` must surface "X" on the wire for
|
|
174
|
+
// both directions — the round-trip the plan pins.
|
|
175
|
+
describe("translation-slice externalSystem round-trip", () => {
|
|
176
|
+
let externalStructure: pluginStructure = {
|
|
177
|
+
...structure,
|
|
178
|
+
outboundTranslationSlices: [
|
|
179
|
+
{
|
|
180
|
+
name: "ToShipper",
|
|
181
|
+
consumedEventTypes: ["OrderPlaced"],
|
|
182
|
+
inboundCommandTypes: ["Ship"],
|
|
183
|
+
targetName: None,
|
|
184
|
+
externalSystem: Some("ShipperGateway"),
|
|
185
|
+
chapter: None,
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
inboundTranslationSlices: [
|
|
189
|
+
{
|
|
190
|
+
name: "FromBilling",
|
|
191
|
+
commandTypes: ["RecordPayment"],
|
|
192
|
+
targetName: "Order",
|
|
193
|
+
externalSystem: Some("BillingProvider"),
|
|
194
|
+
chapter: None,
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
}
|
|
198
|
+
let externalJson =
|
|
199
|
+
Platform_ComponentDefinitionsApi.encodePluginStructureEntry(
|
|
200
|
+
~pluginId="Catalog",
|
|
201
|
+
externalStructure,
|
|
202
|
+
)->JSON.stringify
|
|
203
|
+
|
|
204
|
+
testSync("surfaces Some outbound externalSystem as its inner string", () =>
|
|
205
|
+
expect(externalJson->String.includes("\"externalSystem\":\"ShipperGateway\""))->toEqual(true)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
testSync("surfaces Some inbound externalSystem as its inner string", () =>
|
|
209
|
+
expect(externalJson->String.includes("\"externalSystem\":\"BillingProvider\""))->toEqual(true)
|
|
210
|
+
)
|
|
146
211
|
})
|
|
147
212
|
|
|
148
213
|
describe("visibility filtering (deployed AutoUI hides Internal)", () => {
|
|
@@ -159,6 +224,7 @@ describe("visibility filtering (deployed AutoUI hides Internal)", () => {
|
|
|
159
224
|
searchableFields: ["name"],
|
|
160
225
|
statusField: None,
|
|
161
226
|
visibility: Some("Internal"),
|
|
227
|
+
chapter: None,
|
|
162
228
|
}
|
|
163
229
|
let mixed: pluginStructure = {
|
|
164
230
|
...structure,
|
|
@@ -199,6 +265,7 @@ describe("allowedStates + statusField populated", () => {
|
|
|
199
265
|
searchableFields: ["name"],
|
|
200
266
|
statusField: Some("status"),
|
|
201
267
|
visibility: None,
|
|
268
|
+
chapter: None,
|
|
202
269
|
}
|
|
203
270
|
|
|
204
271
|
let wblWithStates: writableDef = {
|
|
@@ -209,6 +276,7 @@ describe("allowedStates + statusField populated", () => {
|
|
|
209
276
|
producedEventTypes: [],
|
|
210
277
|
consumedEventTypes: [],
|
|
211
278
|
events: [],
|
|
279
|
+
chapter: None,
|
|
212
280
|
}
|
|
213
281
|
|
|
214
282
|
let structureWithStates: pluginStructure = {
|
|
@@ -39,7 +39,8 @@ let qbl = {
|
|
|
39
39
|
labelField: "displayName",
|
|
40
40
|
searchableFields: qbl_searchableFields,
|
|
41
41
|
statusField: undefined,
|
|
42
|
-
visibility: undefined
|
|
42
|
+
visibility: undefined,
|
|
43
|
+
chapter: undefined
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
let wbl_commands = [cmd];
|
|
@@ -63,7 +64,8 @@ let wbl = {
|
|
|
63
64
|
consumedEventTypes: wbl_consumedEventTypes,
|
|
64
65
|
linkedViews: wbl_linkedViews,
|
|
65
66
|
consistencyRead: undefined,
|
|
66
|
-
events: wbl_events
|
|
67
|
+
events: wbl_events,
|
|
68
|
+
chapter: undefined
|
|
67
69
|
};
|
|
68
70
|
|
|
69
71
|
let structure_readModels = [qbl];
|
|
@@ -78,7 +80,8 @@ let structure_automationSlices = [{
|
|
|
78
80
|
name: "Restocker",
|
|
79
81
|
consumedEventTypes: ["StockLow"],
|
|
80
82
|
producedCommandTypes: ["Restock"],
|
|
81
|
-
targetName: "Product"
|
|
83
|
+
targetName: "Product",
|
|
84
|
+
chapter: undefined
|
|
82
85
|
}];
|
|
83
86
|
|
|
84
87
|
let structure_outboundTranslationSlices = [{
|
|
@@ -86,14 +89,16 @@ let structure_outboundTranslationSlices = [{
|
|
|
86
89
|
consumedEventTypes: ["OrderPlaced"],
|
|
87
90
|
inboundCommandTypes: ["Ship"],
|
|
88
91
|
targetName: undefined,
|
|
89
|
-
externalSystem: undefined
|
|
92
|
+
externalSystem: undefined,
|
|
93
|
+
chapter: undefined
|
|
90
94
|
}];
|
|
91
95
|
|
|
92
96
|
let structure_inboundTranslationSlices = [{
|
|
93
97
|
name: "FromBilling",
|
|
94
98
|
commandTypes: ["RecordPayment"],
|
|
95
99
|
targetName: "Order",
|
|
96
|
-
externalSystem: undefined
|
|
100
|
+
externalSystem: undefined,
|
|
101
|
+
chapter: undefined
|
|
97
102
|
}];
|
|
98
103
|
|
|
99
104
|
let structure_extensions = [{
|
|
@@ -168,6 +173,54 @@ globalThis.describe("encodePluginStructureEntry", () => {
|
|
|
168
173
|
globalThis.test("encodes writableDef events", () => {
|
|
169
174
|
globalThis.expect(json.includes("\"events\":[{\"name\":\"ProductAdded\"")).toEqual(true);
|
|
170
175
|
});
|
|
176
|
+
globalThis.test("encodes None outbound externalSystem as null", () => {
|
|
177
|
+
globalThis.expect(json.includes("\"inboundCommandTypes\":[\"Ship\"],\"externalSystem\":null")).toEqual(true);
|
|
178
|
+
});
|
|
179
|
+
globalThis.test("encodes None inbound externalSystem as null", () => {
|
|
180
|
+
globalThis.expect(json.includes("\"commandTypes\":[\"RecordPayment\"],\"externalSystem\":null")).toEqual(true);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
globalThis.describe("translation-slice externalSystem round-trip", () => {
|
|
185
|
+
let externalStructure_readModels = structure_readModels;
|
|
186
|
+
let externalStructure_stateViewSlices = structure_stateViewSlices;
|
|
187
|
+
let externalStructure_stateChangeSlices = structure_stateChangeSlices;
|
|
188
|
+
let externalStructure_aggregates = structure_aggregates;
|
|
189
|
+
let externalStructure_automationSlices = structure_automationSlices;
|
|
190
|
+
let externalStructure_outboundTranslationSlices = [{
|
|
191
|
+
name: "ToShipper",
|
|
192
|
+
consumedEventTypes: ["OrderPlaced"],
|
|
193
|
+
inboundCommandTypes: ["Ship"],
|
|
194
|
+
targetName: undefined,
|
|
195
|
+
externalSystem: "ShipperGateway",
|
|
196
|
+
chapter: undefined
|
|
197
|
+
}];
|
|
198
|
+
let externalStructure_inboundTranslationSlices = [{
|
|
199
|
+
name: "FromBilling",
|
|
200
|
+
commandTypes: ["RecordPayment"],
|
|
201
|
+
targetName: "Order",
|
|
202
|
+
externalSystem: "BillingProvider",
|
|
203
|
+
chapter: undefined
|
|
204
|
+
}];
|
|
205
|
+
let externalStructure_extensions = structure_extensions;
|
|
206
|
+
let externalStructure = {
|
|
207
|
+
readModels: externalStructure_readModels,
|
|
208
|
+
stateViewSlices: externalStructure_stateViewSlices,
|
|
209
|
+
stateChangeSlices: externalStructure_stateChangeSlices,
|
|
210
|
+
aggregates: externalStructure_aggregates,
|
|
211
|
+
automationSlices: externalStructure_automationSlices,
|
|
212
|
+
outboundTranslationSlices: externalStructure_outboundTranslationSlices,
|
|
213
|
+
inboundTranslationSlices: externalStructure_inboundTranslationSlices,
|
|
214
|
+
extensions: externalStructure_extensions,
|
|
215
|
+
extensionPoints: undefined
|
|
216
|
+
};
|
|
217
|
+
let externalJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry("Catalog", externalStructure));
|
|
218
|
+
globalThis.test("surfaces Some outbound externalSystem as its inner string", () => {
|
|
219
|
+
globalThis.expect(externalJson.includes("\"externalSystem\":\"ShipperGateway\"")).toEqual(true);
|
|
220
|
+
});
|
|
221
|
+
globalThis.test("surfaces Some inbound externalSystem as its inner string", () => {
|
|
222
|
+
globalThis.expect(externalJson.includes("\"externalSystem\":\"BillingProvider\"")).toEqual(true);
|
|
223
|
+
});
|
|
171
224
|
});
|
|
172
225
|
|
|
173
226
|
globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", () => {
|
|
@@ -184,7 +237,8 @@ globalThis.describe("visibility filtering (deployed AutoUI hides Internal)", ()
|
|
|
184
237
|
labelField: "name",
|
|
185
238
|
searchableFields: internalQbl_searchableFields,
|
|
186
239
|
statusField: undefined,
|
|
187
|
-
visibility: internalQbl_visibility
|
|
240
|
+
visibility: internalQbl_visibility,
|
|
241
|
+
chapter: undefined
|
|
188
242
|
};
|
|
189
243
|
let mixed_readModels = [
|
|
190
244
|
qbl,
|
|
@@ -245,7 +299,8 @@ globalThis.describe("allowedStates + statusField populated", () => {
|
|
|
245
299
|
labelField: "name",
|
|
246
300
|
searchableFields: qblWithStatus_searchableFields,
|
|
247
301
|
statusField: qblWithStatus_statusField,
|
|
248
|
-
visibility: undefined
|
|
302
|
+
visibility: undefined,
|
|
303
|
+
chapter: undefined
|
|
249
304
|
};
|
|
250
305
|
let wblWithStates_commands = [cmdWithStates];
|
|
251
306
|
let wblWithStates_producedEventTypes = [];
|
|
@@ -259,7 +314,8 @@ globalThis.describe("allowedStates + statusField populated", () => {
|
|
|
259
314
|
consumedEventTypes: wblWithStates_consumedEventTypes,
|
|
260
315
|
linkedViews: wblWithStates_linkedViews,
|
|
261
316
|
consistencyRead: undefined,
|
|
262
|
-
events: wblWithStates_events
|
|
317
|
+
events: wblWithStates_events,
|
|
318
|
+
chapter: undefined
|
|
263
319
|
};
|
|
264
320
|
let structureWithStates_readModels = [qblWithStatus];
|
|
265
321
|
let structureWithStates_stateViewSlices = [];
|
|
@@ -177,7 +177,7 @@ globalThis.describe("ReadModel visibility filter", () => {
|
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
globalThis.describe("Plugin_Structure.make — visibility tagging", () => {
|
|
180
|
-
let structure = Plugin_Structure$ReventlessCore.make("VisibilityPlugin", undefined, readModels, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
180
|
+
let structure = Plugin_Structure$ReventlessCore.make("VisibilityPlugin", undefined, readModels, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
181
181
|
globalThis.test("readModels carries both Public and Internal entries", () => {
|
|
182
182
|
globalThis.expect(structure.readModels.length).toBe(2);
|
|
183
183
|
});
|
|
@@ -295,4 +295,38 @@ describe("Plugin_Structure.make — Phase 2 graph fields", () => {
|
|
|
295
295
|
)->toBe(None)
|
|
296
296
|
})
|
|
297
297
|
})
|
|
298
|
+
|
|
299
|
+
// The generator captures each component's chapter (source-folder grouping band)
|
|
300
|
+
// and passes it as ~componentChapters; Plugin_Structure looks it up by Spec.name
|
|
301
|
+
// and threads it onto every def, so a deployed-graph consumer can render chapter
|
|
302
|
+
// bands without workspace access. See docs/plans/deployed-chapter-grouping.md.
|
|
303
|
+
describe("componentChapters threading", () => {
|
|
304
|
+
let chaptered = Plugin_Structure.make(
|
|
305
|
+
~name="TestPlugin",
|
|
306
|
+
~stateChangeSlices=[module(PsPlaceOrderSlice), module(PsShipOrderSlice)],
|
|
307
|
+
~stateViewSlices=[module(PsOrdersViewSlice)],
|
|
308
|
+
// PlaceOrder + Orders live under a chapter; ShipOrder does not.
|
|
309
|
+
~componentChapters=Dict.fromArray([("PlaceOrder", "Order"), ("Orders", "Order")]),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
testSync("a chaptered state-change slice carries Some(chapter)", () => {
|
|
313
|
+
let placeOrder = chaptered.stateChangeSlices->Array.getUnsafe(0)
|
|
314
|
+
expect(placeOrder.chapter)->toEqual(Some("Order"))
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
testSync("a chaptered state-view slice carries Some(chapter)", () => {
|
|
318
|
+
let orders = chaptered.stateViewSlices->Array.getUnsafe(0)
|
|
319
|
+
expect(orders.chapter)->toEqual(Some("Order"))
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
testSync("a component absent from the map carries None", () => {
|
|
323
|
+
let shipOrder = chaptered.stateChangeSlices->Array.getUnsafe(1)
|
|
324
|
+
expect(shipOrder.chapter)->toEqual(None)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
testSync("no ~componentChapters → every def is chapterless (None)", () => {
|
|
328
|
+
let placeOrder = structure.stateChangeSlices->Array.getUnsafe(0)
|
|
329
|
+
expect(placeOrder.chapter)->toEqual(None)
|
|
330
|
+
})
|
|
331
|
+
})
|
|
298
332
|
})
|
|
@@ -183,7 +183,7 @@ let structure = Plugin_Structure$ReventlessCore.make("TestPlugin", undefined, un
|
|
|
183
183
|
], [
|
|
184
184
|
PsPlaceOrderSlice,
|
|
185
185
|
PsShipOrderSlice
|
|
186
|
-
], undefined, undefined, undefined, undefined, undefined);
|
|
186
|
+
], undefined, undefined, undefined, undefined, undefined, undefined);
|
|
187
187
|
|
|
188
188
|
globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
|
|
189
189
|
globalThis.describe("stateChangeSlices", () => {
|
|
@@ -374,6 +374,37 @@ globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
|
|
|
374
374
|
globalThis.expect(Stdlib_Option.flatMap(orderIdField, s => getProperty(s, "x-reventless-id"))).toBe(undefined);
|
|
375
375
|
});
|
|
376
376
|
});
|
|
377
|
+
globalThis.describe("componentChapters threading", () => {
|
|
378
|
+
let chaptered = Plugin_Structure$ReventlessCore.make("TestPlugin", undefined, undefined, [PsOrdersViewSlice], [
|
|
379
|
+
PsPlaceOrderSlice,
|
|
380
|
+
PsShipOrderSlice
|
|
381
|
+
], undefined, undefined, undefined, undefined, undefined, Object.fromEntries([
|
|
382
|
+
[
|
|
383
|
+
"PlaceOrder",
|
|
384
|
+
"Order"
|
|
385
|
+
],
|
|
386
|
+
[
|
|
387
|
+
"Orders",
|
|
388
|
+
"Order"
|
|
389
|
+
]
|
|
390
|
+
]));
|
|
391
|
+
globalThis.test("a chaptered state-change slice carries Some(chapter)", () => {
|
|
392
|
+
let placeOrder = chaptered.stateChangeSlices[0];
|
|
393
|
+
globalThis.expect(placeOrder.chapter).toEqual("Order");
|
|
394
|
+
});
|
|
395
|
+
globalThis.test("a chaptered state-view slice carries Some(chapter)", () => {
|
|
396
|
+
let orders = chaptered.stateViewSlices[0];
|
|
397
|
+
globalThis.expect(orders.chapter).toEqual("Order");
|
|
398
|
+
});
|
|
399
|
+
globalThis.test("a component absent from the map carries None", () => {
|
|
400
|
+
let shipOrder = chaptered.stateChangeSlices[1];
|
|
401
|
+
globalThis.expect(shipOrder.chapter).toEqual(undefined);
|
|
402
|
+
});
|
|
403
|
+
globalThis.test("no ~componentChapters → every def is chapterless (None)", () => {
|
|
404
|
+
let placeOrder = structure.stateChangeSlices[0];
|
|
405
|
+
globalThis.expect(placeOrder.chapter).toEqual(undefined);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
377
408
|
});
|
|
378
409
|
|
|
379
410
|
export {
|