Skip to content
UQL

Zero dependencies: what we deleted

Bar chart comparing node_modules size after a clean install, with uql-orm at 1.0 MB against five other ORMs up to 75 MBBar chart comparing node_modules size after a clean install, with uql-orm at 1.0 MB against five other ORMs up to 75 MB

npm i uql-orm installs one package. No dependencies, every dialect included.

A month ago it had four mandatory packages, roughly 4 MBs unpacked; now it is 992 KB unpacked.

0.21.0 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.

tslib existed so TypeScript’s helper functions didn’t repeat across files. "importHelpers": false costs a few hundred bytes per file instead of a dependency edge.

sqlstring escaped MySQL and MariaDB literals, badly: a Uint8Array came out as `0` = 255 instead of X'ff00', a plain object as '[object Object]'. It did not crash or warn; the output was just wrong. Dialect.escape handles all three SQL dialects itself now, byte-for-byte identical across 29 value shapes, and those two cases throw instead of writing garbage.

reflect-metadata (264 KB) earned its keep with one call, Reflect.getMetadata('design:type', ...), so @Field() could guess a column’s type from the property. Optional as of this release, gone two releases later.

jiti (1.8 MB), a full TypeScript transpiler, existed to read one file: uql.config.ts. That’s a lot of compiler to hire for one sticky note. Also gone; that config now needs a runtime that already speaks TypeScript (bun, or node --import tsx).

Then the stuff that was never code: 369 sourcemaps nobody opened, and a 108 KB CHANGELOG, shipped in every install anyway. files in package.json is ["dist", "README.md"] now.

Two releases later, reflect-metadata stopped being optional and started being pointless. 0.23.0 moved decorators to the TC39 standard spec: @Field() and @Id() require an explicit type now, checked against the property, so there’s nothing left to reflect. The full story, including what it cost, is in Standard decorators: props & cons.

Install one package into an empty project and count the bytes yourself:

Terminal window
npm i --omit=dev uql-orm
find node_modules -type f -exec cat {} + | wc -c

Unpacked bytes on disk, not the 288 kB you download. Tarballs are compressed, but a cold start loads the unpacked files.

Package Installed Files
uql-orm 0.24.1 1.0 MB 384
@mikro-orm/postgresql 7.1.9 4.7 MB 1,153
drizzle-orm 0.45.2 9.9 MB 2,667
sequelize 6.37.8 15.0 MB 2,708
typeorm 1.1.0 22.5 MB 3,663
@prisma/client 7.9.1 75.0 MB 94

One caveat about this table: 93% of @prisma/client is 70 MB of Rust query compilers cross-compiled to WebAssembly, one per engine it supports. 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.

No row includes a driver. Add pg to any of them and they all grow the same amount. This table is the floor.

UQL is small; your driver may not be. pg, mysql2, mariadb, mongodb and better-sqlite3 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.

Every entry point has a gzip budget checked on every build, because 0.13.0 once let node:async_hooks leak into browser bundles and had to be pulled, so the budget was added after that.

Terminal window
npm i uql-orm

If your install contains something that shouldn’t be there, open an issue.



UQL is a JSON-native TypeScript ORM for Node.js, Bun and Deno. Supports PostgreSQL, PGlite, MySQL, MariaDB, MSSQL, SQLite, CockroachDB, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.