linkgress-orm 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +196 -0
  3. package/dist/database/database-client.interface.d.ts +45 -0
  4. package/dist/database/database-client.interface.d.ts.map +1 -0
  5. package/dist/database/database-client.interface.js +20 -0
  6. package/dist/database/database-client.interface.js.map +1 -0
  7. package/dist/database/index.d.ts +5 -0
  8. package/dist/database/index.d.ts.map +1 -0
  9. package/dist/database/index.js +10 -0
  10. package/dist/database/index.js.map +1 -0
  11. package/dist/database/pg-client.d.ts +30 -0
  12. package/dist/database/pg-client.d.ts.map +1 -0
  13. package/dist/database/pg-client.js +76 -0
  14. package/dist/database/pg-client.js.map +1 -0
  15. package/dist/database/postgres-client.d.ts +44 -0
  16. package/dist/database/postgres-client.d.ts.map +1 -0
  17. package/dist/database/postgres-client.js +111 -0
  18. package/dist/database/postgres-client.js.map +1 -0
  19. package/dist/database/types.d.ts +200 -0
  20. package/dist/database/types.d.ts.map +1 -0
  21. package/dist/database/types.js +8 -0
  22. package/dist/database/types.js.map +1 -0
  23. package/dist/entity/base-entity.d.ts +21 -0
  24. package/dist/entity/base-entity.d.ts.map +1 -0
  25. package/dist/entity/base-entity.js +27 -0
  26. package/dist/entity/base-entity.js.map +1 -0
  27. package/dist/entity/db-column.d.ts +61 -0
  28. package/dist/entity/db-column.d.ts.map +1 -0
  29. package/dist/entity/db-column.js +35 -0
  30. package/dist/entity/db-column.js.map +1 -0
  31. package/dist/entity/db-context.d.ts +665 -0
  32. package/dist/entity/db-context.d.ts.map +1 -0
  33. package/dist/entity/db-context.js +1463 -0
  34. package/dist/entity/db-context.js.map +1 -0
  35. package/dist/entity/entity-base.d.ts +76 -0
  36. package/dist/entity/entity-base.d.ts.map +1 -0
  37. package/dist/entity/entity-base.js +42 -0
  38. package/dist/entity/entity-base.js.map +1 -0
  39. package/dist/entity/entity-builder.d.ts +171 -0
  40. package/dist/entity/entity-builder.d.ts.map +1 -0
  41. package/dist/entity/entity-builder.js +376 -0
  42. package/dist/entity/entity-builder.js.map +1 -0
  43. package/dist/entity/model-config.d.ts +18 -0
  44. package/dist/entity/model-config.d.ts.map +1 -0
  45. package/dist/entity/model-config.js +157 -0
  46. package/dist/entity/model-config.js.map +1 -0
  47. package/dist/index.d.ts +27 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +142 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/migration/db-schema-manager.d.ts +228 -0
  52. package/dist/migration/db-schema-manager.d.ts.map +1 -0
  53. package/dist/migration/db-schema-manager.js +1055 -0
  54. package/dist/migration/db-schema-manager.js.map +1 -0
  55. package/dist/migration/enum-migrator.d.ts +29 -0
  56. package/dist/migration/enum-migrator.d.ts.map +1 -0
  57. package/dist/migration/enum-migrator.js +137 -0
  58. package/dist/migration/enum-migrator.js.map +1 -0
  59. package/dist/query/collection-strategy.factory.d.ts +16 -0
  60. package/dist/query/collection-strategy.factory.d.ts.map +1 -0
  61. package/dist/query/collection-strategy.factory.js +37 -0
  62. package/dist/query/collection-strategy.factory.js.map +1 -0
  63. package/dist/query/collection-strategy.interface.d.ts +146 -0
  64. package/dist/query/collection-strategy.interface.d.ts.map +1 -0
  65. package/dist/query/collection-strategy.interface.js +3 -0
  66. package/dist/query/collection-strategy.interface.js.map +1 -0
  67. package/dist/query/conditions.d.ts +222 -0
  68. package/dist/query/conditions.d.ts.map +1 -0
  69. package/dist/query/conditions.js +446 -0
  70. package/dist/query/conditions.js.map +1 -0
  71. package/dist/query/cte-builder.d.ts +95 -0
  72. package/dist/query/cte-builder.d.ts.map +1 -0
  73. package/dist/query/cte-builder.js +172 -0
  74. package/dist/query/cte-builder.js.map +1 -0
  75. package/dist/query/grouped-query.d.ts +186 -0
  76. package/dist/query/grouped-query.d.ts.map +1 -0
  77. package/dist/query/grouped-query.js +588 -0
  78. package/dist/query/grouped-query.js.map +1 -0
  79. package/dist/query/join-builder.d.ts +106 -0
  80. package/dist/query/join-builder.d.ts.map +1 -0
  81. package/dist/query/join-builder.js +275 -0
  82. package/dist/query/join-builder.js.map +1 -0
  83. package/dist/query/query-builder.d.ts +543 -0
  84. package/dist/query/query-builder.d.ts.map +1 -0
  85. package/dist/query/query-builder.js +2649 -0
  86. package/dist/query/query-builder.js.map +1 -0
  87. package/dist/query/strategies/jsonb-collection-strategy.d.ts +51 -0
  88. package/dist/query/strategies/jsonb-collection-strategy.d.ts.map +1 -0
  89. package/dist/query/strategies/jsonb-collection-strategy.js +210 -0
  90. package/dist/query/strategies/jsonb-collection-strategy.js.map +1 -0
  91. package/dist/query/strategies/temptable-collection-strategy.d.ts +95 -0
  92. package/dist/query/strategies/temptable-collection-strategy.d.ts.map +1 -0
  93. package/dist/query/strategies/temptable-collection-strategy.js +456 -0
  94. package/dist/query/strategies/temptable-collection-strategy.js.map +1 -0
  95. package/dist/query/subquery.d.ts +152 -0
  96. package/dist/query/subquery.d.ts.map +1 -0
  97. package/dist/query/subquery.js +206 -0
  98. package/dist/query/subquery.js.map +1 -0
  99. package/dist/schema/column-builder.d.ts +127 -0
  100. package/dist/schema/column-builder.d.ts.map +1 -0
  101. package/dist/schema/column-builder.js +184 -0
  102. package/dist/schema/column-builder.js.map +1 -0
  103. package/dist/schema/inference.d.ts +26 -0
  104. package/dist/schema/inference.d.ts.map +1 -0
  105. package/dist/schema/inference.js +3 -0
  106. package/dist/schema/inference.js.map +1 -0
  107. package/dist/schema/navigation.d.ts +215 -0
  108. package/dist/schema/navigation.d.ts.map +1 -0
  109. package/dist/schema/navigation.js +233 -0
  110. package/dist/schema/navigation.js.map +1 -0
  111. package/dist/schema/row-type.d.ts +26 -0
  112. package/dist/schema/row-type.d.ts.map +1 -0
  113. package/dist/schema/row-type.js +3 -0
  114. package/dist/schema/row-type.js.map +1 -0
  115. package/dist/schema/sequence-builder.d.ts +87 -0
  116. package/dist/schema/sequence-builder.d.ts.map +1 -0
  117. package/dist/schema/sequence-builder.js +123 -0
  118. package/dist/schema/sequence-builder.js.map +1 -0
  119. package/dist/schema/table-builder.d.ts +122 -0
  120. package/dist/schema/table-builder.d.ts.map +1 -0
  121. package/dist/schema/table-builder.js +132 -0
  122. package/dist/schema/table-builder.js.map +1 -0
  123. package/dist/schema/typed-schema.d.ts +22 -0
  124. package/dist/schema/typed-schema.d.ts.map +1 -0
  125. package/dist/schema/typed-schema.js +28 -0
  126. package/dist/schema/typed-schema.js.map +1 -0
  127. package/dist/types/column-types.d.ts +20 -0
  128. package/dist/types/column-types.d.ts.map +1 -0
  129. package/dist/types/column-types.js +14 -0
  130. package/dist/types/column-types.js.map +1 -0
  131. package/dist/types/custom-types.d.ts +85 -0
  132. package/dist/types/custom-types.d.ts.map +1 -0
  133. package/dist/types/custom-types.js +132 -0
  134. package/dist/types/custom-types.js.map +1 -0
  135. package/dist/types/enum-builder.d.ts +31 -0
  136. package/dist/types/enum-builder.d.ts.map +1 -0
  137. package/dist/types/enum-builder.js +46 -0
  138. package/dist/types/enum-builder.js.map +1 -0
  139. package/dist/types/metadata.d.ts +67 -0
  140. package/dist/types/metadata.d.ts.map +1 -0
  141. package/dist/types/metadata.js +57 -0
  142. package/dist/types/metadata.js.map +1 -0
  143. package/dist/types/type-mapper.d.ts +49 -0
  144. package/dist/types/type-mapper.d.ts.map +1 -0
  145. package/dist/types/type-mapper.js +49 -0
  146. package/dist/types/type-mapper.js.map +1 -0
  147. package/package.json +77 -0
@@ -0,0 +1,1055 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DbSchemaManager = void 0;
37
+ const readline = __importStar(require("readline"));
38
+ const enum_builder_1 = require("../types/enum-builder");
39
+ /**
40
+ * Database schema manager - handles schema creation, deletion, and automatic migrations
41
+ */
42
+ class DbSchemaManager {
43
+ constructor(client, schemaRegistry, options) {
44
+ this.client = client;
45
+ this.schemaRegistry = schemaRegistry;
46
+ this.rl = null;
47
+ this.logQueries = options?.logQueries ?? false;
48
+ this.postMigrationHook = options?.postMigrationHook;
49
+ this.sequenceRegistry = options?.sequenceRegistry ?? new Map();
50
+ }
51
+ /**
52
+ * Get or create readline interface for interactive prompts
53
+ */
54
+ getReadlineInterface() {
55
+ if (!this.rl) {
56
+ this.rl = readline.createInterface({
57
+ input: process.stdin,
58
+ output: process.stdout,
59
+ });
60
+ }
61
+ return this.rl;
62
+ }
63
+ /**
64
+ * Close readline interface if it exists
65
+ */
66
+ closeReadlineInterface() {
67
+ if (this.rl) {
68
+ this.rl.close();
69
+ this.rl = null;
70
+ }
71
+ }
72
+ /**
73
+ * Get qualified table name with schema prefix if specified
74
+ */
75
+ getQualifiedTableName(tableName, schema) {
76
+ return schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
77
+ }
78
+ /**
79
+ * Create all schemas used by tables
80
+ */
81
+ async createSchemas() {
82
+ const schemas = new Set();
83
+ // Collect all unique schemas from tables
84
+ for (const tableSchema of this.schemaRegistry.values()) {
85
+ if (tableSchema.schema) {
86
+ schemas.add(tableSchema.schema);
87
+ }
88
+ }
89
+ if (schemas.size === 0)
90
+ return;
91
+ if (this.logQueries) {
92
+ console.log('Creating schemas...\n');
93
+ }
94
+ for (const schemaName of schemas) {
95
+ const createSchemaSQL = `CREATE SCHEMA IF NOT EXISTS "${schemaName}"`;
96
+ if (this.logQueries) {
97
+ console.log(` Creating schema "${schemaName}"...`);
98
+ }
99
+ await this.client.query(createSchemaSQL);
100
+ if (this.logQueries) {
101
+ console.log(` ✓ Schema "${schemaName}" created\n`);
102
+ }
103
+ }
104
+ }
105
+ /**
106
+ * Create all ENUM types used in the schema
107
+ */
108
+ async createEnumTypes() {
109
+ const enums = enum_builder_1.EnumTypeRegistry.getAll();
110
+ if (enums.size === 0)
111
+ return;
112
+ if (this.logQueries) {
113
+ console.log('Creating ENUM types...\n');
114
+ }
115
+ for (const [enumName, enumDef] of enums.entries()) {
116
+ // Check if enum already exists
117
+ const checkSQL = `
118
+ SELECT EXISTS (
119
+ SELECT 1 FROM pg_type WHERE typname = $1
120
+ ) as exists
121
+ `;
122
+ const result = await this.client.query(checkSQL, [enumName]);
123
+ const exists = result.rows[0]?.exists;
124
+ if (!exists) {
125
+ const values = enumDef.values.map(v => `'${v}'`).join(', ');
126
+ const createEnumSQL = `CREATE TYPE "${enumName}" AS ENUM (${values})`;
127
+ if (this.logQueries) {
128
+ console.log(` Creating ENUM type "${enumName}"...`);
129
+ }
130
+ await this.client.query(createEnumSQL);
131
+ if (this.logQueries) {
132
+ console.log(` ✓ ENUM type "${enumName}" created\n`);
133
+ }
134
+ }
135
+ else if (this.logQueries) {
136
+ console.log(` ENUM type "${enumName}" already exists, skipping\n`);
137
+ }
138
+ }
139
+ }
140
+ /**
141
+ * Create all sequences registered in the schema
142
+ */
143
+ async createSequences() {
144
+ if (this.sequenceRegistry.size === 0)
145
+ return;
146
+ if (this.logQueries) {
147
+ console.log('Creating sequences...\n');
148
+ }
149
+ for (const [_, config] of this.sequenceRegistry.entries()) {
150
+ const qualifiedName = config.schema
151
+ ? `"${config.schema}"."${config.name}"`
152
+ : `"${config.name}"`;
153
+ // Check if sequence already exists
154
+ const checkSQL = config.schema
155
+ ? `SELECT EXISTS (
156
+ SELECT 1 FROM information_schema.sequences
157
+ WHERE sequence_schema = $1 AND sequence_name = $2
158
+ ) as exists`
159
+ : `SELECT EXISTS (
160
+ SELECT 1 FROM information_schema.sequences
161
+ WHERE sequence_name = $1 AND sequence_schema = 'public'
162
+ ) as exists`;
163
+ const checkParams = config.schema ? [config.schema, config.name] : [config.name];
164
+ const result = await this.client.query(checkSQL, checkParams);
165
+ const exists = result.rows[0]?.exists;
166
+ if (!exists) {
167
+ // Build CREATE SEQUENCE statement
168
+ let createSQL = `CREATE SEQUENCE ${qualifiedName}`;
169
+ const options = [];
170
+ if (config.startWith !== undefined) {
171
+ options.push(`START WITH ${config.startWith}`);
172
+ }
173
+ if (config.incrementBy !== undefined) {
174
+ options.push(`INCREMENT BY ${config.incrementBy}`);
175
+ }
176
+ if (config.minValue !== undefined) {
177
+ options.push(`MINVALUE ${config.minValue}`);
178
+ }
179
+ if (config.maxValue !== undefined) {
180
+ options.push(`MAXVALUE ${config.maxValue}`);
181
+ }
182
+ if (config.cache !== undefined) {
183
+ options.push(`CACHE ${config.cache}`);
184
+ }
185
+ if (config.cycle) {
186
+ options.push(`CYCLE`);
187
+ }
188
+ if (options.length > 0) {
189
+ createSQL += ` ${options.join(' ')}`;
190
+ }
191
+ if (this.logQueries) {
192
+ console.log(` Creating sequence ${qualifiedName}...`);
193
+ }
194
+ await this.client.query(createSQL);
195
+ if (this.logQueries) {
196
+ console.log(` ✓ Sequence ${qualifiedName} created\n`);
197
+ }
198
+ }
199
+ else if (this.logQueries) {
200
+ console.log(` Sequence ${qualifiedName} already exists, skipping\n`);
201
+ }
202
+ }
203
+ }
204
+ /**
205
+ * Create a single table
206
+ */
207
+ async createTable(tableName, tableSchema) {
208
+ const columnDefs = [];
209
+ const primaryKeys = [];
210
+ for (const [colKey, colBuilder] of Object.entries(tableSchema.columns)) {
211
+ const config = colBuilder.build();
212
+ let def = `"${config.name}" ${config.type}`;
213
+ if (config.length) {
214
+ def += `(${config.length})`;
215
+ }
216
+ else if (config.precision && config.scale) {
217
+ def += `(${config.precision}, ${config.scale})`;
218
+ }
219
+ else if (config.precision) {
220
+ def += `(${config.precision})`;
221
+ }
222
+ // Handle GENERATED ALWAYS AS IDENTITY
223
+ if (config.identity) {
224
+ def += ' GENERATED ALWAYS AS IDENTITY';
225
+ // Add sequence options if specified
226
+ const seqOptions = [];
227
+ if (config.identity.startWith !== undefined) {
228
+ seqOptions.push(`START WITH ${config.identity.startWith}`);
229
+ }
230
+ if (config.identity.incrementBy !== undefined) {
231
+ seqOptions.push(`INCREMENT BY ${config.identity.incrementBy}`);
232
+ }
233
+ if (seqOptions.length > 0) {
234
+ def += ` (${seqOptions.join(' ')})`;
235
+ }
236
+ }
237
+ if (!config.nullable) {
238
+ def += ' NOT NULL';
239
+ }
240
+ if (config.unique && !config.primaryKey) {
241
+ def += ' UNIQUE';
242
+ }
243
+ if (config.default !== undefined && !config.identity) {
244
+ def += ` DEFAULT ${this.formatDefaultValue(config.default)}`;
245
+ }
246
+ columnDefs.push(def);
247
+ if (config.primaryKey) {
248
+ primaryKeys.push(`"${config.name}"`);
249
+ }
250
+ }
251
+ // Add primary key constraint
252
+ if (primaryKeys.length > 0) {
253
+ columnDefs.push(`PRIMARY KEY (${primaryKeys.join(', ')})`);
254
+ }
255
+ // Add foreign key constraints from schema.foreignKeys (includes ON DELETE/ON UPDATE actions)
256
+ const foreignKeys = tableSchema.foreignKeys || [];
257
+ for (const fk of foreignKeys) {
258
+ const columnList = fk.columns.map(c => `"${c}"`).join(', ');
259
+ const refColumnList = fk.referencedColumns.map(c => `"${c}"`).join(', ');
260
+ // Find the referenced table's schema
261
+ const referencedTableSchema = this.schemaRegistry.get(fk.referencedTable);
262
+ const qualifiedReferencedTable = this.getQualifiedTableName(fk.referencedTable, referencedTableSchema?.schema);
263
+ let fkDef = `CONSTRAINT "${fk.name}" FOREIGN KEY (${columnList}) REFERENCES ${qualifiedReferencedTable}(${refColumnList})`;
264
+ if (fk.onDelete) {
265
+ fkDef += ` ON DELETE ${fk.onDelete.toUpperCase()}`;
266
+ }
267
+ if (fk.onUpdate) {
268
+ fkDef += ` ON UPDATE ${fk.onUpdate.toUpperCase()}`;
269
+ }
270
+ columnDefs.push(fkDef);
271
+ }
272
+ // Fallback: Add foreign key constraints from column references (for backward compatibility)
273
+ // Only add if not already added via foreignKeys array
274
+ const addedFkColumns = new Set(foreignKeys.flatMap(fk => fk.columns));
275
+ for (const [colKey, colBuilder] of Object.entries(tableSchema.columns)) {
276
+ const config = colBuilder.build();
277
+ if (config.references && !addedFkColumns.has(config.name)) {
278
+ columnDefs.push(`FOREIGN KEY ("${config.name}") REFERENCES "${config.references.table}"("${config.references.column}")`);
279
+ }
280
+ }
281
+ const qualifiedTableName = this.getQualifiedTableName(tableName, tableSchema.schema);
282
+ const createTableSQL = `
283
+ CREATE TABLE IF NOT EXISTS ${qualifiedTableName} (
284
+ ${columnDefs.join(',\n ')}
285
+ )
286
+ `;
287
+ if (this.logQueries) {
288
+ console.log(` Creating table ${qualifiedTableName}...`);
289
+ }
290
+ await this.client.query(createTableSQL);
291
+ if (this.logQueries) {
292
+ console.log(` ✓ Table ${qualifiedTableName} created\n`);
293
+ }
294
+ }
295
+ /**
296
+ * Create all tables in the database
297
+ */
298
+ async ensureCreated() {
299
+ if (this.logQueries) {
300
+ console.log('Creating database schema...\n');
301
+ }
302
+ // Create schemas first
303
+ await this.createSchemas();
304
+ // Create enum types
305
+ await this.createEnumTypes();
306
+ // Create sequences
307
+ await this.createSequences();
308
+ // Create tables
309
+ for (const [tableName, tableSchema] of this.schemaRegistry.entries()) {
310
+ await this.createTable(tableName, tableSchema);
311
+ }
312
+ if (this.logQueries) {
313
+ console.log('✓ Database schema created successfully\n');
314
+ }
315
+ // Execute post-migration hook if provided
316
+ if (this.postMigrationHook) {
317
+ if (this.logQueries) {
318
+ console.log('Executing post-migration scripts...\n');
319
+ }
320
+ await this.postMigrationHook(this.client);
321
+ if (this.logQueries) {
322
+ console.log('✓ Post-migration scripts completed\n');
323
+ }
324
+ }
325
+ }
326
+ /**
327
+ * Drop all tables
328
+ */
329
+ async ensureDeleted() {
330
+ if (this.logQueries) {
331
+ console.log('Dropping database schema...\n');
332
+ }
333
+ for (const [tableName, tableSchema] of this.schemaRegistry.entries()) {
334
+ const qualifiedTableName = this.getQualifiedTableName(tableName, tableSchema.schema);
335
+ if (this.logQueries) {
336
+ console.log(` Dropping table ${qualifiedTableName}...`);
337
+ }
338
+ await this.client.query(`DROP TABLE IF EXISTS ${qualifiedTableName} CASCADE`);
339
+ if (this.logQueries) {
340
+ console.log(` ✓ Table ${qualifiedTableName} dropped\n`);
341
+ }
342
+ }
343
+ // Drop sequences
344
+ if (this.sequenceRegistry.size > 0 && this.logQueries) {
345
+ console.log('Dropping sequences...\n');
346
+ }
347
+ for (const [_, config] of this.sequenceRegistry.entries()) {
348
+ const qualifiedName = config.schema
349
+ ? `"${config.schema}"."${config.name}"`
350
+ : `"${config.name}"`;
351
+ if (this.logQueries) {
352
+ console.log(` Dropping sequence ${qualifiedName}...`);
353
+ }
354
+ await this.client.query(`DROP SEQUENCE IF EXISTS ${qualifiedName} CASCADE`);
355
+ if (this.logQueries) {
356
+ console.log(` ✓ Sequence ${qualifiedName} dropped\n`);
357
+ }
358
+ }
359
+ // Drop enum types
360
+ const enums = enum_builder_1.EnumTypeRegistry.getAll();
361
+ if (enums.size > 0 && this.logQueries) {
362
+ console.log('Dropping ENUM types...\n');
363
+ }
364
+ for (const [enumName, _] of enums.entries()) {
365
+ if (this.logQueries) {
366
+ console.log(` Dropping ENUM type "${enumName}"...`);
367
+ }
368
+ await this.client.query(`DROP TYPE IF EXISTS "${enumName}" CASCADE`);
369
+ if (this.logQueries) {
370
+ console.log(` ✓ ENUM type "${enumName}" dropped\n`);
371
+ }
372
+ }
373
+ // Drop schemas (note: CASCADE will drop all objects in the schema)
374
+ const schemas = new Set();
375
+ for (const tableSchema of this.schemaRegistry.values()) {
376
+ if (tableSchema.schema) {
377
+ schemas.add(tableSchema.schema);
378
+ }
379
+ }
380
+ if (schemas.size > 0 && this.logQueries) {
381
+ console.log('Dropping schemas...\n');
382
+ }
383
+ for (const schemaName of schemas) {
384
+ if (this.logQueries) {
385
+ console.log(` Dropping schema "${schemaName}"...`);
386
+ }
387
+ await this.client.query(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`);
388
+ if (this.logQueries) {
389
+ console.log(` ✓ Schema "${schemaName}" dropped\n`);
390
+ }
391
+ }
392
+ if (this.logQueries) {
393
+ console.log('✓ Database schema dropped successfully\n');
394
+ }
395
+ }
396
+ /**
397
+ * Analyze differences between current DB and model schema
398
+ */
399
+ async analyze() {
400
+ console.log('🔍 Analyzing database schema...\n');
401
+ const operations = [];
402
+ // Check schemas
403
+ const modelSchemas = new Set();
404
+ for (const tableSchema of this.schemaRegistry.values()) {
405
+ if (tableSchema.schema) {
406
+ modelSchemas.add(tableSchema.schema);
407
+ }
408
+ }
409
+ // Add schema creation operations if needed
410
+ for (const schemaName of modelSchemas) {
411
+ const result = await this.client.query(`
412
+ SELECT EXISTS (
413
+ SELECT 1 FROM information_schema.schemata WHERE schema_name = $1
414
+ ) as exists
415
+ `, [schemaName]);
416
+ if (!result.rows[0]?.exists) {
417
+ operations.push({ type: 'create_schema', schemaName });
418
+ }
419
+ }
420
+ // Check enums
421
+ const modelEnums = enum_builder_1.EnumTypeRegistry.getAll();
422
+ for (const [enumName, enumDef] of modelEnums.entries()) {
423
+ const result = await this.client.query(`
424
+ SELECT EXISTS (
425
+ SELECT 1 FROM pg_type WHERE typname = $1
426
+ ) as exists
427
+ `, [enumName]);
428
+ if (!result.rows[0]?.exists) {
429
+ operations.push({ type: 'create_enum', enumName, values: enumDef.values });
430
+ }
431
+ }
432
+ // Get all existing tables
433
+ const existingTables = await this.getExistingTables();
434
+ const modelTables = new Set(this.schemaRegistry.keys());
435
+ // Find tables to create
436
+ for (const [tableName, schema] of this.schemaRegistry.entries()) {
437
+ if (!existingTables.has(tableName)) {
438
+ operations.push({ type: 'create_table', tableName, schema });
439
+ }
440
+ }
441
+ // Compare columns for existing tables
442
+ for (const [tableName, schema] of this.schemaRegistry.entries()) {
443
+ if (existingTables.has(tableName)) {
444
+ const existingColumns = await this.getExistingColumns(tableName, schema.schema);
445
+ const modelColumns = new Map();
446
+ // Build model columns map
447
+ for (const [colKey, colBuilder] of Object.entries(schema.columns)) {
448
+ const config = colBuilder.build();
449
+ modelColumns.set(config.name, config);
450
+ }
451
+ // Find columns to add
452
+ for (const [colName, config] of modelColumns.entries()) {
453
+ if (!existingColumns.has(colName)) {
454
+ operations.push({ type: 'add_column', tableName, columnName: colName, config });
455
+ }
456
+ }
457
+ // Find columns to alter
458
+ for (const [colName, dbInfo] of existingColumns.entries()) {
459
+ const modelConfig = modelColumns.get(colName);
460
+ if (modelConfig && this.needsAlter(dbInfo, modelConfig)) {
461
+ operations.push({ type: 'alter_column', tableName, columnName: colName, from: dbInfo, to: modelConfig });
462
+ }
463
+ }
464
+ // Compare indexes
465
+ const existingIndexes = await this.getExistingIndexes(tableName, schema.schema);
466
+ const modelIndexes = schema.indexes || [];
467
+ // Find indexes to create
468
+ for (const modelIndex of modelIndexes) {
469
+ const exists = existingIndexes.some(dbIndex => dbIndex.index_name === modelIndex.name);
470
+ if (!exists) {
471
+ operations.push({
472
+ type: 'create_index',
473
+ tableName,
474
+ indexName: modelIndex.name,
475
+ columns: modelIndex.columns
476
+ });
477
+ }
478
+ }
479
+ // Compare foreign key constraints
480
+ const existingForeignKeys = await this.getExistingForeignKeys(tableName, schema.schema);
481
+ const modelForeignKeys = schema.foreignKeys || [];
482
+ // Find foreign keys to create
483
+ for (const modelFk of modelForeignKeys) {
484
+ const exists = existingForeignKeys.some(dbFk => dbFk.constraint_name === modelFk.name);
485
+ if (!exists) {
486
+ operations.push({
487
+ type: 'create_foreign_key',
488
+ tableName,
489
+ constraint: modelFk
490
+ });
491
+ }
492
+ }
493
+ }
494
+ }
495
+ return operations;
496
+ }
497
+ /**
498
+ * Perform automatic migration - analyze and apply changes
499
+ */
500
+ async migrate() {
501
+ try {
502
+ const operations = await this.analyze();
503
+ if (operations.length === 0) {
504
+ console.log('✓ Database schema is already in sync with model\n');
505
+ return;
506
+ }
507
+ console.log(`📋 Found ${operations.length} operations to perform:\n`);
508
+ // Show all operations
509
+ for (let i = 0; i < operations.length; i++) {
510
+ console.log(`${i + 1}. ${this.describeOperation(operations[i])}`);
511
+ }
512
+ console.log('');
513
+ // Execute operations with confirmations for destructive ones
514
+ for (const operation of operations) {
515
+ await this.executeOperation(operation);
516
+ }
517
+ console.log('\n✓ Migration completed successfully\n');
518
+ // Execute post-migration hook if provided
519
+ if (this.postMigrationHook) {
520
+ if (this.logQueries) {
521
+ console.log('Executing post-migration scripts...\n');
522
+ }
523
+ await this.postMigrationHook(this.client);
524
+ if (this.logQueries) {
525
+ console.log('✓ Post-migration scripts completed\n');
526
+ }
527
+ }
528
+ }
529
+ finally {
530
+ this.closeReadlineInterface();
531
+ }
532
+ }
533
+ /**
534
+ * Execute a single migration operation
535
+ */
536
+ async executeOperation(operation) {
537
+ switch (operation.type) {
538
+ case 'create_schema':
539
+ await this.executeCreateSchema(operation.schemaName);
540
+ break;
541
+ case 'create_enum':
542
+ await this.executeCreateEnum(operation.enumName, operation.values);
543
+ break;
544
+ case 'create_table':
545
+ await this.createTable(operation.tableName, operation.schema);
546
+ break;
547
+ case 'drop_table':
548
+ if (await this.confirm(`Drop table "${operation.tableName}"? This will DELETE ALL DATA in the table.`)) {
549
+ await this.executeDropTable(operation.tableName);
550
+ }
551
+ else {
552
+ console.log(` ⊘ Skipped dropping table "${operation.tableName}"\n`);
553
+ }
554
+ break;
555
+ case 'add_column':
556
+ await this.executeAddColumn(operation.tableName, operation.columnName, operation.config);
557
+ break;
558
+ case 'drop_column':
559
+ if (await this.confirm(`Drop column "${operation.tableName}"."${operation.columnName}"? This will DELETE ALL DATA in the column.`)) {
560
+ await this.executeDropColumn(operation.tableName, operation.columnName);
561
+ }
562
+ else {
563
+ console.log(` ⊘ Skipped dropping column "${operation.tableName}"."${operation.columnName}"\n`);
564
+ }
565
+ break;
566
+ case 'alter_column':
567
+ await this.executeAlterColumn(operation.tableName, operation.columnName, operation.from, operation.to);
568
+ break;
569
+ case 'create_index':
570
+ await this.executeCreateIndex(operation.tableName, operation.indexName, operation.columns);
571
+ break;
572
+ case 'drop_index':
573
+ if (await this.confirm(`Drop index "${operation.indexName}"?`)) {
574
+ await this.executeDropIndex(operation.indexName);
575
+ }
576
+ else {
577
+ console.log(` ⊘ Skipped dropping index "${operation.indexName}"\n`);
578
+ }
579
+ break;
580
+ case 'create_foreign_key':
581
+ await this.executeCreateForeignKey(operation.tableName, operation.constraint);
582
+ break;
583
+ case 'drop_foreign_key':
584
+ if (await this.confirm(`Drop foreign key "${operation.constraintName}"?`)) {
585
+ await this.executeDropForeignKey(operation.tableName, operation.constraintName);
586
+ }
587
+ else {
588
+ console.log(` ⊘ Skipped dropping foreign key "${operation.constraintName}"\n`);
589
+ }
590
+ break;
591
+ }
592
+ }
593
+ /**
594
+ * Execute create schema
595
+ */
596
+ async executeCreateSchema(schemaName) {
597
+ console.log(` Creating schema "${schemaName}"...`);
598
+ await this.client.query(`CREATE SCHEMA IF NOT EXISTS "${schemaName}"`);
599
+ console.log(` ✓ Schema "${schemaName}" created\n`);
600
+ }
601
+ /**
602
+ * Execute create enum
603
+ */
604
+ async executeCreateEnum(enumName, values) {
605
+ console.log(` Creating ENUM type "${enumName}"...`);
606
+ const valueList = values.map(v => `'${v}'`).join(', ');
607
+ await this.client.query(`CREATE TYPE "${enumName}" AS ENUM (${valueList})`);
608
+ console.log(` ✓ ENUM type "${enumName}" created\n`);
609
+ }
610
+ /**
611
+ * Execute drop table
612
+ */
613
+ async executeDropTable(tableName) {
614
+ console.log(` Dropping table "${tableName}"...`);
615
+ await this.client.query(`DROP TABLE "${tableName}" CASCADE`);
616
+ console.log(` ✓ Table "${tableName}" dropped\n`);
617
+ }
618
+ /**
619
+ * Execute add column
620
+ */
621
+ async executeAddColumn(tableName, columnName, config) {
622
+ console.log(` Adding column "${tableName}"."${columnName}"...`);
623
+ let def = `${config.type}`;
624
+ if (config.length) {
625
+ def += `(${config.length})`;
626
+ }
627
+ else if (config.precision && config.scale) {
628
+ def += `(${config.precision}, ${config.scale})`;
629
+ }
630
+ else if (config.precision) {
631
+ def += `(${config.precision})`;
632
+ }
633
+ if (!config.nullable) {
634
+ def += ' NOT NULL';
635
+ }
636
+ if (config.unique) {
637
+ def += ' UNIQUE';
638
+ }
639
+ if (config.default !== undefined) {
640
+ def += ` DEFAULT ${this.formatDefaultValue(config.default)}`;
641
+ }
642
+ const sql = `ALTER TABLE "${tableName}" ADD COLUMN "${columnName}" ${def}`;
643
+ await this.client.query(sql);
644
+ console.log(` ✓ Column "${tableName}"."${columnName}" added\n`);
645
+ }
646
+ /**
647
+ * Execute drop column
648
+ */
649
+ async executeDropColumn(tableName, columnName) {
650
+ console.log(` Dropping column "${tableName}"."${columnName}"...`);
651
+ await this.client.query(`ALTER TABLE "${tableName}" DROP COLUMN "${columnName}"`);
652
+ console.log(` ✓ Column "${tableName}"."${columnName}" dropped\n`);
653
+ }
654
+ /**
655
+ * Execute alter column
656
+ */
657
+ async executeAlterColumn(tableName, columnName, from, to) {
658
+ console.log(` Altering column "${tableName}"."${columnName}"...`);
659
+ console.log(` From: ${this.describeDbColumn(from)}`);
660
+ console.log(` To: ${this.describeModelColumn(to)}`);
661
+ // PostgreSQL requires separate ALTER COLUMN commands for different changes
662
+ // Change type if needed
663
+ const fromType = this.normalizeType(from.data_type);
664
+ const toType = this.normalizeType(to.type);
665
+ if (fromType !== toType) {
666
+ const typeDef = this.buildTypeDefinition(to);
667
+ await this.client.query(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" TYPE ${typeDef} USING "${columnName}"::${typeDef}`);
668
+ console.log(` ✓ Type changed from ${fromType} to ${toType}`);
669
+ }
670
+ // Change nullability if needed
671
+ const fromNullable = from.is_nullable === 'YES';
672
+ const toNullable = to.nullable;
673
+ if (fromNullable !== toNullable) {
674
+ if (toNullable) {
675
+ await this.client.query(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP NOT NULL`);
676
+ console.log(` ✓ Nullability changed to NULLABLE`);
677
+ }
678
+ else {
679
+ await this.client.query(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET NOT NULL`);
680
+ console.log(` ✓ Nullability changed to NOT NULL`);
681
+ }
682
+ }
683
+ // Change default if needed
684
+ const fromDefault = from.column_default;
685
+ const toDefault = to.default !== undefined ? this.formatDefaultValue(to.default) : null;
686
+ if (fromDefault !== toDefault) {
687
+ if (toDefault !== null) {
688
+ await this.client.query(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" SET DEFAULT ${toDefault}`);
689
+ console.log(` ✓ Default changed to ${toDefault}`);
690
+ }
691
+ else {
692
+ await this.client.query(`ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}" DROP DEFAULT`);
693
+ console.log(` ✓ Default removed`);
694
+ }
695
+ }
696
+ console.log(` ✓ Column "${tableName}"."${columnName}" altered\n`);
697
+ }
698
+ /**
699
+ * Execute create index
700
+ */
701
+ async executeCreateIndex(tableName, indexName, columns) {
702
+ console.log(` Creating index "${indexName}" on "${tableName}"...`);
703
+ const columnList = columns.map(col => `"${col}"`).join(', ');
704
+ const sql = `CREATE INDEX "${indexName}" ON "${tableName}" (${columnList})`;
705
+ await this.client.query(sql);
706
+ console.log(` ✓ Index "${indexName}" created\n`);
707
+ }
708
+ /**
709
+ * Execute drop index
710
+ */
711
+ async executeDropIndex(indexName) {
712
+ console.log(` Dropping index "${indexName}"...`);
713
+ await this.client.query(`DROP INDEX "${indexName}"`);
714
+ console.log(` ✓ Index "${indexName}" dropped\n`);
715
+ }
716
+ /**
717
+ * Execute create foreign key
718
+ */
719
+ async executeCreateForeignKey(tableName, constraint) {
720
+ console.log(` Creating foreign key constraint "${constraint.name}" on "${tableName}"...`);
721
+ const columnList = constraint.columns.map((col) => `"${col}"`).join(', ');
722
+ const refColumnList = constraint.referencedColumns.map((col) => `"${col}"`).join(', ');
723
+ let sql = `ALTER TABLE "${tableName}" ADD CONSTRAINT "${constraint.name}" `;
724
+ sql += `FOREIGN KEY (${columnList}) `;
725
+ sql += `REFERENCES "${constraint.referencedTable}" (${refColumnList})`;
726
+ if (constraint.onDelete) {
727
+ sql += ` ON DELETE ${constraint.onDelete.toUpperCase()}`;
728
+ }
729
+ if (constraint.onUpdate) {
730
+ sql += ` ON UPDATE ${constraint.onUpdate.toUpperCase()}`;
731
+ }
732
+ await this.client.query(sql);
733
+ console.log(` ✓ Foreign key constraint "${constraint.name}" created\n`);
734
+ }
735
+ /**
736
+ * Execute drop foreign key
737
+ */
738
+ async executeDropForeignKey(tableName, constraintName) {
739
+ console.log(` Dropping foreign key constraint "${constraintName}"...`);
740
+ await this.client.query(`ALTER TABLE "${tableName}" DROP CONSTRAINT "${constraintName}"`);
741
+ console.log(` ✓ Foreign key constraint "${constraintName}" dropped\n`);
742
+ }
743
+ /**
744
+ * Get all existing tables in the database
745
+ */
746
+ async getExistingTables() {
747
+ const result = await this.client.query(`
748
+ SELECT table_name
749
+ FROM information_schema.tables
750
+ WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
751
+ `);
752
+ const tables = new Map();
753
+ for (const row of result.rows) {
754
+ tables.set(row.table_name, true);
755
+ }
756
+ return tables;
757
+ }
758
+ /**
759
+ * Get all columns for a table
760
+ */
761
+ async getExistingColumns(tableName, schemaName) {
762
+ const result = await this.client.query(`
763
+ SELECT
764
+ column_name,
765
+ data_type,
766
+ character_maximum_length,
767
+ numeric_precision,
768
+ numeric_scale,
769
+ is_nullable,
770
+ column_default
771
+ FROM information_schema.columns
772
+ WHERE table_schema = $1 AND table_name = $2
773
+ ORDER BY ordinal_position
774
+ `, [schemaName || 'public', tableName]);
775
+ const columns = new Map();
776
+ for (const row of result.rows) {
777
+ columns.set(row.column_name, row);
778
+ }
779
+ return columns;
780
+ }
781
+ /**
782
+ * Get all indexes for a table
783
+ */
784
+ async getExistingIndexes(tableName, schemaName) {
785
+ const result = await this.client.query(`
786
+ SELECT
787
+ i.relname as index_name,
788
+ array_agg(a.attname ORDER BY k.ordinality) as column_names
789
+ FROM pg_index ix
790
+ JOIN pg_class t ON t.oid = ix.indrelid
791
+ JOIN pg_class i ON i.oid = ix.indexrelid
792
+ JOIN pg_namespace n ON n.oid = t.relnamespace
793
+ CROSS JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY k(attnum, ordinality)
794
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
795
+ WHERE
796
+ n.nspname = $1
797
+ AND t.relname = $2
798
+ AND NOT ix.indisprimary
799
+ AND NOT ix.indisunique
800
+ GROUP BY i.relname
801
+ `, [schemaName || 'public', tableName]);
802
+ return result.rows.map(row => ({
803
+ index_name: row.index_name,
804
+ column_names: row.column_names
805
+ }));
806
+ }
807
+ /**
808
+ * Get all foreign key constraints for a table
809
+ */
810
+ async getExistingForeignKeys(tableName, schemaName) {
811
+ const result = await this.client.query(`
812
+ SELECT
813
+ tc.constraint_name,
814
+ array_agg(kcu.column_name ORDER BY kcu.ordinal_position) as column_names,
815
+ ccu.table_name AS referenced_table,
816
+ array_agg(ccu.column_name ORDER BY kcu.ordinal_position) as referenced_column_names,
817
+ rc.delete_rule as on_delete,
818
+ rc.update_rule as on_update
819
+ FROM information_schema.table_constraints AS tc
820
+ JOIN information_schema.key_column_usage AS kcu
821
+ ON tc.constraint_name = kcu.constraint_name
822
+ AND tc.table_schema = kcu.table_schema
823
+ JOIN information_schema.constraint_column_usage AS ccu
824
+ ON ccu.constraint_name = tc.constraint_name
825
+ AND ccu.table_schema = tc.table_schema
826
+ JOIN information_schema.referential_constraints AS rc
827
+ ON rc.constraint_name = tc.constraint_name
828
+ AND rc.constraint_schema = tc.table_schema
829
+ WHERE
830
+ tc.table_schema = $1
831
+ AND tc.table_name = $2
832
+ AND tc.constraint_type = 'FOREIGN KEY'
833
+ GROUP BY tc.constraint_name, ccu.table_name, rc.delete_rule, rc.update_rule
834
+ `, [schemaName || 'public', tableName]);
835
+ return result.rows.map(row => ({
836
+ constraint_name: row.constraint_name,
837
+ column_names: row.column_names,
838
+ referenced_table: row.referenced_table,
839
+ referenced_column_names: row.referenced_column_names,
840
+ on_delete: row.on_delete,
841
+ on_update: row.on_update
842
+ }));
843
+ }
844
+ /**
845
+ * Check if a column needs to be altered
846
+ */
847
+ needsAlter(dbInfo, modelConfig) {
848
+ // Compare type
849
+ const dbType = this.normalizeType(dbInfo.data_type);
850
+ const modelType = this.normalizeType(modelConfig.type);
851
+ if (dbType !== modelType) {
852
+ return true;
853
+ }
854
+ // Compare nullability
855
+ const dbNullable = dbInfo.is_nullable === 'YES';
856
+ const modelNullable = modelConfig.nullable;
857
+ if (dbNullable !== modelNullable) {
858
+ return true;
859
+ }
860
+ // Compare default (normalize for comparison)
861
+ const dbDefault = this.normalizeDefault(dbInfo.column_default);
862
+ const modelDefault = modelConfig.default !== undefined
863
+ ? this.normalizeDefault(this.formatDefaultValue(modelConfig.default))
864
+ : null;
865
+ if (dbDefault !== modelDefault) {
866
+ return true;
867
+ }
868
+ return false;
869
+ }
870
+ /**
871
+ * Normalize default values for comparison
872
+ */
873
+ normalizeDefault(value) {
874
+ if (value === null)
875
+ return null;
876
+ let normalized = value.toLowerCase().trim();
877
+ // Remove type casts like ::character varying, ::regclass
878
+ normalized = normalized.replace(/::[a-z_]+(\s+varying)?/g, '');
879
+ // Remove function call parentheses for comparison
880
+ normalized = normalized.replace(/\(\)/g, '');
881
+ // Remove single quotes around strings for comparison
882
+ normalized = normalized.replace(/^'(.*)'$/, '$1');
883
+ // Normalize nextval sequences
884
+ if (normalized.includes('nextval')) {
885
+ return 'auto'; // Treat all nextval as equivalent
886
+ }
887
+ return normalized;
888
+ }
889
+ /**
890
+ * Normalize PostgreSQL type names for comparison
891
+ */
892
+ normalizeType(type) {
893
+ const normalized = type.toLowerCase().trim();
894
+ // Map common variations
895
+ const typeMap = {
896
+ 'character varying': 'varchar',
897
+ 'character': 'char',
898
+ 'integer': 'int',
899
+ 'bigint': 'int8',
900
+ 'smallint': 'int2',
901
+ 'double precision': 'float8',
902
+ 'real': 'float4',
903
+ 'timestamp without time zone': 'timestamp',
904
+ 'timestamp with time zone': 'timestamptz',
905
+ 'time without time zone': 'time',
906
+ 'time with time zone': 'timetz',
907
+ 'serial': 'int',
908
+ 'bigserial': 'int8',
909
+ 'smallserial': 'int2',
910
+ 'numeric': 'decimal',
911
+ };
912
+ return typeMap[normalized] || normalized;
913
+ }
914
+ /**
915
+ * Build type definition for ALTER COLUMN TYPE
916
+ */
917
+ buildTypeDefinition(config) {
918
+ let type = config.type;
919
+ if (type === 'serial')
920
+ type = 'integer';
921
+ if (type === 'bigserial')
922
+ type = 'bigint';
923
+ if (type === 'smallserial')
924
+ type = 'smallint';
925
+ let def = type;
926
+ if (config.length) {
927
+ def += `(${config.length})`;
928
+ }
929
+ else if (config.precision && config.scale) {
930
+ def += `(${config.precision}, ${config.scale})`;
931
+ }
932
+ else if (config.precision) {
933
+ def += `(${config.precision})`;
934
+ }
935
+ return def;
936
+ }
937
+ /**
938
+ * Describe a database column
939
+ */
940
+ describeDbColumn(col) {
941
+ let desc = col.data_type;
942
+ if (col.character_maximum_length) {
943
+ desc += `(${col.character_maximum_length})`;
944
+ }
945
+ else if (col.numeric_precision) {
946
+ desc += `(${col.numeric_precision}${col.numeric_scale ? ',' + col.numeric_scale : ''})`;
947
+ }
948
+ desc += col.is_nullable === 'YES' ? ' NULL' : ' NOT NULL';
949
+ if (col.column_default) {
950
+ desc += ` DEFAULT ${col.column_default}`;
951
+ }
952
+ return desc;
953
+ }
954
+ /**
955
+ * Describe a model column
956
+ */
957
+ describeModelColumn(config) {
958
+ let desc = config.type;
959
+ if (config.length) {
960
+ desc += `(${config.length})`;
961
+ }
962
+ else if (config.precision) {
963
+ desc += `(${config.precision}${config.scale ? ',' + config.scale : ''})`;
964
+ }
965
+ desc += config.nullable ? ' NULL' : ' NOT NULL';
966
+ if (config.default !== undefined) {
967
+ desc += ` DEFAULT ${this.formatDefaultValue(config.default)}`;
968
+ }
969
+ return desc;
970
+ }
971
+ /**
972
+ * Describe a migration operation
973
+ */
974
+ describeOperation(operation) {
975
+ switch (operation.type) {
976
+ case 'create_schema':
977
+ return `Create schema "${operation.schemaName}"`;
978
+ case 'create_enum':
979
+ return `Create ENUM type "${operation.enumName}" (${operation.values.join(', ')})`;
980
+ case 'create_table':
981
+ return `Create table "${operation.tableName}"`;
982
+ case 'drop_table':
983
+ return `Drop table "${operation.tableName}" (DESTRUCTIVE)`;
984
+ case 'add_column':
985
+ return `Add column "${operation.tableName}"."${operation.columnName}" (${this.describeModelColumn(operation.config)})`;
986
+ case 'drop_column':
987
+ return `Drop column "${operation.tableName}"."${operation.columnName}" (DESTRUCTIVE)`;
988
+ case 'alter_column':
989
+ return `Alter column "${operation.tableName}"."${operation.columnName}"`;
990
+ case 'create_index':
991
+ return `Create index "${operation.indexName}" on "${operation.tableName}" (${operation.columns.join(', ')})`;
992
+ case 'drop_index':
993
+ return `Drop index "${operation.indexName}" (DESTRUCTIVE)`;
994
+ case 'create_foreign_key':
995
+ const fk = operation.constraint;
996
+ let desc = `Create foreign key "${fk.name}" on "${operation.tableName}" (${fk.columns.join(', ')}) references "${fk.referencedTable}" (${fk.referencedColumns.join(', ')})`;
997
+ if (fk.onDelete || fk.onUpdate) {
998
+ const actions = [];
999
+ if (fk.onDelete)
1000
+ actions.push(`ON DELETE ${fk.onDelete.toUpperCase()}`);
1001
+ if (fk.onUpdate)
1002
+ actions.push(`ON UPDATE ${fk.onUpdate.toUpperCase()}`);
1003
+ desc += ` [${actions.join(', ')}]`;
1004
+ }
1005
+ return desc;
1006
+ case 'drop_foreign_key':
1007
+ return `Drop foreign key "${operation.constraintName}" (DESTRUCTIVE)`;
1008
+ }
1009
+ }
1010
+ /**
1011
+ * Ask user for confirmation (CLI prompt)
1012
+ */
1013
+ async confirm(question) {
1014
+ // If not running in a TTY (like in tests), default to NO for destructive operations
1015
+ if (!process.stdin.isTTY) {
1016
+ console.log(`\n⚠️ ${question} [y/N]: N (non-interactive mode, defaulting to NO)`);
1017
+ return false;
1018
+ }
1019
+ const rl = this.getReadlineInterface();
1020
+ return new Promise((resolve) => {
1021
+ rl.question(`\n⚠️ ${question} [y/N]: `, (answer) => {
1022
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
1023
+ });
1024
+ });
1025
+ }
1026
+ /**
1027
+ * Format default value for SQL
1028
+ */
1029
+ formatDefaultValue(value) {
1030
+ if (value === null)
1031
+ return 'NULL';
1032
+ if (typeof value === 'string') {
1033
+ // Check if it's a SQL function
1034
+ if (value.toUpperCase().includes('NOW()') || value.toUpperCase().includes('CURRENT_')) {
1035
+ return value;
1036
+ }
1037
+ return `'${value}'`;
1038
+ }
1039
+ if (typeof value === 'boolean')
1040
+ return value ? 'TRUE' : 'FALSE';
1041
+ if (value instanceof Date)
1042
+ return `'${value.toISOString()}'`;
1043
+ return String(value);
1044
+ }
1045
+ /**
1046
+ * Close the schema manager and any open resources
1047
+ */
1048
+ close() {
1049
+ if (this.rl) {
1050
+ this.rl.close();
1051
+ }
1052
+ }
1053
+ }
1054
+ exports.DbSchemaManager = DbSchemaManager;
1055
+ //# sourceMappingURL=db-schema-manager.js.map