@eventcatalog/language-server 0.1.0

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.
Files changed (88) hide show
  1. package/README.md +71 -0
  2. package/SPEC.md +1939 -0
  3. package/dist/ast-utils.d.ts +96 -0
  4. package/dist/ast-utils.d.ts.map +1 -0
  5. package/dist/ast-utils.js +241 -0
  6. package/dist/ast-utils.js.map +1 -0
  7. package/dist/compiler.d.ts +7 -0
  8. package/dist/compiler.d.ts.map +1 -0
  9. package/dist/compiler.js +654 -0
  10. package/dist/compiler.js.map +1 -0
  11. package/dist/ec-completion-provider.d.ts +15 -0
  12. package/dist/ec-completion-provider.d.ts.map +1 -0
  13. package/dist/ec-completion-provider.js +202 -0
  14. package/dist/ec-completion-provider.js.map +1 -0
  15. package/dist/ec-module.d.ts +18 -0
  16. package/dist/ec-module.d.ts.map +1 -0
  17. package/dist/ec-module.js +27 -0
  18. package/dist/ec-module.js.map +1 -0
  19. package/dist/ec-scope-provider.d.ts +2 -0
  20. package/dist/ec-scope-provider.d.ts.map +1 -0
  21. package/dist/ec-scope-provider.js +2 -0
  22. package/dist/ec-scope-provider.js.map +1 -0
  23. package/dist/ec-scope.d.ts +10 -0
  24. package/dist/ec-scope.d.ts.map +1 -0
  25. package/dist/ec-scope.js +18 -0
  26. package/dist/ec-scope.js.map +1 -0
  27. package/dist/ec-validator.d.ts +9 -0
  28. package/dist/ec-validator.d.ts.map +1 -0
  29. package/dist/ec-validator.js +238 -0
  30. package/dist/ec-validator.js.map +1 -0
  31. package/dist/formatter.d.ts +6 -0
  32. package/dist/formatter.d.ts.map +1 -0
  33. package/dist/formatter.js +88 -0
  34. package/dist/formatter.js.map +1 -0
  35. package/dist/generated/ast.d.ts +970 -0
  36. package/dist/generated/ast.d.ts.map +1 -0
  37. package/dist/generated/ast.js +1537 -0
  38. package/dist/generated/ast.js.map +1 -0
  39. package/dist/generated/grammar.d.ts +7 -0
  40. package/dist/generated/grammar.d.ts.map +1 -0
  41. package/dist/generated/grammar.js +6062 -0
  42. package/dist/generated/grammar.js.map +1 -0
  43. package/dist/generated/module.d.ts +14 -0
  44. package/dist/generated/module.d.ts.map +1 -0
  45. package/dist/generated/module.js +21 -0
  46. package/dist/generated/module.js.map +1 -0
  47. package/dist/graph-types.d.ts +32 -0
  48. package/dist/graph-types.d.ts.map +1 -0
  49. package/dist/graph-types.js +2 -0
  50. package/dist/graph-types.js.map +1 -0
  51. package/dist/graph.d.ts +4 -0
  52. package/dist/graph.d.ts.map +1 -0
  53. package/dist/graph.js +931 -0
  54. package/dist/graph.js.map +1 -0
  55. package/dist/index.d.ts +12 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +10 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/main-browser.d.ts +2 -0
  60. package/dist/main-browser.d.ts.map +1 -0
  61. package/dist/main-browser.js +16 -0
  62. package/dist/main-browser.js.map +1 -0
  63. package/dist/main.d.ts +2 -0
  64. package/dist/main.d.ts.map +1 -0
  65. package/dist/main.js +15 -0
  66. package/dist/main.js.map +1 -0
  67. package/package.json +55 -0
  68. package/specification/00-overview.md +99 -0
  69. package/specification/01-domain.md +80 -0
  70. package/specification/02-service.md +50 -0
  71. package/specification/03-event.md +28 -0
  72. package/specification/04-command.md +25 -0
  73. package/specification/05-query.md +25 -0
  74. package/specification/06-channel.md +131 -0
  75. package/specification/08-container.md +54 -0
  76. package/specification/09-data-product.md +39 -0
  77. package/specification/10-flow.md +163 -0
  78. package/specification/11-diagram.md +3 -0
  79. package/specification/12-user.md +54 -0
  80. package/specification/13-team.md +62 -0
  81. package/specification/14-relationships.md +89 -0
  82. package/specification/15-versioning.md +41 -0
  83. package/specification/16-annotations.md +100 -0
  84. package/specification/17-examples.md +373 -0
  85. package/specification/18-grammar.md +242 -0
  86. package/specification/19-visualizer.md +197 -0
  87. package/specification/build-spec.sh +49 -0
  88. package/syntaxes/ec.tmLanguage.json +61 -0
package/SPEC.md ADDED
@@ -0,0 +1,1939 @@
1
+ # EventCatalog DSL — Language Specification
2
+
3
+ > Version: 1.0.0-draft
4
+ > Status: Draft
5
+ > Date: 2026-02-08
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Overview](#overview)
10
+ 2. [Domain](#domain)
11
+ 3. [Service](#service)
12
+ 4. [Event](#event)
13
+ 5. [Command](#command)
14
+ 6. [Query](#query)
15
+ 7. [Channel](#channel)
16
+ 8. [Container](#container)
17
+ 9. [Data Product](#data-product)
18
+ 10. [Flow](#flow)
19
+ 11. [User](#user)
20
+ 12. [Team](#team)
21
+ 13. [Relationships & Pointers](#relationships--pointers)
22
+ 14. [Versioning](#versioning)
23
+ 15. [Metadata & Annotations](#metadata--annotations)
24
+ 16. [Complete Examples](#complete-examples)
25
+ 17. [Full Grammar (EBNF)](#full-grammar-ebnf)
26
+
27
+ ---
28
+
29
+ # Overview
30
+
31
+ The EventCatalog DSL (ECDSL) is a human-readable, declarative language for defining event-driven architectures. It compiles to EventCatalog's frontmatter/markdown format, enabling teams to define domains, services, messages, channels, and their relationships in a single coherent source.
32
+
33
+ ```
34
+ domain Payment {
35
+ version 1.0.0
36
+ owner payment-team
37
+
38
+ service PaymentService {
39
+ version 1.0.0
40
+
41
+ sends event PaymentProcessed {
42
+ version 1.0.0
43
+ summary "Emitted when a payment completes successfully"
44
+ }
45
+
46
+ receives command ProcessPayment
47
+ receives event OrderCreated
48
+ }
49
+ }
50
+ ```
51
+
52
+ ## Design Principles
53
+
54
+ 1. **Readable** — Reads like English; minimal punctuation
55
+ 2. **Hierarchical** — Nesting reflects domain ownership and relationships
56
+ 3. **Concise** — Sane defaults; only specify what you need
57
+ 4. **Complete** — Can express everything EventCatalog supports
58
+ 5. **Composable** — Resources can be defined inline or referenced by ID
59
+
60
+ ## Lexical Structure
61
+
62
+ ### Comments
63
+
64
+ ```
65
+ // Single-line comment
66
+
67
+ /* Multi-line
68
+ comment */
69
+ ```
70
+
71
+ ### Strings
72
+
73
+ ```
74
+ "double-quoted string"
75
+ ```
76
+
77
+ Strings are required for values containing spaces, special characters, or multi-word text. Bare identifiers (no spaces, alphanumeric + hyphens + dots) can be unquoted. Strings follow JSON-style escaping (`\"`, `\\`, `\n`, etc.); raw `"` inside a string is not allowed.
78
+
79
+ ### Identifiers
80
+
81
+ Identifiers are bare words used for resource IDs and type names. Keywords are reserved and cannot be used as identifiers.
82
+
83
+ ```
84
+ OrderCreated // simple
85
+ Payment.OrderCreated // namespaced
86
+ my-service-name // kebab-case
87
+ ```
88
+
89
+ ### Version Literals
90
+
91
+ Semantic versions are bare (unquoted):
92
+
93
+ ```
94
+ version 1.0.0
95
+ version 2.1.0-beta.1
96
+ ```
97
+
98
+ ### Blocks
99
+
100
+ Curly braces delimit blocks. Properties inside blocks are newline-separated (no commas).
101
+
102
+ ```
103
+ resource Foo {
104
+ property value
105
+ property "value with spaces"
106
+ }
107
+ ```
108
+
109
+ ### Property Multiplicity
110
+
111
+ The grammar allows any property to appear multiple times inside a block. The semantic rules are:
112
+
113
+ **Single-value properties** — if repeated, the last occurrence wins:
114
+
115
+ `version`, `name`, `summary`, `address`, `protocol`, `deprecated`, `draft`, `container-type`, `technology`, `authoritative`, `access-mode`, `classification`, `residency`, `retention`
116
+
117
+ `schema` is also single-value but only valid on messages (`event`, `command`, `query`, and inline message definitions).
118
+
119
+ **Repeatable properties** — each occurrence appends a value:
120
+
121
+ `owner`, `sends`, `receives`, `writes-to`, `reads-from`, `flow`, `service` (ref), `subdomain`, `data-product`, `route`, `input`, `output`, `member`, `parameter`
122
+
123
+ `parameter` names must be unique within a channel; duplicate names are an error.
124
+
125
+ Annotations are repeatable by default (see Metadata & Annotations).
126
+
127
+ Unknown properties are parse errors — only the properties listed in each resource's grammar are valid. Duplicate single-value properties are syntactically valid (last wins) but tooling may warn.
128
+
129
+ ---
130
+
131
+ # Domain
132
+
133
+ Top-level bounded context. Can contain services, subdomains, data products, and flows.
134
+
135
+ ```
136
+ domain <id> {
137
+ // Required
138
+ version <semver>
139
+ name "<display name>" // optional, defaults to id
140
+
141
+ // Optional metadata
142
+ summary "<text>"
143
+ owner <owner-ref> // repeatable
144
+ deprecated true
145
+ draft true
146
+
147
+ // Relationships
148
+ service <service-ref> // repeatable (reference to external service)
149
+ subdomain <domain-ref> // repeatable
150
+ data-product <dp-ref> // repeatable
151
+ flow <flow-ref> // repeatable
152
+
153
+ // Domain-level message routing
154
+ sends <message-type> <id> [to <channel-ref>]
155
+ receives <message-type> <id> [from <channel-ref>]
156
+
157
+ // Inline definitions
158
+ service <id> { ... }
159
+ subdomain <id> { ... }
160
+
161
+ // Annotations (see Metadata section)
162
+ @badge(...)
163
+ @repository(...)
164
+ }
165
+ ```
166
+
167
+ ## Subdomains
168
+
169
+ Domains can contain nested subdomains:
170
+
171
+ ```
172
+ domain Logistics {
173
+ version 1.0.0
174
+
175
+ subdomain Shipping {
176
+ version 1.0.0
177
+ summary "Package shipping and tracking"
178
+
179
+ service ShippingService {
180
+ version 1.0.0
181
+ receives event OrderCreated
182
+ sends event ShipmentCreated
183
+ }
184
+ }
185
+
186
+ subdomain Returns {
187
+ version 1.0.0
188
+ summary "Return merchandise authorization"
189
+
190
+ service ReturnsService {
191
+ version 1.0.0
192
+ receives command InitiateReturn
193
+ sends event ReturnApproved
194
+ }
195
+ }
196
+ }
197
+ ```
198
+
199
+ ## EBNF
200
+
201
+ ```ebnf
202
+ domain_decl = "domain" identifier "{" common_props
203
+ { domain_body_item } "}" ;
204
+ domain_body_item = service_decl | subdomain_decl | service_ref_stmt
205
+ | data_product_ref_stmt | flow_ref_stmt
206
+ | sends_stmt | receives_stmt
207
+ | annotation ;
208
+ subdomain_decl = "subdomain" identifier "{" common_props
209
+ { domain_body_item } "}" ;
210
+ ```
211
+
212
+ ---
213
+
214
+ # Service
215
+
216
+ A microservice or application.
217
+
218
+ ```
219
+ service <id> {
220
+ // Required
221
+ version <semver>
222
+ name "<display name>"
223
+
224
+ // Optional metadata
225
+ summary "<text>"
226
+ owner <owner-ref> // repeatable
227
+ deprecated true
228
+ draft true
229
+
230
+ // Message relationships
231
+ sends <message-type> <id>[@<version>] [to <channel-list>]
232
+ receives <message-type> <id>[@<version>] [from <channel-list>]
233
+
234
+ // Data relationships
235
+ writes-to container <container-ref> // repeatable
236
+ reads-from container <container-ref> // repeatable
237
+
238
+ // Flow relationships
239
+ flow <flow-ref> // repeatable
240
+
241
+ // Inline message definitions
242
+ sends event <id> { ... }
243
+ sends command <id> { ... }
244
+ receives query <id> { ... }
245
+
246
+ // Annotations
247
+ @badge(...)
248
+ @repository(...)
249
+ }
250
+ ```
251
+
252
+ **Message types:** `event`, `command`, `query`
253
+
254
+ ## EBNF
255
+
256
+ ```ebnf
257
+ service_decl = "service" identifier "{" common_props
258
+ { service_body_item } "}" ;
259
+ service_body_item= sends_stmt | receives_stmt
260
+ | writes_to_stmt | reads_from_stmt
261
+ | flow_ref_stmt
262
+ | annotation ;
263
+ ```
264
+
265
+ ---
266
+
267
+ # Event
268
+
269
+ A domain event — something that happened.
270
+
271
+ ```
272
+ event <id> {
273
+ // Required
274
+ version <semver>
275
+ name "<display name>"
276
+
277
+ // Optional metadata
278
+ summary "<text>"
279
+ owner <owner-ref>
280
+ schema "<path>"
281
+ deprecated true
282
+ draft true
283
+
284
+ // Annotations
285
+ @badge(...)
286
+ @repository(...)
287
+ }
288
+ ```
289
+
290
+ ## EBNF
291
+
292
+ ```ebnf
293
+ event_decl = "event" identifier "{" message_props "}" ;
294
+ ```
295
+
296
+ ---
297
+
298
+ # Command
299
+
300
+ An instruction to perform an action.
301
+
302
+ ```
303
+ command <id> {
304
+ version <semver>
305
+ name "<display name>"
306
+ summary "<text>"
307
+ owner <owner-ref>
308
+ schema "<path>"
309
+ deprecated true
310
+ draft true
311
+
312
+ channel <channel-ref>
313
+
314
+ // Same annotations as event
315
+ }
316
+ ```
317
+
318
+ ## EBNF
319
+
320
+ ```ebnf
321
+ command_decl = "command" identifier "{" message_props "}" ;
322
+ ```
323
+
324
+ ---
325
+
326
+ # Query
327
+
328
+ A request for information.
329
+
330
+ ```
331
+ query <id> {
332
+ version <semver>
333
+ name "<display name>"
334
+ summary "<text>"
335
+ owner <owner-ref>
336
+ schema "<path>"
337
+ deprecated true
338
+ draft true
339
+
340
+ channel <channel-ref>
341
+
342
+ // Same annotations as event
343
+ }
344
+ ```
345
+
346
+ ## EBNF
347
+
348
+ ```ebnf
349
+ query_decl = "query" identifier "{" message_props "}" ;
350
+ ```
351
+
352
+ ---
353
+
354
+ # Channel
355
+
356
+ A communication channel (topic, queue, exchange, etc.).
357
+
358
+ ```
359
+ channel <id> {
360
+ version <semver>
361
+ name "<display name>"
362
+ summary "<text>"
363
+ owner <owner-ref>
364
+
365
+ address "<address-string>"
366
+ protocol "<protocol>"
367
+
368
+ // Channel parameters
369
+ parameter <name> {
370
+ description "<text>"
371
+ default "<value>"
372
+ enum ["<val1>", "<val2>"]
373
+ examples ["<ex1>", "<ex2>"]
374
+ }
375
+
376
+ // Routing
377
+ route <channel-ref> // repeatable
378
+
379
+ // Annotations
380
+ @badge(...)
381
+ @repository(...)
382
+ }
383
+ ```
384
+
385
+ ## Channel-to-Channel Routing
386
+
387
+ Channels can route to other channels using the `route` statement. This models message pipelines where data flows through multiple channels (e.g., Kafka topic → Kafka topic → MQTT broker):
388
+
389
+ ```
390
+ channel SensorIngestion {
391
+ version 1.0.0
392
+ protocol "Kafka"
393
+ route SensorFiltered
394
+ }
395
+
396
+ channel SensorFiltered {
397
+ version 1.0.0
398
+ protocol "Kafka"
399
+ route MqttDevices
400
+ }
401
+
402
+ channel MqttDevices {
403
+ version 1.0.0
404
+ protocol "MQTT"
405
+ }
406
+ ```
407
+
408
+ This creates a chain: `SensorIngestion → SensorFiltered → MqttDevices`.
409
+
410
+ A channel can route to multiple targets (fan-out):
411
+
412
+ ```
413
+ channel Ingestion {
414
+ version 1.0.0
415
+ route Analytics
416
+ route Archive
417
+ }
418
+ ```
419
+
420
+ Routes can include versioned references:
421
+
422
+ ```
423
+ channel Source {
424
+ version 1.0.0
425
+ route Target@2.0.0
426
+ }
427
+ ```
428
+
429
+ ## Service-to-Channel Routing
430
+
431
+ Services send/receive messages through channels:
432
+
433
+ ```
434
+ service OrderService {
435
+ version 1.0.0
436
+
437
+ // Simple — no channel
438
+ sends event OrderCreated
439
+
440
+ // To a single channel
441
+ sends event OrderCreated to orders-topic
442
+
443
+ // To a channel with version (using @ syntax)
444
+ sends event OrderCreated to orders-topic@1.0.0
445
+
446
+ // To multiple channels (comma-separated)
447
+ sends event OrderCreated to orders-topic, orders-backup-topic
448
+
449
+ // To multiple channels with versions
450
+ sends event OrderCreated to orders-topic@1.0.0, orders-backup-topic@2.0.0
451
+
452
+ // Receiving from a single channel
453
+ receives event PaymentProcessed from payment-events
454
+
455
+ // Receiving from multiple channels
456
+ receives event PaymentProcessed from payment-events, payment-retry-queue
457
+
458
+ // Receiving from channels with versions
459
+ receives event PaymentProcessed from payment-events@1.0.0, payment-retry-queue@2.1.0
460
+ }
461
+ ```
462
+
463
+ ## EBNF
464
+
465
+ ```ebnf
466
+ channel_decl = "channel" identifier "{" common_props
467
+ { channel_body_item } "}" ;
468
+ channel_body_item= address_prop | protocol_prop | parameter_decl
469
+ | route_stmt | annotation ;
470
+ address_prop = "address" string_lit ;
471
+ protocol_prop = "protocol" string_lit ;
472
+ parameter_decl = "parameter" identifier "{" { param_prop } "}" ;
473
+ param_prop = "description" string_lit
474
+ | "default" string_lit
475
+ | "enum" "[" string_lit { "," string_lit } "]"
476
+ | "examples" "[" string_lit { "," string_lit } "]" ;
477
+ route_stmt = "route" resource_ref ;
478
+
479
+ channel_clause = to_clause | from_clause ;
480
+ to_clause = "to" channel_ref_list ;
481
+ from_clause = "from" channel_ref_list ;
482
+ channel_ref_list = channel_ref { "," channel_ref } ;
483
+ channel_ref = identifier [ "@" version_lit ] ;
484
+ ```
485
+
486
+ ---
487
+
488
+ # Container
489
+
490
+ A data store, cache, or external system.
491
+
492
+ ```
493
+ container <id> {
494
+ version <semver>
495
+ name "<display name>"
496
+ summary "<text>"
497
+ owner <owner-ref>
498
+
499
+ // Required
500
+ container-type <database | cache | objectStore | searchIndex
501
+ | dataWarehouse | dataLake | externalSaaS | other>
502
+
503
+ // Optional
504
+ deprecated true
505
+ draft true
506
+ technology "<tech-string>" // e.g., "postgres@15", "redis@7"
507
+ authoritative true
508
+ access-mode <read | write | readWrite | appendOnly>
509
+ classification <public | internal | confidential | regulated>
510
+ residency "<location>"
511
+ retention "<duration>" // e.g., "90d", "10y"
512
+
513
+ // Relationships
514
+ service <service-ref> // repeatable
515
+
516
+ // Annotations
517
+ @badge(...)
518
+ @repository(...)
519
+ }
520
+ ```
521
+
522
+ ## EBNF
523
+
524
+ ```ebnf
525
+ container_decl = "container" identifier "{" common_props
526
+ { container_body_item } "}" ;
527
+ container_body_item = container_type_prop | technology_prop
528
+ | authoritative_prop | access_mode_prop
529
+ | classification_prop | residency_prop
530
+ | retention_prop
531
+ | service_ref_stmt | annotation ;
532
+ container_type_prop = "container-type" container_type_enum ;
533
+ container_type_enum = "database" | "cache" | "objectStore" | "searchIndex"
534
+ | "dataWarehouse" | "dataLake" | "externalSaaS" | "other" ;
535
+ technology_prop = "technology" string_lit ;
536
+ authoritative_prop = "authoritative" bool_lit ;
537
+ access_mode_prop = "access-mode" ( "read" | "write" | "readWrite" | "appendOnly" ) ;
538
+ classification_prop = "classification" ( "public" | "internal" | "confidential" | "regulated" ) ;
539
+ residency_prop = "residency" string_lit ;
540
+ retention_prop = "retention" string_lit ;
541
+ ```
542
+
543
+ ---
544
+
545
+ # Data Product
546
+
547
+ An analytical data product.
548
+
549
+ ```
550
+ data-product <id> {
551
+ version <semver>
552
+ name "<display name>"
553
+ summary "<text>"
554
+ owner <owner-ref>
555
+ deprecated true
556
+ draft true
557
+
558
+ // Data lineage
559
+ input <message-type> <resource-ref> // repeatable
560
+ output <message-type> <resource-ref> { // repeatable, with optional contract
561
+ contract {
562
+ path "<path>"
563
+ name "<name>"
564
+ type "<type>"
565
+ }
566
+ }
567
+
568
+ // Annotations
569
+ @badge(...)
570
+ }
571
+ ```
572
+
573
+ ## EBNF
574
+
575
+ ```ebnf
576
+ data_product_decl = "data-product" identifier "{" common_props
577
+ { dp_body_item } "}" ;
578
+ dp_body_item = input_stmt | output_stmt | annotation ;
579
+ input_stmt = "input" message_type resource_ref ;
580
+ output_stmt = "output" message_type resource_ref [ "{" contract_block "}" ] ;
581
+ contract_block = "contract" "{" "path" string_lit "name" string_lit
582
+ [ "type" string_lit ] "}" ;
583
+ ```
584
+
585
+ ---
586
+
587
+ # Flow
588
+
589
+ Flows define step-by-step business processes using a PM-friendly `when`-block syntax. Resources are referenced by name only — types are resolved from the catalog.
590
+
591
+ ```
592
+ flow <id> {
593
+ version <semver>
594
+ name "<display name>"
595
+ summary "<text>"
596
+ owner <owner-ref>
597
+
598
+ // Entry chain — the starting sequence
599
+ <Name> ["<label>"] -> <Name> ["<label>"] -> ...
600
+
601
+ // When blocks — react to events
602
+ when <TriggerName>
603
+ <ServiceName> "<description>"
604
+ -> "<label>": <OutputName>
605
+
606
+ // Convergence — multiple triggers must complete
607
+ when <TriggerA> and <TriggerB>
608
+ <ServiceName> "<description>"
609
+ }
610
+ ```
611
+
612
+ ## Actors and External Systems
613
+
614
+ To use actors and external systems in flows, define them as top-level resources. The flow resolves their type automatically by name:
615
+
616
+ ```
617
+ actor Customer {
618
+ name "Customer"
619
+ summary "End user on the storefront"
620
+ }
621
+
622
+ external-system WarehouseWMS {
623
+ name "Warehouse WMS"
624
+ summary "Legacy warehouse management system"
625
+ }
626
+ ```
627
+
628
+ Both support optional bodies with `name`, `summary`, and annotations. They can also be bare (no body):
629
+
630
+ ```
631
+ actor Customer
632
+ external-system WarehouseWMS
633
+ ```
634
+
635
+ ## Flow References
636
+
637
+ Resources in flows are referenced by name only (no type keywords like `service` or `event`). Types are resolved from catalog definitions or sibling `.ec` files. If no matching definition is found, the default type is `step`. Compilers may warn on unresolved flow references.
638
+
639
+ Each reference can include an optional label:
640
+
641
+ - `Customer "places an order"` — name with display label
642
+ - `PlaceOrder` — bare name (no label)
643
+ - `PaymentService "processes the payment"` — service with description
644
+
645
+ ## Entry Chains
646
+
647
+ The entry chain defines the starting sequence of a flow. It uses arrow (`->`) syntax:
648
+
649
+ ```
650
+ Customer "places an order"
651
+ -> PlaceOrder
652
+ -> OrderService "creates the order"
653
+ -> OrderCreated
654
+ ```
655
+
656
+ Multiple sources can converge into a chain using commas:
657
+
658
+ ```
659
+ EventA, EventB -> MergingService
660
+ ```
661
+
662
+ ## When Blocks
663
+
664
+ `when` blocks define reactions to events. Each block starts with one or more trigger names, followed by actions:
665
+
666
+ ```
667
+ when OrderCreated
668
+ PaymentService "processes the payment"
669
+ -> "success": PaymentProcessed
670
+ -> "failure": PaymentFailed
671
+ InventoryService "reserves stock"
672
+ -> StockReserved
673
+ ```
674
+
675
+ ### Labeled Outputs
676
+
677
+ Action outputs can have optional labels (quoted strings followed by a colon):
678
+
679
+ ```
680
+ -> "success": PaymentProcessed // labeled output
681
+ -> StockReserved // unlabeled output
682
+ ```
683
+
684
+ ### Convergence
685
+
686
+ Use `and` to require multiple triggers before actions execute:
687
+
688
+ ```
689
+ when PaymentProcessed and StockReserved
690
+ FulfillmentService "ships the order"
691
+ -> OrderShipped
692
+ ```
693
+
694
+ ### Terminal Actions
695
+
696
+ Actions without outputs are terminal steps:
697
+
698
+ ```
699
+ when OrderShipped
700
+ WarehouseWMS "syncs with legacy WMS"
701
+ NotificationService "notifies the customer"
702
+ ```
703
+
704
+ ## Example
705
+
706
+ ```
707
+ flow OrderFulfillment {
708
+ version 1.0.0
709
+ name "Order Fulfillment"
710
+ summary "End-to-end order processing from placement to delivery"
711
+ owner fulfillment-team
712
+
713
+ Customer "places an order"
714
+ -> PlaceOrder
715
+ -> OrderService "creates the order"
716
+ -> OrderCreated
717
+
718
+ when OrderCreated
719
+ PaymentService "processes the payment"
720
+ -> "success": PaymentProcessed
721
+ -> "failure": PaymentFailed
722
+ InventoryService "reserves stock"
723
+ -> StockReserved
724
+
725
+ when PaymentFailed
726
+ NotificationService "notifies the customer of failure"
727
+
728
+ when PaymentProcessed and StockReserved
729
+ FulfillmentService "ships the order"
730
+ -> OrderShipped
731
+
732
+ when OrderShipped
733
+ WarehouseWMS "syncs with legacy WMS"
734
+ NotificationService "notifies the customer"
735
+ -> CustomerNotified
736
+ }
737
+ ```
738
+
739
+ ## EBNF
740
+
741
+ ```ebnf
742
+ flow_decl = "flow" identifier "{" common_props
743
+ { flow_entry_chain | flow_when_block } "}" ;
744
+ flow_entry_chain = flow_ref { "," flow_ref } ( "->" flow_ref )+ ;
745
+ flow_when_block = "when" flow_ref { "and" flow_ref } flow_action+ ;
746
+ flow_action = flow_ref { flow_output } ;
747
+ flow_output = "->" [ string_lit ":" ] flow_ref ;
748
+ flow_ref = identifier [ string_lit ] ;
749
+ ```
750
+
751
+ ---
752
+
753
+ # Diagram
754
+
755
+ _Removed from V1. May be added in a future version._
756
+
757
+ ---
758
+
759
+ # User
760
+
761
+ A user definition for ownership and team membership.
762
+
763
+ ```
764
+ user <id> {
765
+ name "<display name>"
766
+ avatar "<url>"
767
+ role "<role>"
768
+ email "<email>"
769
+ slack "<url>"
770
+ ms-teams "<url>"
771
+
772
+ // Team membership
773
+ team <team-id>
774
+
775
+ // Ownership declarations
776
+ owns domain <id>
777
+ owns service <id>
778
+ owns event <id>
779
+ owns command <id>
780
+ owns query <id>
781
+ }
782
+ ```
783
+
784
+ ## Example
785
+
786
+ ```
787
+ user dboyne {
788
+ name "David Boyne"
789
+ avatar "https://avatars.githubusercontent.com/u/3268013"
790
+ role "Principal Engineer"
791
+ email "david@company.com"
792
+
793
+ owns domain Payment
794
+ owns service PaymentService
795
+ }
796
+ ```
797
+
798
+ ## EBNF
799
+
800
+ ```ebnf
801
+ user_decl = "user" identifier "{" user_props "}" ;
802
+ user_props = "name" string_lit
803
+ | "avatar" string_lit
804
+ | "role" string_lit
805
+ | "email" string_lit
806
+ | "slack" string_lit
807
+ | "ms-teams" string_lit
808
+ | owns_stmt
809
+ | "team" identifier ;
810
+ owns_stmt = "owns" resource_type_kw identifier ;
811
+ resource_type_kw = "domain" | "service" | "event" | "command" | "query" ;
812
+ ```
813
+
814
+ ---
815
+
816
+ # Team
817
+
818
+ A team definition for ownership and membership.
819
+
820
+ ```
821
+ team <id> {
822
+ name "<display name>"
823
+ summary "<text>"
824
+ email "<email>"
825
+ slack "<url>"
826
+ ms-teams "<url>"
827
+
828
+ // Members
829
+ member <user-id> // repeatable
830
+
831
+ // Ownership declarations
832
+ owns domain <id>
833
+ owns service <id>
834
+ owns event <id>
835
+ owns command <id>
836
+ owns query <id>
837
+ }
838
+ ```
839
+
840
+ ## Owner References
841
+
842
+ Owners are referenced by team or user ID:
843
+
844
+ ```
845
+ service OrderService {
846
+ version 1.0.0
847
+ owner payment-team // team reference
848
+ owner dboyne // user reference
849
+ }
850
+ ```
851
+
852
+ ## Example
853
+
854
+ ```
855
+ team orders-team {
856
+ name "Orders Team"
857
+ summary "Responsible for order lifecycle"
858
+ email "orders@company.com"
859
+ slack "https://company.slack.com/channels/orders"
860
+
861
+ member dboyne
862
+ member jane-doe
863
+ }
864
+ ```
865
+
866
+ ## EBNF
867
+
868
+ ```ebnf
869
+ team_decl = "team" identifier "{" team_props "}" ;
870
+ team_props = "name" string_lit
871
+ | "summary" string_lit
872
+ | "email" string_lit
873
+ | "slack" string_lit
874
+ | "ms-teams" string_lit
875
+ | "member" identifier
876
+ | owns_stmt ;
877
+ ```
878
+
879
+ ---
880
+
881
+ # Relationships & Pointers
882
+
883
+ ## Resource References
884
+
885
+ Resources can be referenced by ID alone (resolves to `latest`) or with an explicit version:
886
+
887
+ ```
888
+ // Reference by ID only (latest version)
889
+ receives event OrderCreated
890
+
891
+ // Reference by ID + version using @ syntax
892
+ receives event OrderCreated@2.0.0
893
+
894
+ // Reference with single channel routing
895
+ sends event OrderCreated to orders-topic
896
+
897
+ // Reference with channel version
898
+ sends event OrderCreated@1.0.0 to orders-topic@1.0.0
899
+
900
+ // Reference with multiple channels
901
+ sends event OrderCreated to orders-topic, backup-topic
902
+ receives event PaymentProcessed from payment-events, payment-retry
903
+ ```
904
+
905
+ ## Inline vs. Reference
906
+
907
+ Resources can be defined inline (creating them) or referenced (linking to existing):
908
+
909
+ ```
910
+ service OrderService {
911
+ version 1.0.0
912
+
913
+ // Inline definition — creates the event
914
+ sends event OrderCreated {
915
+ version 1.0.0
916
+ summary "A new order was placed"
917
+ }
918
+
919
+ // Reference only — links to an existing event
920
+ receives event PaymentProcessed
921
+ receives event PaymentProcessed@2.0.0
922
+ }
923
+ ```
924
+
925
+ Inline messages do not support the `channel` statement — use `to`/`from` on the `sends`/`receives` statement for channel routing.
926
+
927
+ ## Pointer Syntax Summary
928
+
929
+ | Syntax | Meaning |
930
+ | ----------------------------- | ----------------------------- |
931
+ | `<id>` | Latest version of resource |
932
+ | `<id>@<version>` | Specific version |
933
+ | `<id> to <channel>` | With single channel routing |
934
+ | `<id> to <channel>@<version>` | Channel with specific version |
935
+ | `<id> to <ch1>, <ch2>, <ch3>` | Multiple channels |
936
+ | `<id> from <channel>` | Received from channel |
937
+ | `<id> from <ch1>, <ch2>` | Received from multiple |
938
+
939
+ ## Data Relationships
940
+
941
+ ```
942
+ writes-to container <container-ref>
943
+ reads-from container <container-ref>
944
+ ```
945
+
946
+ ## EBNF
947
+
948
+ ```ebnf
949
+ resource_ref = identifier [ "@" version_lit ] ;
950
+ message_type = "event" | "command" | "query" ;
951
+
952
+ sends_stmt = "sends" message_type resource_ref [ channel_clause ]
953
+ | "sends" message_type identifier inline_block ;
954
+ receives_stmt = "receives" message_type resource_ref [ channel_clause ]
955
+ | "receives" message_type identifier inline_block ;
956
+
957
+ channel_clause = to_clause | from_clause ;
958
+ to_clause = "to" channel_ref_list ;
959
+ from_clause = "from" channel_ref_list ;
960
+ channel_ref_list = channel_ref { "," channel_ref } ;
961
+ channel_ref = identifier [ "@" version_lit ] ;
962
+
963
+ writes_to_stmt = "writes-to" "container" resource_ref ;
964
+ reads_from_stmt = "reads-from" "container" resource_ref ;
965
+ flow_ref_stmt = "flow" resource_ref ;
966
+ data_product_ref_stmt = "data-product" resource_ref ;
967
+
968
+ inline_block = "{" message_props "}" ;
969
+ ```
970
+
971
+ ---
972
+
973
+ # Versioning
974
+
975
+ ## Declaring Versions
976
+
977
+ Every versioned resource requires a version:
978
+
979
+ ```
980
+ event OrderCreated {
981
+ version 1.0.0
982
+ }
983
+ ```
984
+
985
+ ## Referencing Versions
986
+
987
+ ```
988
+ // Latest (default - no version specified)
989
+ receives event OrderCreated
990
+
991
+ // Specific version using @ syntax
992
+ receives event OrderCreated@2.0.0
993
+
994
+ // Channel with version
995
+ sends event OrderProcessed to payments-channel@1.0.0
996
+
997
+ // Multiple channels with versions
998
+ receives event PaymentFailed from channel-a@1.0.0, channel-b@2.1.0
999
+ ```
1000
+
1001
+ ## Version Defaults
1002
+
1003
+ When no version is specified in a reference, it resolves to `latest`.
1004
+
1005
+ ## EBNF
1006
+
1007
+ ```ebnf
1008
+ int = digit { digit } ;
1009
+ version_lit = int "." int "." int [ "-" prerelease ] ;
1010
+ version_prop = "version" version_lit ;
1011
+ version_ref = "@" version_lit ;
1012
+ resource_ref = identifier [ version_ref ] ;
1013
+ ```
1014
+
1015
+ ---
1016
+
1017
+ # Metadata & Annotations
1018
+
1019
+ Annotations use the `@` prefix and provide additional metadata. They can appear inside any resource block. All annotations are repeatable by default — multiple instances of the same annotation may appear on a single resource.
1020
+
1021
+ ## @badge
1022
+
1023
+ ```
1024
+ @badge("Production", bg: "#22c55e", text: "#fff")
1025
+ @badge("Beta", bg: "#f59e0b", text: "#000", icon: "flask")
1026
+ ```
1027
+
1028
+ ## @repository
1029
+
1030
+ ```
1031
+ @repository(url: "https://github.com/org/repo", language: "TypeScript")
1032
+ ```
1033
+
1034
+ ## @editUrl
1035
+
1036
+ ```
1037
+ @editUrl("https://github.com/org/repo/edit/main/docs/orders.md")
1038
+ ```
1039
+
1040
+ ## @note
1041
+
1042
+ Adds free-form developer notes or reminders to any resource. Notes are repeatable — multiple `@note` annotations can appear on a single resource. Supported on services, events, commands, queries, channels, and inline message definitions.
1043
+
1044
+ ```
1045
+ // Simple note
1046
+ @note("Come back later — needs review")
1047
+
1048
+ // With optional parameters
1049
+ @note("Align schema with PaymentService team", author: "dboyne", priority: "high")
1050
+
1051
+ // Multiple notes on a service
1052
+ service OrderService {
1053
+ version 1.0.0
1054
+ @note("TODO: add retry logic")
1055
+ @note("Waiting on team alignment", priority: "medium")
1056
+ }
1057
+
1058
+ // Notes on events, commands, and queries
1059
+ event OrderCreated {
1060
+ version 1.0.0
1061
+ @note("Schema v2 adds shippingAddress field", author: "alice")
1062
+ }
1063
+
1064
+ command CreateOrder {
1065
+ version 1.0.0
1066
+ @note("Validate idempotency key", author: "bob", priority: "high")
1067
+ }
1068
+
1069
+ // Notes on channels
1070
+ channel OrderEvents {
1071
+ version 1.0.0
1072
+ address "orders.events"
1073
+ protocol "kafka"
1074
+ @note("Partition key is orderId", author: "infra-team")
1075
+ }
1076
+
1077
+ // Notes on inline messages (inside sends/receives)
1078
+ service PaymentService {
1079
+ version 1.0.0
1080
+ sends event PaymentProcessed to PaymentEvents {
1081
+ version 1.0.0
1082
+ @note("Includes refund reference when applicable", author: "dave")
1083
+ }
1084
+ }
1085
+ ```
1086
+
1087
+ ## @detailsPanel
1088
+
1089
+ Controls visibility of detail panel sections:
1090
+
1091
+ ```
1092
+ @detailsPanel {
1093
+ owners visible
1094
+ versions visible
1095
+ changelog hidden
1096
+ producers visible
1097
+ consumers hidden
1098
+ channels visible
1099
+ repository hidden
1100
+ specifications hidden
1101
+ messages hidden
1102
+ domains hidden
1103
+ services hidden
1104
+ containers hidden
1105
+ }
1106
+ ```
1107
+
1108
+ ## EBNF
1109
+
1110
+ ```ebnf
1111
+ annotation = "@" ann_name [ "(" ann_args ")" ] [ ann_block ] ;
1112
+ ann_name = identifier ;
1113
+ ann_args = ann_arg { "," ann_arg } ;
1114
+ ann_arg = [ identifier ":" ] ( string_lit | bool_lit | number_lit | identifier ) ;
1115
+ ann_block = "{" { ann_body_item } "}" ;
1116
+ ```
1117
+
1118
+ ---
1119
+
1120
+ # Complete Examples
1121
+
1122
+ ## Example 1: E-Commerce Platform
1123
+
1124
+ ```
1125
+ // ============================================================
1126
+ // Teams & Users
1127
+ // ============================================================
1128
+
1129
+ user dboyne {
1130
+ name "David Boyne"
1131
+ avatar "https://avatars.githubusercontent.com/u/3268013"
1132
+ role "Principal Engineer"
1133
+ email "david@company.com"
1134
+ }
1135
+
1136
+ user jane-doe {
1137
+ name "Jane Doe"
1138
+ avatar "https://avatars.githubusercontent.com/u/12345"
1139
+ role "Staff Engineer"
1140
+ }
1141
+
1142
+ team orders-team {
1143
+ name "Orders Team"
1144
+ summary "Responsible for order lifecycle"
1145
+ email "orders@company.com"
1146
+ slack "https://company.slack.com/channels/orders"
1147
+
1148
+ member dboyne
1149
+ member jane-doe
1150
+ }
1151
+
1152
+ team payment-team {
1153
+ name "Payment Team"
1154
+ summary "Handles payment processing and fraud detection"
1155
+ email "payments@company.com"
1156
+
1157
+ member jane-doe
1158
+ }
1159
+
1160
+ // ============================================================
1161
+ // Channels
1162
+ // ============================================================
1163
+
1164
+ channel orders-topic {
1165
+ version 1.0.0
1166
+ name "Orders Topic"
1167
+ summary "Kafka topic for all order-related events"
1168
+ address "kafka://production/orders"
1169
+ protocol "Kafka"
1170
+
1171
+ parameter environment {
1172
+ description "Deployment environment"
1173
+ default "production"
1174
+ enum ["production", "staging", "development"]
1175
+ }
1176
+
1177
+ }
1178
+
1179
+ channel payment-queue {
1180
+ version 1.0.0
1181
+ name "Payment Queue"
1182
+ summary "SQS queue for payment commands"
1183
+ address "sqs://us-east-1/payment-processing"
1184
+ protocol "SQS"
1185
+ }
1186
+
1187
+ // ============================================================
1188
+ // Containers
1189
+ // ============================================================
1190
+
1191
+ container orders-db {
1192
+ version 1.0.0
1193
+ name "Orders Database"
1194
+ summary "Primary datastore for order data"
1195
+ owner orders-team
1196
+
1197
+ container-type database
1198
+ technology "postgres@15"
1199
+ authoritative true
1200
+ access-mode readWrite
1201
+ classification confidential
1202
+ residency "us-east-1"
1203
+ retention "7y"
1204
+
1205
+ @repository(url: "https://github.com/company/orders-db")
1206
+ }
1207
+
1208
+ container orders-cache {
1209
+ version 1.0.0
1210
+ name "Orders Cache"
1211
+ summary "Redis cache for hot order lookups"
1212
+
1213
+ container-type cache
1214
+ technology "redis@7"
1215
+ access-mode readWrite
1216
+ retention "24h"
1217
+ }
1218
+
1219
+ // ============================================================
1220
+ // Domain
1221
+ // ============================================================
1222
+
1223
+ domain Orders {
1224
+ version 1.0.0
1225
+ name "Orders Domain"
1226
+ summary "Everything related to order management"
1227
+ owner orders-team
1228
+
1229
+ @badge("Core", bg: "#3b82f6", text: "#fff")
1230
+ @repository(url: "https://github.com/company/orders-domain")
1231
+
1232
+ service OrderService {
1233
+ version 1.0.0
1234
+ name "Order Service"
1235
+ summary "Manages the order lifecycle"
1236
+ owner orders-team
1237
+
1238
+ @repository(url: "https://github.com/company/order-service", language: "TypeScript")
1239
+ @badge("Production", bg: "#22c55e", text: "#fff")
1240
+
1241
+ sends event OrderCreated {
1242
+ version 1.0.0
1243
+ summary "Emitted when a new order is placed"
1244
+ schema "./schemas/order-created.avro"
1245
+ }
1246
+
1247
+ sends event OrderUpdated {
1248
+ version 1.0.0
1249
+ summary "Emitted when order details change"
1250
+ }
1251
+
1252
+ sends command ProcessPayment to payment-queue {
1253
+ version 1.0.0
1254
+ summary "Triggers payment processing for an order"
1255
+ }
1256
+
1257
+ receives event PaymentProcessed from payment-queue
1258
+ receives event InventoryReserved
1259
+
1260
+ writes-to container orders-db
1261
+ reads-from container orders-db
1262
+ writes-to container orders-cache
1263
+ reads-from container orders-cache
1264
+
1265
+ flow OrderFulfillment@1.0.0
1266
+ }
1267
+
1268
+ service NotificationService {
1269
+ version 1.0.0
1270
+ name "Notification Service"
1271
+ summary "Sends email and push notifications for order updates"
1272
+
1273
+ receives event OrderCreated
1274
+ receives event OrderUpdated
1275
+
1276
+ sends command SendEmail {
1277
+ version 1.0.0
1278
+ summary "Dispatches an email notification"
1279
+ }
1280
+ }
1281
+ }
1282
+
1283
+ domain Payment {
1284
+ version 1.0.0
1285
+ name "Payment Domain"
1286
+ summary "Payment processing and fraud detection"
1287
+ owner payment-team
1288
+
1289
+ service PaymentService {
1290
+ version 1.0.0
1291
+ name "Payment Service"
1292
+ summary "Processes payments via Stripe"
1293
+ owner payment-team
1294
+
1295
+ @repository(url: "https://github.com/company/payment-service", language: "Go")
1296
+
1297
+ receives command ProcessPayment from payment-queue
1298
+
1299
+ sends event PaymentProcessed {
1300
+ version 1.0.0
1301
+ summary "Payment completed successfully"
1302
+ }
1303
+
1304
+ sends event PaymentFailed {
1305
+ version 1.0.0
1306
+ summary "Payment was declined or errored"
1307
+ }
1308
+ }
1309
+ }
1310
+
1311
+ // ============================================================
1312
+ // Standalone events (defined outside services)
1313
+ // ============================================================
1314
+
1315
+ event InventoryReserved {
1316
+ version 1.0.0
1317
+ name "Inventory Reserved"
1318
+ summary "Stock has been reserved for an order"
1319
+ owner orders-team
1320
+
1321
+ @badge("Critical", bg: "#ef4444", text: "#fff")
1322
+ }
1323
+
1324
+ // ============================================================
1325
+ // Data Product
1326
+ // ============================================================
1327
+
1328
+ data-product OrderAnalytics {
1329
+ version 1.0.0
1330
+ name "Order Analytics"
1331
+ summary "Real-time and batch analytics for order metrics"
1332
+ owner orders-team
1333
+
1334
+ input event OrderCreated@1.0.0
1335
+ input event PaymentProcessed@1.0.0
1336
+ input event InventoryReserved@1.0.0
1337
+
1338
+ output event OrderMetrics {
1339
+ contract {
1340
+ path "./contracts/order-metrics.json"
1341
+ name "Order Metrics Schema"
1342
+ type "json-schema"
1343
+ }
1344
+ }
1345
+ }
1346
+
1347
+ // ============================================================
1348
+ // Actors & External Systems
1349
+ // ============================================================
1350
+
1351
+ actor Customer {
1352
+ name "Customer"
1353
+ summary "End user on the storefront"
1354
+ }
1355
+
1356
+ external-system WarehouseWMS {
1357
+ name "Warehouse WMS"
1358
+ summary "Legacy warehouse management system via SOAP API"
1359
+ }
1360
+
1361
+ // ============================================================
1362
+ // Flow
1363
+ // ============================================================
1364
+
1365
+ flow OrderFulfillment {
1366
+ version 1.0.0
1367
+ name "Order Fulfillment"
1368
+ summary "End-to-end order processing from placement to delivery"
1369
+ owner orders-team
1370
+
1371
+ Customer "End user on the storefront"
1372
+ -> PlaceOrder
1373
+ -> OrderService
1374
+ -> OrderCreated
1375
+
1376
+ when OrderCreated
1377
+ PaymentService "processes the payment"
1378
+ -> "success": PaymentProcessed
1379
+ -> "failure": PaymentFailed
1380
+ InventoryService "reserves inventory"
1381
+ -> InventoryReserved
1382
+
1383
+ when InventoryReserved
1384
+ WarehouseWMS "Legacy WMS via SOAP API"
1385
+ }
1386
+
1387
+ ```
1388
+
1389
+ ## Example 2: Minimal Service Definition
1390
+
1391
+ The DSL supports minimal definitions where defaults are sufficient:
1392
+
1393
+ ```
1394
+ service OrderService {
1395
+ version 1.0.0
1396
+ sends event OrderCreated
1397
+ receives command ProcessPayment
1398
+ receives event PaymentProcessed
1399
+ }
1400
+ ```
1401
+
1402
+ ## Example 3: Multi-Channel Routing
1403
+
1404
+ ```
1405
+ service EventRouter {
1406
+ version 1.0.0
1407
+ summary "Routes events across multiple channels"
1408
+
1409
+ // Send to multiple channels (comma-separated)
1410
+ sends event OrderCreated to orders-topic, orders-archive-topic
1411
+
1412
+ // Receive from multiple channels
1413
+ receives event PaymentProcessed from payment-events, payment-retry-queue
1414
+ }
1415
+ ```
1416
+
1417
+ ## Example 4: Channel Routing (IoT Pipeline)
1418
+
1419
+ ```
1420
+ channel SensorIngestion {
1421
+ version 1.0.0
1422
+ address "sensors.raw"
1423
+ protocol "Kafka"
1424
+ summary "Raw sensor data ingestion"
1425
+ route SensorFiltered
1426
+ }
1427
+
1428
+ channel SensorFiltered {
1429
+ version 1.0.0
1430
+ address "sensors.filtered"
1431
+ protocol "Kafka"
1432
+ summary "Validated sensor data"
1433
+ route DeviceCommands
1434
+ }
1435
+
1436
+ channel DeviceCommands {
1437
+ version 1.0.0
1438
+ address "devices/+/commands"
1439
+ protocol "MQTT"
1440
+ summary "MQTT topic for device commands"
1441
+ }
1442
+
1443
+ service SensorGateway {
1444
+ version 1.0.0
1445
+ summary "Ingests raw sensor readings"
1446
+ sends event SensorReading to SensorIngestion
1447
+ }
1448
+
1449
+ service FilterService {
1450
+ version 1.0.0
1451
+ summary "Validates and filters sensor data"
1452
+ receives event SensorReading from SensorIngestion
1453
+ sends event DeviceAlert to SensorFiltered
1454
+ }
1455
+
1456
+ service DeviceBridge {
1457
+ version 1.0.0
1458
+ summary "Bridges Kafka to MQTT"
1459
+ receives event DeviceAlert from SensorFiltered
1460
+ sends command RecalibrateDevice to DeviceCommands
1461
+ }
1462
+ ```
1463
+
1464
+ ## Example 5: Subdomains
1465
+
1466
+ ```
1467
+ domain Logistics {
1468
+ version 1.0.0
1469
+
1470
+ subdomain Shipping {
1471
+ version 1.0.0
1472
+ summary "Package shipping and tracking"
1473
+
1474
+ service ShippingService {
1475
+ version 1.0.0
1476
+ receives event OrderCreated
1477
+ sends event ShipmentCreated
1478
+ }
1479
+ }
1480
+
1481
+ subdomain Returns {
1482
+ version 1.0.0
1483
+ summary "Return merchandise authorization"
1484
+
1485
+ service ReturnsService {
1486
+ version 1.0.0
1487
+ receives command InitiateReturn
1488
+ sends event ReturnApproved
1489
+ }
1490
+ }
1491
+ }
1492
+ ```
1493
+
1494
+ ---
1495
+
1496
+ # Full Grammar (EBNF)
1497
+
1498
+ ```ebnf
1499
+ (* Top-level *)
1500
+ program = { top_level_decl } ;
1501
+ top_level_decl = domain_decl | service_decl | event_decl | command_decl
1502
+ | query_decl | channel_decl | container_decl
1503
+ | data_product_decl | flow_decl
1504
+ | user_decl | team_decl | visualizer_decl
1505
+ | actor_decl | external_system_decl ;
1506
+
1507
+ (* Identifiers and literals *)
1508
+ identifier = letter { letter | digit | "-" | "." | "_" } ;
1509
+ int = digit { digit } ;
1510
+ version_lit = int "." int "." int [ "-" prerelease ] ;
1511
+ string_lit = '"' { any_char } '"' ;
1512
+ bool_lit = "true" | "false" ;
1513
+ number_lit = digit { digit } ;
1514
+
1515
+ (* Common properties *)
1516
+ common_props = { version_prop | name_prop | summary_prop | owner_prop
1517
+ | deprecated_prop | draft_prop | annotation } ;
1518
+ message_props = { version_prop | name_prop | summary_prop | owner_prop
1519
+ | schema_prop | deprecated_prop | draft_prop | annotation } ;
1520
+ version_prop = "version" version_lit ;
1521
+ name_prop = "name" string_lit ;
1522
+ summary_prop = "summary" string_lit ;
1523
+ owner_prop = "owner" identifier ;
1524
+ schema_prop = "schema" string_lit ;
1525
+ deprecated_prop = "deprecated" bool_lit ;
1526
+ draft_prop = "draft" bool_lit ;
1527
+
1528
+ (* Annotations *)
1529
+ annotation = "@" ann_name [ "(" ann_args ")" ] [ ann_block ] ;
1530
+ ann_name = identifier ;
1531
+ ann_args = ann_arg { "," ann_arg } ;
1532
+ ann_arg = [ identifier ":" ] ( string_lit | bool_lit | number_lit | identifier ) ;
1533
+ ann_block = "{" { ann_body_item } "}" ;
1534
+
1535
+ (* Resource references *)
1536
+ resource_ref = identifier [ "@" version_lit ] ;
1537
+ message_type = "event" | "command" | "query" ;
1538
+
1539
+ (* Domain *)
1540
+ domain_decl = "domain" identifier "{" common_props
1541
+ { domain_body_item } "}" ;
1542
+ domain_body_item = service_decl | subdomain_decl
1543
+ | data_product_ref_stmt | flow_ref_stmt
1544
+ | sends_stmt | receives_stmt
1545
+ | annotation ;
1546
+ subdomain_decl = "subdomain" identifier "{" common_props
1547
+ { domain_body_item } "}" ;
1548
+
1549
+ (* Service *)
1550
+ service_decl = "service" identifier "{" common_props
1551
+ { service_body_item } "}" ;
1552
+ service_body_item= sends_stmt | receives_stmt
1553
+ | writes_to_stmt | reads_from_stmt
1554
+ | flow_ref_stmt
1555
+ | annotation ;
1556
+
1557
+ (* Sends / Receives *)
1558
+ sends_stmt = "sends" message_type resource_ref [ channel_clause ]
1559
+ | "sends" message_type identifier inline_block ;
1560
+ receives_stmt = "receives" message_type resource_ref [ channel_clause ]
1561
+ | "receives" message_type identifier inline_block ;
1562
+
1563
+ channel_clause = to_clause | from_clause ;
1564
+ to_clause = "to" channel_ref_list ;
1565
+ from_clause = "from" channel_ref_list ;
1566
+ channel_ref_list = channel_ref { "," channel_ref } ;
1567
+ channel_ref = identifier [ "@" version_lit ] ;
1568
+
1569
+ (* Data relationships *)
1570
+ writes_to_stmt = "writes-to" "container" resource_ref ;
1571
+ reads_from_stmt = "reads-from" "container" resource_ref ;
1572
+ flow_ref_stmt = "flow" resource_ref ;
1573
+ data_product_ref_stmt = "data-product" resource_ref ;
1574
+
1575
+ (* Inline message block *)
1576
+ inline_block = "{" message_props "}" ;
1577
+
1578
+ (* Messages: Event, Command, Query *)
1579
+ event_decl = "event" identifier "{" message_props "}" ;
1580
+ command_decl = "command" identifier "{" message_props "}" ;
1581
+ query_decl = "query" identifier "{" message_props "}" ;
1582
+
1583
+ (* Channel *)
1584
+ channel_decl = "channel" identifier "{" common_props
1585
+ { channel_body_item } "}" ;
1586
+ channel_body_item= address_prop | protocol_prop | parameter_decl
1587
+ | route_stmt | annotation ;
1588
+ address_prop = "address" string_lit ;
1589
+ protocol_prop = "protocol" string_lit ;
1590
+ parameter_decl = "parameter" identifier "{" { param_prop } "}" ;
1591
+ param_prop = "description" string_lit
1592
+ | "default" string_lit
1593
+ | "enum" "[" string_lit { "," string_lit } "]"
1594
+ | "examples" "[" string_lit { "," string_lit } "]" ;
1595
+ route_stmt = "route" resource_ref ;
1596
+
1597
+ (* Container *)
1598
+ container_decl = "container" identifier "{" common_props
1599
+ { container_body_item } "}" ;
1600
+ container_body_item = container_type_prop | technology_prop
1601
+ | authoritative_prop | access_mode_prop
1602
+ | classification_prop | residency_prop
1603
+ | retention_prop
1604
+ | service_ref_stmt | annotation ;
1605
+ container_type_prop = "container-type" container_type_enum ;
1606
+ container_type_enum = "database" | "cache" | "objectStore" | "searchIndex"
1607
+ | "dataWarehouse" | "dataLake" | "externalSaaS" | "other" ;
1608
+ technology_prop = "technology" string_lit ;
1609
+ authoritative_prop = "authoritative" bool_lit ;
1610
+ access_mode_prop = "access-mode" ( "read" | "write" | "readWrite" | "appendOnly" ) ;
1611
+ classification_prop = "classification" ( "public" | "internal" | "confidential" | "regulated" ) ;
1612
+ residency_prop = "residency" string_lit ;
1613
+ retention_prop = "retention" string_lit ;
1614
+
1615
+ (* Data Product *)
1616
+ data_product_decl = "data-product" identifier "{" common_props
1617
+ { dp_body_item } "}" ;
1618
+ dp_body_item = input_stmt | output_stmt | annotation ;
1619
+ input_stmt = "input" message_type resource_ref ;
1620
+ output_stmt = "output" message_type resource_ref [ "{" contract_block "}" ] ;
1621
+ contract_block = "contract" "{" "path" string_lit "name" string_lit
1622
+ [ "type" string_lit ] "}" ;
1623
+
1624
+ (* Flow *)
1625
+ flow_decl = "flow" identifier "{" common_props
1626
+ { flow_entry_chain | flow_when_block } "}" ;
1627
+ flow_entry_chain = flow_ref { "," flow_ref } ( "->" flow_ref )+ ;
1628
+ flow_when_block = "when" flow_ref { "and" flow_ref } flow_action+ ;
1629
+ flow_action = flow_ref { flow_output } ;
1630
+ flow_output = "->" [ string_lit ":" ] flow_ref ;
1631
+ flow_ref = identifier [ string_lit ] ;
1632
+
1633
+ (* Actor *)
1634
+ actor_decl = "actor" identifier [ "{" { actor_body_item } "}" ] ;
1635
+ actor_body_item = name_prop | summary_prop | annotation ;
1636
+
1637
+ (* External System *)
1638
+ external_system_decl = "external-system" identifier [ "{" { ext_sys_body_item } "}" ] ;
1639
+ ext_sys_body_item = name_prop | summary_prop | annotation ;
1640
+
1641
+ (* Visualizer *)
1642
+ visualizer_decl = "visualizer" identifier "{" { visualizer_body } "}" ;
1643
+ visualizer_body = name_prop | summary_prop | annotation
1644
+ | legend_prop | search_prop | toolbar_prop
1645
+ | focus_mode_prop | animated_prop | style_prop
1646
+ | domain_decl | service_decl | event_decl | command_decl
1647
+ | query_decl | channel_decl | container_decl
1648
+ | data_product_decl | flow_decl
1649
+ | actor_decl | external_system_decl
1650
+ | service_ref_stmt | domain_ref_stmt
1651
+ | event_ref_stmt | command_ref_stmt | query_ref_stmt
1652
+ | channel_ref_stmt | data_product_ref_stmt | flow_ref_stmt
1653
+ | container_ref_stmt ;
1654
+ legend_prop = "legend" bool_lit ;
1655
+ search_prop = "search" bool_lit ;
1656
+ toolbar_prop = "toolbar" bool_lit ;
1657
+ focus_mode_prop = "focus-mode" bool_lit ;
1658
+ animated_prop = "animated" bool_lit ;
1659
+ style_prop = "style" ( "default" | "post-it" ) ;
1660
+
1661
+ (* User *)
1662
+ user_decl = "user" identifier "{" user_props "}" ;
1663
+ user_props = "name" string_lit
1664
+ | "avatar" string_lit
1665
+ | "role" string_lit
1666
+ | "email" string_lit
1667
+ | "slack" string_lit
1668
+ | "ms-teams" string_lit
1669
+ | owns_stmt
1670
+ | "team" identifier ;
1671
+ owns_stmt = "owns" resource_type_kw identifier ;
1672
+ resource_type_kw = "domain" | "service" | "event" | "command" | "query" ;
1673
+
1674
+ (* Team *)
1675
+ team_decl = "team" identifier "{" team_props "}" ;
1676
+ team_props = "name" string_lit
1677
+ | "summary" string_lit
1678
+ | "email" string_lit
1679
+ | "slack" string_lit
1680
+ | "ms-teams" string_lit
1681
+ | "member" identifier
1682
+ | owns_stmt ;
1683
+
1684
+ (* Resource references *)
1685
+ service_ref_stmt = "service" resource_ref ;
1686
+ domain_ref_stmt = "domain" resource_ref ;
1687
+ event_ref_stmt = "event" resource_ref ;
1688
+ command_ref_stmt = "command" resource_ref ;
1689
+ query_ref_stmt = "query" resource_ref ;
1690
+ channel_ref_stmt = "channel" resource_ref ;
1691
+ container_ref_stmt = "container" resource_ref ;
1692
+ ```
1693
+
1694
+ ## Reserved Keywords
1695
+
1696
+ ```
1697
+ domain service event command query
1698
+ channel container data-product flow
1699
+ user team sends receives
1700
+ writes-to reads-from owns to from
1701
+ version name summary owner schema
1702
+ deprecated draft true false
1703
+ type actor external-system
1704
+ parameter route member
1705
+ input output contract
1706
+ subdomain visualizer legend search toolbar focus-mode
1707
+ animated style when and
1708
+ ```
1709
+
1710
+ ## File Extension
1711
+
1712
+ EventCatalog DSL files use the `.ec` extension:
1713
+
1714
+ ```
1715
+ catalog.ec
1716
+ orders-domain.ec
1717
+ payment-service.ec
1718
+ ```
1719
+
1720
+ Multiple `.ec` files can be used and are merged during compilation. Resources can reference each other across files.
1721
+
1722
+ ## Compilation
1723
+
1724
+ The DSL compiles to EventCatalog's directory structure:
1725
+
1726
+ ```
1727
+ catalog.ec --> domains/Orders/index.mdx
1728
+ domains/Orders/services/OrderService/index.mdx
1729
+ events/OrderCreated/index.mdx
1730
+ commands/ProcessPayment/index.mdx
1731
+ channels/orders-topic/index.mdx
1732
+ users/dboyne.mdx
1733
+ teams/orders-team.mdx
1734
+ ...
1735
+ ```
1736
+
1737
+ Each resource becomes a markdown file with YAML frontmatter matching EventCatalog's content collection schemas.
1738
+
1739
+ ---
1740
+
1741
+ # Visualizer
1742
+
1743
+ The `visualizer` block separates **resource definition** from **visualization**. Resources defined outside a visualizer block exist in the catalog but are not rendered visually. Only resources placed inside a `visualizer` block (or referenced from one) appear in the visual graph.
1744
+
1745
+ This separation lets teams maintain a single source of truth for all resources while controlling exactly what gets visualized and how.
1746
+
1747
+ ```
1748
+ visualizer <id> {
1749
+ name "<display name>"
1750
+ summary "<text>"
1751
+
1752
+ // Display options
1753
+ legend true|false
1754
+ search true|false
1755
+ toolbar true|false
1756
+ focus-mode true|false
1757
+ animated true|false
1758
+ style default|post-it
1759
+
1760
+ // Resources to visualize (inline or reference)
1761
+ <resource definitions or references>
1762
+ }
1763
+ ```
1764
+
1765
+ ## Why Visualizer Exists
1766
+
1767
+ A `.ec` file can define dozens of resources — domains, services, events, channels, and more. Without a visualizer block, there is no way to control which resources appear in the visual graph or how they are presented.
1768
+
1769
+ The visualizer block makes visualization **explicit**:
1770
+
1771
+ - Define resources anywhere (top-level, imported files)
1772
+ - Choose what to visualize by placing resources inside a `visualizer` block
1773
+ - Configure display options per visualization
1774
+
1775
+ ## Multiple Visualizer Blocks
1776
+
1777
+ A file can contain multiple `visualizer` blocks, each presenting a different view over the same resources. Tools (e.g. the playground) allow switching between them.
1778
+
1779
+ ```
1780
+ // Shared resources
1781
+ event OrderCreated {
1782
+ version 1.0.0
1783
+ }
1784
+
1785
+ event PaymentProcessed {
1786
+ version 1.0.0
1787
+ }
1788
+
1789
+ service OrderService {
1790
+ version 1.0.0
1791
+ sends event OrderCreated
1792
+ }
1793
+
1794
+ service PaymentService {
1795
+ version 1.0.0
1796
+ receives event OrderCreated
1797
+ sends event PaymentProcessed
1798
+ }
1799
+
1800
+ // View 1: Order flow only
1801
+ visualizer orders {
1802
+ name "Order Flow"
1803
+
1804
+ service OrderService
1805
+ event OrderCreated
1806
+ }
1807
+
1808
+ // View 2: Full payment pipeline
1809
+ visualizer payments {
1810
+ name "Payment Pipeline"
1811
+
1812
+ service OrderService
1813
+ service PaymentService
1814
+ event OrderCreated
1815
+ event PaymentProcessed
1816
+ }
1817
+ ```
1818
+
1819
+ ## Resources Without a Visualizer
1820
+
1821
+ Resources defined outside any `visualizer` block are valid. They can be:
1822
+
1823
+ - Imported by other `.ec` files
1824
+ - Referenced from within a `visualizer` block
1825
+ - Used for compilation to EventCatalog's markdown format
1826
+
1827
+ If a file contains no `visualizer` block, no visual graph is produced.
1828
+
1829
+ ## Inline vs. Reference
1830
+
1831
+ Resources inside a visualizer can be defined inline (full definition) or referenced by name:
1832
+
1833
+ **Inline** — defines and visualizes the resource:
1834
+
1835
+ ```
1836
+ visualizer main {
1837
+ name "My Architecture"
1838
+
1839
+ service OrderService {
1840
+ version 1.0.0
1841
+ sends event OrderCreated
1842
+ }
1843
+ }
1844
+ ```
1845
+
1846
+ **Reference** — visualizes a resource defined elsewhere:
1847
+
1848
+ ```
1849
+ service OrderService {
1850
+ version 1.0.0
1851
+ sends event OrderCreated
1852
+ }
1853
+
1854
+ visualizer main {
1855
+ name "My Architecture"
1856
+ service OrderService
1857
+ }
1858
+ ```
1859
+
1860
+ When a resource is referenced, the visualizer enriches the node with metadata from the matching top-level definition.
1861
+
1862
+ ## Display Options
1863
+
1864
+ | Property | Type | Default | Description |
1865
+ | ------------ | ------- | --------- | -------------------------------------- |
1866
+ | `name` | string | — | Display title for the visualization |
1867
+ | `summary` | string | — | Description of the visualization |
1868
+ | `legend` | boolean | `true` | Show the node type legend |
1869
+ | `search` | boolean | `true` | Show the search bar |
1870
+ | `toolbar` | boolean | `true` | Show the toolbar (export, zoom, etc.) |
1871
+ | `focus-mode` | boolean | `true` | Enable focus mode for individual nodes |
1872
+ | `animated` | boolean | `true` | Animate edges |
1873
+ | `style` | enum | `default` | Visual style: `default` or `post-it` |
1874
+
1875
+ ## Example
1876
+
1877
+ ```
1878
+ // Teams
1879
+ team orders-team {
1880
+ name "Orders Team"
1881
+ }
1882
+
1883
+ // Resources
1884
+ channel orders-topic {
1885
+ version 1.0.0
1886
+ protocol "Kafka"
1887
+ }
1888
+
1889
+ // Visualizer with display options
1890
+ visualizer order-architecture {
1891
+ name "Order Architecture"
1892
+ summary "Core order processing services"
1893
+ legend true
1894
+ search true
1895
+ animated false
1896
+ style post-it
1897
+
1898
+ domain Orders {
1899
+ version 1.0.0
1900
+ owner orders-team
1901
+
1902
+ service OrderService {
1903
+ version 1.0.0
1904
+ sends event OrderCreated to orders-topic
1905
+ receives command CreateOrder
1906
+ }
1907
+
1908
+ service NotificationService {
1909
+ version 1.0.0
1910
+ receives event OrderCreated
1911
+ }
1912
+ }
1913
+ }
1914
+ ```
1915
+
1916
+ ## EBNF
1917
+
1918
+ ```ebnf
1919
+ visualizer_decl = "visualizer" identifier "{" { visualizer_body } "}" ;
1920
+ visualizer_body = name_prop | summary_prop | annotation
1921
+ | legend_prop | search_prop | toolbar_prop
1922
+ | focus_mode_prop | animated_prop | style_prop
1923
+ | domain_decl | service_decl | event_decl | command_decl
1924
+ | query_decl | channel_decl | container_decl
1925
+ | data_product_decl | flow_decl
1926
+ | actor_decl | external_system_decl
1927
+ | service_ref_stmt | domain_ref_stmt
1928
+ | event_ref_stmt | command_ref_stmt | query_ref_stmt
1929
+ | channel_ref_stmt | data_product_ref_stmt | flow_ref_stmt
1930
+ | container_ref_stmt ;
1931
+ legend_prop = "legend" bool_lit ;
1932
+ search_prop = "search" bool_lit ;
1933
+ toolbar_prop = "toolbar" bool_lit ;
1934
+ focus_mode_prop = "focus-mode" bool_lit ;
1935
+ animated_prop = "animated" bool_lit ;
1936
+ style_prop = "style" ( "default" | "post-it" ) ;
1937
+ ```
1938
+
1939
+ ---