@reventlessdev/reventless-core 3.0.0-alpha.134 → 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 +7 -0
- package/package.json +6 -6
- package/src/components/ReadModel/ReadModel.res +5 -0
- package/src/components/ReadModel/ReadModel_Builder.res +12 -0
- package/src/components/ReadModel/ReadModel_Builder.res.mjs +3 -0
- package/src/plugin/component/Plugin_Structure.res +9 -2
- package/src/plugin/component/Plugin_Structure.res.mjs +7 -4
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +2 -1
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +4 -2
- package/tests/plugin/ManifestVisibilityTest.res +14 -0
- package/tests/plugin/ManifestVisibilityTest.res.mjs +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
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.135 (2026-06-29)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* external-system boxes for translation slices (Event Graph data) ([3f8ad39](https://github.com/ReventlessDev/reventless-core/commit/3f8ad39b78a3cb1182d59a0e1fb203b7dcb7379b))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 3.0.0-alpha.134 (2026-06-27)
|
|
7
14
|
|
|
8
15
|
### Bug Fixes
|
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.135",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -29,21 +29,21 @@
|
|
|
29
29
|
"sury": "11.0.0-alpha.4",
|
|
30
30
|
"uuid": "^13.0.0",
|
|
31
31
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.23",
|
|
32
|
+
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.10",
|
|
32
33
|
"@reventlessdev/rescript-fast-csv": "1.2.0-alpha.11",
|
|
33
34
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.3",
|
|
34
35
|
"@reventlessdev/rescript-node-streams": "1.1.0-alpha.10",
|
|
35
|
-
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.10",
|
|
36
36
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
|
|
37
37
|
"@reventlessdev/rescript-ssh2": "1.1.0-alpha.11",
|
|
38
|
-
"@reventlessdev/
|
|
38
|
+
"@reventlessdev/rescript-uuid": "1.1.0-alpha.13",
|
|
39
39
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.21",
|
|
40
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
41
|
-
"@reventlessdev/
|
|
40
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.62",
|
|
41
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.82"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "^12.3.0",
|
|
45
45
|
"sury-ppx": "11.0.0-alpha.2",
|
|
46
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
46
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.48"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"rescript": "^12.3.0"
|
|
@@ -19,6 +19,11 @@ module type T = {
|
|
|
19
19
|
~opts: Pulumi.ComponentResource.options=?,
|
|
20
20
|
) => component
|
|
21
21
|
let sourceNames: array<string>
|
|
22
|
+
/** Bare event-variant names consumed across every projection mapping's source event
|
|
23
|
+
schema — names the actual events (so a DCB-log-sourced mapping is captured), unlike
|
|
24
|
+
`sourceNames` which only lists source aggregate / DCB-log names. Surfaced by
|
|
25
|
+
Plugin_Structure as `queryableDef.consumedEventTypes`. */
|
|
26
|
+
let consumedEventNames: array<string>
|
|
22
27
|
let outputs: component => outputs
|
|
23
28
|
let operations: component => Pulumi.Output.t<operations>
|
|
24
29
|
let finish: unit => unit
|
|
@@ -25,6 +25,18 @@ module Make = (
|
|
|
25
25
|
->Belt.Set.String.fromArray
|
|
26
26
|
->Belt.Set.String.toArray
|
|
27
27
|
|
|
28
|
+
// Bare event-variant names consumed across every mapping's source event schema — the
|
|
29
|
+
// union the reflected graph turns into Event→ReadModel "projects" edges (see
|
|
30
|
+
// Plugin_Structure / DomainGraph). A DCB-log-sourced mapping declares a narrow event type
|
|
31
|
+
// (e.g. just `OrderPlaced`), so this captures cross-source events `sourceNames` can't name.
|
|
32
|
+
let consumedEventNames =
|
|
33
|
+
Mappings.mappings
|
|
34
|
+
->Array.flatMap((module(Mapping: Mappings.Mapping)) =>
|
|
35
|
+
Reventless.DcbTag.extractVariantNames(Mapping.sourceEventSchema->S.castToUnknown)
|
|
36
|
+
)
|
|
37
|
+
->Belt.Set.String.fromArray
|
|
38
|
+
->Belt.Set.String.toArray
|
|
39
|
+
|
|
28
40
|
type api = QueryDbStorage.api
|
|
29
41
|
type role = QueryDbStorage.role
|
|
30
42
|
type component = ReadModel.component
|
|
@@ -4,6 +4,7 @@ import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output
|
|
|
4
4
|
import * as Belt_SetString from "@rescript/runtime/lib/es6/Belt_SetString.js";
|
|
5
5
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
6
6
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
7
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
7
8
|
import * as Component$ReventlessCore from "../Component.res.mjs";
|
|
8
9
|
import * as ReadModel$ReventlessCore from "./ReadModel.res.mjs";
|
|
9
10
|
import * as EventTopic$ReventlessCore from "../EventTopic/EventTopic.res.mjs";
|
|
@@ -15,6 +16,7 @@ import * as EventCollector_Builder$ReventlessCore from "../EventCollector/EventC
|
|
|
15
16
|
function Make(Spec) {
|
|
16
17
|
return Mappings => (RuntimeEnvironment => (QueryDbStorage => (QueryDbResolvers => (EventCollectorChannel => (EventCollectorRuntimeBuilder => {
|
|
17
18
|
let sourceNames = Belt_SetString.toArray(Belt_SetString.fromArray(Mappings.mappings.map(Mapping => Mapping.sourceName)));
|
|
19
|
+
let consumedEventNames = Belt_SetString.toArray(Belt_SetString.fromArray(Mappings.mappings.flatMap(Mapping => DcbTag$Reventless.extractVariantNames(Mapping.sourceEventSchema))));
|
|
18
20
|
let make = (api, apiRole, allEventTopics, opts) => Component$ReventlessCore.make(ComponentType$ReventlessCore.toString(ReadModel$ReventlessCore.componentType), Spec.name, (extra, extra$1) => {
|
|
19
21
|
let opts_parent = Component$ReventlessCore.toPulumiResource(extra);
|
|
20
22
|
let opts = {
|
|
@@ -88,6 +90,7 @@ function Make(Spec) {
|
|
|
88
90
|
EventCollectorRuntimeBuilder: EventCollectorRuntimeBuilder,
|
|
89
91
|
make: make,
|
|
90
92
|
sourceNames: sourceNames,
|
|
93
|
+
consumedEventNames: consumedEventNames,
|
|
91
94
|
outputs: Component$ReventlessCore.outputs,
|
|
92
95
|
operations: Component$ReventlessCore.operations,
|
|
93
96
|
finish: finish
|
|
@@ -390,12 +390,17 @@ let make = (
|
|
|
390
390
|
~entityName=R.Spec.name,
|
|
391
391
|
stateSchema,
|
|
392
392
|
)
|
|
393
|
+
// The events this read model projects from, qualified to the plugin's event ids so
|
|
394
|
+
// they match the producers' nodes in the graph. Was empty — which dropped projection
|
|
395
|
+
// edges for any event reaching the read model via a DCB-log-sourced mapping (a classic
|
|
396
|
+
// aggregate→view link is also drawn from the producer's linkedViews, deduped downstream).
|
|
397
|
+
let consumed = qualify(~prefix=name, R.consumedEventNames)
|
|
393
398
|
({
|
|
394
399
|
Reventless.Plugin.name: R.Spec.name,
|
|
395
400
|
queryField: qf.listFieldName,
|
|
396
401
|
schema: stateSchema->SuryToJsonSchema.deriveObjectSchema->JSON.stringify,
|
|
397
|
-
consumedEventTypes:
|
|
398
|
-
linkedWriteSide:
|
|
402
|
+
consumedEventTypes: consumed,
|
|
403
|
+
linkedWriteSide: linkedWriteSideFor(consumed),
|
|
399
404
|
labelField,
|
|
400
405
|
searchableFields,
|
|
401
406
|
statusField: statusFieldFromStateSchema(stateSchema),
|
|
@@ -497,6 +502,7 @@ let make = (
|
|
|
497
502
|
consumedEventTypes: qualify(~prefix=name, eventVariantNames(OTS.Spec.consumedEventSchema)),
|
|
498
503
|
inboundCommandTypes: qualify(~prefix=name, commandVariantNames(OTS.Spec.inboundCommandSchema)),
|
|
499
504
|
targetName: OTS.Spec.targetName,
|
|
505
|
+
externalSystem: OTS.Spec.externalSystem,
|
|
500
506
|
}: Reventless.Plugin.outboundTranslationSliceDef))
|
|
501
507
|
|
|
502
508
|
// ── Inbound translation slices ────────────────────────────────────────────
|
|
@@ -506,6 +512,7 @@ let make = (
|
|
|
506
512
|
Reventless.Plugin.name: ITS.Spec.name,
|
|
507
513
|
commandTypes: qualify(~prefix=name, commandVariantNames(ITS.Spec.commandSchema)),
|
|
508
514
|
targetName: ITS.Spec.targetName,
|
|
515
|
+
externalSystem: ITS.Spec.externalSystem,
|
|
509
516
|
}: Reventless.Plugin.inboundTranslationSliceDef))
|
|
510
517
|
|
|
511
518
|
// ── Extensions ───────────────────────────────────────────────────────────
|
|
@@ -323,12 +323,13 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
323
323
|
let qf = Api_Naming$ReventlessCore.queryFieldNamesForReadModel(name, R.Spec.name, undefined);
|
|
324
324
|
let stateSchema = R.Spec.stateSchema;
|
|
325
325
|
let match = labelFieldsFromStateSchema(R.Spec.name, stateSchema);
|
|
326
|
+
let consumed = qualify(name, R.consumedEventNames);
|
|
326
327
|
return {
|
|
327
328
|
name: R.Spec.name,
|
|
328
329
|
queryField: qf.listFieldName,
|
|
329
330
|
schema: JSON.stringify(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(stateSchema)),
|
|
330
|
-
consumedEventTypes:
|
|
331
|
-
linkedWriteSide:
|
|
331
|
+
consumedEventTypes: consumed,
|
|
332
|
+
linkedWriteSide: linkedWriteSideFor(consumed),
|
|
332
333
|
labelField: match[0],
|
|
333
334
|
searchableFields: match[1],
|
|
334
335
|
statusField: statusFieldFromStateSchema(stateSchema),
|
|
@@ -394,12 +395,14 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
394
395
|
name: OTS.Spec.name,
|
|
395
396
|
consumedEventTypes: qualify(name, DcbTag$Reventless.extractVariantNames(OTS.Spec.consumedEventSchema)),
|
|
396
397
|
inboundCommandTypes: qualify(name, DcbTag$Reventless.extractAllVariantNames(OTS.Spec.inboundCommandSchema)),
|
|
397
|
-
targetName: OTS.Spec.targetName
|
|
398
|
+
targetName: OTS.Spec.targetName,
|
|
399
|
+
externalSystem: OTS.Spec.externalSystem
|
|
398
400
|
}));
|
|
399
401
|
let inboundTranslationSliceDefs = inboundTranslationSlices.map(ITS => ({
|
|
400
402
|
name: ITS.Spec.name,
|
|
401
403
|
commandTypes: qualify(name, DcbTag$Reventless.extractAllVariantNames(ITS.Spec.commandSchema)),
|
|
402
|
-
targetName: ITS.Spec.targetName
|
|
404
|
+
targetName: ITS.Spec.targetName,
|
|
405
|
+
externalSystem: ITS.Spec.externalSystem
|
|
403
406
|
}));
|
|
404
407
|
let extensionDefs = extensions.map(E => {
|
|
405
408
|
let delegateNames = E.mappings.map(M => M.delegateName);
|
|
@@ -59,10 +59,11 @@ let structure: pluginStructure = {
|
|
|
59
59
|
consumedEventTypes: ["OrderPlaced"],
|
|
60
60
|
inboundCommandTypes: ["Ship"],
|
|
61
61
|
targetName: None,
|
|
62
|
+
externalSystem: None,
|
|
62
63
|
},
|
|
63
64
|
],
|
|
64
65
|
inboundTranslationSlices: [
|
|
65
|
-
{name: "FromBilling", commandTypes: ["RecordPayment"], targetName: "Order"},
|
|
66
|
+
{name: "FromBilling", commandTypes: ["RecordPayment"], targetName: "Order", externalSystem: None},
|
|
66
67
|
],
|
|
67
68
|
extensions: [
|
|
68
69
|
{
|
|
@@ -85,13 +85,15 @@ let structure_outboundTranslationSlices = [{
|
|
|
85
85
|
name: "ToShipper",
|
|
86
86
|
consumedEventTypes: ["OrderPlaced"],
|
|
87
87
|
inboundCommandTypes: ["Ship"],
|
|
88
|
-
targetName: undefined
|
|
88
|
+
targetName: undefined,
|
|
89
|
+
externalSystem: undefined
|
|
89
90
|
}];
|
|
90
91
|
|
|
91
92
|
let structure_inboundTranslationSlices = [{
|
|
92
93
|
name: "FromBilling",
|
|
93
94
|
commandTypes: ["RecordPayment"],
|
|
94
|
-
targetName: "Order"
|
|
95
|
+
targetName: "Order",
|
|
96
|
+
externalSystem: undefined
|
|
95
97
|
}];
|
|
96
98
|
|
|
97
99
|
let structure_extensions = [{
|
|
@@ -45,6 +45,9 @@ module PublicReadModel: ReventlessInfra.ReadModel.T
|
|
|
45
45
|
type role = unit
|
|
46
46
|
type component
|
|
47
47
|
let sourceNames: array<string> = []
|
|
48
|
+
// A read model that projects an event reaching it via a (DCB-log-sourced) mapping — the
|
|
49
|
+
// case Plugin_Structure must surface as a qualified `consumedEventTypes` entry.
|
|
50
|
+
let consumedEventNames: array<string> = ["OrderPlaced"]
|
|
48
51
|
let make = (~api as _, ~apiRole as _, ~allEventTopics as _, ~opts as _=?): component =>
|
|
49
52
|
Obj.magic(0)
|
|
50
53
|
let outputs = (_: component): ReventlessInfra.ReadModel.outputs => Obj.magic(0)
|
|
@@ -60,6 +63,7 @@ module InternalReadModel: ReventlessInfra.ReadModel.T
|
|
|
60
63
|
type role = unit
|
|
61
64
|
type component
|
|
62
65
|
let sourceNames: array<string> = []
|
|
66
|
+
let consumedEventNames: array<string> = []
|
|
63
67
|
let make = (~api as _, ~apiRole as _, ~allEventTopics as _, ~opts as _=?): component =>
|
|
64
68
|
Obj.magic(0)
|
|
65
69
|
let outputs = (_: component): ReventlessInfra.ReadModel.outputs => Obj.magic(0)
|
|
@@ -128,6 +132,16 @@ describe("Plugin_Structure.make — visibility tagging", () => {
|
|
|
128
132
|
expect(internal->Option.map(d => d.visibility))->toEqual(Some(Some("Internal")))
|
|
129
133
|
})
|
|
130
134
|
|
|
135
|
+
testSync("a read model's mapping-consumed events surface as qualified consumedEventTypes", () => {
|
|
136
|
+
// Q6 #2: a classic read model fed via a (DCB-log-sourced) mapping now reflects the
|
|
137
|
+
// events it projects from, qualified to the plugin — so DomainGraph draws the
|
|
138
|
+
// Event→ReadModel projection edge (e.g. OrderPlaced → Customers).
|
|
139
|
+
let public = structure.readModels->Array.find(d => d.name == "PublicView")
|
|
140
|
+
expect(public->Option.map(d => d.consumedEventTypes))->toEqual(
|
|
141
|
+
Some(["VisibilityPlugin.OrderPlaced"]),
|
|
142
|
+
)
|
|
143
|
+
})
|
|
144
|
+
|
|
131
145
|
testSync("aggregates / stateViewSlices stay empty when none are passed (sanity)", () => {
|
|
132
146
|
expect((
|
|
133
147
|
structure.aggregates->Array.length,
|
|
@@ -53,6 +53,8 @@ let InternalSpec = {
|
|
|
53
53
|
|
|
54
54
|
let sourceNames = [];
|
|
55
55
|
|
|
56
|
+
let consumedEventNames = ["OrderPlaced"];
|
|
57
|
+
|
|
56
58
|
function make(param, param$1, param$2, param$3) {
|
|
57
59
|
return 0;
|
|
58
60
|
}
|
|
@@ -89,6 +91,7 @@ let PublicReadModel_Spec = {
|
|
|
89
91
|
let PublicReadModel = {
|
|
90
92
|
Spec: PublicReadModel_Spec,
|
|
91
93
|
sourceNames: sourceNames,
|
|
94
|
+
consumedEventNames: consumedEventNames,
|
|
92
95
|
make: make,
|
|
93
96
|
outputs: outputs,
|
|
94
97
|
operations: operations,
|
|
@@ -97,6 +100,8 @@ let PublicReadModel = {
|
|
|
97
100
|
|
|
98
101
|
let sourceNames$1 = [];
|
|
99
102
|
|
|
103
|
+
let consumedEventNames$1 = [];
|
|
104
|
+
|
|
100
105
|
function make$1(param, param$1, param$2, param$3) {
|
|
101
106
|
return 0;
|
|
102
107
|
}
|
|
@@ -133,6 +138,7 @@ let InternalReadModel_Spec = {
|
|
|
133
138
|
let InternalReadModel = {
|
|
134
139
|
Spec: InternalReadModel_Spec,
|
|
135
140
|
sourceNames: sourceNames$1,
|
|
141
|
+
consumedEventNames: consumedEventNames$1,
|
|
136
142
|
make: make$1,
|
|
137
143
|
outputs: outputs$1,
|
|
138
144
|
operations: operations$1,
|
|
@@ -183,6 +189,10 @@ globalThis.describe("Plugin_Structure.make — visibility tagging", () => {
|
|
|
183
189
|
let internal = structure.readModels.find(d => d.name === "InternalView");
|
|
184
190
|
globalThis.expect(Stdlib_Option.map(internal, d => d.visibility)).toEqual("Internal");
|
|
185
191
|
});
|
|
192
|
+
globalThis.test("a read model's mapping-consumed events surface as qualified consumedEventTypes", () => {
|
|
193
|
+
let $$public = structure.readModels.find(d => d.name === "PublicView");
|
|
194
|
+
globalThis.expect(Stdlib_Option.map($$public, d => d.consumedEventTypes)).toEqual(["VisibilityPlugin.OrderPlaced"]);
|
|
195
|
+
});
|
|
186
196
|
globalThis.test("aggregates / stateViewSlices stay empty when none are passed (sanity)", () => {
|
|
187
197
|
globalThis.expect([
|
|
188
198
|
structure.aggregates.length,
|