Upgrade guide
Releases that may need a change on your side, newest first. Everything else, including what a custom dialect or driver sees, is in the changelog.
0.112.1 - 2026-11-05
Section titled “0.112.1 - 2026-11-05”The browser client reads rows by the entity it is handed: a Date field is a Date, no longer its ISO text, and a bigint exact, so code that parsed one can stop. The entity has to be registered where the client runs, as importing its class does; a class used only to name the route throws. A cache that persists the client’s rows needs a serializer that keeps a Date and a bigint. The ./browser entry grows from about 2 KB gzipped to about 11 KB, the entity metadata.
uql-orm/browser ships no prebuilt uql-browser.min.js: import the package entry, which every bundler resolves.
0.112.0 - 2026-11-04
Section titled “0.112.0 - 2026-11-04”Run the codemod first: it rewrites the relation decorators.
A relation decorator takes its target first, then its options, as defineEntity’s rel.* do: @ManyToOne(() => User, { references: (post) => post.authorId }), and @OneToMany(() => Post) where nothing else needs saying. The entity option is gone; in plain JavaScript, a decorator still passing it throws on first read.
0.111.2 - 2026-11-04
Section titled “0.111.2 - 2026-11-04”PGlite needs 0.5.10 or later: upgrade @electric-sql/pglite, and @electric-sql/pglite-pgvector with it. A statement now binds up to 65535 values, as on a Postgres server.
0.111.1 - 2026-11-01
Section titled “0.111.1 - 2026-11-01”insertOne, insertMany, saveOne and saveMany resolve to a defined key, so a ! or a check on the result can go. On SQL engines, an insert leaving out a key the entity does not declare a fill for now throws before writing. If only the table states the default, declare it: f.uuid().id().default(sqlgen_random_uuid()).
0.111.0 - 2026-11-01
Section titled “0.111.0 - 2026-11-01”.id() no longer implies a generated key: a generated key says so, as f.number().autoIncrement(), or f.bigint().autoIncrement(). .autoIncrement() makes the column the key, so it replaces .id() rather than following it, and on a column that is no key, where it never generated anything, it is gone. f.number().id() is now a key the caller supplies, an insert has to name it, and .autoIncrement(false) is gone. Run npx uql-codemod, which rewrites each sole numeric key. On MongoDB, where nothing generates a numeric key, undo that for yours.
An insert has to name a key unless the entity says something fills it. defineEntity reads that off its columns. A decorated class says so itself: keep id!: number and list it in [filledKey]?: 'id', as any other field a default fills, or declare it id?. @Field refuses ? on a numeric or .references() key nothing fills. A text key stays allowed ?, which MongoDB generates. Generated entities carry ? on a key the database fills.
On SQLite, a new key the caller supplies is INT, as INTEGER PRIMARY KEY numbers its own rows. An existing table stays as it is, which sync reads as no drift.
0.110.0 - 2026-10-31
Section titled “0.110.0 - 2026-10-31”.narrow<T>() is gone: a column’s type argument says it, f.uuid<UserId>(), and a schema-less JSON payload is f.json<Settings>(). For a builder with no type argument, set the SQL type after: f.number<Cents>().columnType('int').
A brand now holds across a foreign key: a column referencing a branded key has to carry the brand, in a relation and on a decorated property. A filter on a branded Date takes the brand, not a plain Date or string.
The DateColumnType, BlobColumnType and VectorColumnType exports are gone; ColumnType covers them.
0.109.2 - 2026-10-30
Section titled “0.109.2 - 2026-10-30”The deprecated raw export is gone, so raw from uql-orm no longer compiles. Use sql, which uql-codemod renames at the import and every use.
0.109.0 - 2026-10-29
Section titled “0.109.0 - 2026-10-29”Run the codemod first: it rewrites the upsert calls.
An upsert’s update is its query’s $update, beside $conflict and $where, and the options follow the rows as on every other write: upsertOne(SearchQuery, { $conflict: { term: true }, $update: { uses: { $inc: 1 } } }, row, opts), likewise in upsertMany and a trigger’s upsertInto.
0.108.0 - 2026-10-29
Section titled “0.108.0 - 2026-10-29”Run the codemod first: it rewrites the upsert calls.
An upsert’s conflict keys are a query, as updateMany’s $where is: upsertOne(User, { $conflict: { email: true } }, row), likewise in upsertMany and a trigger’s upsertInto. A $where beside it limits which found rows take the update. QueryConflictPaths is QueryConflictKeys.
A JSON column’s default is a document: .default('draft') stores the JSON string "draft", and .default('{}') is refused in favour of .default({}).
On SQL Server, a uuid reads back lowercase, as written: compare ids as read, not uppercased.
On MongoDB, a write of several steps no longer opens a transaction of its own, so it runs on a standalone server: an upsert with an update or a $where, and an updateMany or deleteMany that reads its ids first, cascades, or runs hooks. Wrap it in a transaction where it must land whole or not at all.
0.107.0 - 2026-10-27
Section titled “0.107.0 - 2026-10-27”On MongoDB, a foreign key only a relation declares (in defineEntity’s .relations(), or a plain @Field a @ManyToOne joins by) is now stored as an ObjectId, as an f.references() one already was, so $populate and $count find its rows. Convert the documents written before, per collection and field: db.posts.updateMany({ author: { $regex: '^[0-9a-fA-F]{24}$' } }, [{ $set: { author: { $toObjectId: '$author' } } }]).
0.106.0 - 2026-10-26
Section titled “0.106.0 - 2026-10-26”A JSON column’s payload is a Standard Schema, f.json(schema): the shape option (f.json({ lang: String })) is gone. The codemod leaves a shape it cannot write as a builder, so write the schema by hand or with any Standard Schema library. It also converts a class registered with defineEntity(Class, options) into a defineEntity({ name, fields }) call, and leaves a class it cannot convert as it is, with the reason.
defineEntity has no .references() section. A foreign key is a relation in .relations(), which checks its key type at compile time; the column never names its target. f.references() on a decorated field is unchanged.
0.105.0 - 2026-10-25
Section titled “0.105.0 - 2026-10-25”Run the codemod first: it rewrites what follows where it can and reports the rest.
A column is a builder: @Field(f.string().nullable().length(80)) replaces @Field({ type: String, nullable: true, length: 80 }). It is NOT NULL unless .nullable(), a key is .id() (@Id is gone), and a computed field is @Computed((post) => f.string().as(sql`...`)). A builder has no method for an option its type cannot take, so f.number().length(10) does not compile. The migration builder takes the same methods: table.string('email').length(80).nullable(), .default() for .defaultValue(), .id() for .primaryKey() and .as() for .computed().
@Index, @Filter and @Trigger are gone: list them in @Entity({ indexes, filters, triggers }). An index, check or trigger entry is written by the helper its callback hands over, indexes: (post, index) => [index([post.title], 'by_title')]. A base class carrying them without being a table is @Entity({ abstract: true, ... }). The codemod folds each decorator in, its comments with it.
A JSON index entry reads its path off the column’s ref, as the $where key it serves: index([user.settings.theme]) for text, { column: user.settings.rating, type: Number } for any other value, and type: [String] for an array’s elements. jsonPath and jsonArray are gone. An unnamed JSON index is now named after its path too, so the next migration rebuilds each one once. In the migration builder an expression entry is { expression: sql`lower(email)` }. An entry’s length is prefixLength on a column and charLength on a JSON path.
An entity is a decorated class or defineEntity({ name, fields }) with its other options as chained sections. defineEntity(Class, options), defineField, defineId, defineRelation, defineHook, defineIndex, defineTrigger and defineFilter are gone, and so are @Entity’s fields, relations, hooks and extends. The codemod rewrites a class defineEntity registered as export const Post = defineEntity({ name: 'Post', ... }), a table it named kept as table, folding the same class’s definers in; it reports a class it cannot convert as it is, such as one with methods. A decorated class names its base by extending it.
A foreign key column holding its own type must hold the type of the key it references, and f.decimal(precision, scale) carries its digits into its type: tsc names each mismatch.
$exclude is gone: write $select: { password: false }, which the codemod rewrites it to. A server answers a request still sending it with a 400, as it does any $ key a query does not take.
A $sort that projects a distance or a score puts it on the row’s type, so drop the as WithProjection<...> cast: WithProjection is gone. An empty $project throws on every SQL engine.
On MongoDB Atlas, a vector $sort’s $project holds the distance, lower nearer, as on the SQL engines, not Atlas’s score: flip a check like score > 0.8 into a cut-off on the distance.
0.104.0 - 2026-10-22
Section titled “0.104.0 - 2026-10-22”A read captures its whole query, so QueryFindResult takes the field names or the query: QueryFindResult<User, 'id' | 'name'> keeps working, and QueryFindResult<User, typeof query> replaces the forms that named $select values, $exclude, $populate and $count key by key. QueryProjected, QueryOneProjected, QueryKeysetProjected and ProjectedQuery are gone: a function forwarding a query types it Captured<Query<E>, Q>, as the querier page shows. ReturningResult is WriteResults. tsc names every use to change.
0.103.0 - 2026-10-21
Section titled “0.103.0 - 2026-10-21”NeonQuerierPool takes Neon’s ClientConfig: drop pool options such as max and idleTimeoutMillis. It holds no connection, so build it once at module scope, Cloudflare Workers included, and drop the pool.end() a handler ran for it.
0.102.0 - 2026-10-11
Section titled “0.102.0 - 2026-10-11”On MongoDB, an updateMany or deleteMany that is paged, filtered through a relation, hooked, cascading or returning runs in a transaction, so it needs a replica set. A single-node one is enough for development.
0.101.4 - 2026-10-10
Section titled “0.101.4 - 2026-10-10”uql-orm/migrate and uql-orm/nestjs no longer export the schema introspector classes or UqlContextInterceptor;
the migrator and the Nest module still use them.
0.101.2 - 2026-10-10
Section titled “0.101.2 - 2026-10-10”raw is renamed sql, which editors highlight as SQL: sql`...` , sql.join, sql.text. raw still works but
is deprecated; uql-codemod renames it.
0.101.1 - 2026-10-10
Section titled “0.101.1 - 2026-10-10”uql-orm/migrate, uql-orm/http and the driver entries export only what the docs use. tsc names any import that
is gone.
0.100.0 - 2026-10-08
Section titled “0.100.0 - 2026-10-08”Run uql-codemod first. It moves imports to the renamed entries (uql-orm/mariadb, uql-orm/mongodb,
uql-orm/bun-sql) and off the removed ones (uql-orm/type, uql-orm/dialect, uql-orm/entity, uql-orm/querier,
uql-orm/namingStrategy, now uql-orm), and renames Sqlite3QuerierPool to SqliteQuerierPool and
IMigrationBuilder-style types to MigrationBuilder. Then:
- Upgrade to TypeScript 5.7 or newer, and keep each driver on a supported major (
pg8,mongodb6 or 7, …). - Import only what the docs show from
uql-orm: the abstract dialect and querier classes,SqlQueryContextand helpers such asfieldOfare internal, anduql-orm/utilis gone (the codemod movesraw,refsand the other names the root still exports). allandrunare tags,pool.all`SELECT ... ${id}`, which bind each value. The codemod rewrites a literal statement into one, its?or$1values included, and wraps SQL built at run time insql.text(...). Wrap a SQL string you interpolate into a tag insql.texttoo, or it is bound as a value (building a statement). An interpolatedundefinedthrows, and a JSON document is bound as${JSON.stringify(doc)}.- A pool call inside a
pool.transaction()callback joins the transaction, uncommitted writes included. To read what is committed instead, use a querier of its own,await using querier = await pool.getQuerier(), on an engine with more than one connection (not SQLite, PGlite or local Turso). - In
/http,HookContextandHookareRequestHookContextandRequestHook. - A
decimalis astring. Declaretype: Numberto keep a number. Json<unknown>is a bareJson.upsertOneresolves to the id andupsertManyto the ids: readids[0]forfirstId, and dropcreatedandchanges.- An
undefinedin$whereor$havingthrows. Leave the key out, or writenull. - A nested
transaction()is a savepoint, and is refused on MongoDB. - A to-many update removes the children the payload leaves out. A to-one holding the key cascades
'persist'only (relations). migrator.down()with no options reverts one migration; pass{ step: Infinity }for all.- Replace
pending()andexecuted()withstatus(). Drop thestorageoption and the migration storages (JsonMigrationStorage,MongoMigrationStorage). generate:entitiesrenames a column in place only when the two names differ by case or underscores (firstName,first_name). Write any other rename into the migration yourself, asrenameColumn, before it drops the column.- Catch
UqlUsageErrorwhere you caughtUqlLockUsageError. - On MongoDB, the next migration or
syncgives each collection with checks or enums a validator, replacing a hand-written one. Stored documents are checked on their next update.
0.99.0 - 2026-10-07
Section titled “0.99.0 - 2026-10-07”- A migration file that fails to load, or lacks
upanddown, stopsupanddownbefore anything runs. Move helper modules out of the migrations directory. Migrator.pending()returns names. ReplacegetMigrations,getMigrationFiles,loadMigrationandisMigrationwithstatus(),up()anddown().
0.98.0 - 2026-10-07
Section titled “0.98.0 - 2026-10-07”$regexis case-sensitive on MySQL and MariaDB too. Spell both cases where you relied on the collation:'^[Jj]ohn'.
0.97.0 - 2026-10-07
Section titled “0.97.0 - 2026-10-07”- Checks and enums are diffed and installed as
_uql_<table>__<name>_<hash>. The next migration orsyncadds each under its new name; drop the old ones by hand (each is warned about), or a widened enum keeps rejecting its new values. - On SQLite, D1 and Turso, that change rebuilds each table with a check or an enum. On D1 or remote libSQL, a referenced table needs foreign keys off to rebuild.
0.96.0 - 2026-10-06
Section titled “0.96.0 - 2026-10-06”- A custom
MigrationStoragedoes not needensureStorage. Migrator.loggeris read-only: passloggerin theMigratoroptions.CreateSchemaOptions.foreignKeysis gone; a schema always carries its foreign keys.drift:checkon MongoDB fails where it reported drift. Usesync --dry-runto see index changes.- Turso needs
@tursodatabase/database0.8.2+ and@tursodatabase/serverless1.4.1+. - The next
syncindexes a foreign key whose only covering index is partial, fulltext, vector or a MySQL prefix one.
0.95.0 - 2026-10-01
Section titled “0.95.0 - 2026-10-01”- An upsert takes its update as the fourth argument,
upsertOne(Entity, paths, row, { uses: { $inc: 1 } }), not{ update: ... }.uql-codemodrewrites it. - An empty update
{}leaves a conflicting row as it is. The codemod cannot tell it from an old empty options object, so delete such a{}yourself. upsertOne,upsertMany, andsaveOne/saveManynaming a key write cascaded relations, replacing a found row’s.
0.94.0 - 2026-10-01
Section titled “0.94.0 - 2026-10-01”- A statement on the querier inside its own
findManyStreamloop throwsUqlUsageError. Run it on another connection, or after the loop on SQLite and PGlite (streaming). findManyStreamruns@AfterLoadper row, so a hook querying through itsquerieris refused there too.- A statement binding more values than its engine allows is refused: 100 on D1, 2098 on SQL Server, 32766 on
SQLite, 32767 on PGlite, 65535 on MySQL and MariaDB. Split a long
$in; lists UQL builds are split for you.
0.93.0 - 2026-10-01
Section titled “0.93.0 - 2026-10-01”- The migration builder’s
expris gone.uql-codemodrewrites each default as an entity spells it (expr.now()iscurrentTimestamp). expr.onUpdateNow()has no rewrite: usem.raw(...), or a stamp.
0.92.0 - 2026-09-30
Section titled “0.92.0 - 2026-09-30”- A
Datefield withoutprecisionstores milliseconds on every engine (TIMESTAMPTZ(3),DATETIME2(3)). Generate a migration orsync, which rounds past the millisecond;precision: 6keeps a column. - On MongoDB, a
$selectwithout the key does not returnid. Name it. - Over HTTP, an update or delete answers its changed row count in
data, not ids. ADELETEnaming no rows is a400. uql-orm/browserdropsgetQuerier,getQuerierPool,setQuerierPoolandClientQuerierPool. Createnew HttpQuerier('/api')where you use it;uql-codemodpoints at each use.
0.91.0 - 2026-09-28
Section titled “0.91.0 - 2026-09-28”- An insert must include every field declared
!, except a single-column id and theversion. Declare it?if adefaultValueoronInsertfills it (required fields). - A
type: 'time'field is astring, not aDate.
0.90.0 - 2026-09-26
Section titled “0.90.0 - 2026-09-26”- Import
MongoQuerier,isMongoQuerierandMongoSchemaIntrospectorfrom the MongoDB entry (uql-orm/mongodbsince 0.100.0), including in each MongoDB migration.uql-codemodmoves them.
0.89.0 - 2026-09-26
Section titled “0.89.0 - 2026-09-26”- On SQL Server, a trigger with
oforwhererefuses a body of your own SQL. Write it withinsertInto,updateTableanddeleteFrom, joined in onesqlwhere there are several. - A
$wheremixing a relation’s$sizewith a condition on its rows, and a$text$fieldsnaming a non-string field, do not compile.
0.88.0 - 2026-09-26
Section titled “0.88.0 - 2026-09-26”syncandgenerate:entitiesrefuse a newly required field with nodefaultValueon a table with rows. Declare a default, or add it nullable, fill it, then require it.- On SQLite,
syncrebuilds a table to add a relation’s foreign key: read aplanSync()before the next one. - On SQL Server, a trigger with
ofgets a new name, whichsyncinstalls in place of the old one.
0.86.0 - 2026-09-26
Section titled “0.86.0 - 2026-09-26”uql-orm/betterAuthis removed until it returns as its own package.
0.85.0 - 2026-09-25
Section titled “0.85.0 - 2026-09-25”createFetchHandler,createRequestHandlerandquerierMiddlewarerequireincludeand serve only those entities:createFetchHandler({ pool, include: [User, Post] }).excludeis gone. Leave those entities out ofinclude.
0.84.0 - 2026-09-25
Section titled “0.84.0 - 2026-09-25”A security filter guards writes as well as reads. A write outside it throws UqlSecurityError (403 over HTTP).
- An insert gets the filter’s fields from the context; naming another value throws. Delete any
onInserttenant fill. - An update changing those fields throws. Move rows between tenants under a trusted context resolving to
{}. - A save or upsert naming another tenant’s row fails on its key. On a guarded entity it reads conflicting rows first, so it runs two or three statements.
- A filter that is not plain field equalities (
$in,$or) refuses writes. Write those rows under a{}context. UqlUsageErroris not aTypeError; all three UQL errors extendUqlError. TestqueryErrorKind(err) === 'usage'instead ofinstanceof TypeError.
0.83.0 - 2026-09-24
Section titled “0.83.0 - 2026-09-24”Every SQL engine binds and reads a Date as UTC. Dates written from a non-UTC process read shifted until their
columns are converted, so generate a migration:
- Postgres, CockroachDB:
Datecolumns becomeTIMESTAMPTZ. Run the migration with the sessionTimeZoneset to the zone the app wrote in (usuallyUTC).columnType: 'timestamp'keeps a column zoneless. ADATEreads as UTC midnight. - MySQL, MariaDB:
DATETIMEbecomesDATETIME(3);precision: 0keeps whole seconds. Sessions run in UTC. A raw read on MariaDB returns aDATETIMEas text. - SQLite, libSQL, Turso, D1: dates are stored as UTC text, not epoch milliseconds. Convert rows with
UPDATE t SET col = strftime('%Y-%m-%d %H:%M:%f', col / 1000.0, 'unixepoch') WHERE col NOT LIKE '%-%'.
0.80.0 - 2026-09-23
Section titled “0.80.0 - 2026-09-23”- On SQL engines,
$neexcludes rows where the column is NULL, like$nin,$notand$nor. MongoDB keeps them. To include them:{ $or: [{ status: { $ne: 'draft' } }, { status: null }] }.
0.79.0 - 2026-09-21
Section titled “0.79.0 - 2026-09-21”length,precisionandscaleontype: 'varchar'or'decimal', anddimensionsoncolumnType: 'vector', reach the column. Existing columns read as drift and are altered: check the values fit.
0.77.1 - 2026-09-20
Section titled “0.77.1 - 2026-09-20”- Rename
UqlLockUsageErrortoUqlUsageError, thrown by every misuse. The old name still resolves to it. - Over HTTP, a call the API cannot carry out answers
400, not500.
0.77.0 - 2026-09-19
Section titled “0.77.0 - 2026-09-19”- On an entity with a version field,
updateManyover many rows is refused. Update each row by id.
0.73.0 - 2026-09-18
Section titled “0.73.0 - 2026-09-18”- Rename
WithDistanceandWithScoretoWithProjection. - On SQLite, libSQL and Turso, a vector column created before 0.71.0 is
TEXTand reported bydrift:check. Recreate its table in a migration with the column asF32_BLOB, which libSQL’s vector index needs.
0.70.0 - 2026-09-18
Section titled “0.70.0 - 2026-09-18”- Run the codemod to append
| nullto each nullable column’s property. Give a column that never holds nullnullable: falseinstead. - An
updateManyordeleteManynaming no rows throws. Pass{ unfiltered: true }for the whole table. - A write payload naming a
readonlyfield does not compile. - Replace
$sumDistinctand$avgDistinctwith a$groupon the field.
0.68.1 - 2026-09-17
Section titled “0.68.1 - 2026-09-17”- An RPC contract takes
WireQuery<E>. - A
sqlfragment orUint8Arrayin a browser-client query or payload does not compile, and a cast past the types throws. Keep both server-side. - Run the codemod to rename
D1DatabasetoD1Queryable. - A query type takes its SQL type before its key set:
QueryWhere<E, Sql, K>.
0.68.0 - 2026-09-17
Section titled “0.68.0 - 2026-09-17”- On PostgreSQL and CockroachDB, generate a migration for each numeric
jsonPathindex, whose expression changed. $elemMatchwith one$eqor$incompares by JSON type, so'5'does not match5. An object in$allmatches an element holding its keys.
0.66.0 - 2026-09-16
Section titled “0.66.0 - 2026-09-16”- Run the codemod: every to-one names its foreign key in
referencesand declares the column. For a composite key, declare a column per key and pair them. tscthen reports what registration refused at startup: a skipping security filter, and amappedByorreferencescolumn that cannot hold the key it joins.
0.65.0 - 2026-09-14
Section titled “0.65.0 - 2026-09-14”- Run the codemod, then give each junction column
references. - Declare a relation for any
@Field({ references })column you$populate. - Read
pool.dialect.dialectNameinstead ofmigrator.dialectName.
0.63.0 - 2026-09-13
Section titled “0.63.0 - 2026-09-13”- Write an index expression as
sqlin the list,(user) => [sql`lower(${user.email})`]. - Move SQLite off the Bun SQL entry to
SqliteQuerierPool. - Upgrade
@tursodatabase/serverlessto 1.3+, and pass a libSQL client you built toLibsqlQuerierPool. - SQLite reads an integer past 2^53 as text.
0.62.0 - 2026-09-13
Section titled “0.62.0 - 2026-09-13”- Run the codemod for the renamed options and
col(), which isrefs(Entity). - SQL an entity declares reads columns off a callback’s refs:
(user) => sql`lower(${user.email})`.
0.60.0 - 2026-09-12
Section titled “0.60.0 - 2026-09-12”- A builder migration gets the builder first and the querier second:
up(m, querier). Movequerier.runcalls todefineMigration.
0.58.0 - 2026-09-11
Section titled “0.58.0 - 2026-09-11”Members are named by callbacks and keys, never strings, and the codemod rewrites all of it:
- Definitions:
@Index((post) => [post.title]),mappedBy: (post) => post.author,references. aggregate()takes computed columns in$select, not$agg, and$texttakes$fields: { title: true }.
await pool.aggregate(Order, { $group: { status: true }, $select: { total: { $sum: { amount: true } } },});0.57.0 - 2026-09-11
Section titled “0.57.0 - 2026-09-11”- A to-many
$populateand$countread in the parent’s statement, which needs MySQL 8.0.14+ and SQLite 3.44+. - Each table reads under its own name, relation key or join path, so raw SQL naming a related table uses that alias.
- Every foreign key gets an index unless one leads with it (
index: falseopts out). The next migration adds them.
0.55.0 - 2026-09-10
Section titled “0.55.0 - 2026-09-10”- A BIGINT past 2^53 reads as exact text. Declare
type: BigIntfor a typed exact integer. - The codemod renames
CrdbQuerier/NeonQueriertoPgQuerierandLibsqlQuerier/TursoQueriertoHranaQuerier. - Raw access on Bun SQL is
pool.sql.
0.54.0 - 2026-09-10
Section titled “0.54.0 - 2026-09-10”virtualand theraw('...')call are gone; the codemod rewrites both.- An
after*hook’sthisis a copy of the row as written, so mutating it leaves your object alone.
0.53.0 - 2026-09-10
Section titled “0.53.0 - 2026-09-10”$where takes only a map. An id, a list of ids or a bare SQL fragment there does not compile, and answers 400 over
HTTP. Name the key, and put SQL fragments in $and. The codemod renames QueryWhereMap to QueryWhere.
import { sql } from 'uql-orm';
await pool.findMany(User, { $where: { id: [1, 2] } });await pool.findMany(User, { $where: { $and: [sql`"createdAt" > now()`] } });0.52.0 - 2026-09-09
Section titled “0.52.0 - 2026-09-09”BunSqlPostgresDialect,BunSqlCockroachDialectandBunSqliteDialectare gone; the pool builds the dialect.
0.48.0 to 0.51.0 - 2026-09-09
Section titled “0.48.0 to 0.51.0 - 2026-09-09”saveOne/saveManyupsert on the key a row names. A stale id writes the row, and composite keys work.- A save naming its key fires
@BeforeUpsert/@AfterUpsert: move logic from@BeforeUpdate. - Every write reports its id as the column value, or the key map on a composite.
upsertOneandupsertManyreturnidandids(payload order), notfirstId.- A key not called
id,_idoruuid, and every composite, needs theidKeybrand, which the codemod writes:
import { Entity, Field, f, idKey } from 'uql-orm';
@Entity()export class Enrolment { [idKey]?: 'studentId' | 'courseId';
@Field(f.number().id()) studentId!: number; @Field(f.string().id()) courseId?: string;}- On MongoDB, a key you supply is the document’s
_id; older documents keep their minted id. Ids read as hex strings, and a database-generated key must be one MongoDB can mint.
0.46.0 - 2026-09-08
Section titled “0.46.0 - 2026-09-08”0.45.0 - 2026-09-08
Section titled “0.45.0 - 2026-09-08”syncapplies foreign keys (except on SQLite): read aplanSync()before the first one.- On MySQL and MariaDB, generated keys are not
UNSIGNED, and anUNSIGNEDkey refuses constraints pointing at it. On a database created earlier, run this (undersafe: false) before adding foreign keys, as a migration rather than at boot, since it rebuilds the table under a metadata lock:
ALTER TABLE `Company` MODIFY COLUMN `id` BIGINT AUTO_INCREMENT;- A key filled by
onInsertis not auto-increment. serial,bigserialandsmallserialare gone ascolumnType. Use@Id({ type: Number })or@Id({ type: Number, columnType: 'int' }).
0.44.0 - 2026-09-07
Section titled “0.44.0 - 2026-09-07”- A naming strategy does not rewrite a table you named, so check what
@Entity({ name: 'UserProfile' })maps to. migrator.autoSync,syncForceandsyncEntityare onemigrator.sync(options).
0.43.0 - 2026-09-07
Section titled “0.43.0 - 2026-09-07”An option a column cannot use is an error. Delete:
length,precision,scale,autoIncrement,dimensionsanddistancewhere the type has no use for them.- DDL or generator options on a
virtualfield, andonUpdatebesideupdatable: false. nullable: trueon a key.- A
defaultValuethat is not the stored value (a JSON column keeps its SQL literal,defaultValue: '{}').
0.42.1 - 2026-09-05
Section titled “0.42.1 - 2026-09-05”- Keys and indexes are recognised by their columns, never by name, so nothing in your database is renamed.
- Adding a key column to a table with rows fails until you fill it.
0.42.0 - 2026-09-04
Section titled “0.42.0 - 2026-09-04”- A second
@Idcomposes a two-columnPRIMARY KEYinstead of replacing the first. Narrowing an inherited key is unaffected. EntityMeta.idisids, a list.
0.24.0 - 2026-08-02
Section titled “0.24.0 - 2026-08-02”- The pool runs every operation, so a writing function can take
UniversalQuerier. - A hand-written
QuerierPoolmust implementinsertMany,updateMany,upsertOne,upsertManyandsaveMany.
0.23.0 - 2026-08-01
Section titled “0.23.0 - 2026-08-01”Decorators are the standard TC39 ones. The codemod drops experimentalDecorators,
emitDecoratorMetadata and reflect-metadata, and reports what is left. Then:
- Set
targetto anything butesnext, which leaves decorators untransformed. - Build with esbuild, SWC, Babel
version: '2023-11', Bun ortsc. Oxc does not transform them, so Vite 8 needs one of the others. - Run
uql.config.tswithbunornode --import tsxif it imports entities. - On NestJS, use
defineEntity: Nest’s DI needs parameter decorators (NestJS). - Use Node 22.18 or newer (24 before 0.101.0).