@uql/core 3.3.0 → 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.
Files changed (41) hide show
  1. package/CHANGELOG.md +5 -2
  2. package/README.md +14 -12
  3. package/dist/browser/uql-browser.min.js +83 -94
  4. package/dist/browser/uql-browser.min.js.map +1 -1
  5. package/dist/d1/d1Querier.d.ts +49 -0
  6. package/dist/d1/d1Querier.d.ts.map +1 -0
  7. package/dist/d1/d1Querier.js +40 -0
  8. package/dist/d1/d1Querier.js.map +1 -0
  9. package/dist/d1/d1QuerierPool.d.ts +10 -0
  10. package/dist/d1/d1QuerierPool.d.ts.map +1 -0
  11. package/dist/d1/d1QuerierPool.js +16 -0
  12. package/dist/d1/d1QuerierPool.js.map +1 -0
  13. package/dist/d1/index.d.ts +3 -0
  14. package/dist/d1/index.d.ts.map +1 -0
  15. package/dist/d1/index.js +3 -0
  16. package/dist/d1/index.js.map +1 -0
  17. package/dist/libsql/index.d.ts +3 -0
  18. package/dist/libsql/index.d.ts.map +1 -0
  19. package/dist/libsql/index.js +3 -0
  20. package/dist/libsql/index.js.map +1 -0
  21. package/dist/libsql/libsqlQuerier.d.ts +21 -0
  22. package/dist/libsql/libsqlQuerier.d.ts.map +1 -0
  23. package/dist/libsql/libsqlQuerier.js +81 -0
  24. package/dist/libsql/libsqlQuerier.js.map +1 -0
  25. package/dist/libsql/libsqlQuerierPool.d.ts +12 -0
  26. package/dist/libsql/libsqlQuerierPool.d.ts.map +1 -0
  27. package/dist/libsql/libsqlQuerierPool.js +19 -0
  28. package/dist/libsql/libsqlQuerierPool.js.map +1 -0
  29. package/dist/neon/index.d.ts +3 -0
  30. package/dist/neon/index.d.ts.map +1 -0
  31. package/dist/neon/index.js +3 -0
  32. package/dist/neon/index.js.map +1 -0
  33. package/dist/neon/neonQuerier.d.ts +18 -0
  34. package/dist/neon/neonQuerier.d.ts.map +1 -0
  35. package/dist/neon/neonQuerier.js +40 -0
  36. package/dist/neon/neonQuerier.js.map +1 -0
  37. package/dist/neon/neonQuerierPool.d.ts +11 -0
  38. package/dist/neon/neonQuerierPool.d.ts.map +1 -0
  39. package/dist/neon/neonQuerierPool.js +17 -0
  40. package/dist/neon/neonQuerierPool.js.map +1 -0
  41. 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
- # [3.3.0](https://github.com/rogerpadilla/uql/compare/@uql/core@3.2.0...@uql/core@3.3.0) (2025-12-30)
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
- **Note:** Version bump only for package @uql/core
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
 
package/README.md CHANGED
@@ -2,13 +2,12 @@
2
2
 
3
3
  [![uql maku](assets/logo.svg)](https://uql.app)
4
4
 
5
- [![tests](https://github.com/rogerpadilla/uql/actions/workflows/tests.yml/badge.svg)](https://github.com/rogerpadilla/uql) [![coverage status](https://coveralls.io/repos/rogerpadilla/uql/badge.svg?branch=main)](https://coveralls.io/r/rogerpadilla/uql?branch=main) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/rogerpadilla/uql/blob/main/LICENSE) [![npm version](https://img.shields.io/npm/v/@uql/core.svg)](https://www.npmjs.com/package/@uql/core)
5
+ [![tests](https://github.com/rogerpadilla/uql/actions/workflows/tests.yml/badge.svg)](https://github.com/rogerpadilla/uql) [![Coverage Status](https://coveralls.io/repos/github/rogerpadilla/uql/badge.svg?branch=main)](https://coveralls.io/github/rogerpadilla/uql?branch=main) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/rogerpadilla/uql/blob/main/LICENSE) [![npm version](https://img.shields.io/npm/v/@uql/core.svg)](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, it is designed to be fast, safe, and easy to integrate into any application.
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
- It can run in Node.js, Browser, React Native, Expo, Electron, Deno, Bun, and many more!
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 | Driver
60
- | ------------ | ----------------
61
- | `PostgreSQL` | `pg`
62
- | `SQLite` | `better-sqlite3`
63
- | `MariaDB` | `mariadb`
64
- | `MySQL` | `mysql2`
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
 
@@ -1,108 +1,97 @@
1
- import sqlstring from 'sqlstring-sqlite';
2
- import { AbstractSqlDialect } from '../dialect/index.js';
3
- import { getMeta } from '../entity/index.js';
4
- import { AbstractSqlQuerier, AbstractQuerierPool } from '../querier/index.js';
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
- class SqliteDialect extends AbstractSqlDialect {
7
- constructor(namingStrategy){
8
- super(namingStrategy, '`', 'BEGIN TRANSACTION');
9
- }
10
- addValue(values, value) {
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
- compareFieldOperator(ctx, entity, key, op, val, opts = {}) {
35
- super.compareFieldOperator(ctx, entity, key, op, val, opts);
16
+ /**
17
+ * Escape an identifier (table name, column name, etc.)
18
+ */ escapeId(identifier) {
19
+ return escapeSqlId(identifier, this.escapeIdChar);
36
20
  }
37
- upsert(ctx, entity, conflictPaths, payload) {
21
+ generateCreateTable(entity, options = {}) {
38
22
  const meta = getMeta(entity);
39
- const update = this.getUpsertUpdateAssignments(ctx, meta, conflictPaths, payload, (name)=>`EXCLUDED.${name}`);
40
- const keysStr = this.getUpsertConflictPathsStr(meta, conflictPaths);
41
- const onConflict = update ? `DO UPDATE SET ${update}` : 'DO NOTHING';
42
- this.insert(ctx, entity, payload);
43
- ctx.append(` ON CONFLICT (${keysStr}) ${onConflict}`);
44
- }
45
- escape(value) {
46
- return sqlstring.escape(value);
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
- class SqliteQuerier extends AbstractSqlQuerier {
51
- constructor(db, extra){
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
- async internalRelease() {
70
- if (this.hasOpenTransaction) {
71
- throw TypeError('pending transaction');
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
- // no-op
74
- }
75
- }
76
-
77
- class Sqlite3QuerierPool extends AbstractQuerierPool {
78
- constructor(filename, opts, extra){
79
- super('sqlite', extra), this.filename = filename, this.opts = opts;
80
- }
81
- async getQuerier() {
82
- if (!this.querier) {
83
- let db;
84
- if (typeof Bun !== 'undefined') {
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
- return this.querier;
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
- async end() {
98
- await this.querier.db.close();
99
- delete this.querier;
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
- export { Sqlite3QuerierPool, SqliteDialect, SqliteQuerier };
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) {