mcpField
K
kysely-schema

kysely-schema

github.com/jybill开发044 次安装6 天前更新
代码仓库

关于 kysely-schema

kysely-schema 是将 MySQL CREATE TABLE 语句转换为 Kysely 表类型的工作流 skill,并可按需补充相关 service 方法。它遵循项目约定处理字段类型、命名、文件位置和验证。

主要功能

  1. 01将 MySQL DDL 映射为 Kysely 类型
  2. 02处理可空字段与默认值
  3. 03生成 Selectable、Insertable 和 Updateable 类型
  4. 04按职责放置查询与变更方法
  5. 05检查字段映射和项目约定

适用场景

  1. 01为新数据库表添加 Kysely 类型
  2. 02实现所需的插入或更新方法
  3. 03按现有 service 结构添加查询

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jybill/xqv-skills --skill kysely-schema

安装后在 Claude Code 中输入/kysely-schema确认已加载。

详细介绍

来自项目 README

数据库表结构到代码生成工作流

这个 skill 用于把 MySQL CREATE TABLE 语句转换成本仓库需要的 Kysely 表类型,并在用户需要时继续补齐对应的插入、更新或查询方法。

适用场景

当拿到一份 CREATE TABLE 语句,需要在本仓库里补齐表结构类型,或者继续补齐对应的插入/更新方法时使用。

如果用户只要求补表类型,完成 src/types/database.d.ts 里的 DB 接口、显式 XxxDB、Selectable、Insertable、Updateable 后就停,不要顺手新增 service 方法。

如果用户继续要求新增插入、更新、upsert 或查询方法,再进入对应 *-modify.ts 或 *-query.ts。

总流程

读取 DDL
   │
   ▼
识别表名、字段名、类型、NULL、DEFAULT、自增、注释
   │
   ▼
转换成 camelCase 字段和 Kysely 字段类型
   │
   ▼
更新 src/types/database.d.ts 的 DB 接口和 XxxDB 类型
   │
   ├─ 用户只要表类型 ──► 停止
   │
   └─ 用户还要 service 方法
          │
          ▼
     选择对应领域的 *-modify.ts 或 *-query.ts
          │
          ▼
     导入 Insertable / Updateable / Selectable 类型
          │
          ▼
     生成插入、更新、upsert 或查询方法
          │
          ▼
     检查字段映射、JSDoc、import 合并和 SQL 名称

必守规则

| 规则 | 正确做法 | 常见错误 | | -------------------- | ------------------------------------------------ | ---------------------------- | ------------------- | | 新表 DB shape | 直接写显式 XxxDB | 套 ToGenerated 偷懒 | | 默认值字段 | 写 Generated<type> | 同时写成 type | null | | 允许 NULL 且无默认值 | 写 type | null | 忽略 NULL,写成必填 | | 自增主键 | 写 Generated<number> | 写成普通 number | | 字段命名 | TS 字段用 camelCase,SQL 字段和表名用 snake_case | camelCase 和 snake_case 混用 | | 类型位置 | 表类型只放 src/types/database.d.ts | 在 service 里临时拼类型 | | service 位置 | 增删改放 *-modify.ts,查询放 *-query.ts | 把变更和查询混到一个入口里 | | 注释 | 导出类型、导出函数、class static 方法补 JSDoc | 生成方法没有 JSDoc | | 导入 | 同一路径的 import / import type 合并 | 从同一路径拆多条导入 |

DDL 到 Kysely 类型映射

字段判断优先看 AUTO_INCREMENT、DEFAULT、NULL,不要只根据 MySQL 类型猜 TS 类型。

| DDL 特征 | Kysely 字段类型 | 说明 | | --------------------------------- | ------------------------------------ | --------------------------------- | --------------------- | | AUTO_INCREMENT 主键 | Generated<number> | 自增字段由数据库生成 | | 任意 DEFAULT ... 字段 | Generated<type> | 有默认值就不再标 | null | | NOT NULL 且无默认值 | type | 调用方插入时必须传 | | NULL 且无默认值 | type | null | 只有这种情况才标 null | | datetime / timestamp 有默认值 | Generated<Date> 或项目既有时间类型 | 跟随 database.d.ts 里同类表写法 | | decimal / numeric | string | 保留小数精度,不直接写 number | | tinyint / int / bigint | 跟随项目同类字段 | 业务状态码不要顺手新建魔法数字 | | varchar / text | string | 注释可写字段业务含义 | | json | 跟随项目既有 JSON 字段类型 | 不用 any 或 unknown 糊过去 |

第一步 添加表类型到 DB 接口

在 src/types/database.d.ts 的 DB 类型接口中添加新表,并把它放到对应业务分组的末尾。

export type DB = {
  // monitor
  m_monitor_evaluation: MonitorEvaluationDB;
  m_monitor_evaluation_list: MonitorEvaluationListDB;
  m_monitor_evaluation_dimension: MonitorEvaluationDimensionDB;
};

第二步 创建表结构类型

表结构类型直接写成显式 Kysely 风格,不要再包 ToGenerated。字段判断按上面的映射表处理。

一个完整示例如下。

/**
 * m_monitor_evaluation_list 课堂评价明细
 */
export type MonitorEvaluationListDB = {
  id: Generated<number>;
  monitorId: number; // 三率主记录ID
  dimensionId: number; // 维度ID
  score: string; // 课堂评价分数
  avgScore: string | null; // 平均分数
  type: Generated<number>; // 0 接口获取 1自己算
};
export type MonitorEvaluationList = Selectable<MonitorEvaluationListDB>;
export type InsertMonitorEvaluationListDB = Insertable<MonitorEvaluationListDB>;
export type UpdateMonitorEvaluationListDB = Updateable<MonitorEvaluationListDB>;

如果当前任务只要求补类型,到这里就可以停,不需要硬补 modify 方法。

第三步 选择 service 落点

需要补 service 方法时,先按职责选择文件,不要把查询和变更混在一起。

需求文件位置方法倾向
批量插入、插入更新、按 id 更新、删除对应领域的 *-modify.tsdbInsert<Table>、dbUpdate<Table>ByIds、dbDelete<Table>ByIds
列表、详情、聚合、统计、条件查询对应领域的 *-query.tsdbGet<Table>...、dbList<Table>...
多步骤复杂业务编排对应领域 logic/只有流程足够复杂才拆 class
只是一层很薄的透传留在 controller 或现有 service不要为了形式新增薄 logic

第四步 在 modify 文件里导入新类型

在对应的 src/service/**/modify.ts 文件里,导入这个表对应的新增类型。同一路径的 type import 要合并成一条。

import type { InsertMonitorEvaluationListDB, UpdateMonitorEvaluationListDB } from "#types/database.d.ts";

第五步 创建插入或更新方法

方法放在对应业务类里,写法跟现有项目保持一致。

字段映射要同时检查 updateField、values、MySQL VALUES(...) 和 PostgreSQL 兼容分支 EXCLUDED...。字段名在 TS 对象里用 camelCase,raw SQL 字符串里用真实 snake_case。

/**
 * 插入或更新课堂评价明细
 */
static async dbInsertMonitorEvaluationLists<T extends InsertMonitorEvaluationListDB>(
  params: DBInsertParams<T>,
) {
  const { dataList, tx = db } = params;
  if (isFalsy(dataList)) return [];

  const first = dataList[0];
  const updateField = {
    id: isValidValue(first.id),
    monitorId: isValidValue(first.monitorId),
    dimensionId: isValidValue(first.dimensionId),
    score: isValidValue(first.score),
    avgScore: isValidValue(first.avgScore),
    type: isValidValue(first.type),
  };

  const values = dataList.map((item) => ({
    id: item.id,
    monitorId: item.monitorId,
    dimensionId: item.dimensionId,
    score: item.score,
    avgScore: item.avgScore,
    type: item.type,
  }));

  const result = await DBUtil.upsert(
    tx.insertInto("m_monitor_evaluation_list").values(values),
    {
      id: updateField.id ? sql<number>`VALUES(id)` : undefined,
      monitorId: updateField.monitorId ? sql<number>`VALUES(monitor_id)` : undefined,
      dimensionId: updateField.dimensionId ? sql<number>`VALUES(dimension_id)` : undefined,
      score: updateField.score ? sql<string>`VALUES(score)` : undefined,
      avgScore: updateField.avgScore ? sql<string | null>`VALUES(avg_score)` : undefined,
      type: updateField.type ? sql<number>`VALUES(type)` : undefined,
    },
    {
      id: updateField.id ? sql.ref<number>("EXCLUDED.id") : undefined,
      monitorId: updateField.monitorId ? sql.ref<number>("EXCLUDED.monitor_id") : undefined,
      dimensionId: updateField.dimensionId ? sql.ref<number>("EXCLUDED.dimension_id") : undefined,
      score: updateField.score ? sql.ref<string>("EXCLUDED.score") : undefined,
      avgScore: updateField.avgScore ? sql.ref<string | null>("EXCLUDED.avg_score") : undefined,
      type: updateField.type ? sql.ref<number>("EXCLUDED.type") : undefined,
    },
  )
    .$call(debugSQL(false))
    .executeTakeFirst();

  return DBUtil.setInsertId(dataList, Number(result.insertId));
}

字段映射检查表

| 检查点 | 要看什么 | | ------------------------------------------ | -------------------------------------------------- | -------------------------------------- | | DB 接口 | 新表名是否放到合适分组,表名是否保持 snake_case | | XxxDB | 每个 DDL 字段是否都存在,camelCase 是否正确 | | Generated<> | 自增字段和 default 字段是否都被覆盖 | | | null | 是否只出现在允许 NULL 且无默认值的字段 | | Selectable / Insertable / Updateable | 三个导出类型是否齐全 | | updateField | 是否覆盖所有允许 upsert 更新的字段 | | values | 是否没有漏字段,没有把 snake_case 写进 TS 对象 key | | VALUES(...) | raw SQL 字段是否使用真实 snake_case | | EXCLUDED... | 字段名是否和真实 SQL 字段一致 | | JSDoc | 导出函数或 static 方法是否解释业务用途 |

常见错误

| 错误 | 修正 | | -------------------------------------------- | ------------------------------------------------ | ---------------------- | | 新表直接套 ToGenerated | 改成显式 XxxDB | | DDL 有 default,字段类型还写成 | null | 改成 Generated<type> | | updateField、values、upsert 参数漏字段 | 对照 DDL 和字段映射检查表补齐 | | camelCase 和 snake_case 混着写 | TS 字段用 camelCase,SQL 字符串用 snake_case | | 导出函数和 static 方法没写 JSDoc | 在定义上方补简短 JSDoc | | 为了状态码顺手新增 enum | 先判断是否跨文件复用;单点简单语义不要过度枚举化 | | 用 any、unknown 或断言绕类型 | 补准确类型或跟随既有 JSON 字段写法 |

验证清单

| 项目 | 通过标准 | | ---------------- | ----------------------------------------------- | ----- | | DB 接口 | 已经加了新表类型 | | 表结构类型 | 使用显式 XxxDB 写法 | | 导出类型 | 已导出 Selectable、Insertable、Updateable | | 默认值 | 有默认值的字段已经用了 Generated<> | | null | default 字段没有误标 | null | | service 类型导入 | 对应文件已导入新类型,且 import 已合并 | | 插入或更新方法 | 字段映射完整,raw SQL 字段名正确 | | 命名 | TS camelCase / SQL snake_case 约定一致 | | 注释 | JSDoc 已补齐 |

评论

支持 Markdown