@eventcatalog/core 3.25.6 → 3.26.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-2ILJMBQM.js → chunk-7CRFNX47.js} +1 -1
- package/dist/{chunk-53HXLUNO.js → chunk-ASC3AR2X.js} +1 -1
- package/dist/{chunk-ZEOK723Y.js → chunk-FQNBDDUF.js} +1 -1
- package/dist/{chunk-P23BMUBV.js → chunk-GCNIIIFG.js} +1 -1
- package/dist/{chunk-R7P4GTFQ.js → chunk-XUN32ZVJ.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +562 -19
- package/dist/eventcatalog.js +572 -27
- 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/astro.config.mjs +2 -1
- package/eventcatalog/integrations/eventcatalog-features.ts +13 -0
- package/eventcatalog/public/icons/graphql.svg +3 -1
- package/eventcatalog/src/components/FieldsExplorer/FieldFilters.tsx +225 -0
- package/eventcatalog/src/components/FieldsExplorer/FieldNodeGraph.tsx +521 -0
- package/eventcatalog/src/components/FieldsExplorer/FieldsExplorer.tsx +501 -0
- package/eventcatalog/src/components/FieldsExplorer/FieldsTable.tsx +236 -0
- package/eventcatalog/src/enterprise/fields/field-extractor.test.ts +241 -0
- package/eventcatalog/src/enterprise/fields/field-extractor.ts +183 -0
- package/eventcatalog/src/enterprise/fields/field-indexer.ts +131 -0
- package/eventcatalog/src/enterprise/fields/fields-db.test.ts +186 -0
- package/eventcatalog/src/enterprise/fields/fields-db.ts +453 -0
- package/eventcatalog/src/enterprise/fields/pages/api/fields.ts +43 -0
- package/eventcatalog/src/enterprise/fields/pages/fields.astro +19 -0
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +23 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +14 -16
- package/eventcatalog/src/utils/node-graphs/field-node-graph.ts +192 -0
- package/package.json +4 -2
package/dist/eventcatalog.js
CHANGED
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
} from "./chunk-PLNJC7NZ.js";
|
|
7
7
|
import {
|
|
8
8
|
log_build_default
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-FQNBDDUF.js";
|
|
10
|
+
import "./chunk-7CRFNX47.js";
|
|
11
11
|
import "./chunk-4UVFXLPI.js";
|
|
12
12
|
import {
|
|
13
13
|
runMigrations
|
|
@@ -22,13 +22,13 @@ import {
|
|
|
22
22
|
} from "./chunk-3KXCGYET.js";
|
|
23
23
|
import {
|
|
24
24
|
generate
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-ASC3AR2X.js";
|
|
26
26
|
import {
|
|
27
27
|
logger
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-XUN32ZVJ.js";
|
|
29
29
|
import {
|
|
30
30
|
VERSION
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-GCNIIIFG.js";
|
|
32
32
|
import {
|
|
33
33
|
getEventCatalogConfigFile,
|
|
34
34
|
verifyRequiredFieldsAreInCatalogConfigFile
|
|
@@ -39,21 +39,540 @@ import { Command } from "commander";
|
|
|
39
39
|
import { execSync, spawn } from "child_process";
|
|
40
40
|
import { join } from "path";
|
|
41
41
|
import http from "http";
|
|
42
|
-
import
|
|
43
|
-
import
|
|
42
|
+
import fs3 from "fs";
|
|
43
|
+
import path2 from "path";
|
|
44
44
|
import { fileURLToPath } from "url";
|
|
45
45
|
import boxen from "boxen";
|
|
46
46
|
import updateNotifier from "update-notifier";
|
|
47
47
|
import dotenv from "dotenv";
|
|
48
|
+
|
|
49
|
+
// eventcatalog/src/enterprise/fields/field-indexer.ts
|
|
50
|
+
import path from "path";
|
|
51
|
+
import fs2 from "fs";
|
|
52
|
+
|
|
53
|
+
// eventcatalog/src/enterprise/fields/fields-db.ts
|
|
54
|
+
import Database from "better-sqlite3";
|
|
55
|
+
import fs from "fs";
|
|
56
|
+
var SCHEMA_SQL = `
|
|
57
|
+
CREATE TABLE IF NOT EXISTS fields (
|
|
58
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
59
|
+
path TEXT NOT NULL,
|
|
60
|
+
type TEXT NOT NULL,
|
|
61
|
+
description TEXT NOT NULL DEFAULT '',
|
|
62
|
+
required INTEGER NOT NULL DEFAULT 0,
|
|
63
|
+
schema_format TEXT NOT NULL,
|
|
64
|
+
message_id TEXT NOT NULL,
|
|
65
|
+
message_version TEXT NOT NULL,
|
|
66
|
+
message_type TEXT NOT NULL,
|
|
67
|
+
message_name TEXT NOT NULL DEFAULT '',
|
|
68
|
+
message_summary TEXT NOT NULL DEFAULT '',
|
|
69
|
+
message_owners TEXT NOT NULL DEFAULT '[]'
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
CREATE TABLE IF NOT EXISTS message_producers (
|
|
73
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
74
|
+
message_id TEXT NOT NULL,
|
|
75
|
+
message_version TEXT NOT NULL,
|
|
76
|
+
service_id TEXT NOT NULL,
|
|
77
|
+
service_version TEXT NOT NULL,
|
|
78
|
+
service_name TEXT NOT NULL DEFAULT '',
|
|
79
|
+
service_summary TEXT NOT NULL DEFAULT '',
|
|
80
|
+
service_owners TEXT NOT NULL DEFAULT '[]'
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE TABLE IF NOT EXISTS message_consumers (
|
|
84
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
85
|
+
message_id TEXT NOT NULL,
|
|
86
|
+
message_version TEXT NOT NULL,
|
|
87
|
+
service_id TEXT NOT NULL,
|
|
88
|
+
service_version TEXT NOT NULL,
|
|
89
|
+
service_name TEXT NOT NULL DEFAULT '',
|
|
90
|
+
service_summary TEXT NOT NULL DEFAULT '',
|
|
91
|
+
service_owners TEXT NOT NULL DEFAULT '[]'
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE INDEX IF NOT EXISTS idx_fields_path ON fields(path);
|
|
95
|
+
CREATE INDEX IF NOT EXISTS idx_fields_message ON fields(message_id, message_version);
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_producers_message ON message_producers(message_id, message_version);
|
|
97
|
+
CREATE INDEX IF NOT EXISTS idx_consumers_message ON message_consumers(message_id, message_version);
|
|
98
|
+
`;
|
|
99
|
+
var FTS_SQL = `
|
|
100
|
+
DROP TABLE IF EXISTS fields_fts;
|
|
101
|
+
CREATE VIRTUAL TABLE fields_fts USING fts5(
|
|
102
|
+
path,
|
|
103
|
+
description,
|
|
104
|
+
type,
|
|
105
|
+
content=fields,
|
|
106
|
+
content_rowid=id
|
|
107
|
+
);
|
|
108
|
+
INSERT INTO fields_fts(rowid, path, description, type) SELECT id, path, description, type FROM fields;
|
|
109
|
+
`;
|
|
110
|
+
var FieldsDatabase = class {
|
|
111
|
+
db;
|
|
112
|
+
constructor(dbPath, options) {
|
|
113
|
+
if (options?.recreate && fs.existsSync(dbPath)) {
|
|
114
|
+
fs.unlinkSync(dbPath);
|
|
115
|
+
}
|
|
116
|
+
this.db = new Database(dbPath);
|
|
117
|
+
this.db.pragma("journal_mode = WAL");
|
|
118
|
+
this.db.exec(SCHEMA_SQL);
|
|
119
|
+
}
|
|
120
|
+
insertField(field) {
|
|
121
|
+
this.db.prepare(
|
|
122
|
+
`INSERT INTO fields (path, type, description, required, schema_format, message_id, message_version, message_type, message_name, message_summary, message_owners)
|
|
123
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
124
|
+
).run(
|
|
125
|
+
field.path,
|
|
126
|
+
field.type,
|
|
127
|
+
field.description,
|
|
128
|
+
field.required ? 1 : 0,
|
|
129
|
+
field.schemaFormat,
|
|
130
|
+
field.messageId,
|
|
131
|
+
field.messageVersion,
|
|
132
|
+
field.messageType,
|
|
133
|
+
field.messageName || "",
|
|
134
|
+
field.messageSummary || "",
|
|
135
|
+
JSON.stringify(field.messageOwners || [])
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
insertProducer(messageId, messageVersion, serviceId, serviceVersion, serviceName, serviceSummary, serviceOwners) {
|
|
139
|
+
this.db.prepare(
|
|
140
|
+
`INSERT INTO message_producers (message_id, message_version, service_id, service_version, service_name, service_summary, service_owners)
|
|
141
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
142
|
+
).run(
|
|
143
|
+
messageId,
|
|
144
|
+
messageVersion,
|
|
145
|
+
serviceId,
|
|
146
|
+
serviceVersion,
|
|
147
|
+
serviceName || "",
|
|
148
|
+
serviceSummary || "",
|
|
149
|
+
JSON.stringify(serviceOwners || [])
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
insertConsumer(messageId, messageVersion, serviceId, serviceVersion, serviceName, serviceSummary, serviceOwners) {
|
|
153
|
+
this.db.prepare(
|
|
154
|
+
`INSERT INTO message_consumers (message_id, message_version, service_id, service_version, service_name, service_summary, service_owners)
|
|
155
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
156
|
+
).run(
|
|
157
|
+
messageId,
|
|
158
|
+
messageVersion,
|
|
159
|
+
serviceId,
|
|
160
|
+
serviceVersion,
|
|
161
|
+
serviceName || "",
|
|
162
|
+
serviceSummary || "",
|
|
163
|
+
JSON.stringify(serviceOwners || [])
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
rebuildFts() {
|
|
167
|
+
this.db.exec(FTS_SQL);
|
|
168
|
+
}
|
|
169
|
+
queryFields(params) {
|
|
170
|
+
const {
|
|
171
|
+
q,
|
|
172
|
+
shared,
|
|
173
|
+
conflicting,
|
|
174
|
+
format,
|
|
175
|
+
type,
|
|
176
|
+
messageType,
|
|
177
|
+
message,
|
|
178
|
+
producer,
|
|
179
|
+
consumer,
|
|
180
|
+
required,
|
|
181
|
+
path: fieldPath,
|
|
182
|
+
pageSize = 50,
|
|
183
|
+
cursor
|
|
184
|
+
} = params;
|
|
185
|
+
const conditions = [];
|
|
186
|
+
const bindings = [];
|
|
187
|
+
if (fieldPath) {
|
|
188
|
+
conditions.push(`f.path = ?`);
|
|
189
|
+
bindings.push(fieldPath);
|
|
190
|
+
}
|
|
191
|
+
if (q) {
|
|
192
|
+
conditions.push(`f.id IN (SELECT rowid FROM fields_fts WHERE fields_fts MATCH ?)`);
|
|
193
|
+
const escaped = q.replace(/"/g, '""');
|
|
194
|
+
bindings.push(`"${escaped}" *`);
|
|
195
|
+
}
|
|
196
|
+
if (format) {
|
|
197
|
+
const formats2 = format.split(",").map((f) => f.trim()).filter(Boolean);
|
|
198
|
+
conditions.push(`f.schema_format IN (${formats2.map(() => "?").join(", ")})`);
|
|
199
|
+
bindings.push(...formats2);
|
|
200
|
+
}
|
|
201
|
+
if (type) {
|
|
202
|
+
conditions.push(`f.type = ?`);
|
|
203
|
+
bindings.push(type);
|
|
204
|
+
}
|
|
205
|
+
if (messageType) {
|
|
206
|
+
const types2 = messageType.split(",").map((t) => t.trim()).filter(Boolean);
|
|
207
|
+
conditions.push(`f.message_type IN (${types2.map(() => "?").join(", ")})`);
|
|
208
|
+
bindings.push(...types2);
|
|
209
|
+
}
|
|
210
|
+
if (message) {
|
|
211
|
+
conditions.push(`f.message_id = ?`);
|
|
212
|
+
bindings.push(message);
|
|
213
|
+
}
|
|
214
|
+
if (required) {
|
|
215
|
+
conditions.push(`f.required = 1`);
|
|
216
|
+
}
|
|
217
|
+
if (producer) {
|
|
218
|
+
conditions.push(
|
|
219
|
+
`EXISTS (SELECT 1 FROM message_producers p WHERE p.message_id = f.message_id AND p.message_version = f.message_version AND p.service_id = ?)`
|
|
220
|
+
);
|
|
221
|
+
bindings.push(producer);
|
|
222
|
+
}
|
|
223
|
+
if (consumer) {
|
|
224
|
+
conditions.push(
|
|
225
|
+
`EXISTS (SELECT 1 FROM message_consumers c WHERE c.message_id = f.message_id AND c.message_version = f.message_version AND c.service_id = ?)`
|
|
226
|
+
);
|
|
227
|
+
bindings.push(consumer);
|
|
228
|
+
}
|
|
229
|
+
if (shared) {
|
|
230
|
+
const sharedSubquery = `SELECT path FROM fields GROUP BY path HAVING COUNT(DISTINCT message_id || '/' || message_version) > 1`;
|
|
231
|
+
conditions.push(`f.path IN (${sharedSubquery})`);
|
|
232
|
+
}
|
|
233
|
+
if (conflicting) {
|
|
234
|
+
const conflictSubquery = `SELECT path FROM fields GROUP BY path HAVING COUNT(DISTINCT type) > 1`;
|
|
235
|
+
conditions.push(`f.path IN (${conflictSubquery})`);
|
|
236
|
+
}
|
|
237
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
238
|
+
const cursorConditions = [];
|
|
239
|
+
const cursorBindings = [];
|
|
240
|
+
if (cursor) {
|
|
241
|
+
const lastId = decodeCursor(cursor);
|
|
242
|
+
cursorConditions.push(`f.id > ?`);
|
|
243
|
+
cursorBindings.push(lastId);
|
|
244
|
+
}
|
|
245
|
+
const paginationWhere = cursorConditions.length > 0 ? whereClause ? `${whereClause} AND ${cursorConditions.join(" AND ")}` : `WHERE ${cursorConditions.join(" AND ")}` : whereClause;
|
|
246
|
+
const countSql = `SELECT COUNT(*) as cnt FROM fields f ${whereClause}`;
|
|
247
|
+
const total = this.db.prepare(countSql).get(...bindings).cnt;
|
|
248
|
+
const mainSql = `SELECT f.* FROM fields f ${paginationWhere} ORDER BY f.id ASC LIMIT ?`;
|
|
249
|
+
const allBindings = [...bindings, ...cursorBindings, pageSize];
|
|
250
|
+
const rows = this.db.prepare(mainSql).all(...allBindings);
|
|
251
|
+
const usedInStmt = this.db.prepare(
|
|
252
|
+
`SELECT COUNT(DISTINCT message_id || '/' || message_version) as cnt FROM fields WHERE path = ?`
|
|
253
|
+
);
|
|
254
|
+
const conflictsStmt = this.db.prepare(
|
|
255
|
+
`SELECT type, COUNT(DISTINCT message_id || '/' || message_version) as count FROM fields WHERE path = ? GROUP BY type`
|
|
256
|
+
);
|
|
257
|
+
const fields = rows.map((row) => {
|
|
258
|
+
const producers = this.db.prepare(
|
|
259
|
+
`SELECT service_id, service_version, service_name, service_summary, service_owners FROM message_producers WHERE message_id = ? AND message_version = ?`
|
|
260
|
+
).all(row.message_id, row.message_version);
|
|
261
|
+
const consumers = this.db.prepare(
|
|
262
|
+
`SELECT service_id, service_version, service_name, service_summary, service_owners FROM message_consumers WHERE message_id = ? AND message_version = ?`
|
|
263
|
+
).all(row.message_id, row.message_version);
|
|
264
|
+
const parseOwners = (raw) => {
|
|
265
|
+
try {
|
|
266
|
+
return JSON.parse(raw || "[]");
|
|
267
|
+
} catch {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const usedInCount = usedInStmt.get(row.path).cnt;
|
|
272
|
+
const typeRows = conflictsStmt.all(row.path);
|
|
273
|
+
const conflicts = typeRows.length > 1 ? typeRows.map((r) => ({ type: r.type, count: r.count })) : void 0;
|
|
274
|
+
return {
|
|
275
|
+
id: row.id,
|
|
276
|
+
path: row.path,
|
|
277
|
+
type: row.type,
|
|
278
|
+
description: row.description,
|
|
279
|
+
required: row.required === 1,
|
|
280
|
+
schemaFormat: row.schema_format,
|
|
281
|
+
messageId: row.message_id,
|
|
282
|
+
messageVersion: row.message_version,
|
|
283
|
+
messageType: row.message_type,
|
|
284
|
+
messageName: row.message_name || row.message_id,
|
|
285
|
+
messageSummary: row.message_summary || "",
|
|
286
|
+
messageOwners: parseOwners(row.message_owners),
|
|
287
|
+
usedInCount,
|
|
288
|
+
conflicts,
|
|
289
|
+
producers: producers.map((p) => ({
|
|
290
|
+
id: p.service_id,
|
|
291
|
+
version: p.service_version,
|
|
292
|
+
name: p.service_name || p.service_id,
|
|
293
|
+
summary: p.service_summary || "",
|
|
294
|
+
owners: parseOwners(p.service_owners)
|
|
295
|
+
})),
|
|
296
|
+
consumers: consumers.map((c) => ({
|
|
297
|
+
id: c.service_id,
|
|
298
|
+
version: c.service_version,
|
|
299
|
+
name: c.service_name || c.service_id,
|
|
300
|
+
summary: c.service_summary || "",
|
|
301
|
+
owners: parseOwners(c.service_owners)
|
|
302
|
+
}))
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
const formatsFacetSql = `SELECT f.schema_format as value, COUNT(*) as count FROM fields f ${whereClause} GROUP BY f.schema_format`;
|
|
306
|
+
const formats = this.db.prepare(formatsFacetSql).all(...bindings);
|
|
307
|
+
const typesFacetSql = `SELECT f.type as value, COUNT(*) as count FROM fields f ${whereClause} GROUP BY f.type`;
|
|
308
|
+
const types = this.db.prepare(typesFacetSql).all(...bindings);
|
|
309
|
+
const messageTypesFacetSql = `SELECT f.message_type as value, COUNT(*) as count FROM fields f ${whereClause} GROUP BY f.message_type`;
|
|
310
|
+
const messageTypes = this.db.prepare(messageTypesFacetSql).all(...bindings);
|
|
311
|
+
const lastRow = rows[rows.length - 1];
|
|
312
|
+
const nextCursor = lastRow && rows.length === pageSize ? encodeCursor(lastRow.id) : void 0;
|
|
313
|
+
return {
|
|
314
|
+
fields,
|
|
315
|
+
total,
|
|
316
|
+
cursor: nextCursor,
|
|
317
|
+
facets: { formats, types, messageTypes }
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
close() {
|
|
321
|
+
this.db.close();
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
function encodeCursor(id) {
|
|
325
|
+
return Buffer.from(String(id)).toString("base64url");
|
|
326
|
+
}
|
|
327
|
+
function decodeCursor(cursor) {
|
|
328
|
+
return parseInt(Buffer.from(cursor, "base64url").toString(), 10);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// eventcatalog/src/enterprise/fields/field-extractor.ts
|
|
332
|
+
function extractSchemaFieldsDeep(content, format) {
|
|
333
|
+
if (!content) return [];
|
|
334
|
+
if (format === "json-schema") {
|
|
335
|
+
return extractJsonSchemaFields(content);
|
|
336
|
+
}
|
|
337
|
+
if (format === "avro") {
|
|
338
|
+
return extractAvroFields(content);
|
|
339
|
+
}
|
|
340
|
+
if (format === "proto") {
|
|
341
|
+
return extractProtoFields(content);
|
|
342
|
+
}
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
function extractProtoFields(content) {
|
|
346
|
+
if (!content) return [];
|
|
347
|
+
const fields = [];
|
|
348
|
+
const fieldRegex = /^\s*(repeated\s+|optional\s+|required\s+)?(\w+)\s+(\w+)\s*=\s*\d+\s*;(?:\s*\/\/\s*(.*))?/gm;
|
|
349
|
+
let match;
|
|
350
|
+
while ((match = fieldRegex.exec(content)) !== null) {
|
|
351
|
+
const modifier = (match[1] || "").trim();
|
|
352
|
+
const type = modifier ? `${modifier} ${match[2]}` : match[2];
|
|
353
|
+
fields.push({
|
|
354
|
+
path: match[3],
|
|
355
|
+
type,
|
|
356
|
+
description: match[4]?.trim() || "",
|
|
357
|
+
required: modifier === "required"
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
return fields;
|
|
361
|
+
}
|
|
362
|
+
function extractAvroFields(content) {
|
|
363
|
+
try {
|
|
364
|
+
const schema = JSON.parse(content);
|
|
365
|
+
const fields = [];
|
|
366
|
+
walkAvroRecord(schema, "", fields);
|
|
367
|
+
return fields;
|
|
368
|
+
} catch {
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function getAvroTypeName(type) {
|
|
373
|
+
if (typeof type === "string") return type;
|
|
374
|
+
if (Array.isArray(type)) {
|
|
375
|
+
return type.map((t) => typeof t === "string" ? t : t.type || "complex").join(" | ");
|
|
376
|
+
}
|
|
377
|
+
if (typeof type === "object" && type !== null) {
|
|
378
|
+
if (type.type === "array" && type.items) return `array<${getAvroTypeName(type.items)}>`;
|
|
379
|
+
if (type.type === "record") return type.name || "record";
|
|
380
|
+
return type.type || "complex";
|
|
381
|
+
}
|
|
382
|
+
return "unknown";
|
|
383
|
+
}
|
|
384
|
+
function walkAvroRecord(schema, prefix, fields) {
|
|
385
|
+
if (!schema.fields || !Array.isArray(schema.fields)) return;
|
|
386
|
+
for (const field of schema.fields) {
|
|
387
|
+
const path3 = prefix ? `${prefix}.${field.name}` : field.name;
|
|
388
|
+
const isOptional = Array.isArray(field.type) && field.type.includes("null");
|
|
389
|
+
const typeName = getAvroTypeName(field.type);
|
|
390
|
+
fields.push({
|
|
391
|
+
path: path3,
|
|
392
|
+
type: typeName,
|
|
393
|
+
description: field.doc || "",
|
|
394
|
+
required: !isOptional
|
|
395
|
+
});
|
|
396
|
+
const innerType = Array.isArray(field.type) ? field.type.find((t) => typeof t === "object" && t.type === "record") : typeof field.type === "object" && field.type.type === "record" ? field.type : null;
|
|
397
|
+
if (innerType) {
|
|
398
|
+
walkAvroRecord(innerType, path3, fields);
|
|
399
|
+
}
|
|
400
|
+
const arrayType = Array.isArray(field.type) ? field.type.find((t) => typeof t === "object" && t.type === "array") : typeof field.type === "object" && field.type.type === "array" ? field.type : null;
|
|
401
|
+
if (arrayType && typeof arrayType.items === "object" && arrayType.items.type === "record") {
|
|
402
|
+
walkAvroRecord(arrayType.items, `${path3}[]`, fields);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function extractJsonSchemaFields(content) {
|
|
407
|
+
try {
|
|
408
|
+
const schema = JSON.parse(content);
|
|
409
|
+
const fields = [];
|
|
410
|
+
walkJsonSchema(schema, "", schema.required || [], schema, fields);
|
|
411
|
+
return fields;
|
|
412
|
+
} catch {
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function walkJsonSchema(node, prefix, requiredList, rootSchema, fields) {
|
|
417
|
+
if (node.allOf && Array.isArray(node.allOf)) {
|
|
418
|
+
const merged = { type: "object", properties: {}, required: [] };
|
|
419
|
+
for (let sub of node.allOf) {
|
|
420
|
+
if (sub.$ref) {
|
|
421
|
+
const resolved = resolveLocalRef(sub.$ref, rootSchema);
|
|
422
|
+
if (resolved) sub = resolved;
|
|
423
|
+
else continue;
|
|
424
|
+
}
|
|
425
|
+
Object.assign(merged.properties, sub.properties || {});
|
|
426
|
+
merged.required.push(...sub.required || []);
|
|
427
|
+
}
|
|
428
|
+
walkJsonSchema(merged, prefix, merged.required, rootSchema, fields);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (!node.properties) return;
|
|
432
|
+
for (const [name, prop] of Object.entries(node.properties)) {
|
|
433
|
+
const path3 = prefix ? `${prefix}.${name}` : name;
|
|
434
|
+
const isRequired = requiredList.includes(name);
|
|
435
|
+
if (prop.$ref) {
|
|
436
|
+
const resolved = resolveLocalRef(prop.$ref, rootSchema);
|
|
437
|
+
if (resolved) {
|
|
438
|
+
const type2 = resolved.type || "object";
|
|
439
|
+
fields.push({ path: path3, type: type2, description: resolved.description || "", required: isRequired });
|
|
440
|
+
if (resolved.properties) {
|
|
441
|
+
walkJsonSchema(resolved, path3, resolved.required || [], rootSchema, fields);
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
fields.push({ path: path3, type: "$ref", description: "", required: isRequired });
|
|
445
|
+
}
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
const type = prop.type || (prop.enum ? "enum" : prop.$ref ? "$ref" : "object");
|
|
449
|
+
fields.push({ path: path3, type, description: prop.description || "", required: isRequired });
|
|
450
|
+
if (prop.type === "object" && prop.properties) {
|
|
451
|
+
walkJsonSchema(prop, path3, prop.required || [], rootSchema, fields);
|
|
452
|
+
}
|
|
453
|
+
if (prop.type === "array" && prop.items) {
|
|
454
|
+
if (prop.items.type === "object" && prop.items.properties) {
|
|
455
|
+
walkJsonSchema(prop.items, `${path3}[]`, prop.items.required || [], rootSchema, fields);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function resolveLocalRef(ref, rootSchema) {
|
|
461
|
+
if (!ref.startsWith("#/")) return null;
|
|
462
|
+
const parts = ref.replace("#/", "").split("/");
|
|
463
|
+
let current = rootSchema;
|
|
464
|
+
for (const part of parts) {
|
|
465
|
+
current = current?.[part];
|
|
466
|
+
if (!current) return null;
|
|
467
|
+
}
|
|
468
|
+
return current;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// eventcatalog/src/enterprise/fields/field-indexer.ts
|
|
472
|
+
function detectFormat(fileName) {
|
|
473
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
474
|
+
if (ext === ".proto") return "proto";
|
|
475
|
+
if (ext === ".avro" || ext === ".avsc") return "avro";
|
|
476
|
+
return "json-schema";
|
|
477
|
+
}
|
|
478
|
+
async function buildFieldsIndex(catalogDir, outputDir) {
|
|
479
|
+
const sdkModule = await import("@eventcatalog/sdk");
|
|
480
|
+
const sdk = sdkModule.default(catalogDir);
|
|
481
|
+
const dbDir = path.join(outputDir || catalogDir, ".eventcatalog");
|
|
482
|
+
const dbPath = path.join(dbDir, "fields.db");
|
|
483
|
+
if (!fs2.existsSync(dbDir)) {
|
|
484
|
+
fs2.mkdirSync(dbDir, { recursive: true });
|
|
485
|
+
}
|
|
486
|
+
const db = new FieldsDatabase(dbPath, { recreate: true });
|
|
487
|
+
const warnings = [];
|
|
488
|
+
try {
|
|
489
|
+
const [events, commands, queries] = await Promise.all([
|
|
490
|
+
sdk.getEvents({ latestOnly: true }),
|
|
491
|
+
sdk.getCommands({ latestOnly: true }),
|
|
492
|
+
sdk.getQueries({ latestOnly: true })
|
|
493
|
+
]);
|
|
494
|
+
const collections = [
|
|
495
|
+
{ entries: events, type: "event" },
|
|
496
|
+
{ entries: commands, type: "command" },
|
|
497
|
+
{ entries: queries, type: "query" }
|
|
498
|
+
];
|
|
499
|
+
for (const { entries, type } of collections) {
|
|
500
|
+
for (const entry of entries) {
|
|
501
|
+
const msgId = entry.id;
|
|
502
|
+
const msgVersion = entry.version;
|
|
503
|
+
let schemaData;
|
|
504
|
+
try {
|
|
505
|
+
schemaData = await sdk.getSchemaForMessage(msgId, msgVersion);
|
|
506
|
+
} catch {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (!schemaData) continue;
|
|
510
|
+
const { schema: content, fileName } = schemaData;
|
|
511
|
+
const format = detectFormat(fileName);
|
|
512
|
+
try {
|
|
513
|
+
const fields = extractSchemaFieldsDeep(content, format);
|
|
514
|
+
for (const field of fields) {
|
|
515
|
+
db.insertField({
|
|
516
|
+
path: field.path,
|
|
517
|
+
type: field.type,
|
|
518
|
+
description: field.description,
|
|
519
|
+
required: field.required,
|
|
520
|
+
schemaFormat: format,
|
|
521
|
+
messageId: msgId,
|
|
522
|
+
messageVersion: msgVersion,
|
|
523
|
+
messageType: type,
|
|
524
|
+
messageName: entry.name || msgId,
|
|
525
|
+
messageSummary: entry.summary || "",
|
|
526
|
+
messageOwners: entry.owners || []
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
const { producers, consumers } = await sdk.getProducersAndConsumersForMessage(msgId, msgVersion);
|
|
530
|
+
for (const producer of producers) {
|
|
531
|
+
db.insertProducer(
|
|
532
|
+
msgId,
|
|
533
|
+
msgVersion,
|
|
534
|
+
producer.id,
|
|
535
|
+
producer.version,
|
|
536
|
+
producer.name || producer.id,
|
|
537
|
+
producer.summary || "",
|
|
538
|
+
producer.owners || []
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
for (const consumer of consumers) {
|
|
542
|
+
db.insertConsumer(
|
|
543
|
+
msgId,
|
|
544
|
+
msgVersion,
|
|
545
|
+
consumer.id,
|
|
546
|
+
consumer.version,
|
|
547
|
+
consumer.name || consumer.id,
|
|
548
|
+
consumer.summary || "",
|
|
549
|
+
consumer.owners || []
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
} catch (err) {
|
|
553
|
+
warnings.push({ messageId: msgId, version: msgVersion, error: err.message });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
db.rebuildFts();
|
|
558
|
+
db.close();
|
|
559
|
+
return { dbPath, warnings };
|
|
560
|
+
} catch (err) {
|
|
561
|
+
db.close();
|
|
562
|
+
throw err;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/eventcatalog.ts
|
|
48
567
|
import { isEventCatalogStarterEnabled, isEventCatalogScaleEnabled, isFeatureEnabled } from "@eventcatalog/license";
|
|
49
|
-
var currentDir =
|
|
568
|
+
var currentDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
50
569
|
var program = new Command().version(VERSION);
|
|
51
|
-
var dir =
|
|
52
|
-
var core =
|
|
53
|
-
var eventCatalogDir =
|
|
570
|
+
var dir = path2.resolve(process.env.PROJECT_DIR || process.cwd());
|
|
571
|
+
var core = path2.resolve(process.env.CATALOG_DIR || join(dir, ".eventcatalog-core"));
|
|
572
|
+
var eventCatalogDir = path2.resolve(join(currentDir, "../eventcatalog/"));
|
|
54
573
|
var getInstalledEventCatalogVersion = () => {
|
|
55
574
|
try {
|
|
56
|
-
const pkg =
|
|
575
|
+
const pkg = fs3.readFileSync(join(dir, "package.json"), "utf8");
|
|
57
576
|
const json = JSON.parse(pkg);
|
|
58
577
|
return json.dependencies["@eventcatalog/core"];
|
|
59
578
|
} catch (error) {
|
|
@@ -62,8 +581,8 @@ var getInstalledEventCatalogVersion = () => {
|
|
|
62
581
|
};
|
|
63
582
|
program.name("eventcatalog").description("Documentation tool for event-driven architectures");
|
|
64
583
|
var ensureDir = (dir2) => {
|
|
65
|
-
if (!
|
|
66
|
-
|
|
584
|
+
if (!fs3.existsSync(dir2)) {
|
|
585
|
+
fs3.mkdirSync(dir2);
|
|
67
586
|
}
|
|
68
587
|
};
|
|
69
588
|
var resolveDevPort = async ({ projectDir }) => {
|
|
@@ -196,7 +715,7 @@ var copyCore = () => {
|
|
|
196
715
|
if (eventCatalogDir === core) {
|
|
197
716
|
return;
|
|
198
717
|
}
|
|
199
|
-
|
|
718
|
+
fs3.cpSync(eventCatalogDir, core, {
|
|
200
719
|
recursive: true,
|
|
201
720
|
filter: (src) => {
|
|
202
721
|
return true;
|
|
@@ -204,7 +723,7 @@ var copyCore = () => {
|
|
|
204
723
|
});
|
|
205
724
|
};
|
|
206
725
|
var clearCore = () => {
|
|
207
|
-
if (
|
|
726
|
+
if (fs3.existsSync(core)) fs3.rmSync(core, { recursive: true });
|
|
208
727
|
};
|
|
209
728
|
var checkForUpdate = () => {
|
|
210
729
|
const installedVersion = getInstalledEventCatalogVersion();
|
|
@@ -258,8 +777,8 @@ program.command("dev").description("Run development server of EventCatalog").opt
|
|
|
258
777
|
logger.info("Setting up EventCatalog...", "eventcatalog");
|
|
259
778
|
const isServer = await isOutputServer();
|
|
260
779
|
logger.info(isServer ? "EventCatalog is running in Server Mode" : "EventCatalog is running in Static Mode", "config");
|
|
261
|
-
if (
|
|
262
|
-
dotenv.config({ path:
|
|
780
|
+
if (fs3.existsSync(path2.join(dir, ".env"))) {
|
|
781
|
+
dotenv.config({ path: path2.join(dir, ".env") });
|
|
263
782
|
}
|
|
264
783
|
if (options.debug) {
|
|
265
784
|
logger.info("Debug mode enabled", "debug");
|
|
@@ -278,6 +797,19 @@ program.command("dev").description("Run development server of EventCatalog").opt
|
|
|
278
797
|
);
|
|
279
798
|
const isEventCatalogStarter = await isEventCatalogStarterEnabled();
|
|
280
799
|
const isEventCatalogScale = await isEventCatalogScaleEnabled();
|
|
800
|
+
if (isServer) {
|
|
801
|
+
try {
|
|
802
|
+
logger.info("Building fields index...", "fields");
|
|
803
|
+
const { warnings } = await buildFieldsIndex(dir, core);
|
|
804
|
+
if (warnings.length > 0) {
|
|
805
|
+
logger.info(`Fields index built with ${warnings.length} warning(s)`, "fields");
|
|
806
|
+
} else {
|
|
807
|
+
logger.info("Fields index built successfully", "fields");
|
|
808
|
+
}
|
|
809
|
+
} catch (err) {
|
|
810
|
+
logger.info(`Failed to build fields index: ${err.message}`, "fields");
|
|
811
|
+
}
|
|
812
|
+
}
|
|
281
813
|
checkForUpdate();
|
|
282
814
|
let watchUnsub;
|
|
283
815
|
try {
|
|
@@ -318,8 +850,8 @@ program.command("build").description("Run build of EventCatalog").action(async (
|
|
|
318
850
|
logger.info("Building EventCatalog...", "build");
|
|
319
851
|
const isServer = await isOutputServer();
|
|
320
852
|
logger.info(isServer ? "EventCatalog is running in Server Mode" : "EventCatalog is running in Static Mode", "config");
|
|
321
|
-
if (
|
|
322
|
-
dotenv.config({ path:
|
|
853
|
+
if (fs3.existsSync(path2.join(dir, ".env"))) {
|
|
854
|
+
dotenv.config({ path: path2.join(dir, ".env") });
|
|
323
855
|
}
|
|
324
856
|
await verifyRequiredFieldsAreInCatalogConfigFile(dir);
|
|
325
857
|
copyCore();
|
|
@@ -338,6 +870,19 @@ program.command("build").description("Run build of EventCatalog").action(async (
|
|
|
338
870
|
await resolve_catalog_dependencies_default(dir, core);
|
|
339
871
|
await runMigrations(dir);
|
|
340
872
|
await catalogToAstro(dir, core);
|
|
873
|
+
if (isServer) {
|
|
874
|
+
try {
|
|
875
|
+
logger.info("Building fields index...", "fields");
|
|
876
|
+
const { warnings } = await buildFieldsIndex(dir, core);
|
|
877
|
+
if (warnings.length > 0) {
|
|
878
|
+
logger.info(`Fields index built with ${warnings.length} warning(s)`, "fields");
|
|
879
|
+
} else {
|
|
880
|
+
logger.info("Fields index built successfully", "fields");
|
|
881
|
+
}
|
|
882
|
+
} catch (err) {
|
|
883
|
+
logger.info(`Failed to build fields index: ${err.message}`, "fields");
|
|
884
|
+
}
|
|
885
|
+
}
|
|
341
886
|
checkForUpdate();
|
|
342
887
|
const args = command.args.join(" ").trim();
|
|
343
888
|
await runCommandWithFilteredOutput({
|
|
@@ -373,7 +918,7 @@ var startServerCatalog = ({
|
|
|
373
918
|
isEventCatalogStarter = false,
|
|
374
919
|
isEventCatalogScale = false
|
|
375
920
|
}) => {
|
|
376
|
-
const serverEntryPath =
|
|
921
|
+
const serverEntryPath = path2.join(dir, "dist", "server", "entry.mjs");
|
|
377
922
|
execSync(
|
|
378
923
|
`cross-env PROJECT_DIR='${dir}' CATALOG_DIR='${core}' ENABLE_EMBED=${canEmbedPages} EVENTCATALOG_STARTER=${isEventCatalogStarter} EVENTCATALOG_SCALE=${isEventCatalogScale} node "${serverEntryPath}"`,
|
|
379
924
|
{
|
|
@@ -385,8 +930,8 @@ var startServerCatalog = ({
|
|
|
385
930
|
program.command("preview").description("Serves the contents of your eventcatalog build directory").action(async (options, command) => {
|
|
386
931
|
logger.welcome();
|
|
387
932
|
logger.info("Starting preview of your build...", "preview");
|
|
388
|
-
if (
|
|
389
|
-
dotenv.config({ path:
|
|
933
|
+
if (fs3.existsSync(path2.join(dir, ".env"))) {
|
|
934
|
+
dotenv.config({ path: path2.join(dir, ".env") });
|
|
390
935
|
}
|
|
391
936
|
const canEmbedPages = await isFeatureEnabled(
|
|
392
937
|
"@eventcatalog/backstage-plugin-eventcatalog",
|
|
@@ -399,8 +944,8 @@ program.command("preview").description("Serves the contents of your eventcatalog
|
|
|
399
944
|
program.command("start").description("Serves the contents of your eventcatalog build directory").action(async (options, command) => {
|
|
400
945
|
logger.welcome();
|
|
401
946
|
logger.info("Starting preview of your build...", "preview");
|
|
402
|
-
if (
|
|
403
|
-
dotenv.config({ path:
|
|
947
|
+
if (fs3.existsSync(path2.join(dir, ".env"))) {
|
|
948
|
+
dotenv.config({ path: path2.join(dir, ".env") });
|
|
404
949
|
}
|
|
405
950
|
const canEmbedPages = await isFeatureEnabled(
|
|
406
951
|
"@eventcatalog/backstage-plugin-eventcatalog",
|
|
@@ -426,8 +971,8 @@ program.command("start").description("Serves the contents of your eventcatalog b
|
|
|
426
971
|
}
|
|
427
972
|
});
|
|
428
973
|
program.command("generate [siteDir]").description("Start the generator scripts.").action(async () => {
|
|
429
|
-
if (
|
|
430
|
-
dotenv.config({ path:
|
|
974
|
+
if (fs3.existsSync(path2.join(dir, ".env"))) {
|
|
975
|
+
dotenv.config({ path: path2.join(dir, ".env") });
|
|
431
976
|
}
|
|
432
977
|
await generate(dir);
|
|
433
978
|
});
|