@eventcatalog/core 5.2.1 → 5.4.1

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 (47) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/count-resources.cjs +10 -0
  4. package/dist/analytics/count-resources.js +1 -1
  5. package/dist/analytics/log-build.cjs +11 -1
  6. package/dist/analytics/log-build.js +4 -4
  7. package/dist/{chunk-HVCPU4V3.js → chunk-GAKU752C.js} +1 -1
  8. package/dist/{chunk-VEQLCFU6.js → chunk-GQQFHJSY.js} +2 -2
  9. package/dist/{chunk-QSD6WMYE.js → chunk-NSVRGATQ.js} +1 -1
  10. package/dist/{chunk-OVEXZEJJ.js → chunk-VZNPE26F.js} +1 -1
  11. package/dist/{chunk-MESHMXLR.js → chunk-WG276AIE.js} +1 -1
  12. package/dist/{chunk-ZAZHAVKB.js → chunk-YRZK7HKX.js} +10 -0
  13. package/dist/constants.cjs +1 -1
  14. package/dist/constants.js +1 -1
  15. package/dist/docs/development/guides/fitness-functions/01-what-are-fitness-functions.md +116 -0
  16. package/dist/docs/development/guides/fitness-functions/02-create-fitness-function.md +222 -0
  17. package/dist/docs/development/guides/fitness-functions/03-global-and-resource-functions.md +198 -0
  18. package/dist/docs/development/guides/fitness-functions/04-run-in-ci.md +223 -0
  19. package/dist/docs/development/guides/fitness-functions/05-api-reference.md +271 -0
  20. package/dist/docs/development/guides/fitness-functions/06-examples.md +354 -0
  21. package/dist/docs/development/guides/fitness-functions/_category_.json +11 -0
  22. package/dist/eventcatalog.cjs +25 -1
  23. package/dist/eventcatalog.js +26 -12
  24. package/dist/federation/federate.js +3 -3
  25. package/dist/generate.cjs +1 -1
  26. package/dist/generate.js +3 -3
  27. package/dist/runtime-dependencies/@eventcatalog__fitness.mjs +3 -0
  28. package/dist/runtime-dependencies/@modelcontextprotocol__client.mjs +3 -0
  29. package/dist/runtime-dependencies/@modelcontextprotocol__server.mjs +3 -0
  30. package/dist/runtime-dependencies/manifest.json +3 -4
  31. package/dist/utils/cli-logger.cjs +1 -1
  32. package/dist/utils/cli-logger.js +2 -2
  33. package/eventcatalog/src/features/mcp/apps/generated/canvas.html +73 -74
  34. package/eventcatalog/src/features/mcp/apps/generated/viewer.html +71 -73
  35. package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +1 -1
  36. package/eventcatalog/src/features/mcp/custom-tool-input.ts +38 -0
  37. package/eventcatalog/src/features/mcp/mcp-apps.ts +6 -4
  38. package/eventcatalog/src/features/mcp/mcp-server.ts +24 -17
  39. package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +4 -3
  40. package/eventcatalog/src/features/studio/server/canvas-mcp.ts +2 -4
  41. package/package.json +10 -7
  42. package/dist/runtime-dependencies/@modelcontextprotocol__sdk.mjs +0 -3
  43. package/dist/runtime-dependencies/@modelcontextprotocol__sdk__server__mcp.js.mjs +0 -3
  44. package/dist/runtime-dependencies/@modelcontextprotocol__sdk__server__webStandardStreamableHttp.js.mjs +0 -3
  45. package/dist/runtime-dependencies/@modelcontextprotocol__sdk__types.js.mjs +0 -3
  46. package/eventcatalog/src/features/studio/server/tool-icons.ts +0 -30
  47. package/dist/{chunk-NCEZDQSW.js → chunk-QJ7KKN2D.js} +7 -7
@@ -0,0 +1,198 @@
1
+ ---
2
+ sidebar_position: 3
3
+ sidebar_label: Global and resource functions
4
+ title: Global and resource fitness functions
5
+ description: Put fitness functions at the root of your catalog for catalog-wide rules, or next to a domain, system, service or message so they're given that resource.
6
+ ---
7
+
8
+ A fitness function can live in two places: the `fitness/` folder at the root of your catalog, or a `fitness/` folder inside a resource's folder. Where you put it decides which resource it's given when it runs. It doesn't limit what it can read: every function can query the whole catalog.
9
+
10
+ ## Global fitness functions
11
+
12
+ Functions in the root `fitness/` folder are global. They aren't given a resource, so `resource` is `undefined`, and they check whatever they read from `catalog`.
13
+
14
+ ```
15
+ fitness/
16
+ └── event-ownership.fitness.ts
17
+ ```
18
+
19
+ Use them for standards that apply to everything, like ownership or schema rules. A platform team typically owns this folder.
20
+
21
+ ## Resource fitness functions
22
+
23
+ Functions in a `fitness/` folder inside a resource's folder are given that resource as `resource`. This lets the team that owns a domain, system or service keep its rules next to its documentation.
24
+
25
+ ```
26
+ domains/
27
+ └── Ordering/
28
+ ├── index.mdx
29
+ └── fitness/
30
+ └── domain-boundaries.fitness.ts # resource is the Ordering domain
31
+ ```
32
+
33
+ The resource includes its frontmatter, as the SDK returns it, plus a `type` field. Check `type` before using fields that only some resources have, and TypeScript narrows the resource for you:
34
+
35
+ ```ts title="domains/Ordering/fitness/domain-boundaries.fitness.ts"
36
+ import { defineFitnessFunction } from '@eventcatalog/fitness';
37
+
38
+ export default defineFitnessFunction({
39
+ id: 'ordering-domain-boundaries',
40
+ name: 'Ordering services only use Ordering data stores',
41
+ description: 'Services in the Ordering domain must not read from or write to data stores that belong to other domains.',
42
+
43
+ async run({ resource, catalog }) {
44
+ if (resource?.type !== 'domain') return [];
45
+
46
+ // The data stores and services that belong to the domain, through its systems
47
+ const ownedStores = new Set<string>();
48
+ const services = [...(resource.services ?? [])];
49
+ for (const pointer of resource.systems ?? []) {
50
+ const system = await catalog.getSystem(pointer.id, pointer.version);
51
+ system?.containers?.forEach((container) => ownedStores.add(container.id));
52
+ services.push(...(system?.services ?? []));
53
+ }
54
+
55
+ const violations = [];
56
+ for (const pointer of services) {
57
+ const service = await catalog.getService(pointer.id, pointer.version);
58
+ if (!service) continue;
59
+
60
+ for (const store of [...(service.readsFrom ?? []), ...(service.writesTo ?? [])]) {
61
+ if (ownedStores.has(store.id)) continue;
62
+ violations.push({
63
+ message: `${service.name} uses ${store.id}, which belongs to another domain`,
64
+ resources: [
65
+ { type: 'service', id: service.id, version: service.version },
66
+ { type: 'container', id: store.id },
67
+ ],
68
+ suggestion: 'Consume an event or call an API owned by the other domain instead',
69
+ });
70
+ }
71
+ }
72
+ return violations;
73
+ },
74
+ });
75
+ ```
76
+
77
+ The function starts from the domain it's given, then uses `catalog` to read the domain's [systems](/docs/development/guides/systems/introduction), their services, and the [data stores](/docs/development/guides/resources/data/introduction) those services [read from and write to](/docs/development/guides/resources/data/add-read-write-relationships). The resource sets where the rule starts, not how far it can look.
78
+
79
+ A service function works the same way. This one warns when a service has too many dependencies:
80
+
81
+ ```ts title="services/OrderService/fitness/max-dependencies.fitness.ts"
82
+ import { defineFitnessFunction } from '@eventcatalog/fitness';
83
+
84
+ const MAX_DEPENDENCIES = 6;
85
+
86
+ export default defineFitnessFunction({
87
+ id: 'order-service-max-dependencies',
88
+ name: 'Order Service stays loosely coupled',
89
+ severity: 'warning',
90
+
91
+ run({ resource }) {
92
+ if (resource?.type !== 'service') return [];
93
+
94
+ const dependencies = [...(resource.receives ?? []), ...(resource.readsFrom ?? []), ...(resource.writesTo ?? [])];
95
+ if (dependencies.length <= MAX_DEPENDENCIES) return [];
96
+
97
+ return [
98
+ {
99
+ message: `${resource.name} has ${dependencies.length} dependencies (the limit is ${MAX_DEPENDENCIES})`,
100
+ resources: [{ type: 'service', id: resource.id, version: resource.version }],
101
+ details: 'Counts the messages it receives and the data stores it reads from or writes to.',
102
+ suggestion: 'Split the service, or move work behind events another service handles',
103
+ },
104
+ ];
105
+ },
106
+ });
107
+ ```
108
+
109
+ `run` can be synchronous when it doesn't need to read the catalog.
110
+
111
+ In the results, resource functions are labelled with the resource's id:
112
+
113
+ ```
114
+ ✗ ordering / Ordering services only use Ordering data stores 1 violation
115
+ • Order Service uses payment-database, which belongs to another domain
116
+ Resources: order-service (service), payment-database (container)
117
+ Suggestion: Consume an event or call an API owned by the other domain instead
118
+ ! order-service / Order Service stays loosely coupled 1 warning
119
+ • Order Service has 7 dependencies (the limit is 6)
120
+ Resources: order-service (service)
121
+ Counts the messages it receives and the data stores it reads from or writes to.
122
+ Suggestion: Split the service, or move work behind events another service handles
123
+ ```
124
+
125
+ ## Which resource a function is given
126
+
127
+ EventCatalog works out the resource from the folder that contains `fitness/`. That folder must have an `index.mdx` or `index.md` with an `id`, and it must sit inside one of these folders:
128
+
129
+ | Folder | Resource type |
130
+ |---|---|
131
+ | `domains/`, `subdomains/` | `domain` |
132
+ | `systems/` | `system` |
133
+ | `services/` | `service` |
134
+ | `agents/` | `agent` |
135
+ | `events/` | `event` |
136
+ | `commands/` | `command` |
137
+ | `queries/` | `query` |
138
+ | `flows/` | `flow` |
139
+ | `channels/` | `channel` |
140
+ | `entities/` | `entity` |
141
+ | `containers/` | `container` |
142
+ | `data-products/` | `data-product` |
143
+ | `adrs/` | `adr` |
144
+ | `diagrams/` | `diagram` |
145
+
146
+ Resources can be nested, as they can anywhere in EventCatalog. A function is given the resource whose folder holds its `fitness/` folder, not that resource's parents:
147
+
148
+ ```
149
+ domains/Ordering/
150
+ ├── fitness/ # the Ordering domain
151
+ └── systems/order-management-system/
152
+ ├── fitness/ # the order-management-system system
153
+ └── services/OrderService/
154
+ ├── fitness/ # the OrderService service
155
+ └── events/OrderCreated/
156
+ └── fitness/ # the OrderCreated event
157
+ ```
158
+
159
+ Inside a `fitness/` folder you can organise files into subfolders. They're given the same resource:
160
+
161
+ ```
162
+ domains/Ordering/fitness/
163
+ ├── boundaries/
164
+ │ └── data-stores.fitness.ts # still the Ordering domain
165
+ └── helpers.ts
166
+ ```
167
+
168
+ ## Versions
169
+
170
+ A resource function is given the version of the resource in its folder. In the resource's main folder, that's the version in its `index.mdx`, usually the latest. To check an older version, put the function in that version's folder:
171
+
172
+ ```
173
+ services/OrderService/
174
+ ├── index.mdx # version 1.0.0
175
+ ├── fitness/
176
+ │ └── max-dependencies.fitness.ts # given OrderService 1.0.0
177
+ └── versioned/
178
+ └── 0.0.1/
179
+ ├── index.mdx
180
+ └── fitness/
181
+ └── legacy.fitness.ts # given OrderService 0.0.1
182
+ ```
183
+
184
+ Each file runs once, against the version it sits alongside. A function in the main folder doesn't run against the versions under `versioned/`. See [versioning resources](/docs/development/guides/versioning-resources) for how versions are stored.
185
+
186
+ ## Rules for fitness files
187
+
188
+ - Only files ending in `.fitness.ts` are run. Other files in a `fitness/` folder, like `helpers.ts`, are not, so you can use them for code your functions share and import it with a relative path.
189
+ - A `*.fitness.ts` file outside a `fitness/` folder isn't run. EventCatalog lists it as a warning at the end of the results so you can move it. It doesn't fail the check.
190
+ - `node_modules/`, `dist/` and folders that start with a `.` are ignored.
191
+ - Fitness functions in `federated/` aren't run. If your catalog [federates](/docs/federation/overview) other catalogs, only your own catalog's functions run, never theirs.
192
+ - If EventCatalog can't work out a function's resource, for example because the folder has no `index.mdx`, has no `id`, or isn't in one of the folders above, the function isn't run and is reported as an `Error`.
193
+ - Ids must be unique across the catalog. If two files use the same `id`, the first one (in path order) runs and the others are reported as errors.
194
+
195
+ ## Related
196
+
197
+ - [Create a fitness function](./02-create-fitness-function.md)
198
+ - [API reference](./05-api-reference.md): the `resource` and `catalog` types
@@ -0,0 +1,223 @@
1
+ ---
2
+ sidebar_position: 4
3
+ sidebar_label: Run in CI
4
+ title: Run fitness functions locally and in CI
5
+ description: The eventcatalog fitness check command, its options, output and exit codes, the report it writes, and how to run it in GitHub Actions or GitLab CI.
6
+ ---
7
+
8
+ `eventcatalog fitness check` runs every fitness function in your catalog. Run it locally while you write rules, and in CI so every change to the catalog is checked.
9
+
10
+ ## The command
11
+
12
+ ```bash
13
+ npx eventcatalog fitness check
14
+ ```
15
+
16
+ The command is part of `@eventcatalog/core`, the same package that runs `eventcatalog dev` and `eventcatalog build`. Run it from the root of your catalog. It always checks the whole catalog. Catalogs created with `create-eventcatalog` also have an `npm run fitness` script that runs it.
17
+
18
+ The [EventCatalog CLI](/docs/cli) has the same command. Use it to check a catalog without installing EventCatalog, or to point at a catalog in another folder with `--dir`:
19
+
20
+ ```bash
21
+ npx @eventcatalog/cli --dir ./my-catalog fitness check
22
+ ```
23
+
24
+ | Option | Default | Description |
25
+ |---|---|---|
26
+ | `-o, --output <path>` | `.eventcatalog/fitness/results.json` | Where to write the report, relative to the catalog |
27
+ | `--format <format>` | `text` | `text` prints the results for people to read. `json` prints the full report instead |
28
+
29
+ If your catalog has a `.env` file, it's loaded before your functions run, so they can read settings from `process.env`.
30
+
31
+ ## What it does
32
+
33
+ 1. Finds every `*.fitness.ts` file in a `fitness/` folder, [at the root or in a resource's folder](./03-global-and-resource-functions.md).
34
+ 2. Runs them one at a time, in path order, so every run's results are in the same order.
35
+ 3. Prints the results, writes the report, and exits with a code that tells CI whether the check passed.
36
+
37
+ ## Reading the output
38
+
39
+ ```
40
+ EventCatalog Fitness
41
+
42
+ ✗ ordering / Ordering services only use Ordering data stores 1 violation
43
+ • Order Service uses payment-database, which belongs to another domain
44
+ Resources: order-service (service), payment-database (container)
45
+ Suggestion: Consume an event or call an API owned by the other domain instead
46
+ ! order-service / Order Service stays loosely coupled 1 warning
47
+ • Order Service has 7 dependencies (the limit is 6)
48
+ Resources: order-service (service)
49
+ Counts the messages it receives and the data stores it reads from or writes to.
50
+ Suggestion: Split the service, or move work behind events another service handles
51
+ ✗ Critical services are documented Error
52
+ Cannot read properties of undefined (reading 'owners')
53
+ in fitness/critical-services.fitness.ts
54
+ ✗ Every event has an owner 1 violation
55
+ • Order Amended has no owners
56
+ Resources: order-amended (event)
57
+ Suggestion: Add a team or user to the owners field in the frontmatter
58
+ ! Every event has a schema 1 warning
59
+ • Order Amended has no schema
60
+ Resources: order-amended (event)
61
+
62
+ 5 fitness functions evaluated
63
+
64
+ 0 passed
65
+ 2 warnings
66
+ 2 failed
67
+ 1 error
68
+
69
+ Report written to .eventcatalog/fitness/results.json
70
+ ```
71
+
72
+ Each function gets one line. Functions [next to a resource](./03-global-and-resource-functions.md#resource-fitness-functions) are labelled with the resource's id first.
73
+
74
+ | Symbol | Result | Meaning |
75
+ |---|---|---|
76
+ | `✓` | `Passed` | The function returned no violations |
77
+ | `!` | `n warnings` | A `warning` function found violations |
78
+ | `✗` | `n violations` | An `error` function found violations |
79
+ | `✗` | `Error` | The function couldn't be run. Its error and file are shown under it |
80
+
81
+ Under each function are its violations: the message, the resources it's about, any `details`, and the suggestion. A resource that doesn't match anything in the catalog is shown as `not found in the catalog`.
82
+
83
+ Output is coloured when the command runs in a terminal. Set `NO_COLOR` to turn colour off.
84
+
85
+ ## Warnings and errors
86
+
87
+ Each function has a `severity`:
88
+
89
+ - **`error`** (the default): violations fail the check.
90
+ - **`warning`**: violations are reported, but the check still passes.
91
+
92
+ Use `warning` for a rule you're introducing to an existing catalog. Teams see the violations without their builds failing, and you can change it to `error` once they're fixed.
93
+
94
+ ## Exit codes
95
+
96
+ | Code | When |
97
+ |---|---|
98
+ | `0` | Every function passed, or only `warning` functions found violations |
99
+ | `1` | An `error` function found violations, or a function couldn't be run |
100
+ | `2` | The check itself couldn't run, for example because the catalog folder doesn't exist |
101
+
102
+ A catalog with no fitness functions exits with `0`.
103
+
104
+ ## When a function throws
105
+
106
+ A function that can't be run is reported with the status `Error`, not as a violation. This happens when:
107
+
108
+ - `run` throws an exception or returns a promise that rejects,
109
+ - `run` returns something other than an array of violations, or a violation without a `message`,
110
+ - the file has no default export, fails to load (a syntax error, for example), or the export isn't a valid fitness function,
111
+ - another function already uses the same `id`,
112
+ - EventCatalog can't find the resource the function sits alongside.
113
+
114
+ The other functions still run. Any error fails the check with exit code `1`, so a broken rule can't pass unnoticed. The error message is printed in the terminal, and the message and stack trace are in the report.
115
+
116
+ ## The report
117
+
118
+ Every run writes a JSON report, to `.eventcatalog/fitness/results.json` by default. It's written even when there are no fitness functions, so a missing file always means the check didn't run.
119
+
120
+ The report has a summary, every function's result with its violations and errors, and any fitness files that weren't run.
121
+
122
+ For a catalog with one failing function, it looks like this:
123
+
124
+ ```json title=".eventcatalog/fitness/results.json"
125
+ {
126
+ "schemaVersion": 1,
127
+ "evaluatedAt": "2026-10-18T14:34:11.778Z",
128
+ "catalogRevision": "5b0bede",
129
+ "summary": { "total": 1, "passed": 0, "warnings": 0, "failed": 1, "errors": 0 },
130
+ "functions": [
131
+ {
132
+ "id": "event-ownership",
133
+ "name": "Every event has an owner",
134
+ "description": "Events without an owner have nobody to ask before they change.",
135
+ "severity": "error",
136
+ "status": "failed",
137
+ "source": "fitness/event-ownership.fitness.ts",
138
+ "scope": null,
139
+ "violations": [
140
+ {
141
+ "message": "Order Amended has no owners",
142
+ "resources": [{ "type": "event", "id": "order-amended", "version": "1.0.0", "resolved": true }],
143
+ "suggestion": "Add a team or user to the owners field in the frontmatter"
144
+ }
145
+ ]
146
+ }
147
+ ],
148
+ "diagnostics": []
149
+ }
150
+ ```
151
+
152
+ `catalogRevision` is the git commit the catalog was checked at, or `null` when it isn't in a git repository. See [the report reference](./05-api-reference.md#the-report) for every field.
153
+
154
+ Write it somewhere else with `--output`:
155
+
156
+ ```bash
157
+ npx eventcatalog fitness check --output reports/fitness.json
158
+ ```
159
+
160
+ Or print it to standard output with `--format json` (it's still written to the file too):
161
+
162
+ ```bash
163
+ npx eventcatalog fitness check --format json > fitness.json
164
+ ```
165
+
166
+ The report is written again on every run, so add `.eventcatalog/fitness/` to your `.gitignore` unless you want to commit it. Catalogs created with `create-eventcatalog` already ignore it.
167
+
168
+ ## GitHub Actions
169
+
170
+ ```yaml title=".github/workflows/fitness.yml"
171
+ name: Architecture fitness
172
+ on:
173
+ pull_request:
174
+ push:
175
+ branches: [main]
176
+
177
+ jobs:
178
+ fitness:
179
+ runs-on: ubuntu-latest
180
+ steps:
181
+ - uses: actions/checkout@v4
182
+ - uses: actions/setup-node@v4
183
+ with:
184
+ node-version: 20
185
+ - run: npm ci
186
+ - run: npx eventcatalog fitness check
187
+ - uses: actions/upload-artifact@v4
188
+ if: always()
189
+ with:
190
+ name: fitness-report
191
+ path: .eventcatalog/fitness/results.json
192
+ ```
193
+
194
+ `if: always()` keeps the report when the check fails, which is when you'll want to look at it.
195
+
196
+ If the catalog lives in a subfolder of the repository, set the working directory for the step:
197
+
198
+ ```yaml
199
+ - run: npx eventcatalog fitness check
200
+ working-directory: ./catalog
201
+ ```
202
+
203
+ Or use the EventCatalog CLI with `--dir`: `npx @eventcatalog/cli --dir ./catalog fitness check`.
204
+
205
+ ## GitLab CI
206
+
207
+ ```yaml title=".gitlab-ci.yml"
208
+ fitness:
209
+ stage: test
210
+ image: node:20
211
+ script:
212
+ - npm ci
213
+ - npx eventcatalog fitness check
214
+ artifacts:
215
+ when: always
216
+ paths:
217
+ - .eventcatalog/fitness/results.json
218
+ ```
219
+
220
+ ## Related
221
+
222
+ - [Create a fitness function](./02-create-fitness-function.md)
223
+ - [API reference](./05-api-reference.md)
@@ -0,0 +1,271 @@
1
+ ---
2
+ sidebar_position: 5
3
+ sidebar_label: API reference
4
+ title: Fitness function API reference
5
+ description: defineFitnessFunction, the run context, the resource and catalog it is given, the violation format, and the schema of the results report.
6
+ ---
7
+
8
+ Everything on this page is exported from `@eventcatalog/fitness`. Import the types you need:
9
+
10
+ ```ts
11
+ import { defineFitnessFunction } from '@eventcatalog/fitness';
12
+ import type { FitnessContext, FitnessViolation } from '@eventcatalog/fitness';
13
+ ```
14
+
15
+ ## `defineFitnessFunction`
16
+
17
+ ```ts
18
+ defineFitnessFunction<T extends FitnessFunction>(definition: T): T
19
+ ```
20
+
21
+ Defines a fitness function. Make it the default export of a `*.fitness.ts` file in a [`fitness/` folder](./03-global-and-resource-functions.md):
22
+
23
+ ```ts
24
+ export default defineFitnessFunction({
25
+ id: 'event-ownership',
26
+ name: 'Every event has an owner',
27
+ async run({ resource, catalog }) {
28
+ return [];
29
+ },
30
+ });
31
+ ```
32
+
33
+ It returns the definition unchanged, and throws if the definition is invalid (the runner reports that as an `Error` for the file).
34
+
35
+ ### `FitnessFunction`
36
+
37
+ | Property | Type | Required | Description |
38
+ |---|---|---|---|
39
+ | `id` | `string` | Yes | Identifies the function. Must be unique across the catalog |
40
+ | `name` | `string` | Yes | Shown in the results |
41
+ | `description` | `string` | No | What the function checks. Included in the report |
42
+ | `severity` | `'error' \| 'warning'` | No | `error` (the default) fails the check when violations are found. `warning` reports them without failing it |
43
+ | `run` | `(context: FitnessContext) => FitnessViolation[] \| Promise<FitnessViolation[]>` | Yes | Checks the architecture and returns the violations it finds. An empty array means the check passed |
44
+
45
+ ```ts
46
+ type FitnessFunction = {
47
+ id: string;
48
+ name: string;
49
+ description?: string;
50
+ severity?: 'error' | 'warning';
51
+ run(context: FitnessContext): Promise<FitnessViolation[]> | FitnessViolation[];
52
+ };
53
+ ```
54
+
55
+ ## The run context
56
+
57
+ `run` is given one argument:
58
+
59
+ ```ts
60
+ type FitnessContext = {
61
+ resource?: CatalogResource;
62
+ catalog: ReadOnlyCatalog;
63
+ };
64
+ ```
65
+
66
+ ### `resource`
67
+
68
+ The resource the function sits alongside, as the SDK returns it, with a `type` field added. For `domains/Ordering/fitness/boundaries.fitness.ts` it's the Ordering domain. For functions in the root `fitness/` folder it's `undefined`.
69
+
70
+ `CatalogResource` is a union of every resource type, so checking `type` narrows it:
71
+
72
+ ```ts
73
+ async run({ resource }) {
74
+ if (resource?.type !== 'service') return [];
75
+ resource.receives; // resource is a Service here
76
+ return [];
77
+ }
78
+ ```
79
+
80
+ | `type` | SDK type |
81
+ |---|---|
82
+ | `'domain'` | `Domain` |
83
+ | `'system'` | `System` |
84
+ | `'service'` | `Service` |
85
+ | `'agent'` | `Agent` |
86
+ | `'event'` | `Event` |
87
+ | `'command'` | `Command` |
88
+ | `'query'` | `Query` |
89
+ | `'flow'` | `Flow` |
90
+ | `'channel'` | `Channel` |
91
+ | `'entity'` | `Entity` |
92
+ | `'container'` | `Container` |
93
+ | `'data-product'` | `DataProduct` |
94
+ | `'adr'` | `Adr` |
95
+ | `'diagram'` | `Diagram` |
96
+
97
+ The SDK types are exported from `@eventcatalog/sdk`. See [which resource a function is given](./03-global-and-resource-functions.md#which-resource-a-function-is-given) for how the folder maps to a resource.
98
+
99
+ ### `catalog`
100
+
101
+ Read-only access to the whole catalog. Its methods are the [EventCatalog SDK](/docs/sdk) methods that read the catalog, with the same arguments and return values. Methods that write to the catalog aren't on it.
102
+
103
+ ```ts
104
+ const events = await catalog.getEvents({ latestOnly: true });
105
+ const service = await catalog.getService('order-service');
106
+ const graph = await catalog.getGraph({ type: 'domain', id: 'ordering' }, { flat: true });
107
+ ```
108
+
109
+ Every call returns a copy, so changing a resource you read doesn't change what the next call, or the next function, sees.
110
+
111
+ A getter like `getService` returns `undefined` when the resource doesn't exist. Check for it before using the result.
112
+
113
+ | Area | Methods |
114
+ |---|---|
115
+ | **Messages** | `getEvent`, `getEvents`, `eventHasVersion`, `getExamplesFromEvent`, `getCommand`, `getCommands`, `commandHasVersion`, `getExamplesFromCommand`, `getQuery`, `getQueries`, `queryHasVersion`, `getExamplesFromQuery`, `getMessageBySchemaPath`, `getProducersAndConsumersForMessage`, `getConsumersOfSchema`, `getProducersOfSchema`, `getSchemaForMessage` |
116
+ | **Architecture** | `getDomain`, `getDomains`, `domainHasVersion`, `getUbiquitousLanguageFromDomain`, `getSystem`, `getSystems`, `systemHasVersion`, `getService`, `getServices`, `serviceHasVersion`, `getSpecificationFilesForService`, `getAgent`, `getAgents`, `agentHasVersion`, `getChannel`, `getChannels`, `channelHasVersion`, `getFlow`, `getFlows`, `flowHasVersion`, `getEntity`, `getEntities`, `entityHasVersion`, `getDataStore`, `getDataStores`, `dataStoreHasVersion`, `getDataProduct`, `getDataProducts`, `dataProductHasVersion`, `getDiagram`, `getDiagrams`, `diagramHasVersion`, `getAdr`, `getAdrs`, `adrHasVersion` |
117
+ | **People and docs** | `getTeam`, `getTeams`, `getUser`, `getUsers`, `getOwnersForResource`, `getCustomDoc`, `getCustomDocs`, `getChangelog` |
118
+ | **Relationships** | `getGraph`, `getResourcesByProperty`, `getResourcePath`, `getResourceFolderName`, `getEventCatalogConfigurationFile` |
119
+
120
+ `getGraph` walks the relationships from a resource. See the [architecture graph](/docs/development/guides/architecture-graph) for what it returns.
121
+
122
+ ## Violations
123
+
124
+ `run` returns an array of violations:
125
+
126
+ ```ts
127
+ type FitnessViolation = {
128
+ message: string;
129
+ resources?: FitnessResourceReference[];
130
+ details?: string;
131
+ suggestion?: string;
132
+ };
133
+ ```
134
+
135
+ | Property | Type | Required | Description |
136
+ |---|---|---|---|
137
+ | `message` | `string` | Yes | What's wrong. One line, shown as the violation |
138
+ | `resources` | `FitnessResourceReference[]` | No | The resources the violation is about |
139
+ | `details` | `string` | No | More explanation, shown under the resources |
140
+ | `suggestion` | `string` | No | How to fix it |
141
+
142
+ ```ts
143
+ return [
144
+ {
145
+ message: 'Order Service uses payment-database, which belongs to another domain',
146
+ resources: [
147
+ { type: 'service', id: 'order-service', version: '1.0.0' },
148
+ { type: 'container', id: 'payment-database' },
149
+ ],
150
+ suggestion: 'Consume an event or call an API owned by the other domain instead',
151
+ },
152
+ ];
153
+ ```
154
+
155
+ ### Resource references
156
+
157
+ ```ts
158
+ type FitnessResourceReference = {
159
+ type: string;
160
+ id: string;
161
+ version?: string;
162
+ };
163
+ ```
164
+
165
+ EventCatalog looks up every reference in the catalog and records in the report whether it was found (`resolved`). Without a `version`, it resolves to the latest version and records that version, so the report says exactly which version was checked.
166
+
167
+ `type` can be any of these:
168
+
169
+ | `type` | Also accepted |
170
+ |---|---|
171
+ | `domain` | `subdomain` |
172
+ | `system` | |
173
+ | `service` | |
174
+ | `agent` | |
175
+ | `event` | |
176
+ | `command` | |
177
+ | `query` | |
178
+ | `flow` | |
179
+ | `channel` | |
180
+ | `entity` | |
181
+ | `container` | `dataStore` |
182
+ | `data-product` | `dataProduct` |
183
+ | `adr` | |
184
+ | `diagram` | |
185
+ | `team` | |
186
+ | `user` | |
187
+
188
+ Types are matched without regard to case. A reference with an unknown type, or to a resource that doesn't exist, is kept in the report with `resolved: false` and shown as `not found in the catalog`. It doesn't stop the function.
189
+
190
+ ## Errors
191
+
192
+ A function is reported with the status `error`, and fails the check, when:
193
+
194
+ - it throws, or the promise it returns rejects,
195
+ - it returns something other than an array,
196
+ - a violation isn't an object, or has no `message`, or `details`, `suggestion`, `resources` or a reference has the wrong type,
197
+ - the file has no default export, fails to load, or the export isn't a valid definition,
198
+ - its `id` is already used by another function,
199
+ - the resource it sits alongside can't be found.
200
+
201
+ The other functions still run. The report records the error's `message` and, for exceptions, its `stack`, with paths relative to the catalog.
202
+
203
+ To report something you found as a problem in the architecture, return a violation. Throw only when the function itself can't do its job.
204
+
205
+ ## The report
206
+
207
+ `eventcatalog fitness check` writes the results to `.eventcatalog/fitness/results.json` (change it with [`--output`](./04-run-in-ci.md#the-command)). These types are exported for tools that read it.
208
+
209
+ ```ts
210
+ type FitnessReport = {
211
+ schemaVersion: 1;
212
+ evaluatedAt: string; // ISO 8601
213
+ catalogRevision: string | null; // short git commit, or null outside a git repository
214
+ summary: {
215
+ total: number;
216
+ passed: number;
217
+ warnings: number;
218
+ failed: number;
219
+ errors: number;
220
+ };
221
+ functions: FitnessFunctionResult[];
222
+ diagnostics: { source: string; message: string }[];
223
+ };
224
+
225
+ type FitnessFunctionResult = {
226
+ id: string;
227
+ name: string;
228
+ description?: string;
229
+ severity: 'error' | 'warning';
230
+ status: 'passed' | 'warning' | 'failed' | 'error' | 'not_run';
231
+ source: string; // path of the file, relative to the catalog
232
+ scope: { type: CatalogResourceType; id: string; version?: string } | null;
233
+ violations: {
234
+ message: string;
235
+ resources: { type: string; id: string; version?: string; resolved: boolean }[];
236
+ details?: string;
237
+ suggestion?: string;
238
+ }[];
239
+ error?: { message: string; stack?: string };
240
+ };
241
+ ```
242
+
243
+ | `status` | Meaning |
244
+ |---|---|
245
+ | `passed` | The function ran and found no violations |
246
+ | `warning` | A `warning` function found violations |
247
+ | `failed` | An `error` function found violations |
248
+ | `error` | The function couldn't be run. See `error` |
249
+ | `not_run` | The function has no result |
250
+
251
+ `scope` is the resource the function sits alongside, or `null` for functions in the root `fitness/` folder. Its `version` is left out for resources that don't have one. `diagnostics` lists fitness files that weren't run because they aren't in a `fitness/` folder.
252
+
253
+ ## Running fitness functions from code
254
+
255
+ `@eventcatalog/fitness` also exports what the command uses, for running checks from your own scripts:
256
+
257
+ | Export | Description |
258
+ |---|---|
259
+ | `runFitnessCheck({ dir, output?, format?, color? })` | Does what `eventcatalog fitness check` does: runs the functions, writes the report, prints the results, and returns the exit code (`0`, `1` or `2`) |
260
+ | `evaluateFitness({ dir })` | Runs the functions and returns the `FitnessReport`, without writing or printing anything |
261
+ | `formatFitnessReport(report, { color? })` | The text the command prints for a report |
262
+ | `writeFitnessReport(report, path)` | Writes a report as JSON |
263
+ | `getFitnessExitCode(report)` | `1` when a function failed or couldn't be run, otherwise `0` |
264
+ | `discoverFitnessFiles(dir)` | The fitness files in a catalog and the resource each one sits alongside |
265
+
266
+ ```ts
267
+ import { evaluateFitness } from '@eventcatalog/fitness';
268
+
269
+ const report = await evaluateFitness({ dir: './my-catalog' });
270
+ console.log(`${report.summary.failed} failed`);
271
+ ```