@rdbms-erd/core 0.1.2 → 0.1.4

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/index.js ADDED
@@ -0,0 +1,883 @@
1
+ // src/alignment.ts
2
+ function alignNodePositions(selectedIds, positions, command) {
3
+ const entries = selectedIds.map((id) => {
4
+ const pos = positions[id];
5
+ return pos ? { id, pos: { ...pos } } : null;
6
+ }).filter((e) => Boolean(e));
7
+ if (entries.length < 2) {
8
+ return {};
9
+ }
10
+ const xs = entries.map((e) => e.pos.x);
11
+ const ys = entries.map((e) => e.pos.y);
12
+ const minX = Math.min(...xs);
13
+ const maxX = Math.max(...xs);
14
+ const minY = Math.min(...ys);
15
+ const maxY = Math.max(...ys);
16
+ const centerX = (minX + maxX) / 2;
17
+ const centerY = (minY + maxY) / 2;
18
+ const sortByX = [...entries].sort((a, b) => a.pos.x - b.pos.x);
19
+ const sortByY = [...entries].sort((a, b) => a.pos.y - b.pos.y);
20
+ const out = {};
21
+ for (const e of entries) {
22
+ const next = { ...e.pos };
23
+ if (command === "left") next.x = minX;
24
+ if (command === "h-center") next.x = centerX;
25
+ if (command === "right") next.x = maxX;
26
+ if (command === "top") next.y = minY;
27
+ if (command === "v-center") next.y = centerY;
28
+ if (command === "bottom") next.y = maxY;
29
+ out[e.id] = next;
30
+ }
31
+ if (command === "h-gap") {
32
+ const gap = entries.length > 1 ? (maxX - minX) / (entries.length - 1) : 0;
33
+ sortByX.forEach((e, index) => {
34
+ out[e.id] = { ...e.pos, x: minX + gap * index };
35
+ });
36
+ }
37
+ if (command === "v-gap") {
38
+ const gap = entries.length > 1 ? (maxY - minY) / (entries.length - 1) : 0;
39
+ sortByY.forEach((e, index) => {
40
+ out[e.id] = { ...e.pos, y: minY + gap * index };
41
+ });
42
+ }
43
+ return out;
44
+ }
45
+
46
+ // src/index.ts
47
+ var LOGICAL_DATA_TYPES = [
48
+ "TEXT",
49
+ "DATE",
50
+ "TIME",
51
+ "DATETIME",
52
+ "NUMBER",
53
+ "DECIMAL",
54
+ "FLOAT",
55
+ "BOOLEAN",
56
+ "JSON",
57
+ "UUID",
58
+ "BINARY"
59
+ ];
60
+ function isRelationshipLineRenderable(rel, revealHiddenLines) {
61
+ if (rel.canvasLineHidden === true) return revealHiddenLines;
62
+ return true;
63
+ }
64
+ function migrateLegacyFkLineVisibility(model) {
65
+ for (const table of model.tables) {
66
+ for (const col of table.columns) {
67
+ const legacy = col.showFkRelationLine;
68
+ if (legacy === false && col.isForeignKey) {
69
+ for (const rel of model.relationships) {
70
+ if (rel.targetTableId === table.id && rel.targetColumnId === col.id) {
71
+ rel.canvasLineHidden = true;
72
+ }
73
+ }
74
+ }
75
+ if ("showFkRelationLine" in col) {
76
+ delete col.showFkRelationLine;
77
+ }
78
+ }
79
+ }
80
+ }
81
+ function isObject(value) {
82
+ return typeof value === "object" && value !== null;
83
+ }
84
+ var BUILTIN_DIALECTS = [
85
+ "mssql",
86
+ "oracle",
87
+ "mysql",
88
+ "postgres",
89
+ "sqlite"
90
+ ];
91
+ var DIALECT_DEFAULT_TYPE_MAP = {
92
+ mssql: {
93
+ TEXT: "NVARCHAR(255)",
94
+ DATE: "DATE",
95
+ TIME: "TIME",
96
+ DATETIME: "DATETIME2",
97
+ NUMBER: "INT",
98
+ DECIMAL: "DECIMAL(10,2)",
99
+ FLOAT: "FLOAT",
100
+ BOOLEAN: "BIT",
101
+ JSON: "NVARCHAR(MAX)",
102
+ UUID: "UNIQUEIDENTIFIER",
103
+ BINARY: "VARBINARY(255)"
104
+ },
105
+ oracle: {
106
+ TEXT: "VARCHAR2(255)",
107
+ DATE: "DATE",
108
+ TIME: "TIMESTAMP",
109
+ DATETIME: "TIMESTAMP",
110
+ NUMBER: "NUMBER(10)",
111
+ DECIMAL: "NUMBER(10,2)",
112
+ FLOAT: "BINARY_FLOAT",
113
+ BOOLEAN: "NUMBER(1)",
114
+ JSON: "CLOB",
115
+ UUID: "RAW(16)",
116
+ BINARY: "RAW(255)"
117
+ },
118
+ mysql: {
119
+ TEXT: "VARCHAR(255)",
120
+ DATE: "DATE",
121
+ TIME: "TIME",
122
+ DATETIME: "DATETIME",
123
+ NUMBER: "INT",
124
+ DECIMAL: "DECIMAL(10,2)",
125
+ FLOAT: "FLOAT",
126
+ BOOLEAN: "BOOLEAN",
127
+ JSON: "JSON",
128
+ UUID: "CHAR(36)",
129
+ BINARY: "VARBINARY(255)"
130
+ },
131
+ postgres: {
132
+ TEXT: "VARCHAR(255)",
133
+ DATE: "DATE",
134
+ TIME: "TIME",
135
+ DATETIME: "TIMESTAMP",
136
+ NUMBER: "INTEGER",
137
+ DECIMAL: "NUMERIC(10,2)",
138
+ FLOAT: "REAL",
139
+ BOOLEAN: "BOOLEAN",
140
+ JSON: "JSONB",
141
+ UUID: "UUID",
142
+ BINARY: "BYTEA"
143
+ },
144
+ sqlite: {
145
+ TEXT: "TEXT",
146
+ DATE: "TEXT",
147
+ TIME: "TEXT",
148
+ DATETIME: "TEXT",
149
+ NUMBER: "INTEGER",
150
+ DECIMAL: "NUMERIC(10,2)",
151
+ FLOAT: "REAL",
152
+ BOOLEAN: "INTEGER",
153
+ JSON: "TEXT",
154
+ UUID: "TEXT",
155
+ BINARY: "BLOB"
156
+ }
157
+ };
158
+ var DIALECT_CAPABILITIES = {
159
+ mssql: { supportsSchema: true },
160
+ oracle: { supportsSchema: true },
161
+ mysql: { supportsSchema: true },
162
+ postgres: { supportsSchema: true },
163
+ sqlite: { supportsSchema: false }
164
+ };
165
+ var DIALECT_LABELS = {
166
+ mssql: "MS SQL Server",
167
+ oracle: "Oracle",
168
+ mysql: "MySQL",
169
+ postgres: "PostgreSQL",
170
+ sqlite: "SQLite"
171
+ };
172
+ var DIALECT_DDL_STYLE = {
173
+ mssql: {
174
+ quote: "bracket",
175
+ boolLiteral: "oneZero",
176
+ nowKeyword: "GETDATE()"
177
+ },
178
+ oracle: {
179
+ quote: "double",
180
+ boolLiteral: "oneZero",
181
+ nowKeyword: "CURRENT_TIMESTAMP"
182
+ },
183
+ mysql: {
184
+ quote: "backtick",
185
+ boolLiteral: "oneZero",
186
+ nowKeyword: "CURRENT_TIMESTAMP"
187
+ },
188
+ postgres: {
189
+ quote: "double",
190
+ boolLiteral: "trueFalse",
191
+ nowKeyword: "CURRENT_TIMESTAMP"
192
+ },
193
+ sqlite: {
194
+ quote: "double",
195
+ boolLiteral: "oneZero",
196
+ nowKeyword: "CURRENT_TIMESTAMP"
197
+ }
198
+ };
199
+ function toLogicalTypeMetas(dialect) {
200
+ return LOGICAL_DATA_TYPES.map((id) => ({
201
+ id,
202
+ defaultPhysicalType: DIALECT_DEFAULT_TYPE_MAP[dialect][id]
203
+ }));
204
+ }
205
+ var BUILTIN_DIALECT_METAS_JSON = BUILTIN_DIALECTS.map((dialect) => ({
206
+ id: dialect,
207
+ label: DIALECT_LABELS[dialect],
208
+ supportsSchema: DIALECT_CAPABILITIES[dialect].supportsSchema,
209
+ logicalTypes: toLogicalTypeMetas(dialect),
210
+ ddlStyle: DIALECT_DDL_STYLE[dialect]
211
+ }));
212
+ function defaultDdlRules() {
213
+ return {
214
+ quoteIdentifier: (dialect, identifier) => {
215
+ if (dialect === "mssql") return `[${identifier}]`;
216
+ if (dialect === "mysql") return `\`${identifier}\``;
217
+ return `"${identifier}"`;
218
+ },
219
+ toDefaultExpression: (dialect, col) => {
220
+ const raw = col.defaultValue?.trim();
221
+ if (!raw) return null;
222
+ const upper = raw.toUpperCase();
223
+ const isFunctionLike = /[()]/.test(raw) || upper === "NULL" || upper === "CURRENT_TIMESTAMP" || upper === "CURRENT_DATE";
224
+ if (isFunctionLike) return raw;
225
+ if (col.logicalType === "NUMBER" || col.logicalType === "DECIMAL" || col.logicalType === "FLOAT")
226
+ return raw;
227
+ if (col.logicalType === "BOOLEAN") {
228
+ if (upper === "TRUE" || upper === "FALSE") {
229
+ if (dialect === "mssql" || dialect === "oracle" || dialect === "sqlite")
230
+ return upper === "TRUE" ? "1" : "0";
231
+ return upper;
232
+ }
233
+ if (raw === "1" || raw === "0") return raw;
234
+ return quoteSqlString(raw);
235
+ }
236
+ if (col.logicalType === "DATE" || col.logicalType === "DATETIME") {
237
+ if (dialect === "mssql" && upper === "NOW") return "GETDATE()";
238
+ if (upper === "NOW") return "CURRENT_TIMESTAMP";
239
+ return quoteSqlString(raw);
240
+ }
241
+ if (raw.startsWith("'") && raw.endsWith("'") || raw.startsWith('"') && raw.endsWith('"'))
242
+ return raw;
243
+ return quoteSqlString(raw);
244
+ }
245
+ };
246
+ }
247
+ function toAdapterDialectMeta(meta) {
248
+ const typeMap = {};
249
+ for (const lt of meta.logicalTypes) typeMap[lt.id] = lt.defaultPhysicalType;
250
+ return {
251
+ id: meta.id,
252
+ label: meta.label,
253
+ capabilities: { supportsSchema: meta.supportsSchema },
254
+ logicalTypes: meta.logicalTypes.map((lt) => lt.id),
255
+ defaultPhysicalTypeMap: typeMap
256
+ };
257
+ }
258
+ function mergeDialectMetas(base, host = []) {
259
+ const byId = new Map(
260
+ base.map((meta) => [meta.id, meta])
261
+ );
262
+ for (const meta of host) byId.set(meta.id, meta);
263
+ return Array.from(byId.values());
264
+ }
265
+ function resolveDialectMetas(options) {
266
+ return mergeDialectMetas(
267
+ BUILTIN_DIALECT_METAS_JSON,
268
+ options?.hostMetas ?? []
269
+ );
270
+ }
271
+ function buildDbMetaAdapterFromMetas(metas) {
272
+ const byId = new Map(
273
+ metas.map((meta) => [meta.id, toAdapterDialectMeta(meta)])
274
+ );
275
+ const defaultRules = defaultDdlRules();
276
+ return {
277
+ listDialects: () => Array.from(byId.values()),
278
+ getDialectMeta: (dialect) => byId.get(dialect),
279
+ getDefaultPhysicalType: (dialect, logicalType) => {
280
+ const mapped = byId.get(dialect)?.defaultPhysicalTypeMap[logicalType];
281
+ return mapped ?? DIALECT_DEFAULT_TYPE_MAP.postgres[logicalType];
282
+ },
283
+ getDdlRules: (dialect) => {
284
+ const json = metas.find((m) => m.id === dialect);
285
+ const style = json?.ddlStyle;
286
+ if (!style) return defaultRules;
287
+ const quoteIdentifier2 = (_d, identifier) => {
288
+ if (style.quote === "bracket") return `[${identifier}]`;
289
+ if (style.quote === "backtick") return `\`${identifier}\``;
290
+ return `"${identifier}"`;
291
+ };
292
+ const toDefaultExpression2 = (_d, col) => {
293
+ const raw = col.defaultValue?.trim();
294
+ if (!raw) return null;
295
+ const upper = raw.toUpperCase();
296
+ const isFunctionLike = /[()]/.test(raw) || upper === "NULL" || upper === "CURRENT_TIMESTAMP" || upper === "CURRENT_DATE";
297
+ if (isFunctionLike) return raw;
298
+ if (col.logicalType === "NUMBER" || col.logicalType === "DECIMAL" || col.logicalType === "FLOAT")
299
+ return raw;
300
+ if (col.logicalType === "BOOLEAN") {
301
+ if (upper === "TRUE" || upper === "FALSE") {
302
+ if ((style.boolLiteral ?? "oneZero") === "oneZero")
303
+ return upper === "TRUE" ? "1" : "0";
304
+ return upper;
305
+ }
306
+ if (raw === "1" || raw === "0") return raw;
307
+ return quoteSqlString(raw);
308
+ }
309
+ if (col.logicalType === "DATE" || col.logicalType === "DATETIME") {
310
+ if (upper === "NOW")
311
+ return style.nowKeyword ?? "CURRENT_TIMESTAMP";
312
+ return quoteSqlString(raw);
313
+ }
314
+ if (raw.startsWith("'") && raw.endsWith("'") || raw.startsWith('"') && raw.endsWith('"'))
315
+ return raw;
316
+ return quoteSqlString(raw);
317
+ };
318
+ return { quoteIdentifier: quoteIdentifier2, toDefaultExpression: toDefaultExpression2 };
319
+ }
320
+ };
321
+ }
322
+ function createDefaultDbMetaAdapter(overrides) {
323
+ const base = buildDbMetaAdapterFromMetas(BUILTIN_DIALECT_METAS_JSON);
324
+ return { ...base, ...overrides };
325
+ }
326
+ var defaultDbMetaAdapter = createDefaultDbMetaAdapter();
327
+ function resolveDbMetaAdapter(options) {
328
+ if (options?.dbMetaAdapter) return options.dbMetaAdapter;
329
+ if (options?.hostMetas && options.hostMetas.length > 0) {
330
+ return buildDbMetaAdapterFromMetas(resolveDialectMetas(options));
331
+ }
332
+ return defaultDbMetaAdapter;
333
+ }
334
+ function defaultPhysicalType(dialect, logicalType, options) {
335
+ return resolveDbMetaAdapter(options).getDefaultPhysicalType(
336
+ dialect,
337
+ logicalType
338
+ );
339
+ }
340
+ function normalizePhysicalCompare(s) {
341
+ return s.trim().toUpperCase().replace(/\s+/g, " ");
342
+ }
343
+ function physicalTypeBaseName(s) {
344
+ const n = normalizePhysicalCompare(s);
345
+ const p = n.indexOf("(");
346
+ return p >= 0 ? n.slice(0, p).trim() : n;
347
+ }
348
+ function inferLogicalTypeFromPhysical(dialect, physicalType, options) {
349
+ const raw = physicalType?.trim();
350
+ if (!raw) return "TEXT";
351
+ const metas = resolveDialectMetas(options);
352
+ const meta = metas.find((m) => m.id === dialect);
353
+ const normFull = normalizePhysicalCompare(raw);
354
+ const base = physicalTypeBaseName(raw);
355
+ if (meta) {
356
+ for (const lt of meta.logicalTypes) {
357
+ if (normalizePhysicalCompare(lt.defaultPhysicalType) === normFull) {
358
+ return lt.id;
359
+ }
360
+ }
361
+ for (const lt of meta.logicalTypes) {
362
+ if (physicalTypeBaseName(lt.defaultPhysicalType) === base) {
363
+ return lt.id;
364
+ }
365
+ }
366
+ }
367
+ const b = base;
368
+ if (b === "INT" || b === "INTEGER" || b === "BIGINT" || b === "SMALLINT" || b === "TINYINT" || b === "NUMBER" || b === "SERIAL" || b === "SERIAL4" || b === "SERIAL8") {
369
+ return "NUMBER";
370
+ }
371
+ if (b === "DECIMAL" || b === "NUMERIC" || b === "MONEY" || b === "SMALLMONEY") {
372
+ return "DECIMAL";
373
+ }
374
+ if (b === "FLOAT" || b === "REAL" || b === "DOUBLE" || b === "BINARY_FLOAT" || b === "BINARY_DOUBLE") {
375
+ return "FLOAT";
376
+ }
377
+ if (b === "BIT" || b === "BOOLEAN" || b === "BOOL") {
378
+ return "BOOLEAN";
379
+ }
380
+ if (b === "DATETIME" || b === "DATETIME2" || b === "SMALLDATETIME" || b === "TIMESTAMP" || b === "TIMESTAMPTZ") {
381
+ return "DATETIME";
382
+ }
383
+ if (b === "DATE") return "DATE";
384
+ if (b === "TIME" || b === "TIMETZ") return "TIME";
385
+ if (b.includes("CHAR") || b === "TEXT" || b === "CLOB" || b === "NCLOB" || b === "NCHAR" || b === "NVARCHAR" || b === "VARCHAR" || b === "VARCHAR2") {
386
+ return "TEXT";
387
+ }
388
+ if (b === "JSON" || b === "JSONB") return "JSON";
389
+ if (b === "UUID" || b === "UNIQUEIDENTIFIER") return "UUID";
390
+ if (b === "BINARY" || b === "VARBINARY" || b === "RAW" || b === "BYTEA" || b === "BLOB" || b === "IMAGE") {
391
+ return "BINARY";
392
+ }
393
+ return "TEXT";
394
+ }
395
+ function getRdbmsDialectCapability(dialect, options) {
396
+ const meta = resolveDbMetaAdapter(options).getDialectMeta(dialect);
397
+ return meta?.capabilities ?? { supportsSchema: false };
398
+ }
399
+ function dialectSupportsSchema(dialect, options) {
400
+ return getRdbmsDialectCapability(dialect, options).supportsSchema;
401
+ }
402
+ function parseTypeArguments(physicalType) {
403
+ const m = physicalType.match(/\(([^)]+)\)/);
404
+ if (!m) return null;
405
+ const values = m[1].split(",").map((v) => Number.parseInt(v.trim(), 10)).filter((v) => Number.isFinite(v));
406
+ return values.length > 0 ? values : null;
407
+ }
408
+ function convertPhysicalTypeByLogicalType(physicalType, logicalType, nextDialect, options) {
409
+ const args = parseTypeArguments(physicalType);
410
+ const precision = args?.[0];
411
+ const scale = args?.[1];
412
+ const length = args?.[0];
413
+ if (logicalType === "NUMBER") {
414
+ if (nextDialect === "oracle") {
415
+ if (precision && scale !== void 0)
416
+ return `NUMBER(${precision},${scale})`;
417
+ if (precision) return `NUMBER(${precision})`;
418
+ return "NUMBER(10)";
419
+ }
420
+ if (nextDialect === "mssql") {
421
+ if (precision && scale !== void 0)
422
+ return `NUMERIC(${precision},${scale})`;
423
+ if (precision) return `NUMERIC(${precision},0)`;
424
+ return "INT";
425
+ }
426
+ if (nextDialect === "mysql") {
427
+ if (precision && scale !== void 0)
428
+ return `DECIMAL(${precision},${scale})`;
429
+ if (precision) return `DECIMAL(${precision},0)`;
430
+ return "INT";
431
+ }
432
+ if (nextDialect === "postgres") {
433
+ if (precision && scale !== void 0)
434
+ return `NUMERIC(${precision},${scale})`;
435
+ if (precision) return `NUMERIC(${precision},0)`;
436
+ return "INTEGER";
437
+ }
438
+ return "INTEGER";
439
+ }
440
+ if (logicalType === "DECIMAL") {
441
+ if (precision && scale !== void 0) {
442
+ if (nextDialect === "oracle")
443
+ return `NUMBER(${precision},${scale})`;
444
+ if (nextDialect === "postgres")
445
+ return `NUMERIC(${precision},${scale})`;
446
+ return `DECIMAL(${precision},${scale})`;
447
+ }
448
+ return defaultPhysicalType(nextDialect, logicalType, options);
449
+ }
450
+ if (logicalType === "FLOAT") {
451
+ if (nextDialect === "oracle") return "BINARY_FLOAT";
452
+ if (nextDialect === "sqlite") return "REAL";
453
+ return "FLOAT";
454
+ }
455
+ if (logicalType === "TEXT") {
456
+ if (nextDialect === "oracle")
457
+ return length ? `VARCHAR2(${length})` : "VARCHAR2(255)";
458
+ if (nextDialect === "mssql")
459
+ return length ? `NVARCHAR(${length})` : "NVARCHAR(255)";
460
+ if (nextDialect === "mysql")
461
+ return length ? `VARCHAR(${length})` : "VARCHAR(255)";
462
+ if (nextDialect === "postgres")
463
+ return length ? `VARCHAR(${length})` : "VARCHAR(255)";
464
+ return "TEXT";
465
+ }
466
+ if (logicalType === "BINARY") {
467
+ if (nextDialect === "mssql")
468
+ return length ? `VARBINARY(${length})` : "VARBINARY(255)";
469
+ if (nextDialect === "oracle")
470
+ return length ? `RAW(${length})` : "RAW(255)";
471
+ if (nextDialect === "mysql")
472
+ return length ? `VARBINARY(${length})` : "VARBINARY(255)";
473
+ if (nextDialect === "postgres") return "BYTEA";
474
+ return "BLOB";
475
+ }
476
+ return defaultPhysicalType(nextDialect, logicalType, options);
477
+ }
478
+ function convertDesignDialect(doc, nextDialect, options) {
479
+ if (doc.model.dialect === nextDialect) return doc;
480
+ const supportsSchema = dialectSupportsSchema(nextDialect, options);
481
+ return {
482
+ ...doc,
483
+ model: {
484
+ ...doc.model,
485
+ dialect: nextDialect,
486
+ tables: doc.model.tables.map((table) => ({
487
+ ...table,
488
+ schemaName: supportsSchema ? table.schemaName : void 0,
489
+ columns: table.columns.map((col) => ({
490
+ ...col,
491
+ physicalType: convertPhysicalTypeByLogicalType(
492
+ col.physicalType,
493
+ col.logicalType,
494
+ nextDialect,
495
+ options
496
+ )
497
+ }))
498
+ }))
499
+ }
500
+ };
501
+ }
502
+ function applyLogicalTypeChange(column, nextLogicalType, dialect, options) {
503
+ return {
504
+ ...column,
505
+ logicalType: nextLogicalType,
506
+ physicalType: defaultPhysicalType(dialect, nextLogicalType, options)
507
+ };
508
+ }
509
+ function createColumn(dialect, params, options) {
510
+ const physicalName = params.physicalName ?? params.logicalName;
511
+ const isPrimaryKey = params.isPrimaryKey ?? false;
512
+ const isForeignKey = params.isForeignKey ?? false;
513
+ const col = {
514
+ id: params.id,
515
+ logicalName: params.logicalName,
516
+ physicalName,
517
+ description: params.description,
518
+ logicalType: params.logicalType,
519
+ physicalType: defaultPhysicalType(dialect, params.logicalType, options),
520
+ defaultValue: params.defaultValue,
521
+ nullable: isPrimaryKey ? false : params.nullable ?? true,
522
+ isPrimaryKey,
523
+ isForeignKey,
524
+ referencesPrimaryColumnId: params.referencesPrimaryColumnId
525
+ };
526
+ if (params.color !== void 0) col.color = params.color;
527
+ return col;
528
+ }
529
+ function createEmptyDesign(dialect = "mssql") {
530
+ return {
531
+ schemaVersion: 1,
532
+ model: {
533
+ dialect,
534
+ tables: [],
535
+ relationships: [],
536
+ indexes: []
537
+ },
538
+ layout: {
539
+ nodePositions: {}
540
+ }
541
+ };
542
+ }
543
+ function serializeDesign(doc) {
544
+ return JSON.stringify(doc, null, 2);
545
+ }
546
+ function parseDesign(json, options) {
547
+ const parsed = JSON.parse(json);
548
+ return validateDesignDocument(parsed, options);
549
+ }
550
+ function validateDesignDocument(input, options) {
551
+ if (!isObject(input)) {
552
+ throw new Error("Invalid design document: root must be object");
553
+ }
554
+ if (input.schemaVersion !== 1) {
555
+ throw new Error("Unsupported schemaVersion");
556
+ }
557
+ if (!isObject(input.model) || !Array.isArray(input.model.tables) || !Array.isArray(input.model.relationships) || !Array.isArray(input.model.indexes)) {
558
+ throw new Error("Invalid design document: model is malformed");
559
+ }
560
+ const dialects = new Set(
561
+ resolveDbMetaAdapter(options).listDialects().map((d) => d.id)
562
+ );
563
+ const modelDialect = input.model.dialect;
564
+ if (typeof modelDialect !== "string" || !dialects.has(modelDialect)) {
565
+ throw new Error("Invalid design document: model.dialect is invalid");
566
+ }
567
+ if (!isObject(input.layout) || !isObject(input.layout.nodePositions)) {
568
+ throw new Error("Invalid design document: layout is malformed");
569
+ }
570
+ const doc = input;
571
+ migrateLegacyFkLineVisibility(doc.model);
572
+ return doc;
573
+ }
574
+ function roundTripDesign(doc) {
575
+ return parseDesign(serializeDesign(doc));
576
+ }
577
+ function quoteIdentifier(dialect, identifier, options) {
578
+ const quote = resolveDbMetaAdapter(options).getDdlRules(dialect).quoteIdentifier ?? defaultDdlRules().quoteIdentifier;
579
+ return quote(dialect, identifier);
580
+ }
581
+ function qualifiedTableName(table, dialect, options) {
582
+ const schema = table.schemaName?.trim();
583
+ if (dialectSupportsSchema(dialect, options) && schema) {
584
+ return `${quoteIdentifier(dialect, schema, options)}.${quoteIdentifier(dialect, table.physicalName, options)}`;
585
+ }
586
+ return quoteIdentifier(dialect, table.physicalName, options);
587
+ }
588
+ function quoteSqlString(value) {
589
+ return `'${value.replaceAll("'", "''")}'`;
590
+ }
591
+ function toDefaultExpression(dialect, col, options) {
592
+ const handler = resolveDbMetaAdapter(options).getDdlRules(dialect).toDefaultExpression ?? defaultDdlRules().toDefaultExpression;
593
+ return handler(dialect, col);
594
+ }
595
+ function joinColumnDefs(table, dialect, options) {
596
+ const defs = table.columns.map((col) => {
597
+ const nullable = col.nullable ? "NULL" : "NOT NULL";
598
+ const defaultExpr = toDefaultExpression(dialect, col, options);
599
+ const defaultSql = defaultExpr ? ` DEFAULT ${defaultExpr}` : "";
600
+ return ` ${quoteIdentifier(dialect, col.physicalName, options)} ${col.physicalType}${defaultSql} ${nullable}`;
601
+ });
602
+ const pkColumns = table.columns.filter((col) => col.isPrimaryKey).map((col) => quoteIdentifier(dialect, col.physicalName, options));
603
+ if (pkColumns.length > 0) {
604
+ defs.push(` PRIMARY KEY (${pkColumns.join(", ")})`);
605
+ }
606
+ return defs;
607
+ }
608
+ function createTableSql(table, dialect, options) {
609
+ const columns = joinColumnDefs(table, dialect, options);
610
+ return `CREATE TABLE ${qualifiedTableName(table, dialect, options)} (
611
+ ${columns.join(",\n")}
612
+ );`;
613
+ }
614
+ function createRelationshipSql(rel, model, dialect, index, options) {
615
+ const sourceTable = model.tables.find(
616
+ (table) => table.id === rel.sourceTableId
617
+ );
618
+ const targetTable = model.tables.find(
619
+ (table) => table.id === rel.targetTableId
620
+ );
621
+ if (!sourceTable || !targetTable || !rel.sourceColumnId || !rel.targetColumnId) {
622
+ return null;
623
+ }
624
+ const sourceColumn = sourceTable.columns.find(
625
+ (col) => col.id === rel.sourceColumnId
626
+ );
627
+ const targetColumn = targetTable.columns.find(
628
+ (col) => col.id === rel.targetColumnId
629
+ );
630
+ if (!sourceColumn || !targetColumn) {
631
+ return null;
632
+ }
633
+ const fkName = `FK_${targetTable.physicalName}_${sourceTable.physicalName}_${index + 1}`;
634
+ return [
635
+ `ALTER TABLE ${qualifiedTableName(targetTable, dialect, options)}`,
636
+ ` ADD CONSTRAINT ${quoteIdentifier(dialect, fkName, options)}`,
637
+ ` FOREIGN KEY (${quoteIdentifier(dialect, targetColumn.physicalName, options)})`,
638
+ ` REFERENCES ${qualifiedTableName(sourceTable, dialect, options)} (${quoteIdentifier(dialect, sourceColumn.physicalName, options)});`
639
+ ].join("\n");
640
+ }
641
+ function createIndexSql(indexModel, model, dialect, options) {
642
+ const table = model.tables.find((item) => item.id === indexModel.tableId);
643
+ if (!table || indexModel.columns.length === 0) {
644
+ return null;
645
+ }
646
+ const unique = indexModel.unique ? "UNIQUE " : "";
647
+ const columns = indexModel.columns.map((col) => quoteIdentifier(dialect, col, options)).join(", ");
648
+ return `CREATE ${unique}INDEX ${quoteIdentifier(dialect, indexModel.name, options)} ON ${qualifiedTableName(table, dialect, options)} (${columns});`;
649
+ }
650
+ function buildDdlSql(doc, options) {
651
+ const { model } = doc;
652
+ const tableSql = model.tables.map(
653
+ (table) => createTableSql(table, model.dialect, options)
654
+ );
655
+ const relSql = model.relationships.map(
656
+ (rel, index) => createRelationshipSql(rel, model, model.dialect, index, options)
657
+ ).filter((item) => Boolean(item));
658
+ return [...tableSql, ...relSql].join("\n\n");
659
+ }
660
+ function buildIndexDdlSql(doc, options) {
661
+ const { model } = doc;
662
+ const statements = model.indexes.map((index) => createIndexSql(index, model, model.dialect, options)).filter((item) => Boolean(item));
663
+ return statements.join("\n\n");
664
+ }
665
+ function sliceDocByScope(doc, scope) {
666
+ if (scope.kind === "all") return doc;
667
+ const selected = new Set(scope.tableIds);
668
+ return {
669
+ ...doc,
670
+ model: {
671
+ ...doc.model,
672
+ tables: doc.model.tables.filter((t) => selected.has(t.id)),
673
+ relationships: doc.model.relationships.filter(
674
+ (r) => selected.has(r.sourceTableId) && selected.has(r.targetTableId)
675
+ ),
676
+ indexes: doc.model.indexes.filter((i) => selected.has(i.tableId))
677
+ }
678
+ };
679
+ }
680
+ function styleBasedDdlGenerator(input, options) {
681
+ const scoped = sliceDocByScope(input.doc, input.scope);
682
+ return {
683
+ sql: buildDdlSql(scoped, options),
684
+ diagnostics: analyzeDdlDocument(scoped, options)
685
+ };
686
+ }
687
+ function hasBuiltinDialect(dialectId) {
688
+ return BUILTIN_DIALECTS.includes(dialectId);
689
+ }
690
+ function getBuiltinDdlGenerator(dialectId, _options) {
691
+ if (!hasBuiltinDialect(dialectId)) return void 0;
692
+ return (input) => styleBasedDdlGenerator(input, _options);
693
+ }
694
+ function invokeDdlGeneratorSync(generator, input) {
695
+ const result = generator(input);
696
+ if (result && typeof result.then === "function") {
697
+ throw new Error("Async DDL generator is not supported in sync API");
698
+ }
699
+ return result;
700
+ }
701
+ function runDdlGenerator(doc, scope, options) {
702
+ const input = {
703
+ doc,
704
+ dialectId: doc.model.dialect,
705
+ scope
706
+ };
707
+ const hostGenerator = options?.hostDdlGenerators?.[doc.model.dialect];
708
+ const builtinGenerator = getBuiltinDdlGenerator(doc.model.dialect, options);
709
+ const fallback = () => styleBasedDdlGenerator(input, options);
710
+ const fallbackOnError = options?.fallbackOnHookError ?? true;
711
+ const selectedGenerator = hostGenerator ?? builtinGenerator;
712
+ if (!selectedGenerator) return fallback();
713
+ try {
714
+ return invokeDdlGeneratorSync(selectedGenerator, input);
715
+ } catch (error) {
716
+ if (!fallbackOnError) throw error;
717
+ return fallback();
718
+ }
719
+ }
720
+ function formatDdlDiagnostic(d) {
721
+ const level = d.severity === "error" ? "ERROR" : "WARN";
722
+ const tail = d.context ? ` | ${d.context}` : "";
723
+ return `[${level}][${d.code}] ${d.message}${tail}`;
724
+ }
725
+ function formatDdlDiagnostics(diagnostics) {
726
+ return diagnostics.map(formatDdlDiagnostic).join("\n");
727
+ }
728
+ function analyzeDdlDocument(doc, options) {
729
+ const out = [];
730
+ const { model } = doc;
731
+ const tableById = new Map(model.tables.map((t) => [t.id, t]));
732
+ const physicalTableNames = /* @__PURE__ */ new Map();
733
+ for (const table of model.tables) {
734
+ const qualifiedName = dialectSupportsSchema(model.dialect, options) && table.schemaName?.trim() ? `${table.schemaName.trim()}.${table.physicalName}` : table.physicalName;
735
+ const list = physicalTableNames.get(qualifiedName) ?? [];
736
+ list.push(table.id);
737
+ physicalTableNames.set(qualifiedName, list);
738
+ if (table.columns.length === 0) {
739
+ out.push({
740
+ severity: "warning",
741
+ code: "DDL_EMPTY_TABLE",
742
+ message: "\uCEEC\uB7FC\uC774 \uC5C6\uB294 \uD14C\uC774\uBE14\uC740 CREATE TABLE \uAD6C\uBB38\uC774 \uBE44\uC5B4 \uC788\uAC70\uB098 \uBB34\uC758\uBBF8\uD560 \uC218 \uC788\uB2E4.",
743
+ context: `table:${table.id}`
744
+ });
745
+ }
746
+ }
747
+ for (const [name, ids] of physicalTableNames) {
748
+ if (ids.length > 1) {
749
+ out.push({
750
+ severity: "warning",
751
+ code: "DDL_DUPLICATE_TABLE_NAME",
752
+ message: `\uB3D9\uC77C\uD55C \uBB3C\uB9AC \uD14C\uC774\uBE14\uBA85 "${name}"\uC774 ${ids.length}\uAC1C \uD14C\uC774\uBE14\uC5D0 \uC0AC\uC6A9\uB418\uC5C8\uB2E4.`,
753
+ context: `tables:${ids.join(",")}`
754
+ });
755
+ }
756
+ }
757
+ for (const rel of model.relationships) {
758
+ const ctx = `relationship:${rel.id}`;
759
+ const sourceTable = tableById.get(rel.sourceTableId);
760
+ const targetTable = tableById.get(rel.targetTableId);
761
+ if (!sourceTable || !targetTable) {
762
+ out.push({
763
+ severity: "error",
764
+ code: "DDL_REL_UNKNOWN_TABLE",
765
+ message: "\uAD00\uACC4\uC758 \uC18C\uC2A4 \uB610\uB294 \uD0C0\uAC9F \uD14C\uC774\uBE14\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4 FK DDL\uC744 \uC0DD\uC131\uD560 \uC218 \uC5C6\uB2E4.",
766
+ context: ctx
767
+ });
768
+ continue;
769
+ }
770
+ if (!rel.sourceColumnId || !rel.targetColumnId) {
771
+ out.push({
772
+ severity: "warning",
773
+ code: "DDL_REL_MISSING_COLUMNS",
774
+ message: "\uC18C\uC2A4/\uD0C0\uAC9F \uCEEC\uB7FC\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC544 FK DDL\uC744 \uC0DD\uB7B5\uD55C\uB2E4.",
775
+ context: ctx
776
+ });
777
+ continue;
778
+ }
779
+ const sourceColumn = sourceTable.columns.find(
780
+ (c) => c.id === rel.sourceColumnId
781
+ );
782
+ const targetColumn = targetTable.columns.find(
783
+ (c) => c.id === rel.targetColumnId
784
+ );
785
+ if (!sourceColumn || !targetColumn) {
786
+ out.push({
787
+ severity: "warning",
788
+ code: "DDL_REL_UNKNOWN_COLUMN",
789
+ message: "\uAD00\uACC4\uC5D0 \uC9C0\uC815\uB41C \uCEEC\uB7FC id\uB97C \uD14C\uC774\uBE14\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC5B4 FK DDL\uC744 \uC0DD\uB7B5\uD55C\uB2E4.",
790
+ context: ctx
791
+ });
792
+ }
793
+ }
794
+ for (const indexModel of model.indexes) {
795
+ const ctx = `index:${indexModel.id}`;
796
+ const table = tableById.get(indexModel.tableId);
797
+ if (!table) {
798
+ out.push({
799
+ severity: "warning",
800
+ code: "IDX_UNKNOWN_TABLE",
801
+ message: "\uC778\uB371\uC2A4\uAC00 \uAC00\uB9AC\uD0A4\uB294 \uD14C\uC774\uBE14\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4 \uC778\uB371\uC2A4 DDL\uC744 \uC0DD\uB7B5\uD55C\uB2E4.",
802
+ context: ctx
803
+ });
804
+ continue;
805
+ }
806
+ if (indexModel.columns.length === 0) {
807
+ out.push({
808
+ severity: "warning",
809
+ code: "IDX_EMPTY_COLUMNS",
810
+ message: "\uC778\uB371\uC2A4 \uCEEC\uB7FC \uBAA9\uB85D\uC774 \uBE44\uC5B4 \uC788\uC5B4 \uC778\uB371\uC2A4 DDL\uC744 \uC0DD\uB7B5\uD55C\uB2E4.",
811
+ context: ctx
812
+ });
813
+ continue;
814
+ }
815
+ for (const colPhys of indexModel.columns) {
816
+ if (!table.columns.some((c) => c.physicalName === colPhys)) {
817
+ out.push({
818
+ severity: "warning",
819
+ code: "IDX_UNKNOWN_COLUMN",
820
+ message: `\uC778\uB371\uC2A4\uAC00 \uCC38\uC870\uD558\uB294 \uBB3C\uB9AC \uCEEC\uB7FC\uBA85 "${colPhys}"\uC744(\uB97C) \uD14C\uC774\uBE14 "${table.physicalName}"\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uB2E4.`,
821
+ context: ctx
822
+ });
823
+ }
824
+ }
825
+ }
826
+ return out;
827
+ }
828
+ function generateDdlWithDiagnostics(doc, options) {
829
+ const out = runDdlGenerator(doc, { kind: "all" }, options);
830
+ return {
831
+ sql: out.sql,
832
+ diagnostics: out.diagnostics ?? analyzeDdlDocument(doc, options)
833
+ };
834
+ }
835
+ function generateIndexDdlWithDiagnostics(doc, options) {
836
+ return {
837
+ sql: buildIndexDdlSql(doc, options),
838
+ diagnostics: analyzeDdlDocument(doc, options)
839
+ };
840
+ }
841
+ function generateDdl(doc, options) {
842
+ return runDdlGenerator(doc, { kind: "all" }, options).sql;
843
+ }
844
+ function generateDdlForSelection(doc, tableIds, options) {
845
+ const out = runDdlGenerator(doc, { kind: "selected", tableIds }, options);
846
+ const diagnostics = out.diagnostics ?? analyzeDdlDocument(
847
+ sliceDocByScope(doc, { kind: "selected", tableIds }),
848
+ options
849
+ );
850
+ return { sql: out.sql, diagnostics };
851
+ }
852
+ function generateIndexDdl(doc, options) {
853
+ return buildIndexDdlSql(doc, options);
854
+ }
855
+ function createLargeDesign(tableCount, dialect = "postgres") {
856
+ const doc = createEmptyDesign(dialect);
857
+ const cols = 20;
858
+ for (let i = 0; i < tableCount; i++) {
859
+ const id = `t-${i}`;
860
+ doc.model.tables.push({
861
+ id,
862
+ logicalName: `\uC5D4\uD2F0\uD2F0${i}`,
863
+ physicalName: `TB_T${i}`,
864
+ columns: [
865
+ createColumn(dialect, {
866
+ id: `${id}-pk`,
867
+ logicalName: "\uC2DD\uBCC4\uC790",
868
+ logicalType: "NUMBER",
869
+ nullable: false
870
+ })
871
+ ]
872
+ });
873
+ doc.layout.nodePositions[id] = {
874
+ x: i % cols * 220,
875
+ y: Math.floor(i / cols) * 120
876
+ };
877
+ }
878
+ return doc;
879
+ }
880
+
881
+ export { BUILTIN_DIALECTS, BUILTIN_DIALECT_METAS_JSON, LOGICAL_DATA_TYPES, alignNodePositions, analyzeDdlDocument, applyLogicalTypeChange, convertDesignDialect, createColumn, createDefaultDbMetaAdapter, createEmptyDesign, createLargeDesign, defaultDbMetaAdapter, defaultPhysicalType, dialectSupportsSchema, formatDdlDiagnostic, formatDdlDiagnostics, generateDdl, generateDdlForSelection, generateDdlWithDiagnostics, generateIndexDdl, generateIndexDdlWithDiagnostics, getRdbmsDialectCapability, inferLogicalTypeFromPhysical, isRelationshipLineRenderable, mergeDialectMetas, migrateLegacyFkLineVisibility, parseDesign, resolveDialectMetas, roundTripDesign, serializeDesign, validateDesignDocument };
882
+ //# sourceMappingURL=index.js.map
883
+ //# sourceMappingURL=index.js.map