Skip to content

约定文件结构, 使得Tealina能够帮你生成API文件, 类型声明, 和自动更新index文件的导入导出.

文件结构

  1. 所有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
  1. 一个 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

文件内部

  1. 每个 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

索引

  1. [api-dir]/[method]/index.ts 定义路由和处理函数的映射关系
ts
  export default {
    'category/create': import('./category/create.js'),
    ...
  }
  export default {
    'category/create': import('./category/create.js'),
    ...
  }
  1. [api-dir]/index.ts 定义 HTTP Method 和路由的映射关系
ts
    export default {
      'post': import('./post/index.js'),
      ...
    }
    export default {
      'post': import('./post/index.js'),
      ...
    }

类型声明

  1. [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']>
}