@eventcatalog/core 5.2.1 → 5.4.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.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/count-resources.cjs +10 -0
- package/dist/analytics/count-resources.js +1 -1
- package/dist/analytics/log-build.cjs +11 -1
- package/dist/analytics/log-build.js +4 -4
- package/dist/{chunk-VEQLCFU6.js → chunk-EPDVXHC7.js} +4 -4
- package/dist/{chunk-OVEXZEJJ.js → chunk-O2K4VPDO.js} +1 -1
- package/dist/{chunk-QSD6WMYE.js → chunk-OT2U5OES.js} +1 -1
- package/dist/{chunk-NCEZDQSW.js → chunk-OX6TSVHY.js} +8 -8
- package/dist/{chunk-HVCPU4V3.js → chunk-PAS36QHK.js} +1 -1
- package/dist/{chunk-MESHMXLR.js → chunk-TERJXCLK.js} +1 -1
- package/dist/{chunk-ZAZHAVKB.js → chunk-YRZK7HKX.js} +10 -0
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/docs/development/guides/fitness-functions/01-what-are-fitness-functions.md +116 -0
- package/dist/docs/development/guides/fitness-functions/02-create-fitness-function.md +222 -0
- package/dist/docs/development/guides/fitness-functions/03-global-and-resource-functions.md +198 -0
- package/dist/docs/development/guides/fitness-functions/04-run-in-ci.md +223 -0
- package/dist/docs/development/guides/fitness-functions/05-api-reference.md +271 -0
- package/dist/docs/development/guides/fitness-functions/06-examples.md +354 -0
- package/dist/docs/development/guides/fitness-functions/_category_.json +11 -0
- package/dist/eventcatalog.cjs +25 -1
- package/dist/eventcatalog.js +29 -15
- package/dist/federation/federate.js +5 -5
- package/dist/federation/source-provider.js +2 -2
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/runtime-dependencies/@eventcatalog__fitness.mjs +3 -0
- package/dist/runtime-dependencies/@modelcontextprotocol__client.mjs +3 -0
- package/dist/runtime-dependencies/@modelcontextprotocol__server.mjs +3 -0
- package/dist/runtime-dependencies/manifest.json +3 -4
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +73 -74
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +71 -73
- package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +1 -1
- package/eventcatalog/src/features/mcp/custom-tool-input.ts +38 -0
- package/eventcatalog/src/features/mcp/mcp-apps.ts +6 -4
- package/eventcatalog/src/features/mcp/mcp-server.ts +24 -17
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +2 -4
- package/package.json +9 -6
- package/dist/runtime-dependencies/@modelcontextprotocol__sdk.mjs +0 -3
- package/dist/runtime-dependencies/@modelcontextprotocol__sdk__server__mcp.js.mjs +0 -3
- package/dist/runtime-dependencies/@modelcontextprotocol__sdk__server__webStandardStreamableHttp.js.mjs +0 -3
- package/dist/runtime-dependencies/@modelcontextprotocol__sdk__types.js.mjs +0 -3
- package/eventcatalog/src/features/studio/server/tool-icons.ts +0 -30
- package/dist/{chunk-A2RZR3U4.js → chunk-6K7XZAYI.js} +3 -3
|
@@ -64,6 +64,14 @@ var CUSTOM_ROUTE_PATTERNS = {
|
|
|
64
64
|
};
|
|
65
65
|
var DEFAULT_IGNORES = ["**/versioned/**", "**/dist/**", "**/node_modules/**"];
|
|
66
66
|
var CUSTOM_ROUTE_IGNORES = [...DEFAULT_IGNORES, "pages/**/_*/**", "pages/**/_*"];
|
|
67
|
+
async function countFitnessFunctions(projectDir) {
|
|
68
|
+
try {
|
|
69
|
+
const { discoverFitnessFiles } = await import("@eventcatalog/fitness");
|
|
70
|
+
return discoverFitnessFiles(projectDir).files.length;
|
|
71
|
+
} catch {
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
67
75
|
async function countResources(projectDir) {
|
|
68
76
|
const counts = {};
|
|
69
77
|
for (const [type, patterns] of Object.entries({ ...RESOURCE_PATTERNS, ...CUSTOM_ROUTE_PATTERNS })) {
|
|
@@ -77,6 +85,8 @@ async function countResources(projectDir) {
|
|
|
77
85
|
}
|
|
78
86
|
counts[type] = total;
|
|
79
87
|
}
|
|
88
|
+
const fitnessFunctions = await countFitnessFunctions(projectDir);
|
|
89
|
+
if (fitnessFunctions !== void 0) counts.fitnessFunctions = fitnessFunctions;
|
|
80
90
|
return counts;
|
|
81
91
|
}
|
|
82
92
|
async function hashCatalogContent(projectDir) {
|
|
@@ -169,7 +169,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
|
|
|
169
169
|
var import_os = __toESM(require("os"), 1);
|
|
170
170
|
|
|
171
171
|
// package.json
|
|
172
|
-
var version = "5.
|
|
172
|
+
var version = "5.4.0";
|
|
173
173
|
|
|
174
174
|
// src/constants.ts
|
|
175
175
|
var VERSION = version;
|
|
@@ -251,6 +251,14 @@ var CUSTOM_ROUTE_PATTERNS = {
|
|
|
251
251
|
};
|
|
252
252
|
var DEFAULT_IGNORES = ["**/versioned/**", "**/dist/**", "**/node_modules/**"];
|
|
253
253
|
var CUSTOM_ROUTE_IGNORES = [...DEFAULT_IGNORES, "pages/**/_*/**", "pages/**/_*"];
|
|
254
|
+
async function countFitnessFunctions(projectDir) {
|
|
255
|
+
try {
|
|
256
|
+
const { discoverFitnessFiles } = await import("@eventcatalog/fitness");
|
|
257
|
+
return discoverFitnessFiles(projectDir).files.length;
|
|
258
|
+
} catch {
|
|
259
|
+
return void 0;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
254
262
|
async function countResources(projectDir) {
|
|
255
263
|
const counts = {};
|
|
256
264
|
for (const [type, patterns] of Object.entries({ ...RESOURCE_PATTERNS, ...CUSTOM_ROUTE_PATTERNS })) {
|
|
@@ -264,6 +272,8 @@ async function countResources(projectDir) {
|
|
|
264
272
|
}
|
|
265
273
|
counts[type] = total;
|
|
266
274
|
}
|
|
275
|
+
const fitnessFunctions = await countFitnessFunctions(projectDir);
|
|
276
|
+
if (fitnessFunctions !== void 0) counts.fitnessFunctions = fitnessFunctions;
|
|
267
277
|
return counts;
|
|
268
278
|
}
|
|
269
279
|
async function hashCatalogContent(projectDir) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-EPDVXHC7.js";
|
|
4
4
|
import "../chunk-AD7CL772.js";
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-ZAZHAVKB.js";
|
|
5
|
+
import "../chunk-YRZK7HKX.js";
|
|
7
6
|
import "../chunk-CA6WCQ2T.js";
|
|
7
|
+
import "../chunk-PAS36QHK.js";
|
|
8
8
|
import "../chunk-CPW6G62V.js";
|
|
9
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-OT2U5OES.js";
|
|
10
10
|
import "../chunk-4G7FSRPN.js";
|
|
11
11
|
export {
|
|
12
12
|
log_build_default as default
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getLicenseAnalytics
|
|
3
3
|
} from "./chunk-AD7CL772.js";
|
|
4
|
-
import {
|
|
5
|
-
raiseEvent
|
|
6
|
-
} from "./chunk-HVCPU4V3.js";
|
|
7
4
|
import {
|
|
8
5
|
countResources,
|
|
9
6
|
hashCatalogContent,
|
|
10
7
|
serializeCounts
|
|
11
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YRZK7HKX.js";
|
|
12
9
|
import {
|
|
13
10
|
getStorageType,
|
|
14
11
|
getStudioMode,
|
|
15
12
|
serializeStudioUsage
|
|
16
13
|
} from "./chunk-CA6WCQ2T.js";
|
|
14
|
+
import {
|
|
15
|
+
raiseEvent
|
|
16
|
+
} from "./chunk-PAS36QHK.js";
|
|
17
17
|
import {
|
|
18
18
|
TELEMETRY_REQUEST_TIMEOUT_MS,
|
|
19
19
|
isTelemetryDisabled
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createFederationContentCache
|
|
3
|
-
} from "./chunk-R7Z5ALYI.js";
|
|
4
|
-
import {
|
|
5
|
-
getFederationDiagnostics,
|
|
6
|
-
resolveFederationRules
|
|
7
|
-
} from "./chunk-JJPB6EOZ.js";
|
|
8
1
|
import {
|
|
9
2
|
withFederationOutputTransaction
|
|
10
3
|
} from "./chunk-WRNH5C3U.js";
|
|
@@ -13,7 +6,14 @@ import {
|
|
|
13
6
|
} from "./chunk-5EPNFDT5.js";
|
|
14
7
|
import {
|
|
15
8
|
createFederationSourceProvider
|
|
16
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6K7XZAYI.js";
|
|
10
|
+
import {
|
|
11
|
+
createFederationContentCache
|
|
12
|
+
} from "./chunk-R7Z5ALYI.js";
|
|
13
|
+
import {
|
|
14
|
+
getFederationDiagnostics,
|
|
15
|
+
resolveFederationRules
|
|
16
|
+
} from "./chunk-JJPB6EOZ.js";
|
|
17
17
|
import {
|
|
18
18
|
getEventCatalogConfigFile
|
|
19
19
|
} from "./chunk-4G7FSRPN.js";
|
|
@@ -28,6 +28,14 @@ var CUSTOM_ROUTE_PATTERNS = {
|
|
|
28
28
|
};
|
|
29
29
|
var DEFAULT_IGNORES = ["**/versioned/**", "**/dist/**", "**/node_modules/**"];
|
|
30
30
|
var CUSTOM_ROUTE_IGNORES = [...DEFAULT_IGNORES, "pages/**/_*/**", "pages/**/_*"];
|
|
31
|
+
async function countFitnessFunctions(projectDir) {
|
|
32
|
+
try {
|
|
33
|
+
const { discoverFitnessFiles } = await import("@eventcatalog/fitness");
|
|
34
|
+
return discoverFitnessFiles(projectDir).files.length;
|
|
35
|
+
} catch {
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
31
39
|
async function countResources(projectDir) {
|
|
32
40
|
const counts = {};
|
|
33
41
|
for (const [type, patterns] of Object.entries({ ...RESOURCE_PATTERNS, ...CUSTOM_ROUTE_PATTERNS })) {
|
|
@@ -41,6 +49,8 @@ async function countResources(projectDir) {
|
|
|
41
49
|
}
|
|
42
50
|
counts[type] = total;
|
|
43
51
|
}
|
|
52
|
+
const fitnessFunctions = await countFitnessFunctions(projectDir);
|
|
53
|
+
if (fitnessFunctions !== void 0) counts.fitnessFunctions = fitnessFunctions;
|
|
44
54
|
return counts;
|
|
45
55
|
}
|
|
46
56
|
async function hashCatalogContent(projectDir) {
|
package/dist/constants.cjs
CHANGED
package/dist/constants.js
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 1
|
|
3
|
+
keywords:
|
|
4
|
+
- architecture fitness functions
|
|
5
|
+
- architecture rules
|
|
6
|
+
- architecture governance
|
|
7
|
+
- domain boundaries
|
|
8
|
+
- CI/CD
|
|
9
|
+
sidebar_label: What are fitness functions?
|
|
10
|
+
title: What are fitness functions?
|
|
11
|
+
description: Architecture fitness functions are your architecture rules written as TypeScript, run against your catalog to show which resources break them.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
An architecture fitness function is a rule about your architecture, written as code. You write a TypeScript function that reads your catalog through the [EventCatalog SDK](/docs/sdk) and returns a list of violations. EventCatalog finds your functions, runs them, prints the results and writes them to a report.
|
|
15
|
+
|
|
16
|
+
```ts title="fitness/event-ownership.fitness.ts"
|
|
17
|
+
import { defineFitnessFunction } from '@eventcatalog/fitness';
|
|
18
|
+
|
|
19
|
+
export default defineFitnessFunction({
|
|
20
|
+
id: 'event-ownership',
|
|
21
|
+
name: 'Every event has an owner',
|
|
22
|
+
|
|
23
|
+
async run({ catalog }) {
|
|
24
|
+
const events = await catalog.getEvents({ latestOnly: true });
|
|
25
|
+
|
|
26
|
+
return events
|
|
27
|
+
.filter((event) => !event.owners?.length)
|
|
28
|
+
.map((event) => ({
|
|
29
|
+
message: `${event.name} has no owners`,
|
|
30
|
+
resources: [{ type: 'event', id: event.id, version: event.version }],
|
|
31
|
+
}));
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx eventcatalog fitness check
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
EventCatalog Fitness
|
|
42
|
+
|
|
43
|
+
✗ Every event has an owner 1 violation
|
|
44
|
+
• Order Amended has no owners
|
|
45
|
+
Resources: order-amended (event)
|
|
46
|
+
|
|
47
|
+
1 fitness function evaluated
|
|
48
|
+
|
|
49
|
+
0 passed
|
|
50
|
+
1 failed
|
|
51
|
+
|
|
52
|
+
Report written to .eventcatalog/fitness/results.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Why use them
|
|
56
|
+
|
|
57
|
+
Teams agree on how their architecture should look: which domain owns which data, how coupled a service is allowed to be, what every event needs before it's published. Those decisions usually live in a wiki page or an ADR, and nothing tells you when the architecture stops following them.
|
|
58
|
+
|
|
59
|
+
Fitness functions turn those decisions into checks you can run on every change to your catalog. For example:
|
|
60
|
+
|
|
61
|
+
- Services in the Ordering domain don't read from or write to another domain's data stores.
|
|
62
|
+
- Every event has an owner and a schema.
|
|
63
|
+
- A service receives no more than a set number of messages.
|
|
64
|
+
- Nothing depends on a deprecated service.
|
|
65
|
+
- Business-critical services have the metadata your organization requires.
|
|
66
|
+
|
|
67
|
+
What you can check depends on what your catalog records. A rule about data store access needs `readsFrom` and `writesTo` on your services; a rule about ownership needs `owners`. Fitness functions read your documented architecture. They don't inspect your application code or production traffic.
|
|
68
|
+
|
|
69
|
+
## How they differ from tests
|
|
70
|
+
|
|
71
|
+
If you have written a unit test, you already know the shape: some code runs, makes assertions, and the result is pass or fail. The difference is what's being checked. A test checks how your application behaves. A fitness function checks the architecture your catalog describes: its domains, systems, services, messages, data stores, owners and the relationships between them.
|
|
72
|
+
|
|
73
|
+
## Where fitness functions live
|
|
74
|
+
|
|
75
|
+
Fitness functions are `*.fitness.ts` files in a `fitness/` folder. Put the folder in one of two places:
|
|
76
|
+
|
|
77
|
+
- **The root of your catalog** (`fitness/`), for rules that apply across the whole catalog.
|
|
78
|
+
- **A resource's folder** (`domains/Ordering/fitness/`, `services/OrderService/fitness/`), for rules about that resource. The function is given the resource it sits alongside, so a domain team can keep its rules next to its domain.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
my-catalog/
|
|
82
|
+
├── fitness/
|
|
83
|
+
│ └── event-ownership.fitness.ts # runs against the whole catalog
|
|
84
|
+
├── domains/
|
|
85
|
+
│ └── Ordering/
|
|
86
|
+
│ ├── index.mdx
|
|
87
|
+
│ └── fitness/
|
|
88
|
+
│ └── domain-boundaries.fitness.ts # given the Ordering domain
|
|
89
|
+
└── eventcatalog.config.js
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Every function can read the whole catalog, wherever it lives. See [Global and resource fitness functions](./03-global-and-resource-functions.md) for the folders EventCatalog recognises and how versions are handled.
|
|
93
|
+
|
|
94
|
+
## How results are reported
|
|
95
|
+
|
|
96
|
+
`eventcatalog fitness check` runs every function, one at a time, and:
|
|
97
|
+
|
|
98
|
+
- prints each function's result, its violations and any errors to the terminal,
|
|
99
|
+
- writes the full results to `.eventcatalog/fitness/results.json`,
|
|
100
|
+
- exits with `1` if any function failed or couldn't run, so CI fails the build.
|
|
101
|
+
|
|
102
|
+
Each violation lists the resources it's about. EventCatalog looks each one up in your catalog and records whether it exists and which version was checked, so you can go straight from a violation to the resource that caused it.
|
|
103
|
+
|
|
104
|
+
Results are shown in the terminal and in the JSON report. They aren't shown on pages in your catalog.
|
|
105
|
+
|
|
106
|
+
## Where to go next
|
|
107
|
+
|
|
108
|
+
- **[Create a fitness function](./02-create-fitness-function.md)**: write your first fitness function, run it and fix what it finds. Start here.
|
|
109
|
+
- **[Global and resource fitness functions](./03-global-and-resource-functions.md)**: where to put functions, and which resource each one is given.
|
|
110
|
+
- **[Run in CI](./04-run-in-ci.md)**: the command, its output, exit codes, the report file, and a GitHub Actions workflow.
|
|
111
|
+
- **[API reference](./05-api-reference.md)**: `defineFitnessFunction`, the run context, the violation format and the report schema.
|
|
112
|
+
- **[Examples](./06-examples.md)**: ready-to-use rules for team ownership, shared databases, coupling, circular commands, deprecated messages and more.
|
|
113
|
+
|
|
114
|
+
## Issues?
|
|
115
|
+
|
|
116
|
+
If you have any issues or feedback, please open an issue on [GitHub](https://github.com/event-catalog/eventcatalog/issues) or join our [Discord server](https://eventcatalog.dev/discord).
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
sidebar_label: Create a fitness function
|
|
4
|
+
title: Create a fitness function
|
|
5
|
+
description: Write a fitness function that requires every event to have an owner, run it, read the results and fix what it finds.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
import PromptBox from '@site/src/components/MDX/PromptBox';
|
|
9
|
+
|
|
10
|
+
<PromptBox preview="Help me write my first EventCatalog fitness function">
|
|
11
|
+
Read these EventCatalog docs first:
|
|
12
|
+
|
|
13
|
+
- https://www.eventcatalog.dev/docs/development/guides/fitness-functions/what-are-fitness-functions.md
|
|
14
|
+
- https://www.eventcatalog.dev/docs/development/guides/fitness-functions/create-fitness-function.md
|
|
15
|
+
- https://www.eventcatalog.dev/docs/development/guides/fitness-functions/global-and-resource-functions.md
|
|
16
|
+
- https://www.eventcatalog.dev/docs/development/guides/fitness-functions/api-reference.md
|
|
17
|
+
- https://www.eventcatalog.dev/docs/development/guides/fitness-functions/examples.md
|
|
18
|
+
|
|
19
|
+
Then help me write an architecture fitness function for my EventCatalog.
|
|
20
|
+
|
|
21
|
+
What you need to know: an architecture fitness function is an automated check that my documented architecture still follows a rule my team agreed on. In EventCatalog, it is a TypeScript file ending in .fitness.ts, inside a fitness/ folder. Put it in the fitness/ folder at the root of the catalog for a rule about the whole catalog, or in a fitness/ folder inside a resource's folder (for example domains/Orders/fitness/) to be given that resource when it runs. The file's default export is created with defineFitnessFunction from @eventcatalog/fitness, and has an id, a name, an optional description, a severity of 'error' or 'warning', and a run function. run is given resource (the resource the file sits alongside, if any) and catalog, and returns an array of violations. Each violation has a message, and optionally the resources it is about (type, id and version), details and a suggestion. An empty array means the check passed.
|
|
22
|
+
|
|
23
|
+
Use catalog to get resources out of my catalog. It has the same read methods as the EventCatalog SDK, such as getEvents, getServices, getDomain, getSystem, getDataStores, getTeams and getGraph. Methods that write to the catalog are not available. The SDK reference is at https://www.eventcatalog.dev/docs/sdk.md
|
|
24
|
+
|
|
25
|
+
Start by asking me what kind of fitness function I want to build:
|
|
26
|
+
|
|
27
|
+
- What rule or architectural decision should it check? For example ownership, domain boundaries, coupling between services, deprecated messages or documentation standards.
|
|
28
|
+
- Should it check the whole catalog, or one domain, system, service or message?
|
|
29
|
+
- Should violations fail the check (error), or only be reported (warning)?
|
|
30
|
+
|
|
31
|
+
Before writing it, look at my catalog to see which frontmatter fields and relationships it actually documents, so the function reads data that exists. Then create the file in the right fitness/ folder, run npx eventcatalog fitness check from the root of my catalog, and explain the results to me.
|
|
32
|
+
</PromptBox>
|
|
33
|
+
|
|
34
|
+
Copy this prompt and paste it into your coding agent. It reads these docs, asks what rule you want to check, looks at what your catalog documents, then writes and runs the function for you. To write one yourself, follow the steps below.
|
|
35
|
+
|
|
36
|
+
An architecture fitness function is an automated check that tells you whether your architecture still follows a decision you made. "Services in the Ordering domain don't touch another domain's database" or "every event has an owner" stops being a line in a wiki page and becomes a check that passes or fails.
|
|
37
|
+
|
|
38
|
+
In EventCatalog, a fitness function is a TypeScript function that reads your catalog and returns a list of violations: the places where the architecture your catalog describes breaks the rule. An empty list means the check passed. If you've written a unit test, the shape will feel familiar. The difference is that a test checks how your code behaves, and a fitness function checks your documented architecture. Read [What are fitness functions?](./01-what-are-fitness-functions.md) for more on why teams use them.
|
|
39
|
+
|
|
40
|
+
## What you'll build
|
|
41
|
+
|
|
42
|
+
This guide takes you from an existing catalog to your first working fitness function. You'll write a rule that every event must have an owner, run it, see it fail, and fix the catalog. It takes about five minutes.
|
|
43
|
+
|
|
44
|
+
## What you need
|
|
45
|
+
|
|
46
|
+
- An EventCatalog project with `@eventcatalog/core` installed. If you don't have one, `npx @eventcatalog/create-eventcatalog@latest my-catalog` creates one with example content.
|
|
47
|
+
|
|
48
|
+
Run every command in this guide from the root of your catalog (the folder containing `eventcatalog.config.js`).
|
|
49
|
+
|
|
50
|
+
:::info[New catalogs come with fitness functions]
|
|
51
|
+
Catalogs created with `create-eventcatalog` already have a `fitness/` folder with two starter functions: "Every message has an owner" and "Every event has a producer". They also have `@eventcatalog/fitness` installed and an `npm run fitness` script that runs `eventcatalog fitness check`. Run `npm run fitness` to see them work, then follow this guide to write your own.
|
|
52
|
+
:::
|
|
53
|
+
|
|
54
|
+
## 1. Create a fitness function
|
|
55
|
+
|
|
56
|
+
Fitness functions are files ending in `.fitness.ts`, in a folder called `fitness/`. Create the folder at the root of your catalog and add a file:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
my-catalog/
|
|
60
|
+
├── fitness/
|
|
61
|
+
│ └── event-ownership.fitness.ts
|
|
62
|
+
├── domains/
|
|
63
|
+
└── eventcatalog.config.js
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```ts title="fitness/event-ownership.fitness.ts"
|
|
67
|
+
import { defineFitnessFunction } from '@eventcatalog/fitness';
|
|
68
|
+
|
|
69
|
+
export default defineFitnessFunction({
|
|
70
|
+
id: 'event-ownership',
|
|
71
|
+
name: 'Every event has an owner',
|
|
72
|
+
description: 'Events without an owner have nobody to ask before they change.',
|
|
73
|
+
severity: 'error',
|
|
74
|
+
|
|
75
|
+
async run({ catalog }) {
|
|
76
|
+
return [];
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Every fitness function is the default export of its file, created with `defineFitnessFunction`. It needs:
|
|
82
|
+
|
|
83
|
+
- an `id`, unique across your catalog,
|
|
84
|
+
- a `name`, which is what you'll see in the results,
|
|
85
|
+
- a `run` function, which returns the violations it finds. An empty array means the check passed.
|
|
86
|
+
|
|
87
|
+
`severity` is optional. `error` (the default) fails the check; `warning` reports violations without failing it.
|
|
88
|
+
|
|
89
|
+
:::tip[Editor support]
|
|
90
|
+
You don't need to install anything to run fitness functions: EventCatalog provides `@eventcatalog/fitness` when it loads them. Catalogs created with `create-eventcatalog` already have it installed for editor types. In an older catalog, add the package as a dev dependency to get types and autocomplete:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install --save-dev @eventcatalog/fitness
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If your catalog has a `tsconfig.json`, make sure its `include` covers `**/fitness/**/*.ts`.
|
|
97
|
+
:::
|
|
98
|
+
|
|
99
|
+
## 2. Read the catalog
|
|
100
|
+
|
|
101
|
+
`run` is given a `catalog`: read-only access to your whole catalog, with the same methods and arguments as the [EventCatalog SDK](/docs/sdk). Use it to get every event:
|
|
102
|
+
|
|
103
|
+
```ts title="fitness/event-ownership.fitness.ts"
|
|
104
|
+
async run({ catalog }) {
|
|
105
|
+
const events = await catalog.getEvents({ latestOnly: true });
|
|
106
|
+
return [];
|
|
107
|
+
},
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`latestOnly: true` returns the latest version of each event, so old versions you've kept under `versioned/` aren't checked. Methods that write to the catalog, like `writeEvent`, aren't available. See [the catalog API](./05-api-reference.md#catalog) for every method you can call.
|
|
111
|
+
|
|
112
|
+
## 3. Return a violation
|
|
113
|
+
|
|
114
|
+
Return one violation for each event without an owner:
|
|
115
|
+
|
|
116
|
+
```ts title="fitness/event-ownership.fitness.ts"
|
|
117
|
+
import { defineFitnessFunction } from '@eventcatalog/fitness';
|
|
118
|
+
|
|
119
|
+
export default defineFitnessFunction({
|
|
120
|
+
id: 'event-ownership',
|
|
121
|
+
name: 'Every event has an owner',
|
|
122
|
+
description: 'Events without an owner have nobody to ask before they change.',
|
|
123
|
+
severity: 'error',
|
|
124
|
+
|
|
125
|
+
async run({ catalog }) {
|
|
126
|
+
const events = await catalog.getEvents({ latestOnly: true });
|
|
127
|
+
|
|
128
|
+
return events
|
|
129
|
+
.filter((event) => !event.owners?.length)
|
|
130
|
+
.map((event) => ({
|
|
131
|
+
message: `${event.name} has no owners`,
|
|
132
|
+
resources: [{ type: 'event', id: event.id, version: event.version }],
|
|
133
|
+
suggestion: 'Add a team or user to the owners field in the frontmatter',
|
|
134
|
+
}));
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
A violation needs a `message`. The rest is optional, but worth filling in:
|
|
140
|
+
|
|
141
|
+
- `resources` lists the resources the violation is about. EventCatalog checks that each one exists in your catalog and records the version it checked.
|
|
142
|
+
- `suggestion` tells whoever reads the result how to fix it.
|
|
143
|
+
- `details` adds any extra explanation.
|
|
144
|
+
|
|
145
|
+
## 4. Run it
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npx eventcatalog fitness check
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
In a catalog created with `create-eventcatalog`, `npm run fitness` does the same. EventCatalog finds every `*.fitness.ts` file in your catalog, runs each one, and prints the results. If an event has no owner, you'll see something like this:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
EventCatalog Fitness
|
|
155
|
+
|
|
156
|
+
✗ Every event has an owner 1 violation
|
|
157
|
+
• Order Amended has no owners
|
|
158
|
+
Resources: order-amended (event)
|
|
159
|
+
Suggestion: Add a team or user to the owners field in the frontmatter
|
|
160
|
+
|
|
161
|
+
1 fitness function evaluated
|
|
162
|
+
|
|
163
|
+
0 passed
|
|
164
|
+
1 failed
|
|
165
|
+
|
|
166
|
+
Report written to .eventcatalog/fitness/results.json
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 5. Read the results
|
|
170
|
+
|
|
171
|
+
Each function gets one line:
|
|
172
|
+
|
|
173
|
+
| Symbol | Result | Meaning |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| `✓` | `Passed` | The function returned no violations |
|
|
176
|
+
| `!` | `n warnings` | A `warning` function found violations. The check still passes |
|
|
177
|
+
| `✗` | `n violations` | An `error` function found violations. The check fails |
|
|
178
|
+
| `✗` | `Error` | The function couldn't run, for example because it threw an exception |
|
|
179
|
+
|
|
180
|
+
Under each line are the function's violations, with the resources they're about and the suggestion. Resources that aren't in your catalog are shown as `not found in the catalog`, which usually means a typo in the id or type.
|
|
181
|
+
|
|
182
|
+
Because a violation was found, the command exits with code `1`.
|
|
183
|
+
|
|
184
|
+
## 6. Fix the catalog and run it again
|
|
185
|
+
|
|
186
|
+
Add an owner to the event:
|
|
187
|
+
|
|
188
|
+
```md title="events/OrderAmended/index.mdx" {5-6}
|
|
189
|
+
---
|
|
190
|
+
id: order-amended
|
|
191
|
+
name: Order Amended
|
|
192
|
+
version: 1.0.0
|
|
193
|
+
owners:
|
|
194
|
+
- ordering-platform
|
|
195
|
+
---
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Run the check again:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
npx eventcatalog fitness check
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
EventCatalog Fitness
|
|
206
|
+
|
|
207
|
+
✓ Every event has an owner Passed
|
|
208
|
+
|
|
209
|
+
1 fitness function evaluated
|
|
210
|
+
|
|
211
|
+
1 passed
|
|
212
|
+
|
|
213
|
+
Report written to .eventcatalog/fitness/results.json
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The command now exits with code `0`.
|
|
217
|
+
|
|
218
|
+
## Next steps
|
|
219
|
+
|
|
220
|
+
- [Run your fitness functions in CI](./04-run-in-ci.md), so every pull request is checked, and read the report each run writes.
|
|
221
|
+
- [Put fitness functions next to the resources they're about](./03-global-and-resource-functions.md), so a domain team can own its rules.
|
|
222
|
+
- Read the [API reference](./05-api-reference.md) for everything `run` is given and can return.
|