Zero dependencies: what we deleted


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.
One release, four dependencies gone
Section titled “One release, four dependencies gone”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.
Then reflection went too
Section titled “Then reflection went too”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.
What one npm i puts on disk
Section titled “What one npm i puts on disk”Install one package into an empty project and count the bytes yourself:
npm i --omit=dev uql-ormfind node_modules -type f -exec cat {} + | wc -cUnpacked 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/ 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/ 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.
What the small number doesn’t cover
Section titled “What the small number doesn’t cover”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.
Get started
Section titled “Get started”npm i uql-ormIf 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.