@uql/core 3.1.1 → 3.1.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.
Files changed (171) hide show
  1. package/CHANGELOG.md +134 -187
  2. package/package.json +31 -26
  3. package/dist/CHANGELOG.md +0 -186
  4. package/dist/package.json +0 -131
  5. package/src/@types/index.d.ts +0 -1
  6. package/src/@types/jest.d.ts +0 -6
  7. package/src/browser/http/bus.spec.ts +0 -22
  8. package/src/browser/http/bus.ts +0 -17
  9. package/src/browser/http/http.spec.ts +0 -70
  10. package/src/browser/http/http.ts +0 -55
  11. package/src/browser/http/index.ts +0 -2
  12. package/src/browser/index.ts +0 -4
  13. package/src/browser/options.spec.ts +0 -37
  14. package/src/browser/options.ts +0 -18
  15. package/src/browser/querier/genericClientRepository.spec.ts +0 -105
  16. package/src/browser/querier/genericClientRepository.ts +0 -49
  17. package/src/browser/querier/httpQuerier.ts +0 -82
  18. package/src/browser/querier/index.ts +0 -3
  19. package/src/browser/querier/querier.util.spec.ts +0 -35
  20. package/src/browser/querier/querier.util.ts +0 -18
  21. package/src/browser/type/clientQuerier.ts +0 -45
  22. package/src/browser/type/clientQuerierPool.ts +0 -5
  23. package/src/browser/type/clientRepository.ts +0 -22
  24. package/src/browser/type/index.ts +0 -4
  25. package/src/browser/type/request.ts +0 -25
  26. package/src/dialect/abstractDialect.ts +0 -28
  27. package/src/dialect/abstractSqlDialect-spec.ts +0 -1309
  28. package/src/dialect/abstractSqlDialect.ts +0 -805
  29. package/src/dialect/index.ts +0 -3
  30. package/src/dialect/namingStrategy.spec.ts +0 -52
  31. package/src/dialect/queryContext.ts +0 -69
  32. package/src/entity/decorator/definition.spec.ts +0 -736
  33. package/src/entity/decorator/definition.ts +0 -265
  34. package/src/entity/decorator/entity.ts +0 -8
  35. package/src/entity/decorator/field.ts +0 -9
  36. package/src/entity/decorator/id.ts +0 -9
  37. package/src/entity/decorator/index.ts +0 -5
  38. package/src/entity/decorator/relation.spec.ts +0 -41
  39. package/src/entity/decorator/relation.ts +0 -34
  40. package/src/entity/index.ts +0 -1
  41. package/src/express/@types/express.d.ts +0 -8
  42. package/src/express/@types/index.d.ts +0 -1
  43. package/src/express/index.ts +0 -2
  44. package/src/express/querierMiddleware.ts +0 -217
  45. package/src/express/query.util.spec.ts +0 -40
  46. package/src/express/query.util.ts +0 -21
  47. package/src/index.ts +0 -9
  48. package/src/maria/index.ts +0 -3
  49. package/src/maria/mariaDialect.spec.ts +0 -207
  50. package/src/maria/mariaDialect.ts +0 -42
  51. package/src/maria/mariaQuerierPool.test.ts +0 -23
  52. package/src/maria/mariadbQuerier.test.ts +0 -23
  53. package/src/maria/mariadbQuerier.ts +0 -45
  54. package/src/maria/mariadbQuerierPool.ts +0 -21
  55. package/src/migrate/cli.ts +0 -301
  56. package/src/migrate/generator/index.ts +0 -4
  57. package/src/migrate/generator/mongoSchemaGenerator.spec.ts +0 -112
  58. package/src/migrate/generator/mongoSchemaGenerator.ts +0 -115
  59. package/src/migrate/generator/mysqlSchemaGenerator.spec.ts +0 -34
  60. package/src/migrate/generator/mysqlSchemaGenerator.ts +0 -92
  61. package/src/migrate/generator/postgresSchemaGenerator.spec.ts +0 -44
  62. package/src/migrate/generator/postgresSchemaGenerator.ts +0 -127
  63. package/src/migrate/generator/sqliteSchemaGenerator.spec.ts +0 -33
  64. package/src/migrate/generator/sqliteSchemaGenerator.ts +0 -81
  65. package/src/migrate/index.ts +0 -41
  66. package/src/migrate/introspection/index.ts +0 -4
  67. package/src/migrate/introspection/mongoIntrospector.spec.ts +0 -75
  68. package/src/migrate/introspection/mongoIntrospector.ts +0 -47
  69. package/src/migrate/introspection/mysqlIntrospector.spec.ts +0 -113
  70. package/src/migrate/introspection/mysqlIntrospector.ts +0 -278
  71. package/src/migrate/introspection/postgresIntrospector.spec.ts +0 -112
  72. package/src/migrate/introspection/postgresIntrospector.ts +0 -329
  73. package/src/migrate/introspection/sqliteIntrospector.spec.ts +0 -112
  74. package/src/migrate/introspection/sqliteIntrospector.ts +0 -296
  75. package/src/migrate/migrator-mongo.test.ts +0 -54
  76. package/src/migrate/migrator.spec.ts +0 -255
  77. package/src/migrate/migrator.test.ts +0 -94
  78. package/src/migrate/migrator.ts +0 -719
  79. package/src/migrate/namingStrategy.spec.ts +0 -22
  80. package/src/migrate/schemaGenerator-advanced.spec.ts +0 -138
  81. package/src/migrate/schemaGenerator.spec.ts +0 -190
  82. package/src/migrate/schemaGenerator.ts +0 -478
  83. package/src/migrate/storage/databaseStorage.spec.ts +0 -69
  84. package/src/migrate/storage/databaseStorage.ts +0 -100
  85. package/src/migrate/storage/index.ts +0 -2
  86. package/src/migrate/storage/jsonStorage.ts +0 -58
  87. package/src/migrate/type.ts +0 -1
  88. package/src/mongo/index.ts +0 -3
  89. package/src/mongo/mongoDialect.spec.ts +0 -251
  90. package/src/mongo/mongoDialect.ts +0 -238
  91. package/src/mongo/mongodbQuerier.test.ts +0 -45
  92. package/src/mongo/mongodbQuerier.ts +0 -256
  93. package/src/mongo/mongodbQuerierPool.test.ts +0 -25
  94. package/src/mongo/mongodbQuerierPool.ts +0 -24
  95. package/src/mysql/index.ts +0 -3
  96. package/src/mysql/mysql2Querier.test.ts +0 -20
  97. package/src/mysql/mysql2Querier.ts +0 -49
  98. package/src/mysql/mysql2QuerierPool.test.ts +0 -20
  99. package/src/mysql/mysql2QuerierPool.ts +0 -21
  100. package/src/mysql/mysqlDialect.spec.ts +0 -20
  101. package/src/mysql/mysqlDialect.ts +0 -16
  102. package/src/namingStrategy/defaultNamingStrategy.ts +0 -18
  103. package/src/namingStrategy/index.spec.ts +0 -36
  104. package/src/namingStrategy/index.ts +0 -2
  105. package/src/namingStrategy/snakeCaseNamingStrategy.ts +0 -15
  106. package/src/options.spec.ts +0 -41
  107. package/src/options.ts +0 -18
  108. package/src/postgres/index.ts +0 -3
  109. package/src/postgres/manual-types.d.ts +0 -4
  110. package/src/postgres/pgQuerier.test.ts +0 -25
  111. package/src/postgres/pgQuerier.ts +0 -45
  112. package/src/postgres/pgQuerierPool.test.ts +0 -28
  113. package/src/postgres/pgQuerierPool.ts +0 -21
  114. package/src/postgres/postgresDialect.spec.ts +0 -428
  115. package/src/postgres/postgresDialect.ts +0 -144
  116. package/src/querier/abstractQuerier-test.ts +0 -584
  117. package/src/querier/abstractQuerier.ts +0 -353
  118. package/src/querier/abstractQuerierPool-test.ts +0 -20
  119. package/src/querier/abstractQuerierPool.ts +0 -18
  120. package/src/querier/abstractSqlQuerier-spec.ts +0 -979
  121. package/src/querier/abstractSqlQuerier-test.ts +0 -21
  122. package/src/querier/abstractSqlQuerier.ts +0 -138
  123. package/src/querier/decorator/index.ts +0 -3
  124. package/src/querier/decorator/injectQuerier.spec.ts +0 -74
  125. package/src/querier/decorator/injectQuerier.ts +0 -45
  126. package/src/querier/decorator/serialized.spec.ts +0 -98
  127. package/src/querier/decorator/serialized.ts +0 -13
  128. package/src/querier/decorator/transactional.spec.ts +0 -240
  129. package/src/querier/decorator/transactional.ts +0 -56
  130. package/src/querier/index.ts +0 -4
  131. package/src/repository/genericRepository.spec.ts +0 -111
  132. package/src/repository/genericRepository.ts +0 -74
  133. package/src/repository/index.ts +0 -1
  134. package/src/sqlite/index.ts +0 -3
  135. package/src/sqlite/manual-types.d.ts +0 -4
  136. package/src/sqlite/sqliteDialect.spec.ts +0 -155
  137. package/src/sqlite/sqliteDialect.ts +0 -76
  138. package/src/sqlite/sqliteQuerier.spec.ts +0 -36
  139. package/src/sqlite/sqliteQuerier.test.ts +0 -21
  140. package/src/sqlite/sqliteQuerier.ts +0 -37
  141. package/src/sqlite/sqliteQuerierPool.test.ts +0 -12
  142. package/src/sqlite/sqliteQuerierPool.ts +0 -38
  143. package/src/test/entityMock.ts +0 -375
  144. package/src/test/index.ts +0 -3
  145. package/src/test/it.util.ts +0 -69
  146. package/src/test/spec.util.ts +0 -57
  147. package/src/type/entity.ts +0 -218
  148. package/src/type/index.ts +0 -9
  149. package/src/type/migration.ts +0 -241
  150. package/src/type/namingStrategy.ts +0 -17
  151. package/src/type/querier.ts +0 -143
  152. package/src/type/querierPool.ts +0 -26
  153. package/src/type/query.ts +0 -506
  154. package/src/type/repository.ts +0 -142
  155. package/src/type/universalQuerier.ts +0 -133
  156. package/src/type/utility.ts +0 -21
  157. package/src/util/dialect.util-extra.spec.ts +0 -96
  158. package/src/util/dialect.util.spec.ts +0 -23
  159. package/src/util/dialect.util.ts +0 -134
  160. package/src/util/index.ts +0 -5
  161. package/src/util/object.util.spec.ts +0 -29
  162. package/src/util/object.util.ts +0 -27
  163. package/src/util/raw.ts +0 -11
  164. package/src/util/sql.util-extra.spec.ts +0 -17
  165. package/src/util/sql.util.spec.ts +0 -208
  166. package/src/util/sql.util.ts +0 -104
  167. package/src/util/string.util.spec.ts +0 -46
  168. package/src/util/string.util.ts +0 -35
  169. package/tsconfig.build.json +0 -5
  170. package/tsconfig.json +0 -8
  171. /package/{dist/README.md → README.md} +0 -0
@@ -1,478 +0,0 @@
1
- import { AbstractDialect } from '../dialect/index.js';
2
- import { getMeta } from '../entity/index.js';
3
- import type {
4
- ColumnSchema,
5
- ColumnType,
6
- EntityMeta,
7
- FieldKey,
8
- FieldOptions,
9
- IndexSchema,
10
- NamingStrategy,
11
- SchemaDiff,
12
- SchemaGenerator,
13
- TableSchema,
14
- Type,
15
- } from '../type/index.js';
16
- import { escapeSqlId, getKeys } from '../util/index.js';
17
-
18
- /**
19
- * Abstract base class for SQL schema generation
20
- */
21
- export abstract class AbstractSchemaGenerator extends AbstractDialect implements SchemaGenerator {
22
- /**
23
- * Primary key type for auto-increment integer IDs
24
- */
25
- protected abstract readonly serialPrimaryKeyType: string;
26
-
27
- constructor(
28
- namingStrategy?: NamingStrategy,
29
- protected readonly escapeIdChar: '`' | '"' = '`',
30
- ) {
31
- super(namingStrategy);
32
- }
33
-
34
- /**
35
- * Escape an identifier (table name, column name, etc.)
36
- */
37
- protected escapeId(identifier: string): string {
38
- return escapeSqlId(identifier, this.escapeIdChar);
39
- }
40
-
41
- generateCreateTable<E>(entity: Type<E>, options: { ifNotExists?: boolean } = {}): string {
42
- const meta = getMeta(entity);
43
- const tableName = this.resolveTableName(entity, meta);
44
- const columns = this.generateColumnDefinitions(meta);
45
- const constraints = this.generateTableConstraints(meta);
46
-
47
- const ifNotExists = options.ifNotExists ? 'IF NOT EXISTS ' : '';
48
- let sql = `CREATE TABLE ${ifNotExists}${this.escapeId(tableName)} (\n`;
49
- sql += columns.map((col) => ` ${col}`).join(',\n');
50
-
51
- if (constraints.length > 0) {
52
- sql += ',\n';
53
- sql += constraints.map((c: any) => ` ${c}`).join(',\n');
54
- }
55
-
56
- sql += '\n)';
57
- sql += this.getTableOptions(meta);
58
- sql += ';';
59
-
60
- return sql;
61
- }
62
-
63
- generateDropTable<E>(entity: Type<E>): string {
64
- const meta = getMeta(entity);
65
- const tableName = this.resolveTableName(entity, meta);
66
- return `DROP TABLE IF EXISTS ${this.escapeId(tableName)};`;
67
- }
68
-
69
- generateAlterTable(diff: SchemaDiff): string[] {
70
- const statements: string[] = [];
71
- const tableName = this.escapeId(diff.tableName);
72
-
73
- // Add new columns
74
- if (diff.columnsToAdd?.length) {
75
- for (const column of diff.columnsToAdd) {
76
- const colDef = this.generateColumnDefinitionFromSchema(column);
77
- statements.push(`ALTER TABLE ${tableName} ADD COLUMN ${colDef};`);
78
- }
79
- }
80
-
81
- // Alter existing columns
82
- if (diff.columnsToAlter?.length) {
83
- for (const { to } of diff.columnsToAlter) {
84
- const colDef = this.generateColumnDefinitionFromSchema(to);
85
- const colStatements = this.generateAlterColumnStatements(diff.tableName, to, colDef);
86
- statements.push(...colStatements);
87
- }
88
- }
89
-
90
- // Drop columns
91
- if (diff.columnsToDrop?.length) {
92
- for (const columnName of diff.columnsToDrop) {
93
- statements.push(`ALTER TABLE ${tableName} DROP COLUMN ${this.escapeId(columnName)};`);
94
- }
95
- }
96
-
97
- // Add indexes
98
- if (diff.indexesToAdd?.length) {
99
- for (const index of diff.indexesToAdd) {
100
- statements.push(this.generateCreateIndex(diff.tableName, index));
101
- }
102
- }
103
-
104
- // Drop indexes
105
- if (diff.indexesToDrop?.length) {
106
- for (const indexName of diff.indexesToDrop) {
107
- statements.push(this.generateDropIndex(diff.tableName, indexName));
108
- }
109
- }
110
-
111
- return statements;
112
- }
113
-
114
- generateAlterTableDown(diff: SchemaDiff): string[] {
115
- const statements: string[] = [];
116
- const tableName = this.escapeId(diff.tableName);
117
-
118
- // Rollback additions by dropping columns
119
- if (diff.columnsToAdd?.length) {
120
- for (const column of diff.columnsToAdd) {
121
- statements.push(`ALTER TABLE ${tableName} DROP COLUMN ${this.escapeId(column.name)};`);
122
- }
123
- }
124
-
125
- return statements;
126
- }
127
-
128
- generateCreateIndex(tableName: string, index: IndexSchema): string {
129
- const unique = index.unique ? 'UNIQUE ' : '';
130
- const columns = index.columns.map((c: any) => this.escapeId(c)).join(', ');
131
- return `CREATE ${unique}INDEX ${this.escapeId(index.name)} ON ${this.escapeId(tableName)} (${columns});`;
132
- }
133
-
134
- generateDropIndex(tableName: string, indexName: string): string {
135
- return `DROP INDEX IF EXISTS ${this.escapeId(indexName)};`;
136
- }
137
-
138
- /**
139
- * Generate column definitions from entity metadata
140
- */
141
- public generateColumnDefinitions<E>(meta: EntityMeta<E>): string[] {
142
- const columns: string[] = [];
143
- const fieldKeys = getKeys(meta.fields) as FieldKey<E>[];
144
-
145
- for (const key of fieldKeys) {
146
- const field = meta.fields[key];
147
- if (field?.virtual) continue; // Skip virtual fields
148
-
149
- const colDef = this.generateColumnDefinition(key as string, field, meta);
150
- columns.push(colDef);
151
- }
152
-
153
- return columns;
154
- }
155
-
156
- /**
157
- * Generate a single column definition
158
- */
159
- public generateColumnDefinition<E>(fieldKey: string, field: FieldOptions, meta: EntityMeta<E>): string {
160
- const columnName = this.escapeId(this.resolveColumnName(fieldKey, field));
161
- const isId = field.isId === true;
162
- const isPrimaryKey = isId && meta.id === fieldKey;
163
-
164
- // Determine SQL type
165
- let sqlType: string;
166
- if (isPrimaryKey && field.autoIncrement !== false && !field.onInsert) {
167
- // Auto-increment primary key
168
- sqlType = this.serialPrimaryKeyType;
169
- } else {
170
- sqlType = this.getSqlType(field, field.type);
171
- }
172
-
173
- let definition = `${columnName} ${sqlType}`;
174
-
175
- // PRIMARY KEY constraint (for non-serial types)
176
- if (isPrimaryKey && !sqlType.includes('PRIMARY KEY')) {
177
- definition += ' PRIMARY KEY';
178
- }
179
-
180
- // NULL/NOT NULL
181
- if (!isPrimaryKey) {
182
- const nullable = field.nullable ?? true;
183
- if (!nullable) {
184
- definition += ' NOT NULL';
185
- }
186
- }
187
-
188
- // UNIQUE constraint
189
- if (field.unique && !isPrimaryKey) {
190
- definition += ' UNIQUE';
191
- }
192
-
193
- // DEFAULT value
194
- if (field.defaultValue !== undefined) {
195
- definition += ` DEFAULT ${this.formatDefaultValue(field.defaultValue)}`;
196
- }
197
-
198
- // COMMENT (if supported)
199
- if (field.comment) {
200
- definition += this.generateColumnComment(
201
- this.resolveTableName(meta.entity, meta),
202
- this.resolveColumnName(fieldKey, field),
203
- field.comment,
204
- );
205
- }
206
-
207
- return definition;
208
- }
209
-
210
- /**
211
- * Generate column definition from a ColumnSchema object
212
- */
213
- public generateColumnDefinitionFromSchema(column: ColumnSchema): string {
214
- const columnName = this.escapeId(column.name);
215
- let type = column.type;
216
-
217
- if (column.length && !type.includes('(')) {
218
- type = `${type}(${column.length})`;
219
- } else if (column.precision !== undefined && !type.includes('(')) {
220
- if (column.scale !== undefined) {
221
- type = `${type}(${column.precision}, ${column.scale})`;
222
- } else {
223
- type = `${type}(${column.precision})`;
224
- }
225
- }
226
-
227
- let definition = `${columnName} ${type}`;
228
-
229
- if (column.isPrimaryKey) {
230
- definition += ' PRIMARY KEY';
231
- }
232
-
233
- if (!column.nullable && !column.isPrimaryKey) {
234
- definition += ' NOT NULL';
235
- }
236
-
237
- if (column.isUnique && !column.isPrimaryKey) {
238
- definition += ' UNIQUE';
239
- }
240
-
241
- if (column.defaultValue !== undefined) {
242
- definition += ` DEFAULT ${this.formatDefaultValue(column.defaultValue)}`;
243
- }
244
-
245
- return definition;
246
- }
247
-
248
- /**
249
- * Generate table constraints (indexes, foreign keys, etc.)
250
- */
251
- public generateTableConstraints<E>(meta: EntityMeta<E>): string[] {
252
- const constraints: string[] = [];
253
- const fieldKeys = getKeys(meta.fields) as FieldKey<E>[];
254
- const tableName = this.resolveTableName(meta.entity, meta);
255
-
256
- // Generate indexes from field options
257
- for (const key of fieldKeys) {
258
- const field = meta.fields[key];
259
- if (field?.index) {
260
- const columnName = this.resolveColumnName(key as string, field);
261
- const indexName = typeof field.index === 'string' ? field.index : `idx_${tableName}_${columnName}`;
262
- constraints.push(`INDEX ${this.escapeId(indexName)} (${this.escapeId(columnName)})`);
263
- }
264
- }
265
-
266
- // Generate foreign key constraints from references
267
- for (const key of fieldKeys) {
268
- const field = meta.fields[key];
269
- if (field?.reference) {
270
- const refEntity = field.reference();
271
- const refMeta = getMeta(refEntity);
272
- const refIdField = refMeta.fields[refMeta.id];
273
- const columnName = this.resolveColumnName(key as string, field);
274
- const refTableName = this.resolveTableName(refEntity, refMeta);
275
- const refColumnName = this.resolveColumnName(refMeta.id, refIdField);
276
- const fkName = `fk_${tableName}_${columnName}`;
277
-
278
- constraints.push(
279
- `CONSTRAINT ${this.escapeId(fkName)} FOREIGN KEY (${this.escapeId(columnName)}) ` +
280
- `REFERENCES ${this.escapeId(refTableName)} (${this.escapeId(refColumnName)})`,
281
- );
282
- }
283
- }
284
-
285
- return constraints;
286
- }
287
-
288
- getSqlType(field: FieldOptions, fieldType?: unknown): string {
289
- // Use explicit column type if specified
290
- if (field.columnType) {
291
- return this.mapColumnType(field.columnType, field);
292
- }
293
-
294
- // Handle special types
295
- if (field.type === 'json' || field.type === 'jsonb') {
296
- return this.mapColumnType(field.type as ColumnType, field);
297
- }
298
-
299
- if (field.type === 'vector') {
300
- return this.mapColumnType('vector', field);
301
- }
302
-
303
- // Infer from TypeScript type
304
- const type = fieldType ?? field.type;
305
-
306
- if (type === Number || type === 'number') {
307
- return field.precision ? this.mapColumnType('decimal', field) : 'BIGINT';
308
- }
309
-
310
- if (type === String || type === 'string') {
311
- const length = field.length ?? 255;
312
- return `VARCHAR(${length})`;
313
- }
314
-
315
- if (type === Boolean || type === 'boolean') {
316
- return this.getBooleanType();
317
- }
318
-
319
- if (type === Date || type === 'date') {
320
- return 'TIMESTAMP';
321
- }
322
-
323
- if (type === BigInt || type === 'bigint') {
324
- return 'BIGINT';
325
- }
326
-
327
- // Default to VARCHAR
328
- return `VARCHAR(${field.length ?? 255})`;
329
- }
330
-
331
- /**
332
- * Map uql column type to database-specific SQL type
333
- */
334
- public abstract mapColumnType(columnType: ColumnType, field: FieldOptions): string;
335
-
336
- /**
337
- * Get the boolean type for this database
338
- */
339
- public abstract getBooleanType(): string;
340
-
341
- /**
342
- * Generate ALTER COLUMN statements (database-specific)
343
- */
344
- public abstract generateAlterColumnStatements(
345
- tableName: string,
346
- column: ColumnSchema,
347
- newDefinition: string,
348
- ): string[];
349
-
350
- /**
351
- * Get table options (e.g., ENGINE for MySQL)
352
- */
353
- getTableOptions<E>(meta: EntityMeta<E>): string {
354
- return '';
355
- }
356
-
357
- /**
358
- * Generate column comment clause (if supported)
359
- */
360
- public abstract generateColumnComment(tableName: string, columnName: string, comment: string): string;
361
-
362
- /**
363
- * Format a default value for SQL
364
- */
365
- public formatDefaultValue(value: unknown): string {
366
- if (value === null) {
367
- return 'NULL';
368
- }
369
- if (typeof value === 'string') {
370
- return `'${value.replace(/'/g, "''")}'`;
371
- }
372
- if (typeof value === 'boolean') {
373
- return value ? 'TRUE' : 'FALSE';
374
- }
375
- if (typeof value === 'number' || typeof value === 'bigint') {
376
- return String(value);
377
- }
378
- if (value instanceof Date) {
379
- return `'${value.toISOString()}'`;
380
- }
381
- return String(value);
382
- }
383
-
384
- /**
385
- * Compare two schemas and return the differences
386
- */
387
- diffSchema<E>(entity: Type<E>, currentSchema: TableSchema | undefined): SchemaDiff | undefined {
388
- const meta = getMeta(entity);
389
-
390
- if (!currentSchema) {
391
- // Table doesn't exist, need to create
392
- return {
393
- tableName: this.resolveTableName(entity, meta),
394
- type: 'create',
395
- };
396
- }
397
-
398
- const columnsToAdd: ColumnSchema[] = [];
399
- const columnsToAlter: { from: ColumnSchema; to: ColumnSchema }[] = [];
400
- const columnsToDrop: string[] = [];
401
-
402
- const currentColumns = new Map(currentSchema.columns.map((c: any) => [c.name, c]));
403
- const fieldKeys = getKeys(meta.fields) as FieldKey<E>[];
404
-
405
- // Check for new or altered columns
406
- for (const key of fieldKeys) {
407
- const field = meta.fields[key];
408
- if (field?.virtual) continue;
409
-
410
- const columnName = this.resolveColumnName(key as string, field);
411
- const currentColumn = currentColumns.get(columnName);
412
-
413
- if (!currentColumn) {
414
- // Column needs to be added
415
- columnsToAdd.push(this.fieldToColumnSchema(key as string, field, meta));
416
- } else {
417
- // Check if column needs alteration
418
- const desiredColumn = this.fieldToColumnSchema(key as string, field, meta);
419
- if (this.columnsNeedAlteration(currentColumn, desiredColumn)) {
420
- columnsToAlter.push({ from: currentColumn, to: desiredColumn });
421
- }
422
- }
423
- currentColumns.delete(columnName);
424
- }
425
-
426
- // Remaining columns in currentColumns should be dropped
427
- for (const [name] of currentColumns) {
428
- columnsToDrop.push(name);
429
- }
430
-
431
- if (columnsToAdd.length === 0 && columnsToAlter.length === 0 && columnsToDrop.length === 0) {
432
- return undefined; // No changes needed
433
- }
434
-
435
- return {
436
- tableName: this.resolveTableName(entity, meta),
437
- type: 'alter',
438
- columnsToAdd: columnsToAdd.length > 0 ? columnsToAdd : undefined,
439
- columnsToAlter: columnsToAlter.length > 0 ? columnsToAlter : undefined,
440
- columnsToDrop: columnsToDrop.length > 0 ? columnsToDrop : undefined,
441
- };
442
- }
443
-
444
- /**
445
- * Convert field options to ColumnSchema
446
- */
447
- protected fieldToColumnSchema<E>(fieldKey: string, field: FieldOptions, meta: EntityMeta<E>): ColumnSchema {
448
- const isId = field.isId === true;
449
- const isPrimaryKey = isId && meta.id === fieldKey;
450
-
451
- return {
452
- name: this.resolveColumnName(fieldKey, field),
453
- type: this.getSqlType(field, field.type),
454
- nullable: field.nullable ?? !isPrimaryKey,
455
- defaultValue: field.defaultValue,
456
- isPrimaryKey,
457
- isAutoIncrement: isPrimaryKey && field.autoIncrement !== false && !field.onInsert,
458
- isUnique: field.unique ?? false,
459
- length: field.length,
460
- precision: field.precision,
461
- scale: field.scale,
462
- comment: field.comment,
463
- };
464
- }
465
-
466
- /**
467
- * Check if two columns differ enough to require alteration
468
- */
469
- protected columnsNeedAlteration(current: ColumnSchema, desired: ColumnSchema): boolean {
470
- // Compare relevant properties
471
- return (
472
- current.type.toLowerCase() !== desired.type.toLowerCase() ||
473
- current.nullable !== desired.nullable ||
474
- current.isUnique !== desired.isUnique ||
475
- JSON.stringify(current.defaultValue) !== JSON.stringify(desired.defaultValue)
476
- );
477
- }
478
- }
@@ -1,69 +0,0 @@
1
- import { beforeEach, describe, expect, it, jest } from 'bun:test';
2
- import type { QuerierPool, SqlQuerier } from '../../type/index.js';
3
- import { DatabaseMigrationStorage } from './databaseStorage.js';
4
-
5
- describe('DatabaseMigrationStorage', () => {
6
- let storage: DatabaseMigrationStorage;
7
- let pool: QuerierPool;
8
- let querier: SqlQuerier;
9
- let mockAll: ReturnType<typeof jest.fn>;
10
- let mockRun: ReturnType<typeof jest.fn>;
11
-
12
- beforeEach(() => {
13
- mockAll = jest.fn<any>().mockResolvedValue([]);
14
- mockRun = jest.fn<any>().mockResolvedValue({});
15
- querier = {
16
- all: mockAll,
17
- run: mockRun,
18
- release: jest.fn<any>().mockResolvedValue(undefined),
19
- dialect: {
20
- escapeId: (id: string) => `"${id}"`,
21
- placeholder: (index: number) => `$${index}`,
22
- escapeIdChar: '"',
23
- },
24
- } as any;
25
-
26
- pool = {
27
- getQuerier: jest.fn<any>().mockResolvedValue(querier) as any,
28
- } as any;
29
-
30
- storage = new DatabaseMigrationStorage(pool);
31
- });
32
-
33
- it('ensureStorage should create table', async () => {
34
- await storage.ensureStorage();
35
-
36
- expect(querier.run).toHaveBeenCalledWith(expect.stringContaining('CREATE TABLE IF NOT EXISTS "uql_migrations"'));
37
- expect(querier.release).toHaveBeenCalled();
38
- });
39
-
40
- it('executed should return migration names', async () => {
41
- // 1. ensureStorage
42
- mockRun.mockResolvedValueOnce({} as any);
43
- // 2. executed query
44
- mockAll.mockResolvedValueOnce([{ name: 'm1' }, { name: 'm2' }]);
45
-
46
- const executed = await storage.executed();
47
-
48
- expect(executed).toEqual(['m1', 'm2']);
49
- expect(querier.all).toHaveBeenCalledWith(expect.stringContaining('SELECT "name" FROM "uql_migrations"'));
50
- });
51
-
52
- it('logWithQuerier should insert record', async () => {
53
- await storage.logWithQuerier(querier, 'm3');
54
-
55
- expect(querier.run).toHaveBeenCalledWith(
56
- expect.stringContaining('INSERT INTO "uql_migrations" ("name") VALUES ($1)'),
57
- ['m3'],
58
- );
59
- });
60
-
61
- it('unlogWithQuerier should delete record', async () => {
62
- await storage.unlogWithQuerier(querier, 'm3');
63
-
64
- expect(querier.run).toHaveBeenCalledWith(
65
- expect.stringContaining('DELETE FROM "uql_migrations" WHERE "name" = $1'),
66
- ['m3'],
67
- );
68
- });
69
- });
@@ -1,100 +0,0 @@
1
- import type { MigrationStorage, QuerierPool, SqlQuerier } from '../../type/index.js';
2
- import { isSqlQuerier } from '../../type/index.js';
3
-
4
- /**
5
- * Migration metadata stored in the database
6
- */
7
- interface MigrationRecord {
8
- name: string;
9
- executed_at: Date | number;
10
- }
11
-
12
- /**
13
- * Stores migration state in a database table.
14
- * Uses the querier's dialect for escaping and placeholders.
15
- */
16
- export class DatabaseMigrationStorage implements MigrationStorage {
17
- private readonly tableName: string;
18
- private storageInitialized = false;
19
-
20
- constructor(
21
- private readonly querierPool: QuerierPool,
22
- options: {
23
- tableName?: string;
24
- } = {},
25
- ) {
26
- this.tableName = options.tableName ?? 'uql_migrations';
27
- }
28
-
29
- async ensureStorage(): Promise<void> {
30
- if (this.storageInitialized) {
31
- return;
32
- }
33
-
34
- const querier = await this.querierPool.getQuerier();
35
-
36
- if (!isSqlQuerier(querier)) {
37
- await querier.release();
38
- throw new Error('DatabaseMigrationStorage requires a SQL-based querier');
39
- }
40
-
41
- try {
42
- await this.createTableIfNotExists(querier);
43
- this.storageInitialized = true;
44
- } finally {
45
- await querier.release();
46
- }
47
- }
48
-
49
- private async createTableIfNotExists(querier: SqlQuerier): Promise<void> {
50
- const { escapeId } = querier.dialect;
51
- const sql = `
52
- CREATE TABLE IF NOT EXISTS ${escapeId(this.tableName)} (
53
- ${escapeId('name')} VARCHAR(255) PRIMARY KEY,
54
- ${escapeId('executed_at')} TIMESTAMP DEFAULT CURRENT_TIMESTAMP
55
- )
56
- `;
57
-
58
- await querier.run(sql);
59
- }
60
-
61
- async executed(): Promise<string[]> {
62
- await this.ensureStorage();
63
-
64
- const querier = await this.querierPool.getQuerier();
65
-
66
- if (!isSqlQuerier(querier)) {
67
- await querier.release();
68
- throw new Error('DatabaseMigrationStorage requires a SQL-based querier');
69
- }
70
-
71
- try {
72
- const { escapeId } = querier.dialect;
73
- const sql = `SELECT ${escapeId('name')} FROM ${escapeId(this.tableName)} ORDER BY ${escapeId('name')} ASC`;
74
- const results = await querier.all<MigrationRecord>(sql);
75
- return results.map((r: any) => r.name);
76
- } finally {
77
- await querier.release();
78
- }
79
- }
80
-
81
- /**
82
- * Log a migration as executed - uses provided querier (within transaction)
83
- */
84
- async logWithQuerier(querier: SqlQuerier, migrationName: string): Promise<void> {
85
- await this.ensureStorage();
86
- const { escapeId, placeholder } = querier.dialect;
87
- const sql = `INSERT INTO ${escapeId(this.tableName)} (${escapeId('name')}) VALUES (${placeholder(1)})`;
88
- await querier.run(sql, [migrationName]);
89
- }
90
-
91
- /**
92
- * Unlog a migration - uses provided querier (within transaction)
93
- */
94
- async unlogWithQuerier(querier: SqlQuerier, migrationName: string): Promise<void> {
95
- await this.ensureStorage();
96
- const { escapeId, placeholder } = querier.dialect;
97
- const sql = `DELETE FROM ${escapeId(this.tableName)} WHERE ${escapeId('name')} = ${placeholder(1)}`;
98
- await querier.run(sql, [migrationName]);
99
- }
100
- }
@@ -1,2 +0,0 @@
1
- export * from './databaseStorage.js';
2
- export * from './jsonStorage.js';
@@ -1,58 +0,0 @@
1
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
- import { dirname } from 'node:path';
3
- import type { MigrationStorage, SqlQuerier } from '../../type/index.js';
4
-
5
- /**
6
- * Stores migration state in a JSON file (useful for development/testing)
7
- */
8
- export class JsonMigrationStorage implements MigrationStorage {
9
- private readonly filePath: string;
10
- private cache: string[] | null = null;
11
-
12
- constructor(filePath = './migrations/.uql-migrations.json') {
13
- this.filePath = filePath;
14
- }
15
-
16
- async ensureStorage(): Promise<void> {
17
- try {
18
- await this.load();
19
- } catch {
20
- // File doesn't exist, create it
21
- await this.save([]);
22
- }
23
- }
24
-
25
- async executed(): Promise<string[]> {
26
- await this.ensureStorage();
27
- return this.cache ?? [];
28
- }
29
-
30
- async logWithQuerier(_querier: SqlQuerier, migrationName: string): Promise<void> {
31
- const executed = await this.executed();
32
- if (!executed.includes(migrationName)) {
33
- executed.push(migrationName);
34
- executed.sort();
35
- await this.save(executed);
36
- }
37
- }
38
-
39
- async unlogWithQuerier(_querier: SqlQuerier, migrationName: string): Promise<void> {
40
- const executed = await this.executed();
41
- const index = executed.indexOf(migrationName);
42
- if (index !== -1) {
43
- executed.splice(index, 1);
44
- await this.save(executed);
45
- }
46
- }
47
-
48
- private async load(): Promise<void> {
49
- const content = await readFile(this.filePath, 'utf-8');
50
- this.cache = JSON.parse(content);
51
- }
52
-
53
- private async save(migrations: string[]): Promise<void> {
54
- await mkdir(dirname(this.filePath), { recursive: true });
55
- await writeFile(this.filePath, JSON.stringify(migrations, null, 2), 'utf-8');
56
- this.cache = migrations;
57
- }
58
- }