@uql/core 3.4.2 → 3.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1012,3 +1012,1292 @@ const hooks = {
1012
1012
 
1013
1013
  export { BaseEntity, _Company as Company, _InventoryAdjustment as InventoryAdjustment, _Item as Item, _ItemAdjustment as ItemAdjustment, _ItemTag as ItemTag, _LedgerAccount as LedgerAccount, _MeasureUnit as MeasureUnit, _MeasureUnitCategory as MeasureUnitCategory, _Profile as Profile, _Storehouse as Storehouse, _Tag as Tag, _Tax as Tax, _TaxCategory as TaxCategory, _User as User, _UserWithNonUpdatableId as UserWithNonUpdatableId, clearTables, createSpec, createTables, dropTables };
1014
1014
  //# sourceMappingURL=uql-browser.min.js.map
1015
+ nst querier = await this.getQuerier();
1016
+ try {
1017
+ const sql = `
1018
+ SELECT table_name
1019
+ FROM information_schema.tables
1020
+ WHERE table_schema = 'public'
1021
+ AND table_type = 'BASE TABLE'
1022
+ ORDER BY table_name
1023
+ `;
1024
+ const results = await querier.all(sql);
1025
+ return results.map((r)=>r.table_name);
1026
+ } finally{
1027
+ await querier.release();
1028
+ }
1029
+ }
1030
+ async tableExists(tableName) {
1031
+ const querier = await this.getQuerier();
1032
+ try {
1033
+ return this.tableExistsInternal(querier, tableName);
1034
+ } finally{
1035
+ await querier.release();
1036
+ }
1037
+ }
1038
+ async tableExistsInternal(querier, tableName) {
1039
+ const sql = `
1040
+ SELECT EXISTS (
1041
+ SELECT FROM information_schema.tables
1042
+ WHERE table_schema = 'public'
1043
+ AND table_name = $1
1044
+ ) AS exists
1045
+ `;
1046
+ const results = await querier.all(sql, [
1047
+ tableName
1048
+ ]);
1049
+ return results[0]?.exists ?? false;
1050
+ }
1051
+ async getQuerier() {
1052
+ const querier = await this.pool.getQuerier();
1053
+ if (!isSqlQuerier(querier)) {
1054
+ await querier.release();
1055
+ throw new Error('PostgresSchemaIntrospector requires a SQL-based querier');
1056
+ }
1057
+ return querier;
1058
+ }
1059
+ async getColumns(querier, tableName) {
1060
+ const sql = /*sql*/ `
1061
+ SELECT
1062
+ c.column_name,
1063
+ c.data_type,
1064
+ c.udt_name,
1065
+ c.is_nullable,
1066
+ c.column_default,
1067
+ c.character_maximum_length,
1068
+ c.numeric_precision,
1069
+ c.numeric_scale,
1070
+ COALESCE(
1071
+ (SELECT TRUE FROM information_schema.table_constraints tc
1072
+ JOIN information_schema.key_column_usage kcu
1073
+ ON tc.constraint_name = kcu.constraint_name
1074
+ WHERE tc.table_name = c.table_name
1075
+ AND tc.constraint_type = 'PRIMARY KEY'
1076
+ AND kcu.column_name = c.column_name
1077
+ LIMIT 1),
1078
+ FALSE
1079
+ ) AS is_primary_key,
1080
+ COALESCE(
1081
+ (SELECT TRUE FROM information_schema.table_constraints tc
1082
+ JOIN information_schema.key_column_usage kcu
1083
+ ON tc.constraint_name = kcu.constraint_name
1084
+ WHERE tc.table_name = c.table_name
1085
+ AND tc.constraint_type = 'UNIQUE'
1086
+ AND kcu.column_name = c.column_name
1087
+ LIMIT 1),
1088
+ FALSE
1089
+ ) AS is_unique,
1090
+ pg_catalog.col_description(
1091
+ (SELECT oid FROM pg_catalog.pg_class WHERE relname = c.table_name),
1092
+ c.ordinal_position
1093
+ ) AS column_comment
1094
+ FROM information_schema.columns c
1095
+ WHERE c.table_schema = 'public'
1096
+ AND c.table_name = $1
1097
+ ORDER BY c.ordinal_position
1098
+ `;
1099
+ const results = await querier.all(sql, [
1100
+ tableName
1101
+ ]);
1102
+ return results.map((row)=>({
1103
+ name: row.column_name,
1104
+ type: this.normalizeType(row.data_type, row.udt_name),
1105
+ nullable: row.is_nullable === 'YES',
1106
+ defaultValue: this.parseDefaultValue(row.column_default),
1107
+ isPrimaryKey: row.is_primary_key,
1108
+ isAutoIncrement: this.isAutoIncrement(row.column_default),
1109
+ isUnique: row.is_unique,
1110
+ length: row.character_maximum_length ?? undefined,
1111
+ precision: row.numeric_precision ?? undefined,
1112
+ scale: row.numeric_scale ?? undefined,
1113
+ comment: row.column_comment ?? undefined
1114
+ }));
1115
+ }
1116
+ async getIndexes(querier, tableName) {
1117
+ const sql = /*sql*/ `
1118
+ SELECT
1119
+ i.relname AS index_name,
1120
+ array_agg(a.attname ORDER BY k.n) AS columns,
1121
+ ix.indisunique AS is_unique
1122
+ FROM pg_class t
1123
+ JOIN pg_index ix ON t.oid = ix.indrelid
1124
+ JOIN pg_class i ON i.oid = ix.indexrelid
1125
+ JOIN pg_namespace n ON n.oid = t.relnamespace
1126
+ CROSS JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, n)
1127
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
1128
+ WHERE t.relname = $1
1129
+ AND n.nspname = 'public'
1130
+ AND NOT ix.indisprimary
1131
+ GROUP BY i.relname, ix.indisunique
1132
+ ORDER BY i.relname
1133
+ `;
1134
+ const results = await querier.all(sql, [
1135
+ tableName
1136
+ ]);
1137
+ return results.map((row)=>({
1138
+ name: row.index_name,
1139
+ columns: row.columns,
1140
+ unique: row.is_unique
1141
+ }));
1142
+ }
1143
+ async getForeignKeys(querier, tableName) {
1144
+ const sql = /*sql*/ `
1145
+ SELECT
1146
+ tc.constraint_name,
1147
+ array_agg(kcu.column_name ORDER BY kcu.ordinal_position) AS columns,
1148
+ ccu.table_name AS referenced_table,
1149
+ array_agg(ccu.column_name ORDER BY kcu.ordinal_position) AS referenced_columns,
1150
+ rc.delete_rule,
1151
+ rc.update_rule
1152
+ FROM information_schema.table_constraints tc
1153
+ JOIN information_schema.key_column_usage kcu
1154
+ ON tc.constraint_name = kcu.constraint_name
1155
+ AND tc.table_schema = kcu.table_schema
1156
+ JOIN information_schema.constraint_column_usage ccu
1157
+ ON ccu.constraint_name = tc.constraint_name
1158
+ AND ccu.table_schema = tc.table_schema
1159
+ JOIN information_schema.referential_constraints rc
1160
+ ON rc.constraint_name = tc.constraint_name
1161
+ AND rc.constraint_schema = tc.table_schema
1162
+ WHERE tc.constraint_type = 'FOREIGN KEY'
1163
+ AND tc.table_name = $1
1164
+ AND tc.table_schema = 'public'
1165
+ GROUP BY tc.constraint_name, ccu.table_name, rc.delete_rule, rc.update_rule
1166
+ ORDER BY tc.constraint_name
1167
+ `;
1168
+ const results = await querier.all(sql, [
1169
+ tableName
1170
+ ]);
1171
+ return results.map((row)=>({
1172
+ name: row.constraint_name,
1173
+ columns: row.columns,
1174
+ referencedTable: row.referenced_table,
1175
+ referencedColumns: row.referenced_columns,
1176
+ onDelete: this.normalizeReferentialAction(row.delete_rule),
1177
+ onUpdate: this.normalizeReferentialAction(row.update_rule)
1178
+ }));
1179
+ }
1180
+ async getPrimaryKey(querier, tableName) {
1181
+ const sql = /*sql*/ `
1182
+ SELECT kcu.column_name
1183
+ FROM information_schema.table_constraints tc
1184
+ JOIN information_schema.key_column_usage kcu
1185
+ ON tc.constraint_name = kcu.constraint_name
1186
+ AND tc.table_schema = kcu.table_schema
1187
+ WHERE tc.constraint_type = 'PRIMARY KEY'
1188
+ AND tc.table_name = $1
1189
+ AND tc.table_schema = 'public'
1190
+ ORDER BY kcu.ordinal_position
1191
+ `;
1192
+ const results = await querier.all(sql, [
1193
+ tableName
1194
+ ]);
1195
+ if (results.length === 0) {
1196
+ return undefined;
1197
+ }
1198
+ return results.map((r)=>r.column_name);
1199
+ }
1200
+ normalizeType(dataType, udtName) {
1201
+ // Handle user-defined types and arrays
1202
+ if (dataType === 'USER-DEFINED') {
1203
+ return udtName.toUpperCase();
1204
+ }
1205
+ if (dataType === 'ARRAY') {
1206
+ return `${udtName.replace(/^_/, '')}[]`;
1207
+ }
1208
+ return dataType.toUpperCase();
1209
+ }
1210
+ parseDefaultValue(defaultValue) {
1211
+ if (!defaultValue) {
1212
+ return undefined;
1213
+ }
1214
+ // Remove type casting
1215
+ const cleaned = defaultValue.replace(/::[a-z_]+(\[\])?/gi, '').trim();
1216
+ // Check for common patterns
1217
+ if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
1218
+ return cleaned.slice(1, -1);
1219
+ }
1220
+ if (cleaned === 'true' || cleaned === 'false') {
1221
+ return cleaned === 'true';
1222
+ }
1223
+ if (cleaned === 'NULL') {
1224
+ return null;
1225
+ }
1226
+ if (/^-?\d+$/.test(cleaned)) {
1227
+ return Number.parseInt(cleaned, 10);
1228
+ }
1229
+ if (/^-?\d+\.\d+$/.test(cleaned)) {
1230
+ return Number.parseFloat(cleaned);
1231
+ }
1232
+ // Return as-is for functions like CURRENT_TIMESTAMP, nextval(), etc.
1233
+ return defaultValue;
1234
+ }
1235
+ isAutoIncrement(defaultValue) {
1236
+ if (!defaultValue) {
1237
+ return false;
1238
+ }
1239
+ return defaultValue.includes('nextval(');
1240
+ }
1241
+ normalizeReferentialAction(action) {
1242
+ switch(action.toUpperCase()){
1243
+ case 'CASCADE':
1244
+ return 'CASCADE';
1245
+ case 'SET NULL':
1246
+ return 'SET NULL';
1247
+ case 'RESTRICT':
1248
+ return 'RESTRICT';
1249
+ case 'NO ACTION':
1250
+ return 'NO ACTION';
1251
+ default:
1252
+ return undefined;
1253
+ }
1254
+ }
1255
+ }
1256
+
1257
+ /**
1258
+ * SQLite schema introspector
1259
+ */ class SqliteSchemaIntrospector {
1260
+ constructor(pool){
1261
+ this.pool = pool;
1262
+ }
1263
+ async getTableSchema(tableName) {
1264
+ const querier = await this.getQuerier();
1265
+ try {
1266
+ const exists = await this.tableExistsInternal(querier, tableName);
1267
+ if (!exists) {
1268
+ return undefined;
1269
+ }
1270
+ const [columns, indexes, foreignKeys, primaryKey] = await Promise.all([
1271
+ this.getColumns(querier, tableName),
1272
+ this.getIndexes(querier, tableName),
1273
+ this.getForeignKeys(querier, tableName),
1274
+ this.getPrimaryKey(querier, tableName)
1275
+ ]);
1276
+ return {
1277
+ name: tableName,
1278
+ columns,
1279
+ primaryKey,
1280
+ indexes,
1281
+ foreignKeys
1282
+ };
1283
+ } finally{
1284
+ await querier.release();
1285
+ }
1286
+ }
1287
+ async getTableNames() {
1288
+ const querier = await this.getQuerier();
1289
+ try {
1290
+ const sql = /*sql*/ `
1291
+ SELECT name
1292
+ FROM sqlite_master
1293
+ WHERE type = 'table'
1294
+ AND name NOT LIKE 'sqlite_%'
1295
+ ORDER BY name
1296
+ `;
1297
+ const results = await querier.all(sql);
1298
+ return results.map((r)=>r.name);
1299
+ } finally{
1300
+ await querier.release();
1301
+ }
1302
+ }
1303
+ async tableExists(tableName) {
1304
+ const querier = await this.getQuerier();
1305
+ try {
1306
+ return this.tableExistsInternal(querier, tableName);
1307
+ } finally{
1308
+ await querier.release();
1309
+ }
1310
+ }
1311
+ async tableExistsInternal(querier, tableName) {
1312
+ const sql = /*sql*/ `
1313
+ SELECT COUNT(*) as count
1314
+ FROM sqlite_master
1315
+ WHERE type = 'table'
1316
+ AND name = ?
1317
+ `;
1318
+ const results = await querier.all(sql, [
1319
+ tableName
1320
+ ]);
1321
+ return (results[0]?.count ?? 0) > 0;
1322
+ }
1323
+ async getQuerier() {
1324
+ const querier = await this.pool.getQuerier();
1325
+ if (!isSqlQuerier(querier)) {
1326
+ await querier.release();
1327
+ throw new Error('SqliteSchemaIntrospector requires a SQL-based querier');
1328
+ }
1329
+ return querier;
1330
+ }
1331
+ async getColumns(querier, tableName) {
1332
+ // SQLite uses PRAGMA for table info
1333
+ const sql = `PRAGMA table_info(${this.escapeId(tableName)})`;
1334
+ const results = await querier.all(sql);
1335
+ // Get unique columns from indexes
1336
+ const uniqueColumns = await this.getUniqueColumns(querier, tableName);
1337
+ return results.map((row)=>({
1338
+ name: row.name,
1339
+ type: this.normalizeType(row.type),
1340
+ nullable: row.notnull === 0,
1341
+ defaultValue: this.parseDefaultValue(row.dflt_value),
1342
+ isPrimaryKey: row.pk > 0,
1343
+ isAutoIncrement: row.pk > 0 && row.type.toUpperCase() === 'INTEGER',
1344
+ isUnique: uniqueColumns.has(row.name),
1345
+ length: this.extractLength(row.type),
1346
+ precision: undefined,
1347
+ scale: undefined,
1348
+ comment: undefined
1349
+ }));
1350
+ }
1351
+ async getUniqueColumns(querier, tableName) {
1352
+ const sql = `PRAGMA index_list(${this.escapeId(tableName)})`;
1353
+ const indexes = await querier.all(sql);
1354
+ const uniqueColumns = new Set();
1355
+ for (const index of indexes){
1356
+ if (index.unique && index.origin === 'u') {
1357
+ const indexInfo = await querier.all(`PRAGMA index_info(${this.escapeId(index.name)})`);
1358
+ // Only single-column unique constraints
1359
+ if (indexInfo.length === 1) {
1360
+ uniqueColumns.add(indexInfo[0].name);
1361
+ }
1362
+ }
1363
+ }
1364
+ return uniqueColumns;
1365
+ }
1366
+ async getIndexes(querier, tableName) {
1367
+ const sql = `PRAGMA index_list(${this.escapeId(tableName)})`;
1368
+ const indexes = await querier.all(sql);
1369
+ const result = [];
1370
+ for (const index of indexes){
1371
+ // Skip auto-generated indexes (primary key, unique constraints)
1372
+ if (index.origin !== 'c') {
1373
+ continue;
1374
+ }
1375
+ const columns = await querier.all(`PRAGMA index_info(${this.escapeId(index.name)})`);
1376
+ result.push({
1377
+ name: index.name,
1378
+ columns: columns.map((c)=>c.name),
1379
+ unique: Boolean(index.unique)
1380
+ });
1381
+ }
1382
+ return result;
1383
+ }
1384
+ async getForeignKeys(querier, tableName) {
1385
+ const sql = `PRAGMA foreign_key_list(${this.escapeId(tableName)})`;
1386
+ const results = await querier.all(sql);
1387
+ // Group by id to handle composite foreign keys
1388
+ const grouped = new Map();
1389
+ for (const row of results){
1390
+ const existing = grouped.get(row.id) ?? [];
1391
+ existing.push(row);
1392
+ grouped.set(row.id, existing);
1393
+ }
1394
+ return Array.from(grouped.entries()).map(([id, rows])=>{
1395
+ const first = rows[0];
1396
+ return {
1397
+ name: `fk_${tableName}_${id}`,
1398
+ columns: rows.map((r)=>r.from),
1399
+ referencedTable: first.table,
1400
+ referencedColumns: rows.map((r)=>r.to),
1401
+ onDelete: this.normalizeReferentialAction(first.on_delete),
1402
+ onUpdate: this.normalizeReferentialAction(first.on_update)
1403
+ };
1404
+ });
1405
+ }
1406
+ async getPrimaryKey(querier, tableName) {
1407
+ const sql = `PRAGMA table_info(${this.escapeId(tableName)})`;
1408
+ const results = await querier.all(sql);
1409
+ const pkColumns = results.filter((r)=>r.pk > 0).sort((a, b)=>a.pk - b.pk);
1410
+ if (pkColumns.length === 0) {
1411
+ return undefined;
1412
+ }
1413
+ return pkColumns.map((r)=>r.name);
1414
+ }
1415
+ escapeId(identifier) {
1416
+ return `\`${identifier.replace(/`/g, '``')}\``;
1417
+ }
1418
+ normalizeType(type) {
1419
+ // Extract base type without length/precision
1420
+ const match = type.match(/^([A-Za-z]+)/);
1421
+ return match ? match[1].toUpperCase() : type.toUpperCase();
1422
+ }
1423
+ extractLength(type) {
1424
+ const match = type.match(/\((\d+)\)/);
1425
+ return match ? Number.parseInt(match[1], 10) : undefined;
1426
+ }
1427
+ parseDefaultValue(defaultValue) {
1428
+ if (defaultValue === null) {
1429
+ return undefined;
1430
+ }
1431
+ // Check for common patterns
1432
+ if (defaultValue === 'NULL') {
1433
+ return null;
1434
+ }
1435
+ if (defaultValue === 'CURRENT_TIMESTAMP' || defaultValue === 'CURRENT_DATE' || defaultValue === 'CURRENT_TIME') {
1436
+ return defaultValue;
1437
+ }
1438
+ if (/^'.*'$/.test(defaultValue)) {
1439
+ return defaultValue.slice(1, -1);
1440
+ }
1441
+ if (/^-?\d+$/.test(defaultValue)) {
1442
+ return Number.parseInt(defaultValue, 10);
1443
+ }
1444
+ if (/^-?\d+\.\d+$/.test(defaultValue)) {
1445
+ return Number.parseFloat(defaultValue);
1446
+ }
1447
+ return defaultValue;
1448
+ }
1449
+ normalizeReferentialAction(action) {
1450
+ switch(action.toUpperCase()){
1451
+ case 'CASCADE':
1452
+ return 'CASCADE';
1453
+ case 'SET NULL':
1454
+ return 'SET NULL';
1455
+ case 'RESTRICT':
1456
+ return 'RESTRICT';
1457
+ case 'NO ACTION':
1458
+ return 'NO ACTION';
1459
+ default:
1460
+ return undefined;
1461
+ }
1462
+ }
1463
+ }
1464
+
1465
+ class MongoSchemaGenerator extends AbstractDialect {
1466
+ generateCreateTable(entity) {
1467
+ const meta = getMeta(entity);
1468
+ const collectionName = this.resolveTableName(entity, meta);
1469
+ const indexes = [];
1470
+ for(const key in meta.fields){
1471
+ const field = meta.fields[key];
1472
+ if (field.index) {
1473
+ const columnName = this.resolveColumnName(key, field);
1474
+ const indexName = typeof field.index === 'string' ? field.index : `idx_${collectionName}_${columnName}`;
1475
+ indexes.push({
1476
+ name: indexName,
1477
+ columns: [
1478
+ columnName
1479
+ ],
1480
+ unique: !!field.unique
1481
+ });
1482
+ }
1483
+ }
1484
+ return JSON.stringify({
1485
+ action: 'createCollection',
1486
+ name: collectionName,
1487
+ indexes
1488
+ });
1489
+ }
1490
+ generateDropTable(entity) {
1491
+ const meta = getMeta(entity);
1492
+ const collectionName = this.resolveTableName(entity, meta);
1493
+ return JSON.stringify({
1494
+ action: 'dropCollection',
1495
+ name: collectionName
1496
+ });
1497
+ }
1498
+ generateAlterTable(diff) {
1499
+ const statements = [];
1500
+ if (diff.indexesToAdd?.length) {
1501
+ for (const index of diff.indexesToAdd){
1502
+ statements.push(this.generateCreateIndex(diff.tableName, index));
1503
+ }
1504
+ }
1505
+ return statements;
1506
+ }
1507
+ generateAlterTableDown(diff) {
1508
+ const statements = [];
1509
+ if (diff.indexesToAdd?.length) {
1510
+ for (const index of diff.indexesToAdd){
1511
+ statements.push(this.generateDropIndex(diff.tableName, index.name));
1512
+ }
1513
+ }
1514
+ return statements;
1515
+ }
1516
+ generateCreateIndex(tableName, index) {
1517
+ const key = {};
1518
+ for (const col of index.columns){
1519
+ key[col] = 1;
1520
+ }
1521
+ return JSON.stringify({
1522
+ action: 'createIndex',
1523
+ collection: tableName,
1524
+ name: index.name,
1525
+ key,
1526
+ options: {
1527
+ unique: index.unique,
1528
+ name: index.name
1529
+ }
1530
+ });
1531
+ }
1532
+ generateDropIndex(tableName, indexName) {
1533
+ return JSON.stringify({
1534
+ action: 'dropIndex',
1535
+ collection: tableName,
1536
+ name: indexName
1537
+ });
1538
+ }
1539
+ getSqlType() {
1540
+ return '';
1541
+ }
1542
+ diffSchema(entity, currentSchema) {
1543
+ const meta = getMeta(entity);
1544
+ const collectionName = this.resolveTableName(entity, meta);
1545
+ if (!currentSchema) {
1546
+ return {
1547
+ tableName: collectionName,
1548
+ type: 'create'
1549
+ };
1550
+ }
1551
+ const indexesToAdd = [];
1552
+ const existingIndexes = new Set(currentSchema.indexes?.map((i)=>i.name) ?? []);
1553
+ for(const key in meta.fields){
1554
+ const field = meta.fields[key];
1555
+ if (field.index) {
1556
+ const columnName = this.resolveColumnName(key, field);
1557
+ const indexName = typeof field.index === 'string' ? field.index : `idx_${collectionName}_${columnName}`;
1558
+ if (!existingIndexes.has(indexName)) {
1559
+ indexesToAdd.push({
1560
+ name: indexName,
1561
+ columns: [
1562
+ columnName
1563
+ ],
1564
+ unique: !!field.unique
1565
+ });
1566
+ }
1567
+ }
1568
+ }
1569
+ if (indexesToAdd.length === 0) {
1570
+ return undefined;
1571
+ }
1572
+ return {
1573
+ tableName: collectionName,
1574
+ type: 'alter',
1575
+ indexesToAdd
1576
+ };
1577
+ }
1578
+ }
1579
+
1580
+ class MongoSchemaIntrospector {
1581
+ constructor(pool){
1582
+ this.pool = pool;
1583
+ }
1584
+ async getTableSchema(tableName) {
1585
+ const querier = await this.pool.getQuerier();
1586
+ try {
1587
+ const { db } = querier;
1588
+ const collections = await db.listCollections({
1589
+ name: tableName
1590
+ }).toArray();
1591
+ if (collections.length === 0) {
1592
+ return undefined;
1593
+ }
1594
+ // MongoDB doesn't have a fixed schema, but we can look at the indexes
1595
+ const indexes = await db.collection(tableName).indexes();
1596
+ return {
1597
+ name: tableName,
1598
+ columns: [],
1599
+ indexes: indexes.map((idx)=>({
1600
+ name: idx.name,
1601
+ columns: Object.keys(idx.key),
1602
+ unique: !!idx.unique
1603
+ }))
1604
+ };
1605
+ } finally{
1606
+ await querier.release();
1607
+ }
1608
+ }
1609
+ async getTableNames() {
1610
+ const querier = await this.pool.getQuerier();
1611
+ try {
1612
+ const { db } = querier;
1613
+ const collections = await db.listCollections().toArray();
1614
+ return collections.map((c)=>c.name);
1615
+ } finally{
1616
+ await querier.release();
1617
+ }
1618
+ }
1619
+ async tableExists(tableName) {
1620
+ const names = await this.getTableNames();
1621
+ return names.includes(tableName);
1622
+ }
1623
+ }
1624
+
1625
+ /**
1626
+ * Stores migration state in a database table.
1627
+ * Uses the querier's dialect for escaping and placeholders.
1628
+ */ class DatabaseMigrationStorage {
1629
+ constructor(pool, options = {}){
1630
+ this.pool = pool;
1631
+ this.storageInitialized = false;
1632
+ this.tableName = options.tableName ?? 'uql_migrations';
1633
+ }
1634
+ async ensureStorage() {
1635
+ if (this.storageInitialized) {
1636
+ return;
1637
+ }
1638
+ const querier = await this.pool.getQuerier();
1639
+ if (!isSqlQuerier(querier)) {
1640
+ await querier.release();
1641
+ throw new Error('DatabaseMigrationStorage requires a SQL-based querier');
1642
+ }
1643
+ try {
1644
+ await this.createTableIfNotExists(querier);
1645
+ this.storageInitialized = true;
1646
+ } finally{
1647
+ await querier.release();
1648
+ }
1649
+ }
1650
+ async createTableIfNotExists(querier) {
1651
+ const { escapeId } = querier.dialect;
1652
+ const sql = `
1653
+ CREATE TABLE IF NOT EXISTS ${escapeId(this.tableName)} (
1654
+ ${escapeId('name')} VARCHAR(255) PRIMARY KEY,
1655
+ ${escapeId('executed_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1656
+ )
1657
+ `;
1658
+ await querier.run(sql);
1659
+ }
1660
+ async executed() {
1661
+ await this.ensureStorage();
1662
+ const querier = await this.pool.getQuerier();
1663
+ if (!isSqlQuerier(querier)) {
1664
+ await querier.release();
1665
+ throw new Error('DatabaseMigrationStorage requires a SQL-based querier');
1666
+ }
1667
+ try {
1668
+ const { escapeId } = querier.dialect;
1669
+ const sql = `SELECT ${escapeId('name')} FROM ${escapeId(this.tableName)} ORDER BY ${escapeId('name')} ASC`;
1670
+ const results = await querier.all(sql);
1671
+ return results.map((r)=>r.name);
1672
+ } finally{
1673
+ await querier.release();
1674
+ }
1675
+ }
1676
+ /**
1677
+ * Log a migration as executed - uses provided querier (within transaction)
1678
+ */ async logWithQuerier(querier, migrationName) {
1679
+ await this.ensureStorage();
1680
+ const { escapeId, placeholder } = querier.dialect;
1681
+ const sql = `INSERT INTO ${escapeId(this.tableName)} (${escapeId('name')}) VALUES (${placeholder(1)})`;
1682
+ await querier.run(sql, [
1683
+ migrationName
1684
+ ]);
1685
+ }
1686
+ /**
1687
+ * Unlog a migration - uses provided querier (within transaction)
1688
+ */ async unlogWithQuerier(querier, migrationName) {
1689
+ await this.ensureStorage();
1690
+ const { escapeId, placeholder } = querier.dialect;
1691
+ const sql = `DELETE FROM ${escapeId(this.tableName)} WHERE ${escapeId('name')} = ${placeholder(1)}`;
1692
+ await querier.run(sql, [
1693
+ migrationName
1694
+ ]);
1695
+ }
1696
+ }
1697
+
1698
+ /**
1699
+ * Main class for managing database migrations
1700
+ */ class Migrator {
1701
+ constructor(pool, options = {}){
1702
+ this.pool = pool;
1703
+ this.dialect = options.dialect ?? pool.dialect ?? 'postgres';
1704
+ this.storage = options.storage ?? new DatabaseMigrationStorage(pool, {
1705
+ tableName: options.tableName
1706
+ });
1707
+ this.migrationsPath = options.migrationsPath ?? './migrations';
1708
+ this.logger = options.logger ?? (()=>{});
1709
+ this.entities = options.entities ?? [];
1710
+ this.schemaIntrospector = this.createIntrospector();
1711
+ this.schemaGenerator = options.schemaGenerator ?? this.createGenerator(options.namingStrategy);
1712
+ }
1713
+ /**
1714
+ * Set the schema generator for DDL operations
1715
+ */ setSchemaGenerator(generator) {
1716
+ this.schemaGenerator = generator;
1717
+ }
1718
+ createIntrospector() {
1719
+ switch(this.dialect){
1720
+ case 'postgres':
1721
+ return new PostgresSchemaIntrospector(this.pool);
1722
+ case 'mysql':
1723
+ return new MysqlSchemaIntrospector(this.pool);
1724
+ case 'mariadb':
1725
+ return new MariadbSchemaIntrospector(this.pool);
1726
+ case 'sqlite':
1727
+ return new SqliteSchemaIntrospector(this.pool);
1728
+ case 'mongodb':
1729
+ return new MongoSchemaIntrospector(this.pool);
1730
+ default:
1731
+ return undefined;
1732
+ }
1733
+ }
1734
+ createGenerator(namingStrategy) {
1735
+ switch(this.dialect){
1736
+ case 'postgres':
1737
+ return new PostgresSchemaGenerator(namingStrategy);
1738
+ case 'mysql':
1739
+ return new MysqlSchemaGenerator(namingStrategy);
1740
+ case 'mariadb':
1741
+ return new MysqlSchemaGenerator(namingStrategy);
1742
+ case 'sqlite':
1743
+ return new SqliteSchemaGenerator(namingStrategy);
1744
+ case 'mongodb':
1745
+ return new MongoSchemaGenerator(namingStrategy);
1746
+ default:
1747
+ return undefined;
1748
+ }
1749
+ }
1750
+ /**
1751
+ * Get the SQL dialect
1752
+ */ getDialect() {
1753
+ return this.dialect;
1754
+ }
1755
+ /**
1756
+ * Get all discovered migrations from the migrations directory
1757
+ */ async getMigrations() {
1758
+ const files = await this.getMigrationFiles();
1759
+ const migrations = [];
1760
+ for (const file of files){
1761
+ const migration = await this.loadMigration(file);
1762
+ if (migration) {
1763
+ migrations.push(migration);
1764
+ }
1765
+ }
1766
+ // Sort by name (which typically includes timestamp)
1767
+ return migrations.sort((a, b)=>a.name.localeCompare(b.name));
1768
+ }
1769
+ /**
1770
+ * Get list of pending migrations (not yet executed)
1771
+ */ async pending() {
1772
+ const [migrations, executed] = await Promise.all([
1773
+ this.getMigrations(),
1774
+ this.storage.executed()
1775
+ ]);
1776
+ const executedSet = new Set(executed);
1777
+ return migrations.filter((m)=>!executedSet.has(m.name));
1778
+ }
1779
+ /**
1780
+ * Get list of executed migrations
1781
+ */ async executed() {
1782
+ return this.storage.executed();
1783
+ }
1784
+ /**
1785
+ * Run all pending migrations
1786
+ */ async up(options = {}) {
1787
+ const pendingMigrations = await this.pending();
1788
+ const results = [];
1789
+ let migrationsToRun = pendingMigrations;
1790
+ if (options.to) {
1791
+ const toIndex = migrationsToRun.findIndex((m)=>m.name === options.to);
1792
+ if (toIndex === -1) {
1793
+ throw new Error(`Migration '${options.to}' not found`);
1794
+ }
1795
+ migrationsToRun = migrationsToRun.slice(0, toIndex + 1);
1796
+ }
1797
+ if (options.step !== undefined) {
1798
+ migrationsToRun = migrationsToRun.slice(0, options.step);
1799
+ }
1800
+ for (const migration of migrationsToRun){
1801
+ const result = await this.runMigration(migration, 'up');
1802
+ results.push(result);
1803
+ if (!result.success) {
1804
+ break; // Stop on first failure
1805
+ }
1806
+ }
1807
+ return results;
1808
+ }
1809
+ /**
1810
+ * Rollback migrations
1811
+ */ async down(options = {}) {
1812
+ const [migrations, executed] = await Promise.all([
1813
+ this.getMigrations(),
1814
+ this.storage.executed()
1815
+ ]);
1816
+ const executedSet = new Set(executed);
1817
+ const executedMigrations = migrations.filter((m)=>executedSet.has(m.name)).reverse(); // Rollback in reverse order
1818
+ const results = [];
1819
+ let migrationsToRun = executedMigrations;
1820
+ if (options.to) {
1821
+ const toIndex = migrationsToRun.findIndex((m)=>m.name === options.to);
1822
+ if (toIndex === -1) {
1823
+ throw new Error(`Migration '${options.to}' not found`);
1824
+ }
1825
+ migrationsToRun = migrationsToRun.slice(0, toIndex + 1);
1826
+ }
1827
+ if (options.step !== undefined) {
1828
+ migrationsToRun = migrationsToRun.slice(0, options.step);
1829
+ }
1830
+ for (const migration of migrationsToRun){
1831
+ const result = await this.runMigration(migration, 'down');
1832
+ results.push(result);
1833
+ if (!result.success) {
1834
+ break; // Stop on first failure
1835
+ }
1836
+ }
1837
+ return results;
1838
+ }
1839
+ /**
1840
+ * Run a single migration within a transaction
1841
+ */ async runMigration(migration, direction) {
1842
+ const startTime = Date.now();
1843
+ const querier = await this.pool.getQuerier();
1844
+ if (!isSqlQuerier(querier)) {
1845
+ await querier.release();
1846
+ throw new Error('Migrator requires a SQL-based querier');
1847
+ }
1848
+ try {
1849
+ this.logger(`${direction === 'up' ? 'Running' : 'Reverting'} migration: ${migration.name}`);
1850
+ await querier.beginTransaction();
1851
+ if (direction === 'up') {
1852
+ await migration.up(querier);
1853
+ // Log within the same transaction
1854
+ await this.storage.logWithQuerier(querier, migration.name);
1855
+ } else {
1856
+ await migration.down(querier);
1857
+ // Unlog within the same transaction
1858
+ await this.storage.unlogWithQuerier(querier, migration.name);
1859
+ }
1860
+ await querier.commitTransaction();
1861
+ const duration = Date.now() - startTime;
1862
+ this.logger(`Migration ${migration.name} ${direction === 'up' ? 'applied' : 'reverted'} in ${duration}ms`);
1863
+ return {
1864
+ name: migration.name,
1865
+ direction,
1866
+ duration,
1867
+ success: true
1868
+ };
1869
+ } catch (error) {
1870
+ await querier.rollbackTransaction();
1871
+ const duration = Date.now() - startTime;
1872
+ this.logger(`Migration ${migration.name} failed: ${error.message}`);
1873
+ return {
1874
+ name: migration.name,
1875
+ direction,
1876
+ duration,
1877
+ success: false,
1878
+ error: error
1879
+ };
1880
+ } finally{
1881
+ await querier.release();
1882
+ }
1883
+ }
1884
+ /**
1885
+ * Generate a new migration file
1886
+ */ async generate(name) {
1887
+ const timestamp = this.getTimestamp();
1888
+ const fileName = `${timestamp}_${this.slugify(name)}.ts`;
1889
+ const filePath = join(this.migrationsPath, fileName);
1890
+ const content = this.generateMigrationContent(name);
1891
+ const { writeFile, mkdir } = await import('node:fs/promises');
1892
+ await mkdir(this.migrationsPath, {
1893
+ recursive: true
1894
+ });
1895
+ await writeFile(filePath, content, 'utf-8');
1896
+ this.logger(`Created migration: ${filePath}`);
1897
+ return filePath;
1898
+ }
1899
+ /**
1900
+ * Generate a migration based on entity schema differences
1901
+ */ async generateFromEntities(name) {
1902
+ if (!this.schemaGenerator) {
1903
+ throw new Error('Schema generator not set. Call setSchemaGenerator() first.');
1904
+ }
1905
+ const diffs = await this.getDiffs();
1906
+ const upStatements = [];
1907
+ const downStatements = [];
1908
+ for (const diff of diffs){
1909
+ if (diff.type === 'create') {
1910
+ const entity = this.findEntityForTable(diff.tableName);
1911
+ if (entity) {
1912
+ upStatements.push(this.schemaGenerator.generateCreateTable(entity));
1913
+ downStatements.push(this.schemaGenerator.generateDropTable(entity));
1914
+ }
1915
+ } else if (diff.type === 'alter') {
1916
+ const alterStatements = this.schemaGenerator.generateAlterTable(diff);
1917
+ upStatements.push(...alterStatements);
1918
+ const alterDownStatements = this.schemaGenerator.generateAlterTableDown(diff);
1919
+ downStatements.push(...alterDownStatements);
1920
+ }
1921
+ }
1922
+ if (upStatements.length === 0) {
1923
+ this.logger('No schema changes detected.');
1924
+ return '';
1925
+ }
1926
+ const timestamp = this.getTimestamp();
1927
+ const fileName = `${timestamp}_${this.slugify(name)}.ts`;
1928
+ const filePath = join(this.migrationsPath, fileName);
1929
+ const content = this.generateMigrationContentWithStatements(name, upStatements, downStatements.reverse());
1930
+ const { writeFile, mkdir } = await import('node:fs/promises');
1931
+ await mkdir(this.migrationsPath, {
1932
+ recursive: true
1933
+ });
1934
+ await writeFile(filePath, content, 'utf-8');
1935
+ this.logger(`Created migration from entities: ${filePath}`);
1936
+ return filePath;
1937
+ }
1938
+ /**
1939
+ * Get all schema differences between entities and database
1940
+ */ async getDiffs() {
1941
+ if (!this.schemaGenerator || !this.schemaIntrospector) {
1942
+ throw new Error('Schema generator and introspector must be set');
1943
+ }
1944
+ const entities = this.entities.length > 0 ? this.entities : getEntities();
1945
+ const diffs = [];
1946
+ for (const entity of entities){
1947
+ const meta = getMeta(entity);
1948
+ const tableName = this.schemaGenerator.resolveTableName(entity, meta);
1949
+ const currentSchema = await this.schemaIntrospector.getTableSchema(tableName);
1950
+ const diff = this.schemaGenerator.diffSchema(entity, currentSchema);
1951
+ if (diff) {
1952
+ diffs.push(diff);
1953
+ }
1954
+ }
1955
+ return diffs;
1956
+ }
1957
+ findEntityForTable(tableName) {
1958
+ const entities = this.entities.length > 0 ? this.entities : getEntities();
1959
+ for (const entity of entities){
1960
+ const meta = getMeta(entity);
1961
+ const name = this.schemaGenerator.resolveTableName(entity, meta);
1962
+ if (name === tableName) {
1963
+ return entity;
1964
+ }
1965
+ }
1966
+ return undefined;
1967
+ }
1968
+ /**
1969
+ * Sync schema directly (for development only - not for production!)
1970
+ */ async sync(options = {}) {
1971
+ if (options.force) {
1972
+ return this.syncForce();
1973
+ }
1974
+ return this.autoSync({
1975
+ safe: true
1976
+ });
1977
+ }
1978
+ /**
1979
+ * Drops and recreates all tables (Development only!)
1980
+ */ async syncForce() {
1981
+ if (!this.schemaGenerator) {
1982
+ throw new Error('Schema generator not set. Call setSchemaGenerator() first.');
1983
+ }
1984
+ const entities = this.entities.length > 0 ? this.entities : getEntities();
1985
+ const querier = await this.pool.getQuerier();
1986
+ if (!isSqlQuerier(querier)) {
1987
+ await querier.release();
1988
+ throw new Error('Migrator requires a SQL-based querier');
1989
+ }
1990
+ try {
1991
+ await querier.beginTransaction();
1992
+ // Drop all tables first (in reverse order for foreign keys)
1993
+ for (const entity of [
1994
+ ...entities
1995
+ ].reverse()){
1996
+ const dropSql = this.schemaGenerator.generateDropTable(entity);
1997
+ this.logger(`Executing: ${dropSql}`);
1998
+ await querier.run(dropSql);
1999
+ }
2000
+ // Create all tables
2001
+ for (const entity of entities){
2002
+ const createSql = this.schemaGenerator.generateCreateTable(entity);
2003
+ this.logger(`Executing: ${createSql}`);
2004
+ await querier.run(createSql);
2005
+ }
2006
+ await querier.commitTransaction();
2007
+ this.logger('Schema sync (force) completed');
2008
+ } catch (error) {
2009
+ await querier.rollbackTransaction();
2010
+ throw error;
2011
+ } finally{
2012
+ await querier.release();
2013
+ }
2014
+ }
2015
+ /**
2016
+ * Safely synchronizes the schema by only adding missing tables and columns.
2017
+ */ async autoSync(options = {}) {
2018
+ if (!this.schemaGenerator || !this.schemaIntrospector) {
2019
+ throw new Error('Schema generator and introspector must be set');
2020
+ }
2021
+ const diffs = await this.getDiffs();
2022
+ const statements = [];
2023
+ for (const diff of diffs){
2024
+ if (diff.type === 'create') {
2025
+ const entity = this.findEntityForTable(diff.tableName);
2026
+ if (entity) {
2027
+ statements.push(this.schemaGenerator.generateCreateTable(entity));
2028
+ }
2029
+ } else if (diff.type === 'alter') {
2030
+ const filteredDiff = this.filterDiff(diff, options);
2031
+ const alterStatements = this.schemaGenerator.generateAlterTable(filteredDiff);
2032
+ statements.push(...alterStatements);
2033
+ }
2034
+ }
2035
+ if (statements.length === 0) {
2036
+ if (options.logging) this.logger('Schema is already in sync.');
2037
+ return;
2038
+ }
2039
+ await this.executeSyncStatements(statements, options);
2040
+ }
2041
+ filterDiff(diff, options) {
2042
+ const filteredDiff = {
2043
+ ...diff
2044
+ };
2045
+ if (options.safe !== false) {
2046
+ // In safe mode, we only allow additions
2047
+ delete filteredDiff.columnsToDrop;
2048
+ delete filteredDiff.indexesToDrop;
2049
+ delete filteredDiff.foreignKeysToDrop;
2050
+ }
2051
+ if (!options.drop) {
2052
+ delete filteredDiff.columnsToDrop;
2053
+ }
2054
+ return filteredDiff;
2055
+ }
2056
+ async executeSyncStatements(statements, options) {
2057
+ const querier = await this.pool.getQuerier();
2058
+ try {
2059
+ if (this.dialect === 'mongodb') {
2060
+ await this.executeMongoSyncStatements(statements, options, querier);
2061
+ } else {
2062
+ await this.executeSqlSyncStatements(statements, options, querier);
2063
+ }
2064
+ if (options.logging) this.logger('Schema synchronization completed');
2065
+ } catch (error) {
2066
+ if (this.dialect !== 'mongodb' && isSqlQuerier(querier)) {
2067
+ await querier.rollbackTransaction();
2068
+ }
2069
+ throw error;
2070
+ } finally{
2071
+ await querier.release();
2072
+ }
2073
+ }
2074
+ async executeMongoSyncStatements(statements, options, querier) {
2075
+ const db = querier.db;
2076
+ for (const stmt of statements){
2077
+ const cmd = JSON.parse(stmt);
2078
+ if (options.logging) this.logger(`Executing MongoDB: ${stmt}`);
2079
+ const collectionName = cmd.name || cmd.collection;
2080
+ if (!collectionName) {
2081
+ throw new Error(`MongoDB command missing collection name: ${stmt}`);
2082
+ }
2083
+ const collection = db.collection(collectionName);
2084
+ if (cmd.action === 'createCollection') {
2085
+ await db.createCollection(cmd.name);
2086
+ if (cmd.indexes?.length) {
2087
+ for (const idx of cmd.indexes){
2088
+ const key = Object.fromEntries(idx.columns.map((c)=>[
2089
+ c,
2090
+ 1
2091
+ ]));
2092
+ await collection.createIndex(key, {
2093
+ unique: idx.unique,
2094
+ name: idx.name
2095
+ });
2096
+ }
2097
+ }
2098
+ } else if (cmd.action === 'dropCollection') {
2099
+ await collection.drop();
2100
+ } else if (cmd.action === 'createIndex') {
2101
+ await collection.createIndex(cmd.key, cmd.options);
2102
+ } else if (cmd.action === 'dropIndex') {
2103
+ await collection.dropIndex(cmd.name);
2104
+ }
2105
+ }
2106
+ }
2107
+ async executeSqlSyncStatements(statements, options, querier) {
2108
+ if (!isSqlQuerier(querier)) {
2109
+ throw new Error('Migrator requires a SQL-based querier for this dialect');
2110
+ }
2111
+ await querier.beginTransaction();
2112
+ for (const sql of statements){
2113
+ if (options.logging) this.logger(`Executing: ${sql}`);
2114
+ await querier.run(sql);
2115
+ }
2116
+ await querier.commitTransaction();
2117
+ }
2118
+ /**
2119
+ * Get migration status
2120
+ */ async status() {
2121
+ const [pending, executed] = await Promise.all([
2122
+ this.pending().then((m)=>m.map((x)=>x.name)),
2123
+ this.executed()
2124
+ ]);
2125
+ return {
2126
+ pending,
2127
+ executed
2128
+ };
2129
+ }
2130
+ /**
2131
+ * Get migration files from the migrations directory
2132
+ */ async getMigrationFiles() {
2133
+ try {
2134
+ const files = await readdir(this.migrationsPath);
2135
+ return files.filter((f)=>/\.(ts|js|mjs)$/.test(f)).filter((f)=>!f.endsWith('.d.ts')).sort();
2136
+ } catch (error) {
2137
+ if (error.code === 'ENOENT') {
2138
+ return [];
2139
+ }
2140
+ throw error;
2141
+ }
2142
+ }
2143
+ /**
2144
+ * Load a migration from a file
2145
+ */ async loadMigration(fileName) {
2146
+ const filePath = join(this.migrationsPath, fileName);
2147
+ const fileUrl = pathToFileURL(filePath).href;
2148
+ try {
2149
+ const module = await import(fileUrl);
2150
+ const migration = module.default ?? module;
2151
+ if (this.isMigration(migration)) {
2152
+ return {
2153
+ name: this.getMigrationName(fileName),
2154
+ up: migration.up.bind(migration),
2155
+ down: migration.down.bind(migration)
2156
+ };
2157
+ }
2158
+ this.logger(`Warning: ${fileName} is not a valid migration`);
2159
+ return undefined;
2160
+ } catch (error) {
2161
+ this.logger(`Error loading migration ${fileName}: ${error.message}`);
2162
+ return undefined;
2163
+ }
2164
+ }
2165
+ /**
2166
+ * Check if an object is a valid migration
2167
+ */ isMigration(obj) {
2168
+ return typeof obj === 'object' && obj !== undefined && obj !== null && typeof obj.up === 'function' && typeof obj.down === 'function';
2169
+ }
2170
+ /**
2171
+ * Extract migration name from filename
2172
+ */ getMigrationName(fileName) {
2173
+ return basename(fileName, extname(fileName));
2174
+ }
2175
+ /**
2176
+ * Generate timestamp string for migration names
2177
+ */ getTimestamp() {
2178
+ const now = new Date();
2179
+ return [
2180
+ now.getFullYear(),
2181
+ String(now.getMonth() + 1).padStart(2, '0'),
2182
+ String(now.getDate()).padStart(2, '0'),
2183
+ String(now.getHours()).padStart(2, '0'),
2184
+ String(now.getMinutes()).padStart(2, '0'),
2185
+ String(now.getSeconds()).padStart(2, '0')
2186
+ ].join('');
2187
+ }
2188
+ /**
2189
+ * Convert a string to a slug for filenames
2190
+ */ slugify(text) {
2191
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
2192
+ }
2193
+ /**
2194
+ * Generate migration file content
2195
+ */ generateMigrationContent(name) {
2196
+ return /*ts*/ `import type { SqlQuerier } from '@uql/migrate';
2197
+
2198
+ /**
2199
+ * Migration: ${name}
2200
+ * Created: ${new Date().toISOString()}
2201
+ */
2202
+ export default {
2203
+ async up(querier: SqlQuerier): Promise<void> {
2204
+ // Add your migration logic here
2205
+ // Example:
2206
+ // await querier.run(\`
2207
+ // CREATE TABLE "users" (
2208
+ // "id" SERIAL PRIMARY KEY,
2209
+ // "name" VARCHAR(255) NOT NULL,
2210
+ // "email" VARCHAR(255) UNIQUE NOT NULL,
2211
+ // "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
2212
+ // )
2213
+ // \`);
2214
+ },
2215
+
2216
+ async down(querier: SqlQuerier): Promise<void> {
2217
+ // Add your rollback logic here
2218
+ // Example:
2219
+ // await querier.run(\`DROP TABLE IF EXISTS "users"\`);
2220
+ },
2221
+ };
2222
+ `;
2223
+ }
2224
+ /**
2225
+ * Generate migration file content with SQL statements
2226
+ */ generateMigrationContentWithStatements(name, upStatements, downStatements) {
2227
+ const upSql = upStatements.map((s)=>/*ts*/ ` await querier.run(\`${s}\`);`).join('\n');
2228
+ const downSql = downStatements.map((s)=>/*ts*/ ` await querier.run(\`${s}\`);`).join('\n');
2229
+ return /*ts*/ `import type { SqlQuerier } from '@uql/migrate';
2230
+
2231
+ /**
2232
+ * Migration: ${name}
2233
+ * Created: ${new Date().toISOString()}
2234
+ * Generated from entity definitions
2235
+ */
2236
+ export default {
2237
+ async up(querier: SqlQuerier): Promise<void> {
2238
+ ${upSql}
2239
+ },
2240
+
2241
+ async down(querier: SqlQuerier): Promise<void> {
2242
+ ${downSql}
2243
+ },
2244
+ };
2245
+ `;
2246
+ }
2247
+ }
2248
+ /**
2249
+ * Helper function to define a migration with proper typing
2250
+ */ function defineMigration(migration) {
2251
+ return migration;
2252
+ }
2253
+
2254
+ /**
2255
+ * Stores migration state in a JSON file (useful for development/testing)
2256
+ */ class JsonMigrationStorage {
2257
+ constructor(filePath = './migrations/.uql-migrations.json'){
2258
+ this.cache = null;
2259
+ this.filePath = filePath;
2260
+ }
2261
+ async ensureStorage() {
2262
+ try {
2263
+ await this.load();
2264
+ } catch {
2265
+ // File doesn't exist, create it
2266
+ await this.save([]);
2267
+ }
2268
+ }
2269
+ async executed() {
2270
+ await this.ensureStorage();
2271
+ return this.cache ?? [];
2272
+ }
2273
+ async logWithQuerier(_querier, migrationName) {
2274
+ const executed = await this.executed();
2275
+ if (!executed.includes(migrationName)) {
2276
+ executed.push(migrationName);
2277
+ executed.sort();
2278
+ await this.save(executed);
2279
+ }
2280
+ }
2281
+ async unlogWithQuerier(_querier, migrationName) {
2282
+ const executed = await this.executed();
2283
+ const index = executed.indexOf(migrationName);
2284
+ if (index !== -1) {
2285
+ executed.splice(index, 1);
2286
+ await this.save(executed);
2287
+ }
2288
+ }
2289
+ async load() {
2290
+ const content = await readFile(this.filePath, 'utf-8');
2291
+ this.cache = JSON.parse(content);
2292
+ }
2293
+ async save(migrations) {
2294
+ await mkdir(dirname(this.filePath), {
2295
+ recursive: true
2296
+ });
2297
+ await writeFile(this.filePath, JSON.stringify(migrations, null, 2), 'utf-8');
2298
+ this.cache = migrations;
2299
+ }
2300
+ }
2301
+
2302
+ export { AbstractSchemaGenerator, DatabaseMigrationStorage, JsonMigrationStorage, MysqlSchemaGenerator as MariadbSchemaGenerator, MariadbSchemaIntrospector, Migrator, MysqlSchemaGenerator, MysqlSchemaIntrospector, PostgresSchemaGenerator, PostgresSchemaIntrospector, SqliteSchemaGenerator, SqliteSchemaIntrospector, defineMigration };
2303
+ //# sourceMappingURL=uql-browser.min.js.map