@eventcatalog/core 2.57.1 → 2.58.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-6PT76NQN.js → chunk-GKUPYLJL.js} +1 -1
- package/dist/{chunk-76HGJTBT.js → chunk-PAKFTVIV.js} +1 -1
- package/dist/{chunk-7BNIEBLU.js → chunk-TIETK4WY.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +3 -3
- package/eventcatalog/astro.config.mjs +6 -1
- package/eventcatalog/public/icons/graphql.svg +1 -0
- package/eventcatalog/src/components/Lists/PillListFlat.tsx +108 -27
- package/eventcatalog/src/components/Lists/SpecificationsList.astro +15 -0
- package/eventcatalog/src/components/MDX/Attachments.astro +158 -0
- package/eventcatalog/src/components/MDX/Tiles/Tile.astro +31 -15
- package/eventcatalog/src/components/MDX/Tiles/Tiles.astro +3 -3
- package/eventcatalog/src/components/MDX/components.tsx +2 -0
- package/eventcatalog/src/components/SideBars/ChannelSideBar.astro +25 -0
- package/eventcatalog/src/components/SideBars/DomainSideBar.astro +25 -0
- package/eventcatalog/src/components/SideBars/EntitySideBar.astro +24 -0
- package/eventcatalog/src/components/SideBars/FlowSideBar.astro +25 -0
- package/eventcatalog/src/components/SideBars/MessageSideBar.astro +25 -0
- package/eventcatalog/src/components/SideBars/ServiceSideBar.astro +25 -0
- package/eventcatalog/src/components/SideNav/ListViewSideBar/components/SpecificationList.tsx +20 -0
- package/eventcatalog/src/content.config.ts +20 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +177 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/_[filename].data.ts +98 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +43 -6
- package/package.json +2 -1
|
@@ -25,6 +25,8 @@ const subDomains = (domain.data.domains as CollectionEntry<'domains'>[]) || [];
|
|
|
25
25
|
// @ts-ignore
|
|
26
26
|
const entities = (domain.data.entities as CollectionEntry<'entities'>[]) || [];
|
|
27
27
|
|
|
28
|
+
const attachments = domain.data.attachments || [];
|
|
29
|
+
|
|
28
30
|
const ubiquitousLanguage = await getUbiquitousLanguage(domain);
|
|
29
31
|
const hasUbiquitousLanguage = ubiquitousLanguage.length > 0;
|
|
30
32
|
const ubiquitousLanguageDictionary = hasUbiquitousLanguage ? ubiquitousLanguage[0].data.dictionary : [];
|
|
@@ -106,6 +108,18 @@ const ownersList = filteredOwners.map((o) => ({
|
|
|
106
108
|
href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
|
|
107
109
|
}));
|
|
108
110
|
|
|
111
|
+
const attachmentsList = attachments.map((a) => {
|
|
112
|
+
const attachmentIsURL = typeof a === 'string';
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
label: attachmentIsURL ? a : (a.title ?? a.url),
|
|
116
|
+
href: attachmentIsURL ? a : a.url,
|
|
117
|
+
icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
|
|
118
|
+
target: '_blank' as const,
|
|
119
|
+
subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
|
|
109
123
|
const shouldRenderSideBarSection = (section: string) => {
|
|
110
124
|
if (!domain.data.detailsPanel) {
|
|
111
125
|
return true;
|
|
@@ -208,6 +222,17 @@ const shouldRenderSideBarSection = (section: string) => {
|
|
|
208
222
|
<VersionList versions={domain.data.versions} collectionItem={domain} />
|
|
209
223
|
)
|
|
210
224
|
}
|
|
225
|
+
{
|
|
226
|
+
domain.data.attachments && shouldRenderSideBarSection('attachments') && (
|
|
227
|
+
<PillListFlat
|
|
228
|
+
title={`Attachments (${attachmentsList.length})`}
|
|
229
|
+
pills={attachmentsList}
|
|
230
|
+
emptyMessage={`This domain does not have any attachments.`}
|
|
231
|
+
color="pink"
|
|
232
|
+
client:load
|
|
233
|
+
/>
|
|
234
|
+
)
|
|
235
|
+
}
|
|
211
236
|
{
|
|
212
237
|
domain.data.repository && shouldRenderSideBarSection('repository') && (
|
|
213
238
|
<RepositoryList repository={domain.data.repository?.url} language={domain.data.repository?.language} />
|
|
@@ -23,6 +23,19 @@ const services = (entity.data.services as CollectionEntry<'services'>[]) || [];
|
|
|
23
23
|
// @ts-ignore
|
|
24
24
|
const domains = (entity.data.domains as CollectionEntry<'domains'>[]) || [];
|
|
25
25
|
|
|
26
|
+
const attachments = entity.data.attachments || [];
|
|
27
|
+
|
|
28
|
+
const attachmentsList = attachments.map((a) => {
|
|
29
|
+
const attachmentIsURL = typeof a === 'string';
|
|
30
|
+
return {
|
|
31
|
+
label: attachmentIsURL ? a : (a.title ?? a.url),
|
|
32
|
+
href: attachmentIsURL ? a : a.url,
|
|
33
|
+
icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
|
|
34
|
+
target: '_blank' as const,
|
|
35
|
+
subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
|
|
26
39
|
const ownersList = filteredOwners.map((o) => ({
|
|
27
40
|
label: o.data.name,
|
|
28
41
|
type: o.collection,
|
|
@@ -87,6 +100,17 @@ const shouldRenderSideBarSection = (section: string) => {
|
|
|
87
100
|
<VersionList versions={entity.data.versions} collectionItem={entity} />
|
|
88
101
|
)
|
|
89
102
|
}
|
|
103
|
+
{
|
|
104
|
+
entity.data.attachments && shouldRenderSideBarSection('attachments') && (
|
|
105
|
+
<PillListFlat
|
|
106
|
+
title={`Attachments (${attachmentsList.length})`}
|
|
107
|
+
pills={attachmentsList}
|
|
108
|
+
emptyMessage={`This entity does not have any attachments.`}
|
|
109
|
+
color="pink"
|
|
110
|
+
client:load
|
|
111
|
+
/>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
90
114
|
{
|
|
91
115
|
filteredOwners.length > 0 && shouldRenderSideBarSection('owners') && (
|
|
92
116
|
<OwnersList
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import OwnersList from '@components/Lists/OwnersList';
|
|
3
3
|
import VersionList from '@components/Lists/VersionList.astro';
|
|
4
|
+
import PillListFlat from '@components/Lists/PillListFlat';
|
|
4
5
|
import { buildUrl } from '@utils/url-builder';
|
|
5
6
|
import { getOwner } from '@utils/collections/owners';
|
|
6
7
|
import type { CollectionEntry } from 'astro:content';
|
|
@@ -30,6 +31,19 @@ const ownersList = filteredOwners.map((o) => ({
|
|
|
30
31
|
|
|
31
32
|
const isRSSEnabled = config.rss?.enabled;
|
|
32
33
|
|
|
34
|
+
const attachments = flow.data.attachments || [];
|
|
35
|
+
|
|
36
|
+
const attachmentsList = attachments.map((a) => {
|
|
37
|
+
const attachmentIsURL = typeof a === 'string';
|
|
38
|
+
return {
|
|
39
|
+
label: attachmentIsURL ? a : (a.title ?? a.url),
|
|
40
|
+
href: attachmentIsURL ? a : a.url,
|
|
41
|
+
icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
|
|
42
|
+
target: '_blank' as const,
|
|
43
|
+
subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
33
47
|
const shouldRenderSideBarSection = (section: string) => {
|
|
34
48
|
if (!flow.data.detailsPanel) {
|
|
35
49
|
return true;
|
|
@@ -51,6 +65,17 @@ const shouldRenderSideBarSection = (section: string) => {
|
|
|
51
65
|
<VersionList versions={flow.data.versions} collectionItem={flow} />
|
|
52
66
|
)
|
|
53
67
|
}
|
|
68
|
+
{
|
|
69
|
+
flow.data.attachments && shouldRenderSideBarSection('attachments') && (
|
|
70
|
+
<PillListFlat
|
|
71
|
+
title={`Attachments (${attachmentsList.length})`}
|
|
72
|
+
pills={attachmentsList}
|
|
73
|
+
emptyMessage={`This flow does not have any attachments.`}
|
|
74
|
+
color="pink"
|
|
75
|
+
client:load
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
54
79
|
|
|
55
80
|
{
|
|
56
81
|
shouldRenderSideBarSection('owners') && (
|
|
@@ -29,6 +29,19 @@ const filteredOwners = owners.filter((o) => o !== undefined);
|
|
|
29
29
|
|
|
30
30
|
const resourceGroups = message.data?.resourceGroups || [];
|
|
31
31
|
|
|
32
|
+
const attachments = message.data.attachments || [];
|
|
33
|
+
|
|
34
|
+
const attachmentsList = attachments.map((a) => {
|
|
35
|
+
const attachmentIsURL = typeof a === 'string';
|
|
36
|
+
return {
|
|
37
|
+
label: attachmentIsURL ? a : (a.title ?? a.url),
|
|
38
|
+
href: attachmentIsURL ? a : a.url,
|
|
39
|
+
icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
|
|
40
|
+
target: '_blank' as const,
|
|
41
|
+
subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
|
|
32
45
|
const producerList = producers.map((p) => ({
|
|
33
46
|
label: `${p.data.name}`,
|
|
34
47
|
tag: `v${p.data.version}`,
|
|
@@ -153,6 +166,18 @@ const shouldRenderSideBarSection = (section: string) => {
|
|
|
153
166
|
)
|
|
154
167
|
}
|
|
155
168
|
|
|
169
|
+
{
|
|
170
|
+
message.data.attachments && shouldRenderSideBarSection('attachments') && (
|
|
171
|
+
<PillListFlat
|
|
172
|
+
title={`Attachments (${attachmentsList.length})`}
|
|
173
|
+
pills={attachmentsList}
|
|
174
|
+
emptyMessage={`This ${type} does not have any attachments.`}
|
|
175
|
+
color="pink"
|
|
176
|
+
client:load
|
|
177
|
+
/>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
156
181
|
{
|
|
157
182
|
ownersList.length > 0 && shouldRenderSideBarSection('owners') && (
|
|
158
183
|
<OwnersList
|
|
@@ -35,6 +35,19 @@ const resourceGroups = service.data?.resourceGroups || [];
|
|
|
35
35
|
|
|
36
36
|
const domainsServiceBelongsTo = await getDomainsForService(service);
|
|
37
37
|
|
|
38
|
+
const attachments = service.data.attachments || [];
|
|
39
|
+
|
|
40
|
+
const attachmentsList = attachments.map((a) => {
|
|
41
|
+
const attachmentIsURL = typeof a === 'string';
|
|
42
|
+
return {
|
|
43
|
+
label: attachmentIsURL ? a : (a.title ?? a.url),
|
|
44
|
+
href: attachmentIsURL ? a : a.url,
|
|
45
|
+
icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
|
|
46
|
+
target: '_blank' as const,
|
|
47
|
+
subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
38
51
|
const sendsList = sends
|
|
39
52
|
.sort((a, b) => a.collection.localeCompare(b.collection))
|
|
40
53
|
.map((p) => ({
|
|
@@ -141,6 +154,18 @@ const shouldRenderSideBarSection = (section: string) => {
|
|
|
141
154
|
)
|
|
142
155
|
}
|
|
143
156
|
|
|
157
|
+
{
|
|
158
|
+
service.data.attachments && shouldRenderSideBarSection('attachments') && (
|
|
159
|
+
<PillListFlat
|
|
160
|
+
title={`Attachments (${attachmentsList.length})`}
|
|
161
|
+
pills={attachmentsList}
|
|
162
|
+
emptyMessage={`This service does not have any attachments.`}
|
|
163
|
+
color="pink"
|
|
164
|
+
client:load
|
|
165
|
+
/>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
144
169
|
{
|
|
145
170
|
service.data.specifications && shouldRenderSideBarSection('specifications') && (
|
|
146
171
|
<SpecificationsList collectionItem={service} />
|
package/eventcatalog/src/components/SideNav/ListViewSideBar/components/SpecificationList.tsx
CHANGED
|
@@ -17,6 +17,7 @@ interface SpecificationListProps {
|
|
|
17
17
|
const SpecificationList: React.FC<SpecificationListProps> = ({ specifications, id, version }) => {
|
|
18
18
|
const asyncAPISpecifications = specifications.filter((spec) => spec.type === 'asyncapi');
|
|
19
19
|
const openAPISpecifications = specifications.filter((spec) => spec.type === 'openapi');
|
|
20
|
+
const graphQLSpecifications = specifications.filter((spec) => spec.type === 'graphql');
|
|
20
21
|
|
|
21
22
|
return (
|
|
22
23
|
<ul className="space-y-0.5 border-l border-gray-200/80 ml-[9px] pl-4">
|
|
@@ -56,6 +57,25 @@ const SpecificationList: React.FC<SpecificationListProps> = ({ specifications, i
|
|
|
56
57
|
</span>
|
|
57
58
|
</a>
|
|
58
59
|
))}
|
|
60
|
+
{graphQLSpecifications &&
|
|
61
|
+
graphQLSpecifications.length > 0 &&
|
|
62
|
+
graphQLSpecifications.map((spec) => (
|
|
63
|
+
<a
|
|
64
|
+
key={`${spec.name}-openapi`}
|
|
65
|
+
href={buildUrl(`/docs/services/${id}/${version}/graphql/${spec.filenameWithoutExtension}`)}
|
|
66
|
+
data-active={window.location.href.includes(`docs/services/${id}/${version}/graphql/${spec.filenameWithoutExtension}`)}
|
|
67
|
+
className={`items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md flex justify-between ${
|
|
68
|
+
window.location.href.includes(`docs/services/${id}/${version}/graphql/${spec.filenameWithoutExtension}`)
|
|
69
|
+
? 'bg-purple-100'
|
|
70
|
+
: 'hover:bg-purple-100'
|
|
71
|
+
}`}
|
|
72
|
+
>
|
|
73
|
+
<span className="truncate flex items-center gap-1">{spec.name}</span>
|
|
74
|
+
<span className="text-green-600 ml-2 text-[10px] uppercase font-medium bg-gray-50 px-4 py-0.5 rounded">
|
|
75
|
+
<img src={buildUrl('/icons/graphql.svg', true)} className="w-4 h-4" alt="GraphQL" />
|
|
76
|
+
</span>
|
|
77
|
+
</a>
|
|
78
|
+
))}
|
|
59
79
|
</ul>
|
|
60
80
|
);
|
|
61
81
|
};
|
|
@@ -103,10 +103,11 @@ const baseSchema = z.object({
|
|
|
103
103
|
z.object({
|
|
104
104
|
openapiPath: z.string().optional(),
|
|
105
105
|
asyncapiPath: z.string().optional(),
|
|
106
|
+
graphqlPath: z.string().optional(),
|
|
106
107
|
}),
|
|
107
108
|
z.array(
|
|
108
109
|
z.object({
|
|
109
|
-
type: z.enum(['openapi', 'asyncapi']),
|
|
110
|
+
type: z.enum(['openapi', 'asyncapi', 'graphql']),
|
|
110
111
|
path: z.string(),
|
|
111
112
|
name: z.string().optional(),
|
|
112
113
|
})
|
|
@@ -147,6 +148,20 @@ const baseSchema = z.object({
|
|
|
147
148
|
])
|
|
148
149
|
.optional(),
|
|
149
150
|
visualiser: z.boolean().optional(),
|
|
151
|
+
attachments: z
|
|
152
|
+
.array(
|
|
153
|
+
z.union([
|
|
154
|
+
z.string().url(), // simple case
|
|
155
|
+
z.object({
|
|
156
|
+
url: z.string().url(),
|
|
157
|
+
title: z.string().optional(),
|
|
158
|
+
type: z.string().optional(), // e.g. "architecture-record", "diagram"
|
|
159
|
+
description: z.string().optional(),
|
|
160
|
+
icon: z.string().optional(),
|
|
161
|
+
}),
|
|
162
|
+
])
|
|
163
|
+
)
|
|
164
|
+
.optional(),
|
|
150
165
|
// Used by eventcatalog
|
|
151
166
|
versions: z.array(z.string()).optional(),
|
|
152
167
|
latestVersion: z.string().optional(),
|
|
@@ -259,6 +274,7 @@ const messageDetailsPanelPropertySchema = z.object({
|
|
|
259
274
|
repository: detailPanelPropertySchema.optional(),
|
|
260
275
|
owners: detailPanelPropertySchema.optional(),
|
|
261
276
|
changelog: detailPanelPropertySchema.optional(),
|
|
277
|
+
attachments: detailPanelPropertySchema.optional(),
|
|
262
278
|
});
|
|
263
279
|
|
|
264
280
|
const events = defineCollection({
|
|
@@ -411,6 +427,7 @@ const domains = defineCollection({
|
|
|
411
427
|
versions: detailPanelPropertySchema.optional(),
|
|
412
428
|
owners: detailPanelPropertySchema.optional(),
|
|
413
429
|
changelog: detailPanelPropertySchema.optional(),
|
|
430
|
+
attachments: detailPanelPropertySchema.optional(),
|
|
414
431
|
})
|
|
415
432
|
.optional(),
|
|
416
433
|
})
|
|
@@ -451,6 +468,7 @@ const channels = defineCollection({
|
|
|
451
468
|
repository: detailPanelPropertySchema.optional(),
|
|
452
469
|
owners: detailPanelPropertySchema.optional(),
|
|
453
470
|
changelog: detailPanelPropertySchema.optional(),
|
|
471
|
+
attachments: detailPanelPropertySchema.optional(),
|
|
454
472
|
})
|
|
455
473
|
.optional(),
|
|
456
474
|
})
|
|
@@ -522,6 +540,7 @@ const entities = defineCollection({
|
|
|
522
540
|
versions: detailPanelPropertySchema.optional(),
|
|
523
541
|
owners: detailPanelPropertySchema.optional(),
|
|
524
542
|
changelog: detailPanelPropertySchema.optional(),
|
|
543
|
+
attachments: detailPanelPropertySchema.optional(),
|
|
525
544
|
})
|
|
526
545
|
.optional(),
|
|
527
546
|
})
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Code } from 'astro-expressive-code/components';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
|
|
5
|
+
import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
6
|
+
import Footer from '@layouts/Footer.astro';
|
|
7
|
+
import { Page } from './_[filename].data';
|
|
8
|
+
import { getAbsoluteFilePathForAstroFile } from '@utils/files';
|
|
9
|
+
import { buildUrl, buildEditUrlForResource } from '@utils/url-builder';
|
|
10
|
+
import Admonition from '@components/MDX/Admonition';
|
|
11
|
+
|
|
12
|
+
import { ServerIcon } from '@heroicons/react/24/outline';
|
|
13
|
+
|
|
14
|
+
export const prerender = Page.prerender;
|
|
15
|
+
export const getStaticPaths = Page.getStaticPaths;
|
|
16
|
+
|
|
17
|
+
// Get data
|
|
18
|
+
const { collection, data, filePath, filename } = await Page.getData(Astro);
|
|
19
|
+
|
|
20
|
+
const fileName = filename || 'schema.graphql';
|
|
21
|
+
const pathToSpec = getAbsoluteFilePathForAstroFile(filePath, fileName);
|
|
22
|
+
const fileExists = fs.existsSync(pathToSpec);
|
|
23
|
+
let content = '';
|
|
24
|
+
|
|
25
|
+
if (fileExists) {
|
|
26
|
+
content = fs.readFileSync(pathToSpec, 'utf8');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create comprehensive page title
|
|
30
|
+
const pageTitle = `${collection} | ${data.name} | GraphQL Schema`.replace(/^\w/, (c) => c.toUpperCase());
|
|
31
|
+
|
|
32
|
+
const getServiceBadge = () => {
|
|
33
|
+
return [{ backgroundColor: 'pink', textColor: 'pink', content: 'Service', icon: ServerIcon, class: 'text-pink-400' }];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getGraphQLBadge = () => {
|
|
37
|
+
return [
|
|
38
|
+
{
|
|
39
|
+
backgroundColor: 'white',
|
|
40
|
+
textColor: 'gray',
|
|
41
|
+
content: 'GraphQL Schema',
|
|
42
|
+
iconURL: buildUrl('/icons/graphql.svg', true),
|
|
43
|
+
class: 'text-black',
|
|
44
|
+
id: 'graphql-schema-badge',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const badges = [...getServiceBadge(), ...getGraphQLBadge()];
|
|
50
|
+
|
|
51
|
+
// Index only the latest version
|
|
52
|
+
const pagefindAttributes =
|
|
53
|
+
data.version === data.latestVersion
|
|
54
|
+
? {
|
|
55
|
+
'data-pagefind-body': '',
|
|
56
|
+
'data-pagefind-meta': `title:${pageTitle}`,
|
|
57
|
+
}
|
|
58
|
+
: {};
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
<VerticalSideBarLayout title={pageTitle} description={`GraphQL schema for ${data.name}`}>
|
|
62
|
+
<main class="flex sm:px-8 docs-layout h-full" {...pagefindAttributes}>
|
|
63
|
+
<div class="flex docs-layout w-full">
|
|
64
|
+
<div class="w-full lg:mr-2 pr-8 overflow-y-auto py-8">
|
|
65
|
+
<div class="border-b border-gray-200 md:pb-4">
|
|
66
|
+
<div>
|
|
67
|
+
<div class="flex justify-between items-start">
|
|
68
|
+
<div class="flex-1">
|
|
69
|
+
<h1 class="text-2xl md:text-4xl font-bold text-black mb-2">
|
|
70
|
+
{data.name}
|
|
71
|
+
<span class="text-gray-900">(v{data.version})</span>
|
|
72
|
+
</h1>
|
|
73
|
+
<h2 class="text-lg text-gray-600 font-medium mb-1">GraphQL Schema</h2>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{
|
|
78
|
+
badges && (
|
|
79
|
+
<div class="flex flex-wrap gap-3 py-4">
|
|
80
|
+
{badges.map((badge: any) => (
|
|
81
|
+
<span
|
|
82
|
+
id={badge.id || ''}
|
|
83
|
+
class={`
|
|
84
|
+
inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium
|
|
85
|
+
bg-${badge.backgroundColor || 'white'}-50 border border-${badge.backgroundColor || 'gray'}-200
|
|
86
|
+
text-${badge.textColor || 'gray'}-700 shadow-sm
|
|
87
|
+
transition-all duration-200 ease-out
|
|
88
|
+
${badge.class ? badge.class : ''}
|
|
89
|
+
`}
|
|
90
|
+
>
|
|
91
|
+
{badge.icon && <badge.icon className={`w-4 h-4 flex-shrink-0 text-${badge.textColor || 'gray'}-600`} />}
|
|
92
|
+
{badge.iconURL && <img src={badge.iconURL} class="w-4 h-4 flex-shrink-0 opacity-80" alt="" />}
|
|
93
|
+
<span>{badge.content}</span>
|
|
94
|
+
</span>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div data-pagefind-ignore>
|
|
103
|
+
{
|
|
104
|
+
data.version !== data.latestVersion && (
|
|
105
|
+
<div class="rounded-md bg-gradient-to-r from-purple-50 to-purple-100 p-4 not-prose my-4">
|
|
106
|
+
<div class="flex">
|
|
107
|
+
<div class="flex-shrink-0">
|
|
108
|
+
<svg class="h-5 w-5 text-purple-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
109
|
+
<path
|
|
110
|
+
fill-rule="evenodd"
|
|
111
|
+
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
|
|
112
|
+
clip-rule="evenodd"
|
|
113
|
+
/>
|
|
114
|
+
</svg>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="ml-3">
|
|
117
|
+
<h3 class="text-sm font-medium text-purple-800">New version found</h3>
|
|
118
|
+
<div class="mt-2 text-sm text-purple-700">
|
|
119
|
+
<p>
|
|
120
|
+
You are looking at a previous version of the service <strong>{data.name}</strong>.{' '}
|
|
121
|
+
<a
|
|
122
|
+
class="underline hover:text-primary block pt-2"
|
|
123
|
+
href={buildUrl(`/docs/${collection}/${data.id}/${data.latestVersion}/graphql/${filename}`)}
|
|
124
|
+
>
|
|
125
|
+
The latest version of this GraphQL schema is
|
|
126
|
+
<span>v{data.latestVersion}</span> →
|
|
127
|
+
</a>
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{
|
|
138
|
+
!fileExists && (
|
|
139
|
+
<Admonition type="warning" title="Schema not found">
|
|
140
|
+
<p>The GraphQL schema file could not be found at the expected location.</p>
|
|
141
|
+
</Admonition>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
{
|
|
146
|
+
fileExists && content && (
|
|
147
|
+
<div class="mt-6">
|
|
148
|
+
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
|
149
|
+
<div class="flex items-center gap-2 mb-2">
|
|
150
|
+
<img src={buildUrl('/icons/graphql.svg', true)} class="w-5 h-5" alt="GraphQL" />
|
|
151
|
+
<h3 class="text-lg font-semibold text-gray-800">GraphQL Schema</h3>
|
|
152
|
+
</div>
|
|
153
|
+
<p class="text-sm text-gray-600">
|
|
154
|
+
This schema defines the GraphQL API structure including types, queries, mutations, and subscriptions for{' '}
|
|
155
|
+
{data.name}.
|
|
156
|
+
</p>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div class="not-prose overflow-x-auto">
|
|
160
|
+
<Code code={content} title={`${data.name} v${data.version} - Schema`} lang="graphql" />
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
<Footer />
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</main>
|
|
170
|
+
|
|
171
|
+
<style is:global>
|
|
172
|
+
.docs-layout .prose {
|
|
173
|
+
max-width: none;
|
|
174
|
+
overflow: auto;
|
|
175
|
+
}
|
|
176
|
+
</style>
|
|
177
|
+
</VerticalSideBarLayout>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// pages/docs/[type]/[id]/[version]/asyncapi/[filename]/index.page.ts
|
|
2
|
+
import { isSSR, isAuthEnabled } from '@utils/feature';
|
|
3
|
+
import { HybridPage } from '@utils/page-loaders/hybrid-page';
|
|
4
|
+
import type { CollectionEntry } from 'astro:content';
|
|
5
|
+
import type { CollectionTypes, PageTypes } from '@types';
|
|
6
|
+
|
|
7
|
+
export class Page extends HybridPage {
|
|
8
|
+
static get prerender(): boolean {
|
|
9
|
+
return !isSSR();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static async getStaticPaths(): Promise<Array<{ params: any; props: any }>> {
|
|
13
|
+
if (isSSR()) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { pageDataLoader } = await import('@utils/page-loaders/page-data-loader');
|
|
18
|
+
const { getSpecificationsForService } = await import('@utils/collections/services');
|
|
19
|
+
|
|
20
|
+
const itemTypes: PageTypes[] = ['events', 'commands', 'queries', 'services', 'domains'];
|
|
21
|
+
const allItems = await Promise.all(itemTypes.map((type) => pageDataLoader[type]()));
|
|
22
|
+
|
|
23
|
+
const hasSpecifications = (item: CollectionEntry<CollectionTypes>) => {
|
|
24
|
+
const specifications = getSpecificationsForService(item);
|
|
25
|
+
// Ensure there is at least one 'asyncapi' specification
|
|
26
|
+
return specifications && specifications.some((spec) => spec.type === 'graphql');
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const filteredItems = allItems.map((items) => items.filter(hasSpecifications));
|
|
30
|
+
|
|
31
|
+
return filteredItems.flatMap((items, index) =>
|
|
32
|
+
items.flatMap((item) => {
|
|
33
|
+
const asyncApiSpecifications = getSpecificationsForService(item).filter((spec) => spec.type === 'graphql');
|
|
34
|
+
|
|
35
|
+
return asyncApiSpecifications.map((spec) => ({
|
|
36
|
+
params: {
|
|
37
|
+
type: itemTypes[index],
|
|
38
|
+
id: item.data.id,
|
|
39
|
+
version: item.data.version,
|
|
40
|
+
filename: spec.filenameWithoutExtension || spec.type,
|
|
41
|
+
},
|
|
42
|
+
props: {
|
|
43
|
+
type: itemTypes[index],
|
|
44
|
+
filenameWithoutExtension: spec.filenameWithoutExtension || spec.type,
|
|
45
|
+
filename: spec.filename || spec.type,
|
|
46
|
+
...item,
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected static async fetchData(params: any) {
|
|
54
|
+
const { type, id, version, filename } = params;
|
|
55
|
+
|
|
56
|
+
if (!type || !id || !version || !filename) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { pageDataLoader } = await import('@utils/page-loaders/page-data-loader');
|
|
61
|
+
const { getSpecificationsForService } = await import('@utils/collections/services');
|
|
62
|
+
|
|
63
|
+
// Get all items of the specified type
|
|
64
|
+
const items = await pageDataLoader[type as PageTypes]();
|
|
65
|
+
|
|
66
|
+
// Find the specific item by id and version
|
|
67
|
+
const item = items.find((i) => i.data.id === id && i.data.version === version);
|
|
68
|
+
|
|
69
|
+
if (!item) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check if this item has AsyncAPI specifications
|
|
74
|
+
const specifications = getSpecificationsForService(item);
|
|
75
|
+
const asyncApiSpecifications = specifications.filter((spec) => spec.type === 'graphql');
|
|
76
|
+
|
|
77
|
+
// Find the specific specification
|
|
78
|
+
const spec = asyncApiSpecifications.find((s) => (s.filenameWithoutExtension || s.type) === filename);
|
|
79
|
+
|
|
80
|
+
if (!spec) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
type,
|
|
86
|
+
filenameWithoutExtension: spec.filenameWithoutExtension || spec.type,
|
|
87
|
+
filename: spec.filename || spec.type,
|
|
88
|
+
...item,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected static createNotFoundResponse(): Response {
|
|
93
|
+
return new Response(null, {
|
|
94
|
+
status: 404,
|
|
95
|
+
statusText: 'GraphQL specification not found',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -125,6 +125,7 @@ const getSpecificationBadges = () => {
|
|
|
125
125
|
|
|
126
126
|
const asyncapiSpecs = specifications.filter((spec) => spec.type === 'asyncapi');
|
|
127
127
|
const openapiSpecs = specifications.filter((spec) => spec.type === 'openapi');
|
|
128
|
+
const graphQLSpecs = specifications.filter((spec) => spec.type === 'graphql');
|
|
128
129
|
|
|
129
130
|
if (openapiSpecs.length > 0) {
|
|
130
131
|
for (const spec of openapiSpecs) {
|
|
@@ -156,6 +157,22 @@ const getSpecificationBadges = () => {
|
|
|
156
157
|
}
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
if (graphQLSpecs.length > 0) {
|
|
161
|
+
for (const spec of graphQLSpecs) {
|
|
162
|
+
badges.push({
|
|
163
|
+
backgroundColor: 'white',
|
|
164
|
+
textColor: 'gray',
|
|
165
|
+
content: spec.name || 'GraphQL Spec',
|
|
166
|
+
iconURL: buildUrl('/icons/graphql.svg', true),
|
|
167
|
+
class: 'text-black hover:underline',
|
|
168
|
+
id: 'graphql-badge',
|
|
169
|
+
url: buildUrl(
|
|
170
|
+
`/docs/${props.collection}/${props.data.id}/${props.data.version}/graphql/${spec.filenameWithoutExtension}`
|
|
171
|
+
),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
159
176
|
return badges;
|
|
160
177
|
};
|
|
161
178
|
|
|
@@ -267,17 +284,37 @@ nodeGraphs.push({
|
|
|
267
284
|
<h2 class="text-lg pt-2 text-gray-500 font-light">{props.data.summary}</h2>
|
|
268
285
|
{
|
|
269
286
|
badges && (
|
|
270
|
-
<div class="flex flex-wrap
|
|
287
|
+
<div class="flex flex-wrap gap-3 py-4">
|
|
271
288
|
{badges.map((badge: any) => {
|
|
272
289
|
return (
|
|
273
|
-
<a href={badge.url || '#'} class="
|
|
290
|
+
<a href={badge.url || '#'} class="group transition-all duration-200 hover:scale-105">
|
|
274
291
|
<span
|
|
275
292
|
id={badge.id || ''}
|
|
276
|
-
class={`
|
|
293
|
+
class={`
|
|
294
|
+
inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium
|
|
295
|
+
bg-${badge.backgroundColor || 'white'}-50 border border-${badge.backgroundColor || 'gray'}-200
|
|
296
|
+
text-${badge.textColor || 'gray'}-700 shadow-sm
|
|
297
|
+
hover:bg-${badge.backgroundColor || 'purple'}-100 hover:border-${badge.backgroundColor || 'purple'}-300
|
|
298
|
+
hover:shadow-md hover:text-${badge.textColor || 'purple'}-800
|
|
299
|
+
transition-all duration-200 ease-out
|
|
300
|
+
${badge.class ? badge.class : ''}
|
|
301
|
+
`}
|
|
277
302
|
>
|
|
278
|
-
{badge.icon &&
|
|
279
|
-
|
|
280
|
-
|
|
303
|
+
{badge.icon && (
|
|
304
|
+
<badge.icon
|
|
305
|
+
className={`w-4 h-4 flex-shrink-0 text-${badge.textColor || 'gray'}-600 group-hover:text-${badge.textColor || 'purple'}-700 transition-colors duration-200`}
|
|
306
|
+
/>
|
|
307
|
+
)}
|
|
308
|
+
{badge.iconURL && (
|
|
309
|
+
<img
|
|
310
|
+
src={badge.iconURL}
|
|
311
|
+
class="w-4 h-4 flex-shrink-0 opacity-80 group-hover:opacity-100 transition-opacity duration-200"
|
|
312
|
+
alt=""
|
|
313
|
+
/>
|
|
314
|
+
)}
|
|
315
|
+
<span class={`group-hover:text-${badge.textColor || 'purple'}-800 transition-colors duration-200`}>
|
|
316
|
+
{badge.content}
|
|
317
|
+
</span>
|
|
281
318
|
</span>
|
|
282
319
|
</a>
|
|
283
320
|
);
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"url": "https://github.com/event-catalog/eventcatalog.git"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.58.0",
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
@@ -129,6 +129,7 @@
|
|
|
129
129
|
"prettier": "^3.3.3",
|
|
130
130
|
"prettier-plugin-astro": "^0.14.1",
|
|
131
131
|
"tsup": "^8.1.0",
|
|
132
|
+
"vite": "^7.1.7",
|
|
132
133
|
"vite-tsconfig-paths": "^4.3.2",
|
|
133
134
|
"vitest": "2.1.6"
|
|
134
135
|
},
|