@reventlessdev/reventless-gwt 1.0.0-alpha.85 → 1.0.0-alpha.87
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 +30 -0
- package/package.json +3 -3
- package/src/DomainDeadCode.res +7 -2
- package/src/DomainDeadCode.res.mjs +3 -0
- package/src/DomainGraph.res +0 -0
- package/src/DomainGraph.res.mjs +0 -0
- package/src/FormatterVsCode.res +11 -6
- package/src/FormatterVsCode.res.mjs +3 -3
- package/tests/DomainGraphTest.res +50 -49
- package/tests/DomainGraphTest.res.mjs +38 -37
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,36 @@
|
|
|
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
|
+
# 1.0.0-alpha.87 (2026-07-07)
|
|
7
|
+
|
|
8
|
+
* feat(vscode-protocol)!: typed kind vocabularies (protocol v11, PascalCase wire) ([c087e76](https://github.com/ReventlessDev/reventless-core/commit/c087e76b8e2ed02ba98e7b0ce5c63eed35b39878))
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* every kind vocabulary is uniformly PascalCase on the wire
|
|
13
|
+
(handles→Handles, file→File, …) and protocolVersion bumps to 11. The event
|
|
14
|
+
envelope is unchanged; unknown kinds degrade gracefully. Consumers keying on
|
|
15
|
+
kind strings must respell. The new minCompatibleProtocol/isCompatible exports
|
|
16
|
+
pin the advisory (warn, don't refuse) compat policy.
|
|
17
|
+
|
|
18
|
+
- GraphOps predicates/baseKind/isFqKind become exhaustive matches; read edges typed
|
|
19
|
+
- gwt emitters construct variants (DomainGraph, FormatterVsCode, DomainDeadCode)
|
|
20
|
+
- golden fixture regenerated; one appended Other* unknown-kind sample per typed field
|
|
21
|
+
- fixes two sample values that never occurred on the wire (orphanEvent, notEqual)
|
|
22
|
+
- BridgeDriftTest pins both hand-written genType bridges against src/*.gen.ts
|
|
23
|
+
(surfaced missing position/failLocation re-exports — added)
|
|
24
|
+
- plan → docs/plans/done/vscode-protocol-hardening.md
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# 1.0.0-alpha.86 (2026-07-06)
|
|
29
|
+
|
|
30
|
+
**Note:** Version bump only for package @reventlessdev/reventless-gwt
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
6
36
|
# 1.0.0-alpha.85 (2026-07-06)
|
|
7
37
|
|
|
8
38
|
**Note:** Version bump only for package @reventlessdev/reventless-gwt
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-gwt",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.87",
|
|
4
4
|
"description": "Given-When-Then DSLs and CLI runner for Reventless slice testing",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
"chokidar": "^3.6.0",
|
|
21
21
|
"picocolors": "^1.1.1",
|
|
22
22
|
"sury": "11.0.0-alpha.4",
|
|
23
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.25",
|
|
24
23
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.6",
|
|
24
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.25",
|
|
25
25
|
"@reventlessdev/reventless-core": "3.0.0-alpha.143",
|
|
26
26
|
"@reventlessdev/reventless-infra": "3.0.0-alpha.89",
|
|
27
27
|
"@reventlessdev/reventless-spec": "3.0.0-alpha.67",
|
|
28
|
-
"@reventlessdev/reventless-vscode-protocol": "1.0.0-alpha.
|
|
28
|
+
"@reventlessdev/reventless-vscode-protocol": "1.0.0-alpha.16"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"rescript": "^12.3.0",
|
package/src/DomainDeadCode.res
CHANGED
|
@@ -26,8 +26,13 @@
|
|
|
26
26
|
// and "read models nothing resolves against" need data `pluginStructure` does not
|
|
27
27
|
// carry (the EP declaration list / the id-resolver table) — deferred.
|
|
28
28
|
|
|
29
|
+
// Finding kinds are the typed protocol vocabulary — constructing the variant here
|
|
30
|
+
// (not a string literal) means a typo or rename is a compile error, and the emitter
|
|
31
|
+
// forwards findings without conversion.
|
|
32
|
+
module P = ReventlessVscodeProtocol.Protocol
|
|
33
|
+
|
|
29
34
|
type finding = {
|
|
30
|
-
kind:
|
|
35
|
+
kind: P.deadCodeKind,
|
|
31
36
|
pluginName: string, // plugin owning the producer
|
|
32
37
|
componentName: string, // the producing aggregate / state-change slice
|
|
33
38
|
detail: string, // the orphaned event type
|
|
@@ -76,7 +81,7 @@ let analyze = (
|
|
|
76
81
|
| Some(_) => ()
|
|
77
82
|
| None =>
|
|
78
83
|
findings->Array.push({
|
|
79
|
-
kind:
|
|
84
|
+
kind: OrphanEvent,
|
|
80
85
|
pluginName,
|
|
81
86
|
componentName: w.name,
|
|
82
87
|
detail: evt,
|
package/src/DomainGraph.res
CHANGED
|
Binary file
|
package/src/DomainGraph.res.mjs
CHANGED
|
Binary file
|
package/src/FormatterVsCode.res
CHANGED
|
@@ -141,13 +141,15 @@ let emitDiscoveryItems = (files: array<(string, array<RunnerTypes.testResult>)>)
|
|
|
141
141
|
}
|
|
142
142
|
let component =
|
|
143
143
|
ComponentMeta.componentOfTestFile(path)->Option.map((c): P.componentMeta => {
|
|
144
|
-
|
|
144
|
+
// `ComponentKind.folderName` spellings ARE the typed vocabulary's constructor
|
|
145
|
+
// names, so the identity conversion lands every known kind on its constructor.
|
|
146
|
+
kind: P.componentKindOfString(c.kind),
|
|
145
147
|
name: c.name,
|
|
146
148
|
})
|
|
147
149
|
emit(
|
|
148
150
|
Item({
|
|
149
151
|
id: fileId,
|
|
150
|
-
kind:
|
|
152
|
+
kind: File,
|
|
151
153
|
label: fileLabelOf(path),
|
|
152
154
|
description: relativeToCwd(path),
|
|
153
155
|
uri: fileUri,
|
|
@@ -174,7 +176,7 @@ let emitDiscoveryItems = (files: array<(string, array<RunnerTypes.testResult>)>)
|
|
|
174
176
|
Item({
|
|
175
177
|
id,
|
|
176
178
|
parent,
|
|
177
|
-
kind:
|
|
179
|
+
kind: Suite,
|
|
178
180
|
label,
|
|
179
181
|
uri: ?loc->Option.map(l => uriOf(l.file)),
|
|
180
182
|
range: ?loc->Option.map(rangeOf),
|
|
@@ -197,7 +199,7 @@ let emitDiscoveryItems = (files: array<(string, array<RunnerTypes.testResult>)>)
|
|
|
197
199
|
Item({
|
|
198
200
|
id: testId,
|
|
199
201
|
parent,
|
|
200
|
-
kind:
|
|
202
|
+
kind: Test,
|
|
201
203
|
label: t.name,
|
|
202
204
|
uri: ?loc->Option.map(l => uriOf(l.file)),
|
|
203
205
|
range: ?loc->Option.map(rangeOf),
|
|
@@ -225,7 +227,7 @@ let components = (comps: array<ComponentScan.component>) =>
|
|
|
225
227
|
Components({
|
|
226
228
|
components: comps->Array.map((c): P.componentRef => {
|
|
227
229
|
dir: c.dir,
|
|
228
|
-
kind: c.kind,
|
|
230
|
+
kind: P.componentKindOfString(c.kind),
|
|
229
231
|
name: c.name,
|
|
230
232
|
files: c.files,
|
|
231
233
|
}),
|
|
@@ -332,7 +334,10 @@ let messagePayload = (t: RunnerTypes.testResult): P.failMessage =>
|
|
|
332
334
|
{
|
|
333
335
|
message: hint.message,
|
|
334
336
|
// The mismatch family — lets a client gate the apply-expected quick-fix.
|
|
335
|
-
|
|
337
|
+
// `Outcome.kindName` stays string-returning (JUnit/JSON formatters consume it
|
|
338
|
+
// raw); its spellings are the assertionKind constructor names, so the identity
|
|
339
|
+
// conversion types it losslessly.
|
|
340
|
+
kind: ?Some(P.assertionKindOfString(Outcome.kindName(m))),
|
|
336
341
|
expected: ?expected,
|
|
337
342
|
actual: ?actual,
|
|
338
343
|
location: ?location,
|
|
@@ -163,7 +163,7 @@ function emitDiscoveryItems(files) {
|
|
|
163
163
|
let e = {
|
|
164
164
|
event: "item",
|
|
165
165
|
id: path,
|
|
166
|
-
kind: "
|
|
166
|
+
kind: "File",
|
|
167
167
|
label: e_3,
|
|
168
168
|
description: e_4,
|
|
169
169
|
uri: e_5,
|
|
@@ -193,7 +193,7 @@ function emitDiscoveryItems(files) {
|
|
|
193
193
|
event: "item",
|
|
194
194
|
id: id,
|
|
195
195
|
parent: e_1,
|
|
196
|
-
kind: "
|
|
196
|
+
kind: "Suite",
|
|
197
197
|
label: label,
|
|
198
198
|
uri: e_5,
|
|
199
199
|
range: e_6
|
|
@@ -216,7 +216,7 @@ function emitDiscoveryItems(files) {
|
|
|
216
216
|
event: "item",
|
|
217
217
|
id: testId,
|
|
218
218
|
parent: e_1,
|
|
219
|
-
kind: "
|
|
219
|
+
kind: "Test",
|
|
220
220
|
label: e_3,
|
|
221
221
|
uri: e_5,
|
|
222
222
|
range: e_6
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// LocalHost integration; here hand-built fixtures pin the edge wiring.
|
|
5
5
|
|
|
6
6
|
open JestGlobals
|
|
7
|
+
open ReventlessVscodeProtocol.Protocol
|
|
7
8
|
|
|
8
9
|
let command = (~name, ~mutationField, ~apiExposed=None): Reventless.Plugin.commandDef => {
|
|
9
10
|
name,
|
|
@@ -103,13 +104,13 @@ describe("DomainGraph.build", () => {
|
|
|
103
104
|
~stateViewSlices=[queryable(~name="OrderView", ~consumes=["Shop.Placed"])],
|
|
104
105
|
)
|
|
105
106
|
let g = DomainGraph.build(~structures=[("Shop", shop)])
|
|
106
|
-
expect(nodeKind(g, "Shop_Order_Place"))->toEqual(Some(
|
|
107
|
-
expect(nodeKind(g, "Shop:Order"))->toEqual(Some(
|
|
108
|
-
expect(nodeKind(g, "Shop.Placed"))->toEqual(Some(
|
|
109
|
-
expect(nodeKind(g, "Shop:OrderView"))->toEqual(Some(
|
|
110
|
-
expect(hasEdge(g, "Shop_Order_Place", "Shop:Order",
|
|
111
|
-
expect(hasEdge(g, "Shop:Order", "Shop.Placed",
|
|
112
|
-
expect(hasEdge(g, "Shop.Placed", "Shop:OrderView",
|
|
107
|
+
expect(nodeKind(g, "Shop_Order_Place"))->toEqual(Some(Command))
|
|
108
|
+
expect(nodeKind(g, "Shop:Order"))->toEqual(Some(Aggregate))
|
|
109
|
+
expect(nodeKind(g, "Shop.Placed"))->toEqual(Some(Event))
|
|
110
|
+
expect(nodeKind(g, "Shop:OrderView"))->toEqual(Some(StateViewSlice))
|
|
111
|
+
expect(hasEdge(g, "Shop_Order_Place", "Shop:Order", Handles))->toBe(true)
|
|
112
|
+
expect(hasEdge(g, "Shop:Order", "Shop.Placed", Emits))->toBe(true)
|
|
113
|
+
expect(hasEdge(g, "Shop.Placed", "Shop:OrderView", Projects))->toBe(true)
|
|
113
114
|
})
|
|
114
115
|
|
|
115
116
|
testPromise("payload-less events (in `events`, not producedEventTypes) still get an emitted node", async () => {
|
|
@@ -126,10 +127,10 @@ describe("DomainGraph.build", () => {
|
|
|
126
127
|
],
|
|
127
128
|
)
|
|
128
129
|
let g = DomainGraph.build(~structures=[("Catalog", shop)])
|
|
129
|
-
expect(nodeKind(g, "Catalog.Added"))->toEqual(Some(
|
|
130
|
-
expect(nodeKind(g, "Catalog.Archived"))->toEqual(Some(
|
|
131
|
-
expect(hasEdge(g, "Catalog:Category", "Catalog.Added",
|
|
132
|
-
expect(hasEdge(g, "Catalog:Category", "Catalog.Archived",
|
|
130
|
+
expect(nodeKind(g, "Catalog.Added"))->toEqual(Some(Event))
|
|
131
|
+
expect(nodeKind(g, "Catalog.Archived"))->toEqual(Some(Event))
|
|
132
|
+
expect(hasEdge(g, "Catalog:Category", "Catalog.Added", Emits))->toBe(true)
|
|
133
|
+
expect(hasEdge(g, "Catalog:Category", "Catalog.Archived", Emits))->toBe(true)
|
|
133
134
|
})
|
|
134
135
|
|
|
135
136
|
testPromise("a payload-less event also projects into the aggregate's linked view", async () => {
|
|
@@ -147,8 +148,8 @@ describe("DomainGraph.build", () => {
|
|
|
147
148
|
],
|
|
148
149
|
)
|
|
149
150
|
let g = DomainGraph.build(~structures=[("Catalog", shop)])
|
|
150
|
-
expect(hasEdge(g, "Catalog.Added", "Catalog:Categories",
|
|
151
|
-
expect(hasEdge(g, "Catalog.Archived", "Catalog:Categories",
|
|
151
|
+
expect(hasEdge(g, "Catalog.Added", "Catalog:Categories", Projects))->toBe(true)
|
|
152
|
+
expect(hasEdge(g, "Catalog.Archived", "Catalog:Categories", Projects))->toBe(true)
|
|
152
153
|
})
|
|
153
154
|
|
|
154
155
|
testPromise("an Internal StateViewSlice still gets a node + projects edge (dev graph shows it)", async () => {
|
|
@@ -162,8 +163,8 @@ describe("DomainGraph.build", () => {
|
|
|
162
163
|
],
|
|
163
164
|
)
|
|
164
165
|
let g = DomainGraph.build(~structures=[("Shop", shop)])
|
|
165
|
-
expect(nodeKind(g, "Shop:AvailableView"))->toEqual(Some(
|
|
166
|
-
expect(hasEdge(g, "Shop.Placed", "Shop:AvailableView",
|
|
166
|
+
expect(nodeKind(g, "Shop:AvailableView"))->toEqual(Some(StateViewSlice))
|
|
167
|
+
expect(hasEdge(g, "Shop.Placed", "Shop:AvailableView", Projects))->toBe(true)
|
|
167
168
|
})
|
|
168
169
|
|
|
169
170
|
testPromise("classic linkedViews draws each produced event into the linked read model", async () => {
|
|
@@ -172,8 +173,8 @@ describe("DomainGraph.build", () => {
|
|
|
172
173
|
~readModels=[queryable(~name="Orders")],
|
|
173
174
|
)
|
|
174
175
|
let g = DomainGraph.build(~structures=[("Shop", shop)])
|
|
175
|
-
expect(hasEdge(g, "Shop.Placed", "Shop:Orders",
|
|
176
|
-
expect(hasEdge(g, "Shop.Shipped", "Shop:Orders",
|
|
176
|
+
expect(hasEdge(g, "Shop.Placed", "Shop:Orders", Projects))->toBe(true)
|
|
177
|
+
expect(hasEdge(g, "Shop.Shipped", "Shop:Orders", Projects))->toBe(true)
|
|
177
178
|
})
|
|
178
179
|
|
|
179
180
|
testPromise("automation slices are triggered by their consumed events", async () => {
|
|
@@ -182,8 +183,8 @@ describe("DomainGraph.build", () => {
|
|
|
182
183
|
~automationSlices=[automation(~name="Notify", ~consumes=["Shop.Placed"])],
|
|
183
184
|
)
|
|
184
185
|
let g = DomainGraph.build(~structures=[("Shop", shop)])
|
|
185
|
-
expect(nodeKind(g, "Shop:Notify"))->toEqual(Some(
|
|
186
|
-
expect(hasEdge(g, "Shop.Placed", "Shop:Notify",
|
|
186
|
+
expect(nodeKind(g, "Shop:Notify"))->toEqual(Some(AutomationSlice))
|
|
187
|
+
expect(hasEdge(g, "Shop.Placed", "Shop:Notify", Triggers))->toBe(true)
|
|
187
188
|
})
|
|
188
189
|
|
|
189
190
|
testPromise("an automation routes to the specific command it raises on its target", async () => {
|
|
@@ -206,9 +207,9 @@ describe("DomainGraph.build", () => {
|
|
|
206
207
|
],
|
|
207
208
|
)
|
|
208
209
|
let g = DomainGraph.build(~structures=[("Shop", shop)])
|
|
209
|
-
expect(hasEdge(g, "Shop:AutoShip", "Shop_Ship_ShipOrder",
|
|
210
|
+
expect(hasEdge(g, "Shop:AutoShip", "Shop_Ship_ShipOrder", DelegatesTo))->toBe(true)
|
|
210
211
|
// Matched by name — the target's other command is not linked.
|
|
211
|
-
expect(hasEdge(g, "Shop:AutoShip", "Shop_Ship_Place",
|
|
212
|
+
expect(hasEdge(g, "Shop:AutoShip", "Shop_Ship_Place", DelegatesTo))->toBe(false)
|
|
212
213
|
})
|
|
213
214
|
|
|
214
215
|
testPromise("an inbound translation routes to the command it raises on its target", async () => {
|
|
@@ -223,8 +224,8 @@ describe("DomainGraph.build", () => {
|
|
|
223
224
|
],
|
|
224
225
|
)
|
|
225
226
|
let g = DomainGraph.build(~structures=[("Shop", shop)])
|
|
226
|
-
expect(nodeKind(g, "Shop:ImportProduct"))->toEqual(Some(
|
|
227
|
-
expect(hasEdge(g, "Shop:ImportProduct", "Shop_Product_Add",
|
|
227
|
+
expect(nodeKind(g, "Shop:ImportProduct"))->toEqual(Some(InboundTranslationSlice))
|
|
228
|
+
expect(hasEdge(g, "Shop:ImportProduct", "Shop_Product_Add", DelegatesTo))->toBe(true)
|
|
228
229
|
})
|
|
229
230
|
|
|
230
231
|
testPromise("an outbound translation with a target routes to the command it raises", async () => {
|
|
@@ -237,7 +238,7 @@ describe("DomainGraph.build", () => {
|
|
|
237
238
|
],
|
|
238
239
|
)
|
|
239
240
|
let g = DomainGraph.build(~structures=[("Shop", shop)])
|
|
240
|
-
expect(hasEdge(g, "Shop:ExportProduct", "Shop_Product_Sync",
|
|
241
|
+
expect(hasEdge(g, "Shop:ExportProduct", "Shop_Product_Sync", DelegatesTo))->toBe(true)
|
|
241
242
|
})
|
|
242
243
|
|
|
243
244
|
testPromise("an inbound translation naming an external system draws a box feeding it", async () => {
|
|
@@ -248,11 +249,11 @@ describe("DomainGraph.build", () => {
|
|
|
248
249
|
)
|
|
249
250
|
let g = DomainGraph.build(~structures=[("Shop", shop)])
|
|
250
251
|
// External box: drawn OUTSIDE the plugin (plugin: "") with a stable "ext:<name>" id.
|
|
251
|
-
expect(nodeKind(g, "ext:SupplierFeed"))->toEqual(Some(
|
|
252
|
+
expect(nodeKind(g, "ext:SupplierFeed"))->toEqual(Some(ExternalSystem))
|
|
252
253
|
expect(g.nodes->Array.find(n => n.id == "ext:SupplierFeed")->Option.map(n => n.plugin))->toEqual(Some(""))
|
|
253
254
|
// Inbound direction: external → slice (unlabelled — the arrowhead carries the meaning).
|
|
254
|
-
expect(hasEdge(g, "ext:SupplierFeed", "Shop:ImportProduct",
|
|
255
|
-
expect(edgeLabel(g, "ext:SupplierFeed", "Shop:ImportProduct",
|
|
255
|
+
expect(hasEdge(g, "ext:SupplierFeed", "Shop:ImportProduct", TranslatesIn))->toBe(true)
|
|
256
|
+
expect(edgeLabel(g, "ext:SupplierFeed", "Shop:ImportProduct", TranslatesIn))->toEqual(None)
|
|
256
257
|
})
|
|
257
258
|
|
|
258
259
|
testPromise("an outbound translation naming an external system feeds the box", async () => {
|
|
@@ -262,10 +263,10 @@ describe("DomainGraph.build", () => {
|
|
|
262
263
|
],
|
|
263
264
|
)
|
|
264
265
|
let g = DomainGraph.build(~structures=[("Shop", shop)])
|
|
265
|
-
expect(nodeKind(g, "ext:EmailService"))->toEqual(Some(
|
|
266
|
+
expect(nodeKind(g, "ext:EmailService"))->toEqual(Some(ExternalSystem))
|
|
266
267
|
// Outbound direction: slice → external (unlabelled — the arrowhead carries the meaning).
|
|
267
|
-
expect(hasEdge(g, "Shop:SendEmail", "ext:EmailService",
|
|
268
|
-
expect(edgeLabel(g, "Shop:SendEmail", "ext:EmailService",
|
|
268
|
+
expect(hasEdge(g, "Shop:SendEmail", "ext:EmailService", TranslatesOut))->toBe(true)
|
|
269
|
+
expect(edgeLabel(g, "Shop:SendEmail", "ext:EmailService", TranslatesOut))->toEqual(None)
|
|
269
270
|
})
|
|
270
271
|
|
|
271
272
|
testPromise("translation slices with no external system draw no box (opt-in)", async () => {
|
|
@@ -273,7 +274,7 @@ describe("DomainGraph.build", () => {
|
|
|
273
274
|
~inboundTranslationSlices=[inbound(~name="ImportProduct", ~target="Product")],
|
|
274
275
|
)
|
|
275
276
|
let g = DomainGraph.build(~structures=[("Shop", shop)])
|
|
276
|
-
expect(g.nodes->Array.some(n => n.kind ==
|
|
277
|
+
expect(g.nodes->Array.some(n => n.kind == ExternalSystem))->toBe(false)
|
|
277
278
|
})
|
|
278
279
|
|
|
279
280
|
testPromise("two slices naming the same external system share one deduped box", async () => {
|
|
@@ -290,8 +291,8 @@ describe("DomainGraph.build", () => {
|
|
|
290
291
|
let g = DomainGraph.build(~structures=[("PluginA", a), ("PluginB", b)])
|
|
291
292
|
// One shared box (per-platform dedup by name) with both an out- and an in-edge.
|
|
292
293
|
expect(g.nodes->Array.filter(n => n.id == "ext:EmailService")->Array.length)->toBe(1)
|
|
293
|
-
expect(hasEdge(g, "PluginA:SendEmailA", "ext:EmailService",
|
|
294
|
-
expect(hasEdge(g, "ext:EmailService", "PluginB:FromEmail",
|
|
294
|
+
expect(hasEdge(g, "PluginA:SendEmailA", "ext:EmailService", TranslatesOut))->toBe(true)
|
|
295
|
+
expect(hasEdge(g, "ext:EmailService", "PluginB:FromEmail", TranslatesIn))->toBe(true)
|
|
295
296
|
})
|
|
296
297
|
|
|
297
298
|
testPromise("an event produced by two write-sides is one node with two emit edges", async () => {
|
|
@@ -303,8 +304,8 @@ describe("DomainGraph.build", () => {
|
|
|
303
304
|
)
|
|
304
305
|
let g = DomainGraph.build(~structures=[("Shop", shop)])
|
|
305
306
|
expect(g.nodes->Array.filter(n => n.id == "Shop.Touched")->Array.length)->toBe(1)
|
|
306
|
-
expect(hasEdge(g, "Shop:A", "Shop.Touched",
|
|
307
|
-
expect(hasEdge(g, "Shop:B", "Shop.Touched",
|
|
307
|
+
expect(hasEdge(g, "Shop:A", "Shop.Touched", Emits))->toBe(true)
|
|
308
|
+
expect(hasEdge(g, "Shop:B", "Shop.Touched", Emits))->toBe(true)
|
|
308
309
|
})
|
|
309
310
|
|
|
310
311
|
testPromise("an extension renders the cross-plugin event flow EP→event→Extension→delegate", async () => {
|
|
@@ -321,11 +322,11 @@ describe("DomainGraph.build", () => {
|
|
|
321
322
|
}
|
|
322
323
|
let g = DomainGraph.build(~structures=[("Catalog", catalog)])
|
|
323
324
|
// EP (owned by Ordering) publishes its event; the Catalog extension consumes it…
|
|
324
|
-
expect(nodeKind(g, "Ordering:ep:Ordering.Orders"))->toEqual(Some(
|
|
325
|
-
expect(hasEdge(g, "Ordering:ep:Ordering.Orders", "Ordering.Orders.ItemOrdered",
|
|
326
|
-
expect(hasEdge(g, "Ordering.Orders.ItemOrdered", "Catalog:ext:Ordering.Orders",
|
|
325
|
+
expect(nodeKind(g, "Ordering:ep:Ordering.Orders"))->toEqual(Some(ExtensionPoint))
|
|
326
|
+
expect(hasEdge(g, "Ordering:ep:Ordering.Orders", "Ordering.Orders.ItemOrdered", Publishes))->toBe(true)
|
|
327
|
+
expect(hasEdge(g, "Ordering.Orders.ItemOrdered", "Catalog:ext:Ordering.Orders", Consumes))->toBe(true)
|
|
327
328
|
// …and delegates to ProductDemand.
|
|
328
|
-
expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand",
|
|
329
|
+
expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand", DelegatesTo))->toBe(true)
|
|
329
330
|
// The protocol event is top-level (empty plugin) so it sits between the boxes…
|
|
330
331
|
let protoEvent =
|
|
331
332
|
g.nodes->Array.find(n => n.id == "Ordering.Orders.ItemOrdered")->Option.getOrThrow
|
|
@@ -360,12 +361,12 @@ describe("DomainGraph.build", () => {
|
|
|
360
361
|
let g = DomainGraph.build(~structures=[("Catalog", catalog)])
|
|
361
362
|
// Extension → the command it creates …
|
|
362
363
|
expect(
|
|
363
|
-
hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog_ProductDemand_RecordDemand",
|
|
364
|
+
hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog_ProductDemand_RecordDemand", DelegatesTo),
|
|
364
365
|
)->toBe(true)
|
|
365
366
|
// … the command is handled by the slice (so the full path is Extension → Command → slice) …
|
|
366
|
-
expect(hasEdge(g, "Catalog_ProductDemand_RecordDemand", "Catalog:ProductDemand",
|
|
367
|
+
expect(hasEdge(g, "Catalog_ProductDemand_RecordDemand", "Catalog:ProductDemand", Handles))->toBe(true)
|
|
367
368
|
// … and the extension no longer points straight at the slice.
|
|
368
|
-
expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand",
|
|
369
|
+
expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand", DelegatesTo))->toBe(false)
|
|
369
370
|
})
|
|
370
371
|
|
|
371
372
|
testPromise("an extension with no commands falls back to delegating to the write-side", async () => {
|
|
@@ -380,7 +381,7 @@ describe("DomainGraph.build", () => {
|
|
|
380
381
|
extensions: [extension],
|
|
381
382
|
}
|
|
382
383
|
let g = DomainGraph.build(~structures=[("Catalog", catalog)])
|
|
383
|
-
expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand",
|
|
384
|
+
expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand", DelegatesTo))->toBe(true)
|
|
384
385
|
})
|
|
385
386
|
|
|
386
387
|
testPromise("an owned extension point is fed by the producers of its source events", async () => {
|
|
@@ -405,14 +406,14 @@ describe("DomainGraph.build", () => {
|
|
|
405
406
|
~extensionPoints=[ep],
|
|
406
407
|
)
|
|
407
408
|
let g = DomainGraph.build(~structures=[("Catalog", catalog)])
|
|
408
|
-
expect(nodeKind(g, "Catalog:ep:Catalog.Products"))->toEqual(Some(
|
|
409
|
+
expect(nodeKind(g, "Catalog:ep:Catalog.Products"))->toEqual(Some(ExtensionPoint))
|
|
409
410
|
// The producing write-side emits the source event…
|
|
410
|
-
expect(hasEdge(g, "Catalog:AddProduct", "Catalog.ProductAdded",
|
|
411
|
+
expect(hasEdge(g, "Catalog:AddProduct", "Catalog.ProductAdded", Emits))->toBe(true)
|
|
411
412
|
// …which feeds the extension point — so EP focus reaches the producer.
|
|
412
|
-
expect(hasEdge(g, "Catalog.ProductAdded", "Catalog:ep:Catalog.Products",
|
|
413
|
+
expect(hasEdge(g, "Catalog.ProductAdded", "Catalog:ep:Catalog.Products", Feeds))->toBe(true)
|
|
413
414
|
// …but the EP has NO inbound command protocol (command = unit), so it routes
|
|
414
415
|
// nothing: no backwards EP → Command edge mislabelling the producer's own command.
|
|
415
|
-
expect(hasEdge(g, "Catalog:ep:Catalog.Products", "Catalog_AddProduct",
|
|
416
|
+
expect(hasEdge(g, "Catalog:ep:Catalog.Products", "Catalog_AddProduct", RoutesTo))->toBe(false)
|
|
416
417
|
})
|
|
417
418
|
|
|
418
419
|
testPromise("an extension point with an inbound command protocol routes to its delegate's commands", async () => {
|
|
@@ -436,7 +437,7 @@ describe("DomainGraph.build", () => {
|
|
|
436
437
|
~extensionPoints=[ep],
|
|
437
438
|
)
|
|
438
439
|
let g = DomainGraph.build(~structures=[("Catalog", catalog)])
|
|
439
|
-
expect(hasEdge(g, "Catalog:ep:Catalog.Products", "Catalog_AddProduct",
|
|
440
|
-
expect(hasEdge(g, "Catalog_AddProduct", "Catalog:AddProduct",
|
|
440
|
+
expect(hasEdge(g, "Catalog:ep:Catalog.Products", "Catalog_AddProduct", RoutesTo))->toBe(true)
|
|
441
|
+
expect(hasEdge(g, "Catalog_AddProduct", "Catalog:AddProduct", Handles))->toBe(true)
|
|
441
442
|
})
|
|
442
443
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
4
5
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
5
6
|
import * as DomainGraph$ReventlessGwt from "../src/DomainGraph.res.mjs";
|
|
6
7
|
|
|
@@ -122,7 +123,7 @@ function structure(aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChange
|
|
|
122
123
|
function hasEdge(g, from, to, kind) {
|
|
123
124
|
return g.edges.some(e => {
|
|
124
125
|
if (e.from === from && e.to === to) {
|
|
125
|
-
return e.kind
|
|
126
|
+
return Primitive_object.equal(e.kind, kind);
|
|
126
127
|
} else {
|
|
127
128
|
return false;
|
|
128
129
|
}
|
|
@@ -132,7 +133,7 @@ function hasEdge(g, from, to, kind) {
|
|
|
132
133
|
function edgeLabel(g, from, to, kind) {
|
|
133
134
|
return Stdlib_Option.flatMap(g.edges.find(e => {
|
|
134
135
|
if (e.from === from && e.to === to) {
|
|
135
|
-
return e.kind
|
|
136
|
+
return Primitive_object.equal(e.kind, kind);
|
|
136
137
|
} else {
|
|
137
138
|
return false;
|
|
138
139
|
}
|
|
@@ -154,9 +155,9 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
154
155
|
globalThis.expect(nodeKind(g, "Shop:Order")).toEqual("Aggregate");
|
|
155
156
|
globalThis.expect(nodeKind(g, "Shop.Placed")).toEqual("Event");
|
|
156
157
|
globalThis.expect(nodeKind(g, "Shop:OrderView")).toEqual("StateViewSlice");
|
|
157
|
-
globalThis.expect(hasEdge(g, "Shop_Order_Place", "Shop:Order", "
|
|
158
|
-
globalThis.expect(hasEdge(g, "Shop:Order", "Shop.Placed", "
|
|
159
|
-
globalThis.expect(hasEdge(g, "Shop.Placed", "Shop:OrderView", "
|
|
158
|
+
globalThis.expect(hasEdge(g, "Shop_Order_Place", "Shop:Order", "Handles")).toBe(true);
|
|
159
|
+
globalThis.expect(hasEdge(g, "Shop:Order", "Shop.Placed", "Emits")).toBe(true);
|
|
160
|
+
globalThis.expect(hasEdge(g, "Shop.Placed", "Shop:OrderView", "Projects")).toBe(true);
|
|
160
161
|
});
|
|
161
162
|
globalThis.test("payload-less events (in `events`, not producedEventTypes) still get an emitted node", async () => {
|
|
162
163
|
let shop = structure([writable("Category", undefined, ["Catalog.Added"], undefined, undefined, [
|
|
@@ -177,8 +178,8 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
177
178
|
]]);
|
|
178
179
|
globalThis.expect(nodeKind(g, "Catalog.Added")).toEqual("Event");
|
|
179
180
|
globalThis.expect(nodeKind(g, "Catalog.Archived")).toEqual("Event");
|
|
180
|
-
globalThis.expect(hasEdge(g, "Catalog:Category", "Catalog.Added", "
|
|
181
|
-
globalThis.expect(hasEdge(g, "Catalog:Category", "Catalog.Archived", "
|
|
181
|
+
globalThis.expect(hasEdge(g, "Catalog:Category", "Catalog.Added", "Emits")).toBe(true);
|
|
182
|
+
globalThis.expect(hasEdge(g, "Catalog:Category", "Catalog.Archived", "Emits")).toBe(true);
|
|
182
183
|
});
|
|
183
184
|
globalThis.test("a payload-less event also projects into the aggregate's linked view", async () => {
|
|
184
185
|
let shop = structure([writable("Category", undefined, ["Catalog.Added"], undefined, ["Categories"], [
|
|
@@ -197,8 +198,8 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
197
198
|
"Catalog",
|
|
198
199
|
shop
|
|
199
200
|
]]);
|
|
200
|
-
globalThis.expect(hasEdge(g, "Catalog.Added", "Catalog:Categories", "
|
|
201
|
-
globalThis.expect(hasEdge(g, "Catalog.Archived", "Catalog:Categories", "
|
|
201
|
+
globalThis.expect(hasEdge(g, "Catalog.Added", "Catalog:Categories", "Projects")).toBe(true);
|
|
202
|
+
globalThis.expect(hasEdge(g, "Catalog.Archived", "Catalog:Categories", "Projects")).toBe(true);
|
|
202
203
|
});
|
|
203
204
|
globalThis.test("an Internal StateViewSlice still gets a node + projects edge (dev graph shows it)", async () => {
|
|
204
205
|
let shop = structure([writable("Order", undefined, ["Shop.Placed"], undefined, undefined, undefined)], undefined, [queryable("AvailableView", ["Shop.Placed"], "Internal")], undefined, undefined, undefined, undefined, undefined);
|
|
@@ -207,7 +208,7 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
207
208
|
shop
|
|
208
209
|
]]);
|
|
209
210
|
globalThis.expect(nodeKind(g, "Shop:AvailableView")).toEqual("StateViewSlice");
|
|
210
|
-
globalThis.expect(hasEdge(g, "Shop.Placed", "Shop:AvailableView", "
|
|
211
|
+
globalThis.expect(hasEdge(g, "Shop.Placed", "Shop:AvailableView", "Projects")).toBe(true);
|
|
211
212
|
});
|
|
212
213
|
globalThis.test("classic linkedViews draws each produced event into the linked read model", async () => {
|
|
213
214
|
let shop = structure([writable("Order", undefined, [
|
|
@@ -218,8 +219,8 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
218
219
|
"Shop",
|
|
219
220
|
shop
|
|
220
221
|
]]);
|
|
221
|
-
globalThis.expect(hasEdge(g, "Shop.Placed", "Shop:Orders", "
|
|
222
|
-
globalThis.expect(hasEdge(g, "Shop.Shipped", "Shop:Orders", "
|
|
222
|
+
globalThis.expect(hasEdge(g, "Shop.Placed", "Shop:Orders", "Projects")).toBe(true);
|
|
223
|
+
globalThis.expect(hasEdge(g, "Shop.Shipped", "Shop:Orders", "Projects")).toBe(true);
|
|
223
224
|
});
|
|
224
225
|
globalThis.test("automation slices are triggered by their consumed events", async () => {
|
|
225
226
|
let shop = structure([writable("Order", undefined, ["Shop.Placed"], undefined, undefined, undefined)], undefined, undefined, undefined, [automation("Notify", ["Shop.Placed"], undefined, undefined)], undefined, undefined, undefined);
|
|
@@ -228,7 +229,7 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
228
229
|
shop
|
|
229
230
|
]]);
|
|
230
231
|
globalThis.expect(nodeKind(g, "Shop:Notify")).toEqual("AutomationSlice");
|
|
231
|
-
globalThis.expect(hasEdge(g, "Shop.Placed", "Shop:Notify", "
|
|
232
|
+
globalThis.expect(hasEdge(g, "Shop.Placed", "Shop:Notify", "Triggers")).toBe(true);
|
|
232
233
|
});
|
|
233
234
|
globalThis.test("an automation routes to the specific command it raises on its target", async () => {
|
|
234
235
|
let shop = structure(undefined, undefined, undefined, [writable("Ship", [
|
|
@@ -239,8 +240,8 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
239
240
|
"Shop",
|
|
240
241
|
shop
|
|
241
242
|
]]);
|
|
242
|
-
globalThis.expect(hasEdge(g, "Shop:AutoShip", "Shop_Ship_ShipOrder", "
|
|
243
|
-
globalThis.expect(hasEdge(g, "Shop:AutoShip", "Shop_Ship_Place", "
|
|
243
|
+
globalThis.expect(hasEdge(g, "Shop:AutoShip", "Shop_Ship_ShipOrder", "DelegatesTo")).toBe(true);
|
|
244
|
+
globalThis.expect(hasEdge(g, "Shop:AutoShip", "Shop_Ship_Place", "DelegatesTo")).toBe(false);
|
|
244
245
|
});
|
|
245
246
|
globalThis.test("an inbound translation routes to the command it raises on its target", async () => {
|
|
246
247
|
let shop = structure(undefined, undefined, undefined, [writable("Product", [command("Add", "Shop_Product_Add", undefined)], undefined, undefined, undefined, undefined)], undefined, undefined, [inbound("ImportProduct", ["ImportProduct.Add"], "Product", undefined)], undefined);
|
|
@@ -249,7 +250,7 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
249
250
|
shop
|
|
250
251
|
]]);
|
|
251
252
|
globalThis.expect(nodeKind(g, "Shop:ImportProduct")).toEqual("InboundTranslationSlice");
|
|
252
|
-
globalThis.expect(hasEdge(g, "Shop:ImportProduct", "Shop_Product_Add", "
|
|
253
|
+
globalThis.expect(hasEdge(g, "Shop:ImportProduct", "Shop_Product_Add", "DelegatesTo")).toBe(true);
|
|
253
254
|
});
|
|
254
255
|
globalThis.test("an outbound translation with a target routes to the command it raises", async () => {
|
|
255
256
|
let shop = structure(undefined, undefined, undefined, [writable("Product", [command("Sync", "Shop_Product_Sync", undefined)], undefined, undefined, undefined, undefined)], undefined, [outbound("ExportProduct", ["Shop.Added"], ["ExportProduct.Sync"], "Product", undefined)], undefined, undefined);
|
|
@@ -257,7 +258,7 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
257
258
|
"Shop",
|
|
258
259
|
shop
|
|
259
260
|
]]);
|
|
260
|
-
globalThis.expect(hasEdge(g, "Shop:ExportProduct", "Shop_Product_Sync", "
|
|
261
|
+
globalThis.expect(hasEdge(g, "Shop:ExportProduct", "Shop_Product_Sync", "DelegatesTo")).toBe(true);
|
|
261
262
|
});
|
|
262
263
|
globalThis.test("an inbound translation naming an external system draws a box feeding it", async () => {
|
|
263
264
|
let shop = structure(undefined, undefined, undefined, undefined, undefined, undefined, [inbound("ImportProduct", ["ImportProduct.Add"], "Product", "SupplierFeed")], undefined);
|
|
@@ -267,8 +268,8 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
267
268
|
]]);
|
|
268
269
|
globalThis.expect(nodeKind(g, "ext:SupplierFeed")).toEqual("ExternalSystem");
|
|
269
270
|
globalThis.expect(Stdlib_Option.map(g.nodes.find(n => n.id === "ext:SupplierFeed"), n => n.plugin)).toEqual("");
|
|
270
|
-
globalThis.expect(hasEdge(g, "ext:SupplierFeed", "Shop:ImportProduct", "
|
|
271
|
-
globalThis.expect(edgeLabel(g, "ext:SupplierFeed", "Shop:ImportProduct", "
|
|
271
|
+
globalThis.expect(hasEdge(g, "ext:SupplierFeed", "Shop:ImportProduct", "TranslatesIn")).toBe(true);
|
|
272
|
+
globalThis.expect(edgeLabel(g, "ext:SupplierFeed", "Shop:ImportProduct", "TranslatesIn")).toEqual(undefined);
|
|
272
273
|
});
|
|
273
274
|
globalThis.test("an outbound translation naming an external system feeds the box", async () => {
|
|
274
275
|
let shop = structure(undefined, undefined, undefined, undefined, undefined, [outbound("SendEmail", ["Shop.Placed"], undefined, Primitive_option.some(undefined), "EmailService")], undefined, undefined);
|
|
@@ -277,8 +278,8 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
277
278
|
shop
|
|
278
279
|
]]);
|
|
279
280
|
globalThis.expect(nodeKind(g, "ext:EmailService")).toEqual("ExternalSystem");
|
|
280
|
-
globalThis.expect(hasEdge(g, "Shop:SendEmail", "ext:EmailService", "
|
|
281
|
-
globalThis.expect(edgeLabel(g, "Shop:SendEmail", "ext:EmailService", "
|
|
281
|
+
globalThis.expect(hasEdge(g, "Shop:SendEmail", "ext:EmailService", "TranslatesOut")).toBe(true);
|
|
282
|
+
globalThis.expect(edgeLabel(g, "Shop:SendEmail", "ext:EmailService", "TranslatesOut")).toEqual(undefined);
|
|
282
283
|
});
|
|
283
284
|
globalThis.test("translation slices with no external system draw no box (opt-in)", async () => {
|
|
284
285
|
let shop = structure(undefined, undefined, undefined, undefined, undefined, undefined, [inbound("ImportProduct", undefined, "Product", undefined)], undefined);
|
|
@@ -302,8 +303,8 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
302
303
|
]
|
|
303
304
|
]);
|
|
304
305
|
globalThis.expect(g.nodes.filter(n => n.id === "ext:EmailService").length).toBe(1);
|
|
305
|
-
globalThis.expect(hasEdge(g, "PluginA:SendEmailA", "ext:EmailService", "
|
|
306
|
-
globalThis.expect(hasEdge(g, "ext:EmailService", "PluginB:FromEmail", "
|
|
306
|
+
globalThis.expect(hasEdge(g, "PluginA:SendEmailA", "ext:EmailService", "TranslatesOut")).toBe(true);
|
|
307
|
+
globalThis.expect(hasEdge(g, "ext:EmailService", "PluginB:FromEmail", "TranslatesIn")).toBe(true);
|
|
307
308
|
});
|
|
308
309
|
globalThis.test("an event produced by two write-sides is one node with two emit edges", async () => {
|
|
309
310
|
let shop = structure([
|
|
@@ -315,8 +316,8 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
315
316
|
shop
|
|
316
317
|
]]);
|
|
317
318
|
globalThis.expect(g.nodes.filter(n => n.id === "Shop.Touched").length).toBe(1);
|
|
318
|
-
globalThis.expect(hasEdge(g, "Shop:A", "Shop.Touched", "
|
|
319
|
-
globalThis.expect(hasEdge(g, "Shop:B", "Shop.Touched", "
|
|
319
|
+
globalThis.expect(hasEdge(g, "Shop:A", "Shop.Touched", "Emits")).toBe(true);
|
|
320
|
+
globalThis.expect(hasEdge(g, "Shop:B", "Shop.Touched", "Emits")).toBe(true);
|
|
320
321
|
});
|
|
321
322
|
globalThis.test("an extension renders the cross-plugin event flow EP→event→Extension→delegate", async () => {
|
|
322
323
|
let extension_delegateNames = ["ProductDemand"];
|
|
@@ -354,9 +355,9 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
354
355
|
catalog
|
|
355
356
|
]]);
|
|
356
357
|
globalThis.expect(nodeKind(g, "Ordering:ep:Ordering.Orders")).toEqual("ExtensionPoint");
|
|
357
|
-
globalThis.expect(hasEdge(g, "Ordering:ep:Ordering.Orders", "Ordering.Orders.ItemOrdered", "
|
|
358
|
-
globalThis.expect(hasEdge(g, "Ordering.Orders.ItemOrdered", "Catalog:ext:Ordering.Orders", "
|
|
359
|
-
globalThis.expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand", "
|
|
358
|
+
globalThis.expect(hasEdge(g, "Ordering:ep:Ordering.Orders", "Ordering.Orders.ItemOrdered", "Publishes")).toBe(true);
|
|
359
|
+
globalThis.expect(hasEdge(g, "Ordering.Orders.ItemOrdered", "Catalog:ext:Ordering.Orders", "Consumes")).toBe(true);
|
|
360
|
+
globalThis.expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand", "DelegatesTo")).toBe(true);
|
|
360
361
|
let protoEvent = Stdlib_Option.getOrThrow(g.nodes.find(n => n.id === "Ordering.Orders.ItemOrdered"), undefined);
|
|
361
362
|
globalThis.expect(protoEvent.plugin).toBe("");
|
|
362
363
|
let ext = Stdlib_Option.getOrThrow(g.nodes.find(n => n.id === "Catalog:ext:Ordering.Orders"), undefined);
|
|
@@ -397,9 +398,9 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
397
398
|
"Catalog",
|
|
398
399
|
catalog
|
|
399
400
|
]]);
|
|
400
|
-
globalThis.expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog_ProductDemand_RecordDemand", "
|
|
401
|
-
globalThis.expect(hasEdge(g, "Catalog_ProductDemand_RecordDemand", "Catalog:ProductDemand", "
|
|
402
|
-
globalThis.expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand", "
|
|
401
|
+
globalThis.expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog_ProductDemand_RecordDemand", "DelegatesTo")).toBe(true);
|
|
402
|
+
globalThis.expect(hasEdge(g, "Catalog_ProductDemand_RecordDemand", "Catalog:ProductDemand", "Handles")).toBe(true);
|
|
403
|
+
globalThis.expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand", "DelegatesTo")).toBe(false);
|
|
403
404
|
});
|
|
404
405
|
globalThis.test("an extension with no commands falls back to delegating to the write-side", async () => {
|
|
405
406
|
let extension_delegateNames = ["ProductDemand"];
|
|
@@ -436,7 +437,7 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
436
437
|
"Catalog",
|
|
437
438
|
catalog
|
|
438
439
|
]]);
|
|
439
|
-
globalThis.expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand", "
|
|
440
|
+
globalThis.expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand", "DelegatesTo")).toBe(true);
|
|
440
441
|
});
|
|
441
442
|
globalThis.test("an owned extension point is fed by the producers of its source events", async () => {
|
|
442
443
|
let ep_delegateNames = ["CatalogDcbEventLog"];
|
|
@@ -454,9 +455,9 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
454
455
|
catalog
|
|
455
456
|
]]);
|
|
456
457
|
globalThis.expect(nodeKind(g, "Catalog:ep:Catalog.Products")).toEqual("ExtensionPoint");
|
|
457
|
-
globalThis.expect(hasEdge(g, "Catalog:AddProduct", "Catalog.ProductAdded", "
|
|
458
|
-
globalThis.expect(hasEdge(g, "Catalog.ProductAdded", "Catalog:ep:Catalog.Products", "
|
|
459
|
-
globalThis.expect(hasEdge(g, "Catalog:ep:Catalog.Products", "Catalog_AddProduct", "
|
|
458
|
+
globalThis.expect(hasEdge(g, "Catalog:AddProduct", "Catalog.ProductAdded", "Emits")).toBe(true);
|
|
459
|
+
globalThis.expect(hasEdge(g, "Catalog.ProductAdded", "Catalog:ep:Catalog.Products", "Feeds")).toBe(true);
|
|
460
|
+
globalThis.expect(hasEdge(g, "Catalog:ep:Catalog.Products", "Catalog_AddProduct", "RoutesTo")).toBe(false);
|
|
460
461
|
});
|
|
461
462
|
globalThis.test("an extension point with an inbound command protocol routes to its delegate's commands", async () => {
|
|
462
463
|
let ep_delegateNames = ["AddProduct"];
|
|
@@ -473,8 +474,8 @@ globalThis.describe("DomainGraph.build", () => {
|
|
|
473
474
|
"Catalog",
|
|
474
475
|
catalog
|
|
475
476
|
]]);
|
|
476
|
-
globalThis.expect(hasEdge(g, "Catalog:ep:Catalog.Products", "Catalog_AddProduct", "
|
|
477
|
-
globalThis.expect(hasEdge(g, "Catalog_AddProduct", "Catalog:AddProduct", "
|
|
477
|
+
globalThis.expect(hasEdge(g, "Catalog:ep:Catalog.Products", "Catalog_AddProduct", "RoutesTo")).toBe(true);
|
|
478
|
+
globalThis.expect(hasEdge(g, "Catalog_AddProduct", "Catalog:AddProduct", "Handles")).toBe(true);
|
|
478
479
|
});
|
|
479
480
|
});
|
|
480
481
|
|