Repository navigation
Built for coding agents
deepkit.io now serves the documentation in the form coding agents read best: llms.txt as an index, llms-full.txt with all guides in one file, and every documentation page as plain markdown by adding .md to its URL, for example deepkit.io/en/documentation/orm.md. Package pages include their complete API reference with signatures and source links. Point your agent at it and it has everything it needs to write Deepkit code.
This is the start: we will keep improving how Deepkit works with agents. Runtime types put Deepkit in a unique position here. Every boundary of an application, HTTP, RPC, database, configuration and CLI, validates and serializes against the TypeScript types at runtime. When generated code gets a type subtly wrong, it fails with an exact error at the boundary instead of passing bad data through, so code written by agents is checked in the places where mistakes are expensive.
Fewer dependencies, safer installs
Every dependency is code that runs with our users' permissions, and supply chain attacks on npm packages are common. So this release removes 40 dependencies and replaces the small parts we actually used with our own, tested code. Installing all Deepkit packages now takes 130 MB instead of 219 MB (−41%) and 249 packages instead of 427 (−42%). Our goal is zero external dependencies.
| Package | Removed | Replaced with |
|---|---|---|
@deepkit/type |
uuid (CVE-2026-41907, #705), buffer (#699) |
crypto.getRandomValues; the global Buffer where available, otherwise atob/btoa and TextEncoder/TextDecoder |
@deepkit/core, @deepkit/rpc |
dot-prop, to-fast-properties |
own path functions, still blocking __proto__/prototype/constructor against prototype pollution |
@deepkit/logger |
ansi-styles, format-util |
own color table with the same tag names; util.format on the server |
@deepkit/broker |
@lukeed/ms, js-xxhash |
own duration parser and xxHash32 |
@deepkit/http |
qs, send |
own query string parser (qs 6.14 defaults: depth, parameter and array limits, prototype pollution protection) and own static file serving (path traversal and dotfile protection, ETag, conditional and range requests) |
@deepkit/framework |
compression, mime-types, jimp, probe-image-size, nice-table, selfsigned (with node-forge), faker, sift, fs-extra, buffer, md5, pirates |
zlib response compression, a MIME type table in @deepkit/http, original images in the debugger, a small table renderer, self-signed certificates with node:crypto; the ORM browser's seed feature is removed (see below) |
@deepkit/orm |
sift |
own query matching ($eq, $in, $regex, $elemMatch, … as used by FilterQuery) |
@deepkit/sql, @deepkit/postgres, @deepkit/sqlite |
sqlstring, sqlstring-sqlite, @types/sqlstring, date-fns, fast-glob |
SQL escaping in @deepkit/sql, a date formatter, fs.readdir({ recursive }) |
@deepkit/mongo |
saslprep-patch (which pulled in a placeholder package named fs) |
own SASLprep (RFC 4013) |
@deepkit/type-compiler, @deepkit/vite |
micromatch, @rollup/pluginutils, @typescript/vfs, get-tsconfig, lz-string, strip-json-comments |
own glob matching and createFilter, a local TypeScript lib file list; three were unused |
@deepkit/template |
abstract-syntax-tree (9 packages) |
its parser and generator meriyah and astring directly (no dependencies of their own) and a small require hook |
@deepkit/run |
tsconfig-paths |
own paths mapping (also fixes patterns with text after *) |
@deepkit/create-app, @deepkit/desktop-ui |
fs-extra, on-change |
fs.cpSync, a small change observer |
@deepkit/filesystem-google |
bignumber.js, google-auth-library |
unused |
formidable stays in @deepkit/http for now: it parses all request bodies, and HttpConfig.parser exposes its options.
Also:
@deepkit/sql,@deepkit/postgresand@deepkit/sqliteno longer runeval()on column defaults read from the database; they parse them.- The ORM's query matching (memory adapter) no longer supports
$where, whose string form ran code throughnew Function. - Static file serving no longer reveals whether files outside of the static root exist (a request like
/public/../../etc/passwdanswered 403 for existing and 404 for missing files; the content was never served). - Packages no longer ship
.tsbuildinfofiles: 4.4 MB less per release. - Releases are published only from a clean build that passed the complete test suite (see below).
Smaller installs and bundles
Compared with 1.0.19: a fresh npm install, and a browser bundle (esbuild, minified, gzip).
| Use case | Install size | Installed packages | Browser bundle (gzip) |
|---|---|---|---|
| All 48 packages | 219.1 → 129.6 MB (−41%) | 427 → 249 (−42%), third-party 377 → 200 (−47%) | – |
@deepkit/framework + @deepkit/sqlite |
93.3 → 44.3 MB (−53%) | 265 → 84 (−68%) | – |
RPC client (@deepkit/rpc) |
11.2 → 10.4 MB (−7%) | 24 → 11 | 154.7 → 146.7 KB (−5%) |
@deepkit/bson |
4.4 → 4.0 MB (−9%) | 12 → 4 | 95.5 → 91.4 KB (−4%) |
@deepkit/type |
3.6 → 3.2 MB (−11%) | 11 → 3 | 78.8 → 73.9 KB (−6%) |
buffer, uuid, base64-js, ieee754, dot-prop and to-fast-properties are no longer part of the browser bundle of @deepkit/type.
TypeScript 6, Angular 22, ESM
- Built with TypeScript 6. The type compiler supports TypeScript 6 and versions before 5.5, and resolves types from
.mts/.ctsfiles. - The Angular packages and GUIs use Angular 22.
- The ESM builds of
@deepkit/template,@deepkit/mongo,@deepkit/httpand@deepkit/frameworkload again. A new consumer test installs the packed packages into fresh CommonJS and ESM projects before each release. - All imported
@deepkit/*packages are declared, and@deepkit/*peer dependencies require the current version (#574).
Validation errors that tell you what's wrong
When a value matches no member of a union, the error now points at the actual problem instead of No valid union member found with every member's definition expanded:
shape.kind(type): Expected 'circle' | 'square', got string // unknown discriminator
shape.size(type): Not a number // member selected by the discriminator
tags.1(type): Not a string // closest member: string[]
(type): Expected Circle | Square, got array // no member is close
The same errors come from BSON decoding (RPC, MongoDB) and validate()/assert()/cast(). They are built only when validation fails, so valid values are as fast as before.
Faster
Most of the time of is(), validate(), serialize() and deserialize() was spent before the actual work: resolving the type argument on every call, and allocations. Inline types like is<string[]>() even ran the type VM on every call. Type arguments are now passed directly to ReceiveType parameters and created once per call site, and the generated guards do less per call. Measured with the benchmarks that come with this release, against 1.0.19, Node 26, i9-8950HK:
| 1.0.19 | now | |
|---|---|---|
is<User>() |
412 ns | 49 ns |
validate<User>(), invalid |
617 ns | 184 ns |
serialize<User>() |
641 ns | 177 ns |
deserialize<User>() |
690 ns | 191 ns |
cast<User>() |
1.2 µs | 391 ns |
deserialize<T[]>(), 100 items |
143 µs | 9.0 µs |
| HTTP body with 10 users, deserialize | 111 µs | 4.4 µs |
RPC createRpcMessage() with body |
226 ns | 10 ns |
Of 341 benchmarks, 83 are faster and 4 slower: RPC messages without body, only inside the benchmark suite (equal when measured alone). All results, the comparisons and their history are on deepkit.io/benchmarks. Run them yourself with npm run bench, also against an older version with npm run bench -- --target 1.0.19.
All changes
⚠️ behavior change, please check when updating- 🆕 new feature or API
- 🧪 regression test only: it already worked, now a test keeps it that way
Type compiler
⚠️ declarestatements in your own files (declare type/interface/const,declare global) produce no runtime types any more. A type in the same file that references adeclare typegetsanyfor that part. Declaration files of libraries (.d.tsin node_modules) are not affected. (#601, 77b865f)- Default and aliased imports of library types resolve to the exported declaration, e.g.
import AI from 'openai',import { Client as C },export = Client. Classes imported like this wereanybefore. (97074f8) - tsconfig
extendscan be an array, and the last entry wins, like in tsc. (#600, bdc6533) - Named re-exports (
export { X } from './x', also through anindex.ts) carry their runtime types. (#634, #318, e8543b1) - Optional chaining with type arguments (
a?.b<T>()) no longer produces a SyntaxError. (#612, 9a1f315) - The
__typeof module-level functions is hoisted, so a function can be reflected before its definition in the file. (#664, 4492545) - Inferring a type parameter replaces the whole type reference, which fixes a crash with TypeScript debug assertions. (#509, f4495fe)
- Global lib types of ES2024+ targets (e.g.
PromiseWithResolvers) get runtime types, because lib files are now resolved like TypeScript does instead of from a fixed list. (b234b52) - Types from
.mts/.ctsfiles resolve, and the module format of.mts/.ctsfiles and nested packages is detected correctly. (#639, 493b2bb, 8cb50c6) - TypeScript versions before 5.5 work again. (7e6cbca)
forwardRef(() => Type)is left undecorated, so Angular's linker accepts libraries built with runtime types, like@deepkit/desktop-ui. (c097e96)- The plugin export paths in package.json are correct, and a test checks them against the sources. (#702, 1cc3068, df4f64d)
- The module type for
nodenextis detected. (35ff3b9) - 🧪 The injected TypeScript patch is valid JavaScript, also with Windows paths. (#356, 1a6ec39)
- 🧪 Types from libraries without runtime types. (#352, #555, f9a443b)
- Type arguments are passed as arguments to known
ReceiveTypeparameters instead offn.Ω, and type arguments without local references are module-level variables, so their type is computed once:is<string[]>()ran the type VM on every call (12.7 µs, now 52 ns).typeOf()still returns a new type for inline types. (c39c8fd)
Type
⚠️ The UUID type checks RFC 9562 (hex digits, version 1–8, variant, Nil/Max). Before, any 36-character string with dashes in the right places passed. (#706, 79a865e)⚠️ Union validation errors report the field that's wrong: the discriminator selects the member (shape.size(type): Not a number), an unknown discriminator saysshape.kind(type): Expected 'circle' | 'square', got string, otherwise the closest member's errors orExpected Circle | Square, got arrayinstead ofNo valid union member found. Valid: …. Literals sayExpected 'circle', got numberinstead ofInvalid literal. This only runs when validation fails. (#577, #676, 840b9eb, 97074f8, 35ce1f6, 55bc216)⚠️ A tuple only extends an array if all its elements do; before,[boolean] extends string[]was true. (11a70ee)⚠️ = new Datewithout parentheses counts as a computed default, so the SQL schema no longer bakes in a build-time timestamp, and migrations stop showing a change every time. (#545, 82a2455)⚠️ typedArrayToBuffer()/nodeBufferToTypedArray()are typed asUint8Array; at runtime they still return aBufferwhere one exists. (9036b3b)- Large literal unions (e.g. 86,400 members) are checked with one Map lookup instead of failing with "Too many context variables", and registered template hooks still apply. (#478, 543e189, ae2790e)
[...infer P, infer L]infers the elements after a rest element from the end. (#524, 7483886)ValidationErrorItem.toStringno longer crashes on circular values. (#505, 70ae003)- Deserialized classes that have a parent class are reflectable again. (#241, b86d3d2)
- Errors about missing runtime types explain how to fix the setup. (#562, #508, 8722519)
- The
uuidandbufferdependencies were removed; replacements:crypto.getRandomValues, plus the globalBufferwith an atob/btoa fallback. (#705, #699, 9036b3b) - 🆕 A scored union resolver picks the union member with the most matching properties, also in BSON. (a1f8ee3)
- 🆕 Intrinsic types
Capitalize,Uppercase,Lowercase,Uncapitalize. (#681, ffd1bc7) - A type no longer collapses when
Bresolves tounknown. (#684, 1130ddd) - The packages are built with
isolatedModules. (#683, c730cfc) - An optional
Embeddedproperty can beundefined; validation failed withNot an objectbefore, also when persisting. (#254, 4a8a7a4) - 🧪
Omitof methods whose parameters usekeyof. (#430, 4a8a7a4) ⚠️ Array type guards check every item:is<string[]>(['a', 1, 'b'])wastrue, because only the last item decided (since 1.0.1). (c39c8fd)- Faster type guards and
is()/validate()/serialize()/deserialize()/cast(): discriminated unions select their member by the discriminator, objects outside unions are not scored, no allocations per call, plain objects are recognized withoutinstanceofchecks,isFunction/isClassdon't calltoString()on every call, and anonymous object literals and arrays of primitives are checked inline instead of with a function call. (d5ce462, c39c8fd, b7012e2, 7ce005a)
BSON
- 🧪 Typed BSON serialization rejects
NaNwith a validation error. (#573, ee8c287) - A wrapped BSON value is written as BSON null when it is null or undefined. (#678, 9af338e)
- Union mismatches in BSON report the same errors as
validate()(discriminator, closest member,Expected …), instead ofCannot convert bson type OBJECT to …. (#676, bc146ee, 97074f8, 35ce1f6) - 🧪 Arrays of object literals with
MongoId. (#444, 4a8a7a4)
ORM / SQL / Mongo
⚠️ filter({ field: undefined })means "field is not set" in SQL, Mongo (sent asnull) and the memory adapter. Before, the field was dropped, so the filter matched all rows, also indeleteMany/patchMany. (#692, 1b945c7, 24c6630)⚠️ count()ignores limit/skip/sort and returns the total; it failed on all databases before. (#668, 1ba444a)⚠️ Query matching in the memory adapter (the replacement forsift) no longer supports$whereand$type; neither is part ofFilterQuery. (0070543)- When a reference is loaded as the full entity, the existing instance is upgraded instead of triggering an extra UPDATE. (#636, 32f05c8)
- A property that overrides a parent's property with
Reference/BackReferenceis a reference, sojoinWith()loads it. (#704, 4a8a7a4) - 🧪 Nested joins over a back reference. (#375, 4a8a7a4)
database.migrate()creates all tables first and adds foreign keys afterwards, which fixes Postgres errors when entities are registered in the wrong order. (1ae363e)- SQL column defaults read from the database are parsed instead of run through
eval(). (f0661ec) - SQL escaping moved from
sqlstring/sqlstring-sqliteinto@deepkit/sql, with the same output per platform. (c09bd2d) - SQL migration commands load via dynamic import, so they work in ESM projects. (#685, 2c5766a)
deepkit-sql --pathis back, and the standalone migration commands find the databases again. (#613, 849ef19)⚠️ SQLitemaxConnectionsthrows for:memory:databases above 1 and for values below 1; before, data was silently split across connections, or the pool hung. (#393, dab7244)- better-sqlite3 was updated to 12.11 for Node.js 26. (e6ad1fb)
- Mongo cursor ids are read as bigint in find and aggregate, so large aggregations no longer fail on getMore, and
batchSizeis applied. (#708, 0dd646a, 4c67a33) - Mongo filters with a path into an embedded object, e.g.
{ 'tailMessage._id': id }, convert the value with its type, so aMongoIdis sent as ObjectId. (#697, 4a8a7a4) - 🆕 Mongo
estimateCountoption, a fix for the slow empty count, and the verbose logs are gone. (4b142c8, 1be535f) - Mongo SASLprep is our own RFC 4013 implementation (
saslprep-patchpulled in a placeholderfspackage). (94c9b2e) - ORM event listeners are merged instead of lost when the dispatcher instance changes, and the dispatcher is rebuilt after a context merge. (#671, f3c9683, 168a030)
- A unit-of-work listener with
preventDefaultno longer skips all the other listeners. (#670, 2796bb1) ⚠️ Soft-delete query results have the same shape as normal query results. (#672, ef7bb9f)- The MongoDB client detects a recovered primary and failovers again: its heartbeat could stop for good after the first round when the timer fired a millisecond early. (701b5d0)
HTTP
⚠️ AnHttpErrorthrown in a middleware is answered with its status code. (#589, 253fb8f)⚠️ Other middleware errors return 500 "Internal error" instead of 404. (#439, a953246)⚠️ .timeout()on a middleware fails the request with 504HttpGatewayTimeoutErrorinstead of continuing to the controller, and a latenext()is ignored. (#439, a953246)onResponseand the HTTP logger also run when a middleware ends the response. (#590, 7aa3d08)- The middleware documentation describes
next(), errors and timeouts as they work now. (#591, 4a8a7a4, 56714ba) ⚠️ HttpHeaderparameters match case-insensitively, and the test request builder lowercases header names. (#653, beebd60)⚠️ Query strings use our own parser with the qs 6.14 defaults: 1000 parameters, depth 5, array index limit 20, prototype keys dropped. (b3a50ae)⚠️ Static file serving is our own, replacingsend. It covers ETag, range and conditional requests, and dotfile and path-traversal protection, and no longer reveals whether files outside the root exist. (414a199, 52141c6)⚠️ The MIME table is our own:.js→text/javascript,.mp4→video/mp4, and rare extensions get no Content-Type. (414a199, 68acfea)- Errors outside actions, e.g. in
kernel.authenticate(), also go throughtransformError. (17f309e) ⚠️ The correct InjectorContext scope is set in HTTP and RPC;RpcInjectorContextwas removed. (#673, a56b7dd)- 🧪 Validators inside
HttpQuerydon't leak into other parameters, and body parameters from imported files work. (#614, #458, 47afde0, 7cfaf60) - Query strings without brackets are parsed by a fast path, checked against qs in tests. (d5ce462)
RPC / Broker
- 🆕
preSerialize(value)serializes a value once for many connections; message types that can't splice it throw an RpcError. (#711, 43ecb1e, 1799053) - A socket that was replaced no longer strands calls on the new connection (reconnect race). (#707, 2de8673)
- Subscribed observables and subjects stay alive until they complete, so they are not garbage-collected mid-stream. (6243c3e)
- Messages sent while the server is unreachable are rejected instead of hanging. (1bb0bf9)
- Stopwatch frame names are normalized. (26a06cd)
- The broker uses its own duration parser and xxHash32 instead of
@lukeed/ms/js-xxhash. (f5a8471)
App / Injector / Framework
- Unscoped providers are used in scopes that have none of their own, e.g. CLI; this fixes the
migration:*commands with FrameworkModule. (dca8e69) - Injector performance: a token's type and a config type's place are computed once. (#709, 8b37311)
- Flag properties keep their default when the flag isn't given. (4764a5c)
- Names that are already uppercase stay the same. (afbf6e2)
- 🧪 Failing CLI commands show their error when
debugis enabled. (#687, 262983f) - GUI packages (debugger, API console, ORM browser) are found via module resolution, so they work with Yarn PnP. (#608, e767a1e)
- Without workers, debug mode uses an in-process broker, and an unreachable broker no longer hangs shutdown. (eb3cad7, 43f0144)
⚠️ The debugger sends image previews unchanged instead of downscaling them (jimpwas dropped). (2556945)⚠️ selfSignedcertificates usenode:cryptowith RSA 2048/SHA-256 and a PKCS#8 key; already stored certificates are reused. (bf03088)- Response compression is our own zlib implementation, the CLI table renderer is our own, and the MIME types come from
@deepkit/http. (da8e800, d6a893e, 68acfea) ⚠️ The ORM browser's seed feature was removed together with faker: the Seed tab, theseed/getFakerTypesactions and the seed types in@deepkit/orm-browser-api. Seed databases with a script instead. (604c89d, a9571fb)- The ORM browser shows the app's databases without
forDatabases(), renders array content and no longer allows editing it. (f848e4f, 06d058d)
Core / Logger / Other packages
- Path get/set/delete is our own code, still blocking
__proto__/prototype/constructor, replacingdot-prop/to-fast-properties. (6e9ca42) - Windows paths work in
getCurrentDirName(npm init @deepkit/app) and in the local filesystem adapter. (#688, #694, 75816ef) - The logger uses its own color table with the same tag names, plus
util.formaton the server, replacingansi-styles/format-util. (c2687b9) Single-string messages skiputil.format, so logging is 14% faster than in 1.0.19. (f397091) - Template:
meriyahandastringreplaceabstract-syntax-treeandpirates, files withstatic {}class blocks no longer crash, and the JSX types ship injsx-runtime. (614737b, d8b829b) @deepkit/rundoes its ownpathsmapping, which also fixes patterns with text after*. (dc519a6)- Vite and the type compiler use their own glob matching, and accept
FilterPatternfor include/exclude. (#696, 4d5a636, c1bcfda) - The Bun plugin applies
compilerOptions,includeandexclude. (7fd19ac) - The Bun plugin tests build and run a fixture with
Bun.buildand the runtime preload, and CI installs Bun. (#691, 2ed1f57) - Filesystem: S3 keys no longer get a leading slash with the
pathoption, and FTP/SFTP report directory size 0. (9225f71, b0948b3) - desktop-ui (1f8cff6, 758e8ba, 4b92999, 5288594, 01d52f7):
- dates are formatted withLOCALE_IDand a 24-hour clock (#710)
- the dropdown only re-observes its position when the target changes
-on-changewas replaced with a small observer
- fewer runtime types are emitted
-⚠️ docs.jsonis no longer exported - The
deepkit-sqlanddui-create-fontbins got their shebang. (#598, beb019e) - The ESM builds of template, mongo, http and framework load again. (814ca04)
Packaging / release process
⚠️ All imported@deepkit/*packages are declared, and@deepkit/*peer dependencies require the current version. (#574, ba76daa, ee7546f)⚠️ The packages need Node.js 20 or newer, and they are built with TypeScript 6 and Angular 22; development needs Node.js 22. (8dbef51, e6f2ad0).tsbuildinfois no longer published (4.4 MB less per release), anddist/esm/package.jsonis committed for all ESM packages. (48881dd, 9dbdfcc)@deepkit/frameworkno longer installs@types/ws. (#346, 4a8a7a4)npm run buildstarts clean,npm run testwrites a certificate after a complete green run, and commits andnpm run publishrequire that certificate. (8a0efaf, 958502c, 4195ea8)npm run test:consumerinstalls the packed packages into fresh CommonJS and ESM projects. (b3ae2cf)- 40 dependencies were removed in total, some of them unused, e.g.
bignumber.js,google-auth-library,lz-string,get-tsconfig,strip-json-comments. (3827d6f and the commits above) - 🆕 Every package has benchmarks in
benchmarks/*.bench.ts, plus comparisons with typia, zod, valibot, arktype, TypeBox, class-validator/class-transformer and bson.npm run benchwrites the results per machine tobenchmarks/results,--target 1.0.19runs the same benchmarks against a published version. The ORM comparisons measure Deepkit against Prisma, Drizzle, TypeORM, MikroORM, Sequelize and Mongoose on PostgreSQL, SQLite and MongoDB, with the raw drivers as lower bound. (b5abf6f, 710602b, 975abdc) - Development uses Yarn 4.18.1, which installs no package versions younger than 7 days (
npmMinimalAgeGate). (afb6e4e)
Website
- The desktop-ui API tables are generated by our own
api-docs.tsinstead of typedoc. (a95d1a7) - The translate command is back with OpenAI
gpt-6.1-sol, as a separate CLI entry that Angular never bundles; the other AI features and the Discord bot stay removed. (9df1f68, ee39a35) - 🆕 deepkit.io/benchmarks shows the comparisons with other libraries and databases, the last two releases against master, and the history of whole features (validation, serialization, database, DI, HTTP requests). (57c2f89, add08b7, f646d8c)
- 🆕 The start page explains Deepkit again: one type for validation, HTTP and database in one example, benchmark numbers, sections with code for each part, and the library catalog with what each category is for. (b7abb7c)
- 🆕 Documentation for coding agents:
/llms.txt,/llms-full.txtand every documentation page as markdown under its URL plus.md, with the API reference on package pages. (f59fb57) - 🆕 Every page has a social preview image generated from its content. (e385fb9)
- Search engines: moved URLs redirect permanently (301) instead of temporarily, pages declare their language versions (hreflang) and canonical URL,
/sitemap.xmland/robots.txtexist, every page has its own description, and broken links in the documentation are fixed. The community questions and examples are removed, their URLs redirect to the documentation. (d0becd8, 5d7e1aa, ae434d8)
How we release now
npm run buildalways starts clean.npm run testwrites a test certificate only after a complete, green run.- Commits require that certificate for their changes.
npm run publishonly publishes the exact build that passed the tests.
This makes sure what you install is what we tested.
Thanks
Thanks to @marcus-sa, @lionelhorn, @fergusean, @otnc, @pxDot, @mojobeeping, @Rush, @LEW21 and @jho406 for their contributions to this release.