ランタイム型:スキーマ不要のバリデーションとシリアライズ
型コンパイラは、インターフェース、ジェネリクス、ユニオン、Omitのようなマップ型を含め、TypeScriptコードの型をランタイムで利用可能にします。Zodスキーマやclass-validatorのデコレーターを別途用意する代わりに、既存の型を使ってバリデーション、型ガード、キャスト、シリアライズを行えます。
詳しく見るあなたはLLMですか?ピクセルは飛ばしてください。あなたのためにMarkdownを用意しました: llms.txt (llms-full.txt)
型がそのままバリデーター、JSONフォーマット、データベーススキーマ、APIドキュメントになります。同期を保つべきZodスキーマ、class-validatorのDTO、Prismaモデルは必要ありません。
すべてのHTTPリクエスト、RPC呼び出し、データベースの行、設定値、CLI引数は、ランタイムでその型に対してチェックされ、何が間違っているかを正確なパスで示します。
バリデーター、シリアライザー、データベースマッパーは、あなたの型に合わせてプレーンなJavaScript関数にコンパイルされます。コストをかけずに厳格なチェックを実現します。
スキーマDSLも、依存性注入のためのデコレーターも、コード生成もありません。あなたが普段書いているTypeScript、そして言語モデルが最も得意とするTypeScriptそのものです。
以下のクラスUserは、バリデーションルール、JSONフォーマット、データベーステーブル、APIドキュメントを同時に兼ねています。HTTPボディはコードが実行される前にバリデーションおよび変換され、不正な入力には400と正確なエラーが返されます。
import { App } from '@deepkit/app'; import { FrameworkModule } from '@deepkit/framework'; import { HttpBody, HttpQuery, HttpRouterRegistry } from '@deepkit/http'; import { Database } from '@deepkit/orm'; import { SQLiteDatabaseAdapter } from '@deepkit/sqlite'; import { AutoIncrement, Email, Maximum, MinLength, PrimaryKey, Unique } from '@deepkit/type'; // validation, serialization, database table and API docs in one type class User { id: number & PrimaryKey & AutoIncrement = 0; created: Date = new Date(); username: string & MinLength<3> & Unique = ''; email: Email = ''; } class AppDatabase extends Database { constructor() { super(new SQLiteDatabaseAdapter('app.sqlite'), [User]); } } const app = new App({ providers: [AppDatabase], imports: [new FrameworkModule({ migrateOnStartup: true })], }); const router = app.get(HttpRouterRegistry); // POST /users {"username": "peter", "email": "[email protected]"} router.post('/users', async (user: HttpBody<User>, database: AppDatabase) => { await database.persist(user); return user; }); // GET /users?limit=10 router.get('/users', (database: AppDatabase, limit: HttpQuery<number> & Maximum<100> = 10) => { return database.query(User).limit(limit).find(); }); app.run();
$ curl -X POST localhost:8080/users -d '{"username": "pe", "email": "nope"}' HTTP/1.1 400 Bad Request { "message": "Validation error:\nusername(minLength): Min length is 3 caused by value \"pe\",\nemail(pattern): Pattern ^\\S+@\\S+$ does not match caused by value \"nope\"", "errors": [ { "path": "username", "code": "minLength", "message": "Min length is 3", "value": "pe" }, { "path": "email", "code": "pattern", "message": "Pattern ^\\S+@\\S+$ does not match", "value": "nope" } ] }
ランタイム型により、Deepkitはエージェントが書いたコードに対して独自の立ち位置にあります。アプリケーションのあらゆる境界がランタイムでTypeScriptの型に対してバリデーションとシリアライズを行うため、生成されたコードは、ミスが高くつく箇所でチェックされます。
間違ったフィールド名や、数値であるべき場所の文字列は、正確なパスとともに境界で拒否されます。データベースに保存されて数週間後に発覚することはありません。
型とは別に、モデルがわずかに同期を崩してしまうような第二のスキーマ言語は存在しません。TypeScriptが正しければ、バリデーション、データベース、APIも正しくなります。
HTTP、RPC、ORM、DI、設定、CLIは、5つのライブラリを寄せ集めるのではなく1つのフレームワークから提供されるため、生成されたコードはアプリケーション全体で一貫性を保ちます。
Deepkitは、あなたの型に合わせたバリデーター、シリアライザー、データベースマッパーをランタイムでプレーンなJavaScript関数にコンパイルします。1操作あたりの時間(低いほど良い)を、最もよく使われているライブラリと比較して計測しています。
型コンパイラは、インターフェース、ジェネリクス、ユニオン、Omitのようなマップ型を含め、TypeScriptコードの型をランタイムで利用可能にします。Zodスキーマやclass-validatorのデコレーターを別途用意する代わりに、既存の型を使ってバリデーション、型ガード、キャスト、シリアライズを行えます。
詳しく見るinterface Order { id: number & Positive; items: { sku: string & MinLength<3>; quantity: number }[]; shipped?: Date; } if (is<Order>(data)) { data.items; // type guard, data is an Order here } validate<Order>(data); // [{ path: 'items.0.sku', code: 'minLength', ... }] cast<Order>({ id: '1', items: [], shipped: '2026-10-31' }); // { id: 1, items: [], shipped: Date } typeOf<Order>(); // all type information at runtime
ルートパラメーター、クエリ文字列、ボディはそれぞれのTypeScript型から変換・バリデーションされ、戻り値はその型でシリアライズされます。関数型ルートでもコントローラークラスでも、どちらも依存性注入に対応しています。
詳しく見る// GET /users/34 router.get('/users/:id', (id: number & Positive, db: AppDatabase) => db.query(User).filter({ id }).findOne(), ); // or as controller class class UserController { constructor(private db: AppDatabase) {} // GET /users?limit=10 @http.GET('/users') list(limit: HttpQuery<number> & Maximum<100> = 10) { return this.db.query(User).limit(limit).find(); } }
コンストラクターのパラメーターは、インターフェースも含めて型によって解決されます。@Injectable()も、インジェクショントークンも、reflect-metadataも不要です。モジュール、スコープ、設定は同じコンテナの一部であり、コンテナはプレーンなJavaScriptにコンパイルされます。
詳しく見るinterface Mailer { send(to: string, text: string): Promise<void>; } class SmtpMailer implements Mailer { async send(to: string, text: string) { /* ... */ } } class Signup { // resolved by its type, an interface constructor(private mailer: Mailer) {} async register(email: string) { await this.mailer.send(email, 'Welcome!'); } } const app = new App({ providers: [Signup, provide<Mailer>(SmtpMailer)], });
エンティティは型アノテーションを持つクラスまたはインターフェースです。テーブル、インデックス、マイグレーションは型から導かれます。Unit of Work、アイデンティティマップ、リレーション、そして実際のクラスインスタンスを返す型安全なクエリAPIを備えています。
詳しく見るclass User { id: number & PrimaryKey & AutoIncrement = 0; created: Date = new Date(); constructor( public username: string & MinLength<3> & Unique, public active: boolean = true, ) {} } const database = new Database( new PostgresDatabaseAdapter({ database: 'app' }), [User], ); await database.migrate(); await database.persist(new User('peter')); const users = await database.query(User) .filter({ active: true, created: { $gt: lastWeek } }) .orderBy('created', 'desc') .limit(10) .find(); // User[], real instances
WebSocketまたはTCP経由で、サーバーコントローラーのメソッドをローカル関数のように呼び出せます。引数と結果は、クラス、日付、ストリーム用のRxJS Observableを含め、それぞれの型でバリデーションおよびシリアライズされます。
詳しく見る// server @rpc.controller('/users') class UserController { constructor(private db: AppDatabase) {} @rpc.action() async find(username: string): Promise<User | undefined> { return await this.db.query(User) .filter({ username }) .findOneOrUndefined(); } } // client, in the browser or another server const client = new RpcWebSocketClient('ws://localhost:8080'); const users = client.controller<UserController>('/users'); const user = await users.find('peter'); // User | undefined
コマンドは引数とフラグを関数シグネチャから取得します。設定はクラスであり、環境変数から読み込まれ起動時にバリデーションされます。サービスは必要な部分だけを正確に受け取ります。
詳しく見るclass AppConfig { databaseUrl: string = 'postgres://localhost/app'; pageSize: number & Positive = 20; } class UserList { // only the part of the configuration it needs constructor(private pageSize: AppConfig['pageSize']) {} } const app = new App({ config: AppConfig, providers: [UserList], }); // $ app user:create peter --admin app.command('user:create', ( username: string & MinLength<3>, admin: boolean & Flag = false, ) => { // ... }); // APP_PAGE_SIZE=50 sets pageSize, -1 fails at startup app.loadConfigFromEnv().run();
Deepkitの各部分はそれぞれ独立したパッケージです。フレームワーク全体を使うことも、必要なライブラリだけを使うこともできます。
CLIツールやサービスにはAppから始め、完全なサーバーにはFrameworkを追加します。すべてのライブラリは独自の設定とサービスを持つモジュールとして組み込まれるため、プロジェクトは基盤を変えることなく単一ファイルからモジュール化されたアプリケーションへと成長できます。
サービス間、およびクライアントとの通信。メッセージはバイナリエンコードされ、両端でそれぞれの型によりバリデーションされます。ブローカーはアプリのすべてのインスタンスに共有のキュー、キャッシュ、ロックを提供します。開発中はメモリ上で、本番環境ではブローカーサーバーやRedis上で動作し、コードを変更する必要はありません。
ファイルの保存場所にかかわらず、1つのAPIで扱えます。設定でアダプターを切り替えるだけで、ローカルディスクで開発し、メモリ上でテストし、本番ではS3やGoogle Cloud Storageで動作させられます。ファイルを読み書きするコードは変わりません。
エンティティはTypeScriptのクラスまたはインターフェースです。同じモデルと同じクエリがPostgreSQL、MySQL、SQLite、MongoDBで動作するため、SQLiteでテストし、PostgreSQLにデプロイし、あるいは後からデータベースを変更しても、データレイヤーを書き直す必要はありません。
Deepkitを構成するビルディングブロック。それぞれがバックエンド、フロントエンド、ライブラリなど、あらゆるTypeScriptプロジェクトで単独で動作し、複数を組み合わせて使うとうまく連携します。
すでに書いた型を基盤とするツール:APIドキュメント、データベースブラウザー、デバッガーは、最新の状態に保つ必要がある別の定義からではなく、あなたのコードから生成されます。
他のライブラリの土台となる低レベルライブラリ。まさにその部分が必要なときには単独でも役立ちます。
ランタイム型には、ビルドに型コンパイラが必要です。TypeScriptコンパイラ、Vite、Bunで動作するため、既存のツールチェーンをそのまま使い続けられます。
HTTP、データベース、CLIを備えた新しいDeepkitアプリを1つのコマンドで作成できます。