diff --git a/.github/workflows/scripts/collect-coverage.js b/.github/workflows/scripts/collect-coverage.ts similarity index 100% rename from .github/workflows/scripts/collect-coverage.js rename to .github/workflows/scripts/collect-coverage.ts diff --git a/dbml-homepage/babel.config.js b/dbml-homepage/babel.config.ts similarity index 100% rename from dbml-homepage/babel.config.js rename to dbml-homepage/babel.config.ts diff --git a/dbml-playground/tailwind.config.js b/dbml-playground/tailwind.config.ts similarity index 100% rename from dbml-playground/tailwind.config.js rename to dbml-playground/tailwind.config.ts diff --git a/packages/dbml-cli/__tests__/db2dbml_bin.js b/packages/dbml-cli/__tests__/db2dbml_bin.ts similarity index 100% rename from packages/dbml-cli/__tests__/db2dbml_bin.js rename to packages/dbml-cli/__tests__/db2dbml_bin.ts diff --git a/packages/dbml-cli/__tests__/dbml2sql_bin.js b/packages/dbml-cli/__tests__/dbml2sql_bin.ts similarity index 100% rename from packages/dbml-cli/__tests__/dbml2sql_bin.js rename to packages/dbml-cli/__tests__/dbml2sql_bin.ts diff --git a/packages/dbml-cli/__tests__/sql2dbml_bin.js b/packages/dbml-cli/__tests__/sql2dbml_bin.ts similarity index 100% rename from packages/dbml-cli/__tests__/sql2dbml_bin.js rename to packages/dbml-cli/__tests__/sql2dbml_bin.ts diff --git a/packages/dbml-cli/src/cli/config.js b/packages/dbml-cli/src/cli/config.ts similarity index 100% rename from packages/dbml-cli/src/cli/config.js rename to packages/dbml-cli/src/cli/config.ts diff --git a/packages/dbml-cli/src/cli/connector.js b/packages/dbml-cli/src/cli/connector.ts similarity index 100% rename from packages/dbml-cli/src/cli/connector.js rename to packages/dbml-cli/src/cli/connector.ts diff --git a/packages/dbml-cli/src/cli/export.js b/packages/dbml-cli/src/cli/export.ts similarity index 100% rename from packages/dbml-cli/src/cli/export.js rename to packages/dbml-cli/src/cli/export.ts diff --git a/packages/dbml-cli/src/cli/import.js b/packages/dbml-cli/src/cli/import.ts similarity index 100% rename from packages/dbml-cli/src/cli/import.js rename to packages/dbml-cli/src/cli/import.ts diff --git a/packages/dbml-cli/src/cli/index.js b/packages/dbml-cli/src/cli/index.ts similarity index 100% rename from packages/dbml-cli/src/cli/index.js rename to packages/dbml-cli/src/cli/index.ts diff --git a/packages/dbml-cli/src/cli/outputPlugins/outputConsolePlugin.js b/packages/dbml-cli/src/cli/outputPlugins/outputConsolePlugin.ts similarity index 100% rename from packages/dbml-cli/src/cli/outputPlugins/outputConsolePlugin.js rename to packages/dbml-cli/src/cli/outputPlugins/outputConsolePlugin.ts diff --git a/packages/dbml-cli/src/cli/outputPlugins/outputFilePlugin.js b/packages/dbml-cli/src/cli/outputPlugins/outputFilePlugin.ts similarity index 100% rename from packages/dbml-cli/src/cli/outputPlugins/outputFilePlugin.js rename to packages/dbml-cli/src/cli/outputPlugins/outputFilePlugin.ts diff --git a/packages/dbml-cli/src/cli/utils.js b/packages/dbml-cli/src/cli/utils.ts similarity index 100% rename from packages/dbml-cli/src/cli/utils.js rename to packages/dbml-cli/src/cli/utils.ts diff --git a/packages/dbml-cli/src/cli/validatePlugins/validatePlugins.js b/packages/dbml-cli/src/cli/validatePlugins/validatePlugins.ts similarity index 100% rename from packages/dbml-cli/src/cli/validatePlugins/validatePlugins.js rename to packages/dbml-cli/src/cli/validatePlugins/validatePlugins.ts diff --git a/packages/dbml-cli/src/errors/domainError.js b/packages/dbml-cli/src/errors/domainError.ts similarity index 100% rename from packages/dbml-cli/src/errors/domainError.js rename to packages/dbml-cli/src/errors/domainError.ts diff --git a/packages/dbml-cli/src/errors/index.js b/packages/dbml-cli/src/errors/index.ts similarity index 100% rename from packages/dbml-cli/src/errors/index.js rename to packages/dbml-cli/src/errors/index.ts diff --git a/packages/dbml-cli/src/errors/syntaxError.js b/packages/dbml-cli/src/errors/syntaxError.ts similarity index 100% rename from packages/dbml-cli/src/errors/syntaxError.js rename to packages/dbml-cli/src/errors/syntaxError.ts diff --git a/packages/dbml-cli/src/helpers/logger.js b/packages/dbml-cli/src/helpers/logger.ts similarity index 100% rename from packages/dbml-cli/src/helpers/logger.js rename to packages/dbml-cli/src/helpers/logger.ts diff --git a/packages/dbml-cli/src/index.js b/packages/dbml-cli/src/index.ts similarity index 100% rename from packages/dbml-cli/src/index.js rename to packages/dbml-cli/src/index.ts diff --git a/packages/dbml-core/__benchmarks__/mysql.bench.js b/packages/dbml-core/__benchmarks__/mysql.bench.ts similarity index 100% rename from packages/dbml-core/__benchmarks__/mysql.bench.js rename to packages/dbml-core/__benchmarks__/mysql.bench.ts diff --git a/packages/dbml-core/__benchmarks__/postgres.bench.js b/packages/dbml-core/__benchmarks__/postgres.bench.ts similarity index 100% rename from packages/dbml-core/__benchmarks__/postgres.bench.js rename to packages/dbml-core/__benchmarks__/postgres.bench.ts diff --git a/packages/dbml-core/__tests__/examples/exporter/exporter.spec.ts b/packages/dbml-core/__tests__/examples/exporter/exporter.spec.ts index 0390aae08..9434fc477 100644 --- a/packages/dbml-core/__tests__/examples/exporter/exporter.spec.ts +++ b/packages/dbml-core/__tests__/examples/exporter/exporter.spec.ts @@ -5,6 +5,18 @@ import { readFileSync } from 'fs'; import path from 'path'; import { test, expect, describe } from 'vitest'; +const DBML_WITH_RECORDS = ` +Table users { + id integer [pk] + name varchar +} + +Records users(id, name) { + 1, 'Alice' + 2, 'Bob' +} +`.trim(); + describe('@dbml/core - exporter', () => { const runTest = async (fileName: string, testDir: string, format: ExportFormatOption) => { const fileExtension = getFileExtension(format); @@ -29,3 +41,39 @@ describe('@dbml/core - exporter', () => { }); } }); + +const EXPECTED_DBML_WITH_RECORDS = +`Table "users" { + "id" integer [pk] + "name" varchar +} + +Records users(id, name) { + 1, 'Alice' + 2, 'Bob' +}`; + +const EXPECTED_DBML_WITHOUT_RECORDS = +`Table "users" { + "id" integer [pk] + "name" varchar +}`; + +describe('@dbml/core - exporter flags', () => { + describe('includeRecords', () => { + test('includes records by default', () => { + const res = exporter.export(DBML_WITH_RECORDS, 'dbml'); + expect(res.trim()).toBe(EXPECTED_DBML_WITH_RECORDS); + }); + + test('includes records when includeRecords is true', () => { + const res = exporter.export(DBML_WITH_RECORDS, 'dbml', { includeRecords: true }); + expect(res.trim()).toBe(EXPECTED_DBML_WITH_RECORDS); + }); + + test('omits records when includeRecords is false', () => { + const res = exporter.export(DBML_WITH_RECORDS, 'dbml', { includeRecords: false }); + expect(res.trim()).toBe(EXPECTED_DBML_WITHOUT_RECORDS); + }); + }); +}); diff --git a/packages/dbml-core/__tests__/examples/model_exporter/model_exporter.spec.ts b/packages/dbml-core/__tests__/examples/model_exporter/model_exporter.spec.ts index 214f47b56..8cd9f0301 100644 --- a/packages/dbml-core/__tests__/examples/model_exporter/model_exporter.spec.ts +++ b/packages/dbml-core/__tests__/examples/model_exporter/model_exporter.spec.ts @@ -5,12 +5,42 @@ import MysqlExporter from '../../../src/export/MysqlExporter'; import PostgresExporter from '../../../src/export/PostgresExporter'; import SqlServerExporter from '../../../src/export/SqlServerExporter'; import OracleExporter from '../../../src/export/OracleExporter'; +import ModelExporter from '../../../src/export/ModelExporter'; import { scanTestNames, getFileExtension, isEqualExcludeTokenEmpty } from '../testHelpers'; import { ExportFormatOption } from '../../../types/export/ModelExporter'; import { readFileSync } from 'fs'; import path from 'path'; import { test, expect, describe } from 'vitest'; +const DBML_WITH_RECORDS = ` +Table users { + id integer [pk] + name varchar +} + +Records users(id, name) { + 1, 'Alice' + 2, 'Bob' +} +`.trim(); + +const EXPECTED_DBML_WITH_RECORDS = +`Table "users" { + "id" integer [pk] + "name" varchar +} + +Records users(id, name) { + 1, 'Alice' + 2, 'Bob' +}`; + +const EXPECTED_DBML_WITHOUT_RECORDS = +`Table "users" { + "id" integer [pk] + "name" varchar +}`; + type ExporterClass = | typeof DbmlExporter | typeof JsonExporter @@ -114,3 +144,69 @@ describe('@dbml/core - model_exporter dbml_exporter.escapeNote', () => { expect(DbmlExporter.escapeNote('hell\\\no')).toBe("'''hell\\\\\no'''"); }); }); + +describe('@dbml/core - DbmlExporter flags', () => { + describe('includeRecords', () => { + test('includes records by default', () => { + const database = (new Parser()).parse(DBML_WITH_RECORDS, 'dbmlv2'); + const res = DbmlExporter.export(database.normalize()); + expect(res.trim()).toBe(EXPECTED_DBML_WITH_RECORDS); + }); + + test('includes records when includeRecords is true', () => { + const database = (new Parser()).parse(DBML_WITH_RECORDS, 'dbmlv2'); + const res = DbmlExporter.export(database.normalize(), { includeRecords: true }); + expect(res.trim()).toBe(EXPECTED_DBML_WITH_RECORDS); + }); + + test('omits records when includeRecords is false', () => { + const database = (new Parser()).parse(DBML_WITH_RECORDS, 'dbmlv2'); + const res = DbmlExporter.export(database.normalize(), { includeRecords: false }); + expect(res.trim()).toBe(EXPECTED_DBML_WITHOUT_RECORDS); + }); + }); +}); + +describe('@dbml/core - ModelExporter backwards compatibility', () => { + test('accepts boolean true as isNormalized (old signature)', () => { + const database = (new Parser()).parse(DBML_WITH_RECORDS, 'dbmlv2'); + const normalizedModel = database.normalize(); + const resBoolean = ModelExporter.export(normalizedModel, 'dbml', true); + const resFlags = ModelExporter.export(normalizedModel, 'dbml', { isNormalized: true }); + expect(resBoolean).toBe(resFlags); + expect(resBoolean.trim()).toBe(EXPECTED_DBML_WITH_RECORDS); + }); + + test('accepts boolean false as isNormalized (old signature)', () => { + const database = (new Parser()).parse(DBML_WITH_RECORDS, 'dbmlv2'); + const resBoolean = ModelExporter.export(database, 'dbml', false); + const resFlags = ModelExporter.export(database, 'dbml', { isNormalized: false }); + expect(resBoolean).toBe(resFlags); + expect(resBoolean.trim()).toBe(EXPECTED_DBML_WITH_RECORDS); + }); +}); + +describe('@dbml/core - JsonExporter backwards compatibility', () => { + test('accepts boolean true as isNormalized (old signature)', () => { + const database = (new Parser()).parse(DBML_WITH_RECORDS, 'dbmlv2'); + const normalizedModel = database.normalize(); + const resBoolean = JsonExporter.export(normalizedModel, true); + const resFlags = JsonExporter.export(normalizedModel, { isNormalized: true }); + expect(resBoolean).toBe(resFlags); + // Normalized model is the internal model format — verify the table is present + const parsed = JSON.parse(resBoolean); + expect(Object.values(parsed.tables as Record).map((t) => t.name)).toContain('users'); + }); + + test('accepts boolean false as isNormalized (old signature)', () => { + const database = (new Parser()).parse(DBML_WITH_RECORDS, 'dbmlv2'); + const resBoolean = JsonExporter.export(database, false); + const resFlags = JsonExporter.export(database, { isNormalized: false }); + expect(resBoolean).toBe(resFlags); + // Non-normalized export uses database.export() format: { schemas, notes, records } + const parsed = JSON.parse(resBoolean); + expect(parsed.schemas[0].tables[0].name).toBe('users'); + expect(parsed.records[0].tableName).toBe('users'); + expect(parsed.records[0].columns).toEqual(['id', 'name']); + }); +}); diff --git a/packages/dbml-core/src/export/DbmlExporter.js b/packages/dbml-core/src/export/DbmlExporter.ts similarity index 85% rename from packages/dbml-core/src/export/DbmlExporter.js rename to packages/dbml-core/src/export/DbmlExporter.ts index d3658ef33..27a63c6bd 100644 --- a/packages/dbml-core/src/export/DbmlExporter.js +++ b/packages/dbml-core/src/export/DbmlExporter.ts @@ -2,21 +2,24 @@ import { groupBy, isEmpty, reduce } from 'lodash-es'; import { addDoubleQuoteIfNeeded, formatRecordValue } from '@dbml/parse'; import { shouldPrintSchema } from './utils'; import { DEFAULT_SCHEMA_NAME } from '../model_structure/config'; +import { NormalizedModel } from '../model_structure/database'; +import { NormalizedTable } from '../model_structure/table'; +import { NormalizedTableGroup } from '../model_structure/tableGroup'; class DbmlExporter { - static hasWhiteSpace (str) { + static hasWhiteSpace (str: string): boolean { return /\s/g.test(str); } - static hasSquareBracket (str) { + static hasSquareBracket (str: string): boolean { return /\[|\]/.test(str); } - static isExpression (str) { + static isExpression (str: string): boolean { return /\s*(\*|\+|-|\([A-Za-z0-9_]+\)|\(\))/g.test(str); } - static escapeNote (str) { + static escapeNote (str: string | null): string { if (str === null) { return ''; } @@ -30,7 +33,7 @@ class DbmlExporter { return `'''${newStr}'''`; } - static exportEnums (enumIds, model) { + static exportEnums (enumIds: number[], model: NormalizedModel): string { const enumStrs = enumIds.map((enumId) => { const _enum = model.enums[enumId]; const schema = model.schemas[_enum.schemaId]; @@ -46,7 +49,7 @@ class DbmlExporter { return enumStrs.length ? enumStrs.join('\n') : ''; } - static getFieldLines (tableId, model) { + static getFieldLines (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; const lines = table.fieldIds.map((fieldId) => { @@ -61,7 +64,7 @@ class DbmlExporter { ? `"${field.type.type_name}"` : field.type.type_name}`; - const constraints = []; + const constraints: string[] = []; if (field.unique) { constraints.push('unique'); } @@ -93,7 +96,7 @@ class DbmlExporter { break; case 'string': { - const quote = field.dbdefault.value.includes('\n') ? '\'\'\'' : '\''; + const quote = (field.dbdefault.value as string).includes('\n') ? '\'\'\'' : '\''; value += `${quote}${field.dbdefault.value}${quote}`; break; } @@ -122,7 +125,7 @@ class DbmlExporter { return lines; } - static getIndexLines (tableId, model) { + static getIndexLines (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; const lines = table.indexIds.map((indexId) => { @@ -144,7 +147,7 @@ class DbmlExporter { : column.value; } - const indexSettings = []; + const indexSettings: string[] = []; if (index.pk) { indexSettings.push('pk'); } @@ -169,7 +172,7 @@ class DbmlExporter { return lines; } - static getCheckLines (tableId, model) { + static getCheckLines (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; const lines = table.checkIds.map((checkId) => { @@ -185,7 +188,12 @@ class DbmlExporter { return lines; } - static getTableContentArr (tableIds, model) { + static getTableContentArr (tableIds: number[], model: NormalizedModel): Array<{ + tableId: number; + fieldContents: string[]; + checkContents: string[]; + indexContents: string[]; + }> { const tableContentArr = tableIds.map((tableId) => { const fieldContents = DbmlExporter.getFieldLines(tableId, model); const checkContents = DbmlExporter.getCheckLines(tableId, model); @@ -202,7 +210,7 @@ class DbmlExporter { return tableContentArr; } - static getTableSettings (table) { + static getTableSettings (table: NormalizedTable): string { let settingStr = ''; const settingSep = ', '; if (table.headerColor) { @@ -214,7 +222,7 @@ class DbmlExporter { return settingStr ? ` [${settingStr}]` : ''; } - static exportTables (tableIds, model) { + static exportTables (tableIds: number[], model: NormalizedModel): string { const tableContentArr = DbmlExporter.getTableContentArr(tableIds, model); const tableStrs = tableContentArr.map((tableContent) => { @@ -250,12 +258,12 @@ class DbmlExporter { return tableStrs.length ? tableStrs.join('\n') : ''; } - static buildFieldName (fieldIds, model) { + static buildFieldName (fieldIds: number[], model: NormalizedModel): string { const fieldNames = fieldIds.map((fieldId) => `"${model.fields[fieldId].name}"`).join(', '); return fieldIds.length === 1 ? fieldNames : `(${fieldNames})`; } - static exportRefs (refIds, model) { + static exportRefs (refIds: number[], model: NormalizedModel): string { const strArr = refIds.map((refId) => { const ref = model.refs[refId]; const oneRelationEndpointIndex = ref.endpointIds.findIndex((endpointId) => model.endpoints[endpointId].relation === '1'); @@ -270,7 +278,7 @@ class DbmlExporter { const refEndpointField = model.fields[refEndpoint.fieldIds[0]]; const refEndpointTable = model.tables[refEndpointField.tableId]; const refEndpointSchema = model.schemas[refEndpointTable.schemaId]; - const refEndpointFieldName = this.buildFieldName(refEndpoint.fieldIds, model, 'dbml'); + const refEndpointFieldName = this.buildFieldName(refEndpoint.fieldIds, model); if (ref.name) { line += ` ${shouldPrintSchema(model.schemas[ref.schemaId], model) @@ -285,7 +293,7 @@ class DbmlExporter { const foreignEndpointField = model.fields[foreignEndpoint.fieldIds[0]]; const foreignEndpointTable = model.tables[foreignEndpointField.tableId]; const foreignEndpointSchema = model.schemas[foreignEndpointTable.schemaId]; - const foreignEndpointFieldName = this.buildFieldName(foreignEndpoint.fieldIds, model, 'dbml'); + const foreignEndpointFieldName = this.buildFieldName(foreignEndpoint.fieldIds, model); if (isManyToMany) line += '<> '; else @@ -295,7 +303,7 @@ class DbmlExporter { ? `"${foreignEndpointSchema.name}".` : ''}"${foreignEndpointTable.name}".${foreignEndpointFieldName}`; - const refActions = []; + const refActions: string[] = []; if (ref.onUpdate) { refActions.push(`update: ${ref.onUpdate.toLowerCase()}`); } @@ -313,15 +321,15 @@ class DbmlExporter { return strArr.length ? strArr.join('\n') : ''; } - static getTableGroupSettings (tableGroup) { - const settings = []; + static getTableGroupSettings (tableGroup: NormalizedTableGroup): string { + const settings: string[] = []; if (tableGroup.color) settings.push(`color: ${tableGroup.color}`); return settings.length ? ` [${settings.join(', ')}]` : ''; } - static exportTableGroups (tableGroupIds, model) { + static exportTableGroups (tableGroupIds: number[], model: NormalizedModel): string { const tableGroupStrs = tableGroupIds.map((groupId) => { const group = model.tableGroups[groupId]; const groupSchema = model.schemas[group.schemaId]; @@ -345,7 +353,7 @@ class DbmlExporter { return tableGroupStrs.length ? tableGroupStrs.join('\n') : ''; } - static exportStickyNotes (model) { + static exportStickyNotes (model: NormalizedModel): string { return reduce(model.notes, (result, note) => { const escapedContent = ` ${DbmlExporter.escapeNote(note.content)}`; const stickyNote = `Note ${note.name} {\n${escapedContent}\n}\n`; @@ -355,7 +363,7 @@ class DbmlExporter { }, ''); } - static exportRecords (model) { + static exportRecords (model: NormalizedModel): string { const records = model.records; if (!records || isEmpty(records)) { return ''; @@ -380,7 +388,7 @@ class DbmlExporter { // Merge all rows const allRows = groupRecords.flatMap((record) => { const allColumnIndexes = allColumns.map((col) => record.columns.indexOf(col)); - return record.values.map((row) => allColumnIndexes.map((colIdx) => colIdx === -1 ? { value: null, type: 'expression' } : row[colIdx])); + return record.values.map((row: any[]) => allColumnIndexes.map((colIdx) => colIdx === -1 ? { value: null, type: 'expression' } : row[colIdx])); }); // Build data rows @@ -394,9 +402,11 @@ class DbmlExporter { return recordStrs.join('\n'); } - static export (model) { - const elementStrs = []; + static export (model: NormalizedModel, flags: { includeRecords?: boolean } = {}): string { + const elementStrs: string[] = []; const database = model.database['1']; + // includeRecords defaults to true; set to false to omit TableData blocks from the output + const includeRecords = flags.includeRecords !== false; database.schemaIds.forEach((schemaId) => { const { @@ -410,7 +420,7 @@ class DbmlExporter { }); if (!isEmpty(model.notes)) elementStrs.push(DbmlExporter.exportStickyNotes(model)); - if (!isEmpty(model.records)) elementStrs.push(DbmlExporter.exportRecords(model)); + if (includeRecords && !isEmpty(model.records)) elementStrs.push(DbmlExporter.exportRecords(model)); // all elements already end with 1 '\n', so join('\n') to separate them with 1 blank line return elementStrs.join('\n'); diff --git a/packages/dbml-core/src/export/JsonExporter.js b/packages/dbml-core/src/export/JsonExporter.js deleted file mode 100644 index be72e7cba..000000000 --- a/packages/dbml-core/src/export/JsonExporter.js +++ /dev/null @@ -1,11 +0,0 @@ -class JsonExporter { - static export (model, isNormalized = true) { - if (!isNormalized) { - return JSON.stringify(model.export(), null, 2); - } - - return JSON.stringify(model, null, 2); - } -} - -export default JsonExporter; diff --git a/packages/dbml-core/src/export/JsonExporter.ts b/packages/dbml-core/src/export/JsonExporter.ts new file mode 100644 index 000000000..a8c6042f6 --- /dev/null +++ b/packages/dbml-core/src/export/JsonExporter.ts @@ -0,0 +1,22 @@ +import Database, { NormalizedModel } from 'model_structure/database'; + +class JsonExporter { + static export ( + model: Database | NormalizedModel, + flags: { isNormalized?: boolean } | boolean = {}, + ) { + // Backwards compatibility: if a boolean is passed, treat it as the isNormalized flag + const resolvedFlags = typeof flags === 'boolean' ? { isNormalized: flags } : flags; + + // isNormalized defaults to true; when false, the model is normalized before exporting + const { isNormalized = true } = resolvedFlags; + + if (!isNormalized && model instanceof Database) { + return JSON.stringify(model.export(), null, 2); + } + + return JSON.stringify(model, null, 2); + } +} + +export default JsonExporter; diff --git a/packages/dbml-core/src/export/ModelExporter.js b/packages/dbml-core/src/export/ModelExporter.js deleted file mode 100644 index 4a734db5f..000000000 --- a/packages/dbml-core/src/export/ModelExporter.js +++ /dev/null @@ -1,45 +0,0 @@ -import DbmlExporter from './DbmlExporter'; -import MysqlExporter from './MysqlExporter'; -import PostgresExporter from './PostgresExporter'; -import JsonExporter from './JsonExporter'; -import SqlServerExporter from './SqlServerExporter'; -import OracleExporter from './OracleExporter'; - -class ModelExporter { - static export (model = {}, format = '', isNormalized = true) { - let res = ''; - const normalizedModel = isNormalized ? model : model.normalize(); - switch (format) { - case 'dbml': - res = DbmlExporter.export(normalizedModel); - break; - - case 'mysql': - res = MysqlExporter.export(normalizedModel); - break; - - case 'postgres': - res = PostgresExporter.export(normalizedModel); - break; - - case 'json': - res = JsonExporter.export(model, isNormalized); - break; - - case 'mssql': - res = SqlServerExporter.export(normalizedModel); - break; - - case 'oracle': - res = OracleExporter.export(normalizedModel); - break; - - default: - break; - } - - return res; - } -} - -export default ModelExporter; diff --git a/packages/dbml-core/src/export/ModelExporter.ts b/packages/dbml-core/src/export/ModelExporter.ts new file mode 100644 index 000000000..1fe8954f2 --- /dev/null +++ b/packages/dbml-core/src/export/ModelExporter.ts @@ -0,0 +1,82 @@ +import DbmlExporter from './DbmlExporter'; +import MysqlExporter from './MysqlExporter'; +import PostgresExporter from './PostgresExporter'; +import JsonExporter from './JsonExporter'; +import SqlServerExporter from './SqlServerExporter'; +import OracleExporter from './OracleExporter'; +import Database, { NormalizedModel } from '../model_structure/database'; + +export type ExportFormatOption = 'dbml' | 'mysql' | 'postgres' | 'json' | 'mssql' | 'oracle'; + +export interface ExportFlags { + isNormalized?: boolean; + includeRecords?: boolean; +} + +class ModelExporter { + /** + * @deprecated Passing a boolean as the third argument is deprecated. Use `{ isNormalized: boolean }` instead. + */ + static export ( + model: Database | NormalizedModel, + format: ExportFormatOption, + isNormalized: boolean, + ): string; + + static export ( + model: Database | NormalizedModel, + format: ExportFormatOption, + flags?: ExportFlags, + ): string; + + static export ( + model: Database | NormalizedModel, + format: ExportFormatOption, + flags: ExportFlags | boolean = {}, + ) { + let res = ''; + // Backwards compatibility: if a boolean is passed, treat it as the isNormalized flag + const resolvedFlags: ExportFlags = typeof flags === 'boolean' ? { isNormalized: flags } : flags; + // isNormalized defaults to true; when false, the model is normalized before exporting + + const { + isNormalized = true, + includeRecords = true, + } = resolvedFlags; + + const normalizedModel = isNormalized ? model as NormalizedModel : (model as Database).normalize(); + + switch (format) { + case 'dbml': + res = DbmlExporter.export(normalizedModel, { includeRecords }); + break; + + case 'mysql': + res = MysqlExporter.export(normalizedModel); + break; + + case 'postgres': + res = PostgresExporter.export(normalizedModel); + break; + + case 'json': + res = JsonExporter.export(model, { isNormalized }); + break; + + case 'mssql': + res = SqlServerExporter.export(normalizedModel); + break; + + case 'oracle': + res = OracleExporter.export(normalizedModel); + break; + + default: + break; + } + + return res; + } +} + +export default ModelExporter; diff --git a/packages/dbml-core/src/export/MysqlExporter.js b/packages/dbml-core/src/export/MysqlExporter.ts similarity index 85% rename from packages/dbml-core/src/export/MysqlExporter.js rename to packages/dbml-core/src/export/MysqlExporter.ts index 19d562217..fbab40f52 100644 --- a/packages/dbml-core/src/export/MysqlExporter.js +++ b/packages/dbml-core/src/export/MysqlExporter.ts @@ -4,6 +4,7 @@ import { buildJunctionFields1, buildJunctionFields2, buildNewTableName, + CommentNode, } from './utils'; import { isNumericType, @@ -12,9 +13,10 @@ import { isDateTimeType, isBinaryType, } from '@dbml/parse'; +import { NormalizedModel, RecordValue } from '../model_structure/database'; class MySQLExporter { - static exportRecords (model) { + static exportRecords (model: NormalizedModel): string[] { const records = Object.values(model.records || {}); if (isEmpty(records)) { return []; @@ -32,7 +34,7 @@ class MySQLExporter { : ''; // Value formatter for MySQL - const formatValue = (val) => { + const formatValue = (val: RecordValue): string => { if (val.value === null) return 'NULL'; if (val.type === 'expression') return val.value; @@ -44,7 +46,7 @@ class MySQLExporter { }; // Build the VALUES clause - const valueRows = values.map((row) => { + const valueRows = values.map((row: RecordValue[]) => { const valueStrs = row.map(formatValue); return `(${valueStrs.join(', ')})`; }); @@ -57,7 +59,7 @@ class MySQLExporter { return insertStatements; } - static getFieldLines (tableId, model) { + static getFieldLines (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; const lines = table.fieldIds.map((fieldId) => { @@ -129,14 +131,14 @@ class MySQLExporter { return lines; } - static getCompositePKs (tableId, model) { + static getCompositePKs (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; const compositePkIds = table.indexIds ? table.indexIds.filter((indexId) => model.indexes[indexId].pk) : []; const lines = compositePkIds.map((keyId) => { const key = model.indexes[keyId]; let line = 'PRIMARY KEY'; - const columnArr = []; + const columnArr: string[] = []; key.columnIds.forEach((columnId) => { const column = model.indexColumns[columnId]; @@ -157,7 +159,7 @@ class MySQLExporter { return lines; } - static getCheckLines (tableId, model) { + static getCheckLines (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; if (!table.checkIds || table.checkIds.length === 0) { @@ -180,7 +182,12 @@ class MySQLExporter { return lines; } - static getTableContentArr (tableIds, model) { + static getTableContentArr (tableIds: number[], model: NormalizedModel): Array<{ + tableId: number; + fieldContents: string[]; + checkContents: string[]; + compositePKs: string[]; + }> { const tableContentArr = tableIds.map((tableId) => { const fieldContents = MySQLExporter.getFieldLines(tableId, model); const checkContents = MySQLExporter.getCheckLines(tableId, model); @@ -197,7 +204,7 @@ class MySQLExporter { return tableContentArr; } - static exportTables (tableIds, model) { + static exportTables (tableIds: number[], model: NormalizedModel): string[] { const tableContentArr = MySQLExporter.getTableContentArr(tableIds, model); const tableStrs = tableContentArr.map((tableContent) => { @@ -214,12 +221,12 @@ class MySQLExporter { return tableStrs; } - static buildFieldName (fieldIds, model) { + static buildFieldName (fieldIds: number[], model: NormalizedModel): string { const fieldNames = fieldIds.map((fieldId) => `\`${model.fields[fieldId].name}\``).join(', '); return `(${fieldNames})`; } - static buildTableManyToMany (firstTableFieldsMap, secondTableFieldsMap, tableName, refEndpointSchema, model) { + static buildTableManyToMany (firstTableFieldsMap: Map, secondTableFieldsMap: Map, tableName: string, refEndpointSchema: any, model: NormalizedModel): string { let line = `CREATE TABLE ${shouldPrintSchema(refEndpointSchema, model) ? `\`${refEndpointSchema.name}\`.` : ''}\`${tableName}\` (\n`; const key1s = [...firstTableFieldsMap.keys()].join('`, `'); const key2s = [...secondTableFieldsMap.keys()].join('`, `'); @@ -234,7 +241,15 @@ class MySQLExporter { return line; } - static buildForeignKeyManyToMany (fieldsMap, foreignEndpointFields, refEndpointTableName, foreignEndpointTableName, refEndpointSchema, foreignEndpointSchema, model) { + static buildForeignKeyManyToMany ( + fieldsMap: Map, + foreignEndpointFields: string, + refEndpointTableName: string, + foreignEndpointTableName: string, + refEndpointSchema: any, + foreignEndpointSchema: any, + model: NormalizedModel, + ): string { const refEndpointFields = [...fieldsMap.keys()].join('`, `'); const line = `ALTER TABLE ${shouldPrintSchema(refEndpointSchema, model) ? `\`${refEndpointSchema.name}\`.` @@ -244,7 +259,11 @@ class MySQLExporter { return line; } - static exportRefs (refIds, model, usedTableNames) { + static exportRefs ( + refIds: number[], + model: NormalizedModel, + usedTableNames: Set, + ): string[] { const strArr = refIds.map((refId) => { let line = ''; const ref = model.refs[refId]; @@ -258,12 +277,12 @@ class MySQLExporter { const refEndpointField = model.fields[refEndpoint.fieldIds[0]]; const refEndpointTable = model.tables[refEndpointField.tableId]; const refEndpointSchema = model.schemas[refEndpointTable.schemaId]; - const refEndpointFieldName = this.buildFieldName(refEndpoint.fieldIds, model, 'mysql'); + const refEndpointFieldName = this.buildFieldName(refEndpoint.fieldIds, model); const foreignEndpointField = model.fields[foreignEndpoint.fieldIds[0]]; const foreignEndpointTable = model.tables[foreignEndpointField.tableId]; const foreignEndpointSchema = model.schemas[foreignEndpointTable.schemaId]; - const foreignEndpointFieldName = this.buildFieldName(foreignEndpoint.fieldIds, model, 'mysql'); + const foreignEndpointFieldName = this.buildFieldName(foreignEndpoint.fieldIds, model); if (refOneIndex === -1) { const firstTableFieldsMap = buildJunctionFields1(refEndpoint.fieldIds, model); @@ -298,7 +317,10 @@ class MySQLExporter { return strArr; } - static exportIndexes (indexIds, model) { + static exportIndexes ( + indexIds: number[], + model: NormalizedModel, + ): string[] { // exclude composite pk index const indexArr = indexIds.filter((indexId) => !model.indexes[indexId].pk).map((indexId, i) => { const index = model.indexes[indexId]; @@ -318,7 +340,7 @@ class MySQLExporter { ? `\`${schema.name}\`.` : ''}\`${table.name}\``; - const columnArr = []; + const columnArr: string[] = []; index.columnIds.forEach((columnId) => { const column = model.indexColumns[columnId]; let columnStr = ''; @@ -342,7 +364,10 @@ class MySQLExporter { return indexArr; } - static exportComments (comments, model) { + static exportComments ( + comments: CommentNode[], + model: NormalizedModel, + ): string[] { const commentArr = comments.map((comment) => { let line = ''; if (comment.type === 'table') { @@ -351,7 +376,7 @@ class MySQLExporter { line += `ALTER TABLE ${shouldPrintSchema(schema, model) ? `\`${schema.name}\`.` - : ''}\`${table.name}\` COMMENT = '${table.note.replace(/'/g, '\'\'')}'`; + : ''}\`${table.name}\` COMMENT = '${(table.note || '').replace(/'/g, '\'\'')}'`; } line += ';\n'; return line; @@ -359,7 +384,7 @@ class MySQLExporter { return commentArr; } - static export (model) { + static export (model: NormalizedModel): string { const database = model.database['1']; const usedTableNames = new Set(Object.values(model.tables).map((table) => table.name)); @@ -381,9 +406,9 @@ class MySQLExporter { prevStatements.indexes.push(...MySQLExporter.exportIndexes(indexIds, model)); } - const commentNodes = flatten(tableIds.map((tableId) => { + const commentNodes: CommentNode[] = flatten(tableIds.map((tableId) => { const { note } = model.tables[tableId]; - return note ? [{ type: 'table', tableId }] : []; + return note ? [{ type: 'table' as const, tableId }] : []; })); if (!isEmpty(commentNodes)) { prevStatements.comments.push(...MySQLExporter.exportComments(commentNodes, model)); @@ -395,12 +420,12 @@ class MySQLExporter { return prevStatements; }, { - schemas: [], - enums: [], - tables: [], - indexes: [], - comments: [], - refs: [], + schemas: [] as string[], + enums: [] as string[], + tables: [] as string[], + indexes: [] as string[], + comments: [] as string[], + refs: [] as string[], }); // Export INSERT statements diff --git a/packages/dbml-core/src/export/OracleExporter.js b/packages/dbml-core/src/export/OracleExporter.ts similarity index 85% rename from packages/dbml-core/src/export/OracleExporter.js rename to packages/dbml-core/src/export/OracleExporter.ts index b607b15fc..76ad393e1 100644 --- a/packages/dbml-core/src/export/OracleExporter.js +++ b/packages/dbml-core/src/export/OracleExporter.ts @@ -7,6 +7,7 @@ import { shouldPrintSchema, parseIsoDatetime, formatDatetimeForOracle, + CommentNode, } from './utils'; import { isNumericType, @@ -15,9 +16,11 @@ import { isDateTimeType, isBinaryType, } from '@dbml/parse'; +import { NormalizedModel } from '../model_structure/database'; +import { NormalizedSchema } from '../model_structure/schema'; class OracleExporter { - static exportRecords (model) { + static exportRecords (model: NormalizedModel): string[] { const records = Object.values(model.records || {}); if (isEmpty(records)) { return []; @@ -34,7 +37,7 @@ class OracleExporter { ? `("${columns.join('", "')}")` : ''; - const formatValue = (val) => { + const formatValue = (val: { value: any; type: string }): string => { if (val.value === null) return 'NULL'; if (val.type === 'expression') return val.value; @@ -67,7 +70,7 @@ class OracleExporter { // Build the INSERT ALL statement for multiple rows if (values.length > 1) { - const intoStatements = values.map((row) => { + const intoStatements = values.map((row: { value: any; type: string }[]) => { const valueStrs = row.map(formatValue); return ` INTO ${tableRef} ${columnList} VALUES (${valueStrs.join(', ')})`; }); @@ -83,16 +86,16 @@ class OracleExporter { return insertStatements; } - static buildSchemaToTableNameSetMap (model) { - const schemaToTableNameSetMap = new Map(); + static buildSchemaToTableNameSetMap (model: NormalizedModel): Map> { + const schemaToTableNameSetMap = new Map>(); forEach(model.tables, (table) => { const schema = model.schemas[table.schemaId]; - const tableSet = schemaToTableNameSetMap.get(schema.name); + const tableSet = schemaToTableNameSetMap.get(schema); if (!tableSet) { - schemaToTableNameSetMap.set(schema.name, new Set([table.name])); + schemaToTableNameSetMap.set(schema, new Set([table.name])); return; } @@ -102,11 +105,11 @@ class OracleExporter { return schemaToTableNameSetMap; } - static buildTableNameWithSchema (model, schema, table) { + static buildTableNameWithSchema (model: NormalizedModel, schema: NormalizedSchema, table: { name: string }): string { return `${shouldPrintSchema(schema, model) ? `${escapeObjectName(schema.name, 'oracle')}.` : ''}${escapeObjectName(table.name, 'oracle')}`; } - static exportSchema (schemaName) { + static exportSchema (schemaName: string): string { // According to Oracle, CREATE SCHEMA statement does not actually create a schema and it automatically creates a schema when we create a user // Learn more: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-SCHEMA.html#GUID-2D154F9C-9E2B-4A09-B658-2EA5B99AC838__GUID-CC0A5080-2AF3-4460-AB2B-DEA6C79519BA return `CREATE USER ${escapeObjectName(schemaName, 'oracle')}\n` @@ -116,7 +119,7 @@ class OracleExporter { + 'QUOTA UNLIMITED ON system;\n'; } - static getFieldLines (tableId, model) { + static getFieldLines (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; const lines = table.fieldIds.map((fieldId) => { @@ -145,8 +148,8 @@ class OracleExporter { line += ' GENERATED AS IDENTITY'; // in oracle, increment means using identity. If a clause includes identity, we must ignore not null + default value - cloneField.dbdefault = null; - cloneField.not_null = false; + (cloneField as any).dbdefault = null; + (cloneField as any).not_null = false; } // default value must be placed before the inline constraint expression @@ -175,7 +178,7 @@ class OracleExporter { line += ' PRIMARY KEY'; } - if (cloneField.not_null) { + if ((cloneField as any).not_null) { line += ' NOT NULL'; } @@ -201,14 +204,14 @@ class OracleExporter { return lines; } - static getCompositePKs (tableId, model) { + static getCompositePKs (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; const compositePkIds = table.indexIds ? table.indexIds.filter((indexId) => model.indexes[indexId].pk) : []; const lines = compositePkIds.map((keyId) => { const key = model.indexes[keyId]; let line = 'PRIMARY KEY'; - const columnArr = []; + const columnArr: string[] = []; key.columnIds.forEach((columnId) => { const column = model.indexColumns[columnId]; @@ -229,7 +232,7 @@ class OracleExporter { return lines; } - static getCheckLines (tableId, model) { + static getCheckLines (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; if (!table.checkIds || table.checkIds.length === 0) { @@ -252,7 +255,12 @@ class OracleExporter { return lines; } - static getTableContents (tableIds, model) { + static getTableContents (tableIds: number[], model: NormalizedModel): Array<{ + tableId: number; + fieldContents: string[]; + checkContents: string[]; + compositePKs: string[]; + }> { const tableContentArr = tableIds.map((tableId) => { const fieldContents = this.getFieldLines(tableId, model); const checkContents = this.getCheckLines(tableId, model); @@ -269,7 +277,7 @@ class OracleExporter { return tableContentArr; } - static exportTables (tableIds, model) { + static exportTables (tableIds: number[], model: NormalizedModel): string[] { const tableContentList = this.getTableContents(tableIds, model); const tableStrs = tableContentList.map((tableContent) => { @@ -287,12 +295,12 @@ class OracleExporter { return tableStrs; } - static buildReferenceFieldNamesString (fieldIds, model) { + static buildReferenceFieldNamesString (fieldIds: number[], model: NormalizedModel): string { const fieldNames = fieldIds.map((fieldId) => `"${model.fields[fieldId].name}"`).join(', '); return `(${fieldNames})`; } - static buildTableManyToMany (firstTableFieldsMap, secondTableFieldsMap, tableName) { + static buildTableManyToMany (firstTableFieldsMap: Map, secondTableFieldsMap: Map, tableName: string): string { let line = `CREATE TABLE ${tableName} (\n`; firstTableFieldsMap.forEach((fieldType, fieldName) => { @@ -310,14 +318,14 @@ class OracleExporter { return line; } - static buildForeignKeyManyToMany (foreignEndpointTableName, foreignEndpointFields, refEndpointTableName, refEndpointFieldsString) { + static buildForeignKeyManyToMany (foreignEndpointTableName: string, foreignEndpointFields: Map, refEndpointTableName: string, refEndpointFieldsString: string): string { const foreignEndpointFieldsString = [...foreignEndpointFields.keys()].join('`, `'); const line = `ALTER TABLE ${foreignEndpointTableName} ADD FOREIGN KEY ("${foreignEndpointFieldsString}") REFERENCES ${refEndpointTableName} ${refEndpointFieldsString} DEFERRABLE INITIALLY IMMEDIATE;\n`; return line; } - static exportReferencesAndNewTablesIfExists (refIds, model, usedTableNameMap) { - const result = { refs: [], tables: [] }; + static exportReferencesAndNewTablesIfExists (refIds: number[], model: NormalizedModel, usedTableNameMap: Map>): { refs: string[]; tables: string[] } { + const result: { refs: string[]; tables: string[] } = { refs: [], tables: [] }; refIds.forEach((refId) => { const ref = model.refs[refId]; @@ -404,12 +412,12 @@ class OracleExporter { return result; } - static exportReferenceGrants (model, refIds) { + static exportReferenceGrants (model: NormalizedModel, refIds: number[]): string[] { // only default schema -> ignore it if (Object.keys(model.schemas).length <= 1) { return []; } - const tableNameList = []; + const tableNameList: string[] = []; refIds.forEach((refId) => { const ref = model.refs[refId]; @@ -458,7 +466,7 @@ class OracleExporter { return tableToGrantList; } - static exportIndexes (indexIds, model) { + static exportIndexes (indexIds: number[], model: NormalizedModel): string[] { // exclude composite pk index const indexArr = indexIds.filter((indexId) => !model.indexes[indexId].pk).map((indexId) => { const index = model.indexes[indexId]; @@ -480,7 +488,7 @@ class OracleExporter { const tableName = this.buildTableNameWithSchema(model, schema, table); line += ` ON ${tableName}`; - const columnArr = []; + const columnArr: string[] = []; index.columnIds.forEach((columnId) => { const column = model.indexColumns[columnId]; let columnStr = ''; @@ -500,7 +508,7 @@ class OracleExporter { return indexArr; } - static exportComments (comments, model) { + static exportComments (comments: CommentNode[], model: NormalizedModel): string[] { const commentArr = comments.map((comment) => { let line = 'COMMENT ON'; @@ -511,12 +519,12 @@ class OracleExporter { switch (comment.type) { case 'table': { - line += ` TABLE ${tableName} IS '${table.note.replace(/'/g, '\'\'')}'`; + line += ` TABLE ${tableName} IS '${(table.note || '').replace(/'/g, '\'\'')}'`; break; } case 'column': { - const field = model.fields[comment.fieldId]; - line += ` COLUMN ${tableName}.${escapeObjectName(field.name, 'oracle')} IS '${field.note.replace(/'/g, '\'\'')}'`; + const field = model.fields[comment.fieldId!]; + line += ` COLUMN ${tableName}.${escapeObjectName(field.name, 'oracle')} IS '${(field.note || '').replace(/'/g, '\'\'')}'`; break; } default: @@ -531,7 +539,7 @@ class OracleExporter { return commentArr; } - static export (model) { + static export (model: NormalizedModel): string { const database = model.database['1']; const schemaToTableNameSetMap = this.buildSchemaToTableNameSetMap(model); @@ -553,12 +561,12 @@ class OracleExporter { prevStatements.indexes.push(...this.exportIndexes(indexIds, model)); } - const commentNodes = flatten(tableIds.map((tableId) => { + const commentNodes: CommentNode[] = flatten(tableIds.map((tableId): CommentNode[] => { const { fieldIds, note } = model.tables[tableId]; - const fieldObjects = fieldIds + const fieldObjects: CommentNode[] = fieldIds .filter((fieldId) => model.fields[fieldId].note) - .map((fieldId) => ({ type: 'column', fieldId, tableId })); - return note ? [{ type: 'table', tableId }].concat(fieldObjects) : fieldObjects; + .map((fieldId) => ({ type: 'column' as const, fieldId, tableId })); + return note ? [{ type: 'table' as const, tableId }, ...fieldObjects] : fieldObjects; })); if (!isEmpty(commentNodes)) { @@ -575,12 +583,12 @@ class OracleExporter { return prevStatements; }, { - schemas: [], - tables: [], - indexes: [], - comments: [], - referenceGrants: [], - refs: [], + schemas: [] as string[], + tables: [] as string[], + indexes: [] as string[], + comments: [] as string[], + referenceGrants: [] as string[], + refs: [] as string[], }); // Export INSERT statements with constraint checking disabled diff --git a/packages/dbml-core/src/export/PostgresExporter.js b/packages/dbml-core/src/export/PostgresExporter.ts similarity index 85% rename from packages/dbml-core/src/export/PostgresExporter.js rename to packages/dbml-core/src/export/PostgresExporter.ts index 9ecb9d0ca..b195193c9 100644 --- a/packages/dbml-core/src/export/PostgresExporter.js +++ b/packages/dbml-core/src/export/PostgresExporter.ts @@ -6,6 +6,7 @@ import { buildJunctionFields2, buildNewTableName, hasWhiteSpace, + CommentNode, } from './utils'; import { shouldPrintSchemaName } from '../model_structure/utils'; import { @@ -15,6 +16,8 @@ import { isDateTimeType, isBinaryType, } from '@dbml/parse'; +import { NormalizedModel } from '../model_structure/database'; +import { NormalizedSchema } from '../model_structure/schema'; // PostgreSQL built-in data types // Generated from PostgreSQLParser.g4 and PostgreSQLLexer.g4 @@ -144,8 +147,9 @@ const POSTGRES_RESERVED_KEYWORDS = [ 'USER', ]; + class PostgresExporter { - static exportRecords (model) { + static exportRecords (model: NormalizedModel): string[] { const records = Object.values(model.records || {}); if (isEmpty(records)) { return []; @@ -164,11 +168,11 @@ class PostgresExporter { // Build the column list const columnList = columns.length > 0 - ? `(${columns.map((col) => `"${col}"`).join(', ')})` + ? `(${columns.map((col: string) => `"${col}"`).join(', ')})` : ''; // Value formatter for PostgreSQL - const formatValue = (val) => { + const formatValue = (val: { value: any; type: string }): string => { if (val.value === null) return 'NULL'; if (val.type === 'expression') return val.value; @@ -180,7 +184,7 @@ class PostgresExporter { }; // Build the VALUES clause - const valueRows = values.map((row) => { + const valueRows = values.map((row: { value: any; type: string }[]) => { const valueStrs = row.map(formatValue); return `(${valueStrs.join(', ')})`; }); @@ -188,12 +192,12 @@ class PostgresExporter { const valuesClause = valueRows.join(',\n '); return `INSERT INTO ${tableRef} ${columnList}\nVALUES\n ${valuesClause};`; - }).filter(Boolean); + }).filter(Boolean) as string[]; return insertStatements; } - static exportEnums (enumIds, model) { + static exportEnums (enumIds: number[], model: NormalizedModel): [string, string][] { return enumIds.map((enumId) => { const _enum = model.enums[enumId]; const schema = model.schemas[_enum.schemaId]; @@ -207,11 +211,11 @@ class PostgresExporter { const enumValueStr = enumValueArr.join(',\n'); const enumLine = `CREATE TYPE ${enumName} AS ENUM (\n${enumValueStr}\n);\n`; - return [enumName, enumLine]; + return [enumName, enumLine] as [string, string]; }); } - static getFieldLines (tableId, model, enumSet) { + static getFieldLines (tableId: number, model: NormalizedModel, enumSet: Set): string[] { const table = model.tables[tableId]; const lines = table.fieldIds.map((fieldId) => { @@ -236,8 +240,8 @@ class PostgresExporter { const typeName = shouldDoubleQuote ? `"${originalTypeName}"` : originalTypeName; line = `"${field.name}" ${typeName}`; - } else if (field.type.originalTypeName) { // A custom Postgres type that is not defined as enum in DBML content - line = `"${field.name}" "${field.type.schemaName}"."${field.type.originalTypeName}"`; + } else if ((field.type as any).originalTypeName) { // A custom Postgres type that is not defined as enum in DBML content + line = `"${field.name}" "${field.type.schemaName}"."${(field.type as any).originalTypeName}"`; } else { const schemaName = hasWhiteSpaceOrUpperCase(field.type.schemaName) ? `"${field.type.schemaName}".` : `${field.type.schemaName}.`; const typeName = hasWhiteSpaceOrUpperCase(field.type.type_name) ? `"${field.type.type_name}"` : field.type.type_name; @@ -295,14 +299,14 @@ class PostgresExporter { return lines; } - static getCompositePKs (tableId, model) { + static getCompositePKs (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; const compositePkIds = table.indexIds ? table.indexIds.filter((indexId) => model.indexes[indexId].pk) : []; const lines = compositePkIds.map((keyId) => { const key = model.indexes[keyId]; let line = 'PRIMARY KEY'; - const columnArr = []; + const columnArr: string[] = []; key.columnIds.forEach((columnId) => { const column = model.indexColumns[columnId]; @@ -323,7 +327,7 @@ class PostgresExporter { return lines; } - static getCheckLines (tableId, model) { + static getCheckLines (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; if (!table.checkIds || table.checkIds.length === 0) { @@ -346,7 +350,12 @@ class PostgresExporter { return lines; } - static getTableContentArr (tableIds, model, enumSet) { + static getTableContentArr (tableIds: number[], model: NormalizedModel, enumSet: Set): Array<{ + tableId: number; + fieldContents: string[]; + checkContents: string[]; + compositePKs: string[]; + }> { const tableContentArr = tableIds.map((tableId) => { const fieldContents = PostgresExporter.getFieldLines(tableId, model, enumSet); const checkContents = PostgresExporter.getCheckLines(tableId, model); @@ -363,7 +372,7 @@ class PostgresExporter { return tableContentArr; } - static exportTables (tableIds, model, enumSet) { + static exportTables (tableIds: number[], model: NormalizedModel, enumSet: Set): string[] { const tableContentArr = PostgresExporter.getTableContentArr(tableIds, model, enumSet); const tableStrs = tableContentArr.map((tableContent) => { @@ -379,12 +388,12 @@ class PostgresExporter { return tableStrs; } - static buildFieldName (fieldIds, model) { + static buildFieldName (fieldIds: number[], model: NormalizedModel): string { const fieldNames = fieldIds.map((fieldId) => `"${model.fields[fieldId].name}"`).join(', '); return `(${fieldNames})`; } - static buildTableManyToMany (firstTableFieldsMap, secondTableFieldsMap, tableName, refEndpointSchema, model) { + static buildTableManyToMany (firstTableFieldsMap: Map, secondTableFieldsMap: Map, tableName: string, refEndpointSchema: NormalizedSchema, model: NormalizedModel): string { let line = `CREATE TABLE ${shouldPrintSchema(refEndpointSchema, model) ? `"${refEndpointSchema.name}".` : ''}"${tableName}" (\n`; @@ -401,7 +410,7 @@ class PostgresExporter { return line; } - static buildForeignKeyManyToMany (fieldsMap, foreignEndpointFields, refEndpointTableName, foreignEndpointTableName, refEndpointSchema, foreignEndpointSchema, model) { + static buildForeignKeyManyToMany (fieldsMap: Map, foreignEndpointFields: string, refEndpointTableName: string, foreignEndpointTableName: string, refEndpointSchema: NormalizedSchema, foreignEndpointSchema: NormalizedSchema, model: NormalizedModel): string { const refEndpointFields = [...fieldsMap.keys()].join('", "'); const line = `ALTER TABLE ${shouldPrintSchema(refEndpointSchema, model) ? `"${refEndpointSchema.name}".` @@ -411,7 +420,7 @@ class PostgresExporter { return line; } - static exportRefs (refIds, model, usedTableNames) { + static exportRefs (refIds: number[], model: NormalizedModel, usedTableNames: Set): string[] { const strArr = refIds.map((refId) => { let line = ''; const ref = model.refs[refId]; @@ -425,12 +434,12 @@ class PostgresExporter { const refEndpointField = model.fields[refEndpoint.fieldIds[0]]; const refEndpointTable = model.tables[refEndpointField.tableId]; const refEndpointSchema = model.schemas[refEndpointTable.schemaId]; - const refEndpointFieldName = this.buildFieldName(refEndpoint.fieldIds, model, 'postgres'); + const refEndpointFieldName = this.buildFieldName(refEndpoint.fieldIds, model); const foreignEndpointField = model.fields[foreignEndpoint.fieldIds[0]]; const foreignEndpointTable = model.tables[foreignEndpointField.tableId]; const foreignEndpointSchema = model.schemas[foreignEndpointTable.schemaId]; - const foreignEndpointFieldName = this.buildFieldName(foreignEndpoint.fieldIds, model, 'postgres'); + const foreignEndpointFieldName = this.buildFieldName(foreignEndpoint.fieldIds, model); if (refOneIndex === -1) { // many to many relationship const firstTableFieldsMap = buildJunctionFields1(refEndpoint.fieldIds, model); @@ -465,7 +474,7 @@ class PostgresExporter { return strArr; } - static exportIndexes (indexIds, model) { + static exportIndexes (indexIds: number[], model: NormalizedModel): string[] { // exclude composite pk index const indexArr = indexIds.filter((indexId) => !model.indexes[indexId].pk).map((indexId) => { const index = model.indexes[indexId]; @@ -488,7 +497,7 @@ class PostgresExporter { line += ` USING ${index.type.toUpperCase()}`; } - const columnArr = []; + const columnArr: string[] = []; index.columnIds.forEach((columnId) => { const column = model.indexColumns[columnId]; let columnStr = ''; @@ -509,7 +518,7 @@ class PostgresExporter { return indexArr; } - static exportComments (comments, model) { + static exportComments (comments: CommentNode[], model: NormalizedModel): string[] { const commentArr = comments.map((comment) => { let line = 'COMMENT ON'; const table = model.tables[comment.tableId]; @@ -518,14 +527,14 @@ class PostgresExporter { case 'table': { line += ` TABLE ${shouldPrintSchema(schema, model) ? `"${schema.name}".` - : ''}"${table.name}" IS '${table.note.replace(/'/g, '\'\'')}'`; + : ''}"${table.name}" IS '${(table.note || '').replace(/'/g, '\'\'')}'`; break; } case 'column': { - const field = model.fields[comment.fieldId]; + const field = model.fields[comment.fieldId!]; line += ` COLUMN ${shouldPrintSchema(schema, model) ? `"${schema.name}".` - : ''}"${table.name}"."${field.name}" IS '${field.note.replace(/'/g, '\'\'')}'`; + : ''}"${table.name}"."${field.name}" IS '${(field.note || '').replace(/'/g, '\'\'')}'`; break; } default: @@ -540,14 +549,14 @@ class PostgresExporter { return commentArr; } - static export (model) { + static export (model: NormalizedModel): string { const database = model.database['1']; const usedTableNames = new Set(Object.values(model.tables).map((table) => table.name)); // Pre-collect all user-defined enum names to distinguish them from built-in PostgreSQL types // This prevents built-in types like VARCHAR, INTEGER from being quoted unnecessarily - const enumSet = new Set(); + const enumSet = new Set(); const schemaEnumStatements = database.schemaIds.reduce((prevStatements, schemaId) => { const schema = model.schemas[schemaId]; @@ -569,12 +578,12 @@ class PostgresExporter { return prevStatements; }, { - schemas: [], - enums: [], - tables: [], - indexes: [], - comments: [], - refs: [], + schemas: [] as string[], + enums: [] as string[], + tables: [] as string[], + indexes: [] as string[], + comments: [] as string[], + refs: [] as string[], }); const statements = database.schemaIds.reduce((prevStatements, schemaId) => { @@ -590,12 +599,12 @@ class PostgresExporter { prevStatements.indexes.push(...PostgresExporter.exportIndexes(indexIds, model)); } - const commentNodes = flatten(tableIds.map((tableId) => { + const commentNodes: CommentNode[] = flatten(tableIds.map((tableId): CommentNode[] => { const { fieldIds, note } = model.tables[tableId]; - const fieldObjects = fieldIds + const fieldObjects: CommentNode[] = fieldIds .filter((fieldId) => model.fields[fieldId].note) - .map((fieldId) => ({ type: 'column', fieldId, tableId })); - return note ? [{ type: 'table', tableId }].concat(fieldObjects) : fieldObjects; + .map((fieldId) => ({ type: 'column' as const, fieldId, tableId })); + return note ? [{ type: 'table' as const, tableId }, ...fieldObjects] : fieldObjects; })); if (!isEmpty(commentNodes)) { prevStatements.comments.push(...PostgresExporter.exportComments(commentNodes, model)); diff --git a/packages/dbml-core/src/export/SqlServerExporter.js b/packages/dbml-core/src/export/SqlServerExporter.ts similarity index 85% rename from packages/dbml-core/src/export/SqlServerExporter.js rename to packages/dbml-core/src/export/SqlServerExporter.ts index 9d1bbe91d..7cc529148 100644 --- a/packages/dbml-core/src/export/SqlServerExporter.js +++ b/packages/dbml-core/src/export/SqlServerExporter.ts @@ -4,6 +4,7 @@ import { buildJunctionFields1, buildJunctionFields2, buildNewTableName, + CommentNode, } from './utils'; import { isNumericType, @@ -12,9 +13,11 @@ import { isDateTimeType, isBinaryType, } from '@dbml/parse'; +import { NormalizedModel } from '../model_structure/database'; +import { NormalizedSchema } from '../model_structure/schema'; class SqlServerExporter { - static exportRecords (model) { + static exportRecords (model: NormalizedModel): string[] { const records = Object.values(model.records || {}); if (isEmpty(records)) { return []; @@ -32,7 +35,7 @@ class SqlServerExporter { : ''; // Value formatter for SQL Server - const formatValue = (val) => { + const formatValue = (val: { value: any; type: string }): string => { if (val.value === null) return 'NULL'; if (val.type === 'expression') return val.value; @@ -45,7 +48,7 @@ class SqlServerExporter { }; // Build the VALUES clause - const valueRows = values.map((row) => { + const valueRows = values.map((row: { value: any; type: string }[]) => { const valueStrs = row.map(formatValue); return `(${valueStrs.join(', ')})`; }); @@ -58,7 +61,7 @@ class SqlServerExporter { return insertStatements; } - static getFieldLines (tableId, model) { + static getFieldLines (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; const lines = table.fieldIds.map((fieldId) => { @@ -127,14 +130,14 @@ class SqlServerExporter { return lines; } - static getCompositePKs (tableId, model) { + static getCompositePKs (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; const compositePkIds = table.indexIds ? table.indexIds.filter((indexId) => model.indexes[indexId].pk) : []; const lines = compositePkIds.map((keyId) => { const key = model.indexes[keyId]; let line = 'PRIMARY KEY'; - const columnArr = []; + const columnArr: string[] = []; key.columnIds.forEach((columnId) => { const column = model.indexColumns[columnId]; @@ -155,7 +158,7 @@ class SqlServerExporter { return lines; } - static getCheckLines (tableId, model) { + static getCheckLines (tableId: number, model: NormalizedModel): string[] { const table = model.tables[tableId]; if (!table.checkIds || table.checkIds.length === 0) { @@ -178,7 +181,12 @@ class SqlServerExporter { return lines; } - static getTableContentArr (tableIds, model) { + static getTableContentArr (tableIds: number[], model: NormalizedModel): Array<{ + tableId: number; + fieldContents: string[]; + checkContents: string[]; + compositePKs: string[]; + }> { const tableContentArr = tableIds.map((tableId) => { const fieldContents = SqlServerExporter.getFieldLines(tableId, model); const checkContents = SqlServerExporter.getCheckLines(tableId, model); @@ -195,7 +203,7 @@ class SqlServerExporter { return tableContentArr; } - static exportTables (tableIds, model) { + static exportTables (tableIds: number[], model: NormalizedModel): string[] { const tableContentArr = SqlServerExporter.getTableContentArr(tableIds, model); const tableStrs = tableContentArr.map((tableContent) => { @@ -212,7 +220,7 @@ class SqlServerExporter { return tableStrs; } - static buildTableManyToMany (firstTableFieldsMap, secondTableFieldsMap, tableName, refEndpointSchema, model) { + static buildTableManyToMany (firstTableFieldsMap: Map, secondTableFieldsMap: Map, tableName: string, refEndpointSchema: NormalizedSchema, model: NormalizedModel): string { let line = `CREATE TABLE ${shouldPrintSchema(refEndpointSchema, model) ? `[${refEndpointSchema.name}].` : ''}[${tableName}] (\n`; @@ -229,7 +237,7 @@ class SqlServerExporter { return line; } - static buildForeignKeyManyToMany (fieldsMap, foreignEndpointFields, refEndpointTableName, foreignEndpointTableName, refEndpointSchema, foreignEndpointSchema, model) { + static buildForeignKeyManyToMany (fieldsMap: Map, foreignEndpointFields: string, refEndpointTableName: string, foreignEndpointTableName: string, refEndpointSchema: NormalizedSchema, foreignEndpointSchema: NormalizedSchema, model: NormalizedModel): string { const refEndpointFields = [...fieldsMap.keys()].join('], ['); const line = `ALTER TABLE ${shouldPrintSchema(refEndpointSchema, model) ? `[${refEndpointSchema.name}].` @@ -239,12 +247,12 @@ class SqlServerExporter { return line; } - static buildFieldName (fieldIds, model) { + static buildFieldName (fieldIds: number[], model: NormalizedModel): string { const fieldNames = fieldIds.map((fieldId) => `[${model.fields[fieldId].name}]`).join(', '); return `(${fieldNames})`; } - static exportRefs (refIds, model, usedTableNames) { + static exportRefs (refIds: number[], model: NormalizedModel, usedTableNames: Set): string[] { const strArr = refIds.map((refId) => { let line = ''; const ref = model.refs[refId]; @@ -258,12 +266,12 @@ class SqlServerExporter { const refEndpointField = model.fields[refEndpoint.fieldIds[0]]; const refEndpointTable = model.tables[refEndpointField.tableId]; const refEndpointSchema = model.schemas[refEndpointTable.schemaId]; - const refEndpointFieldName = this.buildFieldName(refEndpoint.fieldIds, model, 'mssql'); + const refEndpointFieldName = this.buildFieldName(refEndpoint.fieldIds, model); const foreignEndpointField = model.fields[foreignEndpoint.fieldIds[0]]; const foreignEndpointTable = model.tables[foreignEndpointField.tableId]; const foreignEndpointSchema = model.schemas[foreignEndpointTable.schemaId]; - const foreignEndpointFieldName = this.buildFieldName(foreignEndpoint.fieldIds, model, 'mssql'); + const foreignEndpointFieldName = this.buildFieldName(foreignEndpoint.fieldIds, model); if (refOneIndex === -1) { // many to many relationship const firstTableFieldsMap = buildJunctionFields1(refEndpoint.fieldIds, model); @@ -300,7 +308,7 @@ class SqlServerExporter { return strArr; } - static exportIndexes (indexIds, model) { + static exportIndexes (indexIds: number[], model: NormalizedModel): string[] { // exclude composite pk index const indexArr = indexIds.filter((indexId) => !model.indexes[indexId].pk).map((indexId, i) => { const index = model.indexes[indexId]; @@ -320,7 +328,7 @@ class SqlServerExporter { ? `[${schema.name}].` : ''}[${table.name}]`; - const columnArr = []; + const columnArr: string[] = []; index.columnIds.forEach((columnId) => { const column = model.indexColumns[columnId]; let columnStr = ''; @@ -340,7 +348,7 @@ class SqlServerExporter { return indexArr; } - static exportComments (comments, model) { + static exportComments (comments: CommentNode[], model: NormalizedModel): string[] { const commentArr = comments.map((comment) => { const table = model.tables[comment.tableId]; const schema = model.schemas[table.schemaId]; @@ -350,15 +358,15 @@ class SqlServerExporter { switch (comment.type) { case 'table': { line += '@name = N\'Table_Description\',\n'; - line += `@value = '${table.note.replace(/'/g, '\'\'')}',\n`; + line += `@value = '${(table.note || '').replace(/'/g, '\'\'')}',\n`; line += `@level0type = N'Schema', @level0name = '${shouldPrintSchema(schema, model) ? `${schema.name}` : 'dbo'}',\n`; line += `@level1type = N'Table', @level1name = '${table.name}';\n`; break; } case 'column': { - const field = model.fields[comment.fieldId]; + const field = model.fields[comment.fieldId!]; line += '@name = N\'Column_Description\',\n'; - line += `@value = '${field.note.replace(/'/g, '\'\'')}',\n`; + line += `@value = '${(field.note || '').replace(/'/g, '\'\'')}',\n`; line += `@level0type = N'Schema', @level0name = '${shouldPrintSchema(schema, model) ? `${schema.name}` : 'dbo'}',\n`; line += `@level1type = N'Table', @level1name = '${table.name}',\n`; line += `@level2type = N'Column', @level2name = '${field.name}';\n`; @@ -376,7 +384,7 @@ class SqlServerExporter { return commentArr; } - static export (model) { + static export (model: NormalizedModel): string { const database = model.database['1']; const usedTableNames = new Set(Object.values(model.tables).map((table) => table.name)); @@ -398,12 +406,12 @@ class SqlServerExporter { prevStatements.indexes.push(...SqlServerExporter.exportIndexes(indexIds, model)); } - const commentNodes = flatten(tableIds.map((tableId) => { + const commentNodes: CommentNode[] = flatten(tableIds.map((tableId): CommentNode[] => { const { fieldIds, note } = model.tables[tableId]; - const fieldObjects = fieldIds + const fieldObjects: CommentNode[] = fieldIds .filter((fieldId) => model.fields[fieldId].note) - .map((fieldId) => ({ type: 'column', fieldId, tableId })); - return note ? [{ type: 'table', tableId }].concat(fieldObjects) : fieldObjects; + .map((fieldId) => ({ type: 'column' as const, fieldId, tableId })); + return note ? [{ type: 'table' as const, tableId }, ...fieldObjects] : fieldObjects; })); if (!isEmpty(commentNodes)) { prevStatements.comments.push(...SqlServerExporter.exportComments(commentNodes, model)); @@ -415,12 +423,12 @@ class SqlServerExporter { return prevStatements; }, { - schemas: [], - enums: [], - tables: [], - indexes: [], - comments: [], - refs: [], + schemas: [] as string[], + enums: [] as string[], + tables: [] as string[], + indexes: [] as string[], + comments: [] as string[], + refs: [] as string[], }); // Export INSERT statements with constraint handling diff --git a/packages/dbml-core/src/export/index.js b/packages/dbml-core/src/export/index.js deleted file mode 100644 index 3687b8ccf..000000000 --- a/packages/dbml-core/src/export/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import ModelExporter from './ModelExporter'; -import Parser from '../parse/Parser'; - -function _export (str, format) { - const database = (new Parser()).parse(str, 'dbmlv2'); - return ModelExporter.export(database.normalize(), format); -} - -export default { - export: _export, -}; diff --git a/packages/dbml-core/src/export/index.ts b/packages/dbml-core/src/export/index.ts new file mode 100644 index 000000000..12afe1bee --- /dev/null +++ b/packages/dbml-core/src/export/index.ts @@ -0,0 +1,11 @@ +import ModelExporter, { ExportFlags, ExportFormatOption } from './ModelExporter'; +import Parser from '../parse/Parser'; + +function _export (str: string, format: ExportFormatOption, flags: ExportFlags = {}) { + const database = (new Parser()).parse(str, 'dbmlv2'); + return ModelExporter.export(database.normalize(), format, flags); +} + +export default { + export: _export, +}; diff --git a/packages/dbml-core/src/export/utils.js b/packages/dbml-core/src/export/utils.ts similarity index 74% rename from packages/dbml-core/src/export/utils.js rename to packages/dbml-core/src/export/utils.ts index 92f9e8038..9b9a5c66a 100644 --- a/packages/dbml-core/src/export/utils.js +++ b/packages/dbml-core/src/export/utils.ts @@ -1,27 +1,35 @@ import { tryExtractDateTime } from '@dbml/parse'; import { DEFAULT_SCHEMA_NAME } from '../model_structure/config'; import { DateTime } from 'luxon'; +import { NormalizedModel } from '../model_structure/database'; +import { NormalizedSchema } from '../model_structure/schema'; -export function hasWhiteSpace (s) { +export interface CommentNode { + type: 'table' | 'column'; + tableId: number; + fieldId?: number; +} + +export function hasWhiteSpace (s: string): boolean { return /\s/g.test(s); } -export function hasWhiteSpaceOrUpperCase (s) { +export function hasWhiteSpaceOrUpperCase (s: string): boolean { return /[\sA-Z]/g.test(s); } -export function shouldPrintSchema (schema, model) { +export function shouldPrintSchema (schema: NormalizedSchema, model: NormalizedModel): boolean { return schema.name !== DEFAULT_SCHEMA_NAME || (schema.name === DEFAULT_SCHEMA_NAME && model.database['1'].hasDefaultSchema); } -export function buildJunctionFields1 (fieldIds, model) { - const fieldsMap = new Map(); +export function buildJunctionFields1 (fieldIds: number[], model: NormalizedModel): Map { + const fieldsMap = new Map(); fieldIds.map((fieldId) => fieldsMap.set(`${model.tables[model.fields[fieldId].tableId].name}_${model.fields[fieldId].name}`, model.fields[fieldId].type.type_name)); return fieldsMap; } -export function buildJunctionFields2 (fieldIds, model, firstTableFieldsMap) { - const fieldsMap = new Map(); +export function buildJunctionFields2 (fieldIds: number[], model: NormalizedModel, firstTableFieldsMap: Map): Map { + const fieldsMap = new Map(); fieldIds.forEach((fieldId) => { let fieldName = `${model.tables[model.fields[fieldId].tableId].name}_${model.fields[fieldId].name}`; let count = 1; @@ -34,7 +42,7 @@ export function buildJunctionFields2 (fieldIds, model, firstTableFieldsMap) { return fieldsMap; } -export function buildNewTableName (firstTable, secondTable, usedTableNames) { +export function buildNewTableName (firstTable: string, secondTable: string, usedTableNames: Set): string { let newTableName = `${firstTable}_${secondTable}`; let count = 1; while (usedTableNames.has(newTableName)) { @@ -54,7 +62,7 @@ export function buildNewTableName (firstTable, secondTable, usedTableNames) { * @returns string * @description This function is a clone version of the buildNewTableName, but without side effect - update the original usedTableNames */ -export function buildUniqueTableName (schemaName, firstTableName, secondTableName, schemaToTableNameSetMap) { +export function buildUniqueTableName (schemaName: NormalizedSchema, firstTableName: string, secondTableName: string, schemaToTableNameSetMap: Map>): string { let newTableName = `${firstTableName}_${secondTableName}`; let count = 1; @@ -70,7 +78,7 @@ export function buildUniqueTableName (schemaName, firstTableName, secondTableNam return newTableName; } -export function escapeObjectName (name, database) { +export function escapeObjectName (name: string, database: string): string { if (!name) { return ''; } @@ -97,7 +105,7 @@ export function escapeObjectName (name, database) { * @param {string} value - The datetime string to parse (supports various formats, normalized to ISO8601) * @returns {{ datetime: DateTime, hasTimezone: boolean } | null} Parsed datetime with timezone info, or null if invalid */ -export function parseIsoDatetime (value) { +export function parseIsoDatetime (value: string): { datetime: DateTime; hasTimezone: boolean } | null { if (!value || typeof value !== 'string') { return null; } @@ -133,7 +141,7 @@ export function parseIsoDatetime (value) { * @param {boolean} hasTimezone - Whether to include timezone * @returns {string} Formatted datetime string for Oracle */ -export function formatDatetimeForOracle (datetime, hasTimezone) { +export function formatDatetimeForOracle (datetime: DateTime, hasTimezone: boolean): string { if (hasTimezone) { // Format with timezone: YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM // Oracle expects the timezone offset in +HH:MM or -HH:MM format diff --git a/packages/dbml-core/src/import/index.js b/packages/dbml-core/src/import/index.ts similarity index 72% rename from packages/dbml-core/src/import/index.js rename to packages/dbml-core/src/import/index.ts index 9007945ed..5b1f14373 100644 --- a/packages/dbml-core/src/import/index.js +++ b/packages/dbml-core/src/import/index.ts @@ -1,15 +1,15 @@ import { generateDatabase } from '../parse/databaseGenerator'; -import Parser from '../parse/Parser'; +import Parser, { ParseFormat } from '../parse/Parser'; import ModelExporter from '../export/ModelExporter'; -function _import (str, format) { +function _import (str: string, format: ParseFormat): string { const database = (new Parser()).parse(str, format); const dbml = ModelExporter.export(database.normalize(), 'dbml'); return dbml; } -function generateDbml (schemaJson) { +function generateDbml (schemaJson: object): string { const database = generateDatabase(schemaJson); const dbml = ModelExporter.export(database.normalize(), 'dbml'); diff --git a/packages/dbml-core/src/model_structure/check.js b/packages/dbml-core/src/model_structure/check.js deleted file mode 100644 index e00271cf9..000000000 --- a/packages/dbml-core/src/model_structure/check.js +++ /dev/null @@ -1,51 +0,0 @@ -import Element from './element'; - -class Check extends Element { - constructor ({ - token, name, expression, table, column = null, injectedPartial = null, - } = {}) { - super(token); - this.name = name; - this.expression = expression; - this.table = table; - this.column = column; - this.injectedPartial = injectedPartial; - this.dbState = this.table.dbState; - this.generateId(); - } - - generateId () { - this.id = this.dbState.generateId('checkId'); - } - - export () { - return { - ...this.shallowExport(), - }; - } - - exportParentIds () { - return { - tableId: this.table.id, - columnId: this.column?.id, - injectedPartialId: this.injectedPartial?.id, - }; - } - - shallowExport () { - return { - name: this.name, - expression: this.expression, - }; - } - - normalize (model) { - model.checks[this.id] = { - id: this.id, - ...this.shallowExport(), - ...this.exportParentIds(), - }; - } -} - -export default Check; diff --git a/packages/dbml-core/src/model_structure/check.ts b/packages/dbml-core/src/model_structure/check.ts new file mode 100644 index 000000000..a4bbc17fa --- /dev/null +++ b/packages/dbml-core/src/model_structure/check.ts @@ -0,0 +1,85 @@ +import { NormalizedModel } from './database'; +import Element, { Token } from './element'; +import Field from './field'; +import Table from './table'; +import TablePartial from './tablePartial'; +import DbState from './dbState'; + +interface RawCheck { + token: Token; + name: string; + expression: string; + table: Table; + column?: Field | null; + injectedPartial?: TablePartial | null; +} + +export interface NormalizedCheck { + id: number; + name: string; + expression: string; + tableId: number; + columnId: number | null; + injectedPartialId: number | null; +} + +export interface NormalizedCheckIdMap { + [_id: number]: NormalizedCheck; +} + +class Check extends Element { + name: string; + expression: string; + table: Table; + column: Field | null; + injectedPartial: TablePartial | null; + dbState: DbState; + + constructor ({ + token, name, expression, table, column = null, injectedPartial = null, + }: RawCheck) { + super(token); + this.name = name; + this.expression = expression; + this.table = table; + this.column = column; + this.injectedPartial = injectedPartial; + this.dbState = this.table.dbState; + this.generateId(); + } + + generateId (): void { + this.id = this.dbState.generateId('checkId'); + } + + export (): { name: string; expression: string } { + return { + ...this.shallowExport(), + }; + } + + exportParentIds (): { tableId: number; columnId: number | null; injectedPartialId: number | null } { + return { + tableId: this.table.id, + columnId: this.column?.id ?? null, + injectedPartialId: this.injectedPartial?.id ?? null, + }; + } + + shallowExport (): { name: string; expression: string } { + return { + name: this.name, + expression: this.expression, + }; + } + + normalize (model: NormalizedModel): void { + model.checks[this.id] = { + id: this.id, + ...this.shallowExport(), + ...this.exportParentIds(), + }; + } +} + +export default Check; diff --git a/packages/dbml-core/src/model_structure/config.js b/packages/dbml-core/src/model_structure/config.ts similarity index 100% rename from packages/dbml-core/src/model_structure/config.js rename to packages/dbml-core/src/model_structure/config.ts diff --git a/packages/dbml-core/src/model_structure/database.js b/packages/dbml-core/src/model_structure/database.ts similarity index 58% rename from packages/dbml-core/src/model_structure/database.js rename to packages/dbml-core/src/model_structure/database.ts index b3a519213..a7fd9849f 100644 --- a/packages/dbml-core/src/model_structure/database.js +++ b/packages/dbml-core/src/model_structure/database.ts @@ -1,18 +1,114 @@ import { capitalize, get } from 'lodash-es'; -import Schema from './schema'; -import Ref from './ref'; -import Enum from './enum'; -import TableGroup from './tableGroup'; -import Table from './table'; -import StickyNote from './stickyNote'; -import Element from './element'; +import Schema, { NormalizedSchemaIdMap, RawSchema } from './schema'; +import Ref, { NormalizedRefIdMap } from './ref'; +import Enum, { NormalizedEnumIdMap } from './enum'; +import TableGroup, { NormalizedTableGroupIdMap } from './tableGroup'; +import Table, { NormalizedTableIdMap } from './table'; +import StickyNote, { NormalizedNoteIdMap } from './stickyNote'; +import Element, { RawNote, Token } from './element'; import { DEFAULT_SCHEMA_NAME, TABLE, TABLE_GROUP, ENUM, REF, NOTE, } from './config'; import DbState from './dbState'; -import TablePartial from './tablePartial'; +import TablePartial, { NormalizedTablePartialIdMap } from './tablePartial'; +import { NormalizedEndpointIdMap } from './endpoint'; +import { NormalizedEnumValueIdMap } from './enumValue'; +import { NormalizedFieldIdMap } from './field'; +import { NormalizedIndexColumnIdMap } from './indexColumn'; +import { NormalizedIndexIdMap } from './indexes'; +import { NormalizedCheckIdMap } from './check'; + +export interface Project { + note: RawNote; + database_type: string; + name: string; +} + +export type RecordValueType = 'string' | 'bool' | 'integer' | 'real' | 'date' | 'time' | 'datetime' | string; + +export interface RecordValue { + value: any; + type: RecordValueType; +} + +export interface RawTableRecord { + schemaName: string | undefined; + tableName: string; + columns: string[]; + values: { + value: any; + type: RecordValueType; + }[][]; +} + +export interface TableRecord extends RawTableRecord { + id: number; +} + +export type NormalizedRecord = TableRecord; + +export interface NormalizedRecordIdMap { + [_id: number]: NormalizedRecord; +} + +export interface RawDatabase { + schemas: RawSchema[]; + tables: any[]; + notes: any[]; + enums: any[]; + refs: any[]; + tableGroups: any[]; + project: Project; + records: RawTableRecord[]; + tablePartials: any[]; +} + +export interface NormalizedDatabase { + id: number; + hasDefaultSchema: boolean; + note: string | null; + databaseType: string; + name: string; + schemaIds: number[]; + noteIds: number[]; +} + +export interface NormalizedDatabaseIdMap { + [_id: number]: NormalizedDatabase; +} + +export interface NormalizedModel { + database: NormalizedDatabaseIdMap; + schemas: NormalizedSchemaIdMap; + endpoints: NormalizedEndpointIdMap; + refs: NormalizedRefIdMap; + fields: NormalizedFieldIdMap; + tables: NormalizedTableIdMap; + tableGroups: NormalizedTableGroupIdMap; + enums: NormalizedEnumIdMap; + enumValues: NormalizedEnumValueIdMap; + indexes: NormalizedIndexIdMap; + indexColumns: NormalizedIndexColumnIdMap; + notes: NormalizedNoteIdMap; + checks: NormalizedCheckIdMap; + tablePartials: NormalizedTablePartialIdMap; + records: NormalizedRecordIdMap; +} class Database extends Element { + dbState: DbState; + hasDefaultSchema: boolean; + schemas: Schema[]; + notes: StickyNote[]; + note: string; + noteToken: Token; + databaseType: string; + name: string; + records: TableRecord[]; + tablePartials: TablePartial[]; + aliases: any[]; + injectedRawRefs: any[]; + constructor ({ schemas = [], tables = [], @@ -20,22 +116,22 @@ class Database extends Element { enums = [], refs = [], tableGroups = [], - project = {}, + project = {} as Project, aliases = [], records = [], tablePartials = [], - }) { - super(); + }: Partial & { aliases?: any[] } = {}) { + super(undefined as unknown as Token); this.dbState = new DbState(); this.generateId(); this.hasDefaultSchema = false; this.schemas = []; this.notes = []; - this.note = project.note ? get(project, 'note.value', project.note) : null; - this.noteToken = project.note ? get(project, 'note.token', project.noteToken) : null; + this.note = (project.note ? get(project, 'note.value', project.note) : null) as string; + this.noteToken = (project.note ? get(project, 'note.token', (project as any).noteToken) : null) as Token; this.databaseType = project.database_type; this.name = project.name; - this.token = project.token; + this.token = (project as any).token; this.aliases = aliases; this.records = []; this.tablePartials = []; @@ -63,17 +159,28 @@ class Database extends Element { }); } - generateId () { + generateId (): void { this.id = this.dbState.generateId('dbId'); } - processNotes (rawNotes) { + processNotes (rawNotes: any[]): void { rawNotes.forEach((note) => { this.pushNote(new StickyNote({ ...note, database: this })); }); } - processRecords (rawRecords) { + pushNote (note: StickyNote): void { + this.checkNote(note); + this.notes.push(note); + } + + checkNote (note: StickyNote): void { + if (this.notes.some((n) => n.name === note.name)) { + note.error(`Notes ${note.name} existed`); + } + } + + processRecords (rawRecords: RawTableRecord[]): void { rawRecords.forEach(({ schemaName, tableName, columns, values, }) => { @@ -87,42 +194,35 @@ class Database extends Element { }); } - processTablePartials (rawTablePartials) { + processTablePartials (rawTablePartials: any[]): TablePartial[] { rawTablePartials.forEach((rawTablePartial) => { this.tablePartials.push(new TablePartial({ ...rawTablePartial, dbState: this.dbState })); }); + return this.tablePartials; } - pushNote (note) { - this.checkNote(note); - this.notes.push(note); - } - - checkNote (note) { - if (this.notes.some((n) => n.name === note.name)) { - note.error(`Notes ${note.name} existed`); - } - } - - processSchemas (rawSchemas) { + processSchemas (rawSchemas: RawSchema[]): void { rawSchemas.forEach((schema) => { this.pushSchema(new Schema({ ...schema, database: this })); }); } - pushSchema (schema) { + pushSchema (schema: Schema): void { this.checkSchema(schema); this.schemas.push(schema); } - checkSchema (schema) { + checkSchema (schema: Schema): void { if (this.schemas.some((s) => s.name === schema.name)) { schema.error(`Schemas ${schema.name} existed`); } } - processSchemaElements (elements, elementType) { - let schema; + processSchemaElements ( + elements: any[], + elementType: any, + ): void { + let schema: Schema; elements.forEach((element) => { if (element.schemaName) { @@ -157,7 +257,7 @@ class Database extends Element { }); } - findOrCreateSchema (schemaName) { + findOrCreateSchema (schemaName: string): Schema { let schema = this.schemas.find((s) => s.name === schemaName || s.alias === schemaName); // create new schema if schema not found if (!schema) { @@ -165,7 +265,7 @@ class Database extends Element { name: schemaName, note: { value: schemaName === DEFAULT_SCHEMA_NAME ? `Default ${capitalize(DEFAULT_SCHEMA_NAME)} Schema` : null, - }, + } as RawNote, database: this, }); @@ -175,7 +275,7 @@ class Database extends Element { return schema; } - findTableAlias (alias) { + findTableAlias (alias: string): Table | null | undefined { const sym = this.aliases.find((a) => a.name === alias); if (!sym || sym.kind !== 'table') return null; @@ -188,7 +288,10 @@ class Database extends Element { return table; } - findTable (schemaName, tableName) { + findTable ( + schemaName: any, + tableName?: any, + ): Table | undefined { let table = null; if (!schemaName) { table = this.findTableAlias(tableName); @@ -202,24 +305,27 @@ class Database extends Element { return schema.findTable(tableName); } - findEnum (schemaName, name) { + findEnum ( + schemaName: string, + name: string, + ): Enum | null | undefined { const schema = this.schemas.find((s) => s.name === schemaName || s.alias === schemaName); if (!schema) return null; const _enum = schema.enums.find((e) => e.name === name); return _enum; } - findTablePartial (name) { + findTablePartial (name: string): TablePartial | undefined { return this.tablePartials.find((tp) => tp.name === name); } - export () { + export (): ReturnType { return { ...this.exportChild(), }; } - shallowExport () { + shallowExport (): { hasDefaultSchema: boolean; note: string; databaseType: string; name: string } { return { hasDefaultSchema: this.hasDefaultSchema, note: this.note, @@ -228,7 +334,7 @@ class Database extends Element { }; } - exportChild () { + exportChild (): { schemas: ReturnType[]; notes: ReturnType[]; records: TableRecord[] } { return { schemas: this.schemas.map((s) => s.export()), notes: this.notes.map((n) => n.export()), @@ -236,15 +342,15 @@ class Database extends Element { }; } - exportChildIds () { + exportChildIds (): { schemaIds: number[]; noteIds: number[] } { return { schemaIds: this.schemas.map((s) => s.id), noteIds: this.notes.map((n) => n.id), }; } - normalize () { - const normalizedModel = { + normalize (): NormalizedModel { + const normalizedModel: NormalizedModel = { database: { [this.id]: { id: this.id, diff --git a/packages/dbml-core/src/model_structure/dbState.js b/packages/dbml-core/src/model_structure/dbState.ts similarity index 56% rename from packages/dbml-core/src/model_structure/dbState.js rename to packages/dbml-core/src/model_structure/dbState.ts index bf75f43c7..86d6811dd 100644 --- a/packages/dbml-core/src/model_structure/dbState.js +++ b/packages/dbml-core/src/model_structure/dbState.ts @@ -1,4 +1,21 @@ export default class DbState { + [key: string]: any; + dbId: number; + schemaId: number; + enumId: number; + tableGroupId: number; + refId: number; + tableId: number; + noteId: number; + enumValueId: number; + endpointId: number; + checkId: number; + indexId: number; + fieldId: number; + indexColumnId: number; + recordId: number; + tablePartialId: number; + constructor () { this.dbId = 1; this.schemaId = 1; @@ -17,7 +34,7 @@ export default class DbState { this.tablePartialId = 1; } - generateId (el) { + generateId (el: string): any { const id = this[el]; this[el] += 1; return id; diff --git a/packages/dbml-core/src/model_structure/element.js b/packages/dbml-core/src/model_structure/element.js deleted file mode 100644 index 6f342f584..000000000 --- a/packages/dbml-core/src/model_structure/element.js +++ /dev/null @@ -1,23 +0,0 @@ -class ElementError extends Error { - constructor (message, location = { start: { line: 1, column: 1 } }) { - super(message); - this.location = location; - this.error = 'error'; - } -} - -class Element { - constructor (token) { - this.token = token; - } - - bind (selection) { - this.selection = selection; - } - - error (message) { - throw new ElementError(message, this.token); - } -} - -export default Element; diff --git a/packages/dbml-core/src/model_structure/element.ts b/packages/dbml-core/src/model_structure/element.ts new file mode 100644 index 000000000..dc118d6d8 --- /dev/null +++ b/packages/dbml-core/src/model_structure/element.ts @@ -0,0 +1,51 @@ +export interface Token { + end: { + column: number; + line: number; + offset: number; + }; + start: { + column: number; + line: number; + offset: number; + }; +} + +export interface RawNote { + value: string; + token: Token; +} + +class ElementError extends Error { + location: { start: { line: number; column: number } }; + error: string; + + constructor ( + message: string, + location: { start: { line: number; column: number } } = { start: { line: 1, column: 1 } }, + ) { + super(message); + this.location = location; + this.error = 'error'; + } +} + +class Element { + token: Token; + id!: number; + selection!: string; + + constructor (token: Token) { + this.token = token; + } + + bind (selection: any): void { + this.selection = selection; + } + + error (message: string): void { + throw new ElementError(message, this.token); + } +} + +export default Element; diff --git a/packages/dbml-core/src/model_structure/endpoint.js b/packages/dbml-core/src/model_structure/endpoint.ts similarity index 60% rename from packages/dbml-core/src/model_structure/endpoint.js rename to packages/dbml-core/src/model_structure/endpoint.ts index d5692308d..7d2758036 100644 --- a/packages/dbml-core/src/model_structure/endpoint.js +++ b/packages/dbml-core/src/model_structure/endpoint.ts @@ -1,11 +1,45 @@ -import Element from './element'; +import Element, { Token } from './element'; import { DEFAULT_SCHEMA_NAME } from './config'; import { shouldPrintSchema, shouldPrintSchemaName } from './utils'; +import Field from './field'; +import Ref from './ref'; +import DbState from './dbState'; +import { NormalizedModel } from './database'; + +export interface RawEndpoint { + schemaName: string | null; + tableName: string; + fieldNames: string[]; + relation: '1' | '*'; + token: Token; +} + +export interface NormalizedEndpoint { + id: number; + schemaName: string | null; + tableName: string; + fieldNames: string[]; + fieldIds: number[]; + relation: string; + refId: number; +} + +export interface NormalizedEndpointIdMap { + [_id: number]: NormalizedEndpoint; +} class Endpoint extends Element { + relation: any; + schemaName: string; + tableName: string; + fieldNames: string[]; + fields: Field[]; + ref: Ref; + dbState: DbState; + constructor ({ tableName, schemaName, fieldNames, relation, token, ref, - }) { + }: { tableName: any; schemaName: any; fieldNames: any; relation: any; token: any; ref: any }) { super(token); this.relation = relation; @@ -29,38 +63,38 @@ class Endpoint extends Element { this.setFields(fieldNames, table); } - generateId () { + generateId (): void { this.id = this.dbState.generateId('endpointId'); } - equals (endpoint) { + equals (endpoint: any): boolean { if (this.fields.length !== endpoint.fields.length) return false; return this.compareFields(endpoint); } - compareFields (endpoint) { + compareFields (endpoint: any): boolean { const sortedThisFieldIds = this.fields.map((field) => field.id).sort(); - const sortedEndpointFieldIds = endpoint.fields.map((field) => field.id).sort(); + const sortedEndpointFieldIds = endpoint.fields.map((field: any) => field.id).sort(); for (let i = 0; i < sortedThisFieldIds.length; i += 1) { if (sortedThisFieldIds[i] !== sortedEndpointFieldIds[i]) return false; } return true; } - export () { + export (): { schemaName: string; tableName: string; fieldNames: string[]; relation: any } { return { ...this.shallowExport(), }; } - exportParentIds () { + exportParentIds (): { refId: number; fieldIds: number[] } { return { refId: this.ref.id, fieldIds: this.fields.map((field) => field.id), }; } - shallowExport () { + shallowExport (): { schemaName: string; tableName: string; fieldNames: string[]; relation: any } { return { schemaName: this.schemaName, tableName: this.tableName, @@ -69,22 +103,25 @@ class Endpoint extends Element { }; } - setFields (fieldNames, table) { + setFields ( + fieldNames: any, + table: any, + ): void { let newFieldNames = (fieldNames && fieldNames.length) ? [...fieldNames] : []; if (!newFieldNames.length) { - const fieldHasPK = table.fields.find((field) => field.pk); + const fieldHasPK = table.fields.find((field: any) => field.pk); if (fieldHasPK) { newFieldNames.push(fieldHasPK.name); } else { - const indexHasPK = table.indexes.find((index) => index.pk); + const indexHasPK = table.indexes.find((index: any) => index.pk); if (indexHasPK) { - newFieldNames = indexHasPK.columns.map((column) => column.value); + newFieldNames = indexHasPK.columns.map((column: any) => column.value); } else { this.error(`Can't find primary or composite key in table ${shouldPrintSchema(table.schema) ? `"${table.schema.name}".` : ''}"${table.name}"`); } } } - newFieldNames.forEach((fieldName) => { + newFieldNames.forEach((fieldName: string) => { const field = table.findField(fieldName); if (!field) { this.error(`Can't find field ${shouldPrintSchema(table.schema) @@ -96,7 +133,7 @@ class Endpoint extends Element { }); } - normalize (model) { + normalize (model: NormalizedModel): void { model.endpoints[this.id] = { id: this.id, ...this.shallowExport(), diff --git a/packages/dbml-core/src/model_structure/enum.js b/packages/dbml-core/src/model_structure/enum.ts similarity index 57% rename from packages/dbml-core/src/model_structure/enum.js rename to packages/dbml-core/src/model_structure/enum.ts index c05f68e67..4b63c433c 100644 --- a/packages/dbml-core/src/model_structure/enum.js +++ b/packages/dbml-core/src/model_structure/enum.ts @@ -1,19 +1,53 @@ import { get } from 'lodash-es'; -import Element from './element'; +import Element, { Token, RawNote } from './element'; import EnumValue from './enumValue'; +import Field from './field'; +import Schema from './schema'; +import DbState from './dbState'; +import { NormalizedModel } from './database'; import { shouldPrintSchema } from './utils'; +interface RawEnum { + name: string; + token: Token; + values: any[]; + note: RawNote; + schema: Schema; + noteToken?: Token | null; +} + +export interface NormalizedEnum { + id: number; + name: string; + note: string | null; + valueIds: number[]; + fieldIds: number[]; + schemaId: number; +} + +export interface NormalizedEnumIdMap { + [_id: number]: NormalizedEnum; +} + class Enum extends Element { + name: string; + values: EnumValue[]; + note: string; + noteToken: Token; + schema: Schema; + fields: Field[]; + dbState: DbState; + constructor ({ name, token, values, note, schema, noteToken = null, - } = {}) { + }: RawEnum) { super(token); if (!name) { this.error('Enum must have a name'); } this.name = name; - this.note = note ? get(note, 'value', note) : null; - this.noteToken = note ? get(note, 'token', noteToken) : null; + this.note = (note ? get(note, 'value', note) : null) as string; + this.noteToken = (note ? get(note, 'token', noteToken) : null) as Token; this.values = []; this.fields = []; this.schema = schema; @@ -23,22 +57,22 @@ class Enum extends Element { this.processValues(values); } - generateId () { + generateId (): void { this.id = this.dbState.generateId('enumId'); } - processValues (rawValues) { - rawValues.forEach((value) => { + processValues (rawValues: any): void { + rawValues.forEach((value: any) => { this.pushValue(new EnumValue({ ...value, _enum: this })); }); } - pushValue (value) { + pushValue (value: any): void { this.checkValue(value); this.values.push(value); } - checkValue (value) { + checkValue (value: any): void { if (this.values.some((v) => v.name === value.name)) { value.error(`Enum value "${value.name}" existed in enum ${shouldPrintSchema(this.schema) ? `"${this.schema.name}".` @@ -46,12 +80,12 @@ class Enum extends Element { } } - pushField (field) { + pushField (field: any): void { this.checkField(field); this.fields.push(field); } - checkField (field) { + checkField (field: any): void { if (this.fields.some((f) => f.id === field.id)) { this.error(`Field ${shouldPrintSchema(field.table.schema) ? `"${field.table.schema.name}".` @@ -61,40 +95,40 @@ class Enum extends Element { } } - export () { + export (): ReturnType & ReturnType { return { ...this.shallowExport(), ...this.exportChild(), }; } - exportChild () { + exportChild (): { values: { name: string; note: string }[] } { return { values: this.values.map((value) => value.export()), }; } - exportChildIds () { + exportChildIds (): { valueIds: number[]; fieldIds: number[] } { return { valueIds: this.values.map((value) => value.id), fieldIds: this.fields.map((field) => field.id), }; } - exportParentIds () { + exportParentIds (): { schemaId: number } { return { schemaId: this.schema.id, }; } - shallowExport () { + shallowExport (): { name: string; note: string } { return { name: this.name, note: this.note, }; } - normalize (model) { + normalize (model: NormalizedModel): void { model.enums[this.id] = { id: this.id, ...this.shallowExport(), diff --git a/packages/dbml-core/src/model_structure/enumValue.js b/packages/dbml-core/src/model_structure/enumValue.js deleted file mode 100644 index 11b72e1b1..000000000 --- a/packages/dbml-core/src/model_structure/enumValue.js +++ /dev/null @@ -1,52 +0,0 @@ -import { get } from 'lodash-es'; -import Element from './element'; - -class EnumValue extends Element { - constructor ({ - name, token, note, _enum, noteToken = null, - } = {}) { - super(token); - if (!name) { - this.error('Enum value must have a name'); - } - this.name = name; - this.note = note ? get(note, 'value', note) : null; - this.noteToken = note ? get(note, 'token', noteToken) : null; - this._enum = _enum; - this.dbState = this._enum.dbState; - this.generateId(); - } - - generateId () { - this.id = this.dbState.generateId('enumValueId'); - } - - export () { - return { - ...this.shallowExport(), - }; - } - - exportParentIds () { - return { - enumId: this._enum.id, - }; - } - - shallowExport () { - return { - name: this.name, - note: this.note, - }; - } - - normalize (model) { - model.enumValues[this.id] = { - id: this.id, - ...this.shallowExport(), - ...this.exportParentIds(), - }; - } -} - -export default EnumValue; diff --git a/packages/dbml-core/src/model_structure/enumValue.ts b/packages/dbml-core/src/model_structure/enumValue.ts new file mode 100644 index 000000000..857db2537 --- /dev/null +++ b/packages/dbml-core/src/model_structure/enumValue.ts @@ -0,0 +1,80 @@ +import { get } from 'lodash-es'; +import Element, { Token, RawNote } from './element'; +import Enum from './enum'; +import DbState from './dbState'; +import { NormalizedModel } from './database'; + +interface RawEnumValue { + name: string; + token: Token; + note: RawNote; + _enum: Enum; + noteToken?: Token | null; +} + +export interface NormalizedEnumValue { + id: number; + name: string; + note: string | null; + enumId: number; +} + +export interface NormalizedEnumValueIdMap { + [_id: number]: NormalizedEnumValue; +} + +class EnumValue extends Element { + name: string; + note: string; + noteToken: Token; + _enum: Enum; + dbState: DbState; + + constructor ({ + name, token, note, _enum, noteToken = null, + }: RawEnumValue) { + super(token); + if (!name) { + this.error('Enum value must have a name'); + } + this.name = name; + this.note = (note ? get(note, 'value', note) : null) as string; + this.noteToken = (note ? get(note, 'token', noteToken) : null) as Token; + this._enum = _enum; + this.dbState = this._enum.dbState; + this.generateId(); + } + + generateId (): void { + this.id = this.dbState.generateId('enumValueId'); + } + + export (): { name: string; note: string } { + return { + ...this.shallowExport(), + }; + } + + exportParentIds (): { enumId: number } { + return { + enumId: this._enum.id, + }; + } + + shallowExport (): { name: string; note: string } { + return { + name: this.name, + note: this.note, + }; + } + + normalize (model: NormalizedModel): void { + model.enumValues[this.id] = { + id: this.id, + ...this.shallowExport(), + ...this.exportParentIds(), + }; + } +} + +export default EnumValue; diff --git a/packages/dbml-core/src/model_structure/field.js b/packages/dbml-core/src/model_structure/field.ts similarity index 51% rename from packages/dbml-core/src/model_structure/field.js rename to packages/dbml-core/src/model_structure/field.ts index 45efd2998..0d0156db6 100644 --- a/packages/dbml-core/src/model_structure/field.js +++ b/packages/dbml-core/src/model_structure/field.ts @@ -1,13 +1,94 @@ import { get } from 'lodash-es'; -import Element from './element'; +import Element, { Token, RawNote } from './element'; import { DEFAULT_SCHEMA_NAME } from './config'; import Check from './check'; +import Endpoint from './endpoint'; +import Enum from './enum'; +import Table from './table'; +import TablePartial from './tablePartial'; +import DbState from './dbState'; +import { NormalizedModel } from './database'; + +export interface InlineRef { + schemaName: string | null; + tableName: string; + fieldNames: string[]; + relation: '>' | '<' | '-' | '<>'; + token: Token; +} + +export interface ColumnType { + schemaName: string | null; + type_name: string; + args: string | null; +} + +export interface RawField { + name: string; + type: any; + unique: boolean; + pk: boolean; + token: Token; + not_null: boolean; + note: RawNote; + dbdefault: any; + increment: boolean; + checks?: any[]; + table: Table; + noteToken?: Token | null; + injectedPartial?: TablePartial | null; + injectedToken?: Token | null; +} + +export interface NormalizedField { + id: number; + name: string; + type: { + schemaName: string | null; + type_name: string; + }; + unique: boolean; + pk: boolean; + not_null: boolean; + note: string | null; + dbdefault?: { + type: 'number' | 'string' | 'boolean' | 'expression'; + value: number | string; + }; + increment: boolean; + endpointIds: number[]; + tableId: number; + enumId: number | null; + injectedPartialId: number | null; + checkIds: number[]; +} + +export interface NormalizedFieldIdMap { + [_id: number]: NormalizedField; +} class Field extends Element { + name: string; + type: any; + unique: boolean; + pk: boolean; + dbState: DbState; + not_null: boolean; + note: string; + noteToken: Token; + dbdefault: any; + increment: boolean; + checks: Check[]; + table: Table; + endpoints: Endpoint[]; + _enum!: Enum; + injectedPartial?: TablePartial; + injectedToken!: Token; + constructor ({ name, type, unique, pk, token, not_null: notNull, note, dbdefault, - increment, checks = [], table = {}, noteToken = null, injectedPartial = null, injectedToken = null, - } = {}) { + increment, checks = [], table = {} as Table, noteToken = null, injectedPartial = null, injectedToken = null, + }: RawField) { super(token); if (!name) { this.error('Field must have a name'); @@ -21,15 +102,15 @@ class Field extends Element { this.unique = unique; this.pk = pk; this.not_null = notNull; - this.note = note ? get(note, 'value', note) : null; - this.noteToken = note ? get(note, 'token', noteToken) : null; + this.note = (note ? get(note, 'value', note) : null) as string; + this.noteToken = (note ? get(note, 'token', noteToken) : null) as Token; this.dbdefault = dbdefault; this.increment = increment; this.checks = []; this.endpoints = []; this.table = table; - this.injectedPartial = injectedPartial; - this.injectedToken = injectedToken; + this.injectedPartial = injectedPartial ?? undefined; + this.injectedToken = injectedToken as Token; this.dbState = this.table.dbState; this.generateId(); this.bindType(); @@ -37,11 +118,11 @@ class Field extends Element { this.processChecks(checks); } - generateId () { + generateId (): void { this.id = this.dbState.generateId('fieldId'); } - bindType () { + bindType (): void { const typeName = this.type.type_name; const typeSchemaName = this.type.schemaName || DEFAULT_SCHEMA_NAME; if (this.type.schemaName) { @@ -65,30 +146,41 @@ class Field extends Element { } } - pushEndpoint (endpoint) { + pushEndpoint (endpoint: any): void { this.endpoints.push(endpoint); } - export () { + export (): ReturnType { return { ...this.shallowExport(), }; } - exportParentIds () { + exportParentIds (): { tableId: number; enumId: number | null } { return { tableId: this.table.id, enumId: this._enum ? this._enum.id : null, }; } - exportChildIds () { + exportChildIds (): { endpointIds: number[] } { return { endpointIds: this.endpoints.map((e) => e.id), }; } - shallowExport () { + shallowExport (): { + name: string; + type: any; + unique: boolean; + pk: boolean; + not_null: boolean; + note: string; + dbdefault: any; + increment: boolean; + injectedPartialId: number | null; + checkIds: number[]; + } { return { name: this.name, type: this.type, @@ -103,7 +195,7 @@ class Field extends Element { }; } - normalize (model) { + normalize (model: NormalizedModel): void { model.fields[this.id] = { id: this.id, ...this.shallowExport(), @@ -114,7 +206,7 @@ class Field extends Element { this.checks.forEach((check) => check.normalize(model)); } - processChecks (checks) { + processChecks (checks: any[]): void { checks.forEach((check) => { this.checks.push(new Check({ ...check, table: this.table, column: this })); }); diff --git a/packages/dbml-core/src/model_structure/indexColumn.js b/packages/dbml-core/src/model_structure/indexColumn.ts similarity index 50% rename from packages/dbml-core/src/model_structure/indexColumn.js rename to packages/dbml-core/src/model_structure/indexColumn.ts index f8229665e..20370a395 100644 --- a/packages/dbml-core/src/model_structure/indexColumn.js +++ b/packages/dbml-core/src/model_structure/indexColumn.ts @@ -1,42 +1,60 @@ import Element from './element'; +import Index from './indexes'; +import DbState from './dbState'; +import { NormalizedModel } from './database'; + +export interface NormalizedIndexColumn { + id: number; + type: string; + value: string; + indexId: number; +} + +export interface NormalizedIndexColumnIdMap { + [_id: number]: NormalizedIndexColumn; +} class IndexColumn extends Element { + type: any; + value: any; + index: Index; + dbState: DbState; + constructor ({ type, value, index, token, - }) { - super(); + }: { type: any; value: any; index: any; token?: any }) { + super(token); this.type = type; this.value = value; this.index = index; - this.token = token; this.dbState = this.index.dbState; this.generateId(); } - generateId () { + generateId (): void { this.id = this.dbState.generateId('indexColumnId'); } - export () { + export (): { type: any; value: any } { return { ...this.shallowExport(), }; } - exportParentIds () { + exportParentIds (): { indexId: number } { return { indexId: this.index.id, }; } - shallowExport () { + shallowExport (): { type: any; value: any } { return { type: this.type, value: this.value, }; } - normalize (model) { + normalize (model: NormalizedModel): void { model.indexColumns[this.id] = { id: this.id, ...this.shallowExport(), diff --git a/packages/dbml-core/src/model_structure/indexes.js b/packages/dbml-core/src/model_structure/indexes.js deleted file mode 100644 index 89fc31568..000000000 --- a/packages/dbml-core/src/model_structure/indexes.js +++ /dev/null @@ -1,93 +0,0 @@ -import Element from './element'; -import IndexColumn from './indexColumn'; - -class Index extends Element { - constructor ({ - columns, type, unique, pk, token, name, note, table = {}, injectedPartial = null, - } = {}) { - super(token); - this.name = name; - this.type = type; - this.unique = unique; - this.note = note ? note.value : null; - this.noteToken = note ? note.token : null; - this.pk = pk; - this.columns = []; - this.table = table; - this.injectedPartial = injectedPartial; - this.dbState = this.table.dbState; - this.generateId(); - - this.processIndexColumns(columns); - } - - generateId () { - this.id = this.dbState.generateId('indexId'); - } - - processIndexColumns (rawColumns) { - rawColumns.forEach((column) => { - this.pushIndexColumn(new IndexColumn({ ...column, index: this })); - }); - } - - pushIndexColumn (column) { - this.checkIndexColumn(column); - this.columns.push(column); - } - - checkIndexColumn (column) { - if (this.columns.some((c) => c.type === column.type && c.value === column.value)) { - column.error(`Index column ${column.value} existed`); - } - } - - export () { - return { - ...this.shallowExport(), - ...this.exportChild(), - }; - } - - exportChild () { - return { - columns: this.columns.map((c) => c.export()), - }; - } - - exportChildIds () { - return { - columnIds: this.columns.map((c) => c.id), - }; - } - - exportParentIds () { - return { - tableId: this.table.id, - }; - } - - shallowExport () { - return { - name: this.name, - type: this.type, - unique: this.unique, - pk: this.pk, - note: this.note, - injectedPartialId: this.injectedPartial?.id, - }; - } - - normalize (model) { - model.indexes[this.id] = { - id: this.id, - ...this.shallowExport(), - ...this.exportChildIds(), - ...this.exportParentIds(), - }; - - this.columns.forEach((c) => c.normalize(model)); - } -} - -export default Index; diff --git a/packages/dbml-core/src/model_structure/indexes.ts b/packages/dbml-core/src/model_structure/indexes.ts new file mode 100644 index 000000000..f32cd7b31 --- /dev/null +++ b/packages/dbml-core/src/model_structure/indexes.ts @@ -0,0 +1,143 @@ +import Element, { RawNote, Token } from './element'; +import IndexColumn from './indexColumn'; +import Table from './table'; +import TablePartial from './tablePartial'; +import DbState from './dbState'; +import { NormalizedModel } from './database'; + +interface RawIndex { + columns: any; + type: any; + unique: boolean; + pk: string; + name: string; + note: RawNote; + table: Table; + token: Token; + injectedPartial?: TablePartial | null; +} + +export interface NormalizedIndex { + id: number; + name: string | null; + type: any; + unique: boolean; + pk: string; + note: string | null; + columnIds: number[]; + tableId: number; + injectedPartialId?: number; +} + +export interface NormalizedIndexIdMap { + [_id: number]: NormalizedIndex; +} + +class Index extends Element { + columns: IndexColumn[]; + type: any; + unique: boolean; + pk: string; + name: string; + note: string; + noteToken: Token; + table: Table; + dbState: DbState; + injectedPartial: TablePartial; + + constructor ({ + columns, type, unique, pk, token, name, note, table = {} as Table, injectedPartial = null, + }: RawIndex) { + super(token); + this.name = name; + this.type = type; + this.unique = unique; + this.note = (note ? note.value : null) as string; + this.noteToken = (note ? note.token : null) as Token; + this.pk = pk; + this.columns = []; + this.table = table; + this.injectedPartial = injectedPartial as TablePartial; + this.dbState = this.table.dbState; + this.generateId(); + + this.processIndexColumns(columns); + } + + generateId (): void { + this.id = this.dbState.generateId('indexId'); + } + + processIndexColumns (rawColumns: any): void { + rawColumns.forEach((column: any) => { + this.pushIndexColumn(new IndexColumn({ ...column, index: this })); + }); + } + + pushIndexColumn (column: any): void { + this.checkIndexColumn(column); + this.columns.push(column); + } + + checkIndexColumn (column: any): void { + if (this.columns.some((c) => c.type === column.type && c.value === column.value)) { + column.error(`Index column ${column.value} existed`); + } + } + + export (): ReturnType & ReturnType { + return { + ...this.shallowExport(), + ...this.exportChild(), + }; + } + + exportChild (): { columns: { type: any; value: any }[] } { + return { + columns: this.columns.map((c) => c.export()), + }; + } + + exportChildIds (): { columnIds: number[] } { + return { + columnIds: this.columns.map((c) => c.id), + }; + } + + exportParentIds (): { tableId: number } { + return { + tableId: this.table.id, + }; + } + + shallowExport (): { + name: string; + type: any; + unique: boolean; + pk: string; + note: string; + injectedPartialId: number | undefined; + } { + return { + name: this.name, + type: this.type, + unique: this.unique, + pk: this.pk, + note: this.note, + injectedPartialId: this.injectedPartial?.id, + }; + } + + normalize (model: NormalizedModel): void { + model.indexes[this.id] = { + id: this.id, + ...this.shallowExport(), + ...this.exportChildIds(), + ...this.exportParentIds(), + }; + + this.columns.forEach((c) => c.normalize(model)); + } +} + +export default Index; diff --git a/packages/dbml-core/src/model_structure/ref.js b/packages/dbml-core/src/model_structure/ref.ts similarity index 55% rename from packages/dbml-core/src/model_structure/ref.js rename to packages/dbml-core/src/model_structure/ref.ts index b5e4086f8..9f50123af 100644 --- a/packages/dbml-core/src/model_structure/ref.js +++ b/packages/dbml-core/src/model_structure/ref.ts @@ -1,21 +1,61 @@ -import Element from './element'; +import Element, { Token } from './element'; import Endpoint from './endpoint'; +import Schema from './schema'; +import DbState from './dbState'; +import Database, { NormalizedModel } from './database'; +import TablePartial from './tablePartial'; import { DEFAULT_SCHEMA_NAME } from './config'; +interface RawRef { + name: string; + color?: string; + endpoints: Endpoint[]; + onDelete: any; + onUpdate: any; + token: Token; + schema: Schema; + injectedPartial?: TablePartial | null; +} + +export interface NormalizedRef { + id: number; + name: string | null; + color?: string; + onUpdate?: string; + onDelete?: string; + schemaId: number; + endpointIds: number[]; + injectedPartialId?: number; +} + +export interface NormalizedRefIdMap { + [_id: number]: NormalizedRef; +} + /** * Compare two pairs of objects * @param {Array} pair1 * @param {Array} pair2 * @returns {Boolean} */ -function isEqualPair (pair1, pair2) { +function isEqualPair (pair1: any[], pair2: any[]): boolean { return pair1[0].equals(pair2[0]) && pair1[1].equals(pair2[1]); } class Ref extends Element { + name: string; + color?: string; + endpoints: Endpoint[]; + onDelete: any; + onUpdate: any; + schema: Schema; + dbState: DbState; + database!: Database; + injectedPartial?: TablePartial; + constructor ({ - name, color, endpoints, onDelete, onUpdate, token, schema = {}, injectedPartial = null, - } = {}) { + name, color, endpoints, onDelete, onUpdate, token, schema = {} as Schema, injectedPartial = null, + }: RawRef) { super(token); this.name = name; this.color = color; @@ -23,19 +63,19 @@ class Ref extends Element { this.onUpdate = onUpdate; this.endpoints = []; this.schema = schema; - this.injectedPartial = injectedPartial; + this.injectedPartial = injectedPartial ?? undefined; this.dbState = this.schema.dbState; this.generateId(); this.processEndpoints(endpoints); } - generateId () { + generateId (): void { this.id = this.dbState.generateId('refId'); } - processEndpoints (rawEndpoints) { - rawEndpoints.forEach((endpoint) => { + processEndpoints (rawEndpoints: any): void { + rawEndpoints.forEach((endpoint: any) => { this.endpoints.push(new Endpoint({ ...endpoint, ref: this })); if (endpoint.schemaName === DEFAULT_SCHEMA_NAME) { // this.schema.database.hasDefaultSchema = true; @@ -52,19 +92,25 @@ class Ref extends Element { // TODO: Handle Error with different number of fields } - equals (ref) { + equals (ref: any): any { return isEqualPair(this.endpoints, ref.endpoints) || isEqualPair(this.endpoints, ref.endpoints.slice().reverse()); } - export () { + export (): ReturnType & ReturnType { return { ...this.shallowExport(), ...this.exportChild(), }; } - shallowExport () { + shallowExport (): { + name: string; + color: string | undefined; + onDelete: any; + onUpdate: any; + injectedPartialId: number | undefined; + } { return { name: this.name, color: this.color, @@ -74,25 +120,25 @@ class Ref extends Element { }; } - exportChild () { + exportChild (): { endpoints: { schemaName: string; tableName: string; fieldNames: string[]; relation: any }[] } { return { endpoints: this.endpoints.map((e) => e.export()), }; } - exportChildIds () { + exportChildIds (): { endpointIds: number[] } { return { endpointIds: this.endpoints.map((e) => e.id), }; } - exportParentIds () { + exportParentIds (): { schemaId: number } { return { schemaId: this.schema.id, }; } - normalize (model) { + normalize (model: NormalizedModel): void { model.refs[this.id] = { id: this.id, ...this.shallowExport(), diff --git a/packages/dbml-core/src/model_structure/schema.js b/packages/dbml-core/src/model_structure/schema.ts similarity index 59% rename from packages/dbml-core/src/model_structure/schema.js rename to packages/dbml-core/src/model_structure/schema.ts index d9362ffe8..dd1dc96d5 100644 --- a/packages/dbml-core/src/model_structure/schema.js +++ b/packages/dbml-core/src/model_structure/schema.ts @@ -1,24 +1,67 @@ import { get } from 'lodash-es'; import Table from './table'; -import Element from './element'; +import Element, { RawNote, Token } from './element'; import Enum from './enum'; import { shouldPrintSchema } from './utils'; import TableGroup from './tableGroup'; import Ref from './ref'; +import Database, { NormalizedModel } from './database'; +import DbState from './dbState'; + +export interface RawSchema { + name: string; + alias?: string; + note?: RawNote; + tables?: Table[]; + refs?: Ref[]; + enums?: Enum[]; + tableGroups?: TableGroup[]; + token?: Token; + database: Database; + noteToken?: Token | null; +} + +export interface NormalizedSchema { + id: number; + name: string; + note: string | null; + alias: string; + tableIds: number[]; + noteIds: number[]; + refIds: number[]; + tableGroupIds: number[]; + enumIds: number[]; + databaseId: number; +} + +export interface NormalizedSchemaIdMap { + [_id: number]: NormalizedSchema; +} class Schema extends Element { + name: string; + alias: string; + note: string; + noteToken: Token; + tables: Table[]; + refs: Ref[]; + enums: Enum[]; + tableGroups: TableGroup[]; + database: Database; + dbState: DbState; + constructor ({ - name, alias, note, tables = [], refs = [], enums = [], tableGroups = [], token, database = {}, noteToken = null, - } = {}) { - super(token); + name, alias, note, tables = [], refs = [], enums = [], tableGroups = [], token, database = {} as Database, noteToken = null, + }: RawSchema) { + super(token as Token); this.tables = []; this.enums = []; this.tableGroups = []; this.refs = []; this.name = name; - this.note = note ? get(note, 'value', note) : null; - this.noteToken = note ? get(note, 'token', noteToken) : null; - this.alias = alias; + this.note = (note ? get(note, 'value', note) : null) as string; + this.noteToken = (note ? get(note, 'token', noteToken) : null) as Token; + this.alias = alias as string; this.database = database; this.dbState = this.database.dbState; this.generateId(); @@ -29,43 +72,43 @@ class Schema extends Element { this.processTableGroups(tableGroups); } - generateId () { + generateId (): void { this.id = this.dbState.generateId('schemaId'); } - processTables (rawTables) { - rawTables.forEach((table) => { + processTables (rawTables: any): void { + rawTables.forEach((table: any) => { this.pushTable(new Table({ ...table, schema: this })); }); } - pushTable (table) { + pushTable (table: any): void { this.checkTable(table); this.tables.push(table); } - checkTable (table) { + checkTable (table: any): void { if (this.tables.some((t) => t.name === table.name)) { table.error(`Table ${shouldPrintSchema(this) ? `"${this.name}".` : ''}"${table.name}" existed`); } } - findTable (tableName) { + findTable (tableName: string): Table | undefined { return this.tables.find((t) => t.name === tableName); } - processEnums (rawEnums) { - rawEnums.forEach((_enum) => { + processEnums (rawEnums: any): void { + rawEnums.forEach((_enum: any) => { this.pushEnum(new Enum({ ..._enum, schema: this })); }); } - pushEnum (_enum) { + pushEnum (_enum: any): void { this.checkEnum(_enum); this.enums.push(_enum); } - checkEnum (_enum) { + checkEnum (_enum: any): void { if (this.enums.some((e) => e.name === _enum.name)) { _enum.error(`Enum ${shouldPrintSchema(this) ? `"${this.name}".` @@ -73,18 +116,18 @@ class Schema extends Element { } } - processRefs (rawRefs) { - rawRefs.forEach((ref) => { + processRefs (rawRefs: any): void { + rawRefs.forEach((ref: any) => { this.pushRef(new Ref({ ...ref, schema: this })); }); } - pushRef (ref) { + pushRef (ref: any): void { this.checkRef(ref); this.refs.push(ref); } - checkRef (ref) { + checkRef (ref: any): void { if (this.refs.some((r) => r.equals(ref))) { const endpoint1 = ref.endpoints[0]; const fieldList1 = endpoint1.fieldNames.map(JSON.stringify).join(', '); @@ -96,38 +139,43 @@ class Schema extends Element { } } - processTableGroups (rawTableGroups) { - rawTableGroups.forEach((tableGroup) => { + processTableGroups (rawTableGroups: any): void { + rawTableGroups.forEach((tableGroup: any) => { this.pushTableGroup(new TableGroup({ ...tableGroup, schema: this })); }); } - pushTableGroup (tableGroup) { + pushTableGroup (tableGroup: any): void { this.checkTableGroup(tableGroup); this.tableGroups.push(tableGroup); } - checkTableGroup (tableGroup) { + checkTableGroup (tableGroup: any): void { if (this.tableGroups.some((tg) => tg.name === tableGroup.name)) { tableGroup.error(`Table Group ${shouldPrintSchema(this) ? `"${this.name}".` : ''}"${tableGroup.name}" existed`); } } - checkSameId (schema) { + checkSameId (schema: any): boolean { return this.name === schema.name || this.alias === schema.name || this.name === schema.alias - || (this.alias && this.alias === schema.alias); + || Boolean(this.alias && this.alias === schema.alias); } - export () { + export (): ReturnType & ReturnType { return { ...this.shallowExport(), ...this.exportChild(), }; } - exportChild () { + exportChild (): { + tables: ReturnType[]; + enums: ReturnType[]; + tableGroups: ReturnType[]; + refs: ReturnType[]; + } { return { tables: this.tables.map((t) => t.export()), enums: this.enums.map((e) => e.export()), @@ -136,22 +184,23 @@ class Schema extends Element { }; } - exportChildIds () { + exportChildIds (): { tableIds: number[]; noteIds: number[]; enumIds: number[]; tableGroupIds: number[]; refIds: number[] } { return { tableIds: this.tables.map((t) => t.id), + noteIds: [], enumIds: this.enums.map((e) => e.id), tableGroupIds: this.tableGroups.map((tg) => tg.id), refIds: this.refs.map((r) => r.id), }; } - exportParentIds () { + exportParentIds (): { databaseId: number } { return { databaseId: this.database.id, }; } - shallowExport () { + shallowExport (): { name: string; note: string; alias: string } { return { name: this.name, note: this.note, @@ -159,7 +208,7 @@ class Schema extends Element { }; } - normalize (model) { + normalize (model: NormalizedModel): void { model.schemas[this.id] = { id: this.id, ...this.shallowExport(), diff --git a/packages/dbml-core/src/model_structure/stickyNote.js b/packages/dbml-core/src/model_structure/stickyNote.js deleted file mode 100644 index 72f63daf0..000000000 --- a/packages/dbml-core/src/model_structure/stickyNote.js +++ /dev/null @@ -1,36 +0,0 @@ -import Element from './element'; - -class StickyNote extends Element { - constructor ({ - name, content, headerColor, token, database = {}, - } = {}) { - super(token); - this.name = name; - this.content = content; - this.headerColor = headerColor; - this.database = database; - this.dbState = this.database.dbState; - this.generateId(); - } - - generateId () { - this.id = this.dbState.generateId('noteId'); - } - - export () { - return { - name: this.name, - content: this.content, - headerColor: this.headerColor, - }; - } - - normalize (model) { - model.notes[this.id] = { - id: this.id, - ...this.export(), - }; - } -} - -export default StickyNote; diff --git a/packages/dbml-core/src/model_structure/stickyNote.ts b/packages/dbml-core/src/model_structure/stickyNote.ts new file mode 100644 index 000000000..8430937b1 --- /dev/null +++ b/packages/dbml-core/src/model_structure/stickyNote.ts @@ -0,0 +1,65 @@ +import Element, { Token } from './element'; +import Database from './database'; +import DbState from './dbState'; +import { NormalizedModel } from './database'; + +export interface RawStickyNote { + name: string; + content: string; + database: Database; + token: Token; + headerColor: string; +} + +export interface NormalizedNote { + id: number; + name: string; + content: string; + headerColor: string | null; +} + +export interface NormalizedNoteIdMap { + [id: number]: NormalizedNote; +} + +class StickyNote extends Element { + name: string; + content: string; + noteToken!: Token; + headerColor: string; + database: Database; + dbState: DbState; + + constructor ({ + name, content, headerColor, token, database = {} as Database, + }: RawStickyNote) { + super(token); + this.name = name; + this.content = content; + this.headerColor = headerColor; + this.database = database; + this.dbState = this.database.dbState; + this.generateId(); + } + + generateId (): void { + this.id = this.dbState.generateId('noteId'); + } + + export (): { name: string; content: string; headerColor: string } { + return { + name: this.name, + content: this.content, + headerColor: this.headerColor, + }; + } + + normalize (model: NormalizedModel): void { + model.notes[this.id] = { + id: this.id, + ...this.export(), + }; + } +} + +export default StickyNote; diff --git a/packages/dbml-core/src/model_structure/table.js b/packages/dbml-core/src/model_structure/table.ts similarity index 68% rename from packages/dbml-core/src/model_structure/table.js rename to packages/dbml-core/src/model_structure/table.ts index da98665aa..a71dcb663 100644 --- a/packages/dbml-core/src/model_structure/table.js +++ b/packages/dbml-core/src/model_structure/table.ts @@ -1,20 +1,70 @@ import { get, isNil } from 'lodash-es'; -import Element from './element'; +import Element, { RawNote, Token } from './element'; import Field from './field'; import Index from './indexes'; import { DEFAULT_SCHEMA_NAME } from './config'; import { shouldPrintSchema } from './utils'; import Check from './check'; +import Schema from './schema'; +import DbState from './dbState'; +import TableGroup from './tableGroup'; +import TablePartial from './tablePartial'; +import { NormalizedModel } from './database'; + +interface RawTable { + name: string; + alias: string; + note: RawNote; + fields: Field[]; + indexes: Index[]; + checks?: any[]; + schema: Schema; + token: Token; + headerColor: string; + partials: TablePartial[]; + noteToken?: Token | null; +} + +export interface NormalizedTable { + id: number; + name: string; + alias: string | null; + note: string | null; + headerColor: string; + fieldIds: number[]; + indexIds: number[]; + checkIds: number[]; + schemaId: number; + groupId: number | null; + partials: TablePartial[]; +} + +export interface NormalizedTableIdMap { + [id: number]: NormalizedTable; +} class Table extends Element { + name: string; + alias: string; + note: string; + noteToken: Token; + fields: Field[]; + indexes: Index[]; + checks: Check[]; + schema: Schema; + headerColor: string; + dbState: DbState; + group!: TableGroup; + partials: TablePartial[]; + constructor ({ - name, alias, note, fields = [], indexes = [], checks = [], schema = {}, token, headerColor, noteToken = null, partials = [], - } = {}) { + name, alias, note, fields = [], indexes = [], checks = [], schema = {} as Schema, token, headerColor, noteToken = null, partials = [], + }: RawTable) { super(token); this.name = name; this.alias = alias; - this.note = note ? get(note, 'value', note) : null; - this.noteToken = note ? get(note, 'token', noteToken) : null; + this.note = (note ? get(note, 'value', note) : null) as string; + this.noteToken = (note ? get(note, 'token', noteToken) : null) as Token; this.headerColor = headerColor; this.fields = []; this.indexes = []; @@ -33,28 +83,28 @@ class Table extends Element { this.processChecks(checks); } - generateId () { + generateId (): void { this.id = this.dbState.generateId('tableId'); } - checkFieldCount () { + checkFieldCount (): void { if (this.fields.length === 0) { this.error('Table must have at least one field'); } } - processFields (rawFields) { - rawFields.forEach((field) => { + processFields (rawFields: any): void { + rawFields.forEach((field: any) => { this.pushField(new Field({ ...field, table: this })); }); } - pushField (field) { + pushField (field: any): void { this.checkField(field); this.fields.push(field); } - checkField (field) { + checkField (field: any): void { if (this.fields.some((f) => f.name === field.name)) { field.error(`Field "${field.name}" existed in table ${shouldPrintSchema(this.schema) ? `"${this.schema.name}".` @@ -62,19 +112,19 @@ class Table extends Element { } } - processIndexes (rawIndexes) { - rawIndexes.forEach((index) => { + processIndexes (rawIndexes: any): void { + rawIndexes.forEach((index: any) => { this.pushIndex(new Index({ ...index, table: this })); }); } - pushIndex (index) { + pushIndex (index: any): void { this.checkIndex(index); this.indexes.push(index); } - checkIndex (index) { - index.columns.forEach((column) => { + checkIndex (index: any): void { + index.columns.forEach((column: any) => { if (column.type === 'column' && !(this.findField(column.value))) { index.error(`Column "${column.value}" do not exist in table ${shouldPrintSchema(this.schema) ? `"${this.schema.name}".` @@ -83,29 +133,29 @@ class Table extends Element { }); } - processChecks (checks) { + processChecks (checks: any[]): void { checks.forEach((check) => { this.pushCheck(new Check({ ...check, table: this })); }); } - pushCheck (check) { + pushCheck (check: any): void { this.checks.push(check); } - findField (fieldName) { + findField (fieldName: any): Field | undefined { return this.fields.find((f) => f.name === fieldName); } - checkSameId (table) { + checkSameId (table: any): boolean { return (this.schema.checkSameId(table.schemaName || DEFAULT_SCHEMA_NAME)) && (this.name === table.name || this.alias === table.name || this.name === table.alias - || (this.alias && this.alias === table.alias)); + || Boolean(this.alias && this.alias === table.alias)); } - processPartials () { + processPartials (): void { if (!this.partials?.length) return; /** * When encountering conflicting columns or settings with identical names, the following resolution order is applied: @@ -126,27 +176,27 @@ class Table extends Element { if (!isNil(this.headerColor)) existingSettingNames.add('headerColor'); // descending order, we'll inserted the partial fields from tail to head - const sortedPartials = this.partials.sort((a, b) => b.order - a.order); + const sortedPartials = this.partials.sort((a: any, b: any) => b.order - a.order); // insert placeholder into table.fields - sortedPartials.toReversed().forEach((partial) => { - this.fields.splice(partial.order, 0, 'dummy'); + [...sortedPartials].reverse().forEach((partial: any) => { + this.fields.splice(partial.order, 0, 'dummy' as any); }); - sortedPartials.forEach((partial) => { - const tablePartial = this.schema.database.findTablePartial(partial.name); - if (!tablePartial) this.error(`Table partial ${partial.name} not found`, partial.token); + sortedPartials.forEach((partial: any) => { + const tablePartial = this.schema.database.findTablePartial(partial.name)!; + if (!tablePartial) this.error(`Table partial ${partial.name} not found`); partial.id = tablePartial.id; if (tablePartial.fields) { // ignore fields that already exist in the table, or have been added by a later partial - const rawFields = tablePartial.fields.filter((f) => !existingFieldNames.has(f.name)); - const fields = rawFields.map((rawField) => { + const rawFields = tablePartial.fields.filter((f: any) => !existingFieldNames.has(f.name)); + const fields = rawFields.map((rawField: any) => { existingFieldNames.add(rawField.name); // convert inline_refs from injected fields to refs if (rawField.inline_refs) { - rawField.inline_refs.forEach((iref) => { + rawField.inline_refs.forEach((iref: any) => { const ref = { token: rawField.token, endpoints: [{ @@ -185,7 +235,7 @@ class Table extends Element { // merge settings if (!existingSettingNames.has('note') && !isNil(tablePartial.note)) { - this.noteToken = null; + this.noteToken = null as unknown as Token; this.note = tablePartial.note; existingSettingNames.add('note'); } @@ -195,11 +245,11 @@ class Table extends Element { } // merge indexes - tablePartial.indexes.forEach((index) => { + tablePartial.indexes.forEach((index: any) => { this.indexes.push(new Index({ ...index, table: this, injectedPartial: tablePartial })); }); - tablePartial.checks.forEach((check) => { + tablePartial.checks.forEach((check: any) => { this.checks.push(new Check({ ...check, name: check.name && `${this.name}.${check.name}`, // deduplicate check names when instantiated to tables @@ -210,21 +260,24 @@ class Table extends Element { }); } - export () { + export (): ReturnType & ReturnType { return { ...this.shallowExport(), ...this.exportChild(), }; } - exportChild () { + exportChild (): { + fields: ReturnType[]; + indexes: ReturnType[]; + } { return { fields: this.fields.map((f) => f.export()), indexes: this.indexes.map((i) => i.export()), }; } - exportChildIds () { + exportChildIds (): { fieldIds: number[]; indexIds: number[]; checkIds: number[] } { return { fieldIds: this.fields.map((f) => f.id), indexIds: this.indexes.map((i) => i.id), @@ -232,14 +285,20 @@ class Table extends Element { }; } - exportParentIds () { + exportParentIds (): { schemaId: number; groupId: number | null } { return { schemaId: this.schema.id, groupId: this.group ? this.group.id : null, }; } - shallowExport () { + shallowExport (): { + name: string; + alias: string; + note: string; + headerColor: string; + partials: TablePartial[]; + } { return { name: this.name, alias: this.alias, @@ -249,7 +308,7 @@ class Table extends Element { }; } - normalize (model) { + normalize (model: NormalizedModel): void { model.tables[this.id] = { id: this.id, ...this.shallowExport(), diff --git a/packages/dbml-core/src/model_structure/tableGroup.js b/packages/dbml-core/src/model_structure/tableGroup.ts similarity index 56% rename from packages/dbml-core/src/model_structure/tableGroup.js rename to packages/dbml-core/src/model_structure/tableGroup.ts index f366a745f..e610e640d 100644 --- a/packages/dbml-core/src/model_structure/tableGroup.js +++ b/packages/dbml-core/src/model_structure/tableGroup.ts @@ -1,30 +1,65 @@ import { get } from 'lodash-es'; -import Element from './element'; +import Element, { RawNote, Token } from './element'; +import Schema from './schema'; +import Table from './table'; +import DbState from './dbState'; +import { NormalizedModel } from './database'; import { shouldPrintSchema } from './utils'; +interface RawTableGroup { + name: string; + tables: Table[]; + schema: Schema; + token: Token; + note: RawNote; + color: string; + noteToken?: Token | null; +} + +export interface NormalizedTableGroup { + id: number; + name: string; + note: string | null; + color: string; + tableIds: number[]; + schemaId: number; +} + +export interface NormalizedTableGroupIdMap { + [_id: number]: NormalizedTableGroup; +} + class TableGroup extends Element { + name: string; + tables: Table[]; + schema: Schema; + dbState: DbState; + note: string; + noteToken: Token; + color: string; + constructor ({ - name, token, tables = [], schema = {}, note, color, noteToken = null, - }) { + name, token, tables = [], schema = {} as Schema, note, color, noteToken = null, + }: RawTableGroup) { super(token); this.name = name; this.tables = []; this.schema = schema; this.dbState = this.schema.dbState; - this.note = note ? get(note, 'value', note) : null; - this.noteToken = note ? get(note, 'token', noteToken) : null; + this.note = (note ? get(note, 'value', note) : null) as string; + this.noteToken = (note ? get(note, 'token', noteToken) : null) as Token; this.color = color; this.generateId(); this.processTables(tables); } - generateId () { + generateId (): void { this.id = this.dbState.generateId('tableGroupId'); } - processTables (rawTables) { - rawTables.forEach((rawTable) => { + processTables (rawTables: any): void { + rawTables.forEach((rawTable: any) => { const table = this.schema.database.findTable(rawTable.schemaName, rawTable.name); if (!table) { this.error(`Table ${rawTable.schemaName ? `"${rawTable.schemaName}".` : ''}${rawTable.name} don't exist`); @@ -33,13 +68,13 @@ class TableGroup extends Element { }); } - pushTable (table) { + pushTable (table: any): void { this.checkTable(table); this.tables.push(table); table.group = this; } - checkTable (table) { + checkTable (table: any): void { if (this.tables.some((t) => t.id === table.id)) { this.error(`Table ${shouldPrintSchema(table.schema) ? `"${table.schema.name}".` : ''}.${table.name} is already in the group`); } @@ -60,25 +95,25 @@ class TableGroup extends Element { }; } - exportChild () { + exportChild (): { tables: { tableName: string; schemaName: string }[] } { return { tables: this.tables.map((t) => ({ tableName: t.name, schemaName: t.schema.name })), }; } - exportChildIds () { + exportChildIds (): { tableIds: number[] } { return { tableIds: this.tables.map((t) => t.id), }; } - exportParentIds () { + exportParentIds (): { schemaId: number } { return { schemaId: this.schema.id, }; } - shallowExport () { + shallowExport (): { name: string; note: string; color: string } { return { name: this.name, note: this.note, @@ -86,7 +121,7 @@ class TableGroup extends Element { }; } - normalize (model) { + normalize (model: NormalizedModel): void { model.tableGroups[this.id] = { id: this.id, ...this.shallowExport(), diff --git a/packages/dbml-core/src/model_structure/tablePartial.js b/packages/dbml-core/src/model_structure/tablePartial.js deleted file mode 100644 index caa687bba..000000000 --- a/packages/dbml-core/src/model_structure/tablePartial.js +++ /dev/null @@ -1,48 +0,0 @@ -import { get } from 'lodash-es'; -import Element from './element'; - -class TablePartial extends Element { - constructor ({ - name, note, fields = [], indexes = [], checks = [], token, headerColor, noteToken = null, dbState, - } = {}) { - super(token); - this.name = name; - this.note = note ? get(note, 'value', note) : null; - this.noteToken = note ? get(note, 'token', noteToken) : null; - this.headerColor = headerColor; - this.fields = fields; - this.indexes = indexes; - this.checks = checks; - this.dbState = dbState; - this.generateId(); - } - - generateId () { - this.id = this.dbState.generateId('tablePartialId'); - } - - export () { - return { - ...this.shallowExport(), - }; - } - - shallowExport () { - return { - name: this.name, - note: this.note, - headerColor: this.headerColor, - fields: this.fields, - indexes: this.indexes, - }; - } - - normalize (model) { - model.tablePartials[this.id] = { - id: this.id, - ...this.shallowExport(), - }; - } -} - -export default TablePartial; diff --git a/packages/dbml-core/src/model_structure/tablePartial.ts b/packages/dbml-core/src/model_structure/tablePartial.ts new file mode 100644 index 000000000..ba39faddc --- /dev/null +++ b/packages/dbml-core/src/model_structure/tablePartial.ts @@ -0,0 +1,98 @@ +import { get } from 'lodash-es'; +import Element, { RawNote, Token } from './element'; +import Field from './field'; +import Index from './indexes'; +import Check from './check'; +import DbState from './dbState'; +import { NormalizedModel } from './database'; + +interface RawTablePartial { + name: string; + note: RawNote; + fields: Field[]; + indexes: Index[]; + checks?: any[]; + token: Token; + headerColor: string; + dbState: DbState; +} + +export interface NormalizedTablePartial { + id: number; + name: string; + note: string; + headerColor: string; + fieldIds: number[]; + indexIds: number[]; + checkIds: number[]; +} + +export interface NormalizedTablePartialIdMap { + [id: number]: NormalizedTablePartial; +} + +class TablePartial extends Element { + name: string; + note: string; + noteToken: Token; + fields: Field[]; + indexes: Index[]; + checks: Check[]; + headerColor: string; + dbState: DbState; + + constructor ({ + name, note, fields = [], indexes = [], checks = [], token, headerColor, noteToken = null, dbState, + }: RawTablePartial & { noteToken?: Token | null }) { + super(token); + this.name = name; + this.note = (note ? get(note, 'value', note) : null) as string; + this.noteToken = (note ? get(note, 'token', noteToken) : null) as Token; + this.headerColor = headerColor; + this.fields = fields as Field[]; + this.indexes = indexes as Index[]; + this.checks = checks as Check[]; + this.dbState = dbState; + this.generateId(); + } + + generateId (): void { + this.id = this.dbState.generateId('tablePartialId'); + } + + export (): ReturnType { + return { + ...this.shallowExport(), + }; + } + + shallowExport (): { + name: string; + note: string; + headerColor: string; + fields: Field[]; + indexes: Index[]; + } { + return { + name: this.name, + note: this.note, + headerColor: this.headerColor, + fields: this.fields, + indexes: this.indexes, + }; + } + + normalize (model: NormalizedModel): void { + model.tablePartials[this.id] = { + id: this.id, + name: this.name, + note: this.note, + headerColor: this.headerColor, + fieldIds: (this.fields as any[]).map((f: any) => f.id).filter(Boolean), + indexIds: (this.indexes as any[]).map((i: any) => i.id).filter(Boolean), + checkIds: (this.checks as any[]).map((c: any) => c.id).filter(Boolean), + }; + } +} + +export default TablePartial; diff --git a/packages/dbml-core/src/model_structure/utils.js b/packages/dbml-core/src/model_structure/utils.ts similarity index 67% rename from packages/dbml-core/src/model_structure/utils.js rename to packages/dbml-core/src/model_structure/utils.ts index 816194237..12b281e04 100644 --- a/packages/dbml-core/src/model_structure/utils.js +++ b/packages/dbml-core/src/model_structure/utils.ts @@ -1,9 +1,9 @@ import { DEFAULT_SCHEMA_NAME } from './config'; -export function shouldPrintSchema (schema) { +export function shouldPrintSchema (schema: any) { return schema.name !== DEFAULT_SCHEMA_NAME || (schema.name === DEFAULT_SCHEMA_NAME && schema.database.hasDefaultSchema); } -export function shouldPrintSchemaName (schemaName) { +export function shouldPrintSchemaName (schemaName: any) { return schemaName !== DEFAULT_SCHEMA_NAME; } diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/AST.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/AST.js deleted file mode 100644 index d1d3d19f8..000000000 --- a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/AST.js +++ /dev/null @@ -1,276 +0,0 @@ -export class Index { - /** - * @param {{ - * name: string, - * unique: boolean, - * pk: boolean, - * type: string, - * columns: {value: string, type: 'column' | 'string' | 'expression'}[], - * }} param0 - */ - constructor ({ - name, unique, pk, type, columns, - }) { - /** @type {string} */ - this.name = name; - - /** @type {string} */ - this.type = type; - - /** @type {boolean} */ - this.unique = unique; - - /** @type {boolean} */ - this.pk = pk; - - /** @type {{value: string, type: 'column' | 'string' | 'expression'}[]} */ - this.columns = columns; - } - - toJSON () { - return { - name: this.name, - type: this.type, - unique: this.unique, - pk: this.pk, - columns: this.columns, - }; - } -} - -export class Field { - /** @type {boolean} */ - // fk; - - /** - * @param {{ - * name: string, - * type: {type_name: string, schemaName: string}, - * not_null: boolean, - * increment: boolean, - * dbdefault: {value: string, type: 'string' | 'number' | 'boolean' | 'expression'}, - * unique: boolean, - * pk: boolean, - * note: {value: string}, - * checks: {expression: string, name?: string}[] - * }} param0 - */ - constructor ({ - name, type, not_null, increment, dbdefault, unique, pk, note, checks, - }) { - /** @type {string} */ - this.name = name; - - /** @type {{type_name: string, schemaName: string}} */ - this.type = type; - - /** @type {boolean} */ - this.not_null = not_null; - - /** @type {boolean} */ - this.increment = increment; - - /** @type {{value: string, type: 'string' | 'number' | 'boolean' | 'expression'}} */ - this.dbdefault = dbdefault; - - /** @type {boolean} */ - this.unique = unique; - - /** @type {boolean} */ - this.pk = pk; - - /** @type {{value: string}} */ - this.note = note; - - /** @type {{expression: string, name?: string}[]} */ - this.checks = checks; - } - - toJSON () { - return { - name: this.name, - type: this.type, - not_null: this.not_null, - increment: this.increment, - dbdefault: this.dbdefault, - unique: this.unique, - pk: this.pk, - note: this.note, - checks: this.checks, - }; - } -} - -export class Table { - /** - * @param {{ - * name: string, - * schemaName: string, - * fields: Field[], - * indexes: Index[], - * note: {value: string}, - * checks: {expression: string, name?: string}[] - * }} param0 - */ - constructor ({ - name, schemaName, fields, indexes, note, checks, - }) { - /** @type {string} */ - this.name = name; - - /** @type {string} */ - this.schemaName = schemaName; - - /** @type {Field[]} */ - this.fields = fields || []; - - /** @type {Index[]} */ - this.indexes = indexes || []; - - /** @type {{value: string}} */ - this.note = note; - - /** @type {{expression: string, name?: string}[]} */ - this.checks = checks || []; - } - - toJSON () { - return { - name: this.name, - schemaName: this.schemaName, - fields: this.fields?.map((f) => f.toJSON()), - indexes: this.indexes?.map((i) => i.toJSON()), - note: this.note, - checks: this.checks, - }; - } -} - -export class Endpoint { - /** - * @param {{ - * tableName: string, - * schemaName: string, - * fieldNames: string[], - * relation: '*' | '1' - * }} param0 - */ - constructor ({ - tableName, schemaName, fieldNames, relation, - }) { - /** @type {string} */ - this.tableName = tableName; - - /** @type {string} */ - this.schemaName = schemaName; - - /** @type {string[]} */ - this.fieldNames = fieldNames; - - /** @type {'*' | '1'} */ - this.relation = relation; - } - - toJSON () { - return { - tableName: this.tableName, - schemaName: this.schemaName, - fieldNames: this.fieldNames, - relation: this.relation, - }; - } -} - -export class Ref { - /** - * @param {{ - * name: string, - * endpoints: Endpoint[], - * onDelete: string, - * onUpdate: string - * }} param0 - */ - constructor ({ - name, endpoints, onDelete, onUpdate, - }) { - /** @type {string} */ - this.name = name; - - /** @type {Endpoint[]} */ - this.endpoints = endpoints || []; - - /** @type {string} */ - this.onDelete = onDelete; - - /** @type {string} */ - this.onUpdate = onUpdate; - } - - toJSON () { - return { - name: this.name, - onDelete: this.onDelete, - onUpdate: this.onUpdate, - endpoints: this.endpoints.map((e) => e.toJSON()), - }; - } -} - -export class Enum { - constructor ({ name, schemaName, values }) { - /** @type {string} */ - this.name = name; - - /** @type {string} */ - this.schemaName = schemaName; - - /** @type {{name: string}[]} */ - this.values = values; - } - - toJSON () { - return { - name: this.name, - schemaName: this.schemaName, - values: this.values, - }; - } -} - -export class TableRecord { - /** - * @param {{ - * tableName: string, - * columns: string[], - * values: { - * value: any, - * type: string, - * }[] - * schemaName?: string, - * }} param0 - */ - constructor ({ - tableName, columns, values, schemaName = undefined, - }) { - /** @type {string} */ - this.tableName = tableName; - - /** @type {string | undefined} */ - this.schemaName = schemaName; - - /** @type {string[]} */ - this.columns = columns; - - /** @type {{value: any, type: string}[]} */ - this.values = values; - } - - toJSON () { - return { - tableName: this.tableName, - schemaName: this.schemaName, - columns: this.columns, - values: this.values, - }; - } -} diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/AST.ts b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/AST.ts new file mode 100644 index 000000000..c07d3cea6 --- /dev/null +++ b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/AST.ts @@ -0,0 +1,248 @@ +export class Index { + name: string; + type: string; + unique: boolean; + pk: boolean; + columns: { value: string; type: 'column' | 'string' | 'expression' }[]; + + constructor ({ + name, unique, pk, type, columns, + }: { + name: string; + unique: boolean; + pk: boolean; + type: string; + columns: { value: string; type: 'column' | 'string' | 'expression' }[]; + }) { + this.name = name; + this.type = type; + this.unique = unique; + this.pk = pk; + this.columns = columns; + } + + toJSON (): { name: string; type: string; unique: boolean; pk: boolean; columns: { value: string; type: 'column' | 'string' | 'expression' }[] } { + return { + name: this.name, + type: this.type, + unique: this.unique, + pk: this.pk, + columns: this.columns, + }; + } +} + +export class Field { + name: string; + type: { type_name: string; schemaName: string }; + not_null: boolean; + increment: boolean; + dbdefault: { value: string; type: 'string' | 'number' | 'boolean' | 'expression' }; + unique: boolean; + pk: boolean; + note: { value: string }; + checks: { expression: string; name?: string }[]; + + constructor ({ + name, type, not_null, increment, dbdefault, unique, pk, note, checks, + }: { + name: string; + type: { type_name: string; schemaName: string }; + not_null: boolean; + increment: boolean; + dbdefault: { value: string; type: 'string' | 'number' | 'boolean' | 'expression' }; + unique: boolean; + pk: boolean; + note: { value: string }; + checks: { expression: string; name?: string }[]; + }) { + this.name = name; + this.type = type; + this.not_null = not_null; + this.increment = increment; + this.dbdefault = dbdefault; + this.unique = unique; + this.pk = pk; + this.note = note; + this.checks = checks; + } + + toJSON (): { + name: string; + type: { type_name: string; schemaName: string }; + not_null: boolean; + increment: boolean; + dbdefault: { value: string; type: 'string' | 'number' | 'boolean' | 'expression' }; + unique: boolean; + pk: boolean; + note: { value: string }; + checks: { expression: string; name?: string }[]; + } { + return { + name: this.name, + type: this.type, + not_null: this.not_null, + increment: this.increment, + dbdefault: this.dbdefault, + unique: this.unique, + pk: this.pk, + note: this.note, + checks: this.checks, + }; + } +} + +export class Table { + name: string; + schemaName: string; + fields: Field[]; + indexes: Index[]; + note: { value: string }; + checks: { expression: string; name?: string }[]; + + constructor ({ + name, schemaName, fields, indexes, note, checks, + }: { + name: string; + schemaName: string; + fields: Field[]; + indexes: Index[]; + note: { value: string }; + checks: { expression: string; name?: string }[]; + }) { + this.name = name; + this.schemaName = schemaName; + this.fields = fields || []; + this.indexes = indexes || []; + this.note = note; + this.checks = checks || []; + } + + toJSON (): { + name: string; + schemaName: string; + fields: ReturnType[]; + indexes: ReturnType[]; + note: { value: string }; + checks: { expression: string; name?: string }[]; + } { + return { + name: this.name, + schemaName: this.schemaName, + fields: this.fields?.map((f) => f.toJSON()), + indexes: this.indexes?.map((i) => i.toJSON()), + note: this.note, + checks: this.checks, + }; + } +} + +export class Endpoint { + tableName: string; + schemaName: string; + fieldNames: string[]; + relation: '*' | '1'; + + constructor ({ + tableName, schemaName, fieldNames, relation, + }: { + tableName: string; + schemaName: string; + fieldNames: string[]; + relation: '*' | '1'; + }) { + this.tableName = tableName; + this.schemaName = schemaName; + this.fieldNames = fieldNames; + this.relation = relation; + } + + toJSON (): { tableName: string; schemaName: string; fieldNames: string[]; relation: '*' | '1' } { + return { + tableName: this.tableName, + schemaName: this.schemaName, + fieldNames: this.fieldNames, + relation: this.relation, + }; + } +} + +export class Ref { + name: string; + endpoints: Endpoint[]; + onDelete: string; + onUpdate: string; + + constructor ({ + name, endpoints, onDelete, onUpdate, + }: { + name: string; + endpoints: Endpoint[]; + onDelete: string; + onUpdate: string; + }) { + this.name = name; + this.endpoints = endpoints || []; + this.onDelete = onDelete; + this.onUpdate = onUpdate; + } + + toJSON (): { name: string; onDelete: string; onUpdate: string; endpoints: ReturnType[] } { + return { + name: this.name, + onDelete: this.onDelete, + onUpdate: this.onUpdate, + endpoints: this.endpoints.map((e) => e.toJSON()), + }; + } +} + +export class Enum { + name: string; + schemaName: string; + values: { name: string }[]; + + constructor ({ name, schemaName, values }: { name: string; schemaName: string; values: { name: string }[] }) { + this.name = name; + this.schemaName = schemaName; + this.values = values; + } + + toJSON (): { name: string; schemaName: string; values: { name: string }[] } { + return { + name: this.name, + schemaName: this.schemaName, + values: this.values, + }; + } +} + +export class TableRecord { + tableName: string; + schemaName: string | undefined; + columns: string[]; + values: { value: any; type: string }[]; + + constructor ({ + tableName, columns, values, schemaName = undefined, + }: { + tableName: string; + columns: string[]; + values: { value: any; type: string }[]; + schemaName?: string; + }) { + this.tableName = tableName; + this.schemaName = schemaName; + this.columns = columns; + this.values = values; + } + + toJSON (): { tableName: string; schemaName: string | undefined; columns: string[]; values: { value: any; type: string }[] } { + return { + tableName: this.tableName, + schemaName: this.schemaName, + columns: this.columns, + values: this.values, + }; + } +} diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/ParserErrorListener.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/ParserErrorListener.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/ASTGeneration/ParserErrorListener.js rename to packages/dbml-core/src/parse/ANTLR/ASTGeneration/ParserErrorListener.ts diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/SyntaxError.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/SyntaxError.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/ASTGeneration/SyntaxError.js rename to packages/dbml-core/src/parse/ANTLR/ASTGeneration/SyntaxError.ts diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/constants.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/constants.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/ASTGeneration/constants.js rename to packages/dbml-core/src/parse/ANTLR/ASTGeneration/constants.ts diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/helpers.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/helpers.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/ASTGeneration/helpers.js rename to packages/dbml-core/src/parse/ANTLR/ASTGeneration/helpers.ts diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/index.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/index.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/ASTGeneration/index.js rename to packages/dbml-core/src/parse/ANTLR/ASTGeneration/index.ts diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js rename to packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.ts diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mysql/MySQLASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mysql/MySQLASTGen.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/ASTGeneration/mysql/MySQLASTGen.js rename to packages/dbml-core/src/parse/ANTLR/ASTGeneration/mysql/MySQLASTGen.ts diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/oraclesql/OracleSQLASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/oraclesql/OracleSQLASTGen.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/ASTGeneration/oraclesql/OracleSQLASTGen.js rename to packages/dbml-core/src/parse/ANTLR/ASTGeneration/oraclesql/OracleSQLASTGen.ts diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/postgres/PostgresASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/postgres/PostgresASTGen.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/ASTGeneration/postgres/PostgresASTGen.js rename to packages/dbml-core/src/parse/ANTLR/ASTGeneration/postgres/PostgresASTGen.ts diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/snowflake/SnowflakeASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/snowflake/SnowflakeASTGen.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/ASTGeneration/snowflake/SnowflakeASTGen.js rename to packages/dbml-core/src/parse/ANTLR/ASTGeneration/snowflake/SnowflakeASTGen.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlLexer.js b/packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlLexer.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlLexer.js rename to packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlLexer.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlParser.js b/packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlParser.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlParser.js rename to packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlParser.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlParserVisitor.js b/packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlParserVisitor.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlParserVisitor.js rename to packages/dbml-core/src/parse/ANTLR/parsers/mssql/TSqlParserVisitor.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlLexer.js b/packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlLexer.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlLexer.js rename to packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlLexer.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlLexerBase.js b/packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlLexerBase.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlLexerBase.js rename to packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlLexerBase.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlParser.js b/packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlParser.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlParser.js rename to packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlParser.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlParserVisitor.js b/packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlParserVisitor.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlParserVisitor.js rename to packages/dbml-core/src/parse/ANTLR/parsers/mysql/MySqlParserVisitor.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlLexer.js b/packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlLexer.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlLexer.js rename to packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlLexer.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlLexerBase.js b/packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlLexerBase.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlLexerBase.js rename to packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlLexerBase.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParser.js b/packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParser.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParser.js rename to packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParser.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParserBase.js b/packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParserBase.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParserBase.js rename to packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParserBase.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParserVisitor.js b/packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParserVisitor.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParserVisitor.js rename to packages/dbml-core/src/parse/ANTLR/parsers/oraclesql/OracleSqlParserVisitor.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLLexer.js b/packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLLexer.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLLexer.js rename to packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLLexer.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLLexerBase.js b/packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLLexerBase.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLLexerBase.js rename to packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLLexerBase.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParser.js b/packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParser.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParser.js rename to packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParser.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParserBase.js b/packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParserBase.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParserBase.js rename to packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParserBase.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParserVisitor.js b/packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParserVisitor.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParserVisitor.js rename to packages/dbml-core/src/parse/ANTLR/parsers/postgresql/PostgreSQLParserVisitor.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeLexer.js b/packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeLexer.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeLexer.js rename to packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeLexer.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeParser.js b/packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeParser.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeParser.js rename to packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeParser.ts diff --git a/packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeParserVisitor.js b/packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeParserVisitor.ts similarity index 100% rename from packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeParserVisitor.js rename to packages/dbml-core/src/parse/ANTLR/parsers/snowflake/SnowflakeParserVisitor.ts diff --git a/packages/dbml-core/src/parse/Parser.js b/packages/dbml-core/src/parse/Parser.ts similarity index 62% rename from packages/dbml-core/src/parse/Parser.js rename to packages/dbml-core/src/parse/Parser.ts index 47dd3cfd7..f08a6c6ed 100644 --- a/packages/dbml-core/src/parse/Parser.js +++ b/packages/dbml-core/src/parse/Parser.ts @@ -1,5 +1,6 @@ import { Compiler } from '@dbml/parse'; import Database from '../model_structure/database'; +import { RawDatabase } from '../model_structure/database'; import { parse } from './ANTLR/ASTGeneration'; import { CompilerError } from './error'; import mysqlParser from './deprecated/mysqlParser.cjs'; @@ -8,39 +9,53 @@ import dbmlParser from './deprecated/dbmlParser.cjs'; import schemarbParser from './deprecated/schemarbParser.cjs'; import mssqlParser from './deprecated/mssqlParser.cjs'; +export type ParseFormat = 'json' + | 'mysql' | 'mysqlLegacy' + | 'postgres' | 'postgresLegacy' + | 'dbml' | 'dbmlv2' + | 'mssql' | 'mssqlLegacy' + | 'schemarb' + | 'snowflake' + | 'oracle'; + class Parser { - constructor (dbmlCompiler) { + public DBMLCompiler: Compiler; + + constructor (dbmlCompiler?: Compiler) { this.DBMLCompiler = dbmlCompiler || new Compiler(); } - static parseJSONToDatabase (rawDatabase) { + static parseJSONToDatabase (rawDatabase: RawDatabase): Database { const database = new Database(rawDatabase); return database; } - static parseMySQLToJSONv2 (str) { + static parseMySQLToJSONv2 (str: string): RawDatabase { return parse(str, 'mysql'); } /** * @deprecated Use the `parseMySQLToJSONv2` method instead */ - static parseMySQLToJSON (str) { - return mysqlParser.parse(str); + static parseMySQLToJSON (str: string): RawDatabase { + return mysqlParser.parse(str) as RawDatabase; } - static parsePostgresToJSONv2 (str) { + static parsePostgresToJSONv2 (str: string): RawDatabase { return parse(str, 'postgres'); } /** * @deprecated Use the `parsePostgresToJSONv2` method instead */ - static parsePostgresToJSON (str) { - return postgresParser.parse(str); + static parsePostgresToJSON (str: string): RawDatabase { + return postgresParser.parse(str) as RawDatabase; } - static parseDBMLToJSONv2 (str, dbmlCompiler) { + static parseDBMLToJSONv2 ( + str: string, + dbmlCompiler?: Compiler, + ): RawDatabase { const compiler = dbmlCompiler || new Compiler(); compiler.setSource(str); @@ -62,46 +77,71 @@ class Parser { if (diags.length > 0) throw CompilerError.create(diags); - return compiler.parse.rawDb(); + return compiler.parse.rawDb() as unknown as RawDatabase; } /** * @deprecated Use the `parseDBMLToJSONv2` method instead */ - static parseDBMLToJSON (str) { - return dbmlParser.parse(str); + static parseDBMLToJSON (str: string): RawDatabase { + return dbmlParser.parse(str) as unknown as RawDatabase; } - static parseSchemaRbToJSON (str) { - return schemarbParser.parse(str); + static parseSchemaRbToJSON (str: string): RawDatabase { + return schemarbParser.parse(str) as RawDatabase; } /** * @deprecated Use the `parseMSSQLToJSONv2` method instead */ - static parseMSSQLToJSON (str) { - return mssqlParser.parseWithPegError(str); + static parseMSSQLToJSON (str: string): RawDatabase { + return (mssqlParser as any).parseWithPegError(str) as RawDatabase; } - static parseMSSQLToJSONv2 (str) { + static parseMSSQLToJSONv2 (str: string): RawDatabase { return parse(str, 'mssql'); } - static parseSnowflakeToJSON (str) { + static parseSnowflakeToJSON (str: string): RawDatabase { return parse(str, 'snowflake'); } - static parseOracleToJSON (str) { + static parseOracleToJSON (str: string): RawDatabase { return parse(str, 'oracle'); } - static parse (str, format) { + /** + * Should use parse() instance method instead of this static method whenever possible + */ + static parse ( + str: string, + format: ParseFormat, + ): Database; + static parse ( + str: RawDatabase, + format: 'json', + ): Database; + static parse ( + str: any, + format: ParseFormat, + ): Database { return new Parser().parse(str, format); } - parse (str, format) { + parse ( + str: string, + format: ParseFormat, + ): Database; + parse ( + str: RawDatabase, + format: 'json', + ): Database; + parse ( + str: any, + format: ParseFormat, + ): Database { try { - let rawDatabase = {}; + let rawDatabase: any = {}; switch (format) { case 'mysql': rawDatabase = Parser.parseMySQLToJSONv2(str); diff --git a/packages/dbml-core/src/parse/databaseGenerator.js b/packages/dbml-core/src/parse/databaseGenerator.ts similarity index 71% rename from packages/dbml-core/src/parse/databaseGenerator.js rename to packages/dbml-core/src/parse/databaseGenerator.ts index 7162ec680..11fee90a6 100644 --- a/packages/dbml-core/src/parse/databaseGenerator.js +++ b/packages/dbml-core/src/parse/databaseGenerator.ts @@ -8,16 +8,16 @@ import { Enum, } from './ANTLR/ASTGeneration/AST'; -const parseJSONToDatabase = (rawDatabase) => { - return new Database(rawDatabase); +const parseJSONToDatabase = (rawDatabase: object): Database => { + return new Database(rawDatabase as any); }; -const createRefs = (rawRefs) => { +const createRefs = (rawRefs: any[]): ReturnType[] => { return rawRefs.map((rawRef) => { const { name, endpoints, onDelete, onUpdate, } = rawRef; - const eps = endpoints.map((ep) => new Endpoint(ep)); + const eps = endpoints.map((ep: any) => new Endpoint(ep)); return new Ref({ name, endpoints: eps, @@ -27,7 +27,7 @@ const createRefs = (rawRefs) => { }); }; -const createEnums = (rawEnums) => { +const createEnums = (rawEnums: any[]): Enum[] => { return rawEnums.map((rawEnum) => { const { name, schemaName, values } = rawEnum; return new Enum({ @@ -38,7 +38,7 @@ const createEnums = (rawEnums) => { }); }; -const createFields = (rawFields, fieldsConstraints) => { +const createFields = (rawFields: any[], fieldsConstraints: Record): Field[] => { return rawFields.map((field) => { const constraints = fieldsConstraints[field.name] || {}; const f = new Field({ @@ -56,7 +56,7 @@ const createFields = (rawFields, fieldsConstraints) => { }); }; -const createIndexes = (rawIndexes) => { +const createIndexes = (rawIndexes: any[]): Index[] => { return rawIndexes.map((rawIndex) => { const { name, unique, pk, type, columns, @@ -72,7 +72,13 @@ const createIndexes = (rawIndexes) => { }); }; -const createTables = (rawTables, rawFields, rawIndexes, rawTableChecks, tableConstraints) => { +const createTables = ( + rawTables: any[], + rawFields: Record, + rawIndexes: Record, + rawTableChecks: Record, + tableConstraints: Record, +): Table[] => { return rawTables.map((rawTable) => { const { name, schemaName, note } = rawTable; const key = schemaName ? `${schemaName}.${name}` : `${name}`; @@ -91,7 +97,15 @@ const createTables = (rawTables, rawFields, rawIndexes, rawTableChecks, tableCon }); }; -const generateDatabase = (schemaJson) => { +const generateDatabase = (schemaJson: { + tables: any[]; + fields: Record; + indexes: Record; + refs: any[]; + enums: any[]; + tableConstraints: Record; + checks: Record; +}): Database => { const { tables: rawTables, fields: rawFields, @@ -118,7 +132,7 @@ const generateDatabase = (schemaJson) => { }; return parseJSONToDatabase(rawDatabase); } catch (err) { - throw new Error(err); + throw new Error(err as string); } }; diff --git a/packages/dbml-core/src/parse/deprecated/mssql/base_parsers.js b/packages/dbml-core/src/parse/deprecated/mssql/base_parsers.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/base_parsers.js rename to packages/dbml-core/src/parse/deprecated/mssql/base_parsers.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/column_definition/actions.js b/packages/dbml-core/src/parse/deprecated/mssql/column_definition/actions.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/column_definition/actions.js rename to packages/dbml-core/src/parse/deprecated/mssql/column_definition/actions.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/column_definition/index.js b/packages/dbml-core/src/parse/deprecated/mssql/column_definition/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/column_definition/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/column_definition/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/constraint_definition/actions.js b/packages/dbml-core/src/parse/deprecated/mssql/constraint_definition/actions.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/constraint_definition/actions.js rename to packages/dbml-core/src/parse/deprecated/mssql/constraint_definition/actions.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/constraint_definition/index.js b/packages/dbml-core/src/parse/deprecated/mssql/constraint_definition/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/constraint_definition/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/constraint_definition/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/expression.js b/packages/dbml-core/src/parse/deprecated/mssql/expression.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/expression.js rename to packages/dbml-core/src/parse/deprecated/mssql/expression.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/fk_definition/actions.js b/packages/dbml-core/src/parse/deprecated/mssql/fk_definition/actions.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/fk_definition/actions.js rename to packages/dbml-core/src/parse/deprecated/mssql/fk_definition/actions.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/fk_definition/index.js b/packages/dbml-core/src/parse/deprecated/mssql/fk_definition/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/fk_definition/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/fk_definition/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/index.js b/packages/dbml-core/src/parse/deprecated/mssql/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/index_definition/actions.js b/packages/dbml-core/src/parse/deprecated/mssql/index_definition/actions.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/index_definition/actions.js rename to packages/dbml-core/src/parse/deprecated/mssql/index_definition/actions.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/index_definition/index.js b/packages/dbml-core/src/parse/deprecated/mssql/index_definition/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/index_definition/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/index_definition/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/keyword_parsers.js b/packages/dbml-core/src/parse/deprecated/mssql/keyword_parsers.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/keyword_parsers.js rename to packages/dbml-core/src/parse/deprecated/mssql/keyword_parsers.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/keyword_utils.js b/packages/dbml-core/src/parse/deprecated/mssql/keyword_utils.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/keyword_utils.js rename to packages/dbml-core/src/parse/deprecated/mssql/keyword_utils.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/actions.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/actions.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/actions.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/actions.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/index.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/actions.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/actions.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/actions.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/actions.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/add/actions.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/add/actions.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/add/actions.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/add/actions.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/add/index.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/add/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/add/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/add/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/index.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/alter_table/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/comments/actions.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/comments/actions.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/comments/actions.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/comments/actions.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/comments/index.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/comments/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/comments/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/comments/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_index/actions.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_index/actions.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_index/actions.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_index/actions.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_index/index.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_index/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_index/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_index/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_table/actions.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_table/actions.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_table/actions.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_table/actions.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_table/index.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_table/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_table/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/create_table/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/index.js b/packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/index.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/index.js rename to packages/dbml-core/src/parse/deprecated/mssql/statements/statement_types/index.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/utils.js b/packages/dbml-core/src/parse/deprecated/mssql/utils.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/utils.js rename to packages/dbml-core/src/parse/deprecated/mssql/utils.ts diff --git a/packages/dbml-core/src/parse/deprecated/mssql/whitespaces.js b/packages/dbml-core/src/parse/deprecated/mssql/whitespaces.ts similarity index 100% rename from packages/dbml-core/src/parse/deprecated/mssql/whitespaces.js rename to packages/dbml-core/src/parse/deprecated/mssql/whitespaces.ts diff --git a/packages/dbml-core/src/parse/error.js b/packages/dbml-core/src/parse/error.js deleted file mode 100644 index 65116f47a..000000000 --- a/packages/dbml-core/src/parse/error.js +++ /dev/null @@ -1,22 +0,0 @@ -export class CompilerError { - /** - * @param {import("../../types").CompilerDiagnostic[]} diags - */ - constructor (diags) { - this.diags = diags; - } - - static create (nestedDiags) { - return new CompilerError(flattenDiag(nestedDiags)); - } - - map (callback) { - return CompilerError.create(this.diags.map(callback)); - } -} - -function flattenDiag (diag) { - if (Array.isArray(diag)) return diag.flatMap(flattenDiag); - if (diag instanceof CompilerError) return diag.diags; - return [diag]; -} diff --git a/packages/dbml-core/src/parse/error.ts b/packages/dbml-core/src/parse/error.ts new file mode 100644 index 000000000..cfb927cb0 --- /dev/null +++ b/packages/dbml-core/src/parse/error.ts @@ -0,0 +1,50 @@ +export type WarningLevel = + | 'error' + | 'warning' + | 'info'; + +export type ErrorCode = number; + +export interface EditorPosition { + line: number; + column: number; +} + +export interface CompilerDiagnostic { + readonly message: Readonly; + readonly filepath?: Readonly; + readonly stack?: Readonly; + readonly location: { + start: Readonly; + // in monaco, if an end position is not specified + // it consumes the word containing the start position + end?: Readonly; + }; + readonly type?: Readonly; + readonly code?: Readonly; +} + +export class CompilerError { + diags: CompilerDiagnostic[]; + + /** + * @param {CompilerDiagnostic[]} diags + */ + constructor (diags: CompilerDiagnostic[]) { + this.diags = diags; + } + + static create (nestedDiags: any): CompilerError { + return new CompilerError(flattenDiag(nestedDiags)); + } + + map (callback: (diag: CompilerDiagnostic) => CompilerDiagnostic): CompilerError { + return CompilerError.create(this.diags.map(callback)); + } +} + +function flattenDiag (diag: any): CompilerDiagnostic[] { + if (Array.isArray(diag)) return diag.flatMap(flattenDiag); + if (diag instanceof CompilerError) return diag.diags; + return [diag]; +} diff --git a/packages/dbml-core/src/transform/index.js b/packages/dbml-core/src/transform/index.ts similarity index 100% rename from packages/dbml-core/src/transform/index.js rename to packages/dbml-core/src/transform/index.ts diff --git a/packages/dbml-core/types/export/ModelExporter.d.ts b/packages/dbml-core/types/export/ModelExporter.d.ts index 3945f9eba..32d236988 100644 --- a/packages/dbml-core/types/export/ModelExporter.d.ts +++ b/packages/dbml-core/types/export/ModelExporter.d.ts @@ -1,7 +1,15 @@ import Database, { NormalizedModel } from '../model_structure/database'; export declare type ExportFormatOption = 'dbml' | 'mysql' | 'postgres' | 'json' | 'mssql' | 'oracle'; +export interface ExportFlags { + isNormalized?: boolean; + includeRecords?: boolean; +} declare class ModelExporter { - static export(model: Database | NormalizedModel, format: ExportFormatOption, isNormalized?: boolean): string; + /** + * @deprecated Passing a boolean as the third argument is deprecated. Use `{ isNormalized: boolean }` instead. + */ + static export(model: Database | NormalizedModel, format: ExportFormatOption, isNormalized: boolean): string; + static export(model: Database | NormalizedModel, format: ExportFormatOption, flags?: ExportFlags): string; } export default ModelExporter; diff --git a/packages/dbml-core/types/export/index.d.ts b/packages/dbml-core/types/export/index.d.ts index cd4843502..7fcceb287 100644 --- a/packages/dbml-core/types/export/index.d.ts +++ b/packages/dbml-core/types/export/index.d.ts @@ -1,7 +1,7 @@ -import { ExportFormatOption } from './ModelExporter'; +import { ExportFormatOption, ExportFlags } from './ModelExporter'; import { RecordValueType } from '../model_structure/database'; -declare function _export(str: string, format: ExportFormatOption): string; +declare function _export(str: string, format: ExportFormatOption, flags?: ExportFlags): string; declare const _default: { export: typeof _export; };