@eventcatalog/core 5.7.6 → 5.9.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-SLN5D6WP.js → chunk-26C26LFE.js} +1 -1
- package/dist/{chunk-E2262TPK.js → chunk-C4L6B5KR.js} +1 -1
- package/dist/{chunk-XB4U75TZ.js → chunk-SZU7I2PD.js} +1 -1
- package/dist/{chunk-UI2XA4V7.js → chunk-UIJUSHOT.js} +1 -1
- package/dist/{chunk-A22MC5P5.js → chunk-WN6ZFSXF.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/docs/api/02-config.md +1 -1
- package/dist/docs/development/00-why-eventcatalog.md +12 -8
- package/dist/docs/development/01-adopting-eventcatalog.md +51 -0
- package/dist/docs/development/{ask-your-architecture/03-install-skills.md → 01-getting-started/install-skills.md} +10 -4
- package/dist/docs/development/01-getting-started/installation.md +5 -1
- package/dist/docs/development/ask-your-architecture/01-intro.md +7 -5
- package/dist/docs/development/ask-your-architecture/03-mcp-server/getting-started.md +17 -3
- package/dist/docs/development/ask-your-architecture/03-mcp-server/introduction.md +3 -0
- package/dist/docs/development/ask-your-architecture/06-sync-docs-with-your-code.md +220 -0
- package/dist/docs/development/ask-your-architecture/07-catch-breaking-changes.md +142 -0
- package/dist/docs/development/components/custom-components/02-create-your-first-component.md +4 -0
- package/dist/docs/development/customization/customize-visualizer/00-visualizer-nodes.md +7 -5
- package/dist/docs/development/developer-tools/api-catalog.md +1 -1
- package/dist/docs/development/{ask-your-architecture/02-llms.txt.md → developer-tools/llms.txt.md} +2 -0
- package/dist/docs/development/{ask-your-architecture/03-schemas.txt.md → developer-tools/schemas.txt.md} +1 -1
- package/dist/docs/development/developer-tools/ubiquitous-language.txt.md +42 -0
- package/dist/docs/development/guides/domains/03-ownership-and-language/02-adding-ubiquitous-language.md +3 -0
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +21 -21
- package/dist/federation/federate.js +2 -2
- 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/components/Settings/LlmAccessSettingsForm.tsx +23 -3
- package/eventcatalog/src/components/SpecificationDownloadButton.astro +24 -0
- package/eventcatalog/src/features/auth/middleware/middleware-auth.ts +1 -0
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +1 -1
- package/eventcatalog/src/features/mcp/mcp-scoped-tools.ts +7 -4
- package/eventcatalog/src/features/mcp/mcp-server.ts +13 -1
- package/eventcatalog/src/features/tools/catalog-tools.ts +97 -30
- package/eventcatalog/src/pages/api/schemas/services/[id]/[version]/[specification]/index.ts +25 -27
- package/eventcatalog/src/pages/discover/[type]/index.astro +9 -2
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename]/[file].ts +7 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +19 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/generic/[filename]/[file].ts +7 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/generic/[filename].astro +21 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename]/[file].ts +7 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +19 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename]/[file].ts +7 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +4 -0
- package/eventcatalog/src/pages/docs/llm/llms.txt.ts +24 -17
- package/eventcatalog/src/pages/docs/llm/schemas.txt.ts +1 -1
- package/eventcatalog/src/pages/docs/llm/ubiquitous-language.txt.ts +68 -0
- package/eventcatalog/src/pages/docs/services/[id]/asyncapi/[filename]/[file].ts +7 -0
- package/eventcatalog/src/pages/docs/services/[id]/graphql/[filename]/[file].ts +7 -0
- package/eventcatalog/src/pages/docs/services/[id]/spec/[filename]/[file].ts +7 -0
- package/eventcatalog/src/pages/docs/services/_latest-raw-specification-route.ts +37 -0
- package/eventcatalog/src/pages/settings/llm-access.astro +2 -0
- package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +7 -3
- package/eventcatalog/src/utils/collections/domains.ts +84 -0
- package/eventcatalog/src/utils/raw-specification.ts +126 -0
- package/package.json +4 -4
- package/dist/docs/development/ask-your-architecture/04-agents/01-overview.md +0 -27
- package/dist/docs/development/ask-your-architecture/04-agents/02-code-to-docs.md +0 -124
- package/dist/docs/development/ask-your-architecture/04-agents/03-breaking-changes.md +0 -170
- package/dist/docs/development/ask-your-architecture/04-agents/_category_.json +0 -12
- package/dist/{chunk-46TBJZC3.js → chunk-QJ7KKN2D.js} +4 -4
|
@@ -12,6 +12,8 @@ import js from '@asyncapi/react-component/browser/standalone/without-parser.js?u
|
|
|
12
12
|
import { AsyncApiComponentWP, type ConfigInterface } from '@asyncapi/react-component';
|
|
13
13
|
import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
14
14
|
import CopyAsMarkdown from '@components/CopyAsMarkdown';
|
|
15
|
+
import SpecificationDownloadButton from '@components/SpecificationDownloadButton.astro';
|
|
16
|
+
import { getRawSpecificationUrl } from '@utils/raw-specification';
|
|
15
17
|
import { isEventCatalogChatVisible } from '@utils/feature';
|
|
16
18
|
import Config from '@utils/eventcatalog-config/catalog';
|
|
17
19
|
import { Page } from './_[filename].data';
|
|
@@ -23,7 +25,7 @@ export const getStaticPaths = Page.getStaticPaths;
|
|
|
23
25
|
// Get data
|
|
24
26
|
const pageData = await Page.getData(Astro);
|
|
25
27
|
if (!pageData) return Astro.rewrite('/404');
|
|
26
|
-
const { collection, data, filePath, filename, path: relativeSpecPath, headers = {} } = pageData;
|
|
28
|
+
const { collection, data, filePath, filename, filenameWithoutExtension, path: relativeSpecPath, headers = {} } = pageData;
|
|
27
29
|
|
|
28
30
|
const fileName = filename || 'asyncapi.yaml';
|
|
29
31
|
const pathToSpec = getAbsoluteFilePathForAstroFile(filePath, fileName);
|
|
@@ -149,6 +151,15 @@ const renderedComponent = renderToString(component);
|
|
|
149
151
|
// Capitalize the first letter of a string
|
|
150
152
|
const pageTitle = `${collection} | ${data.name} | AsyncApi Spec`.replace(/^\w/, (c) => c.toUpperCase());
|
|
151
153
|
|
|
154
|
+
const downloadUrl = getRawSpecificationUrl({
|
|
155
|
+
collection,
|
|
156
|
+
id: data.id,
|
|
157
|
+
version: data.version,
|
|
158
|
+
type: 'asyncapi',
|
|
159
|
+
filename,
|
|
160
|
+
filenameWithoutExtension,
|
|
161
|
+
});
|
|
162
|
+
|
|
152
163
|
// Chat configuration
|
|
153
164
|
const chatEnabled = isEventCatalogChatVisible();
|
|
154
165
|
const chatQuery = `Tell me about the AsyncAPI specification for "${data.name}" (version ${data.version})`;
|
|
@@ -175,9 +186,10 @@ const pagefindAttributes =
|
|
|
175
186
|
// https://github.com/CloudCannon/pagefind/issues/437
|
|
176
187
|
}
|
|
177
188
|
<h2 class="hidden">{pageTitle}</h2>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
189
|
+
<div class="absolute top-3 right-4 z-10 flex items-center gap-2" data-pagefind-ignore>
|
|
190
|
+
{(fileExists || isRemote) && <SpecificationDownloadButton href={downloadUrl} filename={filename} />}
|
|
191
|
+
{
|
|
192
|
+
chatEnabled && (
|
|
181
193
|
<CopyAsMarkdown
|
|
182
194
|
client:only="react"
|
|
183
195
|
schemas={[]}
|
|
@@ -189,9 +201,9 @@ const pagefindAttributes =
|
|
|
189
201
|
preferChatAsDefault={true}
|
|
190
202
|
chatButtonText="Ask about this specification"
|
|
191
203
|
/>
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
</div>
|
|
195
207
|
{
|
|
196
208
|
failedToParse ? (
|
|
197
209
|
<div class="not-prose mx-auto my-12 max-w-3xl rounded-lg border border-red-300 bg-[rgb(var(--ec-card-bg))] p-6">
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { isSSR } from '@utils/feature';
|
|
2
|
+
import { createRawSpecificationRoute } from '@utils/raw-specification';
|
|
3
|
+
import { Page } from '../_[filename].data';
|
|
4
|
+
|
|
5
|
+
// The specification's file, as written, next to its page
|
|
6
|
+
export const prerender = !isSSR();
|
|
7
|
+
export const { getStaticPaths, GET } = createRawSpecificationRoute('generic', Page);
|
|
@@ -10,6 +10,8 @@ import { resolveHeaders } from '@utils/remote-spec';
|
|
|
10
10
|
import Admonition from '@components/MDX/Admonition';
|
|
11
11
|
import NewVersionBanner from '@components/NewVersionBanner.astro';
|
|
12
12
|
import Badge from '@components/Badge.astro';
|
|
13
|
+
import SpecificationDownloadButton from '@components/SpecificationDownloadButton.astro';
|
|
14
|
+
import { getRawSpecificationUrl } from '@utils/raw-specification';
|
|
13
15
|
import { getResourceTypeBadge } from '@utils/collections/resource-type-badge';
|
|
14
16
|
import { getResourceNoun, getSpecificationCodeLanguage } from '@utils/collections/util';
|
|
15
17
|
|
|
@@ -49,6 +51,15 @@ if (isRemote) {
|
|
|
49
51
|
content = readResourceFile(pageData, relativeSpecPath) ?? '';
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
const downloadUrl = getRawSpecificationUrl({
|
|
55
|
+
collection,
|
|
56
|
+
id: data.id,
|
|
57
|
+
version: data.version,
|
|
58
|
+
type: 'generic',
|
|
59
|
+
filename,
|
|
60
|
+
filenameWithoutExtension,
|
|
61
|
+
});
|
|
62
|
+
|
|
52
63
|
const iconURL = resolveIconUrl(getGenericSpecificationIcon(icon));
|
|
53
64
|
|
|
54
65
|
// Create comprehensive page title
|
|
@@ -80,7 +91,16 @@ const pagefindAttributes =
|
|
|
80
91
|
<div class="w-full min-w-0 lg:mr-2 xl:pr-12 py-8 bg-[rgb(var(--ec-page-bg))]">
|
|
81
92
|
<!-- Title and subtitle sized like the resource's overview page, kept compact so the source starts high up -->
|
|
82
93
|
<div class="border-b border-[rgb(var(--ec-page-border))] pb-4">
|
|
83
|
-
<
|
|
94
|
+
<div class="flex items-start justify-between gap-4">
|
|
95
|
+
<h1 class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">{data.name}</h1>
|
|
96
|
+
{
|
|
97
|
+
fileExists && (
|
|
98
|
+
<div data-pagefind-ignore>
|
|
99
|
+
<SpecificationDownloadButton href={downloadUrl} filename={filename} />
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
</div>
|
|
84
104
|
<p class="text-base pt-2 text-[rgb(var(--ec-page-text-muted))] font-light">{specificationName}</p>
|
|
85
105
|
<div class="flex flex-wrap gap-1.5 pt-3">
|
|
86
106
|
{badges.map((badge: any) => <Badge badge={badge} />)}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { isSSR } from '@utils/feature';
|
|
2
|
+
import { createRawSpecificationRoute } from '@utils/raw-specification';
|
|
3
|
+
import { Page } from '../_[filename].data';
|
|
4
|
+
|
|
5
|
+
// The specification's file, as written, next to its page
|
|
6
|
+
export const prerender = !isSSR();
|
|
7
|
+
export const { getStaticPaths, GET } = createRawSpecificationRoute('graphql', Page);
|
|
@@ -10,6 +10,8 @@ import { buildUrl, buildEditUrlForResource } from '@utils/url-builder';
|
|
|
10
10
|
import Admonition from '@components/MDX/Admonition';
|
|
11
11
|
import NewVersionBanner from '@components/NewVersionBanner.astro';
|
|
12
12
|
import Badge from '@components/Badge.astro';
|
|
13
|
+
import SpecificationDownloadButton from '@components/SpecificationDownloadButton.astro';
|
|
14
|
+
import { getRawSpecificationUrl } from '@utils/raw-specification';
|
|
13
15
|
import { getResourceTypeBadge } from '@utils/collections/resource-type-badge';
|
|
14
16
|
import { getResourceNoun } from '@utils/collections/util';
|
|
15
17
|
|
|
@@ -19,7 +21,7 @@ export const getStaticPaths = Page.getStaticPaths;
|
|
|
19
21
|
// Get data
|
|
20
22
|
const pageData = await Page.getData(Astro);
|
|
21
23
|
if (!pageData) return Astro.rewrite('/404');
|
|
22
|
-
const { collection, data, filePath, filename, path: relativeSpecPath } = pageData;
|
|
24
|
+
const { collection, data, filePath, filename, filenameWithoutExtension, path: relativeSpecPath } = pageData;
|
|
23
25
|
|
|
24
26
|
const fileName = filename || 'schema.graphql';
|
|
25
27
|
const pathToSpec = getAbsoluteFilePathForAstroFile(filePath, fileName);
|
|
@@ -36,6 +38,15 @@ if (isRemote) {
|
|
|
36
38
|
content = await fetch(relativeSpecPath).then((res) => res.text());
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
const downloadUrl = getRawSpecificationUrl({
|
|
42
|
+
collection,
|
|
43
|
+
id: data.id,
|
|
44
|
+
version: data.version,
|
|
45
|
+
type: 'graphql',
|
|
46
|
+
filename,
|
|
47
|
+
filenameWithoutExtension,
|
|
48
|
+
});
|
|
49
|
+
|
|
39
50
|
// Create comprehensive page title
|
|
40
51
|
const pageTitle = `${collection} | ${data.name} | GraphQL Schema`.replace(/^\w/, (c) => c.toUpperCase());
|
|
41
52
|
|
|
@@ -79,6 +90,13 @@ const pagefindAttributes =
|
|
|
79
90
|
</h1>
|
|
80
91
|
<h2 class="text-lg text-[rgb(var(--ec-page-text-muted))] font-medium mb-1">GraphQL Schema</h2>
|
|
81
92
|
</div>
|
|
93
|
+
{
|
|
94
|
+
(fileExists || isRemote) && (
|
|
95
|
+
<div data-pagefind-ignore>
|
|
96
|
+
<SpecificationDownloadButton href={downloadUrl} filename={filename} />
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
82
100
|
</div>
|
|
83
101
|
|
|
84
102
|
{
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { isSSR } from '@utils/feature';
|
|
2
|
+
import { createRawSpecificationRoute } from '@utils/raw-specification';
|
|
3
|
+
import { Page } from '../_[filename].data';
|
|
4
|
+
|
|
5
|
+
// The specification's file, as written, next to its page
|
|
6
|
+
export const prerender = !isSSR();
|
|
7
|
+
export const { getStaticPaths, GET } = createRawSpecificationRoute('openapi', Page);
|
|
@@ -76,6 +76,10 @@ if (isRemote) {
|
|
|
76
76
|
// https://github.com/CloudCannon/pagefind/issues/437
|
|
77
77
|
}
|
|
78
78
|
<h2 class="hidden">{pageTitle}</h2>
|
|
79
|
+
{
|
|
80
|
+
// No download button here, unlike the other specification pages: Scalar shows its own
|
|
81
|
+
// "Download OpenAPI Document" link. The file is also served at ./{filename}/{file}.
|
|
82
|
+
}
|
|
79
83
|
{chatEnabled && (
|
|
80
84
|
<div class="absolute top-3 right-14 z-10">
|
|
81
85
|
<CopyAsMarkdown
|
|
@@ -3,7 +3,7 @@ import config from '@config';
|
|
|
3
3
|
import type { APIRoute } from 'astro';
|
|
4
4
|
|
|
5
5
|
import { isLLMSTxtEnabled } from '@utils/feature';
|
|
6
|
-
import { getUbiquitousLanguage } from '@utils/collections/domains';
|
|
6
|
+
import { getTermSummary, getUbiquitousLanguage } from '@utils/collections/domains';
|
|
7
7
|
import { getResourceDocs } from '@utils/collections/resource-docs';
|
|
8
8
|
|
|
9
9
|
const events = await getCollection('events');
|
|
@@ -32,8 +32,8 @@ const ubiquitousLanguages: Record<string, { id: string; version: string; propert
|
|
|
32
32
|
for (const domain of domains) {
|
|
33
33
|
const ubiquitousLanguagesForDomain = await getUbiquitousLanguage(domain);
|
|
34
34
|
if (ubiquitousLanguagesForDomain.length > 0) {
|
|
35
|
-
ubiquitousLanguages[domain.id] = ubiquitousLanguagesForDomain.map((item) => ({
|
|
36
|
-
id: domain.id,
|
|
35
|
+
ubiquitousLanguages[domain.data.id] = ubiquitousLanguagesForDomain.map((item) => ({
|
|
36
|
+
id: domain.data.id,
|
|
37
37
|
version: domain.data.version,
|
|
38
38
|
properties: item.data.dictionary,
|
|
39
39
|
}));
|
|
@@ -44,15 +44,15 @@ for (const domain of domains) {
|
|
|
44
44
|
const renderUbiquitousLanguages = (baseUrl: string) => {
|
|
45
45
|
return Object.entries(ubiquitousLanguages)
|
|
46
46
|
.map(([domainId, items]) => {
|
|
47
|
-
const domainName = domains.find((domain) => domain.id === domainId)?.data.name || domainId;
|
|
47
|
+
const domainName = domains.find((domain) => domain.data.id === domainId)?.data.name || domainId;
|
|
48
48
|
const itemsList = items
|
|
49
49
|
.map((item) => {
|
|
50
50
|
// @ts-ignore
|
|
51
51
|
const propertiesList = Object.entries(item.properties)
|
|
52
|
-
.map(
|
|
53
|
-
(
|
|
54
|
-
|
|
55
|
-
)
|
|
52
|
+
.map(([key, value]: any) => {
|
|
53
|
+
const summary = getTermSummary(value);
|
|
54
|
+
return ` - [${value.name}${summary ? `: - ${summary}` : ''}](${baseUrl}/docs/domains/${domainId}/language.mdx)`;
|
|
55
|
+
})
|
|
56
56
|
.join('\n');
|
|
57
57
|
return propertiesList;
|
|
58
58
|
})
|
|
@@ -93,7 +93,8 @@ export const GET: APIRoute = async ({ params, request }) => {
|
|
|
93
93
|
|
|
94
94
|
const formatVersionedItem = (item: any, type: string, extraParams?: string | string[]) => {
|
|
95
95
|
const params = Array.isArray(extraParams) ? extraParams.join('&') : extraParams || '';
|
|
96
|
-
|
|
96
|
+
const summary = item.data.summary?.trim();
|
|
97
|
+
return `- [${item.data.name} - ${item.data.id} - ${item.data.version} ${params ? `- ${params}` : ''}](${baseUrl}/docs/${type}/${item.data.id}/${item.data.version}.mdx) ${summary ? `- ${summary}` : ''}`;
|
|
97
98
|
};
|
|
98
99
|
|
|
99
100
|
const formatSimpleItem = (item: any, type: string) =>
|
|
@@ -136,19 +137,19 @@ export const GET: APIRoute = async ({ params, request }) => {
|
|
|
136
137
|
`# ${config.organizationName} EventCatalog Documentation\n`,
|
|
137
138
|
`> ${config.tagline}\n`,
|
|
138
139
|
'## Events',
|
|
139
|
-
events.map((item) => formatVersionedItem(item, 'events')).join(''),
|
|
140
|
+
events.map((item) => formatVersionedItem(item, 'events')).join('\n'),
|
|
140
141
|
'\n## Commands',
|
|
141
|
-
commands.map((item) => formatVersionedItem(item, 'commands')).join(''),
|
|
142
|
+
commands.map((item) => formatVersionedItem(item, 'commands')).join('\n'),
|
|
142
143
|
'\n## Queries',
|
|
143
|
-
queries.map((item) => formatVersionedItem(item, 'queries')).join(''),
|
|
144
|
+
queries.map((item) => formatVersionedItem(item, 'queries')).join('\n'),
|
|
144
145
|
'\n## Agents',
|
|
145
|
-
agents.map((item) => formatVersionedItem(item, 'agents')).join(''),
|
|
146
|
+
agents.map((item) => formatVersionedItem(item, 'agents')).join('\n'),
|
|
146
147
|
'\n## Services',
|
|
147
|
-
services.map((item) => formatVersionedItem(item, 'services')).join(''),
|
|
148
|
+
services.map((item) => formatVersionedItem(item, 'services')).join('\n'),
|
|
148
149
|
'\n## Data Products',
|
|
149
150
|
dataProducts.map((item) => formatVersionedItem(item, 'data-products')).join('\n'),
|
|
150
151
|
'\n## Domains',
|
|
151
|
-
domains.map((item) => formatVersionedItem(item, 'domains')).join(''),
|
|
152
|
+
domains.map((item) => formatVersionedItem(item, 'domains')).join('\n'),
|
|
152
153
|
'\n## Flows',
|
|
153
154
|
flows.map((item) => formatVersionedItem(item, 'flows')).join('\n'),
|
|
154
155
|
'\n## Channels',
|
|
@@ -156,8 +157,14 @@ export const GET: APIRoute = async ({ params, request }) => {
|
|
|
156
157
|
.map((item) =>
|
|
157
158
|
formatVersionedItem(item, 'channels', item.data.protocols?.map((protocol) => `protocol - ${protocol}`).join('&'))
|
|
158
159
|
)
|
|
159
|
-
.join(''),
|
|
160
|
-
...(Object.keys(ubiquitousLanguages).length > 0
|
|
160
|
+
.join('\n'),
|
|
161
|
+
...(Object.keys(ubiquitousLanguages).length > 0
|
|
162
|
+
? [
|
|
163
|
+
'\n## Ubiquitous Language',
|
|
164
|
+
`The whole glossary, with terms defined in more than one domain listed together: ${baseUrl}/docs/llm/ubiquitous-language.txt`,
|
|
165
|
+
renderUbiquitousLanguages(baseUrl),
|
|
166
|
+
]
|
|
167
|
+
: []),
|
|
161
168
|
'\n## Containers (Databases, External Systems)',
|
|
162
169
|
containers.map((item) => formatVersionedItem(item, 'containers')).join('\n'),
|
|
163
170
|
'\n## Entities',
|
|
@@ -52,7 +52,7 @@ export const GET: APIRoute = async ({ params, request }) => {
|
|
|
52
52
|
|
|
53
53
|
const formatVersionedItem = (item: (typeof schemas)[number]) => {
|
|
54
54
|
const message = item.data.message;
|
|
55
|
-
return `- [${message.name || message.id} - ${message.id} - ${message.version}](${baseUrl}/api/schemas/${message.collectionName}/${message.id}/${message.version})
|
|
55
|
+
return `- [${message.name || message.id} - ${message.id} - ${message.version}](${baseUrl}/api/schemas/${message.collectionName}/${message.id}/${message.version}) ${message.summary ? `- ${message.summary.trim()}` : ''}`;
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
const formatServiceWithSchema = (item: ServiceWithSchema) => {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import config from '@config';
|
|
2
|
+
import type { APIRoute } from 'astro';
|
|
3
|
+
import { isLLMSTxtEnabled } from '@utils/feature';
|
|
4
|
+
import {
|
|
5
|
+
getTermsDefinedInSeveralDomains,
|
|
6
|
+
getTermSummary,
|
|
7
|
+
getUbiquitousLanguageGlossary,
|
|
8
|
+
type DomainGlossary,
|
|
9
|
+
type UbiquitousLanguageTerm,
|
|
10
|
+
} from '@utils/collections/domains';
|
|
11
|
+
|
|
12
|
+
const formatTerm = (term: UbiquitousLanguageTerm) => {
|
|
13
|
+
const summary = getTermSummary(term);
|
|
14
|
+
return `- ${term.name}${summary ? `: ${summary}` : ''}`;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const GET: APIRoute = async ({ request }) => {
|
|
18
|
+
if (!isLLMSTxtEnabled()) {
|
|
19
|
+
return new Response('ubiquitous-language.txt is not enabled for this Catalog.', { status: 404 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const url = new URL(request.url);
|
|
23
|
+
const baseUrl = process.env.LLMS_TXT_BASE_URL || `${url.origin}`;
|
|
24
|
+
|
|
25
|
+
const glossary = await getUbiquitousLanguageGlossary();
|
|
26
|
+
|
|
27
|
+
const header = [
|
|
28
|
+
`# ${config.organizationName} Ubiquitous Language`,
|
|
29
|
+
'> The business terms used across this organization, grouped by domain. A term only applies within its domain: the same word can mean something different in another domain.',
|
|
30
|
+
].join('\n');
|
|
31
|
+
|
|
32
|
+
if (glossary.length === 0) {
|
|
33
|
+
return new Response(`${header}\n\nNo ubiquitous language terms are defined in this catalog.`, {
|
|
34
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const formatDomain = (domain: DomainGlossary, heading: string) =>
|
|
39
|
+
[
|
|
40
|
+
`${heading} ${domain.name}`,
|
|
41
|
+
...(domain.terms.length > 0
|
|
42
|
+
? [`Full definitions: ${baseUrl}/docs/domains/${domain.id}/language.mdx`, ...domain.terms.map(formatTerm)]
|
|
43
|
+
: []),
|
|
44
|
+
].join('\n');
|
|
45
|
+
|
|
46
|
+
const termsInSeveralDomains = [...getTermsDefinedInSeveralDomains(glossary).values()].map((definitions) =>
|
|
47
|
+
[
|
|
48
|
+
`- ${definitions[0].term.name}`,
|
|
49
|
+
...definitions.map(({ domain, term }) => {
|
|
50
|
+
const summary = getTermSummary(term);
|
|
51
|
+
return ` - ${domain.name}${summary ? `: ${summary}` : ''}`;
|
|
52
|
+
}),
|
|
53
|
+
].join('\n')
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const sections = [
|
|
57
|
+
header,
|
|
58
|
+
...(termsInSeveralDomains.length > 0 ? [`## Terms defined in more than one domain\n${termsInSeveralDomains.join('\n')}`] : []),
|
|
59
|
+
...glossary.flatMap((domain) => [
|
|
60
|
+
formatDomain(domain, '##'),
|
|
61
|
+
...domain.subdomains.map((subdomain) => formatDomain(subdomain, '###')),
|
|
62
|
+
]),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
return new Response(sections.join('\n\n'), {
|
|
66
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
67
|
+
});
|
|
68
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { isSSR } from '@utils/feature';
|
|
2
|
+
import { Page } from '../../../../[type]/[id]/[version]/asyncapi/_[filename].data';
|
|
3
|
+
import { createLatestServiceRawSpecificationRoute } from '../../../_latest-raw-specification-route';
|
|
4
|
+
|
|
5
|
+
// The file of the service's latest specification, at a URL that doesn't change with each release
|
|
6
|
+
export const prerender = !isSSR();
|
|
7
|
+
export const { getStaticPaths, GET } = createLatestServiceRawSpecificationRoute('asyncapi', Page);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { isSSR } from '@utils/feature';
|
|
2
|
+
import { Page } from '../../../../[type]/[id]/[version]/graphql/_[filename].data';
|
|
3
|
+
import { createLatestServiceRawSpecificationRoute } from '../../../_latest-raw-specification-route';
|
|
4
|
+
|
|
5
|
+
// The file of the service's latest specification, at a URL that doesn't change with each release
|
|
6
|
+
export const prerender = !isSSR();
|
|
7
|
+
export const { getStaticPaths, GET } = createLatestServiceRawSpecificationRoute('graphql', Page);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { isSSR } from '@utils/feature';
|
|
2
|
+
import { Page } from '../../../../[type]/[id]/[version]/spec/_[filename].data';
|
|
3
|
+
import { createLatestServiceRawSpecificationRoute } from '../../../_latest-raw-specification-route';
|
|
4
|
+
|
|
5
|
+
// The file of the service's latest specification, at a URL that doesn't change with each release
|
|
6
|
+
export const prerender = !isSSR();
|
|
7
|
+
export const { getStaticPaths, GET } = createLatestServiceRawSpecificationRoute('openapi', Page);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { APIContext, APIRoute } from 'astro';
|
|
2
|
+
import type { SpecificationType } from '@utils/collections/util';
|
|
3
|
+
import {
|
|
4
|
+
getRawSpecificationResponse,
|
|
5
|
+
isRawSpecificationFile,
|
|
6
|
+
notFound,
|
|
7
|
+
toRawSpecificationPaths,
|
|
8
|
+
type SpecificationPage,
|
|
9
|
+
type SpecificationPageData,
|
|
10
|
+
type StaticPath,
|
|
11
|
+
} from '@utils/raw-specification';
|
|
12
|
+
import { getLatestServicePaths, getLatestServiceVersion } from './_latest-version-route';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The route that serves the file of a service's latest version, for links that don't change with each
|
|
16
|
+
* release: /docs/services/{id}/{asyncapi|spec|graphql}/{filename}/{file}
|
|
17
|
+
*/
|
|
18
|
+
export const createLatestServiceRawSpecificationRoute = (type: SpecificationType, Page: SpecificationPage) => {
|
|
19
|
+
const getStaticPaths = async () =>
|
|
20
|
+
(await getLatestServicePaths(toRawSpecificationPaths(await Page.getStaticPaths()))) as StaticPath[];
|
|
21
|
+
|
|
22
|
+
const GET: APIRoute = async ({ params, props }: APIContext) => {
|
|
23
|
+
const version = props.redirectVersion ?? (params.id ? await getLatestServiceVersion(params.id) : undefined);
|
|
24
|
+
if (!version) return notFound('Specification not found');
|
|
25
|
+
|
|
26
|
+
const pageData: SpecificationPageData | undefined = await Page.getData({
|
|
27
|
+
params: { type: 'services', id: params.id, version, filename: params.filename },
|
|
28
|
+
props,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!pageData || !isRawSpecificationFile(pageData, params.file)) return notFound('Specification not found');
|
|
32
|
+
|
|
33
|
+
return getRawSpecificationResponse(type, pageData);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return { getStaticPaths, GET };
|
|
37
|
+
};
|
|
@@ -16,6 +16,7 @@ const apiBase = buildUrl('/api/settings');
|
|
|
16
16
|
const llmsTxtUrl = buildUrl('/llms.txt');
|
|
17
17
|
const llmsFullTxtUrl = buildUrl('/llms-full.txt');
|
|
18
18
|
const schemasTxtUrl = buildUrl('/docs/llm/schemas.txt');
|
|
19
|
+
const ubiquitousLanguageTxtUrl = buildUrl('/docs/llm/ubiquitous-language.txt');
|
|
19
20
|
---
|
|
20
21
|
|
|
21
22
|
<SettingsLayout title="LLM Access" active="llm-access">
|
|
@@ -27,5 +28,6 @@ const schemasTxtUrl = buildUrl('/docs/llm/schemas.txt');
|
|
|
27
28
|
llmsTxtUrl={llmsTxtUrl}
|
|
28
29
|
llmsFullTxtUrl={llmsFullTxtUrl}
|
|
29
30
|
schemasTxtUrl={schemasTxtUrl}
|
|
31
|
+
ubiquitousLanguageTxtUrl={ubiquitousLanguageTxtUrl}
|
|
30
32
|
/>
|
|
31
33
|
</SettingsLayout>
|
|
@@ -119,9 +119,13 @@ export const buildDataProductSections = (
|
|
|
119
119
|
context.resourceDocCategories
|
|
120
120
|
);
|
|
121
121
|
|
|
122
|
-
// Resolve inputs and outputs to their proper sidebar references
|
|
123
|
-
|
|
124
|
-
const
|
|
122
|
+
// Resolve inputs and outputs to their proper sidebar references. The same resource can be
|
|
123
|
+
// listed more than once (e.g. one output per data contract), so show each resource only once.
|
|
124
|
+
const resolveRefs = (pointers: { id: string; version?: string }[]) => [
|
|
125
|
+
...new Set(pointers.map((pointer) => resolvePointerToRef(pointer, context)).filter((ref): ref is string => !!ref)),
|
|
126
|
+
];
|
|
127
|
+
const resolvedInputs = resolveRefs(inputs);
|
|
128
|
+
const resolvedOutputs = resolveRefs(outputs);
|
|
125
129
|
|
|
126
130
|
// Extract data contracts from outputs that have a contract field
|
|
127
131
|
const dataContracts = outputs
|
|
@@ -436,6 +436,90 @@ export const getRootDomains = async (): Promise<Domain[]> => {
|
|
|
436
436
|
return domains.filter((d) => !allSubDomains.some((sd) => sd.data.id === d.data.id));
|
|
437
437
|
};
|
|
438
438
|
|
|
439
|
+
export type UbiquitousLanguageTerm = NonNullable<UbiquitousLanguage['data']['dictionary']>[number];
|
|
440
|
+
|
|
441
|
+
export type DomainGlossary = {
|
|
442
|
+
id: string;
|
|
443
|
+
version: string;
|
|
444
|
+
name: string;
|
|
445
|
+
terms: UbiquitousLanguageTerm[];
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
export type SubdomainGlossary = DomainGlossary & { parentId: string };
|
|
449
|
+
|
|
450
|
+
export type OrganizationGlossaryDomain = DomainGlossary & { subdomains: SubdomainGlossary[] };
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* The ubiquitous language of the whole catalog: every domain with terms (or with a subdomain that has
|
|
454
|
+
* them), with all of its subdomains, at any depth, listed under it. Terms stay with their domain and are
|
|
455
|
+
* never merged, because the same word can mean something different in another domain.
|
|
456
|
+
*/
|
|
457
|
+
export const getUbiquitousLanguageGlossary = async (): Promise<OrganizationGlossaryDomain[]> => {
|
|
458
|
+
const [domains, rootDomains, ubiquitousLanguages] = await Promise.all([
|
|
459
|
+
getDomains({ getAllVersions: false }),
|
|
460
|
+
getRootDomains(),
|
|
461
|
+
getCollection('ubiquitousLanguages'),
|
|
462
|
+
]);
|
|
463
|
+
const domainsById = new Map(domains.map((domain) => [domain.data.id, domain]));
|
|
464
|
+
|
|
465
|
+
const toGlossary = (domain: Domain): DomainGlossary => ({
|
|
466
|
+
id: domain.data.id,
|
|
467
|
+
version: domain.data.version,
|
|
468
|
+
name: domain.data.name || domain.data.id,
|
|
469
|
+
terms: getUbiquitousLanguageFromCollection(domain, ubiquitousLanguages).flatMap(
|
|
470
|
+
(ubiquitousLanguage) => ubiquitousLanguage.data.dictionary || []
|
|
471
|
+
),
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Each domain is only listed once, whether several domains share it or domains point at each other
|
|
475
|
+
const listedDomains = new Set<string>();
|
|
476
|
+
|
|
477
|
+
const getDescendants = (parent: Domain): SubdomainGlossary[] =>
|
|
478
|
+
((parent.data.domains as unknown as Domain[]) || []).flatMap((subdomain) => {
|
|
479
|
+
if (listedDomains.has(subdomain.data.id)) return [];
|
|
480
|
+
listedDomains.add(subdomain.data.id);
|
|
481
|
+
|
|
482
|
+
// Use the current version even when the parent pins an old one: only current versions have glossaries,
|
|
483
|
+
// and their own subdomains resolved
|
|
484
|
+
const currentSubdomain = domainsById.get(subdomain.data.id) || subdomain;
|
|
485
|
+
|
|
486
|
+
return [{ ...toGlossary(currentSubdomain), parentId: parent.data.id }, ...getDescendants(currentSubdomain)];
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Root domains go first. Any domain still not listed after them points in a loop, so has no root.
|
|
490
|
+
return [...rootDomains, ...domains]
|
|
491
|
+
.flatMap((domain) => {
|
|
492
|
+
if (listedDomains.has(domain.data.id)) return [];
|
|
493
|
+
listedDomains.add(domain.data.id);
|
|
494
|
+
|
|
495
|
+
return [
|
|
496
|
+
{
|
|
497
|
+
...toGlossary(domain),
|
|
498
|
+
subdomains: getDescendants(domain).filter((subdomain) => subdomain.terms.length > 0),
|
|
499
|
+
},
|
|
500
|
+
];
|
|
501
|
+
})
|
|
502
|
+
.filter((domain) => domain.terms.length > 0 || domain.subdomains.length > 0);
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
/** The terms defined in more than one domain, keyed by their lowercase name */
|
|
506
|
+
export const getTermsDefinedInSeveralDomains = (glossary: OrganizationGlossaryDomain[]) => {
|
|
507
|
+
const definitions = new Map<string, { domain: DomainGlossary; term: UbiquitousLanguageTerm }[]>();
|
|
508
|
+
|
|
509
|
+
for (const domain of glossary.flatMap((domain) => [domain, ...domain.subdomains])) {
|
|
510
|
+
for (const term of domain.terms) {
|
|
511
|
+
const key = term.name.toLowerCase();
|
|
512
|
+
definitions.set(key, [...(definitions.get(key) || []), { domain, term }]);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return new Map([...definitions].filter(([, termDefinitions]) => new Set(termDefinitions.map((d) => d.domain.id)).size > 1));
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
/** A term's definition on one line, for the text files we generate for LLMs */
|
|
520
|
+
export const getTermSummary = (term: UbiquitousLanguageTerm) =>
|
|
521
|
+
(term.summary || term.description || '').replace(/\s+/g, ' ').trim();
|
|
522
|
+
|
|
439
523
|
export const getDomainsForService = async (service: Service): Promise<Domain[]> => {
|
|
440
524
|
const domains = await getDomains({ getAllVersions: false });
|
|
441
525
|
return domains.filter((d) => {
|