@ adocasts.com/dto (英语)
从 Lucid Models 轻松地制作和生成 DTO 和验证器
将 Lucid Models 转换为 DTO 文件可能是一个乏味的任务. 这个套装旨在让它少一点 读取您模型的属性定义并将其移植到 DTO. 会是完美的吗? 可能不会,但应该可以帮助削减 需要重复完成这项任务.
安装
您可以通过 Ace CLI 轻松地安装和配置 add 命令.
node ace add @adocasts.com/dto
手动安装配置( C)
如果您愿意,也可以手动安装和配置
npm install @adocasts.com/dto
node ace configure @adocasts.com/dto
定义 DTO 导入路径
生成的 DTO 将使用 #dtos/* 用于DTO内部的关系进口.
因此,我们建议在您 package.json
"imports": {
"#dtos/*": "./app/dtos/*.js"
}
生成 DTOs 命令
想要为您的模型生成 DTO 吗 ? 这是命令!
node ace generate:dtos
这将读取您所有的模型文件, 收集其属性和类型 . 然后把这些地产的种类转换成系列安全类型 及其与DTO表述的关系.
您也可以通过使用 DTO 生成验证符和 DTO --validator 旗帜 :
node ace generate:dtos --validator
File Tree Class
------------------------------------------------
└── app/
├── dtos/
│ ├── account.ts AccountDto
│ ├── account_group.ts AccountGroupDto
│ ├── account_type.ts AccountTypeDto
│ ├── income.ts IncomeDto
│ ├── payee.ts PayeeDto
│ └── user.ts UserDto
└── models/
├── account.ts Account
├── account_group.ts AccountGroup
├── account_type.ts AccountType
├── income.ts Income
├── payee.ts Payee
└── user.ts User
- 从定义的位置获取您的模型文件列表
adonisrc.ts文件 - 将这些文件读取为纯文本, 拖放为仅属性定义
- 确定属性名称、类型、关系是否以及是否可选修改
? - 将这些模型类型转换成序列化表示(目前转换非常松散)
- 注意,目前这不包括模型属性上定义的序列化行为(如:
serializeAs(中文(简体) )
- 注意,目前这不包括模型属性上定义的序列化行为(如:
- 从这些转换创建 DTO 属性定义
- 为每个财产编制构造器值设置符
- 收集关系所需进口物品
- 生成 DTO 文件
- 注意, 如果一个文件已经存在于指定的 DTO 位置, 它将被跳过
制作 DTO 命令
要制作一个普通的DTO文件, 还是从一个单一的模型中制作一个单独的DTO? 这是命令!
要制作一个 DTO 命名 AccountDto 在一个位于 dto/account.ts,我们可以运行如下:
node ace make:dto account
这将检查是否有一个模型命名 Account。 。 。 .
如果找到一个模型,它将使用该模型的属性定义生成 AccountDto。 。 。 .
否则,它只会产生一个 AccountDto 带有空类的文件.
您也可以通过使用 DTO 生成一个验证符 --validator 旗帜 :
node ace make:dto account --validator
这将创建一个DTO和一个账户模型的验证器.
File Tree Class
------------------------------------------------
└── app/
├── dtos/
│ ├── account.ts AccountDto
└── models/
├── account.ts Account
如果没有账户模型呢?
如上所述,一个平地 AccountDto 类将在新的 dto/account.ts 文档,它看起来像下面.
export default class AccountDto {}
指定不同的模式
如果 DTO 和 Model 名称不匹配, 您可以指定一个特定的模型, 通过 : --model 旗帜.
node ace make:dto account --model=main_account
现在,而不是寻找一个模型 命名 Account 它会代替
寻找 MainAccount 并使用它创建命名为 DTO AccountDto。 。 。 .
制作校验器命令
想做个模型的验证员吗? 此命令的工作与 make:dto 命令 :
node ace make:validators account
这将检查是否有一个模型命名 Account。 。 。 .
如果找到一个模型,它将使用该模型的属性定义生成 accountValidator。 。 。 .
否则,它只会生成一个带有空子图案的普通验证器文件.
File Tree Variable
------------------------------------------------
└── app/
├── validators/
│ ├── account.ts accountValidator
└── models/
├── account.ts Account
指定不同的模式
就像DTO一样,你可以指定一个不同的模型:
node ace make:validators account --model=main_account
生成验证器命令
想要为您所有模型生成验证器吗 ? 此命令的作用类似 generate:dtos数字 :
node ace generate:validators
这将读取您所有的模型文件, 收集其属性和类型 . 然后把这些财产的种类转换成VineJS验证规则.
File Tree Variable
------------------------------------------------
└── app/
├── validators/
│ ├── account.ts accountValidator
│ ├── account_group.ts accountGroupValidator
│ ├── account_type.ts accountTypeValidator
│ ├── income.ts incomeValidator
│ ├── payee.ts payeeValidator
│ └── user.ts userValidator
└── models/
├── account.ts Account
├── account_group.ts AccountGroup
├── account_type.ts AccountType
├── income.ts Income
├── payee.ts Payee
└── user.ts User
BaseDto 帮助器
在v0.0.4中新添加的,我们现在包括a BaseDto 或者说 BaseModelDto 考虑我们是否从模型中生成你的DTO.
这两个基地都有一个叫 fromArray。通过此程序,您可以在一系列源对象中通过.
然后我们绕过它们 然后把每个都传到一个新的建筑师身上 假设您将把属性 包含在您的 DTO 构造器中.
举个例子
class Test extends BaseModel {
@column()
declare id: number
}
class TestDto extends BaseModelDto {
declare id: number
constructor(instance: Test) {
super()
this.id = instance.id
}
}
const tests = await Test.createMany([{ id: 1 }, { id: 2 }, { id: 3 }])
const dtoArray = TestDto.fromArray(tests)
// [TestDto, TestDto, TestDto]
此外, BaseModelDto 还包括a fromPaginator 帮手 这样你就可以通过一个实例 ModelPaginator 改为a
SimplePaginatorDto 我们已经在这个一揽子方案中界定。 您也可以通过一个 URL 范围的开始和结束, 我们会为您生成这些 URL 在转换期间.
举个简单的例子
class Test extends BaseModel {
@column()
declare id: number
}
class TestDto extends BaseModelDto {
declare id: number
constructor(instance: Test) {
super()
this.id = instance.id
}
}
const tests = await Test.createMany([{ id: 1 }, { id: 2 }, { id: 3 }])
const paginator = await Test.query().paginate(1, 2)
const paginatorDto = TestDto.fromPaginator(paginator, { start: 1, end: 2 })
/**
* {
* data: TestDto[],
* meta: SimplePaginatorDtoMetaContract
* }
*/
const paginationUrls = paginatorDto.meta.pagesInRange
/**
* [
* {
* url: '/?page=1',
* page: 1,
* isActive: true
* },
* {
* url: '/?page=2',
* page: 2,
* isActive: false
* },
* ]
*/
举例来说,如果您在使用Inertia之类的东西,那么您可以按此输入道具
import { SimplePaginatorDtoContract } from '@adocasts.com/dto/types'
import DifficultyDto from '#dtos/difficulty'
const props = defineProps<{
paginated: SimplePaginatorDtoContract<DifficultyDto>
}>()
const rows = props.paginated.data
const info = props.paginated.meta
const urls = props.paginated.meta.pagesInRange
需要注意的东西
- 目前我们从模型的文件名中假定模型的名字.
- 目前没有更改 DTO 输出目录的设置
- 由于反射的限制,我们把模型读作"平正文字". 我不是TypeScript wiz, 所以,如果你知道更好的方法,我都是耳朵!
- 既然我们读成简写
- 目前我们忽略了装饰师和他们的选择
- 既然我们读成简写
示例
所以,我们用账号作为我们的例子 在整个指南, 让我们看看这个账户模型是什么样子!
账户模式
// app/models/account.ts
import { DateTime } from 'luxon'
import { BaseModel, belongsTo, column, computed, hasMany, hasOne } from '@adonisjs/lucid/orm'
import User from './user.js'
import type { BelongsTo, HasMany, HasOne } from '@adonisjs/lucid/types/relations'
import AccountType from '#models/account_type'
import Payee from '#models/payee'
import Stock from '#models/stock'
import Transaction from '#models/transaction'
import AccountTypeService from '#services/account_type_service'
import { columnCurrency } from '#start/orm/column'
import type { AccountGroupConfig } from '#config/account'
export default class Account extends BaseModel {
// region Columns
@column({ isPrimary: true })
declare id: number
@column()
declare userId: number
@column()
declare accountTypeId: number
@column()
declare name: string
@column()
declare note: string
@column.date()
declare dateOpened: DateTime | null
@column.date()
declare dateClosed: DateTime | null
@columnCurrency()
declare balance: number
@columnCurrency()
declare startingBalance: number
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime
// endregion
// region Unmapped Properties
aggregations: Record<string, number> = {}
// endregion
// region Relationships
@belongsTo(() => User)
declare user: BelongsTo<typeof User>
@belongsTo(() => AccountType)
declare accountType: BelongsTo<typeof AccountType>
@hasOne(() => Payee)
declare payee: HasOne<typeof Payee>
@hasMany(() => Stock)
declare stocks: HasMany<typeof Stock>
@hasMany(() => Transaction)
declare transactions: HasMany<typeof Transaction>
// endregion
// region Computed Properties
@computed()
get accountGroup(): AccountGroupConfig {
return AccountTypeService.getAccountTypeGroup(this.accountTypeId)
}
@computed()
get isCreditIncrease(): boolean {
return AccountTypeService.isCreditIncreaseById(this.accountTypeId)
}
@computed()
get isBudgetable() {
return AccountTypeService.isBudgetable(this.accountTypeId)
}
@computed()
get balanceDisplay() {
return '$' + this.balance.toLocaleString('en-US')
}
// endregion
}
有个
- 列属性
- 无效属性
- 未映射的属性, 包含默认值
- 计数器
- 关系
让我们看看,当我们生成我们的DTO!
node ace make:dto account
账户 DTO
import { BaseModelDto } from '@adocasts.com/dto/base'
import Account from '#models/account'
import UserDto from '#dtos/user'
import AccountTypeDto from '#dtos/account_type'
import PayeeDto from '#dtos/payee'
import StockDto from '#dtos/stock'
import TransactionDto from '#dtos/transaction'
import { AccountGroupConfig } from '#config/account'
export default class AccountDto extends BaseModelDto {
declare id: number
declare userId: number
declare accountTypeId: number
declare name: string
declare note: string
declare dateOpened: string | null
declare dateClosed: string | null
declare balance: number
declare startingBalance: number
declare createdAt: string
declare updatedAt: string
aggregations: Record<string, number> = {}
declare user: UserDto | null
declare accountType: AccountTypeDto | null
declare payee: PayeeDto | null
declare stocks: StockDto[]
declare transactions: TransactionDto[]
declare accountGroup: AccountGroupConfig
declare isCreditIncrease: boolean
declare isBudgetable: boolean
declare balanceDisplay: string
constructor(account?: Account) {
super()
if (!account) return
this.id = account.id
this.userId = account.userId
this.accountTypeId = account.accountTypeId
this.name = account.name
this.note = account.note
this.dateOpened = account.dateOpened?.toISO()!
this.dateClosed = account.dateClosed?.toISO()!
this.balance = account.balance
this.startingBalance = account.startingBalance
this.createdAt = account.createdAt.toISO()!
this.updatedAt = account.updatedAt.toISO()!
this.aggregations = account.aggregations
this.user = account.user && new UserDto(account.user)
this.accountType = account.accountType && new AccountTypeDto(account.accountType)
this.payee = account.payee && new PayeeDto(account.payee)
this.stocks = StockDto.fromArray(account.stocks)
this.transactions = TransactionDto.fromArray(account.transactions)
this.accountGroup = account.accountGroup
this.isCreditIncrease = account.isCreditIncrease
this.isBudgetable = account.isBudgetable
this.balanceDisplay = account.balanceDisplay
}
}
它拥有
- 需要的进口(它将试图通过参考模型的进口来获取全部的进口)
- 从我们的模型中列属性
- 无效财产无效
- 我们模型中未显示的属性, 加上默认值
- 转换成DTO表示的关系
- 获取器及其类型,当指定。 如果推断类型, 如果变量名称开始, 类型会默认为字符串或布尔
is - 上述所有项目的构造值设置器
- 帮助方法
fromArray如果需要的话, 将常态化为空数组
示例验证器
让我们看看当我们生成一个验证器 当我们的账户模型:
node ace make:validator account
账户验证人
import vine from '@vinejs/vine'
import Account from '#models/account'
import { AccountGroupConfig } from '#config/account'
export const accountValidator = vine.compile(
vine.object({
id: vine.number(),
userId: vine.number(),
accountTypeId: vine.number(),
name: vine.string().trim(),
note: vine.string().trim(),
dateOpened: vine.string().datetime().optional(),
dateClosed: vine.string().datetime().optional(),
balance: vine.number(),
startingBalance: vine.number(),
createdAt: vine.string().datetime(),
updatedAt: vine.string().datetime(),
user: vine.object({}),
accountType: vine.object({}),
payee: vine.object({}),
stocks: vine.array(vine.object({})),
transactions: vine.array(vine.object({})),
accountGroup: vine.object({}),
isCreditIncrease: vine.boolean(),
isBudgetable: vine.boolean(),
balanceDisplay: vine.string().trim()
})
)
其经有:
- 需要从模型进口
- 所有模型属性的验证规则
- 适当的类型转换( 如 DateTime to string (. datetime ()) )
- 无效属性的可选修改符
- 关系对象和数组验证符
使用生成验证器
一旦您生成了验证器, 您可以在您的控制器或路由中使用它来验证输入的数据 :
import { accountValidator } from '#validators/account'
import { HttpContext } from '@adonisjs/core/http'
export default class AccountsController {
async store({ request, response }: HttpContext) {
try {
// Validate the request data
const data = await accountValidator.validate(request.all())
// Create the account
const account = await Account.create(data)
// Return the account as a DTO
return response.created(new AccountDto(account))
} catch (error) {
// Handle validation errors
if (error.code === 'E_VALIDATION_ERROR') {
return response.unprocessableEntity(error.messages)
}
throw error
}
}
}