@rafaelsilvadeveloper/zod-meta-form 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.
- package/README.md +99 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +2 -0
- package/dist/zodToFormMeta.d.ts +11 -0
- package/dist/zodToFormMeta.js +136 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# @rafaelsilvadeveloper/zod-meta-form
|
|
2
|
+
|
|
3
|
+
A framework-agnostic generator of Form UI metadata from Zod schemas, converting schemas into dynamic, client-ready forms.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@rafaelsilvadeveloper/zod-meta-form)
|
|
6
|
+
[](https://discord.gg/7Fw7snafYS)
|
|
7
|
+
[](https://www.npmjs.com/package/@rafaelsilvadeveloper/zod-meta-form)
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
* 🎯 **Framework Agnostic**: Outputs pure JSON-serializable UI form trees. Works with React, Vue, Svelte, Angular, or Vanilla JS.
|
|
12
|
+
* 🔄 **Recursive Parsing**: Deeply inspects nested `ZodObject` and `ZodArray` schemas.
|
|
13
|
+
* 🏷️ **Auto-Label Generation**: Automatically converts camelCase, snake_case, and kebab-case field names into clean, readable Title Case labels.
|
|
14
|
+
* 🛠️ **Zod Constraint Mapping**: Translates string and number constraints (email, url, uuid, min, max, regex) into form validation rules.
|
|
15
|
+
* 🎛️ **Supports Enums**: Converts Zod `enum` and `nativeEnum` options directly into dropdown options lists.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @rafaelsilvadeveloper/zod-meta-form zod
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
import { zodToFormMeta } from '@rafaelsilvadeveloper/zod-meta-form';
|
|
28
|
+
|
|
29
|
+
// 1. Define your Zod schema
|
|
30
|
+
const signupSchema = z.object({
|
|
31
|
+
firstName: z.string().min(2).describe('Your first name'),
|
|
32
|
+
email: z.string().email(),
|
|
33
|
+
role: z.enum(['developer', 'designer', 'manager']).default('developer'),
|
|
34
|
+
subscribeToNewsletter: z.boolean().optional(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// 2. Generate form UI metadata
|
|
38
|
+
const formMeta = zodToFormMeta(signupSchema);
|
|
39
|
+
|
|
40
|
+
console.log(JSON.stringify(formMeta, null, 2));
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Output Form Metadata Structure
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"type": "object",
|
|
48
|
+
"label": "",
|
|
49
|
+
"required": true,
|
|
50
|
+
"fields": {
|
|
51
|
+
"firstName": {
|
|
52
|
+
"type": "text",
|
|
53
|
+
"label": "First Name",
|
|
54
|
+
"required": true,
|
|
55
|
+
"description": "Your first name",
|
|
56
|
+
"validations": {
|
|
57
|
+
"min": { "value": 2 }
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"email": {
|
|
61
|
+
"type": "email",
|
|
62
|
+
"label": "Email",
|
|
63
|
+
"required": true,
|
|
64
|
+
"validations": {
|
|
65
|
+
"email": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"role": {
|
|
69
|
+
"type": "select",
|
|
70
|
+
"label": "Role",
|
|
71
|
+
"required": true,
|
|
72
|
+
"defaultValue": "developer",
|
|
73
|
+
"options": ["developer", "designer", "manager"]
|
|
74
|
+
},
|
|
75
|
+
"subscribeToNewsletter": {
|
|
76
|
+
"type": "checkbox",
|
|
77
|
+
"label": "Subscribe To Newsletter",
|
|
78
|
+
"required": false
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## API
|
|
85
|
+
|
|
86
|
+
### `zodToFormMeta(schema: z.ZodTypeAny, fieldName?: string): FormMeta`
|
|
87
|
+
Recursively generates form controls, type mappings, validation rules, default values, options, and descriptions from a Zod schema.
|
|
88
|
+
|
|
89
|
+
### `toLabel(fieldName: string): string`
|
|
90
|
+
Converts `camelCase`, `snake_case`, and `kebab-case` string patterns into standard capitalized labels.
|
|
91
|
+
|
|
92
|
+
## Support
|
|
93
|
+
|
|
94
|
+
For support, questions, or discussions, join our Discord server:
|
|
95
|
+
|
|
96
|
+
[](https://discord.gg/7Fw7snafYS)
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toLabel = exports.zodToFormMeta = void 0;
|
|
4
|
+
var zodToFormMeta_1 = require("./zodToFormMeta");
|
|
5
|
+
Object.defineProperty(exports, "zodToFormMeta", { enumerable: true, get: function () { return zodToFormMeta_1.zodToFormMeta; } });
|
|
6
|
+
Object.defineProperty(exports, "toLabel", { enumerable: true, get: function () { return zodToFormMeta_1.toLabel; } });
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type FieldType = 'text' | 'email' | 'url' | 'number' | 'checkbox' | 'select' | 'date' | 'object' | 'array';
|
|
2
|
+
export interface ValidationRule {
|
|
3
|
+
value: any;
|
|
4
|
+
message?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface FieldMeta {
|
|
7
|
+
type: FieldType;
|
|
8
|
+
label: string;
|
|
9
|
+
required: boolean;
|
|
10
|
+
defaultValue?: any;
|
|
11
|
+
description?: string;
|
|
12
|
+
options?: string[];
|
|
13
|
+
validations?: {
|
|
14
|
+
min?: ValidationRule;
|
|
15
|
+
max?: ValidationRule;
|
|
16
|
+
email?: boolean;
|
|
17
|
+
url?: boolean;
|
|
18
|
+
uuid?: boolean;
|
|
19
|
+
regex?: ValidationRule;
|
|
20
|
+
};
|
|
21
|
+
fields?: Record<string, FieldMeta>;
|
|
22
|
+
element?: FieldMeta;
|
|
23
|
+
}
|
|
24
|
+
export type FormMeta = FieldMeta;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { FormMeta } from './types';
|
|
3
|
+
export declare function toLabel(str: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* Recursively parses a Zod schema to produce a UI-friendly form metadata object.
|
|
6
|
+
*
|
|
7
|
+
* @param schema The Zod schema to inspect.
|
|
8
|
+
* @param fieldName The original name of the field (used to generate human-readable labels).
|
|
9
|
+
* @returns A structured metadata configuration for generating form fields.
|
|
10
|
+
*/
|
|
11
|
+
export declare function zodToFormMeta(schema: z.ZodTypeAny, fieldName?: string): FormMeta;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toLabel = toLabel;
|
|
4
|
+
exports.zodToFormMeta = zodToFormMeta;
|
|
5
|
+
function toLabel(str) {
|
|
6
|
+
if (!str)
|
|
7
|
+
return '';
|
|
8
|
+
const words = str
|
|
9
|
+
.replace(/([A-Z])/g, ' $1')
|
|
10
|
+
.replace(/[_-]+/g, ' ')
|
|
11
|
+
.trim()
|
|
12
|
+
.split(/\s+/);
|
|
13
|
+
return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
|
|
14
|
+
}
|
|
15
|
+
function unwrapZodType(schema) {
|
|
16
|
+
let current = schema;
|
|
17
|
+
let required = true;
|
|
18
|
+
let defaultValue = undefined;
|
|
19
|
+
const description = schema.description;
|
|
20
|
+
while (true) {
|
|
21
|
+
const typeName = current._def.typeName;
|
|
22
|
+
if (typeName === 'ZodOptional' || typeName === 'ZodNullable') {
|
|
23
|
+
required = false;
|
|
24
|
+
current = current.unwrap();
|
|
25
|
+
}
|
|
26
|
+
else if (typeName === 'ZodDefault') {
|
|
27
|
+
defaultValue = current._def.defaultValue();
|
|
28
|
+
current = current._def.innerType;
|
|
29
|
+
}
|
|
30
|
+
else if (typeName === 'ZodEffects') {
|
|
31
|
+
current = current.innerType();
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { unwrapped: current, required, defaultValue, description };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Recursively parses a Zod schema to produce a UI-friendly form metadata object.
|
|
41
|
+
*
|
|
42
|
+
* @param schema The Zod schema to inspect.
|
|
43
|
+
* @param fieldName The original name of the field (used to generate human-readable labels).
|
|
44
|
+
* @returns A structured metadata configuration for generating form fields.
|
|
45
|
+
*/
|
|
46
|
+
function zodToFormMeta(schema, fieldName = '') {
|
|
47
|
+
const { unwrapped, required, defaultValue, description } = unwrapZodType(schema);
|
|
48
|
+
const typeName = unwrapped._def.typeName;
|
|
49
|
+
const label = fieldName ? toLabel(fieldName) : '';
|
|
50
|
+
const meta = {
|
|
51
|
+
type: 'text',
|
|
52
|
+
label,
|
|
53
|
+
required,
|
|
54
|
+
};
|
|
55
|
+
if (defaultValue !== undefined) {
|
|
56
|
+
meta.defaultValue = defaultValue;
|
|
57
|
+
}
|
|
58
|
+
if (description !== undefined) {
|
|
59
|
+
meta.description = description;
|
|
60
|
+
}
|
|
61
|
+
if (typeName === 'ZodString') {
|
|
62
|
+
meta.type = 'text';
|
|
63
|
+
const checks = unwrapped._def.checks;
|
|
64
|
+
const validations = {};
|
|
65
|
+
for (const check of checks) {
|
|
66
|
+
if (check.kind === 'email') {
|
|
67
|
+
meta.type = 'email';
|
|
68
|
+
validations.email = true;
|
|
69
|
+
}
|
|
70
|
+
else if (check.kind === 'url') {
|
|
71
|
+
meta.type = 'url';
|
|
72
|
+
validations.url = true;
|
|
73
|
+
}
|
|
74
|
+
else if (check.kind === 'uuid') {
|
|
75
|
+
validations.uuid = true;
|
|
76
|
+
}
|
|
77
|
+
else if (check.kind === 'min') {
|
|
78
|
+
validations.min = { value: check.value, message: check.message };
|
|
79
|
+
}
|
|
80
|
+
else if (check.kind === 'max') {
|
|
81
|
+
validations.max = { value: check.value, message: check.message };
|
|
82
|
+
}
|
|
83
|
+
else if (check.kind === 'regex') {
|
|
84
|
+
validations.regex = { value: check.regex.source, message: check.message };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (Object.keys(validations).length > 0) {
|
|
88
|
+
meta.validations = validations;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (typeName === 'ZodNumber') {
|
|
92
|
+
meta.type = 'number';
|
|
93
|
+
const checks = unwrapped._def.checks;
|
|
94
|
+
const validations = {};
|
|
95
|
+
for (const check of checks) {
|
|
96
|
+
if (check.kind === 'min') {
|
|
97
|
+
validations.min = { value: check.value, message: check.message };
|
|
98
|
+
}
|
|
99
|
+
else if (check.kind === 'max') {
|
|
100
|
+
validations.max = { value: check.value, message: check.message };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (Object.keys(validations).length > 0) {
|
|
104
|
+
meta.validations = validations;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (typeName === 'ZodBoolean') {
|
|
108
|
+
meta.type = 'checkbox';
|
|
109
|
+
}
|
|
110
|
+
else if (typeName === 'ZodDate') {
|
|
111
|
+
meta.type = 'date';
|
|
112
|
+
}
|
|
113
|
+
else if (typeName === 'ZodEnum') {
|
|
114
|
+
meta.type = 'select';
|
|
115
|
+
meta.options = unwrapped._def.values;
|
|
116
|
+
}
|
|
117
|
+
else if (typeName === 'ZodNativeEnum') {
|
|
118
|
+
meta.type = 'select';
|
|
119
|
+
const enumObj = unwrapped._def.values;
|
|
120
|
+
meta.options = Object.values(enumObj).filter((v) => typeof v === 'string');
|
|
121
|
+
}
|
|
122
|
+
else if (typeName === 'ZodObject') {
|
|
123
|
+
meta.type = 'object';
|
|
124
|
+
const shape = unwrapped.shape;
|
|
125
|
+
const fields = {};
|
|
126
|
+
for (const key of Object.keys(shape)) {
|
|
127
|
+
fields[key] = zodToFormMeta(shape[key], key);
|
|
128
|
+
}
|
|
129
|
+
meta.fields = fields;
|
|
130
|
+
}
|
|
131
|
+
else if (typeName === 'ZodArray') {
|
|
132
|
+
meta.type = 'array';
|
|
133
|
+
meta.element = zodToFormMeta(unwrapped.element, '');
|
|
134
|
+
}
|
|
135
|
+
return meta;
|
|
136
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rafaelsilvadeveloper/zod-meta-form",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "A framework-agnostic generator of Form UI metadata from Zod schemas, converting schemas into client-ready forms.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/rafael-packages/zod-meta-form.git"
|
|
8
|
+
},
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/rafael-packages/zod-meta-form/issues"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/rafael-packages/zod-meta-form#readme",
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"test": "bun test",
|
|
22
|
+
"prepare": "bun run build",
|
|
23
|
+
"lint": "eslint src/**/*.ts",
|
|
24
|
+
"format": "prettier --write src/**/*.ts tests/**/*.ts"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"zod",
|
|
28
|
+
"form",
|
|
29
|
+
"metadata",
|
|
30
|
+
"form-generator",
|
|
31
|
+
"typescript",
|
|
32
|
+
"schema",
|
|
33
|
+
"validation"
|
|
34
|
+
],
|
|
35
|
+
"author": "realkalashnikov",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"typescript": "^5.0.0",
|
|
39
|
+
"zod": "^3.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^25.9.1",
|
|
43
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
44
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
45
|
+
"eslint": "^8.57.0",
|
|
46
|
+
"prettier": "^3.3.3",
|
|
47
|
+
"zod": "^3.23.8"
|
|
48
|
+
}
|
|
49
|
+
}
|