@eventcatalog/core 3.41.4 → 3.42.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/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-VQLDZRHC.js → chunk-6FAGUEM4.js} +1 -1
- package/dist/{chunk-YWG7CCN7.js → chunk-KE6YTTLB.js} +1 -1
- package/dist/{chunk-LYRAK5LI.js → chunk-L66TCSM7.js} +1 -1
- package/dist/{chunk-COPXPOV2.js → chunk-UQIDXF2V.js} +1 -1
- package/dist/{chunk-OH2U6UEJ.js → chunk-VPZ77Y6E.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/docs/development/developer-tools/api-catalog.md +114 -0
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/pages/.well-known/api-catalog.ts +191 -0
- package/eventcatalog/src/pages/api-catalog/specifications/[collection]/[id]/[version]/[specification].ts +109 -0
- package/package.json +3 -3
|
@@ -110,7 +110,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
|
|
|
110
110
|
var import_os = __toESM(require("os"), 1);
|
|
111
111
|
|
|
112
112
|
// package.json
|
|
113
|
-
var version = "3.
|
|
113
|
+
var version = "3.42.0";
|
|
114
114
|
|
|
115
115
|
// src/constants.ts
|
|
116
116
|
var VERSION = version;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-L66TCSM7.js";
|
|
4
|
+
import "../chunk-6FAGUEM4.js";
|
|
5
5
|
import "../chunk-3DVHEVHQ.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-VPZ77Y6E.js";
|
|
7
7
|
import "../chunk-5T63CXKU.js";
|
|
8
8
|
export {
|
|
9
9
|
log_build_default as default
|
package/dist/constants.cjs
CHANGED
package/dist/constants.js
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 5
|
|
3
|
+
keywords:
|
|
4
|
+
- api-catalog
|
|
5
|
+
- RFC 9727
|
|
6
|
+
- well-known
|
|
7
|
+
- API discovery
|
|
8
|
+
- OpenAPI
|
|
9
|
+
- AsyncAPI
|
|
10
|
+
- GraphQL
|
|
11
|
+
sidebar_label: api-catalog (RFC 9727)
|
|
12
|
+
title: api-catalog
|
|
13
|
+
description: Machine-readable catalog discovery endpoint for tools and agents
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
import AddedIn from '@site/src/components/MDX/AddedIn';
|
|
17
|
+
|
|
18
|
+
<AddedIn version="3.42.0" />
|
|
19
|
+
|
|
20
|
+
Let API tools, agents, and crawlers discover every service and domain specification in your catalog from a single endpoint, without parsing HTML.
|
|
21
|
+
|
|
22
|
+
### What is RFC 9727?
|
|
23
|
+
|
|
24
|
+
[RFC 9727](https://datatracker.ietf.org/doc/rfc9727/) defines the `/.well-known/api-catalog` well-known URI. It returns a [Linkset](https://www.rfc-editor.org/rfc/rfc9264) document that lists every API an organization publishes along with links to their specifications and documentation.
|
|
25
|
+
|
|
26
|
+
Tools that understand RFC 9727 can point at your catalog URL and immediately enumerate all services and domains, their OpenAPI, AsyncAPI, and GraphQL specs, and their documentation pages. No scraping required.
|
|
27
|
+
|
|
28
|
+
### How it works
|
|
29
|
+
|
|
30
|
+
EventCatalog automatically publishes a Linkset at `/.well-known/api-catalog`. Every service and domain that has `specifications` defined in its frontmatter appears as an entry.
|
|
31
|
+
|
|
32
|
+
Each entry contains:
|
|
33
|
+
|
|
34
|
+
- **`anchor`** - the canonical URL of the service. EventCatalog reads the `servers[].url` field from OpenAPI or AsyncAPI specs and uses that. When no server URL is found it falls back to the EventCatalog documentation page.
|
|
35
|
+
- **`service-desc`** - one link per specification file, pointing at `/api-catalog/specifications/{collection}/{id}/{version}/{specification}` with the correct media type (`application/yaml`, `application/json`, or `application/graphql`).
|
|
36
|
+
- **`service-doc`** - two links per resource: the markdown source and the rendered HTML page.
|
|
37
|
+
|
|
38
|
+
Resources marked `hidden: true` are excluded from the linkset.
|
|
39
|
+
|
|
40
|
+
### Access the endpoint
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
GET /.well-known/api-catalog
|
|
44
|
+
HEAD /.well-known/api-catalog
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The `GET` response body is `application/linkset+json` profiled against RFC 9727:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"linkset": [
|
|
52
|
+
{
|
|
53
|
+
"anchor": "https://api.example.com/orders",
|
|
54
|
+
"service-desc": [
|
|
55
|
+
{
|
|
56
|
+
"href": "https://catalog.example.com/api-catalog/specifications/services/OrderService/1.0.0/openapi-b3BlbmFwaS55bWw",
|
|
57
|
+
"type": "application/yaml",
|
|
58
|
+
"title": "Order Service OpenAPI"
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"service-doc": [
|
|
62
|
+
{
|
|
63
|
+
"href": "https://catalog.example.com/docs/services/OrderService/1.0.0.md",
|
|
64
|
+
"type": "text/markdown",
|
|
65
|
+
"title": "Order Service documentation"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"href": "https://catalog.example.com/docs/services/OrderService/1.0.0",
|
|
69
|
+
"type": "text/html",
|
|
70
|
+
"title": "Order Service documentation"
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The `HEAD` response includes a `Link` header so clients can confirm the endpoint exists before fetching the full body:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Link: <https://catalog.example.com/.well-known/api-catalog>; rel="api-catalog"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Fetch a specification file
|
|
85
|
+
|
|
86
|
+
The raw specification files referenced in `service-desc` are served from:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
GET /api-catalog/specifications/{collection}/{id}/{version}/{specification}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| Segment | Values |
|
|
93
|
+
|---------|--------|
|
|
94
|
+
| `collection` | `services`, `domains` |
|
|
95
|
+
| `id` | The resource `id` field |
|
|
96
|
+
| `version` | The resource `version` field |
|
|
97
|
+
| `specification` | Stable specification identifier, formatted as `{type}-{base64url(path)}` |
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
GET /api-catalog/specifications/services/OrderService/1.0.0/openapi-b3BlbmFwaS55bWw
|
|
103
|
+
Content-Type: application/yaml
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### MCP server entry
|
|
107
|
+
|
|
108
|
+
When the EventCatalog MCP server is enabled, an additional entry pointing at `/docs/mcp` is appended to the linkset. This lets MCP-aware agents discover the catalog's machine interface alongside its API specifications.
|
|
109
|
+
|
|
110
|
+
### What is included
|
|
111
|
+
|
|
112
|
+
Only services and domains are included in v1 of this endpoint. Events, commands, queries, data products, schemas, diagrams, teams, and users are out of scope for this release.
|
|
113
|
+
|
|
114
|
+
For a broader machine-readable index of your catalog content, see [llms.txt](/docs/development/developer-tools/llms.txt) and [schemas.txt](/docs/development/developer-tools/schemas.txt).
|
package/dist/eventcatalog.cjs
CHANGED
|
@@ -114,7 +114,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
|
|
|
114
114
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
115
115
|
|
|
116
116
|
// package.json
|
|
117
|
-
var version = "3.
|
|
117
|
+
var version = "3.42.0";
|
|
118
118
|
|
|
119
119
|
// src/constants.ts
|
|
120
120
|
var VERSION = version;
|
package/dist/eventcatalog.js
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
} from "./chunk-3H2RT3CM.js";
|
|
14
14
|
import {
|
|
15
15
|
log_build_default
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-L66TCSM7.js";
|
|
17
|
+
import "./chunk-6FAGUEM4.js";
|
|
18
18
|
import "./chunk-3DVHEVHQ.js";
|
|
19
19
|
import {
|
|
20
20
|
catalogToAstro
|
|
@@ -28,13 +28,13 @@ import {
|
|
|
28
28
|
} from "./chunk-ULZYHF3V.js";
|
|
29
29
|
import {
|
|
30
30
|
generate
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-KE6YTTLB.js";
|
|
32
32
|
import {
|
|
33
33
|
logger
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-UQIDXF2V.js";
|
|
35
35
|
import {
|
|
36
36
|
VERSION
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-VPZ77Y6E.js";
|
|
38
38
|
import {
|
|
39
39
|
getEventCatalogConfigFile,
|
|
40
40
|
verifyRequiredFieldsAreInCatalogConfigFile
|
package/dist/generate.cjs
CHANGED
package/dist/generate.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generate
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-KE6YTTLB.js";
|
|
4
|
+
import "./chunk-UQIDXF2V.js";
|
|
5
|
+
import "./chunk-VPZ77Y6E.js";
|
|
6
6
|
import "./chunk-5T63CXKU.js";
|
|
7
7
|
export {
|
|
8
8
|
generate
|
package/dist/utils/cli-logger.js
CHANGED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
import yaml from 'js-yaml';
|
|
3
|
+
import { getServices, getSpecificationsForService } from '@utils/collections/services';
|
|
4
|
+
import { getDomains, getSpecificationsForDomain } from '@utils/collections/domains';
|
|
5
|
+
import type { ProcessedSpecification } from '@utils/collections/util';
|
|
6
|
+
import { buildUrl } from '@utils/url-builder';
|
|
7
|
+
import { readResourceFile } from '@utils/resource-files';
|
|
8
|
+
import { isEventCatalogMCPEnabled } from '@utils/feature';
|
|
9
|
+
|
|
10
|
+
const RFC_9727_PROFILE = 'https://www.rfc-editor.org/info/rfc9727';
|
|
11
|
+
const LINKSET_CONTENT_TYPE = `application/linkset+json; profile="${RFC_9727_PROFILE}"`;
|
|
12
|
+
|
|
13
|
+
type LinkTarget = {
|
|
14
|
+
href: string;
|
|
15
|
+
type?: string;
|
|
16
|
+
title?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type ApiCatalogEntry = {
|
|
20
|
+
anchor: string;
|
|
21
|
+
'service-desc': LinkTarget[];
|
|
22
|
+
'service-doc'?: LinkTarget[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type ApiCatalogResource = Awaited<ReturnType<typeof getServices>>[number] | Awaited<ReturnType<typeof getDomains>>[number];
|
|
26
|
+
|
|
27
|
+
const absoluteUrl = (request: Request, pathOrUrl: string) => new URL(pathOrUrl, request.url).toString();
|
|
28
|
+
|
|
29
|
+
const getSpecificationMediaType = (specification: ProcessedSpecification) => {
|
|
30
|
+
const extension = specification.filename.split('.').pop()?.toLowerCase();
|
|
31
|
+
|
|
32
|
+
if (specification.type === 'graphql') return 'application/graphql';
|
|
33
|
+
if (extension === 'json') return 'application/json';
|
|
34
|
+
if (extension === 'yaml' || extension === 'yml') return 'application/yaml';
|
|
35
|
+
|
|
36
|
+
return 'text/plain';
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getSpecificationIdentifier = (specification: ProcessedSpecification) => {
|
|
40
|
+
return `${specification.type}-${Buffer.from(specification.path).toString('base64url')}`;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const parseSpecification = (rawSpecification: string, path: string): unknown => {
|
|
44
|
+
if (path.endsWith('.json')) {
|
|
45
|
+
return JSON.parse(rawSpecification);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return yaml.load(rawSpecification);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const toHttpUrl = (value: unknown): string | undefined => {
|
|
52
|
+
if (typeof value !== 'string' || value.trim() === '') return undefined;
|
|
53
|
+
if (!/^https?:\/\//i.test(value)) return undefined;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const url = new URL(value);
|
|
57
|
+
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
|
58
|
+
return url.toString();
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return undefined;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const getSpecificationsForResource = (resource: ApiCatalogResource) => {
|
|
68
|
+
if (resource.collection === 'domains') {
|
|
69
|
+
return getSpecificationsForDomain(resource);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return getSpecificationsForService(resource);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const getEndpointFromSpecification = (request: Request, resource: ApiCatalogResource) => {
|
|
76
|
+
const specifications = getSpecificationsForResource(resource);
|
|
77
|
+
|
|
78
|
+
for (const specification of specifications) {
|
|
79
|
+
if (specification.type !== 'openapi' && specification.type !== 'asyncapi') continue;
|
|
80
|
+
|
|
81
|
+
const rawSpecification = readResourceFile(resource, specification.path);
|
|
82
|
+
if (!rawSpecification) continue;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const parsedSpecification = parseSpecification(rawSpecification, specification.path) as any;
|
|
86
|
+
|
|
87
|
+
if (specification.type === 'openapi') {
|
|
88
|
+
const serverUrl = parsedSpecification?.servers?.find((server: any) => typeof server?.url === 'string')?.url;
|
|
89
|
+
const endpoint = toHttpUrl(serverUrl);
|
|
90
|
+
if (endpoint) return endpoint;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (specification.type === 'asyncapi') {
|
|
94
|
+
const servers = Object.values(parsedSpecification?.servers ?? {}) as any[];
|
|
95
|
+
const serverUrl = servers.find((server) => typeof server?.url === 'string')?.url;
|
|
96
|
+
const endpoint = toHttpUrl(serverUrl);
|
|
97
|
+
if (endpoint) return endpoint;
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// Invalid or unsupported specifications should not prevent catalog discovery.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const getResourceDocumentationUrl = (request: Request, resource: ApiCatalogResource) => {
|
|
106
|
+
return absoluteUrl(request, buildUrl(`/docs/${resource.collection}/${resource.data.id}/${resource.data.version}`, true));
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const getResourceMarkdownUrl = (request: Request, resource: ApiCatalogResource) => {
|
|
110
|
+
return absoluteUrl(request, buildUrl(`/docs/${resource.collection}/${resource.data.id}/${resource.data.version}.md`, true));
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const toApiCatalogEntry = (request: Request, resource: ApiCatalogResource): ApiCatalogEntry | null => {
|
|
114
|
+
const specifications = getSpecificationsForResource(resource);
|
|
115
|
+
if (specifications.length === 0) return null;
|
|
116
|
+
|
|
117
|
+
const resourceDocumentationUrl = getResourceDocumentationUrl(request, resource);
|
|
118
|
+
const resourceMarkdownUrl = getResourceMarkdownUrl(request, resource);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
anchor: getEndpointFromSpecification(request, resource) ?? resourceDocumentationUrl,
|
|
122
|
+
'service-desc': specifications.map((specification) => ({
|
|
123
|
+
href: absoluteUrl(
|
|
124
|
+
request,
|
|
125
|
+
buildUrl(
|
|
126
|
+
`/api-catalog/specifications/${resource.collection}/${resource.data.id}/${resource.data.version}/${getSpecificationIdentifier(specification)}`,
|
|
127
|
+
true
|
|
128
|
+
)
|
|
129
|
+
),
|
|
130
|
+
type: getSpecificationMediaType(specification),
|
|
131
|
+
title: `${resource.data.name || resource.data.id} ${specification.name}`,
|
|
132
|
+
})),
|
|
133
|
+
'service-doc': [
|
|
134
|
+
{
|
|
135
|
+
href: resourceMarkdownUrl,
|
|
136
|
+
type: 'text/markdown',
|
|
137
|
+
title: `${resource.data.name || resource.data.id} documentation`,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
href: resourceDocumentationUrl,
|
|
141
|
+
type: 'text/html',
|
|
142
|
+
title: `${resource.data.name || resource.data.id} documentation`,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const getMcpCatalogEntry = (request: Request): ApiCatalogEntry | null => {
|
|
149
|
+
if (!isEventCatalogMCPEnabled()) return null;
|
|
150
|
+
|
|
151
|
+
const mcpUrl = absoluteUrl(request, buildUrl('/docs/mcp', true));
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
anchor: mcpUrl,
|
|
155
|
+
'service-desc': [
|
|
156
|
+
{
|
|
157
|
+
href: mcpUrl,
|
|
158
|
+
type: 'application/json',
|
|
159
|
+
title: 'EventCatalog MCP Server',
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const GET: APIRoute = async ({ request }) => {
|
|
166
|
+
const [services, domains] = await Promise.all([getServices({ getAllVersions: true }), getDomains({ getAllVersions: true })]);
|
|
167
|
+
const resources = [...services, ...domains];
|
|
168
|
+
const linkset = resources
|
|
169
|
+
.map((resource) => toApiCatalogEntry(request, resource))
|
|
170
|
+
.filter((entry): entry is ApiCatalogEntry => entry !== null);
|
|
171
|
+
|
|
172
|
+
const mcpEntry = getMcpCatalogEntry(request);
|
|
173
|
+
if (mcpEntry) {
|
|
174
|
+
linkset.push(mcpEntry);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return new Response(JSON.stringify({ linkset }, null, 2), {
|
|
178
|
+
headers: {
|
|
179
|
+
'Content-Type': LINKSET_CONTENT_TYPE,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export const HEAD: APIRoute = async ({ request }) => {
|
|
185
|
+
return new Response(null, {
|
|
186
|
+
headers: {
|
|
187
|
+
'Content-Type': LINKSET_CONTENT_TYPE,
|
|
188
|
+
Link: `<${absoluteUrl(request, buildUrl('/.well-known/api-catalog', true))}>; rel="api-catalog"`,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
import { getCollection } from 'astro:content';
|
|
3
|
+
import { getSpecificationsForDomain } from '@utils/collections/domains';
|
|
4
|
+
import { getSpecificationsForService } from '@utils/collections/services';
|
|
5
|
+
import type { ProcessedSpecification } from '@utils/collections/util';
|
|
6
|
+
import { readResourceFile } from '@utils/resource-files';
|
|
7
|
+
|
|
8
|
+
type SupportedCollection = 'domains' | 'services';
|
|
9
|
+
|
|
10
|
+
const isSupportedCollection = (collection: string | undefined): collection is SupportedCollection => {
|
|
11
|
+
return collection === 'domains' || collection === 'services';
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const getSpecificationMediaType = (specification: ProcessedSpecification) => {
|
|
15
|
+
const extension = specification.filename.split('.').pop()?.toLowerCase();
|
|
16
|
+
|
|
17
|
+
if (specification.type === 'graphql') return 'application/graphql';
|
|
18
|
+
if (extension === 'json') return 'application/json';
|
|
19
|
+
if (extension === 'yaml' || extension === 'yml') return 'application/yaml';
|
|
20
|
+
|
|
21
|
+
return 'text/plain';
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const getSpecificationIdentifier = (specification: ProcessedSpecification) => {
|
|
25
|
+
return `${specification.type}-${Buffer.from(specification.path).toString('base64url')}`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getSpecificationsForResource = (
|
|
29
|
+
resource:
|
|
30
|
+
| Awaited<ReturnType<typeof getCollection<'services'>>>[number]
|
|
31
|
+
| Awaited<ReturnType<typeof getCollection<'domains'>>>[number]
|
|
32
|
+
) => {
|
|
33
|
+
if (resource.collection === 'domains') {
|
|
34
|
+
return getSpecificationsForDomain(resource);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return getSpecificationsForService(resource);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export async function getStaticPaths() {
|
|
41
|
+
const [services, domains] = await Promise.all([getCollection('services'), getCollection('domains')]);
|
|
42
|
+
const resources = [...services, ...domains].filter((resource) => resource.data.hidden !== true);
|
|
43
|
+
|
|
44
|
+
return resources.flatMap((resource) =>
|
|
45
|
+
getSpecificationsForResource(resource).map((specification) => ({
|
|
46
|
+
params: {
|
|
47
|
+
collection: resource.collection,
|
|
48
|
+
id: resource.data.id,
|
|
49
|
+
version: resource.data.version,
|
|
50
|
+
specification: getSpecificationIdentifier(specification),
|
|
51
|
+
},
|
|
52
|
+
props: {
|
|
53
|
+
rawSpecification: readResourceFile(resource, specification.path),
|
|
54
|
+
contentType: getSpecificationMediaType(specification),
|
|
55
|
+
},
|
|
56
|
+
}))
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const GET: APIRoute = async ({ params, props }) => {
|
|
61
|
+
if (props.rawSpecification) {
|
|
62
|
+
return new Response(props.rawSpecification, {
|
|
63
|
+
headers: { 'Content-Type': props.contentType ?? 'text/plain' },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { collection, id, version, specification } = params;
|
|
68
|
+
|
|
69
|
+
if (!isSupportedCollection(collection) || !id || !version || !specification) {
|
|
70
|
+
return new Response(JSON.stringify({ error: 'Missing or invalid collection, id, version, or specification parameter' }), {
|
|
71
|
+
status: 400,
|
|
72
|
+
headers: { 'Content-Type': 'application/json' },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const resources = await getCollection(collection);
|
|
77
|
+
const resource = resources.find((item) => item.data.id === id && item.data.version === version && item.data.hidden !== true);
|
|
78
|
+
|
|
79
|
+
if (!resource) {
|
|
80
|
+
return new Response(JSON.stringify({ error: 'Resource not found' }), {
|
|
81
|
+
status: 404,
|
|
82
|
+
headers: { 'Content-Type': 'application/json' },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const spec = getSpecificationsForResource(resource).find(
|
|
87
|
+
(item) => getSpecificationIdentifier(item) === specification || item.type === specification
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (!spec) {
|
|
91
|
+
return new Response(JSON.stringify({ error: 'Specification not found' }), {
|
|
92
|
+
status: 404,
|
|
93
|
+
headers: { 'Content-Type': 'application/json' },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const rawSpecification = readResourceFile(resource, spec.path);
|
|
98
|
+
|
|
99
|
+
if (!rawSpecification) {
|
|
100
|
+
return new Response(JSON.stringify({ error: 'Specification file could not be read' }), {
|
|
101
|
+
status: 404,
|
|
102
|
+
headers: { 'Content-Type': 'application/json' },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return new Response(rawSpecification, {
|
|
107
|
+
headers: { 'Content-Type': getSpecificationMediaType(spec) },
|
|
108
|
+
});
|
|
109
|
+
};
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"license": "SEE LICENSE IN LICENSE",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"version": "3.
|
|
10
|
+
"version": "3.42.0",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
@@ -113,8 +113,8 @@
|
|
|
113
113
|
"uuid": "^10.0.0",
|
|
114
114
|
"zod": "^4.3.6",
|
|
115
115
|
"@eventcatalog/sdk": "2.23.0",
|
|
116
|
-
"@eventcatalog/
|
|
117
|
-
"@eventcatalog/
|
|
116
|
+
"@eventcatalog/linter": "1.0.26",
|
|
117
|
+
"@eventcatalog/visualiser": "^3.22.1"
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
120
120
|
"@astrojs/check": "^0.9.9",
|