uql-orm 0.69.0 → 0.71.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/README.md +2 -0
- package/dist/browser/querier/httpQuerier.d.ts +7 -7
- package/dist/browser/type/clientQuerier.d.ts +5 -5
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +4 -4
- package/dist/cockroachdb/cockroachDialect.d.ts +6 -0
- package/dist/cockroachdb/cockroachDialect.js +10 -2
- package/dist/d1/d1SqliteDialect.d.ts +3 -0
- package/dist/d1/d1SqliteDialect.js +3 -1
- package/dist/dialect/abstractDialect.d.ts +8 -2
- package/dist/dialect/abstractDialect.js +17 -1
- package/dist/dialect/abstractSqlDialect.d.ts +21 -5
- package/dist/dialect/abstractSqlDialect.js +91 -46
- package/dist/dialect/aliases.d.ts +10 -7
- package/dist/dialect/aliases.js +10 -7
- package/dist/dialect/hydrateColumn.d.ts +3 -2
- package/dist/dialect/hydrateColumn.js +10 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +5 -3
- package/dist/dialect/pgLikeSqlDialect.d.ts +17 -5
- package/dist/dialect/pgLikeSqlDialect.js +34 -15
- package/dist/dialect/queryJoins.d.ts +19 -1
- package/dist/dialect/queryJoins.js +54 -11
- package/dist/dialect/vectorCast.d.ts +2 -0
- package/dist/dialect/vectorCast.js +7 -0
- package/dist/dialect/vectorSqlDialect.d.ts +7 -3
- package/dist/dialect/vectorSqlDialect.js +15 -10
- package/dist/entity/decorator/members.d.ts +25 -11
- package/dist/entity/metadata/definition.d.ts +8 -3
- package/dist/libsql/libsqlDialect.d.ts +1 -1
- package/dist/libsql/libsqlDialect.js +3 -3
- package/dist/maria/mariaDialect.d.ts +3 -9
- package/dist/maria/mariaDialect.js +4 -13
- package/dist/maria/mariadbQuerier.js +9 -3
- package/dist/migrate/codegen/entityCodeGenerator.js +4 -2
- package/dist/migrate/ddl/index.d.ts +1 -0
- package/dist/migrate/ddl/index.js +4 -2
- package/dist/migrate/ddl/indexDdl.d.ts +2 -0
- package/dist/migrate/ddl/indexDdl.js +10 -2
- package/dist/migrate/ddl/pgIndexDdl.d.ts +3 -4
- package/dist/migrate/ddl/pgIndexDdl.js +11 -11
- package/dist/migrate/ddl/sqliteIndexDdl.d.ts +11 -0
- package/dist/migrate/ddl/sqliteIndexDdl.js +38 -0
- package/dist/migrate/generator/mongoCommand.d.ts +28 -0
- package/dist/migrate/generator/mongoCommand.js +8 -0
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +2 -0
- package/dist/migrate/generator/mongoSchemaGenerator.js +50 -6
- package/dist/migrate/introspection/mongoIntrospector.js +33 -7
- package/dist/migrate/introspection/postgresIntrospector.js +19 -0
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +29 -3
- package/dist/migrate/migrator.d.ts +2 -1
- package/dist/migrate/migrator.js +5 -3
- package/dist/mongo/mongoDialect.d.ts +33 -27
- package/dist/mongo/mongoDialect.js +178 -114
- package/dist/mongo/mongodbQuerier.d.ts +0 -2
- package/dist/mongo/mongodbQuerier.js +14 -13
- package/dist/mssql/mssqlDialect.js +4 -2
- package/dist/postgres/postgresDialect.js +2 -2
- package/dist/querier/abstractQuerier.d.ts +16 -11
- package/dist/querier/abstractQuerier.js +28 -8
- package/dist/querier/abstractQuerierPool.d.ts +9 -9
- package/dist/querier/abstractSqlQuerier.d.ts +1 -1
- package/dist/querier/abstractSqlQuerier.js +3 -3
- package/dist/schema/canonicalType.js +10 -4
- package/dist/schema/indexDifferences.d.ts +5 -2
- package/dist/schema/indexDifferences.js +5 -0
- package/dist/schema/schemaASTBuilder.js +3 -2
- package/dist/sqlite/localSqliteQuerierPool.d.ts +10 -11
- package/dist/sqlite/localSqliteQuerierPool.js +8 -11
- package/dist/sqlite/nodeSqliteQuerierPool.d.ts +4 -2
- package/dist/sqlite/nodeSqliteQuerierPool.js +7 -4
- package/dist/sqlite/sqliteDialect.d.ts +9 -1
- package/dist/sqlite/sqliteDialect.js +43 -3
- package/dist/sqlite/sqliteQuerierPool.d.ts +6 -5
- package/dist/sqlite/sqliteQuerierPool.js +10 -7
- package/dist/turso/tursoDialect.d.ts +1 -1
- package/dist/turso/tursoDialect.js +6 -2
- package/dist/turso/tursoLocalDialect.d.ts +1 -1
- package/dist/turso/tursoLocalDialect.js +1 -1
- package/dist/turso/tursoLocalQuerierPool.d.ts +4 -5
- package/dist/turso/tursoLocalQuerierPool.js +3 -10
- package/dist/type/dialect.d.ts +11 -0
- package/dist/type/entity.d.ts +45 -5
- package/dist/type/migration.d.ts +14 -9
- package/dist/type/query.d.ts +6 -0
- package/dist/type/queryAggregate.d.ts +73 -42
- package/dist/type/queryAggregate.js +4 -21
- package/dist/type/universalQuerier.d.ts +9 -9
- package/dist/type/vector.d.ts +17 -0
- package/dist/type/vector.js +6 -0
- package/dist/util/ddlExpression.util.d.ts +2 -0
- package/dist/util/ddlExpression.util.js +5 -0
- package/dist/util/dialect.util.d.ts +17 -3
- package/dist/util/dialect.util.js +40 -6
- package/package.json +5 -3
- package/skills/uql-orm/SKILL.md +142 -0
|
@@ -188,6 +188,14 @@ export function findVectorSort(sort) {
|
|
|
188
188
|
}
|
|
189
189
|
return undefined;
|
|
190
190
|
}
|
|
191
|
+
/** `$candidates`, checked: it can be spelled into a statement, and `/http` input is untyped. */
|
|
192
|
+
export function vectorCandidates(q) {
|
|
193
|
+
const candidates = q.$candidates;
|
|
194
|
+
if (candidates !== undefined && (!Number.isInteger(candidates) || candidates < 1)) {
|
|
195
|
+
throw new TypeError(`$candidates must be a positive integer, got ${JSON.stringify(candidates)}`);
|
|
196
|
+
}
|
|
197
|
+
return candidates;
|
|
198
|
+
}
|
|
191
199
|
/**
|
|
192
200
|
* Every index type that means "vector" to some engine: pgvector's two, the generic one MariaDB and
|
|
193
201
|
* CockroachDB share, and Atlas's. Wider than {@link VECTOR_INDEX_TYPES}, which is the set whose DDL
|
|
@@ -344,22 +352,36 @@ export function parseGroupMap(group, select) {
|
|
|
344
352
|
const entries = [];
|
|
345
353
|
const groupMap = group ?? {};
|
|
346
354
|
for (const alias of getKeys(groupMap)) {
|
|
347
|
-
|
|
348
|
-
|
|
355
|
+
const ref = groupMap[alias];
|
|
356
|
+
if (ref) {
|
|
357
|
+
entries.push({ kind: 'key', alias, path: ref === true ? [alias] : groupRefPath(alias, ref) });
|
|
349
358
|
}
|
|
350
359
|
}
|
|
351
360
|
if (!select) {
|
|
352
361
|
return entries;
|
|
353
362
|
}
|
|
354
363
|
for (const alias of getKeys(select)) {
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
|
|
364
|
+
const { $where: where } = select[alias];
|
|
365
|
+
const call = select[alias];
|
|
366
|
+
const key = getKeys(call).find((name) => name !== '$where');
|
|
367
|
+
if (key === undefined) {
|
|
368
|
+
throw new TypeError(`aggregate '${alias}' names no op, only a $where`);
|
|
369
|
+
}
|
|
370
|
+
// `$countDistinct` normalizes to `$count` plus a `distinct` flag.
|
|
358
371
|
const { op, distinct } = resolveAggregateOp(key);
|
|
359
|
-
|
|
372
|
+
const fieldRef = aggregateFieldRef(alias, call[key]);
|
|
373
|
+
entries.push({ kind: 'fn', alias, op, fieldRef, distinct, ...(where && hasKeys(where) ? { where } : {}) });
|
|
360
374
|
}
|
|
361
375
|
return entries;
|
|
362
376
|
}
|
|
377
|
+
/** The path a group key's `{ transaction: { orderId: true } }` names, one key at each level. */
|
|
378
|
+
function groupRefPath(alias, ref) {
|
|
379
|
+
const [key, ...rest] = isRecord(ref) ? getKeys(ref) : [];
|
|
380
|
+
if (!isRecord(ref) || key === undefined || rest.length) {
|
|
381
|
+
throw new TypeError(`$group '${alias}' names one field by the path to it: got ${JSON.stringify(ref)}`);
|
|
382
|
+
}
|
|
383
|
+
return ref[key] === true ? [key] : [key, ...groupRefPath(alias, ref[key])];
|
|
384
|
+
}
|
|
363
385
|
/** The column an aggregate reads: `'*'`, or the one field its `{ field: true }` names. */
|
|
364
386
|
function aggregateFieldRef(alias, arg) {
|
|
365
387
|
const [field, ...rest] = arg === '*' ? [arg] : namedKeys(arg);
|
|
@@ -418,6 +440,18 @@ export function assertAggregateColumns(clauseMap, emitted, clause) {
|
|
|
418
440
|
}
|
|
419
441
|
}
|
|
420
442
|
}
|
|
443
|
+
/** The text-search config a fulltext index builds with where it states none: language-neutral, no stemming. */
|
|
444
|
+
const DEFAULT_TEXT_CONFIG = 'simple';
|
|
445
|
+
/** The text-search config a fulltext index builds with, which a search it serves has to parse with too. */
|
|
446
|
+
export function fulltextConfig(index) {
|
|
447
|
+
return index.config ?? DEFAULT_TEXT_CONFIG;
|
|
448
|
+
}
|
|
449
|
+
/** The fulltext index over exactly `fields`, in order, which a search of them is served by. */
|
|
450
|
+
export function fulltextIndexOver(meta, fields) {
|
|
451
|
+
return meta.indexes?.find((index) => index.type === 'fulltext' &&
|
|
452
|
+
index.columns.length === fields.length &&
|
|
453
|
+
index.columns.every((entry, at) => entry.column === fields[at]));
|
|
454
|
+
}
|
|
421
455
|
/**
|
|
422
456
|
* The fields a `$text` searches: those it names, or else the columns of the entity's fulltext index,
|
|
423
457
|
* the declaration MySQL's `MATCH` has to name exactly and a MongoDB text index already is. Refused
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "The JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.71.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|
|
@@ -52,11 +52,12 @@
|
|
|
52
52
|
},
|
|
53
53
|
"files": [
|
|
54
54
|
"dist",
|
|
55
|
+
"skills",
|
|
55
56
|
"README.md"
|
|
56
57
|
],
|
|
57
58
|
"scripts": {
|
|
58
|
-
"prepack": "bun run build && bun run verify-dist.ts && cp ../../README.md .",
|
|
59
|
-
"postpack": "rm README.md && npm pkg delete gitHead",
|
|
59
|
+
"prepack": "bun run build && bun run verify-dist.ts && cp ../../README.md . && cp -R ../../skills .",
|
|
60
|
+
"postpack": "rm -r README.md skills && npm pkg delete gitHead",
|
|
60
61
|
"compile.browser": "bun build src/browser/index.ts --minify --sourcemap=linked --format=esm --target=browser --outdir=dist/browser --entry-naming 'uql-browser.min.[ext]'",
|
|
61
62
|
"build": "bun run clean && tsc -b tsconfig.build.json && bun run compile.browser && bun run verify-dist.ts",
|
|
62
63
|
"start": "tsc --watch",
|
|
@@ -167,6 +168,7 @@
|
|
|
167
168
|
"url": "https://github.com/rogerpadilla/uql/issues"
|
|
168
169
|
},
|
|
169
170
|
"keywords": [
|
|
171
|
+
"tanstack-intent",
|
|
170
172
|
"orm",
|
|
171
173
|
"uql",
|
|
172
174
|
"sql",
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uql-orm
|
|
3
|
+
description: >
|
|
4
|
+
Write code with UQL (the uql-orm package), the TypeScript ORM whose queries are plain JSON objects,
|
|
5
|
+
on PostgreSQL, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, MongoDB, Turso, Neon, D1 and PGlite.
|
|
6
|
+
Use when a project imports uql-orm, or when defining entities, querying, populating relations,
|
|
7
|
+
writing transactions, raw SQL or migrations with it. UQL is not Prisma, Drizzle, TypeORM or MikroORM:
|
|
8
|
+
their APIs do not carry over.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# UQL
|
|
12
|
+
|
|
13
|
+
Entities are classes; a query is a JSON object checked key by key against the entity; the same query runs
|
|
14
|
+
on every database UQL supports. There is no schema file, no generated client, and no query builder.
|
|
15
|
+
|
|
16
|
+
The full docs are Markdown at https://uql-orm.dev/llms.txt, one page per URL. Read the page for the task
|
|
17
|
+
before guessing an option: every page listed there is a `.md` URL.
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install uql-orm pg # or mysql2, mariadb, better-sqlite3, mongodb, @libsql/client, ...
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
ESM only. Node 24+, Bun, Deno or an edge runtime, TypeScript 5.2+. Decorators are the TC39 standard:
|
|
26
|
+
never enable `experimentalDecorators` or `emitDecoratorMetadata`, never import `reflect-metadata`.
|
|
27
|
+
In `tsconfig.json`, `module` is `nodenext` or `preserve`, and `target` is a dated one (`es2022`+), not `esnext`.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// uql.config.ts
|
|
31
|
+
import type { Config } from 'uql-orm';
|
|
32
|
+
import { PgQuerierPool } from 'uql-orm/postgres';
|
|
33
|
+
import { Post, User } from './entities.js';
|
|
34
|
+
|
|
35
|
+
export const pool = new PgQuerierPool({ connectionString: process.env.DATABASE_URL });
|
|
36
|
+
|
|
37
|
+
export default { pool, entities: [User, Post] } satisfies Config;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Each driver has its own entry point: `uql-orm/postgres`, `uql-orm/mysql`, `uql-orm/maria`, `uql-orm/sqlite`,
|
|
41
|
+
`uql-orm/mongo`, `uql-orm/libsql`, `uql-orm/turso`, `uql-orm/neon`, `uql-orm/d1`, `uql-orm/pglite`,
|
|
42
|
+
`uql-orm/bunSql`, `uql-orm/mssql`, `uql-orm/cockroachdb`. Build the pool once per process and import it.
|
|
43
|
+
|
|
44
|
+
## Entities
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { Entity, Field, Id, ManyToOne, OneToMany } from 'uql-orm';
|
|
48
|
+
|
|
49
|
+
@Entity()
|
|
50
|
+
export class User {
|
|
51
|
+
@Id({ type: 'uuid', onInsert: () => crypto.randomUUID() })
|
|
52
|
+
id?: string;
|
|
53
|
+
|
|
54
|
+
@Field({ type: String, unique: true, nullable: false })
|
|
55
|
+
email?: string;
|
|
56
|
+
|
|
57
|
+
@OneToMany({ entity: () => Post, mappedBy: (post) => post.author })
|
|
58
|
+
posts?: Post[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Entity()
|
|
62
|
+
export class Post {
|
|
63
|
+
@Id({ type: Number })
|
|
64
|
+
id?: number;
|
|
65
|
+
|
|
66
|
+
@Field({ type: String })
|
|
67
|
+
title?: string | null;
|
|
68
|
+
|
|
69
|
+
@Field({ references: () => User })
|
|
70
|
+
authorId?: string | null;
|
|
71
|
+
|
|
72
|
+
@ManyToOne({ entity: () => User, references: (post) => post.authorId })
|
|
73
|
+
author?: User;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- Every `@Field` states its `type` (`String`, `Number`, `Boolean`, `Date`, `BigInt`, or a column type such as
|
|
78
|
+
`'uuid'`, `'text'`, `'jsonb'`), except a foreign key, which takes `references` and inherits the target key's type.
|
|
79
|
+
- A column is nullable unless it says `nullable: false`, and its property must admit `null` to match:
|
|
80
|
+
`title?: string | null`. A property typed without `| null` on a nullable column is a compile error.
|
|
81
|
+
- Members are named by callbacks, never by strings: `mappedBy: (post) => post.author`, `references: (post) => post.authorId`.
|
|
82
|
+
- `@ManyToMany({ entity: () => Tag, through: () => PostTag })` names its junction entity.
|
|
83
|
+
- `defineEntity` defines the same entity without decorators: https://uql-orm.dev/entities/imperative.md
|
|
84
|
+
|
|
85
|
+
## Queries
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { pool } from './uql.config.js';
|
|
89
|
+
import { User } from './entities.js';
|
|
90
|
+
|
|
91
|
+
const users = await pool.findMany(User, {
|
|
92
|
+
$select: { id: true, email: true },
|
|
93
|
+
$where: { email: { $endsWith: '@uql-orm.dev' }, $or: [{ id: 'a' }, { id: 'b' }] },
|
|
94
|
+
$populate: { posts: { $select: { title: true }, $sort: { id: 'desc' }, $limit: 5 } },
|
|
95
|
+
$sort: { email: 'asc' },
|
|
96
|
+
$skip: 0,
|
|
97
|
+
$limit: 20,
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- The keys are `$select`, `$exclude`, `$where`, `$populate`, `$sort`, `$skip`, `$limit`.
|
|
102
|
+
- `$where` takes a value for equality or an operator map: `$eq`, `$ne`, `$lt`, `$lte`, `$gt`, `$gte`, `$in`,
|
|
103
|
+
`$nin`, `$between`, `$like`, `$ilike`, `$regex`, `$startsWith`, `$endsWith`, `$includes`, `$isNull`,
|
|
104
|
+
`$isNotNull`. `$and`, `$or`, `$not` and `$nor` combine clauses.
|
|
105
|
+
- A result is narrowed to what the query selected and populated: reading an unselected field is a compile error.
|
|
106
|
+
Name that shape with `QueryFindResult<User, 'id' | 'email'>` rather than widening the query.
|
|
107
|
+
- `$populate` loads relations in the same statement. Nothing is lazy: a relation not populated is not there.
|
|
108
|
+
- A query is plain data, so it can be built dynamically, stored, or sent from a browser to `uql-orm/http`.
|
|
109
|
+
- Methods: `findMany`, `findOne`, `findOneById`, `findManyAndCount`, `findManyStream`, `count`, `exists`,
|
|
110
|
+
`aggregate`, `insertOne`, `insertMany`, `updateOneById`, `updateMany`, `saveOne`, `saveMany`, `upsertOne`,
|
|
111
|
+
`upsertMany`, `deleteOneById`, `deleteMany`. Each takes the entity class first.
|
|
112
|
+
- `updateMany` and `deleteMany` with no `$where` throw; `{ unfiltered: true }` means the whole table.
|
|
113
|
+
- `raw()` embeds SQL anywhere a value or field goes; `pool.all(sql, values)` runs a raw `SELECT`.
|
|
114
|
+
|
|
115
|
+
## Connections and transactions
|
|
116
|
+
|
|
117
|
+
Every method is on both the pool and a querier. A pool call acquires a connection for that call and releases it.
|
|
118
|
+
To run several operations on one connection, or atomically, hold a querier:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
await pool.transaction(async (querier) => {
|
|
122
|
+
const userId = await querier.insertOne(User, { email: 'ada@uql-orm.dev' });
|
|
123
|
+
await querier.insertOne(Post, { title: 'Hello', authorId: userId });
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Inside the callback, call `querier`, never `pool`: a `pool` call runs on another connection, outside the
|
|
128
|
+
transaction. A querier from `pool.getQuerier()` is yours to release: bind it with `await using`.
|
|
129
|
+
|
|
130
|
+
## Migrations
|
|
131
|
+
|
|
132
|
+
`npx uql-migrate` reads `uql.config.ts`. `sync` creates what the entities imply (development only);
|
|
133
|
+
`generate:entities` writes the diff as a migration file to review; `up` applies migrations; `generate:from-db`
|
|
134
|
+
writes entity classes from an existing database; `drift:check` fails when the database no longer matches.
|
|
135
|
+
|
|
136
|
+
## Where to read more
|
|
137
|
+
|
|
138
|
+
- Operators, per-dialect SQL: https://uql-orm.dev/querying/comparison-operators.md
|
|
139
|
+
- Relations and deep `$populate`: https://uql-orm.dev/querying/relations.md
|
|
140
|
+
- Every method's signature: https://uql-orm.dev/querying/methods.md
|
|
141
|
+
- Coming from Prisma, Drizzle, TypeORM or MikroORM: https://uql-orm.dev/switching-to-uql.md
|
|
142
|
+
- Breaking changes by version: https://uql-orm.dev/upgrade-guide.md
|