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]'. No crash, no warning, just wrong output that looks fine until it isn’t. 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 compress; cold starts don’t care.

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 thing to hold against this table before you screenshot it: 93% of @prisma/client 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.

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 the small number here, your driver isn’t. 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. Lesson learned, budget added.

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.