@eventcatalog/core 4.0.6 → 4.0.7

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.
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(analytics_exports);
36
36
  var import_os = __toESM(require("os"), 1);
37
37
 
38
38
  // package.json
39
- var version = "4.0.6";
39
+ var version = "4.0.7";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-FHVBLKWN.js";
4
- import "../chunk-Z7BGWB5J.js";
3
+ } from "../chunk-WDA5QYVD.js";
4
+ import "../chunk-RBBWHWE4.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -140,7 +140,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
140
140
  var import_os = __toESM(require("os"), 1);
141
141
 
142
142
  // package.json
143
- var version = "4.0.6";
143
+ var version = "4.0.7";
144
144
 
145
145
  // src/constants.ts
146
146
  var VERSION = version;
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-ZYWUN3T3.js";
4
- import "../chunk-FHVBLKWN.js";
3
+ } from "../chunk-ARHL5SPG.js";
4
+ import "../chunk-WDA5QYVD.js";
5
5
  import "../chunk-DAOXTQVS.js";
6
- import "../chunk-Z7BGWB5J.js";
6
+ import "../chunk-RBBWHWE4.js";
7
7
  import "../chunk-6QENHZZP.js";
8
8
  export {
9
9
  log_build_default as default
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-FHVBLKWN.js";
3
+ } from "./chunk-WDA5QYVD.js";
4
4
  import {
5
5
  countResources,
6
6
  serializeCounts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-Z7BGWB5J.js";
3
+ } from "./chunk-RBBWHWE4.js";
4
4
 
5
5
  // src/utils/cli-logger.ts
6
6
  import pc from "picocolors";
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "4.0.6";
2
+ var version = "4.0.7";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-Z7BGWB5J.js";
3
+ } from "./chunk-RBBWHWE4.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import os from "os";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-ZXAF4JO7.js";
3
+ } from "./chunk-IMCMPLIJ.js";
4
4
  import {
5
5
  cleanup,
6
6
  getEventCatalogConfigFile
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "4.0.6";
28
+ var version = "4.0.7";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-Z7BGWB5J.js";
3
+ } from "./chunk-RBBWHWE4.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -144,7 +144,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
144
144
  var import_picocolors = __toESM(require("picocolors"), 1);
145
145
 
146
146
  // package.json
147
- var version = "4.0.6";
147
+ var version = "4.0.7";
148
148
 
149
149
  // src/constants.ts
150
150
  var VERSION = version;
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  log_build_default
3
- } from "./chunk-ZYWUN3T3.js";
3
+ } from "./chunk-ARHL5SPG.js";
4
4
  import {
5
5
  runMigrations
6
6
  } from "./chunk-XUAF2H54.js";
7
7
  import "./chunk-CA4U2JP7.js";
8
8
  import {
9
9
  generate
10
- } from "./chunk-7443A7LZ.js";
10
+ } from "./chunk-ZOFYLCPF.js";
11
11
  import {
12
12
  logger
13
- } from "./chunk-ZXAF4JO7.js";
13
+ } from "./chunk-IMCMPLIJ.js";
14
14
  import {
15
15
  resolve_catalog_dependencies_default
16
16
  } from "./chunk-LHR4G2UO.js";
@@ -20,7 +20,7 @@ import {
20
20
  import {
21
21
  watch
22
22
  } from "./chunk-O3Y2G6CY.js";
23
- import "./chunk-FHVBLKWN.js";
23
+ import "./chunk-WDA5QYVD.js";
24
24
  import "./chunk-DAOXTQVS.js";
25
25
  import {
26
26
  catalogToAstro
@@ -28,7 +28,7 @@ import {
28
28
  import "./chunk-VC6VUJIX.js";
29
29
  import {
30
30
  VERSION
31
- } from "./chunk-Z7BGWB5J.js";
31
+ } from "./chunk-RBBWHWE4.js";
32
32
  import {
33
33
  linkCoreNodeModules,
34
34
  resolveInstalledCoreNodeModules
package/dist/generate.cjs CHANGED
@@ -108,7 +108,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
108
108
  var import_picocolors = __toESM(require("picocolors"), 1);
109
109
 
110
110
  // package.json
111
- var version = "4.0.6";
111
+ var version = "4.0.7";
112
112
 
113
113
  // src/constants.ts
114
114
  var VERSION = version;
package/dist/generate.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  generate
3
- } from "./chunk-7443A7LZ.js";
4
- import "./chunk-ZXAF4JO7.js";
5
- import "./chunk-Z7BGWB5J.js";
3
+ } from "./chunk-ZOFYLCPF.js";
4
+ import "./chunk-IMCMPLIJ.js";
5
+ import "./chunk-RBBWHWE4.js";
6
6
  import "./chunk-6QENHZZP.js";
7
7
  export {
8
8
  generate
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(cli_logger_exports);
36
36
  var import_picocolors = __toESM(require("picocolors"), 1);
37
37
 
38
38
  // package.json
39
- var version = "4.0.6";
39
+ var version = "4.0.7";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  logger
3
- } from "../chunk-ZXAF4JO7.js";
4
- import "../chunk-Z7BGWB5J.js";
3
+ } from "../chunk-IMCMPLIJ.js";
4
+ import "../chunk-RBBWHWE4.js";
5
5
  export {
6
6
  logger
7
7
  };
@@ -1,12 +1,14 @@
1
1
  import { useEffect, useRef, useCallback, useState, useMemo, memo } from 'react';
2
2
  import { X, Square, Trash2, BookOpen, Copy, Check, Maximize2, Minimize2, Wrench, ChevronDown, MessageSquare } from 'lucide-react';
3
3
  import { useChat } from '@ai-sdk/react';
4
+ import { DefaultChatTransport } from 'ai';
4
5
  import ReactMarkdown from 'react-markdown';
5
6
  import remarkGfm from 'remark-gfm';
6
7
  import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
7
8
  import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
8
9
  import * as Dialog from '@radix-ui/react-dialog';
9
10
  import * as Popover from '@radix-ui/react-popover';
11
+ import { buildUrl } from '@utils/url-builder';
10
12
 
11
13
  interface ToolMetadata {
12
14
  name: string;
@@ -527,6 +529,8 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
527
529
  const [isFullscreen, setIsFullscreen] = useState(false);
528
530
  const [tools, setTools] = useState<ToolMetadata[]>([]);
529
531
  const [showScrollButton, setShowScrollButton] = useState(false);
532
+ const chatApiUrl = useMemo(() => buildUrl('/api/chat', true), []);
533
+ const chatTransport = useMemo(() => new DefaultChatTransport({ api: chatApiUrl }), [chatApiUrl]);
530
534
 
531
535
  // Sort tools with custom ones first
532
536
  const sortedTools = useMemo(() => {
@@ -540,7 +544,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
540
544
  // Fetch available tools when panel opens
541
545
  useEffect(() => {
542
546
  if (isOpen && tools.length === 0) {
543
- fetch('/api/chat')
547
+ fetch(chatApiUrl)
544
548
  .then((res) => res.json())
545
549
  .then((data) => {
546
550
  if (data.tools) {
@@ -597,7 +601,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
597
601
  setShowScrollButton(!isNearBottom);
598
602
  }, []);
599
603
 
600
- const { messages, sendMessage, stop, status, setMessages, error } = useChat();
604
+ const { messages, sendMessage, stop, status, setMessages, error } = useChat({ transport: chatTransport });
601
605
 
602
606
  // Extract user-friendly error message
603
607
  const errorMessage = error?.message || 'Something went wrong. Please try again.';
@@ -4,6 +4,7 @@ import JSONSchemaViewer from '@components/SchemaExplorer/JSONSchemaViewer';
4
4
  import { Code } from 'astro-expressive-code/components';
5
5
  import { isPrivateRemoteSchemaEnabled } from '@utils/feature';
6
6
  import { resolveTemplateVariables } from '@utils/remote-file';
7
+ import { resolveSchemaRefs } from '@utils/json-schema-refs';
7
8
 
8
9
  const {
9
10
  url,
@@ -89,6 +90,10 @@ try {
89
90
  if (isValidJSON(content)) {
90
91
  contentType = 'json';
91
92
  processedData = JSON.parse(content);
93
+ processedData = await resolveSchemaRefs(processedData, {
94
+ baseUrl: String(resolvedUrl),
95
+ headers: resolvedHeaders as HeadersInit,
96
+ });
92
97
 
93
98
  // Check if it's a JSON Schema
94
99
  isSchema = isJSONSchema(processedData);
@@ -122,10 +122,10 @@ export default function DiffViewer({ diffs, onOpenFullscreen, apiAccessEnabled =
122
122
  {isDark && <style dangerouslySetInnerHTML={{ __html: DIFF_DARK_STYLES }} />}
123
123
  <div className="mb-5 flex items-start justify-between">
124
124
  <div className="flex-1">
125
- <h3 className="text-base font-semibold text-[rgb(var(--ec-page-text))] mb-1">Version History</h3>
125
+ <h3 className="text-base font-semibold text-[rgb(var(--ec-page-text))] mb-1">Version Comparison</h3>
126
126
  <p className="text-sm text-[rgb(var(--ec-page-text-muted))]">
127
127
  {apiAccessEnabled
128
- ? `${diffs.length} version comparison${diffs.length !== 1 ? 's' : ''}`
128
+ ? `${diffs.length} selected comparison${diffs.length !== 1 ? 's' : ''}`
129
129
  : 'Compare schema versions side-by-side'}
130
130
  </p>
131
131
  </div>
@@ -142,21 +142,19 @@ export default function DiffViewer({ diffs, onOpenFullscreen, apiAccessEnabled =
142
142
  </div>
143
143
  {apiAccessEnabled ? (
144
144
  <div className={`space-y-6 ${isDark ? 'diff-dark-mode' : ''}`}>
145
- {diffs.map((diff, index) => (
145
+ {diffs.map((diff) => (
146
146
  <div
147
- key={`${diff.newerVersion}-${diff.olderVersion}`}
147
+ key={`${diff.fromVersion}-${diff.toVersion}`}
148
148
  className="border border-[rgb(var(--ec-page-border))] rounded-lg overflow-hidden"
149
149
  >
150
150
  <div className="bg-[rgb(var(--ec-content-hover))] border-b border-[rgb(var(--ec-page-border))] px-4 py-2.5">
151
151
  <div className="flex items-center justify-between">
152
152
  <div className="text-sm">
153
- <span className="font-semibold text-[rgb(var(--ec-page-text))]">v{diff.newerVersion}</span>
153
+ <span className="font-semibold text-[rgb(var(--ec-page-text))]">v{diff.fromVersion}</span>
154
154
  <span className="text-[rgb(var(--ec-page-text-muted))] mx-2">&rarr;</span>
155
- <span className="font-semibold text-[rgb(var(--ec-page-text))]">v{diff.olderVersion}</span>
155
+ <span className="font-semibold text-[rgb(var(--ec-page-text))]">v{diff.toVersion}</span>
156
156
  </div>
157
- <span className="text-xs text-[rgb(var(--ec-page-text-muted))]">
158
- {index === 0 ? 'Latest change' : `${index + 1} version${index + 1 !== 1 ? 's' : ''} ago`}
159
- </span>
157
+ <span className="text-xs text-[rgb(var(--ec-page-text-muted))]">Selected comparison</span>
160
158
  </div>
161
159
  </div>
162
160
  <div className="relative">
@@ -64,43 +64,70 @@ export default function SchemaDetailsPanel({
64
64
  const consumers = message.data.consumers || [];
65
65
  const filename = message.data.schemaPath?.split('/').pop() || `${message.data.id}.${ext || 'json'}`;
66
66
 
67
- // Generate diffs between all consecutive versions
68
- const allDiffs: VersionDiff[] = useMemo(() => {
69
- const diffs: VersionDiff[] = [];
70
- if (!hasMultipleVersions) return diffs;
71
-
72
- for (let i = 0; i < availableVersions.length - 1; i++) {
73
- const newerVersion = availableVersions[i];
74
- const olderVersion = availableVersions[i + 1];
75
-
76
- if (newerVersion.schemaContent && olderVersion.schemaContent) {
77
- const diff = Diff.createTwoFilesPatch(
78
- `v${olderVersion.data.version}`,
79
- `v${newerVersion.data.version}`,
80
- olderVersion.schemaContent,
81
- newerVersion.schemaContent,
82
- '',
83
- '',
84
- { context: 3 }
85
- );
86
-
87
- const diffHtml = html(diff, {
88
- drawFileList: false,
89
- matching: 'lines',
90
- outputFormat: 'side-by-side',
91
- });
92
-
93
- diffs.push({
94
- newerVersion: newerVersion.data.version,
95
- olderVersion: olderVersion.data.version,
96
- diffHtml,
97
- newerContent: newerVersion.schemaContent,
98
- olderContent: olderVersion.schemaContent,
99
- });
100
- }
101
- }
102
- return diffs;
103
- }, [availableVersions]);
67
+ const uniqueAvailableVersions = useMemo(
68
+ () =>
69
+ availableVersions.filter(
70
+ (version, index, versions) => versions.findIndex((item) => item.data.version === version.data.version) === index
71
+ ),
72
+ [availableVersions]
73
+ );
74
+ const defaultToVersion = uniqueAvailableVersions[0]?.data.version || '';
75
+ const defaultFromVersion = uniqueAvailableVersions[1]?.data.version || defaultToVersion;
76
+ const [diffFromVersion, setDiffFromVersion] = useState(defaultFromVersion);
77
+ const [diffToVersion, setDiffToVersion] = useState(defaultToVersion);
78
+ const schemaResourceKey = [
79
+ message.collection,
80
+ message.data.id,
81
+ message.specType || '',
82
+ message.specFilenameWithoutExtension || message.specName || '',
83
+ ].join(':');
84
+
85
+ useEffect(() => {
86
+ setDiffFromVersion(defaultFromVersion);
87
+ setDiffToVersion(defaultToVersion);
88
+ setIsDiffModalOpen(false);
89
+ }, [schemaResourceKey, defaultFromVersion, defaultToVersion]);
90
+
91
+ const diffFromItem = useMemo(
92
+ () => uniqueAvailableVersions.find((version) => version.data.version === diffFromVersion),
93
+ [diffFromVersion, uniqueAvailableVersions]
94
+ );
95
+ const diffToItem = useMemo(
96
+ () => uniqueAvailableVersions.find((version) => version.data.version === diffToVersion),
97
+ [diffToVersion, uniqueAvailableVersions]
98
+ );
99
+ const hasDiffFromContent = !!diffFromItem?.schemaContent?.trim();
100
+ const hasDiffToContent = !!diffToItem?.schemaContent?.trim();
101
+ const selectedDiff: VersionDiff | null = useMemo(() => {
102
+ if (!diffFromItem || !diffToItem) return null;
103
+ if (diffFromItem.data.version === diffToItem.data.version) return null;
104
+ if (!diffFromItem.schemaContent?.trim() || !diffToItem.schemaContent?.trim()) return null;
105
+
106
+ const diff = Diff.createTwoFilesPatch(
107
+ `v${diffFromItem.data.version}`,
108
+ `v${diffToItem.data.version}`,
109
+ diffFromItem.schemaContent,
110
+ diffToItem.schemaContent,
111
+ '',
112
+ '',
113
+ { context: 3 }
114
+ );
115
+
116
+ const diffHtml = html(diff, {
117
+ drawFileList: false,
118
+ matching: 'lines',
119
+ outputFormat: 'side-by-side',
120
+ });
121
+
122
+ return {
123
+ fromVersion: diffFromItem.data.version,
124
+ toVersion: diffToItem.data.version,
125
+ diffHtml,
126
+ fromContent: diffFromItem.schemaContent,
127
+ toContent: diffToItem.schemaContent,
128
+ };
129
+ }, [diffFromItem, diffToItem]);
130
+ const selectedDiffs = selectedDiff ? [selectedDiff] : [];
104
131
 
105
132
  // Check if this is a JSON schema
106
133
  const parsedSchema = useMemo(() => {
@@ -212,7 +239,7 @@ export default function SchemaDetailsPanel({
212
239
  if (examples.length > 0) {
213
240
  tabs.push({ id: 'examples', label: 'Usage Examples', icon: <BookOpenIcon className="h-3.5 w-3.5" /> });
214
241
  }
215
- if (allDiffs.length > 0) {
242
+ if (hasMultipleVersions) {
216
243
  tabs.push({ id: 'diff', label: 'Changes', icon: <ClockIcon className="h-3.5 w-3.5" /> });
217
244
  }
218
245
  tabs.push({ id: 'api', label: 'API', icon: <GlobeAltIcon className="h-3.5 w-3.5" /> });
@@ -341,8 +368,67 @@ export default function SchemaDetailsPanel({
341
368
  copiedId={copiedId}
342
369
  apiAccessEnabled={apiAccessEnabled}
343
370
  />
344
- ) : activeTab === 'diff' && allDiffs.length > 0 ? (
345
- <DiffViewer diffs={allDiffs} onOpenFullscreen={() => setIsDiffModalOpen(true)} apiAccessEnabled={apiAccessEnabled} />
371
+ ) : activeTab === 'diff' && hasMultipleVersions ? (
372
+ <div className="flex h-full min-h-0 flex-col overflow-hidden">
373
+ <div className="mb-4 flex flex-shrink-0 flex-col gap-3 rounded-lg border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-content-hover)/0.45)] p-3 sm:flex-row sm:items-end sm:justify-between">
374
+ <div className="grid flex-1 grid-cols-1 gap-3 sm:grid-cols-2">
375
+ <label className="flex flex-col gap-1.5">
376
+ <span className="text-[11px] font-medium uppercase tracking-[0.14em] text-[rgb(var(--ec-page-text-muted))]">
377
+ From
378
+ </span>
379
+ <select
380
+ value={diffFromVersion}
381
+ onChange={(event) => setDiffFromVersion(event.target.value)}
382
+ className="h-9 rounded-md border border-[rgb(var(--ec-dropdown-border))] bg-[rgb(var(--ec-dropdown-bg))] px-3 text-sm font-mono tabular-nums text-[rgb(var(--ec-page-text))] outline-hidden transition-colors focus:border-[rgb(var(--ec-accent))] focus:ring-1 focus:ring-[rgb(var(--ec-accent)/0.3)]"
383
+ >
384
+ {uniqueAvailableVersions.map((version) => (
385
+ <option key={`from-${version.data.version}`} value={version.data.version}>
386
+ v{version.data.version}
387
+ </option>
388
+ ))}
389
+ </select>
390
+ </label>
391
+ <label className="flex flex-col gap-1.5">
392
+ <span className="text-[11px] font-medium uppercase tracking-[0.14em] text-[rgb(var(--ec-page-text-muted))]">
393
+ To
394
+ </span>
395
+ <select
396
+ value={diffToVersion}
397
+ onChange={(event) => setDiffToVersion(event.target.value)}
398
+ className="h-9 rounded-md border border-[rgb(var(--ec-dropdown-border))] bg-[rgb(var(--ec-dropdown-bg))] px-3 text-sm font-mono tabular-nums text-[rgb(var(--ec-page-text))] outline-hidden transition-colors focus:border-[rgb(var(--ec-accent))] focus:ring-1 focus:ring-[rgb(var(--ec-accent)/0.3)]"
399
+ >
400
+ {uniqueAvailableVersions.map((version) => (
401
+ <option key={`to-${version.data.version}`} value={version.data.version}>
402
+ v{version.data.version}
403
+ </option>
404
+ ))}
405
+ </select>
406
+ </label>
407
+ </div>
408
+ <button
409
+ type="button"
410
+ onClick={() => setIsDiffModalOpen(true)}
411
+ disabled={!selectedDiff}
412
+ className="inline-flex h-9 items-center justify-center gap-1.5 rounded-md border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-dropdown-bg))] px-3 text-xs font-medium text-[rgb(var(--ec-page-text-muted))] transition-colors hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-page-text))] disabled:cursor-not-allowed disabled:opacity-50"
413
+ >
414
+ <ArrowTopRightOnSquareIcon className="h-3.5 w-3.5" />
415
+ Expand
416
+ </button>
417
+ </div>
418
+ <div className="min-h-0 flex-1 overflow-hidden">
419
+ {diffFromVersion === diffToVersion ? (
420
+ <div className="flex h-full items-center justify-center text-[rgb(var(--ec-page-text-muted))]">
421
+ <p className="text-sm">Select two different versions</p>
422
+ </div>
423
+ ) : !hasDiffFromContent || !hasDiffToContent ? (
424
+ <div className="flex h-full items-center justify-center text-[rgb(var(--ec-page-text-muted))]">
425
+ <p className="text-sm">No schema content available</p>
426
+ </div>
427
+ ) : (
428
+ <DiffViewer diffs={selectedDiffs} apiAccessEnabled={apiAccessEnabled} />
429
+ )}
430
+ </div>
431
+ </div>
346
432
  ) : (
347
433
  <SchemaContentViewer
348
434
  message={message}
@@ -507,7 +593,7 @@ export default function SchemaDetailsPanel({
507
593
  <VersionHistoryModal
508
594
  isOpen={isDiffModalOpen}
509
595
  onOpenChange={setIsDiffModalOpen}
510
- diffs={allDiffs}
596
+ diffs={selectedDiffs}
511
597
  messageName={message.data.name}
512
598
  apiAccessEnabled={apiAccessEnabled}
513
599
  />
@@ -28,7 +28,7 @@ export default function VersionHistoryModal({
28
28
  <div className="flex items-center gap-3">
29
29
  <ArrowsPointingOutIcon className="h-6 w-6 text-[rgb(var(--ec-icon-color))]" />
30
30
  <div>
31
- <Dialog.Title className="text-xl font-semibold text-[rgb(var(--ec-page-text))]">Version History</Dialog.Title>
31
+ <Dialog.Title className="text-xl font-semibold text-[rgb(var(--ec-page-text))]">Version Comparison</Dialog.Title>
32
32
  <Dialog.Description className="text-sm text-[rgb(var(--ec-page-text-muted))] mt-1">
33
33
  {messageName}
34
34
  </Dialog.Description>
@@ -51,7 +51,7 @@ export default function VersionHistoryModal({
51
51
  <DiffViewer diffs={diffs} apiAccessEnabled={apiAccessEnabled} />
52
52
  ) : (
53
53
  <div className="flex items-center justify-center h-full">
54
- <p className="text-[rgb(var(--ec-page-text-muted))] text-center">No version history available</p>
54
+ <p className="text-[rgb(var(--ec-page-text-muted))] text-center">No version comparison available</p>
55
55
  </div>
56
56
  )}
57
57
  </div>
@@ -45,9 +45,9 @@ export interface SchemaItem {
45
45
  }
46
46
 
47
47
  export interface VersionDiff {
48
- newerVersion: string;
49
- olderVersion: string;
48
+ fromVersion: string;
49
+ toVersion: string;
50
50
  diffHtml: string;
51
- newerContent: string;
52
- olderContent: string;
51
+ fromContent: string;
52
+ toContent: string;
53
53
  }
@@ -0,0 +1,158 @@
1
+ type Fetcher = (input: string | URL, init?: RequestInit) => Promise<Response>;
2
+
3
+ type ResolveSchemaRefsOptions = {
4
+ baseUrl: string;
5
+ headers?: HeadersInit;
6
+ fetcher?: Fetcher;
7
+ maxDepth?: number;
8
+ };
9
+
10
+ type ResolveContext = Required<Pick<ResolveSchemaRefsOptions, 'baseUrl' | 'fetcher' | 'maxDepth'>> & {
11
+ headers?: HeadersInit;
12
+ cache: Map<string, unknown>;
13
+ };
14
+
15
+ type RefTarget = {
16
+ schema: unknown;
17
+ root: unknown;
18
+ baseUrl: string;
19
+ refKey: string;
20
+ };
21
+
22
+ const isObject = (value: unknown): value is Record<string, unknown> =>
23
+ typeof value === 'object' && value !== null && !Array.isArray(value);
24
+
25
+ const decodeJsonPointerSegment = (segment: string) => decodeURIComponent(segment).replace(/~1/g, '/').replace(/~0/g, '~');
26
+
27
+ const resolveJsonPointer = (document: unknown, pointer: string): unknown => {
28
+ if (!pointer || pointer === '#') return document;
29
+
30
+ const path = pointer.startsWith('#') ? pointer.slice(1) : pointer;
31
+ if (!path) return document;
32
+ if (!path.startsWith('/')) return undefined;
33
+
34
+ return path
35
+ .slice(1)
36
+ .split('/')
37
+ .map(decodeJsonPointerSegment)
38
+ .reduce<unknown>((current, segment) => {
39
+ if (current === undefined || current === null) return undefined;
40
+ if (Array.isArray(current)) return current[Number(segment)];
41
+ if (isObject(current)) return current[segment];
42
+ return undefined;
43
+ }, document);
44
+ };
45
+
46
+ const splitRef = (ref: string, baseUrl: string) => {
47
+ const [urlPart = '', fragmentPart = ''] = ref.split('#');
48
+ const url = urlPart ? new URL(urlPart, baseUrl).toString() : baseUrl;
49
+ return {
50
+ url,
51
+ fragment: `#${fragmentPart}`,
52
+ refKey: `${url}#${fragmentPart}`,
53
+ };
54
+ };
55
+
56
+ const loadRemoteDocument = async (url: string, context: ResolveContext) => {
57
+ if (context.cache.has(url)) return context.cache.get(url);
58
+
59
+ const response = await context.fetcher(url, {
60
+ headers: context.headers,
61
+ });
62
+
63
+ if (!response.ok) {
64
+ throw new Error(`Failed to fetch referenced schema: ${response.status} ${response.statusText}`);
65
+ }
66
+
67
+ const document = await response.json();
68
+ context.cache.set(url, document);
69
+ return document;
70
+ };
71
+
72
+ const resolveRef = async (
73
+ ref: string,
74
+ root: unknown,
75
+ currentBaseUrl: string,
76
+ context: ResolveContext
77
+ ): Promise<RefTarget | undefined> => {
78
+ const { url, fragment, refKey } = splitRef(ref, currentBaseUrl);
79
+ const isCurrentDocument = url === currentBaseUrl;
80
+ const document = isCurrentDocument ? root : await loadRemoteDocument(url, context);
81
+ const schema = resolveJsonPointer(document, fragment);
82
+
83
+ if (schema === undefined) return undefined;
84
+
85
+ return {
86
+ schema,
87
+ root: document,
88
+ baseUrl: url,
89
+ refKey,
90
+ };
91
+ };
92
+
93
+ const resolveNode = async (
94
+ node: unknown,
95
+ root: unknown,
96
+ currentBaseUrl: string,
97
+ context: ResolveContext,
98
+ seenRefs: Set<string>,
99
+ depth: number
100
+ ): Promise<unknown> => {
101
+ if (depth > context.maxDepth) return node;
102
+
103
+ if (Array.isArray(node)) {
104
+ return Promise.all(node.map((item) => resolveNode(item, root, currentBaseUrl, context, seenRefs, depth)));
105
+ }
106
+
107
+ if (!isObject(node)) return node;
108
+
109
+ const ref = typeof node.$ref === 'string' ? node.$ref : undefined;
110
+ if (ref) {
111
+ const target = await resolveRef(ref, root, currentBaseUrl, context);
112
+ if (!target || seenRefs.has(target.refKey)) return { ...node };
113
+
114
+ const nextSeenRefs = new Set(seenRefs);
115
+ nextSeenRefs.add(target.refKey);
116
+
117
+ const resolvedSchema = await resolveNode(target.schema, target.root, target.baseUrl, context, nextSeenRefs, depth + 1);
118
+ const siblingEntries = Object.entries(node).filter(([key]) => key !== '$ref');
119
+ const resolvedSiblings = Object.fromEntries(
120
+ await Promise.all(
121
+ siblingEntries.map(async ([key, value]) => [
122
+ key,
123
+ await resolveNode(value, root, currentBaseUrl, context, seenRefs, depth),
124
+ ])
125
+ )
126
+ );
127
+
128
+ if (isObject(resolvedSchema)) {
129
+ return {
130
+ ...resolvedSchema,
131
+ ...resolvedSiblings,
132
+ };
133
+ }
134
+
135
+ return Object.keys(resolvedSiblings).length > 0 ? resolvedSiblings : resolvedSchema;
136
+ }
137
+
138
+ return Object.fromEntries(
139
+ await Promise.all(
140
+ Object.entries(node).map(async ([key, value]) => [
141
+ key,
142
+ await resolveNode(value, root, currentBaseUrl, context, seenRefs, depth),
143
+ ])
144
+ )
145
+ );
146
+ };
147
+
148
+ export const resolveSchemaRefs = async (schema: unknown, options: ResolveSchemaRefsOptions): Promise<unknown> => {
149
+ const context: ResolveContext = {
150
+ baseUrl: options.baseUrl,
151
+ headers: options.headers,
152
+ fetcher: options.fetcher ?? fetch,
153
+ maxDepth: options.maxDepth ?? 25,
154
+ cache: new Map([[options.baseUrl, schema]]),
155
+ };
156
+
157
+ return resolveNode(schema, schema, options.baseUrl, context, new Set(), 0);
158
+ };
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "license": "SEE LICENSE IN LICENSE",
9
9
  "type": "module",
10
- "version": "4.0.6",
10
+ "version": "4.0.7",
11
11
  "publishConfig": {
12
12
  "access": "public"
13
13
  },
@@ -116,9 +116,9 @@
116
116
  "update-notifier": "^7.3.1",
117
117
  "uuid": "^10.0.0",
118
118
  "zod": "^4.3.6",
119
+ "@eventcatalog/linter": "1.1.4",
119
120
  "@eventcatalog/sdk": "2.25.0",
120
- "@eventcatalog/visualiser": "^4.0.0",
121
- "@eventcatalog/linter": "1.1.3"
121
+ "@eventcatalog/visualiser": "^4.0.1"
122
122
  },
123
123
  "devDependencies": {
124
124
  "@astrojs/check": "^0.9.9",