@chnak/zod-to-markdown 1.0.1 → 1.0.2

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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(cd D:/Date/20260321/zod-to-markdown && npm test)"
5
+ ]
6
+ }
7
+ }
@@ -0,0 +1,18 @@
1
+ | 字段 | 类型 | 描述 |
2
+ |------|------|------|
3
+ | id | String | 公司ID |
4
+ | name | String | 公司名称 |
5
+ | departments[] | name | 部门名称 |
6
+ | departments[] | employees[] | 员工ID |
7
+ | departments[] | employees[] | 姓名 |
8
+ | departments[] | employees[] | 职位 |
9
+ | departments[] | employees[] | 技能 |
10
+ | departments[] | employees[] | 邮箱 |
11
+ | departments[] | employees[] | 电话 |
12
+ | departments[] | location.street | 街道 |
13
+ | departments[] | location.city | 城市 |
14
+ | departments[] | location.country | 国家 |
15
+ | departments[] | location.coordinates.lat | 纬度 |
16
+ | departments[] | location.coordinates.lng | 经度 |
17
+ | foundedYear | Number | 成立年份 |
18
+ | tags[] | Array<String> | 标签 |
@@ -0,0 +1,60 @@
1
+ import { z } from 'zod';
2
+ import { zodSchemaToTable } from '../src/index';
3
+ import * as fs from 'fs';
4
+
5
+ // 多层嵌套 Schema 示例
6
+
7
+ // 第一层:坐标
8
+ const coordinatesSchema = z.object({
9
+ lat: z.number().describe('纬度'),
10
+ lng: z.number().describe('经度'),
11
+ }).describe('地理坐标');
12
+
13
+ // 第二层:地址
14
+ const addressSchema = z.object({
15
+ street: z.string().describe('街道'),
16
+ city: z.string().describe('城市'),
17
+ country: z.string().describe('国家'),
18
+ coordinates: coordinatesSchema.describe('坐标'),
19
+ }).describe('地址');
20
+
21
+ // 爱好
22
+ const hobbySchema = z.object({
23
+ name: z.string().describe('爱好名称'),
24
+ level: z.enum(['beginner', 'intermediate', 'advanced']).describe('等级'),
25
+ equipment: z.array(z.object({
26
+ name: z.string().describe('装备名称'),
27
+ brand: z.string().optional().describe('品牌'),
28
+ })).optional().describe('相关装备'),
29
+ }).describe('爱好');
30
+
31
+ // 主要 Schema
32
+ const companySchema = z.object({
33
+ id: z.string().uuid().describe('公司ID'),
34
+ name: z.string().describe('公司名称'),
35
+ departments: z.array(z.object({
36
+ name: z.string().describe('部门名称'),
37
+ employees: z.array(z.object({
38
+ id: z.string().describe('员工ID'),
39
+ name: z.string().describe('姓名'),
40
+ title: z.string().describe('职位'),
41
+ skills: z.array(z.string()).describe('技能'),
42
+ contact: z.object({
43
+ email: z.string().describe('邮箱'),
44
+ phone: z.string().optional().describe('电话'),
45
+ }).describe('联系方式'),
46
+ })).describe('员工列表'),
47
+ location: addressSchema.describe('部门地址'),
48
+ })).describe('部门列表'),
49
+ foundedYear: z.number().describe('成立年份'),
50
+ tags: z.array(z.string()).describe('标签'),
51
+ }).describe('公司');
52
+
53
+ const markdown = zodSchemaToTable(companySchema);
54
+
55
+ console.log('## Company Schema 文档 (多层级嵌套)\n');
56
+ console.log(markdown);
57
+
58
+ // 保存到文件
59
+ fs.writeFileSync('examples/company-schema-table.md', markdown);
60
+ console.log('\n已保存到 examples/company-schema-table.md');
@@ -0,0 +1,11 @@
1
+ | 字段 | 类型 | 描述 |
2
+ |------|------|------|
3
+ | id | String | 用户ID |
4
+ | name | String | 姓名 |
5
+ | profile.bio | String | 个人简介 |
6
+ | profile.avatar | String | 头像URL |
7
+ | profile.age | Number | 年龄 |
8
+ | settings.theme | Enum(light | dark) | 主题 |
9
+ | settings.language | String | 语言 |
10
+ | settings.notifications.email | Boolean | 邮件通知 |
11
+ | settings.notifications.push | Boolean | 推送通知 |
@@ -0,0 +1,42 @@
1
+ import { z } from 'zod';
2
+ import { zodSchemaToTable } from '../src/index';
3
+ import * as fs from 'fs';
4
+
5
+ // 纯对象嵌套示例
6
+ const coordinatesSchema = z.object({
7
+ lat: z.number().describe('纬度'),
8
+ lng: z.number().describe('经度'),
9
+ });
10
+
11
+ const addressSchema = z.object({
12
+ street: z.string().describe('街道'),
13
+ city: z.string().describe('城市'),
14
+ country: z.string().describe('国家'),
15
+ coordinates: coordinatesSchema.describe('坐标'),
16
+ });
17
+
18
+ const userSchema = z.object({
19
+ id: z.string().uuid().describe('用户ID'),
20
+ name: z.string().describe('姓名'),
21
+ profile: z.object({
22
+ bio: z.string().describe('个人简介'),
23
+ avatar: z.string().describe('头像URL'),
24
+ age: z.number().describe('年龄'),
25
+ }).describe('用户资料'),
26
+ settings: z.object({
27
+ theme: z.enum(['light', 'dark']).describe('主题'),
28
+ language: z.string().describe('语言'),
29
+ notifications: z.object({
30
+ email: z.boolean().describe('邮件通知'),
31
+ push: z.boolean().describe('推送通知'),
32
+ }).describe('通知设置'),
33
+ }).describe('设置'),
34
+ });
35
+
36
+ const markdown = zodSchemaToTable(userSchema);
37
+
38
+ console.log('## User Schema (Object 嵌套)\n');
39
+ console.log(markdown);
40
+
41
+ fs.writeFileSync('examples/nested-object-example.md', markdown);
42
+ console.log('已保存到 examples/nested-object-example.md');
package/lib/index.d.ts CHANGED
@@ -1,2 +1,8 @@
1
1
  import { z } from "zod";
2
+ /**
3
+ * 将 Zod schema 转换为表格形式的 Markdown
4
+ * @param schema - 要转换的 Zod schema
5
+ * @returns 表格形式的 Markdown 字符串
6
+ */
7
+ export declare function zodSchemaToTable(schema: z.ZodTypeAny): string;
2
8
  export declare function zodSchemaToMarkdown(schema: z.ZodTypeAny, indentLevel?: number): string;
package/lib/index.js CHANGED
@@ -1,7 +1,212 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.zodSchemaToMarkdown = void 0;
3
+ exports.zodSchemaToMarkdown = exports.zodSchemaToTable = void 0;
4
4
  const zod_1 = require("zod");
5
+ /**
6
+ * 将 Zod schema 转换为表格形式的 Markdown
7
+ * @param schema - 要转换的 Zod schema
8
+ * @returns 表格形式的 Markdown 字符串
9
+ */
10
+ function zodSchemaToTable(schema) {
11
+ if (!(schema instanceof zod_1.z.ZodObject)) {
12
+ return zodSchemaToMarkdown(schema);
13
+ }
14
+ // 收集所有嵌套字段
15
+ const fields = [];
16
+ collectFields(schema, '', fields);
17
+ // 表头
18
+ const rows = [];
19
+ rows.push('| 字段 | 类型 | 描述 |');
20
+ rows.push('|------|------|------|');
21
+ fields.forEach(({ path, type, description }) => {
22
+ rows.push(`| ${path} | ${type} | ${description} |`);
23
+ });
24
+ return rows.join('\n') + '\n';
25
+ }
26
+ exports.zodSchemaToTable = zodSchemaToTable;
27
+ /**
28
+ * 递归收集所有嵌套字段
29
+ */
30
+ function collectFields(schema, prefix, fields) {
31
+ // 处理 Optional - 包装类型
32
+ if (schema instanceof zod_1.z.ZodOptional) {
33
+ const innerType = getTypeString(schema.unwrap());
34
+ // 对于简单类型直接包装
35
+ if (schema.unwrap() instanceof zod_1.z.ZodObject || schema.unwrap() instanceof zod_1.z.ZodArray) {
36
+ collectFields(schema.unwrap(), prefix, fields);
37
+ }
38
+ else {
39
+ fields.push({
40
+ path: prefix,
41
+ type: `Optional<${innerType}>`,
42
+ description: schema.description || '-',
43
+ });
44
+ }
45
+ return;
46
+ }
47
+ // 处理 Nullable - 包装类型
48
+ if (schema instanceof zod_1.z.ZodNullable) {
49
+ const innerType = getTypeString(schema.unwrap());
50
+ if (schema.unwrap() instanceof zod_1.z.ZodObject || schema.unwrap() instanceof zod_1.z.ZodArray) {
51
+ collectFields(schema.unwrap(), prefix, fields);
52
+ }
53
+ else {
54
+ fields.push({
55
+ path: prefix,
56
+ type: `Nullable<${innerType}>`,
57
+ description: schema.description || '-',
58
+ });
59
+ }
60
+ return;
61
+ }
62
+ // 处理 Default
63
+ if (schema instanceof zod_1.z.ZodDefault) {
64
+ collectFields(schema.removeDefault(), prefix, fields);
65
+ return;
66
+ }
67
+ // 处理 Array - 收集元素类型
68
+ if (schema instanceof zod_1.z.ZodArray) {
69
+ const element = schema.element;
70
+ // 如果是对象数组,展开其字段
71
+ if (element instanceof zod_1.z.ZodObject) {
72
+ const innerFields = [];
73
+ collectFields(element, '', innerFields);
74
+ innerFields.forEach(field => {
75
+ fields.push({
76
+ path: `${prefix}[]`,
77
+ type: field.path || getTypeString(element),
78
+ description: field.description,
79
+ });
80
+ });
81
+ }
82
+ else {
83
+ // 非对象数组,保持原样
84
+ fields.push({
85
+ path: `${prefix}[]`,
86
+ type: getTypeString(schema),
87
+ description: schema.description || '-',
88
+ });
89
+ }
90
+ return;
91
+ }
92
+ // 处理 ZodObject - 递归展开
93
+ if (schema instanceof zod_1.z.ZodObject) {
94
+ const shape = schema.shape;
95
+ Object.keys(shape).forEach(key => {
96
+ const subSchema = shape[key];
97
+ const newPath = prefix ? `${prefix}.${key}` : key;
98
+ collectFields(subSchema, newPath, fields);
99
+ });
100
+ return;
101
+ }
102
+ // 处理 Record
103
+ if (schema instanceof zod_1.z.ZodRecord) {
104
+ fields.push({
105
+ path: prefix,
106
+ type: `Record<${getTypeString(schema.keySchema)}, ${getTypeString(schema.valueSchema)}>`,
107
+ description: schema.description || '-',
108
+ });
109
+ return;
110
+ }
111
+ // 处理 Tuple
112
+ if (schema instanceof zod_1.z.ZodTuple) {
113
+ const items = schema.items.map((item) => getTypeString(item)).join(', ');
114
+ fields.push({
115
+ path: prefix,
116
+ type: `Tuple(${items})`,
117
+ description: schema.description || '-',
118
+ });
119
+ return;
120
+ }
121
+ // 其他类型直接添加
122
+ fields.push({
123
+ path: prefix,
124
+ type: getTypeString(schema),
125
+ description: schema.description || '-',
126
+ });
127
+ }
128
+ /**
129
+ * 获取类型的字符串描述
130
+ */
131
+ function getTypeString(schema) {
132
+ if (schema instanceof zod_1.z.ZodOptional) {
133
+ return `Optional<${getTypeString(schema.unwrap())}>`;
134
+ }
135
+ if (schema instanceof zod_1.z.ZodNullable) {
136
+ return `Nullable<${getTypeString(schema.unwrap())}>`;
137
+ }
138
+ if (schema instanceof zod_1.z.ZodDefault) {
139
+ return `Default<${getTypeString(schema.removeDefault())}>`;
140
+ }
141
+ if (schema instanceof zod_1.z.ZodArray) {
142
+ return `Array<${getTypeString(schema.element)}>`;
143
+ }
144
+ if (schema instanceof zod_1.z.ZodString) {
145
+ let result = 'String';
146
+ if (schema.minLength !== null || schema.maxLength !== null) {
147
+ const constraints = [];
148
+ if (schema.minLength !== null)
149
+ constraints.push(`min: ${schema.minLength}`);
150
+ if (schema.maxLength !== null)
151
+ constraints.push(`max: ${schema.maxLength}`);
152
+ result += ` (${constraints.join(', ')})`;
153
+ }
154
+ return result;
155
+ }
156
+ if (schema instanceof zod_1.z.ZodNumber) {
157
+ let result = 'Number';
158
+ if (schema.minValue !== null || schema.maxValue !== null) {
159
+ const constraints = [];
160
+ if (schema.minValue !== null)
161
+ constraints.push(`min: ${schema.minValue}`);
162
+ if (schema.maxValue !== null)
163
+ constraints.push(`max: ${schema.maxValue}`);
164
+ result += ` (${constraints.join(', ')})`;
165
+ }
166
+ return result;
167
+ }
168
+ if (schema instanceof zod_1.z.ZodEnum) {
169
+ return `Enum(${schema.options.join(' | ')})`;
170
+ }
171
+ if (schema instanceof zod_1.z.ZodLiteral) {
172
+ return `Literal(${JSON.stringify(schema.value)})`;
173
+ }
174
+ if (schema instanceof zod_1.z.ZodBoolean)
175
+ return 'Boolean';
176
+ if (schema instanceof zod_1.z.ZodBigInt)
177
+ return 'BigInt';
178
+ if (schema instanceof zod_1.z.ZodDate)
179
+ return 'Date';
180
+ if (schema instanceof zod_1.z.ZodNaN)
181
+ return 'NaN';
182
+ if (schema instanceof zod_1.z.ZodNever)
183
+ return 'Never';
184
+ if (schema instanceof zod_1.z.ZodUnknown)
185
+ return 'Unknown';
186
+ if (schema instanceof zod_1.z.ZodVoid)
187
+ return 'Void';
188
+ if (schema instanceof zod_1.z.ZodRecord) {
189
+ return `Record<${getTypeString(schema.keySchema)}, ${getTypeString(schema.valueSchema)}>`;
190
+ }
191
+ if (schema instanceof zod_1.z.ZodTuple) {
192
+ const items = schema.items.map((item) => getTypeString(item)).join(', ');
193
+ return `Tuple(${items})`;
194
+ }
195
+ if (schema instanceof zod_1.z.ZodUnion) {
196
+ const options = schema.options.map((opt) => getTypeString(opt)).join(' | ');
197
+ return `Union(${options})`;
198
+ }
199
+ if (schema instanceof zod_1.z.ZodDiscriminatedUnion) {
200
+ return `DiscriminatedUnion(${schema.discriminator})`;
201
+ }
202
+ if (schema instanceof zod_1.z.ZodIntersection) {
203
+ return 'Intersection';
204
+ }
205
+ if (schema instanceof zod_1.z.ZodEffects) {
206
+ return `Effects(${schema._def.effect.type})`;
207
+ }
208
+ return schema.constructor.name;
209
+ }
5
210
  function zodSchemaToMarkdown(schema, indentLevel = 0) {
6
211
  let markdown = "";
7
212
  const indent = " ".repeat(indentLevel);
package/lib/index.test.js CHANGED
@@ -131,3 +131,81 @@ describe('zodSchemaToMarkdown', () => {
131
131
  expect((0, index_1.zodSchemaToMarkdown)(schema)).toBe(expected);
132
132
  });
133
133
  });
134
+ describe('zodSchemaToTable', () => {
135
+ it('should convert a simple object schema to table', () => {
136
+ const schema = zod_1.z.object({
137
+ name: zod_1.z.string(),
138
+ age: zod_1.z.number(),
139
+ });
140
+ const expected = `| 字段 | 类型 | 描述 |
141
+ |------|------|------|
142
+ | name | String | - |
143
+ | age | Number | - |
144
+ `;
145
+ expect((0, index_1.zodSchemaToTable)(schema)).toBe(expected);
146
+ });
147
+ it('should convert object schema with optional and nullable to table', () => {
148
+ const schema = zod_1.z.object({
149
+ name: zod_1.z.string().optional(),
150
+ email: zod_1.z.string().nullable(),
151
+ });
152
+ const expected = `| 字段 | 类型 | 描述 |
153
+ |------|------|------|
154
+ | name | Optional<String> | - |
155
+ | email | Nullable<String> | - |
156
+ `;
157
+ expect((0, index_1.zodSchemaToTable)(schema)).toBe(expected);
158
+ });
159
+ it('should convert object schema with enum to table', () => {
160
+ const schema = zod_1.z.object({
161
+ role: zod_1.z.enum(['admin', 'user', 'guest']),
162
+ });
163
+ const expected = `| 字段 | 类型 | 描述 |
164
+ |------|------|------|
165
+ | role | Enum(admin | user | guest) | - |
166
+ `;
167
+ expect((0, index_1.zodSchemaToTable)(schema)).toBe(expected);
168
+ });
169
+ it('should convert object schema with array to table', () => {
170
+ const schema = zod_1.z.object({
171
+ tags: zod_1.z.array(zod_1.z.string()),
172
+ scores: zod_1.z.array(zod_1.z.number()),
173
+ });
174
+ const expected = `| 字段 | 类型 | 描述 |
175
+ |------|------|------|
176
+ | tags[] | Array<String> | - |
177
+ | scores[] | Array<Number> | - |
178
+ `;
179
+ expect((0, index_1.zodSchemaToTable)(schema)).toBe(expected);
180
+ });
181
+ it('should convert object schema with description to table', () => {
182
+ const schema = zod_1.z.object({
183
+ name: zod_1.z.string().describe('用户名称'),
184
+ age: zod_1.z.number().describe('用户年龄'),
185
+ });
186
+ const expected = `| 字段 | 类型 | 描述 |
187
+ |------|------|------|
188
+ | name | String | 用户名称 |
189
+ | age | Number | 用户年龄 |
190
+ `;
191
+ expect((0, index_1.zodSchemaToTable)(schema)).toBe(expected);
192
+ });
193
+ it('should convert object schema with literal to table', () => {
194
+ const schema = zod_1.z.object({
195
+ status: zod_1.z.literal('active'),
196
+ count: zod_1.z.literal(1),
197
+ });
198
+ const expected = `| 字段 | 类型 | 描述 |
199
+ |------|------|------|
200
+ | status | Literal("active") | - |
201
+ | count | Literal(1) | - |
202
+ `;
203
+ expect((0, index_1.zodSchemaToTable)(schema)).toBe(expected);
204
+ });
205
+ it('should fallback to markdown for non-object schema', () => {
206
+ const schema = zod_1.z.string();
207
+ const expected = `- String
208
+ `;
209
+ expect((0, index_1.zodSchemaToTable)(schema)).toBe(expected);
210
+ });
211
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chnak/zod-to-markdown",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "A utility function to convert Zod schemas to Markdown documentation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",