uql-orm 0.9.1 → 0.9.2

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.
@@ -5648,1383 +5648,3 @@ const hooks = {
5648
5648
 
5649
5649
  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, _VectorItem as VectorItem, clearTables, createMockQuerierPool, createSpec, createTables, dropTables, loadTsDefaultExportWithJiti };
5650
5650
  //# sourceMappingURL=uql-browser.min.js.map
5651
- ized = defaultValue.toUpperCase();
5652
- if (normalized === 'NULL') {
5653
- return null;
5654
- }
5655
- if (normalized === 'CURRENT_TIMESTAMP' || normalized === 'CURRENT_TIMESTAMP()') {
5656
- return 'CURRENT_TIMESTAMP';
5657
- }
5658
- if (/^-?\d+$/.test(defaultValue)) {
5659
- return Number.parseInt(defaultValue, 10);
5660
- }
5661
- if (/^-?\d+\.\d+$/.test(defaultValue)) {
5662
- return Number.parseFloat(defaultValue);
5663
- }
5664
- if (defaultValue?.startsWith("'") && defaultValue?.endsWith("'")) {
5665
- return defaultValue.slice(1, -1);
5666
- }
5667
- return defaultValue;
5668
- }
5669
- }
5670
- /**
5671
- * Alias for MysqlSchemaIntrospector.
5672
- * MariaDB uses the same information_schema structure as MySQL.
5673
- */ const MariadbSchemaIntrospector = MysqlSchemaIntrospector;
5674
-
5675
- /**
5676
- * PostgreSQL schema introspector
5677
- */ class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
5678
- constructor(pool){
5679
- super(pool.dialect), this.pool = pool;
5680
- }
5681
- // ============================================================================
5682
- // SQL Queries (dialect-specific)
5683
- // ============================================================================
5684
- getTableNamesQuery() {
5685
- return /*sql*/ `
5686
- SELECT table_name
5687
- FROM information_schema.tables
5688
- WHERE table_schema = 'public'
5689
- AND table_type = 'BASE TABLE'
5690
- ORDER BY table_name
5691
- `;
5692
- }
5693
- tableExistsQuery() {
5694
- return /*sql*/ `
5695
- SELECT EXISTS (
5696
- SELECT FROM information_schema.tables
5697
- WHERE table_schema = 'public'
5698
- AND table_name = $1
5699
- ) AS exists
5700
- `;
5701
- }
5702
- parseTableExistsResult(results) {
5703
- return results[0]?.['exists'] ?? false;
5704
- }
5705
- getColumnsQuery(_tableName) {
5706
- return /*sql*/ `
5707
- SELECT
5708
- c.column_name,
5709
- c.data_type,
5710
- c.udt_name,
5711
- c.is_nullable,
5712
- c.column_default,
5713
- c.character_maximum_length,
5714
- c.numeric_precision,
5715
- c.numeric_scale,
5716
- c.is_identity,
5717
- c.identity_generation,
5718
- COALESCE(
5719
- (SELECT TRUE FROM information_schema.table_constraints tc
5720
- JOIN information_schema.key_column_usage kcu
5721
- ON tc.constraint_name = kcu.constraint_name
5722
- WHERE tc.table_name = c.table_name
5723
- AND tc.constraint_type = 'PRIMARY KEY'
5724
- AND kcu.column_name = c.column_name
5725
- LIMIT 1),
5726
- FALSE
5727
- ) AS is_primary_key,
5728
- COALESCE(
5729
- (SELECT TRUE FROM information_schema.table_constraints tc
5730
- JOIN information_schema.key_column_usage kcu
5731
- ON tc.constraint_name = kcu.constraint_name
5732
- WHERE tc.table_name = c.table_name
5733
- AND tc.constraint_type = 'UNIQUE'
5734
- AND kcu.column_name = c.column_name
5735
- LIMIT 1),
5736
- FALSE
5737
- ) AS is_unique,
5738
- pg_catalog.col_description(
5739
- (SELECT oid FROM pg_catalog.pg_class WHERE relname = c.table_name),
5740
- c.ordinal_position
5741
- ) AS column_comment
5742
- FROM information_schema.columns c
5743
- WHERE c.table_schema = 'public'
5744
- AND c.table_name = $1
5745
- ORDER BY c.ordinal_position
5746
- `;
5747
- }
5748
- getIndexesQuery(_tableName) {
5749
- return /*sql*/ `
5750
- SELECT
5751
- i.relname AS index_name,
5752
- array_to_json(array_agg(a.attname ORDER BY k.n)) AS columns,
5753
- ix.indisunique AS is_unique
5754
- FROM pg_class t
5755
- JOIN pg_index ix ON t.oid = ix.indrelid
5756
- JOIN pg_class i ON i.oid = ix.indexrelid
5757
- JOIN pg_namespace n ON n.oid = t.relnamespace
5758
- CROSS JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, n)
5759
- JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
5760
- WHERE t.relname = $1
5761
- AND n.nspname = 'public'
5762
- AND NOT ix.indisprimary
5763
- GROUP BY i.relname, ix.indisunique
5764
- ORDER BY i.relname
5765
- `;
5766
- }
5767
- getForeignKeysQuery(_tableName) {
5768
- return /*sql*/ `
5769
- SELECT
5770
- tc.constraint_name,
5771
- array_to_json(array_agg(kcu.column_name ORDER BY kcu.ordinal_position)) AS columns,
5772
- ccu.table_name AS referenced_table,
5773
- array_to_json(array_agg(ccu.column_name ORDER BY kcu.ordinal_position)) AS referenced_columns,
5774
- rc.delete_rule,
5775
- rc.update_rule
5776
- FROM information_schema.table_constraints tc
5777
- JOIN information_schema.key_column_usage kcu
5778
- ON tc.constraint_name = kcu.constraint_name
5779
- AND tc.table_schema = kcu.table_schema
5780
- JOIN information_schema.constraint_column_usage ccu
5781
- ON ccu.constraint_name = tc.constraint_name
5782
- AND ccu.table_schema = tc.table_schema
5783
- JOIN information_schema.referential_constraints rc
5784
- ON rc.constraint_name = tc.constraint_name
5785
- AND rc.constraint_schema = tc.table_schema
5786
- WHERE tc.constraint_type = 'FOREIGN KEY'
5787
- AND tc.table_name = $1
5788
- AND tc.table_schema = 'public'
5789
- GROUP BY tc.constraint_name, ccu.table_name, rc.delete_rule, rc.update_rule
5790
- ORDER BY tc.constraint_name
5791
- `;
5792
- }
5793
- getPrimaryKeyQuery(_tableName) {
5794
- return /*sql*/ `
5795
- SELECT kcu.column_name
5796
- FROM information_schema.table_constraints tc
5797
- JOIN information_schema.key_column_usage kcu
5798
- ON tc.constraint_name = kcu.constraint_name
5799
- AND tc.table_schema = kcu.table_schema
5800
- WHERE tc.constraint_type = 'PRIMARY KEY'
5801
- AND tc.table_name = $1
5802
- AND tc.table_schema = 'public'
5803
- ORDER BY kcu.ordinal_position
5804
- `;
5805
- }
5806
- // ============================================================================
5807
- // Internal Types
5808
- // ============================================================================
5809
- mapTableNameRow(row) {
5810
- return row.table_name;
5811
- }
5812
- async mapColumnsResult(_querier, _tableName, results) {
5813
- return results.map((row)=>({
5814
- name: row.column_name,
5815
- type: this.normalizeType(row.data_type, row.udt_name),
5816
- nullable: row.is_nullable === 'YES',
5817
- defaultValue: this.parseDefaultValue(row.column_default),
5818
- isPrimaryKey: row.is_primary_key,
5819
- isAutoIncrement: this.isAutoIncrement(row.column_default, row.is_identity),
5820
- isUnique: row.is_unique,
5821
- length: row.character_maximum_length ?? undefined,
5822
- precision: row.numeric_precision ?? undefined,
5823
- scale: row.numeric_scale ?? undefined,
5824
- comment: row.column_comment ?? undefined
5825
- }));
5826
- }
5827
- async mapIndexesResult(_querier, _tableName, results) {
5828
- return results.map((row)=>({
5829
- name: row.index_name,
5830
- columns: row.columns,
5831
- unique: row.is_unique
5832
- }));
5833
- }
5834
- async mapForeignKeysResult(_querier, _tableName, results) {
5835
- return results.map((row)=>({
5836
- name: row.constraint_name,
5837
- columns: row.columns,
5838
- referencedTable: row.referenced_table,
5839
- referencedColumns: row.referenced_columns,
5840
- onDelete: this.normalizeReferentialAction(row.delete_rule),
5841
- onUpdate: this.normalizeReferentialAction(row.update_rule)
5842
- }));
5843
- }
5844
- mapPrimaryKeyResult(results) {
5845
- if (results.length === 0) {
5846
- return undefined;
5847
- }
5848
- return results.map((r)=>r.column_name);
5849
- }
5850
- // ============================================================================
5851
- // PostgreSQL-specific helpers
5852
- // ============================================================================
5853
- normalizeType(dataType, udtName) {
5854
- // Handle user-defined types and arrays
5855
- if (dataType === 'USER-DEFINED') {
5856
- return udtName.toUpperCase();
5857
- }
5858
- if (dataType === 'ARRAY') {
5859
- return `${udtName.replace(/^_/, '').toUpperCase()}[]`;
5860
- }
5861
- return dataType.toUpperCase();
5862
- }
5863
- parseDefaultValue(defaultValue) {
5864
- if (!defaultValue) {
5865
- return undefined;
5866
- }
5867
- // Remove type casting (e.g., ::text, ::character varying, ::text[])
5868
- const cleaned = defaultValue.replace(/::[a-z_]+(\s+[a-z_]+)?(\[\])?/gi, '').trim();
5869
- if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
5870
- return cleaned.slice(1, -1);
5871
- }
5872
- if (cleaned === 'true' || cleaned === 'false') {
5873
- return cleaned === 'true';
5874
- }
5875
- if (cleaned === 'NULL') {
5876
- return null;
5877
- }
5878
- if (/^-?\d+$/.test(cleaned)) {
5879
- return Number.parseInt(cleaned, 10);
5880
- }
5881
- if (/^-?\d+\.\d+$/.test(cleaned)) {
5882
- return Number.parseFloat(cleaned);
5883
- }
5884
- // Return cleaned value for functions like CURRENT_TIMESTAMP, nextval(), etc.
5885
- return cleaned;
5886
- }
5887
- isAutoIncrement(columnDefault, isIdentity) {
5888
- // PostgreSQL identity columns (GENERATED ... AS IDENTITY)
5889
- if (isIdentity === 'YES') {
5890
- return true;
5891
- }
5892
- // Serial/bigserial columns use nextval()
5893
- return columnDefault?.includes('nextval(') ?? false;
5894
- }
5895
- }
5896
-
5897
- /**
5898
- * SQLite schema introspector
5899
- */ class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
5900
- constructor(pool){
5901
- super(pool.dialect), this.pool = pool;
5902
- }
5903
- // ============================================================================
5904
- // SQL Queries (dialect-specific)
5905
- // ============================================================================
5906
- getTableNamesQuery() {
5907
- return /*sql*/ `
5908
- SELECT name
5909
- FROM sqlite_master
5910
- WHERE type = 'table'
5911
- AND name NOT LIKE 'sqlite_%'
5912
- ORDER BY name
5913
- `;
5914
- }
5915
- tableExistsQuery() {
5916
- return /*sql*/ `
5917
- SELECT COUNT(*) as count
5918
- FROM sqlite_master
5919
- WHERE type = 'table'
5920
- AND name = ?
5921
- `;
5922
- }
5923
- parseTableExistsResult(results) {
5924
- const row = results[0];
5925
- if (row?.count !== undefined) {
5926
- return (this.toNumber(row.count) ?? 0) > 0;
5927
- }
5928
- return false;
5929
- }
5930
- // SQLite uses PRAGMA which doesn't use parameterized queries in the same way
5931
- getColumnsQuery(tableName) {
5932
- return `PRAGMA table_info(${this.escapeId(tableName)})`;
5933
- }
5934
- getIndexesQuery(tableName) {
5935
- return `PRAGMA index_list(${this.escapeId(tableName)})`;
5936
- }
5937
- getForeignKeysQuery(tableName) {
5938
- return `PRAGMA foreign_key_list(${this.escapeId(tableName)})`;
5939
- }
5940
- getPrimaryKeyQuery(tableName) {
5941
- return `PRAGMA table_info(${this.escapeId(tableName)})`;
5942
- }
5943
- getColumnsParams(_tableName) {
5944
- return [];
5945
- }
5946
- getIndexesParams(_tableName) {
5947
- return [];
5948
- }
5949
- getForeignKeysParams(_tableName) {
5950
- return [];
5951
- }
5952
- getPrimaryKeyParams(_tableName) {
5953
- return [];
5954
- }
5955
- // ============================================================================
5956
- // Row Mapping (dialect-specific)
5957
- // ============================================================================
5958
- mapTableNameRow(row) {
5959
- return row.name;
5960
- }
5961
- async mapColumnsResult(querier, tableName, results) {
5962
- // Get unique columns from indexes
5963
- const uniqueColumns = await this.getUniqueColumns(querier, tableName);
5964
- return results.map((row)=>({
5965
- name: row.name,
5966
- type: this.normalizeType(row.type),
5967
- nullable: row.notnull === 0,
5968
- defaultValue: this.parseDefaultValue(row.dflt_value),
5969
- isPrimaryKey: row.pk > 0,
5970
- isAutoIncrement: row.pk > 0 && row.type.toUpperCase() === 'INTEGER',
5971
- isUnique: uniqueColumns.has(row.name),
5972
- length: this.extractLength(row.type),
5973
- precision: undefined,
5974
- scale: undefined,
5975
- comment: undefined
5976
- }));
5977
- }
5978
- async mapIndexesResult(querier, _tableName, results) {
5979
- const indexSchemas = [];
5980
- for (const index of results){
5981
- const columns = await querier.all(`PRAGMA index_info(${this.escapeId(index.name)})`);
5982
- // Include user-created indexes ('c') and multi-column unique constraints ('u')
5983
- // Skip primary key indexes ('pk') and single-column unique constraints
5984
- const isUserCreated = index.origin === 'c';
5985
- const isCompositeUnique = index.origin === 'u' && columns.length > 1;
5986
- if (isUserCreated || isCompositeUnique) {
5987
- indexSchemas.push({
5988
- name: index.name,
5989
- columns: columns.map((c)=>c.name),
5990
- unique: Boolean(index.unique)
5991
- });
5992
- }
5993
- }
5994
- return indexSchemas;
5995
- }
5996
- async mapForeignKeysResult(_querier, tableName, results) {
5997
- // Group by id to handle composite foreign keys
5998
- const grouped = new Map();
5999
- for (const row of results){
6000
- const id = row.id;
6001
- const existing = grouped.get(id) ?? [];
6002
- existing.push(row);
6003
- grouped.set(id, existing);
6004
- }
6005
- return Array.from(grouped.entries()).map(([id, rows])=>{
6006
- const first = rows[0];
6007
- return {
6008
- name: `fk_${tableName}_${id}`,
6009
- columns: rows.map((r)=>r.from),
6010
- referencedTable: first.table,
6011
- referencedColumns: rows.map((r)=>r.to),
6012
- onDelete: this.normalizeReferentialAction(first.on_delete),
6013
- onUpdate: this.normalizeReferentialAction(first.on_update)
6014
- };
6015
- });
6016
- }
6017
- mapPrimaryKeyResult(results) {
6018
- const pkColumns = results.filter((r)=>r.pk > 0).sort((a, b)=>a.pk - b.pk);
6019
- if (pkColumns.length === 0) {
6020
- return undefined;
6021
- }
6022
- return pkColumns.map((r)=>r.name);
6023
- }
6024
- // ============================================================================
6025
- // SQLite-specific helpers
6026
- // ============================================================================
6027
- async getUniqueColumns(querier, tableName) {
6028
- const results = await querier.all(`PRAGMA index_list(${this.escapeId(tableName)})`);
6029
- const uniqueColumns = new Set();
6030
- for (const index of results){
6031
- if (index.unique) {
6032
- const indexInfo = await querier.all(`PRAGMA index_info(${this.escapeId(index.name)})`);
6033
- // Only single-column unique constraints
6034
- if (indexInfo.length === 1) {
6035
- uniqueColumns.add(indexInfo[0].name);
6036
- }
6037
- }
6038
- }
6039
- return uniqueColumns;
6040
- }
6041
- normalizeType(type) {
6042
- // Extract base type without length/precision
6043
- const match = type.match(/^([A-Za-z]+)/);
6044
- return match ? match[1].toUpperCase() : type.toUpperCase();
6045
- }
6046
- extractLength(type) {
6047
- const match = type.match(/\((\d+)\)/);
6048
- return match ? Number.parseInt(match[1], 10) : undefined;
6049
- }
6050
- parseDefaultValue(defaultValue) {
6051
- if (defaultValue === null) {
6052
- return undefined;
6053
- }
6054
- if (defaultValue === 'NULL') {
6055
- return null;
6056
- }
6057
- if (defaultValue === 'CURRENT_TIMESTAMP' || defaultValue === 'CURRENT_DATE' || defaultValue === 'CURRENT_TIME') {
6058
- return defaultValue;
6059
- }
6060
- if (/^'.*'$/.test(defaultValue)) {
6061
- return defaultValue.slice(1, -1);
6062
- }
6063
- if (/^-?\d+$/.test(defaultValue)) {
6064
- return Number.parseInt(defaultValue, 10);
6065
- }
6066
- if (typeof defaultValue !== 'string') {
6067
- return defaultValue;
6068
- }
6069
- if (/^-?\d+\.\d+$/.test(defaultValue)) {
6070
- return Number.parseFloat(defaultValue);
6071
- }
6072
- const upper = defaultValue.toUpperCase();
6073
- if (upper === 'TRUE') return 1;
6074
- if (upper === 'FALSE') return 0;
6075
- return defaultValue;
6076
- }
6077
- }
6078
-
6079
- /**
6080
- * Schema AST Module
6081
- *
6082
- * Provides a unified graph representation of database schema for:
6083
- * - Schema diffing and migration generation
6084
- * - Entity code generation from database
6085
- * - Drift detection
6086
- * - Smart relation inference
6087
- */ // Canonical type utilities
6088
- /**
6089
- * Introspect the database and build a SchemaAST from it.
6090
- *
6091
- * @param introspector - The schema introspector to use
6092
- * @returns The SchemaAST representing the database schema
6093
- */ async function introspectSchema(introspector) {
6094
- return introspector.introspect();
6095
- }
6096
-
6097
- /**
6098
- * Async factory for schema generators. Use this for MongoDB so the optional peer
6099
- * `mongodb` is only loaded when this path runs. SQL dialects delegate to {@link createSchemaGenerator}.
6100
- */ async function createSchemaGeneratorAsync(dialect, namingStrategy, defaultForeignKeyAction) {
6101
- if (dialect.dialectName === 'mongodb') {
6102
- const { MongoSchemaGenerator } = await import('./mongoSchemaGenerator-BT2_u1wI.js');
6103
- return new MongoSchemaGenerator(namingStrategy, defaultForeignKeyAction);
6104
- }
6105
- return createSchemaGenerator(dialect, namingStrategy, defaultForeignKeyAction);
6106
- }
6107
-
6108
- /**
6109
- * Stores migration state in a database table.
6110
- * Uses the querier's dialect for escaping and placeholders.
6111
- */ class DatabaseMigrationStorage {
6112
- constructor(pool, options = {}){
6113
- this.pool = pool;
6114
- this.storageInitialized = false;
6115
- this.tableName = options.tableName ?? 'uql_migrations';
6116
- }
6117
- async ensureStorage() {
6118
- if (this.storageInitialized) {
6119
- return;
6120
- }
6121
- const querier = await acquireQuerierForMigrations(this.pool);
6122
- if (!isSqlQuerier(querier)) {
6123
- await querier.release();
6124
- throw new Error('DatabaseMigrationStorage requires a SQL-based querier');
6125
- }
6126
- try {
6127
- await this.createTableIfNotExists(querier);
6128
- this.storageInitialized = true;
6129
- } finally{
6130
- await querier.release();
6131
- }
6132
- }
6133
- async createTableIfNotExists(querier) {
6134
- const { dialect } = querier;
6135
- const sql = `
6136
- CREATE TABLE IF NOT EXISTS ${dialect.escapeId(this.tableName)} (
6137
- ${dialect.escapeId('name')} VARCHAR(255) PRIMARY KEY,
6138
- ${dialect.escapeId('executed_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP
6139
- )
6140
- `;
6141
- await querier.run(sql);
6142
- }
6143
- async executed() {
6144
- await this.ensureStorage();
6145
- const querier = await acquireQuerierForMigrations(this.pool);
6146
- if (!isSqlQuerier(querier)) {
6147
- await querier.release();
6148
- throw new Error('DatabaseMigrationStorage requires a SQL-based querier');
6149
- }
6150
- try {
6151
- const { dialect } = querier;
6152
- const sql = `SELECT ${dialect.escapeId('name')} FROM ${dialect.escapeId(this.tableName)} ORDER BY ${dialect.escapeId('name')} ASC`;
6153
- const results = await querier.all(sql);
6154
- return results.map((r)=>r.name);
6155
- } finally{
6156
- await querier.release();
6157
- }
6158
- }
6159
- /**
6160
- * Log a migration as executed - uses provided querier (within transaction)
6161
- */ async logWithQuerier(querier, migrationName) {
6162
- await this.ensureStorage();
6163
- const { dialect } = querier;
6164
- const sql = `INSERT INTO ${dialect.escapeId(this.tableName)} (${dialect.escapeId('name')}) VALUES (${dialect.placeholder(1)})`;
6165
- await querier.run(sql, [
6166
- migrationName
6167
- ]);
6168
- }
6169
- /**
6170
- * Unlog a migration - uses provided querier (within transaction)
6171
- */ async unlogWithQuerier(querier, migrationName) {
6172
- await this.ensureStorage();
6173
- const { dialect } = querier;
6174
- const sql = `DELETE FROM ${dialect.escapeId(this.tableName)} WHERE ${dialect.escapeId('name')} = ${dialect.placeholder(1)}`;
6175
- await querier.run(sql, [
6176
- migrationName
6177
- ]);
6178
- }
6179
- }
6180
-
6181
- /**
6182
- * Main class for managing database migrations
6183
- */ class Migrator {
6184
- get logger() {
6185
- return this._logger;
6186
- }
6187
- set logger(value) {
6188
- this._logger = new LoggerWrapper$1(value);
6189
- }
6190
- get entities() {
6191
- return this._entities ?? getEntities();
6192
- }
6193
- constructor(pool, options = {}){
6194
- this.pool = pool;
6195
- this.dialectName = pool.dialect.dialectName ?? 'postgres';
6196
- this._namingStrategy = options.namingStrategy;
6197
- this.storage = options.storage ?? new DatabaseMigrationStorage(pool, {
6198
- tableName: options.tableName
6199
- });
6200
- this.migrationsPath = options.migrationsPath ?? './migrations';
6201
- this._logger = new LoggerWrapper$1(options.logger, options.slowQuery);
6202
- this._entities = options.entities;
6203
- this.schemaIntrospector = this.createIntrospector();
6204
- this.schemaGenerator = options.schemaGenerator ?? (this.dialectName === 'mongodb' ? undefined : this.createGenerator(options.namingStrategy));
6205
- }
6206
- /** Loads MongoDB schema generator on first use; SQL generators are set in the constructor (or via {@link setSchemaGenerator}). */ async ensureSchemaGenerator() {
6207
- if (this.schemaGenerator || this.dialectName !== 'mongodb') {
6208
- return;
6209
- }
6210
- if (!this._mongoSchemaLoadPromise) {
6211
- this._mongoSchemaLoadPromise = createSchemaGeneratorAsync(this.pool.dialect, this._namingStrategy).then((gen)=>{
6212
- if (gen) {
6213
- this.schemaGenerator = gen;
6214
- }
6215
- });
6216
- }
6217
- await this._mongoSchemaLoadPromise;
6218
- }
6219
- /**
6220
- * Set the schema generator for DDL operations
6221
- */ setSchemaGenerator(generator) {
6222
- this.schemaGenerator = generator;
6223
- }
6224
- createIntrospector() {
6225
- const d = this.dialectName;
6226
- if (!isKnownMigratorDialect(d)) {
6227
- return undefined;
6228
- }
6229
- switch(d){
6230
- case 'postgres':
6231
- case 'cockroachdb':
6232
- return new PostgresSchemaIntrospector(this.pool);
6233
- case 'mysql':
6234
- case 'mariadb':
6235
- return new MysqlSchemaIntrospector(this.pool);
6236
- case 'sqlite':
6237
- return new SqliteSchemaIntrospector(this.pool);
6238
- case 'mongodb':
6239
- return new MongoSchemaIntrospector(this.pool);
6240
- default:
6241
- return undefined;
6242
- }
6243
- }
6244
- createGenerator(namingStrategy) {
6245
- if (!isKnownMigratorDialect(this.dialectName)) {
6246
- return undefined;
6247
- }
6248
- return createSchemaGenerator(this.pool.dialect);
6249
- }
6250
- /**
6251
- * Get all discovered migrations from the migrations directory
6252
- */ async getMigrations() {
6253
- const files = await this.getMigrationFiles();
6254
- const migrations = [];
6255
- for (const file of files){
6256
- const migration = await this.loadMigration(file);
6257
- if (migration) {
6258
- migrations.push(migration);
6259
- }
6260
- }
6261
- // Sort by name (which typically includes timestamp)
6262
- return migrations.sort((a, b)=>a.name.localeCompare(b.name));
6263
- }
6264
- /**
6265
- * Get list of pending migrations (not yet executed)
6266
- */ async pending() {
6267
- const [migrations, executed] = await Promise.all([
6268
- this.getMigrations(),
6269
- this.storage.executed()
6270
- ]);
6271
- const executedSet = new Set(executed);
6272
- return migrations.filter((m)=>!executedSet.has(m.name));
6273
- }
6274
- /**
6275
- * Get list of executed migrations
6276
- */ async executed() {
6277
- return this.storage.executed();
6278
- }
6279
- /**
6280
- * Run all pending migrations
6281
- */ async up(options = {}) {
6282
- const pendingMigrations = await this.pending();
6283
- const results = [];
6284
- let migrationsToRun = pendingMigrations;
6285
- if (options.to) {
6286
- const toIndex = migrationsToRun.findIndex((m)=>m.name === options.to);
6287
- if (toIndex === -1) {
6288
- throw new Error(`Migration '${options.to}' not found`);
6289
- }
6290
- migrationsToRun = migrationsToRun.slice(0, toIndex + 1);
6291
- }
6292
- if (options.step !== undefined) {
6293
- migrationsToRun = migrationsToRun.slice(0, options.step);
6294
- }
6295
- for (const migration of migrationsToRun){
6296
- const result = await this.runMigration(migration, 'up');
6297
- results.push(result);
6298
- if (!result.success) {
6299
- break; // Stop on first failure
6300
- }
6301
- }
6302
- return results;
6303
- }
6304
- /**
6305
- * Rollback migrations
6306
- */ async down(options = {}) {
6307
- const [migrations, executed] = await Promise.all([
6308
- this.getMigrations(),
6309
- this.storage.executed()
6310
- ]);
6311
- const executedSet = new Set(executed);
6312
- const executedMigrations = migrations.filter((m)=>executedSet.has(m.name)).reverse(); // Rollback in reverse order
6313
- const results = [];
6314
- let migrationsToRun = executedMigrations;
6315
- if (options.to) {
6316
- const toIndex = migrationsToRun.findIndex((m)=>m.name === options.to);
6317
- if (toIndex === -1) {
6318
- throw new Error(`Migration '${options.to}' not found`);
6319
- }
6320
- migrationsToRun = migrationsToRun.slice(0, toIndex + 1);
6321
- }
6322
- if (options.step !== undefined) {
6323
- migrationsToRun = migrationsToRun.slice(0, options.step);
6324
- }
6325
- for (const migration of migrationsToRun){
6326
- const result = await this.runMigration(migration, 'down');
6327
- results.push(result);
6328
- if (!result.success) {
6329
- break; // Stop on first failure
6330
- }
6331
- }
6332
- return results;
6333
- }
6334
- /**
6335
- * Run a single migration within a transaction
6336
- */ async runMigration(migration, direction) {
6337
- const startTime = Date.now();
6338
- const querier = await acquireQuerierForMigrations(this.pool);
6339
- if (!isSqlQuerier(querier)) {
6340
- await querier.release();
6341
- throw new Error('Migrator requires a SQL-based querier');
6342
- }
6343
- try {
6344
- this.logger.logMigration(`${direction === 'up' ? 'Running' : 'Reverting'} migration: ${migration.name}`);
6345
- await querier.beginTransaction();
6346
- if (direction === 'up') {
6347
- await migration.up(querier);
6348
- // Log within the same transaction
6349
- await this.storage.logWithQuerier(querier, migration.name);
6350
- } else {
6351
- await migration.down(querier);
6352
- // Unlog within the same transaction
6353
- await this.storage.unlogWithQuerier(querier, migration.name);
6354
- }
6355
- await querier.commitTransaction();
6356
- const duration = Date.now() - startTime;
6357
- this.logger.logMigration(`Migration ${migration.name} ${direction === 'up' ? 'applied' : 'reverted'} in ${duration}ms`);
6358
- return {
6359
- name: migration.name,
6360
- direction,
6361
- duration,
6362
- success: true
6363
- };
6364
- } catch (error) {
6365
- await querier.rollbackTransaction();
6366
- const duration = Date.now() - startTime;
6367
- this.logger.logError(`Migration ${migration.name} failed: ${error.message}`, error);
6368
- return {
6369
- name: migration.name,
6370
- direction,
6371
- duration,
6372
- success: false,
6373
- error: error
6374
- };
6375
- } finally{
6376
- await querier.release();
6377
- }
6378
- }
6379
- /**
6380
- * Generate a new migration file
6381
- */ async generate(name) {
6382
- const timestamp = this.getTimestamp();
6383
- const fileName = `${timestamp}_${this.slugify(name)}.ts`;
6384
- const filePath = join(this.migrationsPath, fileName);
6385
- const content = buildSqlQuerierMigrationModule({
6386
- migrationName: name,
6387
- createdAt: new Date(),
6388
- upInner: EMPTY_MANUAL_MIGRATION_UP_INNER,
6389
- downInner: EMPTY_MANUAL_MIGRATION_DOWN_INNER
6390
- });
6391
- await mkdir(this.migrationsPath, {
6392
- recursive: true
6393
- });
6394
- await writeFile(filePath, content, 'utf-8');
6395
- this.logger.logInfo(`Created migration: ${filePath}`);
6396
- return filePath;
6397
- }
6398
- /**
6399
- * Generate a migration based on entity schema differences
6400
- */ async generateFromEntities(name) {
6401
- await this.ensureSchemaGenerator();
6402
- if (!this.schemaGenerator) {
6403
- throw new Error('Schema generator not set. Call setSchemaGenerator() first.');
6404
- }
6405
- const diffs = await this.getDiffs();
6406
- const upStatements = [];
6407
- const downStatements = [];
6408
- for (const diff of diffs){
6409
- if (diff.type === 'create') {
6410
- const entity = await this.findEntityForTable(diff.tableName);
6411
- if (entity) {
6412
- upStatements.push(...this.schemaGenerator.generateCreateTable(entity));
6413
- downStatements.push(this.schemaGenerator.generateDropTable(entity));
6414
- }
6415
- } else if (diff.type === 'alter') {
6416
- const alterStatements = this.schemaGenerator.generateAlterTable(diff);
6417
- upStatements.push(...alterStatements);
6418
- const alterDownStatements = this.schemaGenerator.generateAlterTableDown(diff);
6419
- downStatements.push(...alterDownStatements);
6420
- }
6421
- }
6422
- if (upStatements.length === 0) {
6423
- this.logger.logInfo('No schema changes detected.');
6424
- return '';
6425
- }
6426
- const timestamp = this.getTimestamp();
6427
- const fileName = `${timestamp}_${this.slugify(name)}.ts`;
6428
- const filePath = join(this.migrationsPath, fileName);
6429
- const down = [
6430
- ...downStatements
6431
- ].reverse();
6432
- const content = buildSqlQuerierMigrationModule({
6433
- migrationName: name,
6434
- createdAt: new Date(),
6435
- docExtraLines: [
6436
- 'Generated from entity definitions'
6437
- ],
6438
- upInner: emitSqlRunCalls(upStatements),
6439
- downInner: emitSqlRunCalls(down)
6440
- });
6441
- await mkdir(this.migrationsPath, {
6442
- recursive: true
6443
- });
6444
- await writeFile(filePath, content, 'utf-8');
6445
- this.logger.logInfo(`Created migration from entities: ${filePath}`);
6446
- return filePath;
6447
- }
6448
- /**
6449
- * Get all schema differences between entities and database
6450
- */ async getDiffs() {
6451
- await this.ensureSchemaGenerator();
6452
- if (!this.schemaGenerator || !this.schemaIntrospector) {
6453
- throw new Error('Schema generator and introspector must be set');
6454
- }
6455
- const ast = await introspectSchema(this.schemaIntrospector);
6456
- const diffs = [];
6457
- for (const entity of this.entities){
6458
- const meta = getMeta$1(entity);
6459
- const tableName = this.schemaGenerator.resolveTableName(entity, meta);
6460
- const currentTable = ast.getTable(tableName);
6461
- const diff = this.schemaGenerator.diffSchema(entity, currentTable);
6462
- if (diff) {
6463
- diffs.push(diff);
6464
- }
6465
- }
6466
- return diffs;
6467
- }
6468
- async findEntityForTable(tableName) {
6469
- await this.ensureSchemaGenerator();
6470
- if (!this.schemaGenerator) {
6471
- return undefined;
6472
- }
6473
- for (const entity of this.entities){
6474
- const meta = getMeta$1(entity);
6475
- const name = this.schemaGenerator.resolveTableName(entity, meta);
6476
- if (name === tableName) {
6477
- return entity;
6478
- }
6479
- }
6480
- return undefined;
6481
- }
6482
- /**
6483
- * Sync schema directly (for development only - not for production!)
6484
- */ async sync(options = {}) {
6485
- if (options.force) {
6486
- return this.syncForce();
6487
- }
6488
- return this.autoSync({
6489
- safe: true
6490
- });
6491
- }
6492
- /**
6493
- * Drops and recreates all tables (Development only!)
6494
- */ async syncForce() {
6495
- await this.ensureSchemaGenerator();
6496
- if (!this.schemaGenerator) {
6497
- throw new Error('Schema generator not set. Call setSchemaGenerator() first.');
6498
- }
6499
- const querier = await acquireQuerierForMigrations(this.pool);
6500
- if (!isSqlQuerier(querier)) {
6501
- await querier.release();
6502
- throw new Error('Migrator requires a SQL-based querier');
6503
- }
6504
- try {
6505
- await querier.beginTransaction();
6506
- // Drop all tables first (in reverse order for foreign keys)
6507
- for (const entity of [
6508
- ...this.entities
6509
- ].reverse()){
6510
- const dropSql = this.schemaGenerator.generateDropTable(entity);
6511
- this.logger.logSchema(`Executing: ${dropSql}`);
6512
- await querier.run(dropSql);
6513
- }
6514
- // Create all tables
6515
- for (const entity of this.entities){
6516
- const createStmts = this.schemaGenerator.generateCreateTable(entity);
6517
- for (const createSql of createStmts){
6518
- this.logger.logSchema(`Executing: ${createSql}`);
6519
- await querier.run(createSql);
6520
- }
6521
- }
6522
- await querier.commitTransaction();
6523
- this.logger.logSchema('Schema sync (force) completed');
6524
- } catch (error) {
6525
- await querier.rollbackTransaction();
6526
- throw error;
6527
- } finally{
6528
- await querier.release();
6529
- }
6530
- }
6531
- /**
6532
- * Safely synchronizes the schema by only adding missing tables and columns.
6533
- */ async autoSync(options = {}) {
6534
- await this.ensureSchemaGenerator();
6535
- if (!this.schemaGenerator || !this.schemaIntrospector) {
6536
- throw new Error('Schema generator and introspector must be set');
6537
- }
6538
- const diffs = await this.getDiffs();
6539
- const statements = [];
6540
- for (const diff of diffs){
6541
- if (diff.type === 'create') {
6542
- const entity = await this.findEntityForTable(diff.tableName);
6543
- if (entity) {
6544
- statements.push(...this.schemaGenerator.generateCreateTable(entity));
6545
- }
6546
- } else if (diff.type === 'alter') {
6547
- const filteredDiff = this.filterDiff(diff, options);
6548
- const alterStatements = this.schemaGenerator.generateAlterTable(filteredDiff);
6549
- statements.push(...alterStatements);
6550
- }
6551
- }
6552
- if (statements.length === 0) {
6553
- if (options.logging) this.logger.logSchema('Schema is already in sync.');
6554
- return;
6555
- }
6556
- await this.executeSyncStatements(statements, options);
6557
- }
6558
- filterDiff(diff, options) {
6559
- const filteredDiff = {
6560
- ...diff
6561
- };
6562
- if (options.safe !== false) {
6563
- // In safe mode, we only allow additions (creating tables/columns)
6564
- // We block drops and alterations to prevent accidental data loss
6565
- if (filteredDiff.columnsToDrop?.length) {
6566
- this.logger.logSkippedMigration(`[AutoSync] Skipped dropping ${filteredDiff.columnsToDrop.length} columns in table '${diff.tableName}': ${filteredDiff.columnsToDrop.join(', ')} (safe mode active)`);
6567
- delete filteredDiff.columnsToDrop;
6568
- }
6569
- if (filteredDiff.columnsToAlter?.length) {
6570
- this.logger.logSkippedMigration(`[AutoSync] Skipped altering ${filteredDiff.columnsToAlter.length} columns in table '${diff.tableName}': ${filteredDiff.columnsToAlter.map((c)=>c.to.name).join(', ')} (safe mode active). Use a migration or { safe: false } to apply.`);
6571
- delete filteredDiff.columnsToAlter;
6572
- }
6573
- delete filteredDiff.indexesToDrop;
6574
- delete filteredDiff.foreignKeysToDrop;
6575
- }
6576
- if (!options.drop && filteredDiff.columnsToDrop?.length) {
6577
- this.logger.logSkippedMigration(`[AutoSync] Skipped dropping ${filteredDiff.columnsToDrop.length} columns in table '${diff.tableName}' (drop: false). Use { drop: true } to apply.`);
6578
- delete filteredDiff.columnsToDrop;
6579
- }
6580
- return filteredDiff;
6581
- }
6582
- async executeSyncStatements(statements, options) {
6583
- const querier = await acquireQuerierForMigrations(this.pool);
6584
- try {
6585
- if (this.dialectName === 'mongodb') {
6586
- await this.executeMongoSyncStatements(statements, options, querier);
6587
- } else {
6588
- await this.executeSqlSyncStatements(statements, options, querier);
6589
- }
6590
- if (options.logging) this.logger.logSchema('Schema synchronization completed');
6591
- } catch (error) {
6592
- if (this.dialectName !== 'mongodb' && isSqlQuerier(querier)) {
6593
- await querier.rollbackTransaction();
6594
- }
6595
- throw error;
6596
- } finally{
6597
- await querier.release();
6598
- }
6599
- }
6600
- async executeMongoSyncStatements(statements, options, querier) {
6601
- const db = querier.db;
6602
- for (const stmt of statements){
6603
- const cmd = JSON.parse(stmt);
6604
- if (options.logging) this.logger.logSchema(`Executing MongoDB: ${stmt}`);
6605
- const collectionName = cmd.name || cmd.collection;
6606
- if (!collectionName) {
6607
- throw new Error(`MongoDB command missing collection name: ${stmt}`);
6608
- }
6609
- const collection = db.collection(collectionName);
6610
- if (cmd.action === 'createCollection') {
6611
- await db.createCollection(cmd.name);
6612
- if (cmd.indexes?.length) {
6613
- for (const idx of cmd.indexes){
6614
- const key = Object.fromEntries(idx.columns.map((c)=>[
6615
- c,
6616
- 1
6617
- ]));
6618
- await collection.createIndex(key, {
6619
- unique: idx.unique,
6620
- name: idx.name
6621
- });
6622
- }
6623
- }
6624
- } else if (cmd.action === 'dropCollection') {
6625
- await collection.drop();
6626
- } else if (cmd.action === 'createIndex') {
6627
- await collection.createIndex(cmd.key, cmd.options);
6628
- } else if (cmd.action === 'dropIndex') {
6629
- await collection.dropIndex(cmd.name);
6630
- }
6631
- }
6632
- }
6633
- async executeSqlSyncStatements(statements, options, querier) {
6634
- if (!isSqlQuerier(querier)) {
6635
- throw new Error('Migrator requires a SQL-based querier for this dialect');
6636
- }
6637
- await querier.beginTransaction();
6638
- for (const sql of statements){
6639
- if (options.logging) this.logger.logSchema(`Executing: ${sql}`);
6640
- await querier.run(sql);
6641
- }
6642
- await querier.commitTransaction();
6643
- }
6644
- /**
6645
- * Get migration status
6646
- */ async status() {
6647
- const [pending, executed] = await Promise.all([
6648
- this.pending().then((m)=>m.map((x)=>x.name)),
6649
- this.executed()
6650
- ]);
6651
- return {
6652
- pending,
6653
- executed
6654
- };
6655
- }
6656
- /**
6657
- * Get migration files from the migrations directory
6658
- */ async getMigrationFiles() {
6659
- try {
6660
- const files = await readdir(this.migrationsPath);
6661
- return files.filter((f)=>/\.(ts|js|mjs)$/.test(f)).filter((f)=>!f.endsWith('.d.ts')).sort();
6662
- } catch (error) {
6663
- if (error.code === 'ENOENT') {
6664
- return [];
6665
- }
6666
- throw error;
6667
- }
6668
- }
6669
- /**
6670
- * Load a migration from a file
6671
- */ async loadMigration(fileName) {
6672
- const filePath = join(this.migrationsPath, fileName);
6673
- const fileUrl = pathToFileURL(filePath).href;
6674
- try {
6675
- const module = await import(fileUrl);
6676
- const migration = module.default ?? module;
6677
- if (this.isMigration(migration)) {
6678
- return {
6679
- name: this.getMigrationName(fileName),
6680
- up: migration.up.bind(migration),
6681
- down: migration.down.bind(migration)
6682
- };
6683
- }
6684
- this.logger.logWarn(`Warning: ${fileName} is not a valid migration`);
6685
- return undefined;
6686
- } catch (error) {
6687
- this.logger.logError(`Error loading migration ${fileName}: ${error.message}`, error);
6688
- return undefined;
6689
- }
6690
- }
6691
- /**
6692
- * Check if an object is a valid migration
6693
- */ isMigration(obj) {
6694
- return typeof obj === 'object' && obj !== undefined && obj !== null && typeof obj.up === 'function' && typeof obj.down === 'function';
6695
- }
6696
- /**
6697
- * Extract migration name from filename
6698
- */ getMigrationName(fileName) {
6699
- return basename(fileName, extname(fileName));
6700
- }
6701
- /**
6702
- * Generate timestamp string for migration names
6703
- */ getTimestamp() {
6704
- const now = new Date();
6705
- return [
6706
- now.getFullYear(),
6707
- String(now.getMonth() + 1).padStart(2, '0'),
6708
- String(now.getDate()).padStart(2, '0'),
6709
- String(now.getHours()).padStart(2, '0'),
6710
- String(now.getMinutes()).padStart(2, '0'),
6711
- String(now.getSeconds()).padStart(2, '0')
6712
- ].join('');
6713
- }
6714
- /**
6715
- * Convert a string to a slug for filenames
6716
- */ slugify(text) {
6717
- return text.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
6718
- }
6719
- }
6720
- /**
6721
- * Helper function to define a migration with proper typing
6722
- */ function defineMigration(migration) {
6723
- return migration;
6724
- }
6725
- /**
6726
- * Define a migration using the type-safe builder API.
6727
- *
6728
- * @example
6729
- * ```ts
6730
- * export default defineBuilderMigration({
6731
- * async up(m) {
6732
- * await m.createTable('users', (table) => {
6733
- * table.id();
6734
- * table.string('email', 255).unique();
6735
- * table.timestamps();
6736
- * });
6737
- * },
6738
- * async down(m) {
6739
- * await m.dropTable('users');
6740
- * }
6741
- * });
6742
- * ```
6743
- */ function defineBuilderMigration(migration) {
6744
- return migration;
6745
- }
6746
-
6747
- /**
6748
- * Stores migration state in a JSON file.
6749
- * Useful for development or environments without a database.
6750
- */ class JsonMigrationStorage {
6751
- constructor(filePath = './migrations/.uql-migrations.json'){
6752
- this.executedMigrations = [];
6753
- this.filePath = filePath;
6754
- }
6755
- async ensureStorage() {
6756
- const content = await fs.readFile(this.filePath, 'utf-8').catch(async (error)=>{
6757
- if (error.code === 'ENOENT') {
6758
- const initial = '[]';
6759
- await fs.writeFile(this.filePath, initial, 'utf-8');
6760
- return initial;
6761
- }
6762
- throw error;
6763
- });
6764
- this.executedMigrations = JSON.parse(content);
6765
- }
6766
- async executed() {
6767
- await this.ensureStorage();
6768
- return this.executedMigrations;
6769
- }
6770
- async logWithQuerier(_querier, migrationName) {
6771
- await this.ensureStorage();
6772
- if (!this.executedMigrations.includes(migrationName)) {
6773
- this.executedMigrations.push(migrationName);
6774
- await this.save();
6775
- }
6776
- }
6777
- async unlogWithQuerier(_querier, migrationName) {
6778
- await this.ensureStorage();
6779
- this.executedMigrations = this.executedMigrations.filter((m)=>m !== migrationName);
6780
- await this.save();
6781
- }
6782
- async save() {
6783
- await fs.writeFile(this.filePath, JSON.stringify(this.executedMigrations, null, 2), 'utf-8');
6784
- }
6785
- }
6786
-
6787
- /**
6788
- * Service for synchronizing schemas between entities and database.
6789
- */ class SchemaSync {
6790
- constructor(options){
6791
- this.options = options;
6792
- }
6793
- /**
6794
- * Execute schema synchronization.
6795
- */ async sync() {
6796
- const direction = this.options.direction ?? 'bidirectional';
6797
- switch(direction){
6798
- case 'entity-to-db':
6799
- return this.syncEntityToDb();
6800
- case 'db-to-entity':
6801
- return this.syncDbToEntity();
6802
- case 'bidirectional':
6803
- return this.syncBidirectional();
6804
- }
6805
- }
6806
- /**
6807
- * Sync from entities to database (push).
6808
- */ async syncEntityToDb() {
6809
- const entityAST = this.buildEntityAST();
6810
- const dbAST = await this.buildDatabaseAST();
6811
- const differ = new SchemaASTDiffer();
6812
- const diff = differ.diff(entityAST, dbAST);
6813
- // In safe mode, filter out destructive changes
6814
- const safeDiff = this.options.safe ? this.filterDestructiveChanges(diff) : diff;
6815
- return {
6816
- direction: 'entity-to-db',
6817
- success: true,
6818
- dbChanges: safeDiff,
6819
- entityChanges: undefined,
6820
- conflicts: [],
6821
- summary: this.formatSummary('entity-to-db', safeDiff),
6822
- details: {
6823
- db: this.getDiffCounts(safeDiff),
6824
- entity: {
6825
- created: 0,
6826
- dropped: 0,
6827
- altered: 0
6828
- }
6829
- }
6830
- };
6831
- }
6832
- /**
6833
- * Sync from database to entities (pull).
6834
- */ async syncDbToEntity() {
6835
- const entityAST = this.buildEntityAST();
6836
- const dbAST = await this.buildDatabaseAST();
6837
- const differ = new SchemaASTDiffer();
6838
- // Diff in opposite direction: DB is source, entity is target
6839
- const diff = differ.diff(dbAST, entityAST);
6840
- // Apply safe mode if enabled
6841
- const safeDiff = this.options.safe ? this.filterDestructiveChanges(diff) : diff;
6842
- return {
6843
- direction: 'db-to-entity',
6844
- success: true,
6845
- dbChanges: undefined,
6846
- entityChanges: safeDiff,
6847
- conflicts: [],
6848
- summary: this.formatSummary('db-to-entity', safeDiff),
6849
- details: {
6850
- db: {
6851
- created: 0,
6852
- dropped: 0,
6853
- altered: 0
6854
- },
6855
- entity: this.getDiffCounts(safeDiff)
6856
- }
6857
- };
6858
- }
6859
- /**
6860
- * Bidirectional sync with conflict detection.
6861
- */ async syncBidirectional() {
6862
- const entityAST = this.buildEntityAST();
6863
- const dbAST = await this.buildDatabaseAST();
6864
- const differ = new SchemaASTDiffer();
6865
- // Get both directions
6866
- const entityToDb = differ.diff(entityAST, dbAST);
6867
- const dbToEntity = differ.diff(dbAST, entityAST);
6868
- // Detect conflicts (changes in both directions for same elements)
6869
- const conflicts = this.detectConflicts(entityToDb, dbToEntity);
6870
- // Filter out conflicting changes
6871
- let safeEntityToDb = this.filterConflictingChanges(entityToDb, conflicts);
6872
- let safeDbToEntity = this.filterConflictingChanges(dbToEntity, conflicts);
6873
- // Apply safe mode if enabled
6874
- if (this.options.safe) {
6875
- safeEntityToDb = this.filterDestructiveChanges(safeEntityToDb);
6876
- safeDbToEntity = this.filterDestructiveChanges(safeDbToEntity);
6877
- }
6878
- return {
6879
- direction: 'bidirectional',
6880
- success: conflicts.length === 0,
6881
- dbChanges: safeEntityToDb,
6882
- entityChanges: safeDbToEntity,
6883
- conflicts,
6884
- summary: this.formatBidirectionalSummary(safeEntityToDb, safeDbToEntity, conflicts),
6885
- details: {
6886
- db: this.getDiffCounts(safeEntityToDb),
6887
- entity: this.getDiffCounts(safeDbToEntity)
6888
- }
6889
- };
6890
- }
6891
- /**
6892
- * Build SchemaAST from entities.
6893
- */ buildEntityAST() {
6894
- const builder = new SchemaASTBuilder();
6895
- return builder.fromEntities(this.options.entities);
6896
- }
6897
- /**
6898
- * Build SchemaAST from database.
6899
- */ async buildDatabaseAST() {
6900
- return this.options.introspector.introspect();
6901
- }
6902
- /**
6903
- * Filter destructive changes for safe mode.
6904
- */ filterDestructiveChanges(diff) {
6905
- return {
6906
- ...diff,
6907
- tablesToDrop: [],
6908
- columnDiffs: diff.columnDiffs.filter((d)=>d.type !== 'drop'),
6909
- indexDiffs: diff.indexDiffs.filter((d)=>d.type !== 'drop'),
6910
- relationshipDiffs: diff.relationshipDiffs.filter((d)=>d.type !== 'drop')
6911
- };
6912
- }
6913
- /**
6914
- * Detect conflicts between bidirectional changes.
6915
- */ detectConflicts(entityToDb, dbToEntity) {
6916
- const conflicts = [];
6917
- // Find column alterations that exist in both directions (type conflicts)
6918
- const entityColAlters = new Map(entityToDb.columnDiffs.filter((d)=>d.type === 'alter').map((d)=>[
6919
- `${d.table}.${d.column}`,
6920
- d
6921
- ]));
6922
- for (const dbColDiff of dbToEntity.columnDiffs.filter((d)=>d.type === 'alter')){
6923
- const key = `${dbColDiff.table}.${dbColDiff.column}`;
6924
- const entityColDiff = entityColAlters.get(key);
6925
- if (entityColDiff) {
6926
- // Both sides want to alter the same column - conflict
6927
- conflicts.push({
6928
- type: 'type_mismatch',
6929
- table: dbColDiff.table,
6930
- column: dbColDiff.column,
6931
- entityValue: entityColDiff.expected?.type,
6932
- dbValue: dbColDiff.expected?.type,
6933
- suggestion: `Column has different types. Entity: ${this.formatType(entityColDiff.expected?.type)}, DB: ${this.formatType(dbColDiff.expected?.type)}`
6934
- });
6935
- }
6936
- }
6937
- return conflicts;
6938
- }
6939
- /**
6940
- * Filter out conflicting changes.
6941
- */ filterConflictingChanges(diff, conflicts) {
6942
- const conflictKeys = new Set(conflicts.map((c)=>`${c.table}.${c.column ?? ''}`));
6943
- return {
6944
- ...diff,
6945
- columnDiffs: diff.columnDiffs.filter((d)=>!conflictKeys.has(`${d.table}.${d.column}`)),
6946
- tablesToAlter: diff.tablesToAlter.filter((t)=>!conflictKeys.has(`${t.name}.`))
6947
- };
6948
- }
6949
- /**
6950
- * Get counts of changes from a diff.
6951
- */ getDiffCounts(diff) {
6952
- return {
6953
- created: diff.tablesToCreate.length + diff.columnDiffs.filter((d)=>d.type === 'add').length,
6954
- dropped: diff.tablesToDrop.length + diff.columnDiffs.filter((d)=>d.type === 'drop').length,
6955
- altered: diff.columnDiffs.filter((d)=>d.type === 'alter').length
6956
- };
6957
- }
6958
- /**
6959
- * Format summary for single-direction sync.
6960
- */ formatSummary(direction, diff) {
6961
- const lines = [];
6962
- if (direction === 'entity-to-db') {
6963
- lines.push('Entity → Database Changes:');
6964
- } else {
6965
- lines.push('Database → Entity Changes:');
6966
- }
6967
- if (diff.tablesToCreate.length > 0) {
6968
- lines.push(` + ${diff.tablesToCreate.length} table(s) to create`);
6969
- }
6970
- if (diff.tablesToDrop.length > 0) {
6971
- lines.push(` - ${diff.tablesToDrop.length} table(s) to drop`);
6972
- }
6973
- if (diff.columnDiffs.length > 0) {
6974
- const adds = diff.columnDiffs.filter((d)=>d.type === 'add').length;
6975
- const drops = diff.columnDiffs.filter((d)=>d.type === 'drop').length;
6976
- const alters = diff.columnDiffs.filter((d)=>d.type === 'alter').length;
6977
- if (adds) lines.push(` + ${adds} column(s) to add`);
6978
- if (drops) lines.push(` - ${drops} column(s) to drop`);
6979
- if (alters) lines.push(` ~ ${alters} column(s) to alter`);
6980
- }
6981
- if (diff.indexDiffs.length > 0) {
6982
- lines.push(` ${diff.indexDiffs.length} index change(s)`);
6983
- }
6984
- if (!diff.hasDifferences) {
6985
- lines.push(' Schema is already in sync.');
6986
- }
6987
- return lines.join('\n');
6988
- }
6989
- /**
6990
- * Format summary for bidirectional sync.
6991
- */ formatBidirectionalSummary(entityToDb, dbToEntity, conflicts) {
6992
- const lines = [];
6993
- lines.push('Bidirectional Sync Report');
6994
- lines.push('========================\n');
6995
- if (conflicts.length > 0) {
6996
- lines.push(`⚠️ ${conflicts.length} conflict(s) require manual resolution:\n`);
6997
- for (const conflict of conflicts){
6998
- lines.push(` • ${conflict.table}.${conflict.column ?? ''}: ${conflict.suggestion}`);
6999
- }
7000
- lines.push('');
7001
- }
7002
- if (entityToDb.hasDifferences) {
7003
- lines.push('Entity → Database:');
7004
- lines.push(this.formatSummary('entity-to-db', entityToDb).split('\n').slice(1).join('\n'));
7005
- lines.push('');
7006
- }
7007
- if (dbToEntity.hasDifferences) {
7008
- lines.push('Database → Entity:');
7009
- lines.push(this.formatSummary('db-to-entity', dbToEntity).split('\n').slice(1).join('\n'));
7010
- }
7011
- if (!entityToDb.hasDifferences && !dbToEntity.hasDifferences && conflicts.length === 0) {
7012
- lines.push('✓ Schema is in sync.');
7013
- }
7014
- return lines.join('\n');
7015
- }
7016
- /**
7017
- * Format type for display.
7018
- */ formatType(type) {
7019
- if (!type?.category) return 'unknown';
7020
- return type.length ? `${type.category}(${type.length})` : type.category;
7021
- }
7022
- }
7023
- /**
7024
- * Create a SchemaSync instance.
7025
- */ function createSchemaSync(options) {
7026
- return new SchemaSync(options);
7027
- }
7028
-
7029
- export { AbstractSqlSchemaIntrospector, ColumnBuilder, DatabaseMigrationStorage, DriftDetector, EMPTY_MANUAL_MIGRATION_DOWN_INNER, EMPTY_MANUAL_MIGRATION_UP_INNER, EntityCodeGenerator, EntityMerger, JsonMigrationStorage, MariadbSchemaIntrospector, MigrationBuilder, MigrationCodeGenerator, Migrator, MongoSchemaIntrospector, MysqlSchemaIntrospector, OperationRecorder, PostgresSchemaIntrospector, SchemaSync, SmartRelationDetector, SqlExpression, SqlSchemaGenerator, SqliteSchemaIntrospector, TableBuilder, acquireQuerierForMigrations, assertCliConfig, buildSqlQuerierMigrationModule, createDriftDetector, createDryRunBuilder, createEntityCodeGenerator, createEntityMerger, createMigrationCodeGenerator, createRelationDetector, createSchemaGenerator, createSchemaGeneratorAsync, createSchemaSync, defineBuilderMigration, defineMigration, detectDrift, emitSqlRunCall, emitSqlRunCalls, formatDefaultValue, loadConfig, t };
7030
- //# sourceMappingURL=uql-browser.min.js.map