Skip to content

Commit fa66de5

Browse files
committed
Merge branch 'release/v1.0.18'
2 parents c7daf33 + f6c466e commit fa66de5

9 files changed

Lines changed: 862 additions & 7 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Schema Serialization Demo
2+
3+
This demo demonstrates how to serialize and deserialize schema types to/from JSON format.
4+
5+
## What it demonstrates
6+
7+
- Converting schema definitions to plain JSON objects
8+
- Sending schemas over the network (as JSON strings)
9+
- Reconstructing schemas from JSON
10+
- Round-trip serialization (schema → JSON → schema)
11+
- Working with complex nested schemas
12+
13+
## Use Cases
14+
15+
Schema serialization is useful for:
16+
17+
1. **API Documentation**: Send schema definitions to clients for validation
18+
2. **Remote Validation**: Share validation rules between client and server
19+
3. **Database Storage**: Store schema definitions in databases
20+
4. **Configuration**: Define schemas in JSON configuration files
21+
5. **Code Generation**: Generate code from serialized schemas
22+
23+
## Running the demo
24+
25+
```bash
26+
pnpm install
27+
pnpm start
28+
```
29+
30+
Or with tsx:
31+
32+
```bash
33+
npx tsx index.ts
34+
```
35+
36+
## Key Functions
37+
38+
- `serializeSchema(schema)` - Converts a Type schema to a plain JSON object
39+
- `deserializeSchema(json)` - Reconstructs a Type schema from a JSON object
40+
41+
## Limitations
42+
43+
- Function-based default values (e.g., `.default(() => 'value')`) cannot be serialized
44+
- Custom validation functions are not preserved (standard validators are restored)
45+
- Only static, JSON-serializable values are maintained
46+
47+
## Example Output
48+
49+
The demo will show:
50+
51+
1. The original schema structure
52+
2. The serialized JSON representation
53+
3. The deserialized schema (identical to the original)
54+
4. Round-trip verification
55+
5. Complex nested schema serialization
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/* eslint-disable no-console */
2+
3+
/**
4+
* Schema Serialization Demo
5+
*
6+
* This demo shows how to serialize and deserialize schema types to/from JSON.
7+
* This is useful for:
8+
* - Sending schema definitions over the network
9+
* - Storing schemas in databases
10+
* - Sharing schemas between different systems
11+
*/
12+
13+
import { array, deserializeSchema, int, object, serializeSchema, string, z } from 'zeed'
14+
15+
// Define a user schema
16+
const userSchema = z.object({
17+
id: z.string().default('auto-generated'),
18+
name: z.string().describe('Full name of the user'),
19+
email: z.string(),
20+
age: z.int().optional(),
21+
role: z.enum(['admin', 'user', 'guest']).default('user'),
22+
tags: z.array(z.string()).optional(),
23+
metadata: z.object({
24+
createdAt: z.string(),
25+
updatedAt: z.string().optional(),
26+
}).optional(),
27+
})
28+
29+
console.log('Original schema:')
30+
console.log(userSchema)
31+
32+
// Serialize the schema to a plain JSON object
33+
const serialized = serializeSchema(userSchema)
34+
console.log('\nSerialized schema:')
35+
console.log(JSON.stringify(serialized, null, 2))
36+
37+
// Convert to JSON string (as you might send over network)
38+
const jsonString = JSON.stringify(serialized)
39+
console.log('\nJSON string length:', jsonString.length)
40+
41+
// Deserialize back to a schema
42+
const deserialized = deserializeSchema(JSON.parse(jsonString))
43+
console.log('\nDeserialized schema:')
44+
console.log(deserialized)
45+
46+
// Use the deserialized schema to validate data
47+
const userData = {
48+
name: 'Alice Smith',
49+
50+
age: 30,
51+
role: 'admin',
52+
tags: ['developer', 'typescript'],
53+
}
54+
55+
console.log('\nValidating data with deserialized schema:')
56+
console.log('Input:', userData)
57+
58+
// Note: You would use a parsing function here when available
59+
// For now, we can just verify the schema structure is correct
60+
console.log('Schema type:', deserialized.type)
61+
console.log('Schema has object definition:', !!deserialized._object)
62+
console.log('Name field type:', deserialized._object.name.type)
63+
console.log('Role field enum values:', deserialized._object.role._enumValues)
64+
console.log('Age field is optional:', deserialized._object.age._optional)
65+
66+
// Demonstrate round-trip serialization
67+
const reserialized = serializeSchema(deserialized)
68+
const areEqual = JSON.stringify(serialized) === JSON.stringify(reserialized)
69+
console.log('\nRound-trip successful:', areEqual)
70+
71+
// Example of a complex nested schema
72+
const apiResponseSchema = object({
73+
success: z.boolean(),
74+
data: z.object({
75+
users: array(userSchema),
76+
total: int(),
77+
}),
78+
error: string().optional(),
79+
})
80+
81+
console.log('\n--- Complex Schema Example ---')
82+
const complexSerialized = serializeSchema(apiResponseSchema)
83+
console.log('Complex schema serialized:')
84+
console.log(JSON.stringify(complexSerialized, null, 2))
85+
86+
const complexDeserialized = deserializeSchema(complexSerialized)
87+
console.log('\nComplex schema deserialized successfully:', complexDeserialized.type === 'object')
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "demo-schema-serialization",
3+
"type": "module",
4+
"version": "1.0.0",
5+
"private": true,
6+
"scripts": {
7+
"start": "tsx index.ts"
8+
},
9+
"dependencies": {
10+
"zeed": "file:../.."
11+
},
12+
"devDependencies": {
13+
"tsx": "^4.20.6"
14+
}
15+
}

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "zeed",
33
"type": "module",
4-
"version": "1.0.17",
5-
"packageManager": "pnpm@10.17.0",
4+
"version": "1.0.18",
5+
"packageManager": "pnpm@10.18.0",
66
"description": "🌱 Simple foundation library",
77
"author": {
88
"name": "Dirk Holtwick",
@@ -81,18 +81,18 @@
8181
},
8282
"devDependencies": {
8383
"@antfu/eslint-config": "^5.4.1",
84-
"@antfu/ni": "^25.0.0",
85-
"@types/node": "^24.5.2",
84+
"@antfu/ni": "^26.1.0",
85+
"@types/node": "^24.6.2",
8686
"@vitejs/plugin-vue": "^6.0.1",
8787
"@vitest/browser": "^3.2.4",
8888
"@vitest/coverage-v8": "^3.2.4",
8989
"esbuild": "^0.25.10",
90-
"eslint": "^9.36.0",
90+
"eslint": "^9.37.0",
9191
"playwright": "^1.55.1",
9292
"pnpm": "^10.17.1",
9393
"tsup": "^8.5.0",
94-
"typescript": "^5.9.2",
95-
"vite": "^7.1.7",
94+
"typescript": "^5.9.3",
95+
"vite": "^7.1.9",
9696
"vitest": "^3.2.4"
9797
}
9898
}

src/common/schema/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,37 @@ const rpcCall = z.rpc(z.object({ id: z.string() }), z.number())
8484
// Type: (info: { id: string }) => number | Promise<number>
8585
```
8686

87+
### Serialization and Deserialization
88+
89+
You can serialize schema definitions to plain JSON objects and deserialize them back. This is useful for sending schema definitions over the network or storing them in databases.
90+
91+
```ts
92+
import { deserializeSchema, serializeSchema } from './schema'
93+
94+
// Define a schema
95+
const userSchema = z.object({
96+
name: z.string(),
97+
age: z.number().optional(),
98+
role: z.stringLiterals(['admin', 'user']).default('user'),
99+
})
100+
101+
// Serialize to plain JSON
102+
const serialized = serializeSchema(userSchema)
103+
// Result: { type: 'object', object: { name: { type: 'string' }, ... } }
104+
105+
// Send over network, store in DB, etc.
106+
const jsonString = JSON.stringify(serialized)
107+
108+
// Deserialize back to a schema
109+
const deserialized = deserializeSchema(JSON.parse(jsonString))
110+
// deserialized is now a Type instance identical to userSchema
111+
112+
// Use the deserialized schema for validation
113+
const user = deserialized.parse({ name: 'Alice', role: 'admin' })
114+
```
115+
116+
**Note**: Function defaults (e.g., `.default(() => 'value')`) cannot be serialized and will be omitted. Only static default values are preserved during serialization.
117+
87118
## API Reference
88119

89120
- `string()`, `number()`, `int()`, `boolean()`, `none()`, `any()`
@@ -93,6 +124,8 @@ const rpcCall = z.rpc(z.object({ id: z.string() }), z.number())
93124
- `func(args, ret)`, `rpc(info, ret)`
94125
- `.optional()`, `.default(value)`, `.meta({ desc })`, `.extend({...})`
95126
- `parse(obj)`, `map(obj, fn)`
127+
- `serializeSchema(schema)` - Converts a schema to a plain JSON object
128+
- `deserializeSchema(json)` - Reconstructs a schema from a JSON object
96129

97130
## Notes
98131

src/common/schema/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './parse-args'
55
export * from './parse-env'
66
export * from './parse-object'
77
export * from './schema'
8+
export * from './serialize'
89
export * from './type-test'
910
export * from './utils'
1011
export * from './z'

src/common/schema/parse-object.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,5 +218,24 @@ describe('schema parse obj', () => {
218218
},
219219
// optionalObj is missing (not undefined) since it's optional and not provided
220220
})
221+
222+
const configSchema = z.object({
223+
cspAllowedDomains: z.string().default('*.holtwick.de *.brie.fi *.apperdeck.com *.replies.io *.sentry.io').describe('Allowed domains for Content-Security-Policy, space separated. Use * as wildcard.'),
224+
hstsMaxAge: z.number().default(31536000).describe('Max age for HTTP Strict Transport Security (HSTS) in seconds. Set to 0 to disable.'),
225+
securityHeaders: z.boolean().default(true).describe('Enable security headers like Content-Security-Policy, X-Frame-Options, etc.'),
226+
trustProxy: z.boolean().default(true).describe('Trust the X-Forwarded-* headers, useful if you run behind a reverse proxy.'),
227+
})
228+
229+
const result5 = schemaParseObject(configSchema, {
230+
securityHeaders: 'false',
231+
hstsMaxAge: '0',
232+
notAllowed: 'should be kept',
233+
}, {
234+
skipDefault: true,
235+
})
236+
expect(result5).toEqual({
237+
securityHeaders: false,
238+
hstsMaxAge: 0,
239+
})
221240
})
222241
})

0 commit comments

Comments
 (0)