@uql/core 3.1.0 → 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 (170) hide show
  1. package/CHANGELOG.md +134 -176
  2. package/README.md +413 -0
  3. package/package.json +31 -26
  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
@@ -1,296 +0,0 @@
1
- import type {
2
- ColumnSchema,
3
- ForeignKeySchema,
4
- IndexSchema,
5
- QuerierPool,
6
- SchemaIntrospector,
7
- SqlQuerier,
8
- TableSchema,
9
- } from '../../type/index.js';
10
- import { isSqlQuerier } from '../../type/index.js';
11
-
12
- /**
13
- * SQLite schema introspector
14
- */
15
- export class SqliteSchemaIntrospector implements SchemaIntrospector {
16
- constructor(private readonly querierPool: QuerierPool) {}
17
-
18
- async getTableSchema(tableName: string): Promise<TableSchema | undefined> {
19
- const querier = await this.getQuerier();
20
-
21
- try {
22
- const exists = await this.tableExistsInternal(querier, tableName);
23
- if (!exists) {
24
- return undefined;
25
- }
26
-
27
- const [columns, indexes, foreignKeys, primaryKey] = await Promise.all([
28
- this.getColumns(querier, tableName),
29
- this.getIndexes(querier, tableName),
30
- this.getForeignKeys(querier, tableName),
31
- this.getPrimaryKey(querier, tableName),
32
- ]);
33
-
34
- return {
35
- name: tableName,
36
- columns,
37
- primaryKey,
38
- indexes,
39
- foreignKeys,
40
- };
41
- } finally {
42
- await querier.release();
43
- }
44
- }
45
-
46
- async getTableNames(): Promise<string[]> {
47
- const querier = await this.getQuerier();
48
-
49
- try {
50
- const sql = /*sql*/ `
51
- SELECT name
52
- FROM sqlite_master
53
- WHERE type = 'table'
54
- AND name NOT LIKE 'sqlite_%'
55
- ORDER BY name
56
- `;
57
-
58
- const results = await querier.all<{ name: string }>(sql);
59
- return results.map((r: any) => r.name);
60
- } finally {
61
- await querier.release();
62
- }
63
- }
64
-
65
- async tableExists(tableName: string): Promise<boolean> {
66
- const querier = await this.getQuerier();
67
-
68
- try {
69
- return this.tableExistsInternal(querier, tableName);
70
- } finally {
71
- await querier.release();
72
- }
73
- }
74
-
75
- private async tableExistsInternal(querier: SqlQuerier, tableName: string): Promise<boolean> {
76
- const sql = /*sql*/ `
77
- SELECT COUNT(*) as count
78
- FROM sqlite_master
79
- WHERE type = 'table'
80
- AND name = ?
81
- `;
82
-
83
- const results = await querier.all<{ count: number }>(sql, [tableName]);
84
- return (results[0]?.count ?? 0) > 0;
85
- }
86
-
87
- private async getQuerier(): Promise<SqlQuerier> {
88
- const querier = await this.querierPool.getQuerier();
89
-
90
- if (!isSqlQuerier(querier)) {
91
- await querier.release();
92
- throw new Error('SqliteSchemaIntrospector requires a SQL-based querier');
93
- }
94
-
95
- return querier;
96
- }
97
-
98
- private async getColumns(querier: SqlQuerier, tableName: string): Promise<ColumnSchema[]> {
99
- // SQLite uses PRAGMA for table info
100
- const sql = `PRAGMA table_info(${this.escapeId(tableName)})`;
101
-
102
- const results = await querier.all<{
103
- cid: number;
104
- name: string;
105
- type: string;
106
- notnull: number;
107
- dflt_value: string | null;
108
- pk: number;
109
- }>(sql);
110
-
111
- // Get unique columns from indexes
112
- const uniqueColumns = await this.getUniqueColumns(querier, tableName);
113
-
114
- return results.map((row: any) => ({
115
- name: row.name,
116
- type: this.normalizeType(row.type),
117
- nullable: row.notnull === 0,
118
- defaultValue: this.parseDefaultValue(row.dflt_value),
119
- isPrimaryKey: row.pk > 0,
120
- isAutoIncrement: row.pk > 0 && row.type.toUpperCase() === 'INTEGER',
121
- isUnique: uniqueColumns.has(row.name),
122
- length: this.extractLength(row.type),
123
- precision: undefined,
124
- scale: undefined,
125
- comment: undefined, // SQLite doesn't support column comments
126
- }));
127
- }
128
-
129
- private async getUniqueColumns(querier: SqlQuerier, tableName: string): Promise<Set<string>> {
130
- const sql = `PRAGMA index_list(${this.escapeId(tableName)})`;
131
-
132
- const indexes = await querier.all<{
133
- seq: number;
134
- name: string;
135
- unique: number;
136
- origin: string;
137
- partial: number;
138
- }>(sql);
139
-
140
- const uniqueColumns = new Set<string>();
141
-
142
- for (const index of indexes) {
143
- if (index.unique && index.origin === 'u') {
144
- const indexInfo = await querier.all<{ name: string }>(`PRAGMA index_info(${this.escapeId(index.name)})`);
145
- // Only single-column unique constraints
146
- if (indexInfo.length === 1) {
147
- uniqueColumns.add(indexInfo[0].name);
148
- }
149
- }
150
- }
151
-
152
- return uniqueColumns;
153
- }
154
-
155
- private async getIndexes(querier: SqlQuerier, tableName: string): Promise<IndexSchema[]> {
156
- const sql = `PRAGMA index_list(${this.escapeId(tableName)})`;
157
-
158
- const indexes = await querier.all<{
159
- seq: number;
160
- name: string;
161
- unique: number;
162
- origin: string;
163
- partial: number;
164
- }>(sql);
165
-
166
- const result: IndexSchema[] = [];
167
-
168
- for (const index of indexes) {
169
- // Skip auto-generated indexes (primary key, unique constraints)
170
- if (index.origin !== 'c') {
171
- continue;
172
- }
173
-
174
- const columns = await querier.all<{ name: string }>(`PRAGMA index_info(${this.escapeId(index.name)})`);
175
-
176
- result.push({
177
- name: index.name,
178
- columns: columns.map((c: any) => c.name),
179
- unique: Boolean(index.unique),
180
- });
181
- }
182
-
183
- return result;
184
- }
185
-
186
- private async getForeignKeys(querier: SqlQuerier, tableName: string): Promise<ForeignKeySchema[]> {
187
- const sql = `PRAGMA foreign_key_list(${this.escapeId(tableName)})`;
188
-
189
- const results = await querier.all<{
190
- id: number;
191
- seq: number;
192
- table: string;
193
- from: string;
194
- to: string;
195
- on_update: string;
196
- on_delete: string;
197
- match: string;
198
- }>(sql);
199
-
200
- // Group by id to handle composite foreign keys
201
- const grouped = new Map<number, typeof results>();
202
- for (const row of results) {
203
- const existing = grouped.get(row.id) ?? [];
204
- existing.push(row);
205
- grouped.set(row.id, existing);
206
- }
207
-
208
- return Array.from(grouped.entries()).map(([id, rows]) => {
209
- const first = rows[0];
210
- return {
211
- name: `fk_${tableName}_${id}`,
212
- columns: rows.map((r: any) => r.from),
213
- referencedTable: first.table,
214
- referencedColumns: rows.map((r: any) => r.to),
215
- onDelete: this.normalizeReferentialAction(first.on_delete),
216
- onUpdate: this.normalizeReferentialAction(first.on_update),
217
- };
218
- });
219
- }
220
-
221
- private async getPrimaryKey(querier: SqlQuerier, tableName: string): Promise<string[] | undefined> {
222
- const sql = `PRAGMA table_info(${this.escapeId(tableName)})`;
223
-
224
- const results = await querier.all<{
225
- cid: number;
226
- name: string;
227
- type: string;
228
- notnull: number;
229
- dflt_value: string | null;
230
- pk: number;
231
- }>(sql);
232
-
233
- const pkColumns = results.filter((r: any) => r.pk > 0).sort((a: any, b: any) => a.pk - b.pk);
234
-
235
- if (pkColumns.length === 0) {
236
- return undefined;
237
- }
238
-
239
- return pkColumns.map((r: any) => r.name);
240
- }
241
-
242
- private escapeId(identifier: string): string {
243
- return `\`${identifier.replace(/`/g, '``')}\``;
244
- }
245
-
246
- private normalizeType(type: string): string {
247
- // Extract base type without length/precision
248
- const match = type.match(/^([A-Za-z]+)/);
249
- return match ? match[1].toUpperCase() : type.toUpperCase();
250
- }
251
-
252
- private extractLength(type: string): number | undefined {
253
- const match = type.match(/\((\d+)\)/);
254
- return match ? Number.parseInt(match[1], 10) : undefined;
255
- }
256
-
257
- private parseDefaultValue(defaultValue: string | null): unknown {
258
- if (defaultValue === null) {
259
- return undefined;
260
- }
261
-
262
- // Check for common patterns
263
- if (defaultValue === 'NULL') {
264
- return null;
265
- }
266
- if (defaultValue === 'CURRENT_TIMESTAMP' || defaultValue === 'CURRENT_DATE' || defaultValue === 'CURRENT_TIME') {
267
- return defaultValue;
268
- }
269
- if (/^'.*'$/.test(defaultValue)) {
270
- return defaultValue.slice(1, -1);
271
- }
272
- if (/^-?\d+$/.test(defaultValue)) {
273
- return Number.parseInt(defaultValue, 10);
274
- }
275
- if (/^-?\d+\.\d+$/.test(defaultValue)) {
276
- return Number.parseFloat(defaultValue);
277
- }
278
-
279
- return defaultValue;
280
- }
281
-
282
- private normalizeReferentialAction(action: string): 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | undefined {
283
- switch (action.toUpperCase()) {
284
- case 'CASCADE':
285
- return 'CASCADE';
286
- case 'SET NULL':
287
- return 'SET NULL';
288
- case 'RESTRICT':
289
- return 'RESTRICT';
290
- case 'NO ACTION':
291
- return 'NO ACTION';
292
- default:
293
- return undefined;
294
- }
295
- }
296
- }
@@ -1,54 +0,0 @@
1
- import { beforeEach, describe, expect, it, jest } from 'bun:test';
2
- import { Entity, Field, Id } from '../entity/index.js';
3
- import type { QuerierPool } from '../type/index.js';
4
- import { Migrator } from './migrator.js';
5
-
6
- @Entity()
7
- class SyncMongoUser {
8
- @Id() id?: string;
9
- @Field({ index: true }) name?: string;
10
- }
11
-
12
- describe('Migrator autoSync MongoDB Integration', () => {
13
- let migrator: Migrator;
14
- let pool: QuerierPool;
15
- let db: any;
16
-
17
- beforeEach(() => {
18
- db = {
19
- listCollections: jest.fn<any>().mockReturnValue({
20
- toArray: jest.fn<any>().mockResolvedValue([]),
21
- }),
22
- createCollection: jest.fn<any>().mockResolvedValue({}),
23
- collection: jest.fn<any>().mockReturnValue({
24
- indexes: jest.fn<any>().mockResolvedValue([]),
25
- createIndex: jest.fn<any>().mockResolvedValue({}),
26
- }),
27
- };
28
-
29
- const querier = {
30
- db,
31
- release: jest.fn<any>().mockResolvedValue(undefined),
32
- };
33
-
34
- pool = {
35
- getQuerier: jest.fn<any>().mockResolvedValue(querier),
36
- dialect: 'mongodb',
37
- } as unknown as QuerierPool;
38
-
39
- migrator = new Migrator(pool, {
40
- entities: [SyncMongoUser],
41
- });
42
- });
43
-
44
- it('should generate createCollection and createIndex for MongoDB', async () => {
45
- await migrator.autoSync({ logging: true });
46
-
47
- expect(db.createCollection).toHaveBeenCalledWith('SyncMongoUser');
48
- expect(db.collection).toHaveBeenCalledWith('SyncMongoUser');
49
- expect(db.collection('SyncMongoUser').createIndex).toHaveBeenCalledWith(
50
- { name: 1 },
51
- expect.objectContaining({ name: 'idx_SyncMongoUser_name' }),
52
- );
53
- });
54
- });
@@ -1,255 +0,0 @@
1
- import { beforeEach, describe, expect, it, jest, mock } from 'bun:test';
2
- import { Entity, Id } from '../entity/index.js';
3
- import type { Migration, MigrationStorage, QuerierPool, SqlQuerier } from '../type/index.js';
4
- import { MongoSchemaGenerator } from './generator/mongoSchemaGenerator.js';
5
- import { MysqlSchemaGenerator } from './generator/mysqlSchemaGenerator.js';
6
- import { PostgresSchemaGenerator } from './generator/postgresSchemaGenerator.js';
7
- import { SqliteSchemaGenerator } from './generator/sqliteSchemaGenerator.js';
8
- import { MongoSchemaIntrospector } from './introspection/mongoIntrospector.js';
9
- import { MysqlSchemaIntrospector } from './introspection/mysqlIntrospector.js';
10
- import { PostgresSchemaIntrospector } from './introspection/postgresIntrospector.js';
11
- import { SqliteSchemaIntrospector } from './introspection/sqliteIntrospector.js';
12
- import { Migrator } from './migrator.js';
13
-
14
- mock.module('node:fs/promises', () => ({
15
- readdir: jest.fn<any>().mockResolvedValue([]) as any,
16
- mkdir: jest.fn<any>().mockResolvedValue(undefined) as any,
17
- writeFile: jest.fn<any>().mockResolvedValue(undefined) as any,
18
- }));
19
-
20
- describe('Migrator Core Methods', () => {
21
- let migrator: Migrator;
22
- let storage: MigrationStorage;
23
- let pool: QuerierPool;
24
- let querier: SqlQuerier;
25
- let mockExecuted: ReturnType<typeof jest.fn>;
26
-
27
- beforeEach(() => {
28
- querier = {
29
- beginTransaction: jest.fn<any>().mockResolvedValue(undefined),
30
- commitTransaction: jest.fn<any>().mockResolvedValue(undefined),
31
- rollbackTransaction: jest.fn<any>().mockResolvedValue(undefined),
32
- release: jest.fn<any>().mockResolvedValue(undefined),
33
- all: jest.fn<any>().mockResolvedValue([]),
34
- run: jest.fn<any>().mockResolvedValue({}),
35
- dialect: {
36
- escapeIdChar: '"',
37
- },
38
- } as any;
39
-
40
- pool = {
41
- getQuerier: jest.fn<any>().mockResolvedValue(querier),
42
- dialect: 'postgres',
43
- } as any;
44
-
45
- mockExecuted = jest.fn<any>().mockResolvedValue([]);
46
- storage = {
47
- executed: mockExecuted,
48
- logWithQuerier: jest.fn<any>().mockResolvedValue(undefined),
49
- unlogWithQuerier: jest.fn<any>().mockResolvedValue(undefined),
50
- ensureStorage: jest.fn<any>().mockResolvedValue(undefined),
51
- } as any;
52
-
53
- migrator = new Migrator(pool, { storage });
54
-
55
- // Mock getMigrations to return some dummy migrations
56
- const mockMigrations: Migration[] = [
57
- {
58
- name: '20250101000000_m1',
59
- up: jest.fn<any>().mockResolvedValue(undefined),
60
- down: jest.fn<any>().mockResolvedValue(undefined),
61
- },
62
- {
63
- name: '20250102000000_m2',
64
- up: jest.fn<any>().mockResolvedValue(undefined),
65
- down: jest.fn<any>().mockResolvedValue(undefined),
66
- },
67
- {
68
- name: '20250103000000_m3',
69
- up: jest.fn<any>().mockResolvedValue(undefined),
70
- down: jest.fn<any>().mockResolvedValue(undefined),
71
- },
72
- ];
73
- jest.spyOn(migrator, 'getMigrations').mockResolvedValue(mockMigrations);
74
- });
75
-
76
- it('pending should return non-executed migrations', async () => {
77
- mockExecuted.mockResolvedValue(['20250101000000_m1']);
78
-
79
- const pending = await migrator.pending();
80
-
81
- expect(pending).toHaveLength(2);
82
- expect(pending[0].name).toBe('20250102000000_m2');
83
- expect(pending[1].name).toBe('20250103000000_m3');
84
- });
85
-
86
- it('up should run all pending migrations', async () => {
87
- mockExecuted.mockResolvedValue(['20250101000000_m1']);
88
-
89
- const results = await migrator.up();
90
-
91
- expect(results).toHaveLength(2);
92
- expect(results[0].name).toBe('20250102000000_m2');
93
- expect(results[1].name).toBe('20250103000000_m3');
94
-
95
- const migrations = await migrator.getMigrations();
96
- expect(migrations[1].up).toHaveBeenCalled();
97
- expect(migrations[2].up).toHaveBeenCalled();
98
- expect(storage.logWithQuerier).toHaveBeenCalledTimes(2);
99
- });
100
-
101
- it('up to a specific migration', async () => {
102
- mockExecuted.mockResolvedValue([]);
103
-
104
- const results = await migrator.up({ to: '20250102000000_m2' });
105
-
106
- expect(results).toHaveLength(2);
107
- expect(results[0].name).toBe('20250101000000_m1');
108
- expect(results[1].name).toBe('20250102000000_m2');
109
- });
110
-
111
- it('down should rollback the last migration', async () => {
112
- mockExecuted.mockResolvedValue(['20250101000000_m1', '20250102000000_m2']);
113
-
114
- const results = await migrator.down({ step: 1 });
115
-
116
- expect(results).toHaveLength(1);
117
- expect(results[0].name).toBe('20250102000000_m2');
118
-
119
- const migrations = await migrator.getMigrations();
120
- expect(migrations[1].down).toHaveBeenCalled();
121
- expect(storage.unlogWithQuerier).toHaveBeenCalledTimes(1);
122
- });
123
-
124
- it('down to a specific migration', async () => {
125
- mockExecuted.mockResolvedValue(['20250101000000_m1', '20250102000000_m2', '20250103000000_m3']);
126
-
127
- // From current state to m1 (inclusive), so roll back m3 and m2
128
- const results = await migrator.down({ to: '20250102000000_m2' });
129
-
130
- expect(results).toHaveLength(2);
131
- expect(results[0].name).toBe('20250103000000_m3');
132
- expect(results[1].name).toBe('20250102000000_m2');
133
- });
134
-
135
- it('generateFromEntities should create a migration file', async () => {
136
- @Entity()
137
- class DummyEntity {
138
- @Id() id?: number;
139
- }
140
-
141
- migrator = new Migrator(pool, { entities: [DummyEntity] });
142
- jest.spyOn(migrator, 'getMigrations').mockResolvedValue([]);
143
-
144
- const generator = {
145
- resolveTableName: jest.fn<any>().mockReturnValue('DiffUser'),
146
- diffSchema: jest.fn<any>().mockReturnValue({
147
- tableName: 'DiffUser',
148
- type: 'alter',
149
- columnsToAdd: [{ name: 'age', type: 'INTEGER' }],
150
- }),
151
- generateAlterTable: jest.fn<any>().mockReturnValue(['ALTER TABLE "DiffUser" ADD COLUMN "age" INTEGER;']),
152
- generateAlterTableDown: jest.fn<any>().mockReturnValue(['ALTER TABLE "DiffUser" DROP COLUMN "age";']),
153
- };
154
- migrator.setSchemaGenerator(generator as any);
155
-
156
- const introspector = {
157
- getTableSchema: jest.fn<any>().mockResolvedValue({ name: 'DiffUser', columns: [] }),
158
- getTableNames: jest.fn<any>().mockResolvedValue([]),
159
- tableExists: jest.fn<any>().mockResolvedValue(true),
160
- };
161
- migrator.schemaIntrospector = introspector as any;
162
-
163
- const { mkdir, writeFile } = await import('node:fs/promises');
164
-
165
- const filePath = await migrator.generateFromEntities('add_age');
166
-
167
- expect(filePath).not.toBe('');
168
- expect(filePath).toContain('add_age.ts');
169
- expect(mkdir).toHaveBeenCalled();
170
- expect(writeFile).toHaveBeenCalledWith(
171
- expect.any(String),
172
- expect.stringContaining('ALTER TABLE "DiffUser" ADD COLUMN "age" INTEGER;'),
173
- 'utf-8',
174
- );
175
- });
176
-
177
- it('status should return pending and executed migrations', async () => {
178
- mockExecuted.mockResolvedValue(['20250101000000_m1']);
179
-
180
- const status = await migrator.status();
181
-
182
- expect(status.pending).toEqual(['20250102000000_m2', '20250103000000_m3']);
183
- expect(status.executed).toEqual(['20250101000000_m1']);
184
- });
185
-
186
- it('sync should call autoSync by default', async () => {
187
- const autoSyncSpy = jest.spyOn(migrator, 'autoSync').mockResolvedValue(undefined);
188
- await migrator.sync();
189
- expect(autoSyncSpy).toHaveBeenCalledWith({ safe: true });
190
- });
191
-
192
- it('syncForce should drop and create tables', async () => {
193
- @Entity()
194
- class SyncEntity {
195
- @Id() id?: number;
196
- }
197
- migrator = new Migrator(pool, { entities: [SyncEntity] });
198
-
199
- const generator = {
200
- resolveTableName: jest.fn<any>().mockReturnValue('SyncEntity'),
201
- generateDropTable: jest.fn<any>().mockReturnValue('DROP TABLE "SyncEntity"'),
202
- generateCreateTable: jest.fn<any>().mockReturnValue('CREATE TABLE "SyncEntity"'),
203
- generateAlterTable: jest.fn<any>(),
204
- generateAlterTableDown: jest.fn<any>(),
205
- diffSchema: jest.fn<any>(),
206
- };
207
- migrator.setSchemaGenerator(generator as any);
208
-
209
- await migrator.syncForce();
210
-
211
- expect(querier.run).toHaveBeenCalledWith('DROP TABLE "SyncEntity"');
212
- expect(querier.run).toHaveBeenCalledWith('CREATE TABLE "SyncEntity"');
213
- expect(querier.commitTransaction).toHaveBeenCalled();
214
- });
215
-
216
- describe('Dialect Auto-Inference', () => {
217
- it('should infer Postgres generator and introspector', () => {
218
- const m = new Migrator(pool);
219
- expect((m as any).schemaGenerator).toBeInstanceOf(PostgresSchemaGenerator);
220
- expect((m as any).schemaIntrospector).toBeInstanceOf(PostgresSchemaIntrospector);
221
- });
222
-
223
- it('should infer MySQL generator and introspector', () => {
224
- const mysqlPool = { ...pool, dialect: 'mysql' };
225
- const m = new Migrator(mysqlPool as any);
226
- expect((m as any).schemaGenerator).toBeInstanceOf(MysqlSchemaGenerator);
227
- expect((m as any).schemaIntrospector).toBeInstanceOf(MysqlSchemaIntrospector);
228
- });
229
-
230
- it('should infer SQLite generator and introspector', () => {
231
- const sqlitePool = { ...pool, dialect: 'sqlite' };
232
- const m = new Migrator(sqlitePool as any);
233
- expect((m as any).schemaGenerator).toBeInstanceOf(SqliteSchemaGenerator);
234
- expect((m as any).schemaIntrospector).toBeInstanceOf(SqliteSchemaIntrospector);
235
- });
236
-
237
- it('should infer MongoDB generator and introspector', () => {
238
- const mongoPool = { ...pool, dialect: 'mongodb' };
239
- const m = new Migrator(mongoPool as any);
240
- expect((m as any).schemaGenerator).toBeInstanceOf(MongoSchemaGenerator);
241
- expect((m as any).schemaIntrospector).toBeInstanceOf(MongoSchemaIntrospector);
242
- });
243
-
244
- it('should allow overriding dialect in options', () => {
245
- const m = new Migrator(pool, { dialect: 'mysql' });
246
- expect((m as any).schemaGenerator).toBeInstanceOf(MysqlSchemaGenerator);
247
- });
248
-
249
- it('should allow overriding generator in options', () => {
250
- const customGenerator = {} as any;
251
- const m = new Migrator(pool, { dialect: 'postgres', schemaGenerator: customGenerator });
252
- expect((m as any).schemaGenerator).toBe(customGenerator);
253
- });
254
- });
255
- });
@@ -1,94 +0,0 @@
1
- import { beforeEach, describe, expect, it, jest } from 'bun:test';
2
- import { Entity, Field, Id } from '../entity/index.js';
3
- import type { QuerierPool, SqlQuerier, TableSchema } from '../type/index.js';
4
- import { Migrator } from './migrator.js';
5
-
6
- @Entity()
7
- class SyncUser {
8
- @Id() id?: number;
9
- @Field() name?: string;
10
- }
11
-
12
- @Entity()
13
- class SyncProfile {
14
- @Id() id?: number;
15
- @Field() bio?: string;
16
- @Field({ reference: () => SyncUser }) userId?: number;
17
- }
18
-
19
- describe('Migrator autoSync Integration', () => {
20
- let migrator: Migrator;
21
- let pool: QuerierPool;
22
-
23
- beforeEach(() => {
24
- // Mock pool and querier for testing
25
- const querier = {
26
- run: jest.fn<any>().mockResolvedValue({}),
27
- all: jest.fn<any>().mockResolvedValue([]),
28
- beginTransaction: jest.fn<any>().mockResolvedValue(undefined),
29
- commitTransaction: jest.fn<any>().mockResolvedValue(undefined),
30
- rollbackTransaction: jest.fn<any>().mockResolvedValue(undefined),
31
- release: jest.fn<any>().mockResolvedValue(undefined),
32
- dialect: {
33
- escapeIdChar: '"',
34
- placeholder: jest.fn<any>().mockReturnValue('?'),
35
- },
36
- };
37
- pool = {
38
- getQuerier: jest.fn<any>().mockResolvedValue(querier),
39
- dialect: 'postgres',
40
- } as unknown as QuerierPool;
41
-
42
- migrator = new Migrator(pool, {
43
- entities: [SyncUser, SyncProfile],
44
- });
45
- });
46
-
47
- it('should generate create statements for new tables', async () => {
48
- // Mock introspector to return nothing
49
- const introspector = {
50
- getTableSchema: jest.fn<any>().mockResolvedValue(undefined),
51
- getTableNames: jest.fn<any>().mockResolvedValue([]),
52
- tableExists: jest.fn<any>().mockResolvedValue(false),
53
- };
54
- migrator.schemaIntrospector = introspector as any;
55
-
56
- await migrator.autoSync({ logging: true });
57
-
58
- const querier = (await pool.getQuerier()) as SqlQuerier;
59
- expect(querier.run).toHaveBeenCalledWith(expect.stringContaining('CREATE TABLE "SyncUser"'));
60
- expect(querier.run).toHaveBeenCalledWith(expect.stringContaining('CREATE TABLE "SyncProfile"'));
61
- });
62
-
63
- it('should generate alter statements for missing columns', async () => {
64
- // Mock introspector to return existing table with one column missing
65
- const introspector = {
66
- getTableSchema: jest.fn<any>().mockImplementation((name: any) => {
67
- if (name === 'SyncUser') {
68
- return Promise.resolve({
69
- name: 'SyncUser',
70
- columns: [
71
- {
72
- name: 'id',
73
- type: 'INTEGER',
74
- nullable: false,
75
- isPrimaryKey: true,
76
- isAutoIncrement: true,
77
- isUnique: false,
78
- },
79
- ],
80
- } as TableSchema);
81
- }
82
- return Promise.resolve(undefined);
83
- }),
84
- };
85
- migrator.schemaIntrospector = introspector as any;
86
-
87
- await migrator.autoSync({ logging: true });
88
-
89
- const querier = (await pool.getQuerier()) as SqlQuerier;
90
- expect(querier.run).toHaveBeenCalledWith(
91
- expect.stringContaining('ALTER TABLE "SyncUser" ADD COLUMN "name" VARCHAR(255)'),
92
- );
93
- });
94
- });