npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ts-mysql-ddl

v1.2.0

Published

TypeScript DSL for MySQL schema definition + enum generation — define tables/enums in TS, generate DDL SQL and TypeScript enums

Readme

ts-mysql-ddl — MySQL Schema DSL

概述

ts-mysql-ddl 是一个 TypeScript DSL,用于以纯 TS 代码定义 MySQL 数据表结构,直接生成 DDL SQL。它是数据库结构的唯一事实来源(Single Source of Truth),同时为 mock 数据生成提供类型信息。

架构设计

| 层 | 文件 | 职责 | |---|---|---| | 类型系统 | mysql-type.ts | MysqlType 枚举 + 类型守卫 | | 列定义 | column-def.ts | ColumnDef 联合类型(按 type 分支锁参数) | | 列引用 | column-ref.ts | ColumnRef 类 + col() 工厂 | | 表定义 | table-def.ts | TableSchema / ForeignKey / IndexDef 接口 | | 工厂 | define-table.ts | defineTable(name, input) → TableSchema | | DDL 生成 | generate-ddl.ts | generateDdl(tables) → SQL string | | 自动扫描 | scan-tables.ts | scanTables(dir) 自动发现 *.table.ts | | 枚举生成 | generate-enums.ts | generateEnums({ schemaDir, outDir })defineEnum 生成 TS enum 文件 |

核心类型

MysqlType

枚举定义 MySQL 数据类型家族,每个家族对应一组编译期校验的参数:

INT / TINYINT / SMALLINT / BIGINT   → autoIncrement, default(number), enumRef
DECIMAL                               → precision, scale, default(string)
VARCHAR / CHAR                        → length, default(string), enumRef
TEXT                                   → (无额外参数)
DATE                                   → (无额外参数)
DATETIME / TIMESTAMP                   → default('CURRENT_TIMESTAMP' only)

所有类型通用参数:primaryKey, index, unique, comment, deleted

ColumnDef 联合类型

type ColumnDef = IntColumn | DecimalColumn | StringColumn | TextColumn | DateColumn | DateTimeColumn

每个接口按 type 分支限制参数,编译期拦截非法组合:

  • IntColumn: 不能有 length, precision, scale
  • StringColumn: 不能有 autoIncrement, precision
  • DateTimeColumn: default 只能是 'CURRENT_TIMESTAMP'

EnumShape

type EnumShape = Record<string, string | number>

表示代码层面的枚举(非 MySQL ENUM),DDL 中存 int/string。由 enumRef 字段引用真实 TS 枚举,供 mock 生成使用。

ColumnRef

class ColumnRef {
  readonly def: ColumnDef;         // 列定义
  _table: string = '';             // 所属表名(由 defineTable/registerColumns 设置)
  _column: string = '';            // 列名(由 defineTable/registerColumns 设置)
  toString(): string               // 返回 _column,未初始化时返回 '?'
}

_table / _column 是 identity 字段,由以下时机回填:

  • defineTable() 调用时
  • scanTables() 的 Phase 1 阶段

关键约束:ColumnRef 创建时 identity 为空。两阶段初始化确保在所有 FK 引用解析前,所有列的 identity 已设置完成。

使用方式

方式一:手动编排(适用于少量表)

import { MysqlType, col, defineTable, generateDdl } from '@fastify-api/ts-mysql-ddl';

const merchantCols = {
  id:   col({ type: MysqlType.VARCHAR, length: 12, primaryKey: true }),
  name: col({ type: MysqlType.VARCHAR, length: 100 }),
};
const merchant = defineTable('merchant', { columns: merchantCols, comment: 'Merchant' });

const orderCols = {
  id:     col({ type: MysqlType.INT, autoIncrement: true }),
  mer_id: col({ type: MysqlType.VARCHAR, length: 20 }),
  status: col({ type: MysqlType.VARCHAR, length: 20, enumRef: OrderStatus }),
};
const order = defineTable('order', {
  columns: orderCols,
  indexes: [{ columns: [orderCols.status, orderCols.deleted] }],
  foreignKeys: {
    fk_order_merchant: { columns: [orderCols.mer_id], ref: merchant.cols.id, onDelete: 'CASCADE' },
  },
  paginated: true,  // 数据量大,需要分页查询
  generator: 'snowflake',  // 非自增 PK 的 ID 生成器
});

const sql = generateDdl([merchant, order]);

方式二:自动扫描(推荐)

按文件命名约定组织 *.table.ts 文件:

schema/
├── merchant.table.ts
├── order.table.ts
├── coupon.table.ts
└── ...

每个文件只需导出数据,不调 defineTable

// schema/order.table.ts
import { col, MysqlType } from '@fastify-api/ts-mysql-ddl';
import { columns as merchant } from './merchant.table';

export const columns = { ... };          // Record<string, ColumnRef> 必需
export const indexes = [...];            // IndexDef[] 可选
export const foreignKeys = { ... };      // Record<string, ForeignKey> 可选
export const primaryKey = ...;          // ColumnRef | ColumnRef[] 可选
export const comment = '...';           // string 可选
export const paginated = true;          // boolean 可选,true=数据量大→分页/瀑布流/可搜索选择器
export const generator = 'snowflake';   // string 可选,非自增主键的 ID 生成器标识

扫描:

const tables = await scanTables('./schema');
const sql = generateDdl(tables);

表名从文件名推导:order.table.tsorder

scanTables() 使用两阶段初始化:

  1. Phase 1:遍历所有模块,逐列回填 _table / _column(此时所有 ColumnRef 获得 identity)
  2. Phase 2:为每个模块调 defineTable() 构建完整 TableSchema

因此 FK 引用(ref: merchant.id)无论文件加载顺序如何,都能正确解析。

外键引用模式

直接引用(不同表)

import { columns as merchant } from './merchant.table';

export const foreignKeys = {
  fk_order_merchant: {
    columns: [columns.mer_id],
    ref: merchant.id,           // ColumnRef 直接引用
    onDelete: 'CASCADE',
  },
};

懒引用(自引用/循环引用)

export const foreignKeys = {
  fk_category_parent: {
    columns: [columns.parent_id],
    ref: () => columns.id,      // 延迟求值,避免循环依赖
    onDelete: 'CASCADE',
  },
};

ref() => ColumnRef 时,DDL 生成阶段再求值。

复合外键

export const foreignKeys = {
  fk_order_detail_product: {
    columns: [columns.sku_id, columns.tenant_id],
    ref: product.columns,        // 注意:此处需要确认是否支持复合 FK → 见下方约束
  },
};

当前约束:复合 FK 的 ref 语法暂未定型,当前仅支持单列 FK。

分页标记(paginated)

paginated 是表级元数据,标记该表的数据量级,不参与 DDL 生成,供下游组件决定交互形式:

| 值 | 数据量 | 前端行为 | 选择器 | |---|---|---|---| | true | 大 | admin:分页列表;小程序:瀑布流 | auto-complete(可搜索) | | false / 未设置 | 小 | 全量列表 | 简单下拉 |

手动编排:

defineTable('order', {
  columns: orderCols,
  paginated: true,
});

自动扫描(导出即可):

// schema/order.table.ts
export const paginated = true;

该标志可通过 table._schema.paginated 读取。

ID 生成器(generator)

为非自增主键表声明 ID 生成器标识,不参与 DDL 生成,供下游工具自动使用正确的生成逻辑。

手动编排:

defineTable('pay', {
  columns: payCols,
  primaryKey: payCols.id,
  generator: 'snowflake',
});

自动扫描(导出即可):

// schema/merchant.table.ts
export const primaryKey = columns.id;
export const generator = 'merchantId';

自增主键表无需声明 generator

索引与约束

单列索引(在列定义中)

col({ type: MysqlType.VARCHAR, length: 20, index: true })
// → KEY `idx_status` (`status`)

单列唯一(在列定义中)

col({ type: MysqlType.VARCHAR, length: 50, unique: true })
// → UNIQUE KEY `uk_name` (`name`)

复合索引

indexes: [
  { columns: [columns.status, columns.deleted] },
  // → KEY `idx_status_deleted` (`status`, `deleted`)
]

复合唯一

indexes: [
  { columns: [columns.user_id, columns.order_no], unique: true },
  // → UNIQUE KEY `uk_user_id_order_no` (`user_id`, `order_no`)
]

命名规则

| 来源 | 自动命名规则 | |---|---| | 列级 index: true | idx_{column} | | 列级 unique: true | uk_{column} | | 复合索引(未提供 name) | idx_{col1}_{col2}_... | | 复合唯一(未提供 name) | uk_{col1}_{col2}_... |

如需自定义索引名,设置 IndexDef.name

{ columns: [columns.status, columns.deleted], name: 'idx_order_status' }

列定义完整参数

整数类型(INT / TINYINT / SMALLINT / BIGINT)

| 参数 | 类型 | 说明 | |---|---|---| | type | IntMysqlType | 必需 | | autoIncrement | boolean | 可选 | | primaryKey | boolean | 可选(简写,也可在表级声明) | | index | boolean | 可选,生成单列索引 | | unique | boolean | 可选,生成唯一索引 | | default | number | 可选 | | comment | string | 可选 | | enumRef | EnumShape | 可选,代码级枚举引用 | | deleted | boolean | 可选,标识为软删除字段 |

小数类型(DECIMAL)

| 参数 | 类型 | 说明 | |---|---|---| | type | 'DECIMAL' | 必需 | | precision | number | 必需 | | scale | number | 必需 | | default | string | 可选(如 '0.00') | | comment | string | 可选 | | enumRef | EnumShape | 可选 |

字符串类型(VARCHAR / CHAR)

| 参数 | 类型 | 说明 | |---|---|---| | type | StringMysqlType | 必需 | | length | number | VARCHAR 必需,CHAR 必需 | | primaryKey | boolean | 可选 | | index | boolean | 可选 | | unique | boolean | 可选 | | default | string | 可选 | | comment | string | 可选 | | enumRef | EnumShape | 可选 | | deleted | boolean | 可选 |

文本类型(TEXT)

| 参数 | 类型 | 说明 | |---|---|---| | type | 'TEXT' | 必需 | | comment | string | 可选 |

日期时间类型(DATETIME / TIMESTAMP)

| 参数 | 类型 | 说明 | |---|---|---| | type | DateTimeMysqlType | 必需 | | default | 'CURRENT_TIMESTAMP' | 可选 | | comment | string | 可选 |

日期类型(DATE)

| 参数 | 类型 | 说明 | |---|---|---| | type | 'DATE' | 必需 | | comment | string | 可选 |

DDL 生成

generateDdl(tables: TableSchema[]) 输出 SQL:

  • 每个表生成 CREATE TABLE IF NOT EXISTS
  • 默认 ENGINE=InnoDB, CHARSET=utf8mb4
  • 按表定义顺序输出,以 -- 表注释 的 SQL 注释分隔
  • 外键自动推导引用表/列的标识

编译期校验

  • 列定义:按 type 分支锁定合法参数(如 VARCHAR 不能设 autoIncrement)
  • 列引用:primaryKey / indexes.columns / foreignKeys.columns 只能用 ColumnRef 对象,不能字符串
  • 外键引用:ref 必须是 ColumnRef 或 () => ColumnRef
  • 可选导出:scanTables 对缺少 columns 导出的文件输出警告并跳过

枚举生成(generateEnums)

从 schema defineEnum() 定义自动生成 TypeScript enum + label Record 文件。

使用方式

CLI(与 DDL 生成同一条命令):

# 生成 DDL SQL
ts-mysql-ddl --src ./schema --dest ./001_init.sql

# 生成 TS 枚举文件
ts-mysql-ddl generate-enums --src ./schema --dest ./enums

API

import { generateEnums } from 'ts-mysql-ddl';

const files = await generateEnums({
  schemaDir: './schema',
  outDir: './enums',
});
// → ['_common', 'coupon', 'merchant', ...]

输出结构

每个 *.table.ts 文件对应一个输出文件,文件名等于表名:

enums/
├── _common.ts       # _common.ts 中顶层 export 的 defineEnum
├── coupon.ts        # coupon.table.ts 的 enums 对象中定义的枚举
├── merchant.ts
├── pay.ts
└── ...

_common.ts 处理

如果 schema 目录中存在 _common.ts,其顶层 export const Xxx = defineEnum(...) 定义会被:

  1. 生成到 _common.ts 输出文件
  2. 自动去重:引用 _common 枚举的 table 文件在生成的输出中用 export { Xxx } from './_common' 替代重复生成

示例

输入 _common.ts

export const AcquiringTypeEnum = defineEnum('AcquiringType', {
  WECHAT: { value: 'wechat', label: '微信' },
  UNIONPAY: { value: 'unionpay', label: '银联商务' },
});

输入 coupon.table.ts

import { AcquiringTypeEnum } from './_common';

export const enums = {
  CouponStatus: defineEnum('CouponStatus', {
    PENDING: { value: 'pending', label: '待审核' },
    LISTED: { value: 'listed', label: '已上架' },
  }),
  AcquiringType: AcquiringTypeEnum,  // 来自 _common
};

生成 enums/_common.ts

export enum AcquiringType {
  WECHAT = 'wechat',
  UNIONPAY = 'unionpay',
}
export const ACQUIRING_TYPE_LABEL: Record<AcquiringType, string> = {
  [AcquiringType.WECHAT]: '微信',
  [AcquiringType.UNIONPAY]: '银联商务',
};

生成 enums/coupon.ts

export enum CouponStatus {
  PENDING = 'pending',
  LISTED = 'listed',
}
export const COUPON_STATUS_LABEL: Record<CouponStatus, string> = {
  [CouponStatus.PENDING]: '待审核',
  [CouponStatus.LISTED]: '已上架',
};

export { AcquiringType, ACQUIRING_TYPE_LABEL } from './_common';

开发指南

# 类型检查
cd ts-mysql-ddl
npm run typecheck

# tsx 运行测试
npx tsx test-scan/verify.ts

提交方式

git add -A && git commit -m "feat: add generateEnums API and CLI subcommand"
git push