@uql/core 3.1.4 → 3.4.0
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.
- package/CHANGELOG.md +6 -3
- package/README.md +40 -26
- package/dist/browser/uql-browser.min.js +112 -123
- package/dist/browser/uql-browser.min.js.map +1 -1
- package/dist/d1/d1Querier.d.ts +49 -0
- package/dist/d1/d1Querier.d.ts.map +1 -0
- package/dist/d1/d1Querier.js +40 -0
- package/dist/d1/d1Querier.js.map +1 -0
- package/dist/d1/d1QuerierPool.d.ts +10 -0
- package/dist/d1/d1QuerierPool.d.ts.map +1 -0
- package/dist/d1/d1QuerierPool.js +16 -0
- package/dist/d1/d1QuerierPool.js.map +1 -0
- package/dist/d1/index.d.ts +3 -0
- package/dist/d1/index.d.ts.map +1 -0
- package/dist/d1/index.js +3 -0
- package/dist/d1/index.js.map +1 -0
- package/dist/libsql/index.d.ts +3 -0
- package/dist/libsql/index.d.ts.map +1 -0
- package/dist/libsql/index.js +3 -0
- package/dist/libsql/index.js.map +1 -0
- package/dist/libsql/libsqlQuerier.d.ts +21 -0
- package/dist/libsql/libsqlQuerier.d.ts.map +1 -0
- package/dist/libsql/libsqlQuerier.js +81 -0
- package/dist/libsql/libsqlQuerier.js.map +1 -0
- package/dist/libsql/libsqlQuerierPool.d.ts +12 -0
- package/dist/libsql/libsqlQuerierPool.d.ts.map +1 -0
- package/dist/libsql/libsqlQuerierPool.js +19 -0
- package/dist/libsql/libsqlQuerierPool.js.map +1 -0
- package/dist/migrate/cli.js +7 -7
- package/dist/migrate/cli.js.map +1 -1
- package/dist/migrate/introspection/mongoIntrospector.d.ts +2 -2
- package/dist/migrate/introspection/mongoIntrospector.d.ts.map +1 -1
- package/dist/migrate/introspection/mongoIntrospector.js +5 -5
- package/dist/migrate/introspection/mongoIntrospector.js.map +1 -1
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +2 -2
- package/dist/migrate/introspection/mysqlIntrospector.d.ts.map +1 -1
- package/dist/migrate/introspection/mysqlIntrospector.js +4 -4
- package/dist/migrate/introspection/mysqlIntrospector.js.map +1 -1
- package/dist/migrate/introspection/postgresIntrospector.d.ts +2 -2
- package/dist/migrate/introspection/postgresIntrospector.d.ts.map +1 -1
- package/dist/migrate/introspection/postgresIntrospector.js +4 -4
- package/dist/migrate/introspection/postgresIntrospector.js.map +1 -1
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +2 -2
- package/dist/migrate/introspection/sqliteIntrospector.d.ts.map +1 -1
- package/dist/migrate/introspection/sqliteIntrospector.js +4 -4
- package/dist/migrate/introspection/sqliteIntrospector.js.map +1 -1
- package/dist/migrate/migrator.d.ts +2 -2
- package/dist/migrate/migrator.d.ts.map +1 -1
- package/dist/migrate/migrator.js +13 -13
- package/dist/migrate/migrator.js.map +1 -1
- package/dist/migrate/storage/databaseStorage.d.ts +2 -2
- package/dist/migrate/storage/databaseStorage.d.ts.map +1 -1
- package/dist/migrate/storage/databaseStorage.js +5 -5
- package/dist/migrate/storage/databaseStorage.js.map +1 -1
- package/dist/neon/index.d.ts +3 -0
- package/dist/neon/index.d.ts.map +1 -0
- package/dist/neon/index.js +3 -0
- package/dist/neon/index.js.map +1 -0
- package/dist/neon/neonQuerier.d.ts +18 -0
- package/dist/neon/neonQuerier.d.ts.map +1 -0
- package/dist/neon/neonQuerier.js +40 -0
- package/dist/neon/neonQuerier.js.map +1 -0
- package/dist/neon/neonQuerierPool.d.ts +11 -0
- package/dist/neon/neonQuerierPool.d.ts.map +1 -0
- package/dist/neon/neonQuerierPool.js +17 -0
- package/dist/neon/neonQuerierPool.js.map +1 -0
- package/dist/querier/decorator/transactional.d.ts +2 -2
- package/dist/querier/decorator/transactional.d.ts.map +1 -1
- package/dist/querier/decorator/transactional.js +2 -2
- package/dist/querier/decorator/transactional.js.map +1 -1
- package/package.json +16 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
# [3.4.0](https://github.com/rogerpadilla/uql/compare/@uql/core@3.3.0...@uql/core@3.4.0) (2025-12-30)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add support for Neon, LibSQL, and D1 database queriers, including new querier pools, implementations, tests, and updated documentation. ([aad1df0](https://github.com/rogerpadilla/uql/commit/aad1df0f6bab112b2a0eb0fdffee15dcbc6b0824))
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
|
|
@@ -192,7 +195,7 @@ Reflect major changes in the package structure and dependencies.
|
|
|
192
195
|
- Update dependencies.
|
|
193
196
|
|
|
194
197
|
```ts
|
|
195
|
-
const ids = await
|
|
198
|
+
const ids = await pool.transaction(async (querier) => {
|
|
196
199
|
const data = await querier.findMany(...);
|
|
197
200
|
const ids = await querier.insertMany(...);
|
|
198
201
|
return ids;
|
package/README.md
CHANGED
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://uql.app)
|
|
4
4
|
|
|
5
|
-
[](https://github.com/rogerpadilla/uql) [](https://github.com/rogerpadilla/uql) [](https://coveralls.io/github/rogerpadilla/uql?branch=main) [](https://github.com/rogerpadilla/uql/blob/main/LICENSE) [](https://www.npmjs.com/package/@uql/core)
|
|
6
6
|
|
|
7
|
-
[UQL](https://uql.app) is the [smartest ORM](https://medium.com/@rogerpadillac/in-search-of-the-perfect-orm-e01fcc9bce3d) for TypeScript
|
|
7
|
+
**[UQL](https://uql.app)** is the [smartest ORM](https://medium.com/@rogerpadillac/in-search-of-the-perfect-orm-e01fcc9bce3d) for TypeScript. It is engineered to be **fast**, **safe**, and **universally compatible**.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Uses a consistent API for distinct databases, including PostgreSQL, MySQL, MariaDB, and SQLite (inspired by MongoDB glorious syntax).
|
|
9
|
+
- **Runs Everywhere**: Node.js, Bun, Deno, Cloudflare Workers, Electron, React Native, and even the Browser.
|
|
10
|
+
- **Unified API**: A consistent, expressive query interface for PostgreSQL, MySQL, MariaDB, SQLite, LibSQL, Neon, Cloudflare D1, and MongoDB (inspired by its glorious syntax).
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
|
|
@@ -33,7 +32,7 @@ See [this article](https://medium.com/@rogerpadillac/in-search-of-the-perfect-or
|
|
|
33
32
|
|
|
34
33
|
- **Type-safe and Context-aware queries**: Squeeze the power of `TypeScript` for auto-completion and validation of operators at any depth, [including relations and their fields](https://www.uql.app/docs/querying-relations).
|
|
35
34
|
- **Context-Object SQL Generation**: Uses a sophisticated `QueryContext` pattern to ensure perfectly indexed placeholders ($1, $2, etc.) and robust SQL fragment management, even in the most complex sub-queries.
|
|
36
|
-
- **Unified API across Databases**: Write once, run anywhere. Seamlessly switch between `PostgreSQL`, `MySQL`, `MariaDB`, `SQLite`, and even `MongoDB`.
|
|
35
|
+
- **Unified API across Databases**: Write once, run anywhere. Seamlessly switch between `PostgreSQL`, `MySQL`, `MariaDB`, `SQLite`, `LibSQL`, `Neon`, `Cloudflare D1`, and even `MongoDB`.
|
|
37
36
|
- **Serializable JSON Syntax**: Queries can be expressed as `100%` valid `JSON`, allowing them to be easily transported from frontend to backend.
|
|
38
37
|
- **Naming Strategies**: Effortlessly translate between TypeScript `CamelCase` and database `snake_case` (or any custom format) with a pluggable system.
|
|
39
38
|
- **Built-in Serialization**: A centralized task queue and `@Serialized()` decorator ensure database operations are thread-safe and race-condition free by default.
|
|
@@ -56,12 +55,15 @@ See [this article](https://medium.com/@rogerpadillac/in-search-of-the-perfect-or
|
|
|
56
55
|
|
|
57
56
|
2. Install one of the specific adapters for your database:
|
|
58
57
|
|
|
59
|
-
| Database
|
|
60
|
-
|
|
|
61
|
-
| `PostgreSQL` | `pg`
|
|
62
|
-
| `
|
|
63
|
-
| `MariaDB`
|
|
64
|
-
| `
|
|
58
|
+
| Database | Driver
|
|
59
|
+
| :--- | :---
|
|
60
|
+
| `PostgreSQL` (incl. CockroachDB, YugabyteDB) | `pg`
|
|
61
|
+
| `MySQL` (incl. TiDB, Aurora) | `mysql2`
|
|
62
|
+
| `MariaDB` | `mariadb`
|
|
63
|
+
| `SQLite` | `better-sqlite3`
|
|
64
|
+
| `Cloudflare D1` | `Native Binding`
|
|
65
|
+
| `LibSQL` (Turso) | `@libsql/client`
|
|
66
|
+
| `Neon` (Serverless Postgres) | `@neondatabase/serverless`
|
|
65
67
|
|
|
66
68
|
|
|
67
69
|
|
|
@@ -92,11 +94,12 @@ bun add pg
|
|
|
92
94
|
Annotate your classes with decorators from `@uql/core`. UQL supports detailed schema metadata for precise DDL generation.
|
|
93
95
|
|
|
94
96
|
```ts
|
|
97
|
+
import { v7 as uuidv7 } from 'uuid';
|
|
95
98
|
import { Entity, Id, Field, OneToOne, OneToMany, ManyToOne, ManyToMany, type Relation } from '@uql/core';
|
|
96
99
|
|
|
97
100
|
@Entity()
|
|
98
101
|
export class User {
|
|
99
|
-
@Id()
|
|
102
|
+
@Id({ onInsert: () => uuidv7() })
|
|
100
103
|
id?: string;
|
|
101
104
|
|
|
102
105
|
@Field({ length: 100, index: true })
|
|
@@ -114,7 +117,7 @@ export class User {
|
|
|
114
117
|
|
|
115
118
|
@Entity()
|
|
116
119
|
export class Profile {
|
|
117
|
-
@Id()
|
|
120
|
+
@Id({ onInsert: () => uuidv7() })
|
|
118
121
|
id?: string;
|
|
119
122
|
|
|
120
123
|
@Field()
|
|
@@ -147,7 +150,7 @@ export class Post {
|
|
|
147
150
|
|
|
148
151
|
@Entity()
|
|
149
152
|
export class Tag {
|
|
150
|
-
@Id()
|
|
153
|
+
@Id({ onInsert: () => uuidv7() })
|
|
151
154
|
id?: string;
|
|
152
155
|
|
|
153
156
|
@Field()
|
|
@@ -156,7 +159,7 @@ export class Tag {
|
|
|
156
159
|
|
|
157
160
|
@Entity()
|
|
158
161
|
export class PostTag {
|
|
159
|
-
@Id()
|
|
162
|
+
@Id({ onInsert: () => uuidv7() })
|
|
160
163
|
id?: string;
|
|
161
164
|
|
|
162
165
|
@Field({ reference: () => Post })
|
|
@@ -178,7 +181,7 @@ A querier-pool can be set in any of the bootstrap files of your app (e.g. in the
|
|
|
178
181
|
import { SnakeCaseNamingStrategy } from '@uql/core';
|
|
179
182
|
import { PgQuerierPool } from '@uql/core/postgres';
|
|
180
183
|
|
|
181
|
-
export const
|
|
184
|
+
export const pool = new PgQuerierPool(
|
|
182
185
|
{
|
|
183
186
|
host: 'localhost',
|
|
184
187
|
user: 'theUser',
|
|
@@ -211,10 +214,10 @@ Repositories provide a clean, Data-Mapper style interface for your entities.
|
|
|
211
214
|
```ts
|
|
212
215
|
import { GenericRepository } from '@uql/core';
|
|
213
216
|
import { User } from './shared/models/index.js';
|
|
214
|
-
import {
|
|
217
|
+
import { pool } from './shared/orm.js';
|
|
215
218
|
|
|
216
219
|
// Get a querier from the pool
|
|
217
|
-
const querier = await
|
|
220
|
+
const querier = await pool.getQuerier();
|
|
218
221
|
|
|
219
222
|
try {
|
|
220
223
|
const userRepository = new GenericRepository(User, querier);
|
|
@@ -246,10 +249,10 @@ UQL's query syntax is context-aware. When you query a relation, the available fi
|
|
|
246
249
|
|
|
247
250
|
```ts
|
|
248
251
|
import { GenericRepository } from '@uql/core';
|
|
249
|
-
import {
|
|
252
|
+
import { pool } from './shared/orm.js';
|
|
250
253
|
import { User } from './shared/models/index.js';
|
|
251
254
|
|
|
252
|
-
const authorsWithPopularPosts = await
|
|
255
|
+
const authorsWithPopularPosts = await pool.transaction(async (querier) => {
|
|
253
256
|
const userRepository = new GenericRepository(User, querier);
|
|
254
257
|
|
|
255
258
|
return userRepository.findMany({
|
|
@@ -282,6 +285,7 @@ const authorsWithPopularPosts = await querierPool.transaction(async (querier) =>
|
|
|
282
285
|
Define complex logic directly in your entities using `raw` functions from `uql/util`. These are highly efficient as they are resolved during SQL generation.
|
|
283
286
|
|
|
284
287
|
```ts
|
|
288
|
+
import { v7 as uuidv7 } from 'uuid';
|
|
285
289
|
import { Entity, Id, Field, raw } from '@uql/core';
|
|
286
290
|
import { ItemTag } from './shared/models/index.js';
|
|
287
291
|
|
|
@@ -313,10 +317,10 @@ export class Item {
|
|
|
313
317
|
UQL ensures your operations are serialized and thread-safe.
|
|
314
318
|
|
|
315
319
|
```ts
|
|
316
|
-
import {
|
|
320
|
+
import { pool } from './shared/orm.js';
|
|
317
321
|
import { User, Profile } from './shared/models/index.js';
|
|
318
322
|
|
|
319
|
-
const result = await
|
|
323
|
+
const result = await pool.transaction(async (querier) => {
|
|
320
324
|
const user = await querier.findOne(User, { $where: { email: '...' } });
|
|
321
325
|
const profileId = await querier.insertOne(Profile, { userId: user.id, ... });
|
|
322
326
|
return { userId: user.id, profileId };
|
|
@@ -339,7 +343,7 @@ import { PgQuerierPool } from '@uql/core/postgres';
|
|
|
339
343
|
import { User, Post } from './shared/models/index.js';
|
|
340
344
|
|
|
341
345
|
export default {
|
|
342
|
-
|
|
346
|
+
pool: new PgQuerierPool({ /* config */ }),
|
|
343
347
|
entities: [User, Post],
|
|
344
348
|
migrationsPath: './migrations',
|
|
345
349
|
};
|
|
@@ -377,9 +381,9 @@ In development, you can use `autoSync` to automatically keep your database in sy
|
|
|
377
381
|
|
|
378
382
|
```ts
|
|
379
383
|
import { Migrator } from '@uql/core/migrate';
|
|
380
|
-
import {
|
|
384
|
+
import { pool } from './shared/orm.js';
|
|
381
385
|
|
|
382
|
-
const migrator = new Migrator(
|
|
386
|
+
const migrator = new Migrator(pool);
|
|
383
387
|
await migrator.autoSync({ logging: true });
|
|
384
388
|
```
|
|
385
389
|
|
|
@@ -406,3 +410,13 @@ For those who want to see the "engine under the hood," check out these resources
|
|
|
406
410
|
- [PostgreSQL Spec](https://github.com/rogerpadilla/uql/blob/main/packages/core/src/postgres/postgresDialect.spec.ts) | [MySQL Spec](https://github.com/rogerpadilla/uql/blob/main/packages/core/src/mysql/mysqlDialect.spec.ts) | [SQLite Spec](https://github.com/rogerpadilla/uql/blob/main/packages/core/src/sqlite/sqliteDialect.spec.ts).
|
|
407
411
|
- [Querier Integration Tests](https://github.com/rogerpadilla/uql/blob/main/packages/core/src/querier/abstractSqlQuerier-spec.ts): Testing the interaction between SQL generation and connection management.
|
|
408
412
|
- [MongoDB Migration Tests](https://github.com/rogerpadilla/uql/blob/main/packages/core/src/migrate/migrator-mongo.it.ts): Integration tests ensuring correct collection and index synchronization for MongoDB.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Built with ❤️ and supported by
|
|
417
|
+
|
|
418
|
+
UQL is an open-source project driven by the community and proudly sponsored by **[Variability.ai](https://variability.ai)**.
|
|
419
|
+
|
|
420
|
+
> "Intelligence in Every Fluctuation"
|
|
421
|
+
|
|
422
|
+
Their support helps us maintain and evolve the "Smartest ORM" for developers everywhere. Thank you for being part of our journey!
|
|
@@ -1,108 +1,97 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { isSqlQuerier } from '../type/index.js';
|
|
2
|
+
export { isSqlQuerier } from '../type/index.js';
|
|
3
|
+
import { AbstractDialect } from '../dialect/index.js';
|
|
4
|
+
import { getMeta, getEntities } from '../entity/index.js';
|
|
5
|
+
import { escapeSqlId, getKeys } from '../util/index.js';
|
|
6
|
+
import { readdir, readFile, mkdir, writeFile } from 'node:fs/promises';
|
|
7
|
+
import { join, basename, extname, dirname } from 'node:path';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (value instanceof Date) {
|
|
12
|
-
value = value.getTime();
|
|
13
|
-
} else if (typeof value === 'boolean') {
|
|
14
|
-
value = value ? 1 : 0;
|
|
15
|
-
}
|
|
16
|
-
return super.addValue(values, value);
|
|
17
|
-
}
|
|
18
|
-
compare(ctx, entity, key, val, opts) {
|
|
19
|
-
if (key === '$text') {
|
|
20
|
-
const meta = getMeta(entity);
|
|
21
|
-
const search = val;
|
|
22
|
-
const fields = search.$fields.map((fKey)=>{
|
|
23
|
-
const field = meta.fields[fKey];
|
|
24
|
-
const columnName = this.resolveColumnName(fKey, field);
|
|
25
|
-
return this.escapeId(columnName);
|
|
26
|
-
});
|
|
27
|
-
const tableName = this.resolveTableName(entity, meta);
|
|
28
|
-
ctx.append(`${this.escapeId(tableName)} MATCH {${fields.join(' ')}} : `);
|
|
29
|
-
ctx.addValue(search.$value);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
super.compare(ctx, entity, key, val, opts);
|
|
10
|
+
/**
|
|
11
|
+
* Abstract base class for SQL schema generation
|
|
12
|
+
*/ class AbstractSchemaGenerator extends AbstractDialect {
|
|
13
|
+
constructor(namingStrategy, escapeIdChar = '`'){
|
|
14
|
+
super(namingStrategy), this.escapeIdChar = escapeIdChar;
|
|
33
15
|
}
|
|
34
|
-
|
|
35
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Escape an identifier (table name, column name, etc.)
|
|
18
|
+
*/ escapeId(identifier) {
|
|
19
|
+
return escapeSqlId(identifier, this.escapeIdChar);
|
|
36
20
|
}
|
|
37
|
-
|
|
21
|
+
generateCreateTable(entity, options = {}) {
|
|
38
22
|
const meta = getMeta(entity);
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
23
|
+
const tableName = this.resolveTableName(entity, meta);
|
|
24
|
+
const columns = this.generateColumnDefinitions(meta);
|
|
25
|
+
const constraints = this.generateTableConstraints(meta);
|
|
26
|
+
const ifNotExists = options.ifNotExists ? 'IF NOT EXISTS ' : '';
|
|
27
|
+
let sql = `CREATE TABLE ${ifNotExists}${this.escapeId(tableName)} (\n`;
|
|
28
|
+
sql += columns.map((col)=>` ${col}`).join(',\n');
|
|
29
|
+
if (constraints.length > 0) {
|
|
30
|
+
sql += ',\n';
|
|
31
|
+
sql += constraints.map((c)=>` ${c}`).join(',\n');
|
|
32
|
+
}
|
|
33
|
+
sql += '\n)';
|
|
34
|
+
sql += this.getTableOptions(meta);
|
|
35
|
+
sql += ';';
|
|
36
|
+
return sql;
|
|
47
37
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
super(new SqliteDialect(extra?.namingStrategy)), this.db = db, this.extra = extra;
|
|
53
|
-
}
|
|
54
|
-
async internalAll(query, values) {
|
|
55
|
-
this.extra?.logger?.(query, values);
|
|
56
|
-
return this.db.prepare(query).all(values || []);
|
|
57
|
-
}
|
|
58
|
-
async internalRun(query, values) {
|
|
59
|
-
this.extra?.logger?.(query, values);
|
|
60
|
-
const { changes, lastInsertRowid } = this.db.prepare(query).run(values || []);
|
|
61
|
-
const firstId = lastInsertRowid ? Number(lastInsertRowid) - (changes - 1) : undefined;
|
|
62
|
-
const ids = firstId ? Array(changes).fill(firstId).map((i, index)=>i + index) : [];
|
|
63
|
-
return {
|
|
64
|
-
changes,
|
|
65
|
-
ids,
|
|
66
|
-
firstId
|
|
67
|
-
};
|
|
38
|
+
generateDropTable(entity) {
|
|
39
|
+
const meta = getMeta(entity);
|
|
40
|
+
const tableName = this.resolveTableName(entity, meta);
|
|
41
|
+
return `DROP TABLE IF EXISTS ${this.escapeId(tableName)};`;
|
|
68
42
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
43
|
+
generateAlterTable(diff) {
|
|
44
|
+
const statements = [];
|
|
45
|
+
const tableName = this.escapeId(diff.tableName);
|
|
46
|
+
// Add new columns
|
|
47
|
+
if (diff.columnsToAdd?.length) {
|
|
48
|
+
for (const column of diff.columnsToAdd){
|
|
49
|
+
const colDef = this.generateColumnDefinitionFromSchema(column);
|
|
50
|
+
statements.push(`ALTER TABLE ${tableName} ADD COLUMN ${colDef};`);
|
|
51
|
+
}
|
|
72
52
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const { Database } = await import('bun:sqlite');
|
|
86
|
-
db = new Database(this.filename, this.opts);
|
|
87
|
-
db.run('PRAGMA journal_mode = WAL');
|
|
88
|
-
} else {
|
|
89
|
-
const { default: Database } = await import('better-sqlite3');
|
|
90
|
-
db = new Database(this.filename, this.opts);
|
|
91
|
-
db.pragma('journal_mode = WAL');
|
|
53
|
+
// Alter existing columns
|
|
54
|
+
if (diff.columnsToAlter?.length) {
|
|
55
|
+
for (const { to } of diff.columnsToAlter){
|
|
56
|
+
const colDef = this.generateColumnDefinitionFromSchema(to);
|
|
57
|
+
const colStatements = this.generateAlterColumnStatements(diff.tableName, to, colDef);
|
|
58
|
+
statements.push(...colStatements);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Drop columns
|
|
62
|
+
if (diff.columnsToDrop?.length) {
|
|
63
|
+
for (const columnName of diff.columnsToDrop){
|
|
64
|
+
statements.push(`ALTER TABLE ${tableName} DROP COLUMN ${this.escapeId(columnName)};`);
|
|
92
65
|
}
|
|
93
|
-
this.querier = new SqliteQuerier(db, this.extra);
|
|
94
66
|
}
|
|
95
|
-
|
|
67
|
+
// Add indexes
|
|
68
|
+
if (diff.indexesToAdd?.length) {
|
|
69
|
+
for (const index of diff.indexesToAdd){
|
|
70
|
+
statements.push(this.generateCreateIndex(diff.tableName, index));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Drop indexes
|
|
74
|
+
if (diff.indexesToDrop?.length) {
|
|
75
|
+
for (const indexName of diff.indexesToDrop){
|
|
76
|
+
statements.push(this.generateDropIndex(diff.tableName, indexName));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return statements;
|
|
96
80
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
81
|
+
generateAlterTableDown(diff) {
|
|
82
|
+
const statements = [];
|
|
83
|
+
const tableName = this.escapeId(diff.tableName);
|
|
84
|
+
// Rollback additions by dropping columns
|
|
85
|
+
if (diff.columnsToAdd?.length) {
|
|
86
|
+
for (const column of diff.columnsToAdd){
|
|
87
|
+
statements.push(`ALTER TABLE ${tableName} DROP COLUMN ${this.escapeId(column.name)};`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return statements;
|
|
100
91
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
//# sourceMappingURL=uql-browser.min.js.map
|
|
105
|
-
>this.escapeId(c)).join(', ');
|
|
92
|
+
generateCreateIndex(tableName, index) {
|
|
93
|
+
const unique = index.unique ? 'UNIQUE ' : '';
|
|
94
|
+
const columns = index.columns.map((c)=>this.escapeId(c)).join(', ');
|
|
106
95
|
return `CREATE ${unique}INDEX ${this.escapeId(index.name)} ON ${this.escapeId(tableName)} (${columns});`;
|
|
107
96
|
}
|
|
108
97
|
generateDropIndex(tableName, indexName) {
|
|
@@ -620,8 +609,8 @@ export { Sqlite3QuerierPool, SqliteDialect, SqliteQuerier };
|
|
|
620
609
|
* MySQL/MariaDB schema introspector.
|
|
621
610
|
* Works with both MySQL and MariaDB as they share the same information_schema structure.
|
|
622
611
|
*/ class MysqlSchemaIntrospector {
|
|
623
|
-
constructor(
|
|
624
|
-
this.
|
|
612
|
+
constructor(pool){
|
|
613
|
+
this.pool = pool;
|
|
625
614
|
}
|
|
626
615
|
async getTableSchema(tableName) {
|
|
627
616
|
const querier = await this.getQuerier();
|
|
@@ -684,7 +673,7 @@ export { Sqlite3QuerierPool, SqliteDialect, SqliteQuerier };
|
|
|
684
673
|
return (results[0]?.count ?? 0) > 0;
|
|
685
674
|
}
|
|
686
675
|
async getQuerier() {
|
|
687
|
-
const querier = await this.
|
|
676
|
+
const querier = await this.pool.getQuerier();
|
|
688
677
|
if (!isSqlQuerier(querier)) {
|
|
689
678
|
await querier.release();
|
|
690
679
|
throw new Error('MysqlSchemaIntrospector requires a SQL-based querier');
|
|
@@ -842,8 +831,8 @@ export { Sqlite3QuerierPool, SqliteDialect, SqliteQuerier };
|
|
|
842
831
|
/**
|
|
843
832
|
* PostgreSQL schema introspector
|
|
844
833
|
*/ class PostgresSchemaIntrospector {
|
|
845
|
-
constructor(
|
|
846
|
-
this.
|
|
834
|
+
constructor(pool){
|
|
835
|
+
this.pool = pool;
|
|
847
836
|
}
|
|
848
837
|
async getTableSchema(tableName) {
|
|
849
838
|
const querier = await this.getQuerier();
|
|
@@ -907,7 +896,7 @@ export { Sqlite3QuerierPool, SqliteDialect, SqliteQuerier };
|
|
|
907
896
|
return results[0]?.exists ?? false;
|
|
908
897
|
}
|
|
909
898
|
async getQuerier() {
|
|
910
|
-
const querier = await this.
|
|
899
|
+
const querier = await this.pool.getQuerier();
|
|
911
900
|
if (!isSqlQuerier(querier)) {
|
|
912
901
|
await querier.release();
|
|
913
902
|
throw new Error('PostgresSchemaIntrospector requires a SQL-based querier');
|
|
@@ -1115,8 +1104,8 @@ export { Sqlite3QuerierPool, SqliteDialect, SqliteQuerier };
|
|
|
1115
1104
|
/**
|
|
1116
1105
|
* SQLite schema introspector
|
|
1117
1106
|
*/ class SqliteSchemaIntrospector {
|
|
1118
|
-
constructor(
|
|
1119
|
-
this.
|
|
1107
|
+
constructor(pool){
|
|
1108
|
+
this.pool = pool;
|
|
1120
1109
|
}
|
|
1121
1110
|
async getTableSchema(tableName) {
|
|
1122
1111
|
const querier = await this.getQuerier();
|
|
@@ -1179,7 +1168,7 @@ export { Sqlite3QuerierPool, SqliteDialect, SqliteQuerier };
|
|
|
1179
1168
|
return (results[0]?.count ?? 0) > 0;
|
|
1180
1169
|
}
|
|
1181
1170
|
async getQuerier() {
|
|
1182
|
-
const querier = await this.
|
|
1171
|
+
const querier = await this.pool.getQuerier();
|
|
1183
1172
|
if (!isSqlQuerier(querier)) {
|
|
1184
1173
|
await querier.release();
|
|
1185
1174
|
throw new Error('SqliteSchemaIntrospector requires a SQL-based querier');
|
|
@@ -1436,11 +1425,11 @@ class MongoSchemaGenerator extends AbstractDialect {
|
|
|
1436
1425
|
}
|
|
1437
1426
|
|
|
1438
1427
|
class MongoSchemaIntrospector {
|
|
1439
|
-
constructor(
|
|
1440
|
-
this.
|
|
1428
|
+
constructor(pool){
|
|
1429
|
+
this.pool = pool;
|
|
1441
1430
|
}
|
|
1442
1431
|
async getTableSchema(tableName) {
|
|
1443
|
-
const querier = await this.
|
|
1432
|
+
const querier = await this.pool.getQuerier();
|
|
1444
1433
|
try {
|
|
1445
1434
|
const { db } = querier;
|
|
1446
1435
|
const collections = await db.listCollections({
|
|
@@ -1465,7 +1454,7 @@ class MongoSchemaIntrospector {
|
|
|
1465
1454
|
}
|
|
1466
1455
|
}
|
|
1467
1456
|
async getTableNames() {
|
|
1468
|
-
const querier = await this.
|
|
1457
|
+
const querier = await this.pool.getQuerier();
|
|
1469
1458
|
try {
|
|
1470
1459
|
const { db } = querier;
|
|
1471
1460
|
const collections = await db.listCollections().toArray();
|
|
@@ -1484,8 +1473,8 @@ class MongoSchemaIntrospector {
|
|
|
1484
1473
|
* Stores migration state in a database table.
|
|
1485
1474
|
* Uses the querier's dialect for escaping and placeholders.
|
|
1486
1475
|
*/ class DatabaseMigrationStorage {
|
|
1487
|
-
constructor(
|
|
1488
|
-
this.
|
|
1476
|
+
constructor(pool, options = {}){
|
|
1477
|
+
this.pool = pool;
|
|
1489
1478
|
this.storageInitialized = false;
|
|
1490
1479
|
this.tableName = options.tableName ?? 'uql_migrations';
|
|
1491
1480
|
}
|
|
@@ -1493,7 +1482,7 @@ class MongoSchemaIntrospector {
|
|
|
1493
1482
|
if (this.storageInitialized) {
|
|
1494
1483
|
return;
|
|
1495
1484
|
}
|
|
1496
|
-
const querier = await this.
|
|
1485
|
+
const querier = await this.pool.getQuerier();
|
|
1497
1486
|
if (!isSqlQuerier(querier)) {
|
|
1498
1487
|
await querier.release();
|
|
1499
1488
|
throw new Error('DatabaseMigrationStorage requires a SQL-based querier');
|
|
@@ -1517,7 +1506,7 @@ class MongoSchemaIntrospector {
|
|
|
1517
1506
|
}
|
|
1518
1507
|
async executed() {
|
|
1519
1508
|
await this.ensureStorage();
|
|
1520
|
-
const querier = await this.
|
|
1509
|
+
const querier = await this.pool.getQuerier();
|
|
1521
1510
|
if (!isSqlQuerier(querier)) {
|
|
1522
1511
|
await querier.release();
|
|
1523
1512
|
throw new Error('DatabaseMigrationStorage requires a SQL-based querier');
|
|
@@ -1556,10 +1545,10 @@ class MongoSchemaIntrospector {
|
|
|
1556
1545
|
/**
|
|
1557
1546
|
* Main class for managing database migrations
|
|
1558
1547
|
*/ class Migrator {
|
|
1559
|
-
constructor(
|
|
1560
|
-
this.
|
|
1561
|
-
this.dialect = options.dialect ??
|
|
1562
|
-
this.storage = options.storage ?? new DatabaseMigrationStorage(
|
|
1548
|
+
constructor(pool, options = {}){
|
|
1549
|
+
this.pool = pool;
|
|
1550
|
+
this.dialect = options.dialect ?? pool.dialect ?? 'postgres';
|
|
1551
|
+
this.storage = options.storage ?? new DatabaseMigrationStorage(pool, {
|
|
1563
1552
|
tableName: options.tableName
|
|
1564
1553
|
});
|
|
1565
1554
|
this.migrationsPath = options.migrationsPath ?? './migrations';
|
|
@@ -1576,15 +1565,15 @@ class MongoSchemaIntrospector {
|
|
|
1576
1565
|
createIntrospector() {
|
|
1577
1566
|
switch(this.dialect){
|
|
1578
1567
|
case 'postgres':
|
|
1579
|
-
return new PostgresSchemaIntrospector(this.
|
|
1568
|
+
return new PostgresSchemaIntrospector(this.pool);
|
|
1580
1569
|
case 'mysql':
|
|
1581
|
-
return new MysqlSchemaIntrospector(this.
|
|
1570
|
+
return new MysqlSchemaIntrospector(this.pool);
|
|
1582
1571
|
case 'mariadb':
|
|
1583
|
-
return new MariadbSchemaIntrospector(this.
|
|
1572
|
+
return new MariadbSchemaIntrospector(this.pool);
|
|
1584
1573
|
case 'sqlite':
|
|
1585
|
-
return new SqliteSchemaIntrospector(this.
|
|
1574
|
+
return new SqliteSchemaIntrospector(this.pool);
|
|
1586
1575
|
case 'mongodb':
|
|
1587
|
-
return new MongoSchemaIntrospector(this.
|
|
1576
|
+
return new MongoSchemaIntrospector(this.pool);
|
|
1588
1577
|
default:
|
|
1589
1578
|
return undefined;
|
|
1590
1579
|
}
|
|
@@ -1698,7 +1687,7 @@ class MongoSchemaIntrospector {
|
|
|
1698
1687
|
* Run a single migration within a transaction
|
|
1699
1688
|
*/ async runMigration(migration, direction) {
|
|
1700
1689
|
const startTime = Date.now();
|
|
1701
|
-
const querier = await this.
|
|
1690
|
+
const querier = await this.pool.getQuerier();
|
|
1702
1691
|
if (!isSqlQuerier(querier)) {
|
|
1703
1692
|
await querier.release();
|
|
1704
1693
|
throw new Error('Migrator requires a SQL-based querier');
|
|
@@ -1840,7 +1829,7 @@ class MongoSchemaIntrospector {
|
|
|
1840
1829
|
throw new Error('Schema generator not set. Call setSchemaGenerator() first.');
|
|
1841
1830
|
}
|
|
1842
1831
|
const entities = this.entities.length > 0 ? this.entities : getEntities();
|
|
1843
|
-
const querier = await this.
|
|
1832
|
+
const querier = await this.pool.getQuerier();
|
|
1844
1833
|
if (!isSqlQuerier(querier)) {
|
|
1845
1834
|
await querier.release();
|
|
1846
1835
|
throw new Error('Migrator requires a SQL-based querier');
|
|
@@ -1912,7 +1901,7 @@ class MongoSchemaIntrospector {
|
|
|
1912
1901
|
return filteredDiff;
|
|
1913
1902
|
}
|
|
1914
1903
|
async executeSyncStatements(statements, options) {
|
|
1915
|
-
const querier = await this.
|
|
1904
|
+
const querier = await this.pool.getQuerier();
|
|
1916
1905
|
try {
|
|
1917
1906
|
if (this.dialect === 'mongodb') {
|
|
1918
1907
|
await this.executeMongoSyncStatements(statements, options, querier);
|