Schema 类

Schema 类

本指南涵盖模型如何使用 Lucid 从数据库生成的 schema 类。你将学会如何:

  • 阅读和理解生成的 schema 类
  • 了解每种数据库列产生的 TypeScript 类型
  • 处理默认映射需要特别注意的常见类型(枚举、JSON、日期、主键)
  • 使用 schema 规则全局或按表自定义生成的类型
  • 使用特定类型、访问器和读/写钩子在模型类上覆盖列

有关生成工作流本身(schema:generate 何时运行、采用遗留数据库、提交生成的文件),请参见 schema 生成指南。有关迁移优先的理念,请参见简介

生成的 schema 类剖析

当迁移运行时,Lucid 为每个表在 database/schema.ts 中写入一个类。每个类继承 BaseModel,声明 static table,暴露一个只读的列名 $columns 元组,并使用适当的 @column 装饰器声明每一列。

database/schema.ts
import { DateTime } from 'luxon'
import { BaseModel, column } from '@adonisjs/lucid/orm'
export class PostsSchema extends BaseModel {
static table = 'posts'
static $columns = ['id', 'title', 'content', 'createdAt', 'updatedAt'] as const
$columns = PostsSchema.$columns
@column({ isPrimary: true })
declare id: number
@column()
declare title: string
@column()
declare content: string
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime | null
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime | null
}

关于生成输出有几个值得了解的要点:

  • 列名从 snake_case 转换为 camelCase。数据库中的 created_at 在模型上变为 createdAt,内部记录 columnName: 'created_at',以便查询定位正确的列。
  • static $columns 元组驱动自动补全。query().select(...) 等方法使用此元组提供类型安全的列名补全。
  • 主键列从数据库检测并用 @column({ isPrimary: true }) 装饰。
  • 日期和 datetime 列使用 @column.date@column.dateTime 装饰器,而非普通的 @column。两者都返回 Luxon DateTime 实例。

你永远不直接编辑 database/schema.ts。该文件在每次生成时被覆盖;通过 schema 规则模型级覆盖来自定义。

你的模型继承生成的类并添加行为。

app/models/post.ts
import { PostsSchema } from '#database/schema'
export default class Post extends PostsSchema {}

列类型参考

Lucid 通过内部映射将数据库特定的列类型转换为 TypeScript 类型。数据库驱动程序报告原生列类型(如 varcharint4),Lucid 将其映射到内部类别(如 stringnumber),内部类别决定最终的 TypeScript 类型。

内部类型也是你使用 schema 规则自定义类型时定位的键。

内部类型

内部类型默认 TypeScript 类型描述
numbernumber整数和浮点列
bigintbigint | number大整数。联合默认值涵盖了将 bigint 作为 JavaScript number(当值适合时)和 bigint(当不适合时)返回的驱动。如果你的驱动一致,使用 schema 规则覆盖。
decimalstring带精度的小数类型。默认为 string 以保留精确精度;当应用端精度损失可接受时覆盖为 number
booleanboolean布尔列
stringstring所有文本和字符类型
dateDateTime不带时间的日期。使用 @column.date 装饰器。
timestring不带日期的时间
DateTimeDateTime组合日期和时间。使用 @column.dateTime 装饰器。
binaryBuffer二进制和 blob 列
jsonanyJSON 列
jsonbanyPostgreSQL JSONB 列
uuidstringUUID 和 GUID 列
enumstring枚举类型
setstringMySQL SET 类型
unknownanyLucid 不识别的类型。回退为 any 以便列可用而无需手动转换。

数据库类型到内部类型

下表列出 Lucid 识别的具体数据库类型及其映射方式。对于未列出的任何类型,Lucid 回退为 unknown

数值类型

数据库类型内部类型备注
smallintintegerintnumber标准整数
bigintunsigned big intbigint大整数
decimalnumericmoneydecimal精确小数
realdoubledouble precisionfloatnumber浮点数
tinyintbooleanMySQL 布尔约定
mediumintnumberMySQL 中等整数
smallmoneydecimalMSSQL 货币
smallserialserialnumberPostgreSQL 自增
bigserialbigintPostgreSQL 大自增
int2int4numberPostgreSQL 整数别名
int8bigintPostgreSQL bigint 别名
float4float8numberPostgreSQL 浮点别名
oidnumberPostgreSQL 对象标识符
yearnumberMySQL 年份类型

布尔类型

数据库类型内部类型
booleanboolboolean
bit(MSSQL)boolean

文本类型

数据库类型内部类型备注
charvarchartextstring标准文本
charactercharacter varyingstringPostgreSQL 变体
tinytextmediumtextlongtextstringMySQL 文本大小
ncharnvarcharclobstring其他方言
ntextsysnamestringMSSQL 文本
xmlstring存储为文本的 XML

日期和时间类型

数据库类型内部类型备注
datedate仅日期
timetime仅时间,映射为 string
time without time zonetime with time zonetimePostgreSQL 时间变体
datetimetimestampDateTime组合日期和时间
timestamp without time zoneDateTimePostgreSQL
timestamp with time zoneDateTime带时区的 PostgreSQL
smalldatetimedatetime2DateTimeMSSQL 变体
datetimeoffsetDateTime带时区的 MSSQL
intervalstringPostgreSQL 间隔存储为字符串

二进制类型

数据库类型内部类型
byteablobbinary
tinyblobmediumbloblongblobbinary
binaryvarbinarybinary
imagerowversionbinary

JSON 类型

数据库类型内部类型
jsonjson
jsonbjsonb

UUID 类型

数据库类型内部类型
uuiduuid
uniqueidentifieruuid

枚举和集合类型

数据库类型内部类型
enumenum
setset

PostgreSQL 特殊类型

数据库类型内部类型备注
inetcidrmacaddrmacaddr8string网络地址
tsvectortsquerystring全文搜索
bitbit varyingstring位串
hstoreunknown键值存储
int4rangeint8rangenumrangetsrangetstzrangedaterangeunknown范围类型
int4multirangeint8multirangenummultirangetsmultirangetstzmultirangedatemultirangeunknown多范围类型(PostgreSQL 14+)
pg_lsnnamepg_snapshottxid_snapshotstring特殊标识符类型
regclassregprocregprocedureregoperregoperatorregtyperegroleregnamespacestring对象标识符类型
pointstring点几何
linelsegboxpathpolygoncirclestring几何形状
multipointmultilinestringmultipolygongeometrycollectionstring几何集合
geometrygeographyunknown未类型化空间

MSSQL 特殊类型

数据库类型内部类型
hierarchyidstring
sql_variantunknown

SQLite 类型亲和变体

SQLite 以大写形式返回类型,与标准小写变体一起映射:

数据库类型内部类型
INTEGERnumber
REALnumber
TEXTstring
BLOBbinary
NUMERICnumber

有关包括方言特定差异的完整最新映射,请参见 GitHub 上的映射源码

方言限定类型查找

在查询通用映射之前,生成器首先查找 {dialect}.{columnType}。这使一个类型在不同方言中可以表示不同含义。例如,bit 在 PostgreSQL 上是位串(映射为 string),但 mssql.bit 是 SQL Server 上的 1 位布尔(映射为 boolean)。用户很少直接与此交互;只有当你定义需要定位特定方言对类型的解释的 schema 规则时才重要。

可空性和列顺序

两个约定应用于每个生成的类:

  • 可空性。 当数据库列可为空且不是主键时,Lucid 在 TypeScript 类型后追加 | null。主键永远不会收到 | null 后缀,无论其数据库可空性如何。
  • 列顺序。 列在生成的类中按属性名按字母顺序排序。顺序不反映表的物理列顺序。

不是有效 JavaScript 的标识符

当列名以不是有效 JavaScript 标识符的字符开头时(例如数字如 2fa_secret),生成器在属性名前加下划线(_2fa_secret),并在装饰器的 columnName 参数中记录原始列名,以便查询仍然定位正确的列。

@column({ columnName: '2fa_secret' })
declare _2fa_secret: string | null

使用常见数据类型

大多数类型无需额外配置即可工作。以下类型需要说明,因为它们要么需要设计决策(枚举、JSON),要么产生更丰富的运行时对象(日期、主键)。

枚举

数据库原生枚举类型通常不适合应用数据,原因有三:

  • SQLite 不支持枚举。基于枚举的迁移在方言之间不可移植。
  • PostgreSQL 将枚举存储为独立类型,增加了迁移开销。
  • 在生产中添加、移除或重命名枚举值通常需要表重写或手动 SQL,某些方言完全禁止其中一些操作。

Lucid 默认将数据库枚举列映射为 TypeScript string。推荐的模式是将值存储为整数(或短字符串),在应用代码中定义语义。

database/migrations/xxxx_create_posts_table.ts
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
async up() {
this.schema.createTable('posts', (table) => {
table.increments('id')
table.string('title').notNullable()
table.integer('status').unsigned().notNullable().defaultTo(0)
table.timestamps(true, true)
})
}
}
app/models/post.ts
import { PostsSchema } from '#database/schema'
export const PostStatus = {
DRAFT: 0,
PUBLISHED: 1,
ARCHIVED: 2,
} as const
export type PostStatus = (typeof PostStatus)[keyof typeof PostStatus]
export default class Post extends PostsSchema {
get isDraft() {
return this.status === PostStatus.DRAFT
}
get isPublished() {
return this.status === PostStatus.PUBLISHED
}
}

PostStatus 同时用作运行时值对象和 TypeScript 类型。你可以在同一文件中将其用作任一种。

如果你仍然偏好原生枚举,使用 schema 规则自定义生成的列类型以产生严格的联合类型。

JSON 列

JSON 和 JSONB 列默认映射为 TypeScript any。你获得最大灵活性,但牺牲了类型安全。

@column()
declare metadata: any

两条路径可以收紧此类型。

第一条是应用于你应用中每个 JSON 列的 schema 规则。

database/schema_rules.ts
import { type SchemaRules } from '@adonisjs/lucid/types/schema_generator'
export default {
types: {
json: {
tsType: 'JSON<any>',
decorators: [{ name: '@column' }],
imports: [
{ source: '#types/db', typeImports: ['JSON'] },
],
},
},
tables: {},
} satisfies SchemaRules
types/db.ts
export type JSON<T> = T

第二条是针对一个特定列的模型级覆盖

app/models/post.ts
import type { JSON } from '#types/db'
import { column } from '@adonisjs/lucid/orm'
import { PostsSchema } from '#database/schema'
export default class Post extends PostsSchema {
@column()
declare metadata: JSON<{ seoTitle?: string; keywords?: string[] }>
}

两种方法可以一起使用。参见下面的组合全局和表规则

PostgreSQL 中 jsonjsonb 的区别在于运行时:jsonb 可索引且查询稍快,而 json 保留精确的输入格式。两者在 TypeScript 中默认都映射为 any,规则将它们作为单独的内部类型(json vs jsonb)定位,因此你可以相同或不同地对待它们。

日期和时间戳

每个日期和时间戳列都映射为 Luxon DateTime 实例,因此日期拥有完整的 API 可用,而非原始字符串。

app/models/post.ts
import { PostsSchema } from '#database/schema'
const post = await Post.findOrFail(params.id)
post.createdAt.toFormat('yyyy-MM-dd')
const daysSinceCreated = DateTime.now().diff(post.createdAt, 'days').days
if (post.publishedOn && post.publishedOn > DateTime.now()) {
// 预定稍后发布
}

生成的 schema 类对日期列使用 @column.date,对 datetime 和 timestamp 列使用 @column.dateTime。两个装饰器在当前版本中功能相同并接受相同的选项。

使用 table.timestamps(true, true) 创建的时间戳列标注了 autoCreateautoUpdate,它们告诉 Lucid 分别在插入和更新时自动设置它们。

@column.dateTime({ autoCreate: true })
declare createdAt: DateTime | null
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime | null

主键

Lucid 在生成 schema 类时从数据库检测主键列。无论其名称如何,该列都用 @column({ isPrimary: true }) 装饰,其类型匹配数据库列的类型(整数、字符串、UUID)。

database/migrations/xxxx_create_oauth_states_table.ts
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
async up() {
this.schema.createTable('oauth_states', (table) => {
table.string('key').notNullable().primary()
table.text('value').notNullable()
table.timestamp('updated_at')
})
}
}
database/schema.ts(生成的)
export class OauthStatesSchema extends BaseModel {
static table = 'oauth_states'
@column({ isPrimary: true })
declare key: string
@column()
declare value: string
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime | null
}

当你的主键不叫 id 时,也在你的应用模型上设置 static primaryKey,以便关联关系和默认查找器使用正确的列。

app/models/oauth_state.ts
import { OauthStatesSchema } from '#database/schema'
export default class OauthState extends OauthStatesSchema {
static primaryKey = 'key'
}

Lucid 模型仅支持单一主键。当表有复合主键时,Lucid 使用第一列。要自定义决策,使用自定义主键检测规则。

使用 schema 规则自定义类型

Schema 规则允许你更改 Lucid 生成类型和装饰器的方式,而无需手动编辑 database/schema.ts。规则存放在数据库配置中 schemaGeneration.rulesPaths 指向的文件中(通常是 database/schema_rules.ts),由生成器自动加载。参见配置指南了解字段参考。

一个初始规则文件如下所示:

database/schema_rules.ts
import { type SchemaRules } from '@adonisjs/lucid/types/schema_generator'
export default {
types: {},
columns: {},
tables: {},
} satisfies SchemaRules

规则文件的结构

规则文件接受五个顶级键,其中任何一个都可以省略。

types

按内部类型名键控的规则(numberbigintdecimalstringjsonjsonbuuidenumset 以及内部类型表中的其他类型)。规则适用于每个表中映射到该内部类型的每一列。

columns

按数据库列名键控的规则。规则适用于每个表中具有该确切名称的每一列。Lucid 的内置默认值使用此功能将 autoCreate 应用于每个 created_at 列,将 autoCreate + autoUpdate 应用于每个 updated_at 列。

tables

按表规则。每个表条目可以定义自己的 typescolumnsskipColumnsprimaryKey,仅适用于该表。

primaryKey

一个函数,决定每个表的主键如何表示。接收表名、检测到的主键列名和完整的列元数据。参见自定义主键检测

规则解析优先级

对于每一列,生成器按以下顺序执行五步查找并使用第一个匹配:

  1. 表特定列规则,位于 tables[tableName].columns[columnName]
  2. 表特定类型规则,位于 tables[tableName].types[internalType]
  3. 全局列规则,位于 columns[columnName]
  4. 主键规则结果,当该列是检测到的主键时
  5. 全局类型规则,位于 types[internalType]

更具体的规则优先于不太具体的规则。表列规则覆盖全局列规则,全局列规则覆盖全局类型规则。

规则在生成 schema 文件时应用。更改规则后,运行 node ace schema:generate 重新生成,或等待下一个迁移命令自动重新生成。

规则值的结构

每个规则返回一个带有三个字段的 ColumnInfo 对象:

type ColumnInfo = {
tsType: string
decorators?: { name: string; args?: Record<string, any> }[]
imports?: ImportInfo[]
}

tsType

在列上声明的 TypeScript 类型,作为字符串('number''string''JSON<any>'`'admin' | 'user'`)。

decorators

在列上方发出的装饰器。{ name, args? } 对象的数组,其中 name 是以 @column 开头的装饰器名(例如 '@column''@column.date''@column.dateTime'),args 是装饰器参数的可选对象。

imports

生成的文件需要的导入,以便装饰器和类型可以解析。每个条目是一个 ImportInfo 对象:{ source, namedImports?, typeImports?, defaultImport?, defaultTypeImport? }。对运行时值(如 Luxon 的 DateTime)使用 namedImports,对仅类型导入使用 typeImports

规则也可以返回 ColumnInfo 的函数。函数接收数据库类型字符串('varchar''int4' 等)并返回规则。当同一内部类型需要根据具体数据库类型略有不同的处理时使用此方式。

types: {
string: (dataType) => ({
tsType: dataType === 'text' ? 'string' : `string`,
decorators: [{ name: '@column' }],
imports: [],
}),
}

全局类型规则

全局类型规则定位映射到给定内部类型的每一列。

database/schema_rules.ts
import { type SchemaRules } from '@adonisjs/lucid/types/schema_generator'
export default {
types: {
json: {
tsType: 'JSON<any>',
decorators: [{ name: '@column' }],
imports: [
{ source: '#types/db', typeImports: ['JSON'] },
],
},
bigint: {
tsType: 'bigint',
decorators: [{ name: '@column' }],
},
},
tables: {},
} satisfies SchemaRules

使用这些规则,每个 JSON 列变为 JSON<any>,每个 bigint 列变为 TypeScript bigint

全局列规则

全局列规则定位每个表中具有给定名称的每一列。Lucid 附带两个内置全局列规则,默认继承:

  • created_at 发出 @column.dateTime({ autoCreate: true })
  • updated_at 发出 @column.dateTime({ autoCreate: true, autoUpdate: true })

为你一致使用的列添加自己的规则。例如,如果数据库中的每个加密列都命名为 ciphertext_*,全局规则保持它们统一类型化:

database/schema_rules.ts
export default {
columns: {
deleted_at: {
tsType: 'DateTime | null',
decorators: [{ name: '@column.dateTime' }],
imports: [
{ source: 'luxon', namedImports: ['DateTime'] },
],
},
},
} satisfies SchemaRules

表特定列规则

列规则定位一个特定表上的一个特定列。

database/schema_rules.ts
export default {
tables: {
users: {
columns: {
role: {
tsType: `'admin' | 'moderator' | 'user'`,
decorators: [{ name: '@column' }],
},
},
},
},
} satisfies SchemaRules

使用此规则,users.role 生成为:

@column()
declare role: 'admin' | 'moderator' | 'user'

表特定类型规则

按表类型规则为一个表覆盖全局类型规则。当一个表与应用其余部分对类型的处理方式不同时使用此方式。

database/schema_rules.ts
export default {
types: {
json: {
tsType: 'JSON<any>',
decorators: [{ name: '@column' }],
imports: [
{ source: '#types/db', typeImports: ['JSON'] },
],
},
},
tables: {
audit_events: {
types: {
json: {
tsType: 'Record<string, unknown>',
decorators: [{ name: '@column' }],
},
},
},
},
} satisfies SchemaRules

应用中的 JSON 列类型化为 JSON<any>,但 audit_events 内的 JSON 列类型化为 Record<string, unknown>

跳过列

要从生成的 schema 类中排除特定列(例如,由数据库触发器或单独工具管理的列),在表的 skipColumns 中列出它们。

database/schema_rules.ts
export default {
tables: {
users: {
skipColumns: ['search_tsv', 'ltree_path'],
},
},
} satisfies SchemaRules

这些列完全从生成的类中排除。通过模型的查询仍会命中该表,但这些列对 TypeScript 和 Lucid 的变更跟踪不可见。

组合规则

规则从最不具体到最具体组合。列规则细化类型规则,按表规则细化全局规则,装饰器参数累积。

database/schema_rules.ts
export default {
types: {
json: {
tsType: 'JSON<any>',
decorators: [{ name: '@column' }],
imports: [
{ source: '#types/db', typeImports: ['JSON'] },
],
},
},
columns: {
deleted_at: {
tsType: 'DateTime | null',
decorators: [{ name: '@column.dateTime' }],
imports: [
{ source: 'luxon', namedImports: ['DateTime'] },
],
},
},
tables: {
posts: {
columns: {
metadata: {
tsType: 'JSON<{ title?: string; description?: string; ogImage?: string }>',
decorators: [{ name: '@column' }],
imports: [
{ source: '#types/db', typeImports: ['JSON'] },
],
},
},
},
},
} satisfies SchemaRules

应用中的 JSON 列变为 JSON<any>,除了 posts.metadata 使用其特定形状。每个 deleted_at 列都变为可空的 DateTime,无论它出现在哪个表上。

自定义主键检测

默认情况下,Lucid 使用数据库返回的第一个主键列并应用 @column({ isPrimary: true }),TypeScript 类型从列的数据库类型推断(因此 integer 主键变为 numberuuid 主键变为 string)。使用 primaryKey 函数覆盖此行为,可以全局或按表。

回调具有以下签名:

type PrimaryKeyRule = (
tableName: string,
primaryKeys: string[],
columns: Record<string, DatabaseColumn>,
) => { columnName: string; columnInfo: ColumnInfo } | undefined

columns 是从列名到列元数据的映射。每个条目具有以下形状:

type DatabaseColumn = {
type: string
nullable: boolean
defaultValue?: any
maxLength?: number | null
}

返回 undefined 让 Lucid 对该表回退到默认检测。

database/schema_rules.ts
import { type SchemaRules } from '@adonisjs/lucid/types/schema_generator'
export default {
primaryKey: (tableName, primaryKeys, columns) => {
const columnName = primaryKeys[0]
if (!columnName || !columns[columnName]) {
return undefined
}
return {
columnName,
columnInfo: {
tsType: 'string',
decorators: [{ name: '@column', args: { isPrimary: true } }],
imports: [],
},
}
},
} satisfies SchemaRules

表级 primaryKey 规则仅覆盖该表的全局规则。

database/schema_rules.ts
export default {
tables: {
oauth_states: {
primaryKey: (tableName, primaryKeys, columns) => {
const columnName = primaryKeys[0]
if (!columnName || !columns[columnName]) {
return undefined
}
return {
columnName,
columnInfo: {
tsType: 'string',
decorators: [{ name: '@column', args: { isPrimary: true } }],
imports: [],
},
}
},
},
},
} satisfies SchemaRules

如果你为恰好是主键的列定义了列规则(在 columns 下),列规则获胜,primaryKey 规则对该列被跳过。

在模型上覆盖列

Schema 规则自定义生成器发出的内容。模型级覆盖允许单个模型在 TypeScript 级别细化或重新声明列,在模型文件本身中。

在以下情况下使用模型覆盖:

  • 只有一个模型需要类型细化,schema 规则过于大材小用。
  • 你想要列上的自定义读或写行为(通过 consumeprepare 或 getter 和 setter),这些不属于生成的文件。
  • 你想要向装饰器添加额外选项(columnNamemeta)而不编辑 schema 规则。

通过在模型上使用兼容类型重新声明列来覆盖。

app/models/post.ts
import type { JSON } from '#types/db'
import { column } from '@adonisjs/lucid/orm'
import { PostsSchema } from '#database/schema'
export default class Post extends PostsSchema {
@column()
declare metadata: JSON<Partial<{
title: string
description: string
ogImage: string
keywords: string[]
}>>
}

模型继承 schema 类的列定义,除非你覆盖它,重新声明的属性遮蔽父类的属性。

@column 选项参考

@column 装饰器接受具有以下字段的 options 对象。这些适用于普通的 @column() 装饰器和日期变体(@column.date()@column.dateTime())。

columnName

当无法从属性名派生时的实际数据库列名。在生成的类中很少需要(生成器已经记录了此信息),但在模型覆盖中有用,当你想重命名属性而不更改数据库时。

@column({ columnName: 'email_address' })
declare email: string

isPrimary

将列标记为主键。由生成器自动为检测到的主键设置。仅在生成器无法确定主键时才在覆盖上显式声明。

consume

从数据库读取行时调用的函数。将原始列值转换为模型使用的形状。常见情况包括驱动作为字符串返回的 JSON 列、你想要作为字符串的数值 ID,或需要解密的加密值。

@column({
consume: (value) => typeof value === 'string' ? JSON.parse(value) : value,
})
declare preferences: Record<string, unknown>

回调接收原始值、属性名和模型实例。

prepare

在值发送到数据库进行插入或更新之前调用的函数。consume 的逆操作。用于在持久化之前序列化、加密或规范化值。

@column({
prepare: (value) => typeof value === 'object' ? JSON.stringify(value) : value,
})
declare preferences: Record<string, unknown>

meta

附加到列的任意元数据对象。Lucid 不使用此字段;用于你自己的工具(自定义装饰器、表单生成器、管理面板),这些工具需要每列的额外信息。

@column({ meta: { editableInAdmin: false } })
declare internalId: string

日期列特定的选项

@column.date@column.dateTime 装饰器接受上述每个 @column 选项,以及两个用于自动时间戳的额外字段。

autoCreate

当为 true 时,Lucid 在插入新行时将列设置为当前 DateTime。由生成器应用于 created_atupdated_at 列。

@column.dateTime({ autoCreate: true })
declare createdAt: DateTime | null

autoUpdate

当为 true 时,Lucid 在每次保存模型时将列设置为当前 DateTime。与 autoCreate 配对用于 updatedAt 风格的列,或单独用于跟踪最近修改但不在创建时设置的字段。

@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime | null

列的自定义 getter 和 setter

当你需要超出 consumeprepare 所表达的自定义读或写行为时,在模型上为列添加 TypeScript getter 或 setter。Lucid 仍然通过 $attributes 读写底层属性,因此 getter 和 setter 包装该值而不替换 Lucid 的存储。

模式是:在 $attributes 下声明一个私有的后备字段,并用公共名称暴露 getter 或 setter。

app/models/user.ts
import { column } from '@adonisjs/lucid/orm'
import { UsersSchema } from '#database/schema'
export default class User extends UsersSchema {
@column({ columnName: 'avatar_path' })
declare avatarPath: string | null
get avatarUrl(): string | null {
return this.avatarPath
? `https://cdn.example.com/${this.avatarPath}`
: null
}
}

对于仍需落入列中的写端转换,prepare 通常是更好的选择,因为它在每次插入和更新时自动运行。当转换依赖于赋值时的其他模型属性时才使用 setter。

类型兼容性约束

TypeScript 强制要求重新声明的属性类型与基类的类型兼容。你可以缩窄类型,但不能将其更改为不兼容的类型。

// 有效:将 string 缩窄为联合类型
@column()
declare status: 'draft' | 'published'
// 有效:将 any 缩窄为特定类型
@column()
declare metadata: JSON<{ title: string }>
// 无效:不能将 string 更改为 number
@column()
declare title: number
// 无效:不能将 number 更改为 string
@column()
declare id: string

当你需要从根本上更改列的类型时,使用 schema 规则在生成时设置正确的类型;然后模型可以进一步细化生成的类型。参见组合全局和表规则