ts-mysql-ddl 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,323 @@
1
+ # ts-mysql-ddl — MySQL Schema DSL
2
+
3
+ ## 概述
4
+
5
+ `ts-mysql-ddl` 是一个 TypeScript DSL,用于以纯 TS 代码定义 MySQL 数据表结构,直接生成 DDL SQL。它是数据库结构的唯一事实来源(Single Source of Truth),同时为 mock 数据生成提供类型信息。
6
+
7
+ ## 架构设计
8
+
9
+ | 层 | 文件 | 职责 |
10
+ |---|---|---|
11
+ | 类型系统 | `mysql-type.ts` | MysqlType 枚举 + 类型守卫 |
12
+ | 列定义 | `column-def.ts` | ColumnDef 联合类型(按 type 分支锁参数) |
13
+ | 列引用 | `column-ref.ts` | ColumnRef 类 + `col()` 工厂 |
14
+ | 表定义 | `table-def.ts` | TableSchema / ForeignKey / IndexDef 接口 |
15
+ | 工厂 | `define-table.ts` | `defineTable(name, input)` → TableSchema |
16
+ | DDL 生成 | `generate-ddl.ts` | `generateDdl(tables)` → SQL string |
17
+ | 自动扫描 | `scan-tables.ts` | `scanTables(dir)` 自动发现 `*.table.ts` |
18
+
19
+ ## 核心类型
20
+
21
+ ### MysqlType
22
+
23
+ 枚举定义 MySQL 数据类型家族,每个家族对应一组编译期校验的参数:
24
+
25
+ ```
26
+ INT / TINYINT / SMALLINT / BIGINT → autoIncrement, default(number), enumRef
27
+ DECIMAL → precision, scale, default(string)
28
+ VARCHAR / CHAR → length, default(string), enumRef
29
+ TEXT → (无额外参数)
30
+ DATE → (无额外参数)
31
+ DATETIME / TIMESTAMP → default('CURRENT_TIMESTAMP' only)
32
+ ```
33
+
34
+ 所有类型通用参数:`primaryKey`, `index`, `unique`, `comment`, `deleted`
35
+
36
+ ### ColumnDef 联合类型
37
+
38
+ ```ts
39
+ type ColumnDef = IntColumn | DecimalColumn | StringColumn | TextColumn | DateColumn | DateTimeColumn
40
+ ```
41
+
42
+ 每个接口按 type 分支限制参数,编译期拦截非法组合:
43
+
44
+ - IntColumn: 不能有 `length`, `precision`, `scale`
45
+ - StringColumn: 不能有 `autoIncrement`, `precision`
46
+ - DateTimeColumn: `default` 只能是 `'CURRENT_TIMESTAMP'`
47
+
48
+ ### EnumShape
49
+
50
+ ```ts
51
+ type EnumShape = Record<string, string | number>
52
+ ```
53
+
54
+ 表示代码层面的枚举(非 MySQL ENUM),DDL 中存 int/string。由 `enumRef` 字段引用真实 TS 枚举,供 mock 生成使用。
55
+
56
+ ### ColumnRef
57
+
58
+ ```ts
59
+ class ColumnRef {
60
+ readonly def: ColumnDef; // 列定义
61
+ _table: string = ''; // 所属表名(由 defineTable/registerColumns 设置)
62
+ _column: string = ''; // 列名(由 defineTable/registerColumns 设置)
63
+ toString(): string // 返回 _column,未初始化时返回 '?'
64
+ }
65
+ ```
66
+
67
+ `_table` / `_column` 是 identity 字段,由以下时机回填:
68
+ - `defineTable()` 调用时
69
+ - `scanTables()` 的 Phase 1 阶段
70
+
71
+ **关键约束**:ColumnRef 创建时 identity 为空。两阶段初始化确保在所有 FK 引用解析前,所有列的 identity 已设置完成。
72
+
73
+ ## 使用方式
74
+
75
+ ### 方式一:手动编排(适用于少量表)
76
+
77
+ ```ts
78
+ import { MysqlType, col, defineTable, generateDdl } from '@fastify-api/ts-mysql-ddl';
79
+
80
+ const merchantCols = {
81
+ id: col({ type: MysqlType.VARCHAR, length: 12, primaryKey: true }),
82
+ name: col({ type: MysqlType.VARCHAR, length: 100 }),
83
+ };
84
+ const merchant = defineTable('merchant', { columns: merchantCols, comment: 'Merchant' });
85
+
86
+ const orderCols = {
87
+ id: col({ type: MysqlType.INT, autoIncrement: true }),
88
+ mer_id: col({ type: MysqlType.VARCHAR, length: 20 }),
89
+ status: col({ type: MysqlType.VARCHAR, length: 20, enumRef: OrderStatus }),
90
+ };
91
+ const order = defineTable('order', {
92
+ columns: orderCols,
93
+ indexes: [{ columns: [orderCols.status, orderCols.deleted] }],
94
+ foreignKeys: {
95
+ fk_order_merchant: { columns: [orderCols.mer_id], ref: merchant.cols.id, onDelete: 'CASCADE' },
96
+ },
97
+ });
98
+
99
+ const sql = generateDdl([merchant, order]);
100
+ ```
101
+
102
+ ### 方式二:自动扫描(推荐)
103
+
104
+ 按文件命名约定组织 `*.table.ts` 文件:
105
+
106
+ ```
107
+ schema/
108
+ ├── merchant.table.ts
109
+ ├── order.table.ts
110
+ ├── coupon.table.ts
111
+ └── ...
112
+ ```
113
+
114
+ 每个文件只需导出数据,不调 `defineTable`:
115
+
116
+ ```ts
117
+ // schema/order.table.ts
118
+ import { col, MysqlType } from '@fastify-api/ts-mysql-ddl';
119
+ import { columns as merchant } from './merchant.table';
120
+
121
+ export const columns = { ... }; // Record<string, ColumnRef> 必需
122
+ export const indexes = [...]; // IndexDef[] 可选
123
+ export const foreignKeys = { ... }; // Record<string, ForeignKey> 可选
124
+ export const primaryKey = ...; // ColumnRef | ColumnRef[] 可选
125
+ export const comment = '...'; // string 可选
126
+ ```
127
+
128
+ 扫描:
129
+
130
+ ```ts
131
+ const tables = await scanTables('./schema');
132
+ const sql = generateDdl(tables);
133
+ ```
134
+
135
+ 表名从文件名推导:`order.table.ts` → `order`。
136
+
137
+ `scanTables()` 使用两阶段初始化:
138
+ 1. **Phase 1**:遍历所有模块,逐列回填 `_table` / `_column`(此时所有 ColumnRef 获得 identity)
139
+ 2. **Phase 2**:为每个模块调 `defineTable()` 构建完整 TableSchema
140
+
141
+ 因此 FK 引用(`ref: merchant.id`)无论文件加载顺序如何,都能正确解析。
142
+
143
+ ## 外键引用模式
144
+
145
+ ### 直接引用(不同表)
146
+
147
+ ```ts
148
+ import { columns as merchant } from './merchant.table';
149
+
150
+ export const foreignKeys = {
151
+ fk_order_merchant: {
152
+ columns: [columns.mer_id],
153
+ ref: merchant.id, // ColumnRef 直接引用
154
+ onDelete: 'CASCADE',
155
+ },
156
+ };
157
+ ```
158
+
159
+ ### 懒引用(自引用/循环引用)
160
+
161
+ ```ts
162
+ export const foreignKeys = {
163
+ fk_category_parent: {
164
+ columns: [columns.parent_id],
165
+ ref: () => columns.id, // 延迟求值,避免循环依赖
166
+ onDelete: 'CASCADE',
167
+ },
168
+ };
169
+ ```
170
+
171
+ 当 `ref` 是 `() => ColumnRef` 时,DDL 生成阶段再求值。
172
+
173
+ ### 复合外键
174
+
175
+ ```ts
176
+ export const foreignKeys = {
177
+ fk_order_detail_product: {
178
+ columns: [columns.sku_id, columns.tenant_id],
179
+ ref: product.columns, // 注意:此处需要确认是否支持复合 FK → 见下方约束
180
+ },
181
+ };
182
+ ```
183
+
184
+ **当前约束**:复合 FK 的 `ref` 语法暂未定型,当前仅支持单列 FK。
185
+
186
+ ## 索引与约束
187
+
188
+ ### 单列索引(在列定义中)
189
+
190
+ ```ts
191
+ col({ type: MysqlType.VARCHAR, length: 20, index: true })
192
+ // → KEY `idx_status` (`status`)
193
+ ```
194
+
195
+ ### 单列唯一(在列定义中)
196
+
197
+ ```ts
198
+ col({ type: MysqlType.VARCHAR, length: 50, unique: true })
199
+ // → UNIQUE KEY `uk_name` (`name`)
200
+ ```
201
+
202
+ ### 复合索引
203
+
204
+ ```ts
205
+ indexes: [
206
+ { columns: [columns.status, columns.deleted] },
207
+ // → KEY `idx_status_deleted` (`status`, `deleted`)
208
+ ]
209
+ ```
210
+
211
+ ### 复合唯一
212
+
213
+ ```ts
214
+ indexes: [
215
+ { columns: [columns.user_id, columns.order_no], unique: true },
216
+ // → UNIQUE KEY `uk_user_id_order_no` (`user_id`, `order_no`)
217
+ ]
218
+ ```
219
+
220
+ ### 命名规则
221
+
222
+ | 来源 | 自动命名规则 |
223
+ |---|---|
224
+ | 列级 `index: true` | `idx_{column}` |
225
+ | 列级 `unique: true` | `uk_{column}` |
226
+ | 复合索引(未提供 name) | `idx_{col1}_{col2}_...` |
227
+ | 复合唯一(未提供 name) | `uk_{col1}_{col2}_...` |
228
+
229
+ 如需自定义索引名,设置 `IndexDef.name`:
230
+
231
+ ```ts
232
+ { columns: [columns.status, columns.deleted], name: 'idx_order_status' }
233
+ ```
234
+
235
+ ## 列定义完整参数
236
+
237
+ ### 整数类型(INT / TINYINT / SMALLINT / BIGINT)
238
+
239
+ | 参数 | 类型 | 说明 |
240
+ |---|---|---|
241
+ | type | IntMysqlType | 必需 |
242
+ | autoIncrement | boolean | 可选 |
243
+ | primaryKey | boolean | 可选(简写,也可在表级声明) |
244
+ | index | boolean | 可选,生成单列索引 |
245
+ | unique | boolean | 可选,生成唯一索引 |
246
+ | default | number | 可选 |
247
+ | comment | string | 可选 |
248
+ | enumRef | EnumShape | 可选,代码级枚举引用 |
249
+ | deleted | boolean | 可选,标识为软删除字段 |
250
+
251
+ ### 小数类型(DECIMAL)
252
+
253
+ | 参数 | 类型 | 说明 |
254
+ |---|---|---|
255
+ | type | 'DECIMAL' | 必需 |
256
+ | precision | number | 必需 |
257
+ | scale | number | 必需 |
258
+ | default | string | 可选(如 `'0.00'`) |
259
+ | comment | string | 可选 |
260
+ | enumRef | EnumShape | 可选 |
261
+
262
+ ### 字符串类型(VARCHAR / CHAR)
263
+
264
+ | 参数 | 类型 | 说明 |
265
+ |---|---|---|
266
+ | type | StringMysqlType | 必需 |
267
+ | length | number | VARCHAR 必需,CHAR 必需 |
268
+ | primaryKey | boolean | 可选 |
269
+ | index | boolean | 可选 |
270
+ | unique | boolean | 可选 |
271
+ | default | string | 可选 |
272
+ | comment | string | 可选 |
273
+ | enumRef | EnumShape | 可选 |
274
+ | deleted | boolean | 可选 |
275
+
276
+ ### 文本类型(TEXT)
277
+
278
+ | 参数 | 类型 | 说明 |
279
+ |---|---|---|
280
+ | type | 'TEXT' | 必需 |
281
+ | comment | string | 可选 |
282
+
283
+ ### 日期时间类型(DATETIME / TIMESTAMP)
284
+
285
+ | 参数 | 类型 | 说明 |
286
+ |---|---|---|
287
+ | type | DateTimeMysqlType | 必需 |
288
+ | default | 'CURRENT_TIMESTAMP' | 可选 |
289
+ | comment | string | 可选 |
290
+
291
+ ### 日期类型(DATE)
292
+
293
+ | 参数 | 类型 | 说明 |
294
+ |---|---|---|
295
+ | type | 'DATE' | 必需 |
296
+ | comment | string | 可选 |
297
+
298
+ ## DDL 生成
299
+
300
+ `generateDdl(tables: TableSchema[])` 输出 SQL:
301
+
302
+ - 每个表生成 `CREATE TABLE IF NOT EXISTS`
303
+ - 默认 ENGINE=InnoDB, CHARSET=utf8mb4
304
+ - 按表定义顺序输出,以 `-- 表注释` 的 SQL 注释分隔
305
+ - 外键自动推导引用表/列的标识
306
+
307
+ ## 编译期校验
308
+
309
+ - 列定义:按 type 分支锁定合法参数(如 VARCHAR 不能设 autoIncrement)
310
+ - 列引用:`primaryKey` / `indexes.columns` / `foreignKeys.columns` 只能用 ColumnRef 对象,不能字符串
311
+ - 外键引用:`ref` 必须是 ColumnRef 或 `() => ColumnRef`
312
+ - 可选导出:`scanTables` 对缺少 `columns` 导出的文件输出警告并跳过
313
+
314
+ ## 开发指南
315
+
316
+ ```bash
317
+ # 类型检查
318
+ cd ts-mysql-ddl
319
+ npm run typecheck
320
+
321
+ # tsx 运行测试
322
+ npx tsx test-scan/verify.ts
323
+ ```
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI wrapper: delegates to the TypeScript entry via tsx.
4
+ * tsx is required as a peer dependency.
5
+ */
6
+ const { spawnSync } = require('node:child_process');
7
+ const { resolve } = require('node:path');
8
+
9
+ const cli = resolve(__dirname, '..', 'src', 'cli.ts');
10
+
11
+ const result = spawnSync('npx', ['tsx', cli, ...process.argv.slice(2)], {
12
+ stdio: 'inherit',
13
+ shell: true,
14
+ });
15
+
16
+ process.exit(result.status ?? 1);
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Build schema: scan *.table.ts directory → generate DDL → optionally write to file.
3
+ *
4
+ * Usage:
5
+ * const sql = await buildSchema({ schemaDir: './schema' });
6
+ * await buildSchema({ schemaDir: './schema', outFile: './001_init.sql' });
7
+ */
8
+ export interface BuildSchemaOptions {
9
+ /** Directory to scan for *.table.ts files */
10
+ schemaDir: string;
11
+ /** Optional output file path — writes generated SQL if provided */
12
+ outFile?: string;
13
+ }
14
+ /**
15
+ * Scan schema directory, resolve FK dependency order, generate DDL.
16
+ * Optionally writes result to `outFile`.
17
+ * Returns the generated SQL string.
18
+ */
19
+ export declare function buildSchema(options: BuildSchemaOptions): Promise<string>;
package/dist/build.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Build schema: scan *.table.ts directory → generate DDL → optionally write to file.
3
+ *
4
+ * Usage:
5
+ * const sql = await buildSchema({ schemaDir: './schema' });
6
+ * await buildSchema({ schemaDir: './schema', outFile: './001_init.sql' });
7
+ */
8
+ import { writeFileSync } from 'node:fs';
9
+ import { scanTables } from './scan-tables.js';
10
+ import { generateDdl } from './generate-ddl.js';
11
+ /** Topological sort by FK dependency — referenced tables come first */
12
+ function sortByDependency(tables) {
13
+ const byName = new Map(tables.map(t => [t._schema.name, t]));
14
+ const visited = new Set();
15
+ const sorted = [];
16
+ function visit(name) {
17
+ if (visited.has(name))
18
+ return;
19
+ visited.add(name);
20
+ const table = byName.get(name);
21
+ if (!table)
22
+ return;
23
+ // Visit FK refs first
24
+ const fks = table._schema.foreignKeys;
25
+ if (fks) {
26
+ for (const fk of Object.values(fks)) {
27
+ const ref = typeof fk.ref === 'function' ? fk.ref() : fk.ref;
28
+ if (ref._table && ref._table !== name) {
29
+ visit(ref._table);
30
+ }
31
+ }
32
+ }
33
+ sorted.push(table);
34
+ }
35
+ for (const t of tables)
36
+ visit(t._schema.name);
37
+ return sorted;
38
+ }
39
+ /**
40
+ * Scan schema directory, resolve FK dependency order, generate DDL.
41
+ * Optionally writes result to `outFile`.
42
+ * Returns the generated SQL string.
43
+ */
44
+ export async function buildSchema(options) {
45
+ const { schemaDir, outFile } = options;
46
+ const tables = sortByDependency(await scanTables(schemaDir));
47
+ const sql = generateDdl(tables);
48
+ if (outFile) {
49
+ writeFileSync(outFile, sql + '\n', 'utf-8');
50
+ console.log(`Generated ${tables.length} tables → ${outFile}`);
51
+ }
52
+ return sql;
53
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CLI for ts-mysql-ddl: scan *.table.ts → generate DDL → write SQL file.
4
+ *
5
+ * Usage:
6
+ * ts-mysql-ddl --src ./schema --dest ./001_init.sql
7
+ */
8
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CLI for ts-mysql-ddl: scan *.table.ts → generate DDL → write SQL file.
4
+ *
5
+ * Usage:
6
+ * ts-mysql-ddl --src ./schema --dest ./001_init.sql
7
+ */
8
+ import { buildSchema } from './build.js';
9
+ function parseArgs() {
10
+ const args = process.argv.slice(2);
11
+ let src = '';
12
+ let dest = '';
13
+ for (let i = 0; i < args.length; i++) {
14
+ const arg = args[i];
15
+ if (arg === '--src' && i + 1 < args.length) {
16
+ src = args[++i];
17
+ }
18
+ if (arg === '--dest' && i + 1 < args.length) {
19
+ dest = args[++i];
20
+ }
21
+ }
22
+ if (!src)
23
+ throw new Error('Missing required flag: --src <schema_dir>');
24
+ if (!dest)
25
+ throw new Error('Missing required flag: --dest <output_file>');
26
+ return { src, dest };
27
+ }
28
+ const { src, dest } = parseArgs();
29
+ await buildSchema({ schemaDir: src, outFile: dest });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Column definition — discriminated union by MysqlType.
3
+ *
4
+ * Each type family only allows the parameters that make sense for that type.
5
+ * Compile-time errors if you try to use `length` on INT or `autoIncrement` on VARCHAR.
6
+ */
7
+ interface BaseColumn {
8
+ nullable?: boolean;
9
+ comment?: string;
10
+ }
11
+ /** Any TypeScript enum value — string or numeric */
12
+ export type EnumShape = Record<string, string | number>;
13
+ export interface IntColumn extends BaseColumn {
14
+ type: typeof import('./mysql-type').MysqlType.TINYINT | typeof import('./mysql-type').MysqlType.SMALLINT | typeof import('./mysql-type').MysqlType.INT | typeof import('./mysql-type').MysqlType.BIGINT;
15
+ autoIncrement?: true;
16
+ primaryKey?: true;
17
+ default?: number;
18
+ enumRef?: EnumShape;
19
+ index?: boolean;
20
+ unique?: boolean;
21
+ }
22
+ export interface DecimalColumn extends BaseColumn {
23
+ type: typeof import('./mysql-type').MysqlType.DECIMAL;
24
+ precision: number;
25
+ scale: number;
26
+ default?: string;
27
+ }
28
+ export interface StringColumn extends BaseColumn {
29
+ type: typeof import('./mysql-type').MysqlType.CHAR | typeof import('./mysql-type').MysqlType.VARCHAR;
30
+ length: number;
31
+ enumRef?: EnumShape;
32
+ primaryKey?: true;
33
+ default?: string;
34
+ index?: boolean;
35
+ unique?: boolean;
36
+ }
37
+ export interface TextColumn extends BaseColumn {
38
+ type: typeof import('./mysql-type').MysqlType.TEXT;
39
+ }
40
+ export interface DateColumn extends BaseColumn {
41
+ type: typeof import('./mysql-type').MysqlType.DATE;
42
+ default?: 'CURRENT_TIMESTAMP';
43
+ }
44
+ export interface DateTimeColumn extends BaseColumn {
45
+ type: typeof import('./mysql-type').MysqlType.DATETIME | typeof import('./mysql-type').MysqlType.TIMESTAMP;
46
+ default?: 'CURRENT_TIMESTAMP';
47
+ }
48
+ export type ColumnDef = IntColumn | DecimalColumn | StringColumn | TextColumn | DateColumn | DateTimeColumn;
49
+ /** Get the SQL type name with parameters, e.g. "VARCHAR(100)" or "DECIMAL(10,2)" */
50
+ export declare function columnSqlType(def: ColumnDef): string;
51
+ export {};
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Column definition — discriminated union by MysqlType.
3
+ *
4
+ * Each type family only allows the parameters that make sense for that type.
5
+ * Compile-time errors if you try to use `length` on INT or `autoIncrement` on VARCHAR.
6
+ */
7
+ // ── Helpers ──
8
+ function isIntType(t) {
9
+ return t === 'TINYINT' || t === 'SMALLINT' || t === 'INT' || t === 'BIGINT';
10
+ }
11
+ function isStringType(t) {
12
+ return t === 'CHAR' || t === 'VARCHAR';
13
+ }
14
+ /** Get the SQL type name with parameters, e.g. "VARCHAR(100)" or "DECIMAL(10,2)" */
15
+ export function columnSqlType(def) {
16
+ const t = def.type;
17
+ if (isIntType(t))
18
+ return t;
19
+ if (t === 'DECIMAL') {
20
+ const d = def;
21
+ return `${t}(${d.precision},${d.scale})`;
22
+ }
23
+ if (isStringType(t)) {
24
+ const d = def;
25
+ return `${t}(${d.length})`;
26
+ }
27
+ // TEXT, DATE, DATETIME, TIMESTAMP — no params
28
+ return t;
29
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * ColumnRef — a column definition that also carries its table/column identity.
3
+ *
4
+ * Created via `col()` factory, identity is set by `defineTable()`.
5
+ * `toString()` enables use in template literals:
6
+ * name: `idx_${columns.status}`
7
+ */
8
+ import type { ColumnDef } from './column-def';
9
+ export declare class ColumnRef {
10
+ /** Underlying column definition (immutable) */
11
+ readonly def: ColumnDef;
12
+ /** Table name — set by defineTable() */
13
+ _table: string;
14
+ /** Column name — set by defineTable() */
15
+ _column: string;
16
+ constructor(def: ColumnDef);
17
+ /** For template literals: `idx_${col}` → "idx_status" */
18
+ toString(): string;
19
+ }
20
+ /**
21
+ * Factory to create a ColumnRef from a ColumnDef.
22
+ *
23
+ * Usage:
24
+ * const columns = {
25
+ * id: col({ type: MysqlType.INT, autoIncrement: true }),
26
+ * name: col({ type: MysqlType.VARCHAR, length: 50 }),
27
+ * status: col({ type: MysqlType.VARCHAR, length: 20 }),
28
+ * };
29
+ */
30
+ export declare function col(def: ColumnDef): ColumnRef;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * ColumnRef — a column definition that also carries its table/column identity.
3
+ *
4
+ * Created via `col()` factory, identity is set by `defineTable()`.
5
+ * `toString()` enables use in template literals:
6
+ * name: `idx_${columns.status}`
7
+ */
8
+ export class ColumnRef {
9
+ /** Underlying column definition (immutable) */
10
+ def;
11
+ /** Table name — set by defineTable() */
12
+ _table = '';
13
+ /** Column name — set by defineTable() */
14
+ _column = '';
15
+ constructor(def) {
16
+ this.def = def;
17
+ }
18
+ /** For template literals: `idx_${col}` → "idx_status" */
19
+ toString() {
20
+ return this._column || '?';
21
+ }
22
+ }
23
+ /**
24
+ * Factory to create a ColumnRef from a ColumnDef.
25
+ *
26
+ * Usage:
27
+ * const columns = {
28
+ * id: col({ type: MysqlType.INT, autoIncrement: true }),
29
+ * name: col({ type: MysqlType.VARCHAR, length: 50 }),
30
+ * status: col({ type: MysqlType.VARCHAR, length: 20 }),
31
+ * };
32
+ */
33
+ export function col(def) {
34
+ return new ColumnRef(def);
35
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Factory that creates a TableSchema from column refs and metadata.
3
+ *
4
+ * Input columns are ColumnRef objects (created via `col()`).
5
+ * defineTable sets their _table and _column identity.
6
+ *
7
+ * Usage:
8
+ * const columns = {
9
+ * id: col({ type: MysqlType.VARCHAR, length: 12, primaryKey: true }),
10
+ * name: col({ type: MysqlType.VARCHAR, length: 100 }),
11
+ * };
12
+ *
13
+ * export const merchant = defineTable('merchant', { columns });
14
+ *
15
+ * export const coupon = defineTable('coupon', {
16
+ * columns: {
17
+ * mer_id: col({ type: MysqlType.VARCHAR, length: 20 }),
18
+ * },
19
+ * foreignKeys: {
20
+ * fk_coupon_merchant: {
21
+ * columns: [columns.mer_id],
22
+ * ref: merchant.columns.id,
23
+ * },
24
+ * },
25
+ * });
26
+ */
27
+ import { ColumnRef } from './column-ref';
28
+ import type { TableSchema, ForeignKey, IndexDef } from './table-def';
29
+ export interface DefineTableInput {
30
+ columns: Record<string, ColumnRef>;
31
+ primaryKey?: ColumnRef | ColumnRef[];
32
+ foreignKeys?: Record<string, ForeignKey>;
33
+ indexes?: IndexDef[];
34
+ comment?: string;
35
+ }
36
+ export declare function defineTable(name: string, input: DefineTableInput): TableSchema;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Factory that creates a TableSchema from column refs and metadata.
3
+ *
4
+ * Input columns are ColumnRef objects (created via `col()`).
5
+ * defineTable sets their _table and _column identity.
6
+ *
7
+ * Usage:
8
+ * const columns = {
9
+ * id: col({ type: MysqlType.VARCHAR, length: 12, primaryKey: true }),
10
+ * name: col({ type: MysqlType.VARCHAR, length: 100 }),
11
+ * };
12
+ *
13
+ * export const merchant = defineTable('merchant', { columns });
14
+ *
15
+ * export const coupon = defineTable('coupon', {
16
+ * columns: {
17
+ * mer_id: col({ type: MysqlType.VARCHAR, length: 20 }),
18
+ * },
19
+ * foreignKeys: {
20
+ * fk_coupon_merchant: {
21
+ * columns: [columns.mer_id],
22
+ * ref: merchant.columns.id,
23
+ * },
24
+ * },
25
+ * });
26
+ */
27
+ export function defineTable(name, input) {
28
+ const colDefs = {};
29
+ const refs = {};
30
+ for (const [colName, colRef] of Object.entries(input.columns)) {
31
+ // Set column identity on the ref
32
+ colRef._table = name;
33
+ colRef._column = colName;
34
+ colDefs[colName] = colRef.def;
35
+ refs[colName] = colRef;
36
+ }
37
+ const internal = {
38
+ name,
39
+ columns: colDefs,
40
+ };
41
+ if (input.primaryKey)
42
+ internal.primaryKey = input.primaryKey;
43
+ if (input.foreignKeys)
44
+ internal.foreignKeys = input.foreignKeys;
45
+ if (input.indexes)
46
+ internal.indexes = input.indexes;
47
+ if (input.comment)
48
+ internal.comment = input.comment;
49
+ return { _schema: internal, columns: refs };
50
+ }