约定文件结构, 使得Tealina能够帮你生成API文件, 类型声明, 和自动更新index文件的导入导出.
文件结构
- 所有API文件存储在
[api-dir]/[method]
,
md
api-dir
├─ index.ts
└─ post
├─ index.ts
├─ login.ts
├─ category
└─── create.ts
api-dir
├─ index.ts
└─ post
├─ index.ts
├─ login.ts
├─ category
└─── create.ts
- 一个 API 一个 文件
md
api-dir
├─ index.ts
└─ post
├─ index.ts
├─ login.ts
├─ category
└─── create.ts
api-dir
├─ index.ts
└─ post
├─ index.ts
├─ login.ts
├─ category
└─── create.ts
文件内部
- 每个 API 文件有 export default 处理函数
ts
import type { AuthedHandler } from '../../../../types/handler.js'
import type { Pure } from '../../../../types/pure.js'
import { convention } from '../../../convention.js'
type ApiType = AuthedHandler<{ body: Pure.CategoryCreateInput }, Pure.Category>
/** 这里的注释会被提取到API文档 */
const handler: ApiType = async (req, res) => {
...
}
export default convention(handler)
import type { AuthedHandler } from '../../../../types/handler.js'
import type { Pure } from '../../../../types/pure.js'
import { convention } from '../../../convention.js'
type ApiType = AuthedHandler<{ body: Pure.CategoryCreateInput }, Pure.Category>
/** 这里的注释会被提取到API文档 */
const handler: ApiType = async (req, res) => {
...
}
export default convention(handler)
ts
import type { NextFunction, Request, Response } from 'express'
interface RawPayload {
body?: unknown
params?: unknown
query?: unknown
}
export interface AuthedHandler<
T extends RawPayload = {},
Tresponse = null,
Theaders extends Request['headers'] = AuthHeaders,
Tlocals extends Record<string, any> = AuthedLocals,
> {
(
req: Request<T['params'], Tresponse, T['body'], T['query']>,
res: Response<Tresponse, Tlocals>,
next: NextFunction,
): any
}
...
import type { NextFunction, Request, Response } from 'express'
interface RawPayload {
body?: unknown
params?: unknown
query?: unknown
}
export interface AuthedHandler<
T extends RawPayload = {},
Tresponse = null,
Theaders extends Request['headers'] = AuthHeaders,
Tlocals extends Record<string, any> = AuthedLocals,
> {
(
req: Request<T['params'], Tresponse, T['body'], T['query']>,
res: Response<Tresponse, Tlocals>,
next: NextFunction,
): any
}
...
ts
export namespace Pure {
interface Category{
/** @default {autoincrement()} */
id: number
categoryName: string
description: string
}
interface CategoryCreateInput{
/** @default {autoincrement()} */
id?: number
categoryName: string
description: string
}
}
export namespace Pure {
interface Category{
/** @default {autoincrement()} */
id: number
categoryName: string
description: string
}
interface CategoryCreateInput{
/** @default {autoincrement()} */
id?: number
categoryName: string
description: string
}
}
ts
import type { RequestHandler } from 'express'
import type { CustomHandlerType } from '../types/handler.js'
type ConstrainedHandlerType = readonly [...RequestHandler[], CustomHandlerType]
type EnsureHandlerType = <const T extends ConstrainedHandlerType>(
...handlers: T
) => T
// 只是做类型检查
export const convention: EnsureHandlerType = (...handlers) => handlers
import type { RequestHandler } from 'express'
import type { CustomHandlerType } from '../types/handler.js'
type ConstrainedHandlerType = readonly [...RequestHandler[], CustomHandlerType]
type EnsureHandlerType = <const T extends ConstrainedHandlerType>(
...handlers: T
) => T
// 只是做类型检查
export const convention: EnsureHandlerType = (...handlers) => handlers
索引
[api-dir]/[method]/index.ts
定义路由和处理函数的映射关系
ts
export default {
'category/create': import('./category/create.js'),
...
}
export default {
'category/create': import('./category/create.js'),
...
}
[api-dir]/index.ts
定义 HTTP Method 和路由的映射关系
ts
export default {
'post': import('./post/index.js'),
...
}
export default {
'post': import('./post/index.js'),
...
}
类型声明
[api-dir].d.ts
API 类型入口文件
ts
import apis from '../src/api-v1/index.ts'
import type { ResolveApiType } from './handler.js'
type RawApis = typeof apis
export type ApiTypesRecord = {
[Method in keyof RawApis]: ResolveApiType<Awaited<RawApis[Method]>['default']>
}
import apis from '../src/api-v1/index.ts'
import type { ResolveApiType } from './handler.js'
type RawApis = typeof apis
export type ApiTypesRecord = {
[Method in keyof RawApis]: ResolveApiType<Awaited<RawApis[Method]>['default']>
}