<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:fh="http://purl.org/syndication/history/1.0"><channel><title>UQL | Blog</title><description>The smartest TypeScript ORM: serializable queries, type-safe to the leaf, no codegen, fast, and a unified API across Postgres, CockroachDB, MySQL, SQLite, MongoDB, and the edge.</description><link>https://uql-orm.dev</link><language>en</language><fh:complete/><atom:link rel="self" href="https://uql-orm.dev/blog/rss.xml"/><item><title>What does an ORM really cost you?</title><link>https://uql-orm.dev/blog/what-orms-really-cost</link><guid isPermaLink="true">https://uql-orm.dev/blog/what-orms-really-cost</guid><description>Six ORMs, one full PostgreSQL lifecycle, timed per step. Prisma comes fifth of six, and one step is responsible for most of it.</description><pubDate>Sun, 09 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My bench now does real stuff, full PostgreSQL round-trip operations.&lt;/p&gt;
&lt;p&gt;Six ORMs, one real PostgreSQL, and a full lifecycle per pass: insert 10 rows, read 200 back with a filter and a sort, update one, read it again, load 50 parents with their children, delete, read the empty table. Every step timed separately, median of 250 interleaved iterations.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-results&quot;&gt;The results&lt;/h2&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry&lt;/th&gt;
&lt;th&gt;Adds&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UQL&lt;/td&gt;
&lt;td&gt;+232µs&lt;/td&gt;
&lt;td&gt;1,621µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;+685µs&lt;/td&gt;
&lt;td&gt;2,074µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeORM&lt;/td&gt;
&lt;td&gt;+838µs&lt;/td&gt;
&lt;td&gt;2,227µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequelize&lt;/td&gt;
&lt;td&gt;+1,185µs&lt;/td&gt;
&lt;td&gt;2,574µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prisma&lt;/td&gt;
&lt;td&gt;+1,345µs&lt;/td&gt;
&lt;td&gt;2,734µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MikroORM&lt;/td&gt;
&lt;td&gt;+2,236µs&lt;/td&gt;
&lt;td&gt;3,625µs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Adds&lt;/strong&gt; is the number that matters, and it is the whole reason this benchmark works. Every entry pays the same PostgreSQL bill, so the totals compress into a 2.2x range and every ORM looks about the same. Subtract the floor, which is hand-written SQL with the rows mapped by hand at 1,389µs, and what is left is the ORM’s own contribution. That spans nearly 9.6x.&lt;/p&gt;
&lt;p&gt;Everything above runs on node-postgres, so the driver is not the variable. Four of the six support nothing else.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;prisma&quot;&gt;Prisma&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Prisma is not slow across the board, and it does not finish last; it was MikroORM at 3,625µs against Prisma’s 2,734µs. Prisma’s nested read, the hardest step in the set, is the second fastest of any ORM here at 440µs. Its update, delete and single-row reads are all mid-field.&lt;/p&gt;
&lt;p&gt;It is one step:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;INSERT 10 rows&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;raw pg (hand-written)&lt;/td&gt;
&lt;td&gt;454µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UQL&lt;/td&gt;
&lt;td&gt;488µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MikroORM&lt;/td&gt;
&lt;td&gt;625µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;635µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequelize&lt;/td&gt;
&lt;td&gt;643µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeORM&lt;/td&gt;
&lt;td&gt;668µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prisma&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,366µs&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That one step is 2.0x the next slowest ORM and it is most of why Prisma places fifth. Turning on its query log shows what it sends:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;What Prisma sends for createManyAndReturn&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;INSERT INTO&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;email&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;companyId&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;VALUES&lt;/span&gt;&lt;span&gt; ($&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,$&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;,$&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;), ($&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;,$&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;,$&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;), ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;COMMIT&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;So &lt;code dir=&quot;auto&quot;&gt;createManyAndReturn&lt;/code&gt; wraps the batch in an explicit transaction, where the other five send one statement and stop. That is a fair thing to do and it is not the explanation: a BEGIN/COMMIT pair on this machine costs 88µs, and the gap to TypeORM, the next slowest here, is 698µs. The transaction is about an eighth of it. The rest is Prisma’s own overhead on the way in and out, which is the part you cannot opt out of.&lt;/p&gt;
&lt;p&gt;Prisma 7 dropped its Rust query engine, and the client runtime is now TypeScript. This is the faster Prisma, not the old one.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-nested-read-is-where-orms-are-actually-decided&quot;&gt;The nested read is where ORMs are actually decided&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;It is the only step that loads a relation, and it has the widest spread of any read:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SELECT 50 parents with their children&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;raw pg (hand-written)&lt;/td&gt;
&lt;td&gt;256µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UQL&lt;/td&gt;
&lt;td&gt;346µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prisma&lt;/td&gt;
&lt;td&gt;440µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeORM&lt;/td&gt;
&lt;td&gt;498µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;535µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequelize&lt;/td&gt;
&lt;td&gt;680µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MikroORM&lt;/td&gt;
&lt;td&gt;1,205µs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is the step worth caring about, because it is the one an ORM exists to do. Anyone can send an &lt;code dir=&quot;auto&quot;&gt;INSERT&lt;/code&gt;. Turning two result sets into an object graph without an N+1 is the actual job, and the field spans 3.5x on it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;run-it-yourself&quot;&gt;Run it yourself&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://github.com/rogerpadilla/ts-orm-benchmark.git&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ts-orm-benchmark&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bun&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;DATABASE_URL&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;postgres:///postgres&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bench&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It creates its own database and rewrites its own result tables, so the published numbers cannot drift from the last run. CI runs the full lifecycle with assertions on every push.&lt;/p&gt;
&lt;p&gt;The latest set always lives on the &lt;a href=&quot;https://uql-orm.dev/benchmark&quot;&gt;benchmark page&lt;/a&gt;, and there is a feature-by-feature &lt;a href=&quot;https://uql-orm.dev/comparison&quot;&gt;ORM comparison&lt;/a&gt; if speed is not your only axis. If your numbers come out different, open an issue.&lt;/p&gt;
&lt;hr&gt;
&lt;section id=&quot;giscus-wrapper&quot;&gt;&lt;!-- Content injected via client-side script to support View Transitions --&gt;&lt;/section&gt;</content:encoded><category>benchmark</category><category>performance</category><category>postgresql</category><category>orm</category></item><item><title>Zero dependencies: what we deleted</title><link>https://uql-orm.dev/blog/zero-dependencies</link><guid isPermaLink="true">https://uql-orm.dev/blog/zero-dependencies</guid><description>npm i uql-orm installs one package and nothing else. Getting there meant deleting a 1.8 MB transpiler, a 264 KB metadata polyfill, and the sourcemaps nobody read.</description><pubDate>Sun, 02 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://uql-orm.dev/&quot;&gt;UQL&lt;/a&gt; is a dependency-free TypeScript ORM for PostgreSQL, MySQL, MariaDB, SQLite and MongoDB.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;npm i uql-orm&lt;/code&gt; installs one package. No dependencies, every dialect included.&lt;/p&gt;
&lt;p&gt;A month ago it had four mandatory packages, roughly 4 MBs unpacked; now it is 992 KB unpacked.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-release-four-dependencies-gone&quot;&gt;One release, four dependencies gone&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;0.21.0&lt;/code&gt; did it in one pass: ~4.0 MB down to 992 KB, cold start from 16.9 ms to 8.9 ms. Four packages, one job each, none worth a dependency of its own.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;tslib&lt;/code&gt; existed so TypeScript’s helper functions didn’t repeat across files. &lt;code dir=&quot;auto&quot;&gt;&quot;importHelpers&quot;: false&lt;/code&gt; costs a few hundred bytes per file instead of a dependency edge.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;sqlstring&lt;/code&gt; escaped MySQL and MariaDB literals, badly: a &lt;code dir=&quot;auto&quot;&gt;Uint8Array&lt;/code&gt; came out as &lt;code dir=&quot;auto&quot;&gt;`0` = 255&lt;/code&gt; instead of &lt;code dir=&quot;auto&quot;&gt;X&apos;ff00&apos;&lt;/code&gt;, a plain object as &lt;code dir=&quot;auto&quot;&gt;&apos;[object Object]&apos;&lt;/code&gt;. No crash, no warning, just wrong output that looks fine until it isn’t. &lt;code dir=&quot;auto&quot;&gt;Dialect.escape&lt;/code&gt; handles all three SQL dialects itself now, byte-for-byte identical across 29 value shapes, and those two cases throw instead of writing garbage.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;reflect-metadata&lt;/code&gt; (264 KB) earned its keep with one call, &lt;code dir=&quot;auto&quot;&gt;Reflect.getMetadata(&apos;design:type&apos;, ...)&lt;/code&gt;, so &lt;code dir=&quot;auto&quot;&gt;@Field()&lt;/code&gt; could guess a column’s type from the property. Optional as of this release, gone two releases later.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;jiti&lt;/code&gt; (1.8 MB), a full TypeScript transpiler, existed to read one file: &lt;code dir=&quot;auto&quot;&gt;uql.config.ts&lt;/code&gt;. That’s a lot of compiler to hire for one sticky note. Also gone; that config now needs a runtime that already speaks TypeScript (&lt;code dir=&quot;auto&quot;&gt;bun&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;node --import tsx&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Then the stuff that was never code: 369 sourcemaps nobody opened, and a 108 KB CHANGELOG, shipped in every install anyway. &lt;code dir=&quot;auto&quot;&gt;files&lt;/code&gt; in &lt;code dir=&quot;auto&quot;&gt;package.json&lt;/code&gt; is &lt;code dir=&quot;auto&quot;&gt;[&quot;dist&quot;, &quot;README.md&quot;]&lt;/code&gt; now.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;then-reflection-went-too&quot;&gt;Then reflection went too&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Two releases later, &lt;code dir=&quot;auto&quot;&gt;reflect-metadata&lt;/code&gt; stopped being optional and started being pointless. &lt;code dir=&quot;auto&quot;&gt;0.23.0&lt;/code&gt; moved decorators to the &lt;a href=&quot;https://github.com/tc39/proposal-decorators&quot;&gt;TC39 standard spec&lt;/a&gt;: &lt;code dir=&quot;auto&quot;&gt;@Field()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;@Id()&lt;/code&gt; require an explicit &lt;code dir=&quot;auto&quot;&gt;type&lt;/code&gt; now, checked against the property, so there’s nothing left to reflect. The full story, including what it cost, is in &lt;a href=&quot;https://uql-orm.dev/blog/standard-decorators&quot;&gt;Standard decorators: props &amp;#x26; cons&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-one-npm-i-puts-on-disk&quot;&gt;What one &lt;code dir=&quot;auto&quot;&gt;npm i&lt;/code&gt; puts on disk&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Install one package into an empty project and count the bytes yourself:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--omit=dev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uql-orm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;node_modules&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;wc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Unpacked bytes on disk, not the 288 kB you download. Tarballs compress; cold starts don’t care.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Installed&lt;/th&gt;
&lt;th&gt;Files&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;uql-orm&lt;/code&gt; 0.24.1&lt;/td&gt;
&lt;td&gt;1.0 MB&lt;/td&gt;
&lt;td&gt;384&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;@mikro-orm/postgresql&lt;/code&gt; 7.1.9&lt;/td&gt;
&lt;td&gt;4.7 MB&lt;/td&gt;
&lt;td&gt;1,153&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;drizzle-orm&lt;/code&gt; 0.45.2&lt;/td&gt;
&lt;td&gt;9.9 MB&lt;/td&gt;
&lt;td&gt;2,667&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;sequelize&lt;/code&gt; 6.37.8&lt;/td&gt;
&lt;td&gt;15.0 MB&lt;/td&gt;
&lt;td&gt;2,708&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;typeorm&lt;/code&gt; 1.1.0&lt;/td&gt;
&lt;td&gt;22.5 MB&lt;/td&gt;
&lt;td&gt;3,663&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;@prisma/client&lt;/code&gt; 7.9.1&lt;/td&gt;
&lt;td&gt;75.0 MB&lt;/td&gt;
&lt;td&gt;94&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;One thing to hold against this table before you screenshot it: 93% of &lt;code dir=&quot;auto&quot;&gt;@prisma/client&lt;/code&gt; is 70 MB of Rust query compilers cross-compiled to WebAssembly, one per engine it supports, which is either an engineering marvel or a cry for help. UQL ships every dialect too, so that part isn’t a fair hit on Prisma. Per dialect it’s actually smaller: 4.9 MB for its Postgres compiler alone against 19.9 kB gzipped for UQL’s entire Postgres entry point.&lt;/p&gt;
&lt;p&gt;No row includes a driver. Add &lt;code dir=&quot;auto&quot;&gt;pg&lt;/code&gt; to any of them and they all grow the same amount. This table is the floor.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-small-number-doesnt-cover&quot;&gt;What the small number doesn’t cover&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;UQL is the small number here, your driver isn’t. &lt;code dir=&quot;auto&quot;&gt;pg&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;mysql2&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;mariadb&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;mongodb&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;better-sqlite3&lt;/code&gt; all have their own weight, and the last one needs a native build. UQL’s promise is narrower than “small”: it adds nothing on top of whichever one you already picked.&lt;/p&gt;
&lt;p&gt;Every entry point has a gzip budget checked on every build, because 0.13.0 once let &lt;code dir=&quot;auto&quot;&gt;node:async_hooks&lt;/code&gt; leak into browser bundles and had to be pulled. Lesson learned, budget added.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;get-started&quot;&gt;Get started&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uql-orm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://uql-orm.dev/getting-started&quot;&gt;Quick Start&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://uql-orm.dev/blog/standard-decorators&quot;&gt;Standard decorators: props &amp;#x26; cons&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://uql-orm.dev/comparison&quot;&gt;How UQL compares&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/rogerpadilla/uql&quot;&gt;GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your install contains something that shouldn’t be there, open an issue.&lt;/p&gt;
&lt;hr&gt;
&lt;section id=&quot;giscus-wrapper&quot;&gt;&lt;!-- Content injected via client-side script to support View Transitions --&gt;&lt;/section&gt;</content:encoded><category>performance</category><category>bundle-size</category><category>orm</category></item><item><title>Standard decorators: props &amp; cons</title><link>https://uql-orm.dev/blog/standard-decorators</link><guid isPermaLink="true">https://uql-orm.dev/blog/standard-decorators</guid><description>The annotation is now checked against the property it describes, which legacy decorators could never do. It cost three decorators, parameter injection, and NestJS compatibility.</description><pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://uql-orm.dev/&quot;&gt;UQL&lt;/a&gt; is a dependency-free TypeScript ORM for PostgreSQL, MySQL, MariaDB, SQLite and MongoDB.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;UQL 0.23 moved its decorators to the &lt;a href=&quot;https://github.com/tc39/proposal-decorators&quot;&gt;TC39 standard spec&lt;/a&gt;. &lt;code dir=&quot;auto&quot;&gt;experimentalDecorators&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;emitDecoratorMetadata&lt;/code&gt; are gone, &lt;code dir=&quot;auto&quot;&gt;reflect-metadata&lt;/code&gt; is gone, and one thing became possible that was not possible before.&lt;/p&gt;
&lt;p&gt;It also deleted three decorators, removed parameter injection, and locked NestJS out of the decorator API entirely. Migration posts usually stop at the wins. This is the whole ledger.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-it-looks-like&quot;&gt;What it looks like&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Before: legacy decorators&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Entity, Field, Id, ManyToOne } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;uql-orm&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;reflect-metadata&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Entity&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Id&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Field&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;ManyToOne&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;company&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Relation&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Company&lt;/span&gt;&lt;span&gt;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;After: the standard spec&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Entity&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Id&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Number&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Field&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;ManyToOne&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Company&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;company&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Company&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;More typing. &lt;code dir=&quot;auto&quot;&gt;type&lt;/code&gt; on every field, &lt;code dir=&quot;auto&quot;&gt;entity&lt;/code&gt; on every relation, because nothing reflects any more. That looks like a pure loss until you notice what the compiler can now do with it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-type-you-declare-is-checked-against-the-property&quot;&gt;The type you declare is checked against the property&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the part worth the migration.&lt;/p&gt;
&lt;p&gt;Under the legacy spec, a property decorator has this shape:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PropertyDecorator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Object&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;propertyKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;symbol&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;There is no type parameter carrying the property’s type. The decorator is handed a key and nothing else, so the options you pass it are just data. Write this and it compiled fine:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Field&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;age&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; number;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You got a &lt;code dir=&quot;auto&quot;&gt;TEXT&lt;/code&gt; column for a number, and you found out when the data looked wrong. &lt;code dir=&quot;auto&quot;&gt;emitDecoratorMetadata&lt;/code&gt; did not save you, because an explicit &lt;code dir=&quot;auto&quot;&gt;type&lt;/code&gt; skipped inference entirely:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;the old definition.ts&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (opts.type) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;opts, typeInferred&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;opts, type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;inferType&lt;/span&gt;&lt;span&gt;&lt;span&gt;(entity, key), typeInferred&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Reflection was the fallback, not the check. The moment you stated a type, the one thing that knew the real type stopped looking.&lt;/p&gt;
&lt;p&gt;The standard spec hands a field decorator a &lt;code dir=&quot;auto&quot;&gt;ClassFieldDecoratorContext&amp;#x3C;This, Value&gt;&lt;/code&gt;, which is generic in the field’s value type. That single difference is the whole story, because now a decorator can constrain what it may be attached to:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;packages/uql-orm/src/entity/decorator/members.ts&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/** A member decorator that also constrains the property it may be applied to. */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MemberDecorator&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;V&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ClassFieldDecoratorContext&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;unknown&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;V&lt;/span&gt;&lt;span&gt;&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/** A declared `type` wins; otherwise the column is the referenced primary key&apos;s own type. */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DeclaredValue&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;infer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;FieldType&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TsTypeOf&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;references&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EntityGetter&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;infer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;E&lt;/span&gt;&lt;span&gt;&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IdValue&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;E&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;never&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Field&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;FieldOptions&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;DeclaredValue&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt; ({ &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;FieldType&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;references&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EntityGetter&lt;/span&gt;&lt;span&gt; })&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MemberDecorator&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;DeclaredValue&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;@Field({ type: String })&lt;/code&gt; returns a decorator that only applies to &lt;code dir=&quot;auto&quot;&gt;string | undefined&lt;/code&gt;. Put it on a &lt;code dir=&quot;auto&quot;&gt;number&lt;/code&gt; and it does not compile. The mandatory &lt;code dir=&quot;auto&quot;&gt;type&lt;/code&gt; stopped being redundant typing and became a claim the compiler checks.&lt;/p&gt;
&lt;p&gt;Once one option can name the value a property holds, the others have to agree with it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { OneToMany } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;uql-orm&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;ManyToOne&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Company&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;supplier&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; Vendor;        &lt;/span&gt;&lt;span&gt;// error: entity and property disagree&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;OneToMany&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Item&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;items&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; Item;             &lt;/span&gt;&lt;span&gt;// error: a to-many cardinality needs an array&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Field&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;createdAt&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; Date;         &lt;/span&gt;&lt;span&gt;// error: a Date field is not an integer column&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Field&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;references&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;authorId&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; number;        &lt;/span&gt;&lt;span&gt;// error: User&apos;s key is a uuid, so this column is a string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Id&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;uuid&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;onInsert&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;42&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string;              &lt;/span&gt;&lt;span&gt;// error: a uuid column is not stamped with a number&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;None of these were catchable before. They are not new bugs the migration introduced, they are old bugs it made visible. A type-test suite pins every one of them and fails the build if any stops erroring, and all but the foreign key survive being reached through the imperative &lt;code dir=&quot;auto&quot;&gt;defineEntity&lt;/code&gt; too.&lt;/p&gt;
&lt;p&gt;The last two arrived later, in 0.24.3. Same mechanism, applied to the two other things that decide what a column holds: the key a foreign key points at, and the generator that stamps a value into it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-reflection-was-costing&quot;&gt;What reflection was costing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;reflect-metadata&lt;/code&gt; was 264 KB, carried for one call: &lt;code dir=&quot;auto&quot;&gt;Reflect.getMetadata(&apos;design:type&apos;, ...)&lt;/code&gt;. It also required every consumer to import it once, globally, before any entity loaded, and to remember to keep two compiler flags on.&lt;/p&gt;
&lt;p&gt;It bought less than it looked like. Reflected types could not survive a circular import, which is why &lt;code dir=&quot;auto&quot;&gt;Relation&amp;#x3C;T&gt;&lt;/code&gt; existed at all: an alias whose only job was to break a cycle that reflection itself created. It is deleted now.&lt;/p&gt;
&lt;p&gt;Dropping it is part of why the package &lt;a href=&quot;https://uql-orm.dev/blog/zero-dependencies&quot;&gt;installs 1 MB with no dependencies&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-we-gave-up&quot;&gt;What we gave up&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The honest column.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;@InjectQuerier()&lt;/code&gt; is gone.&lt;/strong&gt; The standard spec has no parameter decorators, and the &lt;a href=&quot;https://github.com/tc39/proposal-class-method-parameter-decorators&quot;&gt;TC39 proposal for them&lt;/a&gt; is still Stage 1. A &lt;code dir=&quot;auto&quot;&gt;@Transactional()&lt;/code&gt; method now reads its querier from &lt;code dir=&quot;auto&quot;&gt;AsyncLocalStorage&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Transactional, currentQuerier } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;uql-orm&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UserService&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Transactional&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Partial&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;currentQuerier&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;insertOne&lt;/span&gt;&lt;span&gt;(User, data);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;@Log()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;@Serialized()&lt;/code&gt; are gone.&lt;/strong&gt; A standard-spec decorator cannot preserve a generic method’s signature, so both would have quietly widened the types of anything they wrapped. The useful half of &lt;code dir=&quot;auto&quot;&gt;@Log()&lt;/code&gt;, error enrichment, was moved into the querier itself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NestJS projects cannot use UQL’s decorators at all.&lt;/strong&gt; Nest injects constructor parameters with a parameter decorator, so a Nest project keeps &lt;code dir=&quot;auto&quot;&gt;experimentalDecorators: true&lt;/code&gt;, and one &lt;code dir=&quot;auto&quot;&gt;tsconfig.json&lt;/code&gt; cannot mix specs. This is not a temporary gap that closes when a proposal advances a stage. Those projects use &lt;a href=&quot;https://uql-orm.dev/entities/imperative&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;defineEntity&lt;/code&gt;&lt;/a&gt; instead, which carries the same checks, bar the foreign key one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;target: &apos;esnext&apos;&lt;/code&gt; is now forbidden.&lt;/strong&gt; It is the one target where TypeScript emits decorator syntax untransformed, which Node and every browser reject with a &lt;code dir=&quot;auto&quot;&gt;SyntaxError&lt;/code&gt;. Every dated target downlevels it correctly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;declare&lt;/code&gt; fields stop working.&lt;/strong&gt; The spec has nothing to decorate on a &lt;code dir=&quot;auto&quot;&gt;declare&lt;/code&gt; member, so narrowing an inherited relation needs a real field with an initializer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Oxc, Vite 8’s default transformer, implements no decorators at all.&lt;/strong&gt; It preserves &lt;code dir=&quot;auto&quot;&gt;@Entity()&lt;/code&gt; verbatim at every target, so a Vite 8 project needs esbuild, SWC or Babel through a plugin. esbuild, SWC, Babel with &lt;code dir=&quot;auto&quot;&gt;version: &apos;2023-11&apos;&lt;/code&gt;, Bun and &lt;code dir=&quot;auto&quot;&gt;tsc&lt;/code&gt; all handle the spec.&lt;/p&gt;
&lt;p&gt;Node 24 is the new minimum, shipped in the same release.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-two-things-that-nearly-broke-it&quot;&gt;The two things that nearly broke it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Neither is in the spec documents, and both cost real time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;Symbol.metadata&lt;/code&gt; does not exist yet.&lt;/strong&gt; No runtime we support defines it, checked on Node 24 and Bun 1.3. TypeScript’s decorator emit reads it to decide whether to build the metadata object at all, so without a polyfill every &lt;code dir=&quot;auto&quot;&gt;context.metadata&lt;/code&gt; is &lt;code dir=&quot;auto&quot;&gt;undefined&lt;/code&gt; and field registration is silently dropped rather than failing. UQL defines it with &lt;code dir=&quot;auto&quot;&gt;Symbol.for&lt;/code&gt;, not &lt;code dir=&quot;auto&quot;&gt;Symbol()&lt;/code&gt;, so a duplicated copy of the module under HMR or dual-loading lands on the same symbol, and so it agrees with the key esbuild and SWC fall back to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tsc and SWC disagree about inheritance.&lt;/strong&gt; tsc chains a subclass’s &lt;code dir=&quot;auto&quot;&gt;context.metadata&lt;/code&gt; to its parent’s. SWC does not, in any decorator version. Anything built on that prototype chain works under one compiler and quietly loses inherited fields under the other. UQL resolves inheritance by walking the class prototype chain instead, and there is a test that constructs the unchained shape SWC emits to prove it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-codemod&quot;&gt;The codemod&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;uql-codemod&lt;/code&gt; does most of the mechanical work, reading types from the real type checker rather than guessing from the parse tree:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uql-codemod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--project=tsconfig.json&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--dry-run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uql-codemod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--project=tsconfig.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It rewrites &lt;code dir=&quot;auto&quot;&gt;@Field&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;@Id&lt;/code&gt; types, relation &lt;code dir=&quot;auto&quot;&gt;entity&lt;/code&gt; getters, &lt;code dir=&quot;auto&quot;&gt;Relation&amp;#x3C;T&gt;&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;T&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;@InjectQuerier()&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;currentQuerier()&lt;/code&gt;, and strips both flags from &lt;code dir=&quot;auto&quot;&gt;tsconfig.json&lt;/code&gt; while preserving your comments and formatting. It refuses to touch what it cannot be sure about: &lt;code dir=&quot;auto&quot;&gt;target: esnext&lt;/code&gt;, values inherited through &lt;code dir=&quot;auto&quot;&gt;extends&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;@Log()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;@Serialized()&lt;/code&gt;, options objects it cannot read, and branded string ids.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;tsc&lt;/code&gt; is the rest of the migration, and that is the point. The annotations the codemod inserts are checked against the properties they describe, so anything it got wrong is a compile error rather than a silently wrong column.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;was-it-worth-it&quot;&gt;Was it worth it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Yes, and not because of the compiler flags.&lt;/p&gt;
&lt;p&gt;Removing two tsconfig settings and a 264 KB polyfill is worth something, but it is housekeeping. The reason to do this was that reflection put the type in two places and let them drift, and no amount of care fixes a design where the compiler cannot see the mistake. Now there is one declaration and the compiler checks it against the property.&lt;/p&gt;
&lt;p&gt;The bill was real: three decorators, parameter injection, and a framework’s worth of users pushed onto a different API. If you are on NestJS, this release cost you something and handed back a smaller install. That is a worse trade than everyone else got, and not one I can improve until TC39 moves.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;get-started&quot;&gt;Get started&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uql-orm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uql-codemod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--project=tsconfig.json&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--dry-run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://uql-orm.dev/upgrade-guide&quot;&gt;Upgrade guide&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://uql-orm.dev/entities/basic&quot;&gt;Entities: decorators&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://uql-orm.dev/entities/imperative&quot;&gt;the imperative API&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://uql-orm.dev/blog/zero-dependencies&quot;&gt;Zero dependencies: what we deleted to fit on the edge&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://uql-orm.dev/comparison&quot;&gt;How UQL compares&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/rogerpadilla/uql&quot;&gt;GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the codemod leaves something behind that it could have handled, open an issue with the entity that tripped it.&lt;/p&gt;
&lt;hr&gt;
&lt;section id=&quot;giscus-wrapper&quot;&gt;&lt;!-- Content injected via client-side script to support View Transitions --&gt;&lt;/section&gt;</content:encoded><category>typescript</category><category>decorators</category><category>orm</category></item><item><title>Semantic search: native vector similarity in a multi-dialect ORM</title><link>https://uql-orm.dev/blog/semantic-search</link><guid isPermaLink="true">https://uql-orm.dev/blog/semantic-search</guid><description>UQL 0.3 ships native semantic search across PostgreSQL, MariaDB, SQLite, and MongoDB Atlas, with automatic index migration.</description><pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://uql-orm.dev/&quot;&gt;UQL&lt;/a&gt; is a dependency-free TypeScript ORM for PostgreSQL, MySQL, MariaDB, SQLite and MongoDB.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Most ORMs stop short of vector similarity. The moment you need it, you drop to raw SQL, hand-writing distance expressions and working around dialect quirks outside your type-safe query API.&lt;/p&gt;
&lt;p&gt;UQL 0.3 adds native semantic search to the regular query API, including automatic index migration for HNSW and IVFFlat indexes.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-it-looks-like&quot;&gt;What it looks like&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;You write&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; pool.&lt;/span&gt;&lt;span&gt;findMany&lt;/span&gt;&lt;span&gt;(Article, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$select&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt;, title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$sort&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { embedding&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { $vector&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; queryEmbedding, $distance&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;cosine&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; } },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$limit&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;UQL generates the right SQL for your database:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Generated SQL (PostgreSQL)&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Article&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ORDER BY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;embedding&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;=&gt;&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;LIMIT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Generated SQL (MariaDB)&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;Article&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ORDER BY&lt;/span&gt;&lt;span&gt; VEC_DISTANCE_COSINE(&lt;/span&gt;&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;embedding&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, ?)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;LIMIT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Generated SQL (SQLite)&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;Article&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ORDER BY&lt;/span&gt;&lt;span&gt; vec_distance_cosine(&lt;/span&gt;&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;embedding&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, ?)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;LIMIT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The same query works on every dialect, with no raw SQL or dialect checks in your code.&lt;/p&gt;
&lt;p&gt;For MongoDB, UQL translates the same query into an Atlas &lt;code dir=&quot;auto&quot;&gt;$vectorSearch&lt;/code&gt; pipeline. The Atlas search index itself has to be created in Atlas; see the &lt;a href=&quot;https://uql-orm.dev/querying/semantic-search&quot;&gt;reference&lt;/a&gt; for details.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;MongoDB Atlas&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$vectorSearch&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;embedding_index&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;embedding&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;queryVector&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;numCandidates&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;limit&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt; } }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;entity-setup&quot;&gt;Entity setup&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Define your vector field and index; UQL handles schema generation, extension creation, and index building:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Entity, Id, Field, Index } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;uql-orm&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Entity&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Index&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;embedding&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;], { type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;hnsw&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, distance&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;cosine&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, m&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;&lt;span&gt;, efConstruction&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;64&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Article&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Id&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Number&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Field&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Field&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, dimensions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1536&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;embedding&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For Postgres, UQL automatically emits &lt;code dir=&quot;auto&quot;&gt;CREATE EXTENSION IF NOT EXISTS vector&lt;/code&gt;. MariaDB has vector support built in; SQLite requires loading the &lt;a href=&quot;https://github.com/asg017/sqlite-vec&quot;&gt;sqlite-vec&lt;/a&gt; extension.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;key-features&quot;&gt;Key features&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;distance-projection&quot;&gt;Distance projection&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Project the computed distance into your results with &lt;code dir=&quot;auto&quot;&gt;$project&lt;/code&gt;, without computing the distance twice:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; { WithDistance } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;uql-orm&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; pool.&lt;/span&gt;&lt;span&gt;findMany&lt;/span&gt;&lt;span&gt;(Article, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$select&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt;, title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$sort&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { embedding&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { $vector&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; queryVec, $distance&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;cosine&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, $project&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;distance&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; } },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$limit&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;})) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WithDistance&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Article&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;distance&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&gt;[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;results[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].distance;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Annotate the result with the exported &lt;code dir=&quot;auto&quot;&gt;WithDistance&amp;#x3C;Article, &apos;distance&apos;&gt;&lt;/code&gt; helper to type the projected &lt;code dir=&quot;auto&quot;&gt;distance&lt;/code&gt; field.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-distance-metrics&quot;&gt;4 distance metrics&lt;/h3&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Postgres&lt;/th&gt;
&lt;th&gt;MariaDB&lt;/th&gt;
&lt;th&gt;SQLite&lt;/th&gt;
&lt;th&gt;MongoDB Atlas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;cosine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;=&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (index-defined)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;l2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;-&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (index-defined)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;inner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;#&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (index-defined)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;l1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;+&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;3-vector-types&quot;&gt;3 vector types&lt;/h3&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Storage&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&apos;vector&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32-bit float&lt;/td&gt;
&lt;td&gt;Standard embeddings (OpenAI, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&apos;halfvec&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16-bit float&lt;/td&gt;
&lt;td&gt;50% storage savings, near-identical accuracy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&apos;sparsevec&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sparse&lt;/td&gt;
&lt;td&gt;SPLADE, BM25-style sparse retrieval&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;halfvec&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;sparsevec&lt;/code&gt; are Postgres-only. MariaDB and SQLite transparently map them to their native &lt;code dir=&quot;auto&quot;&gt;VECTOR&lt;/code&gt; type, so your entities work everywhere.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;vector-indexes&quot;&gt;Vector indexes&lt;/h3&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Postgres&lt;/th&gt;
&lt;th&gt;MariaDB&lt;/th&gt;
&lt;th&gt;MongoDB Atlas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HNSW&lt;/td&gt;
&lt;td&gt;✅ with &lt;code dir=&quot;auto&quot;&gt;m&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;efConstruction&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IVFFlat&lt;/td&gt;
&lt;td&gt;✅ with &lt;code dir=&quot;auto&quot;&gt;lists&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ &lt;code dir=&quot;auto&quot;&gt;VECTOR INDEX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;code dir=&quot;auto&quot;&gt;$vectorSearch&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;why-sort&quot;&gt;Why &lt;code dir=&quot;auto&quot;&gt;$sort&lt;/code&gt;?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Vector similarity search is fundamentally sorting by distance. UQL reuses the existing &lt;code dir=&quot;auto&quot;&gt;$sort&lt;/code&gt; API, which composes naturally with &lt;code dir=&quot;auto&quot;&gt;$where&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;$select&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;$limit&lt;/code&gt;, and regular sort fields:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; pool.&lt;/span&gt;&lt;span&gt;findMany&lt;/span&gt;&lt;span&gt;(Article, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$where&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { category&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;science&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$sort&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { embedding&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { $vector&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; queryVec, $distance&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;cosine&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; }, title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;asc&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$limit&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;get-started&quot;&gt;Get started&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uql-orm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://uql-orm.dev/querying/semantic-search&quot;&gt;Full documentation →&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://uql-orm.dev/comparison&quot;&gt;How UQL compares&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/rogerpadilla/uql&quot;&gt;GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you run into issues or missing features, open an issue on &lt;a href=&quot;https://github.com/rogerpadilla/uql&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;section id=&quot;giscus-wrapper&quot;&gt;&lt;!-- Content injected via client-side script to support View Transitions --&gt;&lt;/section&gt;</content:encoded><category>release</category><category>semantic-search</category><category>vector</category></item><item><title>In search of the fastest TypeScript ORM</title><link>https://uql-orm.dev/blog/in-search-of-the-fastest-typescript-orm</link><guid isPermaLink="true">https://uql-orm.dev/blog/in-search-of-the-fastest-typescript-orm</guid><description>Pure SQL generation speed across 7 TypeScript ORMs and query builders, measured with an open, reproducible benchmark.</description><pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;aside aria-label=&quot;Superseded&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Superseded&lt;/p&gt;&lt;div&gt;&lt;p&gt;This post measures pure SQL-generation speed, with no database involved. That benchmark has since been retired: it turned out to account for well under 0.5% of a real request, so it stopped being a meaningful way to compare ORMs. The &lt;a href=&quot;https://uql-orm.dev/benchmark&quot;&gt;benchmark page&lt;/a&gt; and &lt;a href=&quot;https://uql-orm.dev/blog/what-orms-really-cost&quot;&gt;what-orms-really-cost&lt;/a&gt; now measure a full PostgreSQL round trip instead, and that is the current, maintained comparison. The numbers and prose below are kept as they were for the record, but no longer track any actively re-run benchmark.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;I kept seeing &lt;em&gt;“just use Drizzle, it’s lightweight”&lt;/em&gt; and &lt;em&gt;“ORMs are slow, use a query builder”&lt;/em&gt; repeated everywhere. So I decided to actually measure it.&lt;/p&gt;
&lt;p&gt;The benchmark measures &lt;strong&gt;pure SQL generation speed&lt;/strong&gt;: no database, no network, no connection pool. That isolates the overhead the ORM adds to every request. 7 entries, 8 query types, 3 runs averaged, on an Apple Silicon M4.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;methodology&quot;&gt;Methodology&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Environment:&lt;/strong&gt; Node.js v24.18.1, Apple M4 Pro, 3 runs averaged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versions:&lt;/strong&gt; Latest stable of every entry as of August 2026 (TypeORM 1.1.0, MikroORM 7.1.9, Sequelize 6.37.8, Drizzle 0.45.2, Knex 3.3.0, Kysely 0.29.4, UQL 0.24.0).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fairness:&lt;/strong&gt; Each ORM uses its most idiomatic API (QueryBuilder for TypeORM/MikroORM), which benefits them by skipping entity overhead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What’s measured:&lt;/strong&gt; Pure SQL string generation, with no database, no I/O, and no connection pool. This isolates ORM overhead only.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Why no Prisma?&lt;/strong&gt; Prisma’s query compiler is Rust compiled to WebAssembly rather than pure JS/TS, and it exposes no public SQL-compilation API, so it cannot be measured this way.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;the-results&quot;&gt;The results&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;insert-10-rows-in-batch&quot;&gt;INSERT: 10 rows in batch&lt;/h3&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry&lt;/th&gt;
&lt;th&gt;ops/sec&lt;/th&gt;
&lt;th&gt;vs winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UQL&lt;/td&gt;
&lt;td&gt;698K&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knex&lt;/td&gt;
&lt;td&gt;463K&lt;/td&gt;
&lt;td&gt;0.66x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequelize&lt;/td&gt;
&lt;td&gt;196K&lt;/td&gt;
&lt;td&gt;0.28x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kysely&lt;/td&gt;
&lt;td&gt;189K&lt;/td&gt;
&lt;td&gt;0.27x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MikroORM&lt;/td&gt;
&lt;td&gt;111K&lt;/td&gt;
&lt;td&gt;0.16x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeORM&lt;/td&gt;
&lt;td&gt;42K&lt;/td&gt;
&lt;td&gt;0.06x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;12K&lt;/td&gt;
&lt;td&gt;0.02x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;update-set--where&quot;&gt;UPDATE: SET + WHERE&lt;/h3&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry&lt;/th&gt;
&lt;th&gt;ops/sec&lt;/th&gt;
&lt;th&gt;vs winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UQL&lt;/td&gt;
&lt;td&gt;2,161K&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kysely&lt;/td&gt;
&lt;td&gt;796K&lt;/td&gt;
&lt;td&gt;0.37x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knex&lt;/td&gt;
&lt;td&gt;695K&lt;/td&gt;
&lt;td&gt;0.32x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeORM&lt;/td&gt;
&lt;td&gt;282K&lt;/td&gt;
&lt;td&gt;0.13x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequelize&lt;/td&gt;
&lt;td&gt;236K&lt;/td&gt;
&lt;td&gt;0.11x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MikroORM&lt;/td&gt;
&lt;td&gt;218K&lt;/td&gt;
&lt;td&gt;0.10x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;79K&lt;/td&gt;
&lt;td&gt;0.04x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;upsert-on-conflict-by-id&quot;&gt;UPSERT: ON CONFLICT by id&lt;/h3&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry&lt;/th&gt;
&lt;th&gt;ops/sec&lt;/th&gt;
&lt;th&gt;vs winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UQL&lt;/td&gt;
&lt;td&gt;691K&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knex&lt;/td&gt;
&lt;td&gt;433K&lt;/td&gt;
&lt;td&gt;0.63x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kysely&lt;/td&gt;
&lt;td&gt;335K&lt;/td&gt;
&lt;td&gt;0.48x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequelize&lt;/td&gt;
&lt;td&gt;327K&lt;/td&gt;
&lt;td&gt;0.47x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeORM&lt;/td&gt;
&lt;td&gt;261K&lt;/td&gt;
&lt;td&gt;0.38x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MikroORM&lt;/td&gt;
&lt;td&gt;260K&lt;/td&gt;
&lt;td&gt;0.38x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;36K&lt;/td&gt;
&lt;td&gt;0.05x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;delete-simple-where&quot;&gt;DELETE: simple WHERE&lt;/h3&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry&lt;/th&gt;
&lt;th&gt;ops/sec&lt;/th&gt;
&lt;th&gt;vs winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UQL&lt;/td&gt;
&lt;td&gt;3,996K&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequelize&lt;/td&gt;
&lt;td&gt;1,361K&lt;/td&gt;
&lt;td&gt;0.34x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kysely&lt;/td&gt;
&lt;td&gt;1,264K&lt;/td&gt;
&lt;td&gt;0.32x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knex&lt;/td&gt;
&lt;td&gt;1,084K&lt;/td&gt;
&lt;td&gt;0.27x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeORM&lt;/td&gt;
&lt;td&gt;507K&lt;/td&gt;
&lt;td&gt;0.13x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MikroORM&lt;/td&gt;
&lt;td&gt;263K&lt;/td&gt;
&lt;td&gt;0.07x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;207K&lt;/td&gt;
&lt;td&gt;0.05x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;select-1-field&quot;&gt;SELECT: 1 field&lt;/h3&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry&lt;/th&gt;
&lt;th&gt;ops/sec&lt;/th&gt;
&lt;th&gt;vs winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UQL&lt;/td&gt;
&lt;td&gt;4,675K&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequelize&lt;/td&gt;
&lt;td&gt;3,084K&lt;/td&gt;
&lt;td&gt;0.66x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kysely&lt;/td&gt;
&lt;td&gt;1,520K&lt;/td&gt;
&lt;td&gt;0.33x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knex&lt;/td&gt;
&lt;td&gt;1,092K&lt;/td&gt;
&lt;td&gt;0.23x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeORM&lt;/td&gt;
&lt;td&gt;591K&lt;/td&gt;
&lt;td&gt;0.13x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MikroORM&lt;/td&gt;
&lt;td&gt;565K&lt;/td&gt;
&lt;td&gt;0.12x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;229K&lt;/td&gt;
&lt;td&gt;0.05x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;select-where--sort--limit&quot;&gt;SELECT: WHERE + SORT + LIMIT&lt;/h3&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry&lt;/th&gt;
&lt;th&gt;ops/sec&lt;/th&gt;
&lt;th&gt;vs winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UQL&lt;/td&gt;
&lt;td&gt;1,365K&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knex&lt;/td&gt;
&lt;td&gt;614K&lt;/td&gt;
&lt;td&gt;0.45x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kysely&lt;/td&gt;
&lt;td&gt;419K&lt;/td&gt;
&lt;td&gt;0.31x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequelize&lt;/td&gt;
&lt;td&gt;381K&lt;/td&gt;
&lt;td&gt;0.28x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeORM&lt;/td&gt;
&lt;td&gt;276K&lt;/td&gt;
&lt;td&gt;0.20x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MikroORM&lt;/td&gt;
&lt;td&gt;73K&lt;/td&gt;
&lt;td&gt;0.05x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;59K&lt;/td&gt;
&lt;td&gt;0.04x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;select-complex-or--operators&quot;&gt;SELECT: complex $or + operators&lt;/h3&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry&lt;/th&gt;
&lt;th&gt;ops/sec&lt;/th&gt;
&lt;th&gt;vs winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UQL&lt;/td&gt;
&lt;td&gt;741K&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knex&lt;/td&gt;
&lt;td&gt;243K&lt;/td&gt;
&lt;td&gt;0.33x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kysely&lt;/td&gt;
&lt;td&gt;216K&lt;/td&gt;
&lt;td&gt;0.29x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeORM&lt;/td&gt;
&lt;td&gt;158K&lt;/td&gt;
&lt;td&gt;0.21x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequelize&lt;/td&gt;
&lt;td&gt;150K&lt;/td&gt;
&lt;td&gt;0.20x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;34K&lt;/td&gt;
&lt;td&gt;0.05x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MikroORM&lt;/td&gt;
&lt;td&gt;28K&lt;/td&gt;
&lt;td&gt;0.04x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;aggregate-group-by--count--having&quot;&gt;AGGREGATE: GROUP BY + COUNT + HAVING&lt;/h3&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry&lt;/th&gt;
&lt;th&gt;ops/sec&lt;/th&gt;
&lt;th&gt;vs winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UQL&lt;/td&gt;
&lt;td&gt;1,482K&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequelize&lt;/td&gt;
&lt;td&gt;416K&lt;/td&gt;
&lt;td&gt;0.28x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knex&lt;/td&gt;
&lt;td&gt;304K&lt;/td&gt;
&lt;td&gt;0.21x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeORM&lt;/td&gt;
&lt;td&gt;277K&lt;/td&gt;
&lt;td&gt;0.19x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kysely&lt;/td&gt;
&lt;td&gt;201K&lt;/td&gt;
&lt;td&gt;0.14x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;74K&lt;/td&gt;
&lt;td&gt;0.05x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MikroORM&lt;/td&gt;
&lt;td&gt;69K&lt;/td&gt;
&lt;td&gt;0.05x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://rogerpadilla.github.io/ts-orm-benchmark/chart.html&quot;&gt;Interactive charts →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;three-things-that-surprised-me&quot;&gt;Three things that surprised me&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;1. The “lightweight” option is the slowest thing in the benchmark.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Drizzle, marketed as lightweight, is slower than Sequelize (a full ORM from 2014) in every single category. The functional expression-tree approach creates more intermediate objects than Sequelize’s simple string concatenation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Standalone query builders can’t beat a well-designed ORM.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Knex and Kysely have zero entity/relation overhead; they’re just SQL string builders. Yet UQL, a full ORM with entities, relations, and migrations, is faster than both in all 8 categories, by 1.5x on batch INSERT up to 4.9x on aggregates. The conventional wisdom (“ORMs are slow”) doesn’t hold when the ORM pre-computes its metadata and avoids intermediate allocations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. MikroORM pays double.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;MikroORM v7 uses Kysely internally as its SQL generator (it replaced Knex in v7), so every query goes through two compilation layers: MikroORM → Kysely → SQL string. In these tables, MikroORM averages about 3.8x slower than Kysely alone (from 1.3x on UPSERT to 7.7x on complex &lt;code dir=&quot;auto&quot;&gt;$or&lt;/code&gt;). That’s the cost of layering abstractions.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;how-uql-gets-there&quot;&gt;How UQL gets there&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I got curious why the gap was so large, so I dug into the approach. Most ORMs figure out your schema at query time: &lt;em&gt;“What table does &lt;code dir=&quot;auto&quot;&gt;User&lt;/code&gt; map to? What column is &lt;code dir=&quot;auto&quot;&gt;companyId&lt;/code&gt;? Is it nullable?”&lt;/em&gt; They answer these questions on every single query.&lt;/p&gt;
&lt;p&gt;UQL answers them once at startup. Field-to-column mappings, table names, and relation paths are all pre-computed into lookup tables before the first query runs. At query time, generating SQL is just reading from a cache.&lt;/p&gt;
&lt;p&gt;The other difference is allocation. When TypeORM builds a SELECT, it creates a QueryBuilder, then an expression tree, then walks the tree to produce SQL. UQL pushes SQL fragments directly into a string buffer, creating no intermediate objects and no garbage-collection pressure.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;when-this-matters-in-production&quot;&gt;When this matters in production&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Database latency is 1-50ms and ORM overhead is microseconds, so for a low-traffic app the difference is noise. It starts to matter at scale. Take the WHERE + SORT + LIMIT query at 1,000 req/s: UQL (1,365K ops/s) spends under 1ms of CPU per second generating SQL, while MikroORM (73K ops/s) spends roughly 14ms, about 19x more CPU for the same queries before a single byte hits the network. In serverless, where you pay per ms of CPU, that shows up on the bill; in containers, it shows up as horizontal scaling cost.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;reproduce-it&quot;&gt;Reproduce it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Full disclosure: I’m the author of UQL. That’s exactly why I built the benchmark as an independent repo anyone can audit and reproduce.&lt;/p&gt;
&lt;p&gt;This SQL-generation benchmark itself is no longer maintained (see the note above); for the current, actively re-run benchmark and its repro steps, see the &lt;a href=&quot;https://uql-orm.dev/benchmark&quot;&gt;benchmark page&lt;/a&gt;. There’s also a broader feature-by-feature comparison on the &lt;a href=&quot;https://uql-orm.dev/comparison&quot;&gt;comparison page&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;section id=&quot;giscus-wrapper&quot;&gt;&lt;!-- Content injected via client-side script to support View Transitions --&gt;&lt;/section&gt;</content:encoded><category>benchmark</category><category>performance</category><category>typescript</category><category>orm</category></item><item><title>In search of the perfect TypeScript ORM</title><link>https://uql-orm.dev/blog/in-search-of-the-perfect-orm</link><guid isPermaLink="true">https://uql-orm.dev/blog/in-search-of-the-perfect-orm</guid><description>What makes a perfect ORM? I evaluated the top 5 features every TypeScript ORM should have, and why the existing ones fall short.</description><pubDate>Sat, 15 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href=&quot;https://medium.com/@rogerpadillac/in-search-of-the-perfect-orm-e01fcc9bce3d&quot;&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://uql-orm.dev/&quot;&gt;UQL&lt;/a&gt; is a dependency-free TypeScript ORM for PostgreSQL, MySQL, MariaDB, SQLite and MongoDB.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-an-orm&quot;&gt;What is an ORM?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;An ORM provides a simpler way to interact with databases in an app: it lets developers work with data as objects.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-a-perfect-orm-for-typescript&quot;&gt;What is a perfect ORM for TypeScript?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Below are the ideal features a TypeScript ORM should have, why such features are the most important ones, and how the existing ORMs fall short in most of these areas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The top 5 features a perfect TypeScript ORM should have are:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-serializable-queries&quot;&gt;1. Serializable queries&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Queries that can travel between the layers of a system keep the design simple. If the client can send a query to the server over HTTP or websockets using the ORM’s own syntax, you no longer need an extra query language on top: the &lt;code dir=&quot;auto&quot;&gt;GraphQL =&gt; ORM =&gt; Database&lt;/code&gt; flow collapses to &lt;code dir=&quot;auto&quot;&gt;ORM =&gt; Database&lt;/code&gt;. Concretely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No pseudo-language for queries and no context switching: the syntax is standard JSON, entirely declarative and serializable.&lt;/li&gt;
&lt;li&gt;No additional servers and no extra steps in the build process.&lt;/li&gt;
&lt;li&gt;Editors and IDEs understand the queries natively, without custom plugins or extensions.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;2-native-typescript&quot;&gt;2. Native TypeScript&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Use TypeScript itself for everything: JSON queries, classes, and decorators.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Type-safe queries and models that are natively validated by the same language that you use to write your app.&lt;/li&gt;
&lt;li&gt;Context-aware queries allow auto-completion of the appropriate operators and fields according to the different parts of a query.&lt;/li&gt;
&lt;li&gt;Entity definition with standard classes and decorators to avoid the need for proprietary DSLs (as happens with Prisma), extra steps in the build process, and custom extensions for the editors.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;3-multi-level-operators&quot;&gt;3. Multi-level operators&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Operations such as filter, sort, limit, project, and others work on any level of the queries (including relations and their fields).&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-consistent-api-across-databases&quot;&gt;4. Consistent API across databases&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Write the queries for any database in a consistent way and then transparently optimize these queries for the configured database dialect.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;5-universal-syntax-language-agnostic&quot;&gt;5. Universal syntax (language agnostic)&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Because the queries are standard JSON, they can be produced or consumed from any language: JSON is a first-class format in Python, Rust, Go, and virtually everything else. That also makes UQL implementations in other languages feasible.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;why-do-the-current-top-3-typescript-orms-fall-short&quot;&gt;Why do the current top 3 TypeScript ORMs fall short?&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;what-does-typeorm-lack-to-be-a-perfect-orm&quot;&gt;What does TypeORM lack to be a perfect ORM?&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;1. Lack of 100% serializable queries:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Notice how the &lt;code dir=&quot;auto&quot;&gt;LessThan&lt;/code&gt; operator is a function that has to be imported and called; that alone makes the query impossible to serialize.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { LessThan } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;typeorm&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;loadedPosts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; dataSource.&lt;/span&gt;&lt;span&gt;getRepository&lt;/span&gt;&lt;span&gt;(Post).&lt;/span&gt;&lt;span&gt;findBy&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;likes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LessThan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;2. Lack of Native TypeScript:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The query dissolves into strings because &lt;code dir=&quot;auto&quot;&gt;relations&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;where&lt;/code&gt; accept any string, so any invalid string can go there.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;posts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; connection.manager.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(Post, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;relations&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;&amp;#x3C; anything can go here &gt;&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;3. Lack of consistent API across databases:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the section that TypeORM has for MongoDB, there is a self-explanatory warning about this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Lack of Universal Syntax (language agnostic):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It relies on closures to support advanced queries (and not every language out there supports closures).&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h3 id=&quot;what-does-prisma-lack-to-be-a-perfect-orm&quot;&gt;What does Prisma lack to be a perfect ORM?&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;1. Lack of Native TypeScript:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Context-switching between the custom DSL and TypeScript makes this process obtrusive.&lt;/li&gt;
&lt;li&gt;Extra steps in the build process to generate the corresponding files from the custom DSL.&lt;/li&gt;
&lt;li&gt;It is required to install a custom extension for VS Code to get (basic) autocompletion from the editor for the DSL, which is far from being as good and reliable as with TypeScript.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;datasource&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;provider &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;postgresql&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;url      &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;DATABASE_URL&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;generator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;provider &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;prisma-client-js&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id        &lt;/span&gt;&lt;span&gt;Int&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;@id&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@default&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;autoincrement&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;createdAt &lt;/span&gt;&lt;span&gt;DateTime&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@default&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;email     &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;@unique&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name      &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;role      &lt;/span&gt;&lt;span&gt;Role&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt;@default&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;USER&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;posts     &lt;/span&gt;&lt;span&gt;Post&lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;2. Lack of consistent API across databases:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Prisma exposes low-level details about MongoDB that could be encapsulated by the ORM.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@id&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@default&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;@map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;_id&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;@db.ObjectId&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// Other fields&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;3. Lack of Universal Syntax (language agnostic):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It relies on its own proprietary DSL to define the models.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h3 id=&quot;what-does-mikroorm-lack-to-be-a-perfect-orm&quot;&gt;What does MikroORM lack to be a perfect ORM?&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;1. Lack of 100% serializable queries:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Notice how that query uses two separate methods, one for the update and another for the where; that structure cannot be serialized.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;qb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; orm.em.&lt;/span&gt;&lt;span&gt;createQueryBuilder&lt;/span&gt;&lt;span&gt;(Author);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;qb.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;test 123&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PublisherType.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;GLOBAL&lt;/span&gt;&lt;span&gt; }).&lt;/span&gt;&lt;span&gt;where&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;123&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; PublisherType.&lt;/span&gt;&lt;span&gt;LOCAL&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;2. Lack of Native TypeScript:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Dissolves into strings: any string can go inside the &lt;code dir=&quot;auto&quot;&gt;fields&lt;/code&gt; array, so the query loses the possibility of being type-safe.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; em.&lt;/span&gt;&lt;span&gt;findOne&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Author,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fields&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;books.title&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;books.author&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;books.price&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;3. Lack of Multi-level operators:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What if you need to filter the records of a relation or sort them? This seems unachievable in a type-safe way from what can be seen in the MikroORM docs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Lack of consistent API across databases:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { EntityManager } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@mikro-orm/mongodb&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;em&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; orm.em &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EntityManager&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;qb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; em.&lt;/span&gt;&lt;span&gt;aggregate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;MikroORM exposes low-level details about MongoDB that could be encapsulated by the ORM.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;why-is-uql-the-closest-to-a-perfect-orm&quot;&gt;Why is UQL the closest to a perfect ORM?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In short, because it was designed from the beginning with all of the above foundations. Let’s see how.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-100-serializable-queries&quot;&gt;1. 100% serializable queries&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Even the insert and update operations have a fully serializable API.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lastUsers&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; pool.&lt;/span&gt;&lt;span&gt;findMany&lt;/span&gt;&lt;span&gt;(User, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$select&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt;, name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt;, email&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$sort&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { createdAt&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$limit&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;2-native-typescript-with-truly-type-safe-queries&quot;&gt;2. Native TypeScript with truly type-safe queries&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every operator and field is validated according to the context. For example, the possible values for the &lt;code dir=&quot;auto&quot;&gt;$select&lt;/code&gt; operator will automatically depend on the level.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lastUsersWithProfiles&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; pool.&lt;/span&gt;&lt;span&gt;findMany&lt;/span&gt;&lt;span&gt;(User, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$select&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt;, name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$populate&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;profile&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { $select&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt;, picture&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt; }, $required&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$sort&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { createdAt&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$limit&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;3-multi-level-operators-1&quot;&gt;3. Multi-level operators&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The operators work on any level. For example, &lt;code dir=&quot;auto&quot;&gt;$sort&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;$where&lt;/code&gt; can be applied to the relations and their fields in a type-safe and context-aware way.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;items&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; pool.&lt;/span&gt;&lt;span&gt;findMany&lt;/span&gt;&lt;span&gt;(Item, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$select&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt;, name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$populate&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;measureUnit&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$select&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt;, name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$where&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { $ne&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;unidad&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; } },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$required&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tax&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { $select&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt;, name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; } },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$where&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;price&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { $gte&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { $istartsWith&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$sort&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tax&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;measureUnit&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;createdAt&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$limit&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;4-consistent-api-across-databases-1&quot;&gt;4. Consistent API across databases&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;One API for every database: the same entities and queries transparently work on any supported database, with the dialect-specific SQL (or MongoDB commands) generated under the hood. This makes it easier to switch from a Document to a Relational database (or vice-versa); the &lt;a href=&quot;https://uql-orm.dev/switching-to-uql&quot;&gt;migration guide&lt;/a&gt; walks through what that move looks like coming from Mongoose.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;5-universal-syntax-across-languages&quot;&gt;5. Universal syntax across languages&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Its syntax is standard JSON, so queries can be produced or consumed from other languages, enabling interoperation or even implementations of UQL in languages such as Python or Rust.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;See more at &lt;strong&gt;&lt;a href=&quot;https://uql-orm.dev&quot;&gt;uql-orm.dev&lt;/a&gt;&lt;/strong&gt;. Please star it ⭐ on &lt;strong&gt;&lt;a href=&quot;https://github.com/rogerpadilla/uql&quot;&gt;GitHub&lt;/a&gt;&lt;/strong&gt; if you like the idea!&lt;/p&gt;
&lt;hr&gt;
&lt;section id=&quot;giscus-wrapper&quot;&gt;&lt;!-- Content injected via client-side script to support View Transitions --&gt;&lt;/section&gt;</content:encoded><category>orm</category><category>typescript</category><category>architecture</category></item></channel></rss>