@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 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.85",
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.14"
28
+ "@reventlessdev/reventless-vscode-protocol": "1.0.0-alpha.16"
29
29
  },
30
30
  "devDependencies": {
31
31
  "rescript": "^12.3.0",
@@ -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: string, // "OrphanEvent"
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: "OrphanEvent",
84
+ kind: OrphanEvent,
80
85
  pluginName,
81
86
  componentName: w.name,
82
87
  detail: evt,
@@ -63,7 +63,10 @@ function analyze(structures) {
63
63
  return findings;
64
64
  }
65
65
 
66
+ let P;
67
+
66
68
  export {
69
+ P,
67
70
  analyze,
68
71
  }
69
72
  /* No side effect */
Binary file
Binary file
@@ -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
- kind: c.kind,
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: "file",
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: "suite",
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: "test",
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
- kind: ?Some(Outcome.kindName(m)),
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: "file",
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: "suite",
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: "test",
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("Command"))
107
- expect(nodeKind(g, "Shop:Order"))->toEqual(Some("Aggregate"))
108
- expect(nodeKind(g, "Shop.Placed"))->toEqual(Some("Event"))
109
- expect(nodeKind(g, "Shop:OrderView"))->toEqual(Some("StateViewSlice"))
110
- expect(hasEdge(g, "Shop_Order_Place", "Shop:Order", "handles"))->toBe(true)
111
- expect(hasEdge(g, "Shop:Order", "Shop.Placed", "emits"))->toBe(true)
112
- expect(hasEdge(g, "Shop.Placed", "Shop:OrderView", "projects"))->toBe(true)
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("Event"))
130
- expect(nodeKind(g, "Catalog.Archived"))->toEqual(Some("Event"))
131
- expect(hasEdge(g, "Catalog:Category", "Catalog.Added", "emits"))->toBe(true)
132
- expect(hasEdge(g, "Catalog:Category", "Catalog.Archived", "emits"))->toBe(true)
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", "projects"))->toBe(true)
151
- expect(hasEdge(g, "Catalog.Archived", "Catalog:Categories", "projects"))->toBe(true)
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("StateViewSlice"))
166
- expect(hasEdge(g, "Shop.Placed", "Shop:AvailableView", "projects"))->toBe(true)
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", "projects"))->toBe(true)
176
- expect(hasEdge(g, "Shop.Shipped", "Shop:Orders", "projects"))->toBe(true)
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("AutomationSlice"))
186
- expect(hasEdge(g, "Shop.Placed", "Shop:Notify", "triggers"))->toBe(true)
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", "delegatesTo"))->toBe(true)
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", "delegatesTo"))->toBe(false)
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("InboundTranslationSlice"))
227
- expect(hasEdge(g, "Shop:ImportProduct", "Shop_Product_Add", "delegatesTo"))->toBe(true)
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", "delegatesTo"))->toBe(true)
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("ExternalSystem"))
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", "translatesIn"))->toBe(true)
255
- expect(edgeLabel(g, "ext:SupplierFeed", "Shop:ImportProduct", "translatesIn"))->toEqual(None)
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("ExternalSystem"))
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", "translatesOut"))->toBe(true)
268
- expect(edgeLabel(g, "Shop:SendEmail", "ext:EmailService", "translatesOut"))->toEqual(None)
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 == "ExternalSystem"))->toBe(false)
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", "translatesOut"))->toBe(true)
294
- expect(hasEdge(g, "ext:EmailService", "PluginB:FromEmail", "translatesIn"))->toBe(true)
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", "emits"))->toBe(true)
307
- expect(hasEdge(g, "Shop:B", "Shop.Touched", "emits"))->toBe(true)
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("ExtensionPoint"))
325
- expect(hasEdge(g, "Ordering:ep:Ordering.Orders", "Ordering.Orders.ItemOrdered", "publishes"))->toBe(true)
326
- expect(hasEdge(g, "Ordering.Orders.ItemOrdered", "Catalog:ext:Ordering.Orders", "consumes"))->toBe(true)
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", "delegatesTo"))->toBe(true)
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", "delegatesTo"),
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", "handles"))->toBe(true)
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", "delegatesTo"))->toBe(false)
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", "delegatesTo"))->toBe(true)
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("ExtensionPoint"))
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", "emits"))->toBe(true)
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", "feeds"))->toBe(true)
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", "routesTo"))->toBe(false)
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", "routesTo"))->toBe(true)
440
- expect(hasEdge(g, "Catalog_AddProduct", "Catalog:AddProduct", "handles"))->toBe(true)
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 === 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 === 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", "handles")).toBe(true);
158
- globalThis.expect(hasEdge(g, "Shop:Order", "Shop.Placed", "emits")).toBe(true);
159
- globalThis.expect(hasEdge(g, "Shop.Placed", "Shop:OrderView", "projects")).toBe(true);
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", "emits")).toBe(true);
181
- globalThis.expect(hasEdge(g, "Catalog:Category", "Catalog.Archived", "emits")).toBe(true);
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", "projects")).toBe(true);
201
- globalThis.expect(hasEdge(g, "Catalog.Archived", "Catalog:Categories", "projects")).toBe(true);
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", "projects")).toBe(true);
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", "projects")).toBe(true);
222
- globalThis.expect(hasEdge(g, "Shop.Shipped", "Shop:Orders", "projects")).toBe(true);
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", "triggers")).toBe(true);
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", "delegatesTo")).toBe(true);
243
- globalThis.expect(hasEdge(g, "Shop:AutoShip", "Shop_Ship_Place", "delegatesTo")).toBe(false);
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", "delegatesTo")).toBe(true);
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", "delegatesTo")).toBe(true);
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", "translatesIn")).toBe(true);
271
- globalThis.expect(edgeLabel(g, "ext:SupplierFeed", "Shop:ImportProduct", "translatesIn")).toEqual(undefined);
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", "translatesOut")).toBe(true);
281
- globalThis.expect(edgeLabel(g, "Shop:SendEmail", "ext:EmailService", "translatesOut")).toEqual(undefined);
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", "translatesOut")).toBe(true);
306
- globalThis.expect(hasEdge(g, "ext:EmailService", "PluginB:FromEmail", "translatesIn")).toBe(true);
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", "emits")).toBe(true);
319
- globalThis.expect(hasEdge(g, "Shop:B", "Shop.Touched", "emits")).toBe(true);
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", "publishes")).toBe(true);
358
- globalThis.expect(hasEdge(g, "Ordering.Orders.ItemOrdered", "Catalog:ext:Ordering.Orders", "consumes")).toBe(true);
359
- globalThis.expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand", "delegatesTo")).toBe(true);
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", "delegatesTo")).toBe(true);
401
- globalThis.expect(hasEdge(g, "Catalog_ProductDemand_RecordDemand", "Catalog:ProductDemand", "handles")).toBe(true);
402
- globalThis.expect(hasEdge(g, "Catalog:ext:Ordering.Orders", "Catalog:ProductDemand", "delegatesTo")).toBe(false);
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", "delegatesTo")).toBe(true);
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", "emits")).toBe(true);
458
- globalThis.expect(hasEdge(g, "Catalog.ProductAdded", "Catalog:ep:Catalog.Products", "feeds")).toBe(true);
459
- globalThis.expect(hasEdge(g, "Catalog:ep:Catalog.Products", "Catalog_AddProduct", "routesTo")).toBe(false);
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", "routesTo")).toBe(true);
477
- globalThis.expect(hasEdge(g, "Catalog_AddProduct", "Catalog:AddProduct", "handles")).toBe(true);
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