类型注解
类型注解是普通的 TypeScript 类型,它们包含可在运行时读取的元信息,并可改变各类函数的行为。Deepkit 已经提供了一些类型注解,覆盖了许多使用场景。例如,可以将类属性标记为主键、引用或索引。数据库库可以在运行时使用这些信息来创建正确的 SQL 查询,而无需事先进行代码生成。
验证器约束(如 MaxLength
、Maximum
或 Positive
)也可以添加到任意类型上。还可以告诉序列化器如何序列化或反序列化特定值。此外,还可以创建完全自定义的类型注解并在运行时读取它们,以便在运行时以非常个性化的方式使用类型系统。
Deepkit 自带一整套类型注解,全部都可以直接从 @deepkit/type
使用。它们的设计目标是不来自多个库,从而避免将代码直接绑定到某个特定库,如 Deepkit RPC 或 Deepkit Database。这样即使使用了例如数据库类型注解,也更易于在前端复用类型。
下面是现有类型注解的列表。@deepkit/type
和 @deepkit/bson
的验证器和序列化器以及 @deepkit/orm
的 Deepkit Database 会以不同方式使用这些信息。参见相应章节以了解更多内容。
整数/浮点数
整数和浮点数以 number
作为基础定义,并有多个子变体:
integer | 任意大小的整数。 |
int8 | 介于 -128 和 127 之间的整数。 |
uint8 | 介于 0 和 255 之间的整数。 |
int16 | 介于 -32768 和 32767 之间的整数。 |
uint16 | 介于 0 和 65535 之间的整数。 |
int32 | 介于 -2147483648 和 2147483647 之间的整数。 |
uint32 | 介于 0 和 4294967295 之间的整数。 |
float | 与 number 相同,但在数据库上下文中可能具有不同含义。 |
float32 | 介于 -3.40282347e+38 和 3.40282347e+38 的浮点数。请注意,由于精度问题,JavaScript 无法正确检查该范围,但这些信息对数据库或二进制序列化器可能很有用。 |
float64 | 与 number 相同,但在数据库上下文中可能具有不同含义。 |
import { integer } from '@deepkit/type'; interface User { id: integer; }
这里用户的 id
在运行时是一个 number,但在验证和序列化中会被解释为整数。
这意味着,例如,在验证中不允许使用浮点数,并且序列化器会自动将浮点数转换为整数。
import { is, integer } from '@deepkit/type'; is<integer>(12); //true is<integer>(12.5); //false
子类型可以以相同方式使用,当只允许特定范围的数字时非常有用。
import { is, int8 } from '@deepkit/type'; is<int8>(-5); //true is<int8>(5); //true is<int8>(-200); //false is<int8>(2500); //false
import { is, float, float32, float64 } from '@deepkit/type'; is<float>(12.5); //true is<float32>(12.5); //true is<float64>(12.5); //true
UUID
UUID v4 通常在数据库中以二进制存储,在 JSON 中以字符串存储。
import { is, UUID } from '@deepkit/type'; is<UUID>('f897399a-9f23-49ac-827d-c16f8e4810a0'); //true is<UUID>('asd'); //false
MongoID
将该字段标记为 MongoDB 的 ObjectId。在类型上解析为字符串。在 MongoDB 中以二进制存储。
import { MongoId, serialize, is } from '@deepkit/type'; serialize<MongoId>('507f1f77bcf86cd799439011'); //507f1f77bcf86cd799439011 is<MongoId>('507f1f77bcf86cd799439011'); //true is<MongoId>('507f1f77bcf86cd799439011'); //false class User { id: MongoId = ''; //在用户插入后将由 Deepkit ORM 自动设置 }
Bigint
默认情况下,普通的 bigint 类型在 JSON 中序列化为 number(在 BSON 中为 long)。然而这在可保存的范围上有局限性,因为 JavaScript 中的 bigint 理论上大小无限,而 JavaScript 的 number 和 BSON 的 long 是有限的。为绕过此限制,提供了 BinaryBigInt
和 SignedBinaryBigInt
类型。
BinaryBigInt
与 bigint 相同,但在数据库中序列化为无符号二进制(大小不受限,而非大多数数据库中的 8 字节),并在 JSON 中序列化为字符串。负值将被转换为正数(abs(x)
)。
import { BinaryBigInt } from '@deepkit/type'; interface User { id: BinaryBigInt; } const user: User = { id: 24n }; serialize<User>({ id: 24n }); //{id: '24'} serialize<BinaryBigInt>(24); //'24' serialize<BinaryBigInt>(-24); //'0'
Deepkit ORM 将 BinaryBigInt 作为二进制字段存储。
SignedBinaryBigInt
与 BinaryBigInt
相同,但也能存储负数。Deepkit ORM 将 SignedBinaryBigInt
作为二进制存储。该二进制有一个额外的前导符号字节,并以无符号整数表示:255 表示负数,0 表示零,1 表示正数。
import { SignedBinaryBigInt } from '@deepkit/type'; interface User { id: SignedBinaryBigInt; }
MapName
用于在序列化时更改属性的名称。
import { serialize, deserialize, MapName } from '@deepkit/type'; interface User { firstName: string & MapName<'first_name'>; } serialize<User>({ firstName: 'Peter' }) // {first_name: 'Peter'} deserialize<User>({ first_name: 'Peter' }) // {firstName: 'Peter'}
Group
可以将属性分组。在序列化时,例如可以排除某个分组。更多信息参见“序列化”章节。
import { serialize } from '@deepkit/type'; interface Model { username: string; password: string & Group<'secret'> } serialize<Model>( { username: 'Peter', password: 'nope' }, { groupsExclude: ['secret'] } ); //{username: 'Peter'}
Data
每个属性都可以添加可通过反射 API 读取的额外元数据。更多信息参见运行时类型反射。
import { ReflectionClass } from '@deepkit/type'; interface Model { username: string; title: string & Data<'key', 'value'> } const reflection = ReflectionClass.from<Model>(); reflection.getProperty('title').getData()['key']; //value;
Excluded
可将某个属性在特定目标的序列化过程中排除。
import { serialize, deserialize, Excluded } from '@deepkit/type'; interface Auth { title: string; password: string & Excluded<'json'> } const item = deserialize<Auth>({ title: 'Peter', password: 'secret' }); item.password; //undefined,因为 deserialize 的默认序列化器名为 `json` item.password = 'secret'; const json = serialize<Auth>(item); json.password; //依然为 undefined,因为 serialize 的序列化器也叫 `json`
Embedded
将字段标记为嵌入类型。
import { PrimaryKey, Embedded, serialize, deserialize } from '@deepkit/type'; interface Address { street: string; postalCode: string; city: string; country: string; } interface User { id: number & PrimaryKey; address: Embedded<Address>; } const user: User { id: 12, address : { street: 'abc', postalCode : '1234', city : 'Hamburg', country : 'Germany' } } ; serialize<User>(user); { id: 12, address_street : 'abc', address_postalCode : '1234', address_city : 'Hamburg', address_country : 'Germany' } //对于反序列化,你需要提供嵌入后的结构 deserialize<User>({ id: 12, address_street: 'abc', //... });
可以更改前缀(默认是属性名)。
interface User { id: number & PrimaryKey; address: Embedded<Address, { prefix: 'addr_' }>; } serialize<User>(user); { id: 12, addr_street : 'abc', addr_postalCode : '1234', } //或者完全移除前缀 interface User { id: number & PrimaryKey; address: Embedded<Address, { prefix: '' }>; } serialize<User>(user); { id: 12, street : 'abc', postalCode : '1234', }
Entity
为接口添加实体信息。仅用于数据库上下文。
import { Entity, PrimaryKey } from '@deepkit/type'; interface User extends Entity<{ name: 'user', collection: 'users'> { id: number & PrimaryKey; username: string; }
PrimaryKey
将字段标记为主键。仅用于数据库上下文。
import { PrimaryKey } from '@deepkit/type'; interface User { id: number & PrimaryKey; }
AutoIncrement
将字段标记为自增。仅用于数据库上下文。
通常与 PrimaryKey
一起使用。
import { AutoIncrement } from '@deepkit/type'; interface User { id: number & PrimaryKey & AutoIncrement; }
Reference
将字段标记为引用(外键)。仅用于数据库上下文。
import { Reference } from '@deepkit/type'; interface User { id: number & PrimaryKey; group: number & Reference<Group>; } interface Group { id: number & PrimaryKey; }
在此示例中,User.group
是一个拥有方引用,也就是 SQL 中的外键。这意味着 User
表有一列 group
引用 Group
表。Group
表是该引用的目标表。
BackReference
将字段标记为反向引用。仅用于数据库上下文。
interface User { id: number & PrimaryKey; group: number & Reference<Group>; } interface Group { id: number & PrimaryKey; users: User[] & BackReference; }
在此示例中,Group.users
是一个反向引用。这意味着 User
表有一列 group
引用 Group
表。
一旦执行带有联接的数据库查询,Group
会有一个虚拟属性 users
,它会自动填充所有 group
id 与该 Group
的 id 相同的用户。属性 users
本身不会存储在数据库中。
Index
将字段标记为索引。仅用于数据库上下文。
import { Index } from '@deepkit/type'; interface User { id: number & PrimaryKey; username: string & Index; }
Unique
将字段标记为唯一。仅用于数据库上下文。
import { Unique } from '@deepkit/type'; interface User { id: number & PrimaryKey; username: string & Unique; }
DatabaseField
使用 DatabaseField
可以定义数据库特定的选项,如真实的数据库列类型、默认值等。
import { DatabaseField } from '@deepkit/type'; interface User { id: number & PrimaryKey; username: string & DatabaseField<{ type: 'varchar(255)' }>; }
验证
待办
参见验证约束类型。
InlineRuntimeType
用于内联一个运行时类型。仅用于高级场景。
import { InlineRuntimeType, ReflectionKind, Type } from '@deepkit/type'; const type: Type = { kind: ReflectionKind.string }; type Query = { field: InlineRuntimeType<typeof type>; } const resolved = typeOf<Query>(); // { field: string }
在 TypeScript 中,类型 Query
是 { field: any }
,但在运行时是 { field: string }
。
当你构建一个高度可定制的系统、接收运行时类型并在各种其他场景中复用它们时,这会很有用。
ResetAnnotation
重置某个属性的所有注解。仅用于高级场景。
import { ResetAnnotation } from '@deepkit/type'; interface User { id: number & PrimaryKey; } interface UserCreationPayload { id: User['id'] & ResetAnnotation<'primaryKey'>; }
自定义类型注解
你可以定义自己的类型注解。
type MyAnnotation = { __meta?: ['myAnnotation'] };
按照约定,类型注解被定义为仅包含一个可选属性 __meta
的对象字面量,该属性的类型是元组。该元组的第一个条目是其唯一名称,后续条目则是任意选项。这样可以为类型注解附加更多可选配置。
type AnnotationOption<T extends { title: string }> = { __meta?: ['myAnnotation', T] };
类型注解通过交叉类型运算符 &
使用。可以在一个类型上使用任意数量的类型注解。
type Username = string & MyAnnotation; type Title = string & MyAnnotation & AnnotationOption<{ title: 'Hello' }>;
可以通过 typeOf<T>()
和 typeAnnotation
的类型对象读取类型注解:
import { typeOf, typeAnnotation } from '@deepkit/type'; const type = typeOf<Username>(); const annotation = typeAnnotation.getForName(type, 'myAnnotation'); //[]
annotation
的结果要么是一个包含选项的数组(如果使用了类型注解 myAnnotation
),要么是 undefined
(如果未使用)。如果类型注解具有如 AnnotationOption
所示的额外选项,则传入的值可以在数组中找到。
已有的类型注解如 MapName
、Group
、Data
等有各自的注解对象:
import { typeOf, Group, groupAnnotation } from '@deepkit/type'; type Username = string & Group<'a'> & Group<'b'>; const type = typeOf<Username>(); groupAnnotation.getAnnotations(type); //['a', 'b']
参见运行时类型反射以了解更多。