> Every UQL docs page, as Markdown: https://uql-orm.dev/llms.txt
> The same docs over MCP: https://uql-orm.dev/mcp
> Before writing UQL code, read the skill: https://uql-orm.dev/.well-known/agent-skills/uql-orm/SKILL.md

# Upgrade guide

> What each release asks of you, newest first, with the version and date it landed.

Source: https://uql-orm.dev/upgrade-guide

Only releases that *may* require something on your side. Everything else, and what a custom dialect or driver sees, is in the [changelog](https://github.com/rogerpadilla/uql/blob/main/CHANGELOG.md).

## 0.77.1 - 2026-09-20

Rename `UqlLockUsageError` to `UqlUsageError`, which every misuse now throws: the old name still resolves to it, so an `instanceof` keeps working. A call the API cannot carry out answers `400` over HTTP where it answered `500`.

## 0.73.0 - 2026-09-18

Rename `WithDistance` and `WithScore` to `WithProjection`, which types the row any `$sort` `$project` names. On SQLite, libSQL and Turso, a vector column created before 0.71.0 is `TEXT`, which `drift:check` now reports: recreate its table in a migration with the column as `F32_BLOB`, which libSQL’s vector index needs to build.

## 0.70.0 - 2026-09-18

Run the [codemod](https://uql-orm.dev/codemod.md): a nullable column’s property admits `null`, `name?: string | null`, so it appends the `| null`. Give a column that never holds one `nullable: false` instead. An `updateMany` or `deleteMany` naming no rows throws; pass `{ unfiltered: true }` where you mean the whole table. A write payload naming a `readonly` field no longer compiles, since its value was dropped. Replace `$sumDistinct` and `$avgDistinct` with a `$group` on the field.

## 0.68.1 - 2026-09-17

An RPC contract takes [`WireQuery<E>`](https://uql-orm.dev/trpc.md), a `raw` or a `Uint8Array` in a browser-client query or payload no longer compiles, and a cast past the types throws rather than corrupting the row: keep both server-side. Run the [codemod](https://uql-orm.dev/codemod.md) for `D1Database`, now `D1Queryable`. A query type names its `raw` before its key set, `QueryWhere<E, Raw, K>`.

## 0.68.0 - 2026-09-17

On PostgreSQL and CockroachDB, [generate a migration](https://uql-orm.dev/migrations.md) for a numeric `jsonPath` index: its expression changed, and the planner no longer matches the old one. An `$elemMatch` on one `$eq` or `$in` compares by JSON type, so `'5'` stops matching `5`, and an object in `$all` matches an element holding its keys rather than only an identical one.

## 0.66.0 - 2026-09-16

Run the [codemod](https://uql-orm.dev/codemod.md): every to-one names its foreign key in `references`, and declares the column where a relation used to create it. For a to-one onto a composite key, declare a column per key and pair them. Then `tsc` points at what registration used to refuse at startup: a security filter that skips, and a `mappedBy` or `references` naming a column that cannot hold the key it joins.

## 0.65.0 - 2026-09-14

Run the [codemod](https://uql-orm.dev/codemod.md), then give each junction column `references`, declare a relation for any `@Field({ references })` column you `$populate`, and read `pool.dialect.dialectName` instead of `migrator.dialectName`.

## 0.63.0 - 2026-09-13

Write an index expression as `raw` in the list, ``(user) => [raw`lower(${user.email})`]``, not a callback per expression. Move SQLite off `uql-orm/bunSql` to [`Sqlite3QuerierPool`](https://uql-orm.dev/sqlite.md), upgrade `@tursodatabase/serverless` to 1.3+, and pass a libSQL client you built to [`LibsqlQuerierPool`](https://uql-orm.dev/turso.md#libsql). SQLite now reads an integer past 2^53 as text.

## 0.62.0 - 2026-09-13

Run the [codemod](https://uql-orm.dev/codemod.md): it rewrites the renamed options and `col()`, now [`refs(Entity)`](https://uql-orm.dev/querying/raw-sql.md). SQL an entity declares reads its columns off a callback’s refs, ``(user) => raw`lower(${user.email})` ``.

## 0.60.0 - 2026-09-12

A [builder migration](https://uql-orm.dev/migrations/builder.md) receives the builder, with the querier second: `up(m, querier)`. One written to call `querier.run` there now reaches the builder instead; move it to `defineMigration`.

## 0.58.0 - 2026-09-11

A member is never named by a string, so a rename in your editor reaches it. Definitions read it off a key map: `@Index((post) => [post.title])`, `mappedBy: (post) => post.author`, `references`. Statements name it by a key: `aggregate()` takes its computed columns in `$select` instead of `$agg`, and `$text` takes `$fields: { title: true }`. The [codemod](https://uql-orm.dev/codemod.md) rewrites all of it.

```ts
await pool.aggregate(Order, {
  $group: { status: true },
  $select: { total: { $sum: { amount: true } } },
});
```

## 0.57.0 - 2026-09-11

A to-many `$populate` and `$count` are read in the parent’s statement, which needs MySQL 8.0.14+ and SQLite 3.44+. Each table in a statement reads under its own name, relation key or join path, so a `raw()` naming a related table has to use that alias. Every foreign key gets an index unless one already leads with it (`index: false` opts a column out), so the next migration adds them.

## 0.55.0 - 2026-09-10

A BIGINT past 2^53 reads back as its exact text, where most drivers rounded it; declare `type: BigInt` for a typed exact integer. The [codemod](https://uql-orm.dev/codemod.md) renames `CrdbQuerier`/`NeonQuerier` to `PgQuerier` and `LibsqlQuerier`/`TursoQuerier` to `HranaQuerier`. Raw access on Bun SQL is `pool.sql`.

## 0.54.0 - 2026-09-10

`virtual` and `raw('sql')` are gone; the [codemod](https://uql-orm.dev/codemod.md) rewrites both. An `after*` hook’s `this` is now a copy of the row as written, so mutating it leaves the object you passed alone.

## 0.53.0 - 2026-09-10

`$where` takes a map and nothing else, so TypeScript reports a wrong value on the key it sits on. An id, a list of ids or a bare `raw()` in its place stops compiling, and over HTTP answers 400. Name the key, and put a raw expression in `$and`:

```ts
import { raw } from 'uql-orm';

await pool.findMany(User, { $where: { id: [1, 2] } });
await pool.findMany(User, { $where: { $and: [raw`"createdAt" > now()`] } });
```

The [codemod](https://uql-orm.dev/codemod.md) renames `QueryWhereMap` to `QueryWhere` and reports the rest.

## 0.52.0 - 2026-09-09

The pool builds the engine’s own dialect, so `BunSqlPostgresDialect`, `BunSqlCockroachDialect` and `BunSqliteDialect` are gone.

## 0.48.0 to 0.51.0 - 2026-09-09

Four releases in one day, all about how a write names its rows.

`saveOne`/`saveMany` upsert on the key a row names, instead of guessing from whether an id is present. A stale id writes the row now rather than updating nothing, and [composite keys work](https://uql-orm.dev/querying/methods.md#saveone--savemany). A row that names its key fires `@BeforeUpsert`/`@AfterUpsert`, so move whatever a `@BeforeUpdate` was doing on a save.

Every write reports its id in one shape: the column’s value on a single key, the [key map](https://uql-orm.dev/querying/methods.md#composite-keys) on a composite, where a composite insert used to report `undefined`. `firstId` is gone from `upsertOne` and `upsertMany`, replaced by `id` and by `ids` in payload order.

A key not called `id`, `_id` or `uuid`, and a composite whatever its columns are called, has to be named by the `idKey` brand, which the [codemod](https://uql-orm.dev/codemod.md) writes. `@Id` refuses one without it:

```ts
import { Entity, Id, idKey } from 'uql-orm';

@Entity()
export class Enrolment {
  [idKey]?: 'studentId' | 'courseId';

  @Id({ type: Number }) studentId?: number;
  @Id({ type: String }) courseId?: string;
}
```

On MongoDB, a key you supply is the document’s `_id`, where it used to land beside the one the driver minted so `findOneById` never found the row; documents written before this keep the minted id. Ids read back as hex strings, and a key left to the database has to be one [MongoDB can mint](https://uql-orm.dev/mongodb.md#the-key-has-to-be-one-mongodb-can-produce).

## 0.46.0 - 2026-09-08

`virtual` is renamed to [`computed`](https://uql-orm.dev/entities/computed-fields.md). Both work for one release, giving both throws, and the [codemod](https://uql-orm.dev/codemod.md) rewrites it.

## 0.45.0 - 2026-09-08

A sync now applies foreign keys, so read a `planSync()` before the first one; not on SQLite, whose only way to change a constraint is rebuilding the table. A generated key is spelled from the type it declares, so on MySQL and MariaDB it is no longer `UNSIGNED`, and a key left `UNSIGNED` refuses every constraint pointing at it:

```sql title="MySQL"
-- on a database created before this, under safe: false
ALTER TABLE `Company` MODIFY COLUMN `id` BIGINT AUTO_INCREMENT;
```

Run it before adding foreign keys to those tables, as a migration rather than at boot, since it rebuilds the table under a metadata lock. A key filled by `onInsert` is no longer auto-increment, and `serial`, `bigserial` and `smallserial` are gone as `columnType`: declare the width, `@Id({ type: Number })` or `@Id({ type: Number, columnType: 'int' })`.

## 0.44.0 - 2026-09-07

A naming strategy no longer rewrites a table you named yourself, so check which table `@Entity({ name: 'UserProfile' })` entities now map to; ones that name no table are unaffected. `migrator.autoSync`, `syncForce` and `syncEntity` are one `migrator.sync(options)`.

## 0.43.0 - 2026-09-07

An option a column cannot use is now an error where it used to be silently ignored. Delete `length`, `precision`, `scale`, `autoIncrement`, `dimensions` and `distance` where the column’s type has no use for them, any DDL or generator option on a `virtual` field, `onUpdate` beside `updatable: false`, `nullable: true` on a key, and a `defaultValue` that is not the value the column holds (a JSON column keeps the SQL literal it stores, `defaultValue: '{}'`).

## 0.42.1 - 2026-09-05

Nothing in your database is renamed or rewritten: keys and indexes are recognised by the columns they cover, never by name. Adding a key column to a table that already has rows fails until you fill it in.

## 0.42.0 - 2026-09-04

A second `@Id` composes the key instead of replacing the first, so an entity relying on that gains a column and a two-column [`PRIMARY KEY`](https://uql-orm.dev/entities/basic.md#composite-primary-keys); narrowing an *inherited* key is unaffected. `EntityMeta.id` is now `ids`, a list.

## 0.24.0 - 2026-08-02

The pool runs every operation, so a function that writes can take `UniversalQuerier` (a querier or the pool). Only a hand-written `QuerierPool` has anything to do: `insertMany`, `updateMany`, `upsertOne`, `upsertMany` and `saveMany` are no longer optional.

## 0.23.0 - 2026-08-01

**Decorators are the standard TC39 ones now.** No `experimentalDecorators`, no `emitDecoratorMetadata`, no `reflect-metadata`. The [codemod](https://uql-orm.dev/codemod.md) does most of it and reports what is left. Then, by hand:

- **`target` must not be `esnext`**, the one target where TypeScript leaves decorators untransformed.
- **Your build must transform them.** esbuild, SWC, Babel `version: '2023-11'`, Bun and `tsc` do. **Oxc does not**, so Vite 8 needs one of the others.
- **`uql.config.ts` needs `bun` or `node --import tsx`** if it imports entities, since decorators are not erasable syntax.
- **NestJS: use `defineEntity`.** Nest’s DI needs parameter decorators, and one `tsconfig.json` cannot mix specs. See [NestJS](https://uql-orm.dev/nestjs.md).
- **Node 24 is the minimum.**
