hanzio 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.
Files changed (51) hide show
  1. package/README.md +510 -0
  2. package/dist/api-wrapper/index.d.ts +84 -0
  3. package/dist/api-wrapper/index.js +250 -0
  4. package/dist/array/index.d.ts +22 -0
  5. package/dist/cache/index.d.ts +19 -0
  6. package/dist/date/index.d.ts +11 -0
  7. package/dist/gzip/index.d.ts +2 -0
  8. package/dist/gzip/index.js +16 -0
  9. package/dist/index.d.ts +12 -0
  10. package/dist/index.js +1565 -0
  11. package/dist/jwt/index.d.ts +3 -0
  12. package/dist/jwt/index.js +19 -0
  13. package/dist/math/index.d.ts +2 -0
  14. package/dist/math/linearRegressionTrend.d.ts +5 -0
  15. package/dist/math/metricsSeriesColors.d.ts +42 -0
  16. package/dist/p-queue/index.d.ts +95 -0
  17. package/dist/p-queue/index.js +439 -0
  18. package/dist/promise/index.d.ts +21 -0
  19. package/dist/sitemap/index.d.ts +14 -0
  20. package/dist/sitemap/index.js +552 -0
  21. package/dist/state/index.d.ts +101 -0
  22. package/dist/string/index.d.ts +8 -0
  23. package/dist/typedswitch/index.d.ts +124 -0
  24. package/dist/url/index.d.ts +1 -0
  25. package/dist/zod/index.d.ts +8 -0
  26. package/dist/zod/index.js +210 -0
  27. package/dist/zod/jsonToZodSchemaMapper.d.ts +6 -0
  28. package/dist/zod/zodMinMaxFilter.d.ts +14 -0
  29. package/dist/zod/zodToTypeString.d.ts +9 -0
  30. package/package.json +102 -0
  31. package/src/api-wrapper/index.ts +492 -0
  32. package/src/array/index.ts +187 -0
  33. package/src/cache/index.ts +154 -0
  34. package/src/date/index.ts +54 -0
  35. package/src/gzip/index.ts +29 -0
  36. package/src/index.ts +12 -0
  37. package/src/jwt/index.ts +25 -0
  38. package/src/math/index.ts +11 -0
  39. package/src/math/linearRegressionTrend.ts +36 -0
  40. package/src/math/metricsSeriesColors.ts +208 -0
  41. package/src/p-queue/index.ts +690 -0
  42. package/src/promise/index.ts +73 -0
  43. package/src/sitemap/index.ts +195 -0
  44. package/src/state/index.ts +331 -0
  45. package/src/string/index.ts +86 -0
  46. package/src/typedswitch/index.ts +309 -0
  47. package/src/url/index.ts +5 -0
  48. package/src/zod/index.ts +29 -0
  49. package/src/zod/jsonToZodSchemaMapper.ts +44 -0
  50. package/src/zod/zodMinMaxFilter.ts +28 -0
  51. package/src/zod/zodToTypeString.ts +222 -0
@@ -0,0 +1,309 @@
1
+ /**
2
+ * typedSwitch - A type-safe switch statement for discriminated unions
3
+ *
4
+ * Supports two input modes:
5
+ *
6
+ * 1. STRING INPUT - switch on a string/enum value directly:
7
+ *
8
+ * @example
9
+ * const status: 'success' | 'error' = 'success'
10
+ *
11
+ * // All cases required (no default)
12
+ * typedSwitch(status, {
13
+ * success: (val) => 'handled success',
14
+ * error: (val) => 'handled error',
15
+ * })
16
+ *
17
+ * // Partial cases with default handler
18
+ * typedSwitch(status, {
19
+ * success: (val) => 'handled success',
20
+ * }, (val) => 'default handler')
21
+ *
22
+ *
23
+ * 2. OBJECT INPUT - switch on a discriminated union with custom key:
24
+ *
25
+ * @example
26
+ * type Event =
27
+ * | { type: 'click'; x: number; y: number }
28
+ * | { type: 'scroll'; offset: number }
29
+ *
30
+ * // All cases required (no default)
31
+ * typedSwitch(event, 'type', {
32
+ * click: (e) => `Clicked at ${e.x}, ${e.y}`,
33
+ * scroll: (e) => `Scrolled ${e.offset}px`,
34
+ * })
35
+ *
36
+ * // Partial cases with default handler
37
+ * typedSwitch(event, 'type', {
38
+ * click: (e) => `Clicked at ${e.x}, ${e.y}`,
39
+ * }, (e) => 'unhandled event')
40
+ *
41
+ * // Works with any discriminant key (not just 'type')
42
+ * type Order = { status: 'pending' } | { status: 'completed' }
43
+ * typedSwitch(order, 'status', {
44
+ * pending: (o) => 'waiting...',
45
+ * completed: (o) => 'done!',
46
+ * })
47
+ *
48
+ *
49
+ * 3. WITH CONSTRAINT - use `typedSwitch<T>` to enforce return type:
50
+ *
51
+ * @example
52
+ * interface HasId { id: string }
53
+ *
54
+ * // All handlers must return something with { id: string }
55
+ * typedSwitch<HasId>(status, {
56
+ * success: () => ({ id: '123', name: 'John' }), // ✓ OK
57
+ * error: () => ({ id: '456' }), // ✓ OK
58
+ * })
59
+ *
60
+ * // Type error - missing 'id' property
61
+ * typedSwitch<HasId>(status, {
62
+ * success: () => ({ name: 'John' }), // ✗ Error!
63
+ * error: () => ({ id: '456' }),
64
+ * })
65
+ */
66
+
67
+ // ═══════════════════════════════════════════════════════════════════════════
68
+ // Helper types for return type inference
69
+ // ═══════════════════════════════════════════════════════════════════════════
70
+
71
+ /** Extract raw return type from a handler function (preserves Promise) */
72
+ // biome-ignore lint/suspicious/noExplicitAny: Required for type extraction from functions
73
+ type RawHandlerReturnType<T> = T extends (...args: any[]) => infer R ? R : never
74
+
75
+ /** Get union of all raw return types from a cases object's values */
76
+ type RawCasesReturnType<Cases> = {
77
+ [K in keyof Cases]: Cases[K] extends undefined
78
+ ? never
79
+ : RawHandlerReturnType<Cases[K]>
80
+ }[keyof Cases]
81
+
82
+ // ═══════════════════════════════════════════════════════════════════════════
83
+ // String input mode types
84
+ // ═══════════════════════════════════════════════════════════════════════════
85
+
86
+ /** Cases for string input - all cases required */
87
+ export type StringSwitchCases<T extends string, R> = {
88
+ [K in T]: (value: K) => R | Promise<R>
89
+ }
90
+
91
+ /** Partial cases for string input (when default is provided) */
92
+ export type PartialStringSwitchCases<T extends string, R> = {
93
+ [K in T]?: (value: K) => R | Promise<R>
94
+ }
95
+
96
+ // ═══════════════════════════════════════════════════════════════════════════
97
+ // Object input mode types
98
+ // ═══════════════════════════════════════════════════════════════════════════
99
+
100
+ /** Cases for object input with custom key - all cases required */
101
+ export type ObjectSwitchCases<T, K extends keyof T, R> = T[K] extends string
102
+ ? {
103
+ [V in T[K]]: (value: Extract<T, Record<K, V>>) => R | Promise<R>
104
+ }
105
+ : never
106
+
107
+ /** Partial cases for object input with custom key (when default is provided) */
108
+ export type PartialObjectSwitchCases<
109
+ T,
110
+ K extends keyof T,
111
+ R
112
+ > = T[K] extends string
113
+ ? {
114
+ [V in T[K]]?: (value: Extract<T, Record<K, V>>) => R | Promise<R>
115
+ }
116
+ : never
117
+
118
+ type Handler = (value: unknown) => unknown
119
+ type CasesRecord = Record<string, Handler | undefined>
120
+
121
+ function runSwitch(
122
+ value: string | Record<string, unknown>,
123
+ keyOrCases: string | CasesRecord,
124
+ casesOrDefault?: CasesRecord | Handler,
125
+ defaultCase?: Handler
126
+ ): unknown {
127
+ let key: string
128
+ let cases: CasesRecord
129
+ let defaultHandler: Handler | undefined
130
+ let discriminantKey: string | undefined
131
+
132
+ if (typeof value === 'string') {
133
+ // String input: typedSwitch(string, cases, default?)
134
+ key = value
135
+ cases = keyOrCases as CasesRecord
136
+ defaultHandler = casesOrDefault as Handler | undefined
137
+ } else {
138
+ // Object input: typedSwitch(obj, key, cases, default?)
139
+ if (typeof keyOrCases !== 'string') {
140
+ throw new Error(
141
+ 'Invalid typedSwitch call: object mode requires a string discriminant key'
142
+ )
143
+ }
144
+
145
+ discriminantKey = keyOrCases
146
+ const discriminantValue = value[discriminantKey]
147
+ if (typeof discriminantValue !== 'string') {
148
+ throw new Error(
149
+ `Invalid discriminant value for key "${discriminantKey}": expected string, received ${typeof discriminantValue}`
150
+ )
151
+ }
152
+
153
+ key = discriminantValue
154
+ cases = casesOrDefault as CasesRecord
155
+ defaultHandler = defaultCase
156
+ }
157
+
158
+ const handler = cases[key]
159
+
160
+ if (handler) {
161
+ return handler(value)
162
+ }
163
+
164
+ if (defaultHandler) {
165
+ return defaultHandler(value)
166
+ }
167
+
168
+ const availableCases = Object.keys(cases).join(', ')
169
+ const availableCasesSuffix =
170
+ availableCases.length > 0
171
+ ? ` Available cases: ${availableCases}`
172
+ : ' Available cases: (none)'
173
+ const contextSuffix = discriminantKey
174
+ ? ` (discriminant key: "${discriminantKey}")`
175
+ : ''
176
+
177
+ throw new Error(
178
+ `Unhandled case: ${key}${contextSuffix}.${availableCasesSuffix}`
179
+ )
180
+ }
181
+
182
+ // ═══════════════════════════════════════════════════════════════════════════
183
+ // Function overloads
184
+ // ═══════════════════════════════════════════════════════════════════════════
185
+
186
+ // String input - all cases required (no default)
187
+ export function typedSwitch<
188
+ T extends string,
189
+ Cases extends { [K in T]: (value: K) => unknown }
190
+ >(value: T, cases: Cases): RawCasesReturnType<Cases>
191
+
192
+ // String input - partial cases allowed (with default)
193
+ export function typedSwitch<
194
+ T extends string,
195
+ Cases extends { [K in T]?: (value: K) => unknown },
196
+ DefaultCase extends (value: T) => unknown
197
+ >(
198
+ value: T,
199
+ cases: Cases,
200
+ defaultCase: DefaultCase
201
+ ): RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>
202
+
203
+ // Object input with key - all cases required (no default)
204
+ export function typedSwitch<
205
+ T extends Record<string, unknown>,
206
+ K extends keyof T,
207
+ TKey extends T[K] & string,
208
+ Cases extends { [V in TKey]: (value: Extract<T, Record<K, V>>) => unknown }
209
+ >(value: T, key: K, cases: Cases): RawCasesReturnType<Cases>
210
+
211
+ // Object input with key - partial cases allowed (with default)
212
+ export function typedSwitch<
213
+ T extends Record<string, unknown>,
214
+ K extends keyof T,
215
+ TKey extends T[K] & string,
216
+ Cases extends { [V in TKey]?: (value: Extract<T, Record<K, V>>) => unknown },
217
+ DefaultCase extends (value: T) => unknown
218
+ >(
219
+ value: T,
220
+ key: K,
221
+ cases: Cases,
222
+ defaultCase: DefaultCase
223
+ ): RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>
224
+
225
+ // String input - all cases required with return constraint (no default)
226
+ export function typedSwitch<Constraint, T extends string = string>(
227
+ value: T,
228
+ cases: { [K in T]: (value: K) => Constraint }
229
+ ): Constraint
230
+ export function typedSwitch<Constraint, T extends string = string>(
231
+ value: T,
232
+ cases: { [K in T]: (value: K) => Constraint | Promise<Constraint> }
233
+ ): Constraint | Promise<Constraint>
234
+
235
+ // String input - partial cases allowed with return constraint (with default)
236
+ export function typedSwitch<Constraint, T extends string = string>(
237
+ value: T,
238
+ cases: { [K in T]?: (value: K) => Constraint },
239
+ defaultCase: (value: T) => Constraint
240
+ ): Constraint
241
+ export function typedSwitch<Constraint, T extends string = string>(
242
+ value: T,
243
+ cases: { [K in T]?: (value: K) => Constraint | Promise<Constraint> },
244
+ defaultCase: (value: T) => Constraint | Promise<Constraint>
245
+ ): Constraint | Promise<Constraint>
246
+
247
+ // Object input with key - all cases required with return constraint (no default)
248
+ export function typedSwitch<
249
+ Constraint,
250
+ T extends Record<string, unknown> = Record<string, unknown>,
251
+ K extends keyof T = keyof T
252
+ >(
253
+ value: T,
254
+ key: K,
255
+ cases: {
256
+ [V in T[K] & string]: (value: Extract<T, Record<K, V>>) => Constraint
257
+ }
258
+ ): Constraint
259
+ export function typedSwitch<
260
+ Constraint,
261
+ T extends Record<string, unknown> = Record<string, unknown>,
262
+ K extends keyof T = keyof T
263
+ >(
264
+ value: T,
265
+ key: K,
266
+ cases: {
267
+ [V in T[K] & string]: (
268
+ value: Extract<T, Record<K, V>>
269
+ ) => Constraint | Promise<Constraint>
270
+ }
271
+ ): Constraint | Promise<Constraint>
272
+
273
+ // Object input with key - partial cases allowed with return constraint (with default)
274
+ export function typedSwitch<
275
+ Constraint,
276
+ T extends Record<string, unknown> = Record<string, unknown>,
277
+ K extends keyof T = keyof T
278
+ >(
279
+ value: T,
280
+ key: K,
281
+ cases: {
282
+ [V in T[K] & string]?: (value: Extract<T, Record<K, V>>) => Constraint
283
+ },
284
+ defaultCase: (value: T) => Constraint
285
+ ): Constraint
286
+ export function typedSwitch<
287
+ Constraint,
288
+ T extends Record<string, unknown> = Record<string, unknown>,
289
+ K extends keyof T = keyof T
290
+ >(
291
+ value: T,
292
+ key: K,
293
+ cases: {
294
+ [V in T[K] & string]?: (
295
+ value: Extract<T, Record<K, V>>
296
+ ) => Constraint | Promise<Constraint>
297
+ },
298
+ defaultCase: (value: T) => Constraint | Promise<Constraint>
299
+ ): Constraint | Promise<Constraint>
300
+
301
+ // Implementation
302
+ export function typedSwitch(
303
+ value: string | Record<string, unknown>,
304
+ keyOrCases: string | CasesRecord,
305
+ casesOrDefault?: CasesRecord | Handler,
306
+ defaultCase?: Handler
307
+ ): unknown {
308
+ return runSwitch(value, keyOrCases, casesOrDefault, defaultCase)
309
+ }
@@ -0,0 +1,5 @@
1
+ export function getDomainFaviconUrl(domain: string, size = 128): string {
2
+ return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(
3
+ domain
4
+ )}&sz=${size}`
5
+ }
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod'
2
+
3
+ export { jsonToZodSchemaMapper } from './jsonToZodSchemaMapper'
4
+ export {
5
+ toGteLteFilter,
6
+ type ZodMinMaxFilter,
7
+ zodMinMaxFilter
8
+ } from './zodMinMaxFilter'
9
+ export { zodToExample, zodToTypeString } from './zodToTypeString'
10
+
11
+ export type GenericId<TypeName extends string> = string & z.$brand<TypeName>
12
+
13
+ export function createZId<TypeName extends string>(typeName: TypeName) {
14
+ return z.string().min(1, `${typeName} ID is required`).brand<TypeName>()
15
+ }
16
+
17
+ export function zSingleOrArray<T extends z.ZodType>(
18
+ schema: T
19
+ ): z.ZodUnion<readonly [z.ZodArray<T>, T]> {
20
+ return z.union([z.array(schema), schema])
21
+ }
22
+
23
+ export function normalizeSingleOrArray<T>(
24
+ value: T | readonly T[] | null | undefined
25
+ ): T[] {
26
+ if (value == null) return []
27
+ if (Array.isArray(value)) return [...(value as readonly T[])]
28
+ return [value as T]
29
+ }
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod'
2
+
3
+ type PropertyConfig<T, U> = {
4
+ [K in keyof T]?: (jsonObject: U) => T[K]
5
+ }
6
+
7
+ export function jsonToZodSchemaMapper<
8
+ T extends z.ZodType,
9
+ U extends Record<string, any>
10
+ >(
11
+ jsonObject: U,
12
+ zodSchema: T,
13
+ config: PropertyConfig<z.infer<T>, U>
14
+ ): z.infer<T> {
15
+ const result: Partial<z.infer<T>> = {}
16
+
17
+ const shape = zodSchema instanceof z.ZodObject ? zodSchema.shape : {}
18
+
19
+ for (const key of Object.keys(shape)) {
20
+ const fieldConfig = config[key as keyof z.infer<T>]
21
+ let value: any = jsonObject[key as keyof U]
22
+
23
+ if (fieldConfig) {
24
+ value = fieldConfig(jsonObject)
25
+ }
26
+
27
+ if (
28
+ shape[key] instanceof z.ZodObject &&
29
+ value &&
30
+ typeof value === 'object'
31
+ ) {
32
+ // @ts-expect-error recursive mapper narrows key by shape iteration
33
+ result[key] = jsonToZodSchemaMapper(
34
+ value,
35
+ shape[key] as z.ZodObject,
36
+ (config[key as keyof z.infer<T>] || {}) as PropertyConfig<any, any>
37
+ )
38
+ } else {
39
+ result[key as keyof z.infer<T>] = value
40
+ }
41
+ }
42
+
43
+ return zodSchema.parse(result)
44
+ }
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod'
2
+
3
+ export const zodMinMaxFilter = z.object({
4
+ min: z.number().optional(),
5
+ max: z.number().optional()
6
+ })
7
+
8
+ export type ZodMinMaxFilter = z.infer<typeof zodMinMaxFilter>
9
+
10
+ /**
11
+ * Maps a min/max filter to a generic `gte`/`lte` range object (e.g. ORM-friendly).
12
+ * Returns `undefined` if both bounds are undefined.
13
+ */
14
+ export function toGteLteFilter(
15
+ filter: ZodMinMaxFilter | undefined
16
+ ): { gte?: number; lte?: number } | undefined {
17
+ if (!filter) return undefined
18
+
19
+ const hasMin = filter.min !== undefined
20
+ const hasMax = filter.max !== undefined
21
+
22
+ if (!hasMin && !hasMax) return undefined
23
+
24
+ return {
25
+ ...(hasMin && { gte: filter.min }),
26
+ ...(hasMax && { lte: filter.max })
27
+ }
28
+ }
@@ -0,0 +1,222 @@
1
+ import type { z } from 'zod'
2
+
3
+ // Helper to safely get a property from an unknown def object
4
+ function getDefProp<T>(def: unknown, prop: string): T | undefined {
5
+ return (def as Record<string, unknown>)?.[prop] as T | undefined
6
+ }
7
+
8
+ /**
9
+ * Convert a Zod schema to a human-readable TypeScript-like interface string
10
+ */
11
+ export function zodToTypeString(schema: z.ZodType, indent = 0): string {
12
+ const pad = ' '.repeat(indent)
13
+ const innerPad = ' '.repeat(indent + 1)
14
+
15
+ // Get the schema definition - try both Zod 4 and Zod 3 patterns
16
+ const def =
17
+ (schema as unknown as { _zod?: { def: unknown } })._zod?.def ??
18
+ (schema as unknown as { _def: unknown })._def
19
+
20
+ if (!def) return 'unknown'
21
+
22
+ // Zod 4 uses 'type' property with lowercase values (e.g., 'object', 'string')
23
+ // Zod 3 uses 'typeName' with 'Zod' prefix (e.g., 'ZodObject', 'ZodString')
24
+ const schemaType =
25
+ getDefProp<string>(def, 'type') ?? getDefProp<string>(def, 'typeName')
26
+
27
+ switch (schemaType) {
28
+ // Zod 4 lowercase types
29
+ case 'object': {
30
+ const shape = getDefProp<Record<string, z.ZodType>>(def, 'shape')
31
+ if (!shape || Object.keys(shape).length === 0) return '{}'
32
+
33
+ const fields = Object.entries(shape).map(([key, value]) => {
34
+ // Zod 4: check value.type === 'optional', Zod 3: check _def.typeName
35
+ const valueType = getDefProp<string>(
36
+ (value as unknown as { _zod?: { def: unknown } })._zod?.def ??
37
+ (value as unknown as { _def?: unknown })._def,
38
+ 'type'
39
+ )
40
+ const isOptional = valueType === 'optional'
41
+ const innerSchema = isOptional
42
+ ? (getDefProp<z.ZodType>(
43
+ (value as unknown as { _zod?: { def: unknown } })._zod?.def ??
44
+ (value as unknown as { _def?: unknown })._def,
45
+ 'innerType'
46
+ ) ?? value)
47
+ : value
48
+ const typeStr = zodToTypeString(innerSchema, indent + 1)
49
+ return `${innerPad}${key}${isOptional ? '?' : ''}: ${typeStr}`
50
+ })
51
+
52
+ return `{\n${fields.join('\n')}\n${pad}}`
53
+ }
54
+
55
+ case 'array': {
56
+ // Zod 4 uses 'element', Zod 3 uses 'type'
57
+ const itemType =
58
+ getDefProp<z.ZodType>(def, 'element') ??
59
+ getDefProp<z.ZodType>(def, 'type')
60
+ return itemType ? `${zodToTypeString(itemType, indent)}[]` : 'unknown[]'
61
+ }
62
+
63
+ case 'string':
64
+ return 'string'
65
+
66
+ case 'number':
67
+ return 'number'
68
+
69
+ case 'boolean':
70
+ return 'boolean'
71
+
72
+ case 'date':
73
+ return 'Date'
74
+
75
+ case 'null':
76
+ return 'null'
77
+
78
+ case 'undefined':
79
+ return 'undefined'
80
+
81
+ case 'any':
82
+ return 'any'
83
+
84
+ case 'unknown':
85
+ return 'unknown'
86
+
87
+ case 'record': {
88
+ const valueType = getDefProp<z.ZodType>(def, 'valueType')
89
+ return valueType
90
+ ? `Record<string, ${zodToTypeString(valueType, indent)}>`
91
+ : 'Record<string, unknown>'
92
+ }
93
+
94
+ case 'enum': {
95
+ const values = getDefProp<string[]>(def, 'values')
96
+ return values ? values.map((v) => `"${v}"`).join(' | ') : 'string'
97
+ }
98
+
99
+ case 'literal': {
100
+ const value = getDefProp<unknown>(def, 'value')
101
+ return typeof value === 'string' ? `"${value}"` : String(value)
102
+ }
103
+
104
+ case 'union': {
105
+ const options = getDefProp<z.ZodType[]>(def, 'options')
106
+ return options
107
+ ? options.map((o) => zodToTypeString(o, indent)).join(' | ')
108
+ : 'unknown'
109
+ }
110
+
111
+ case 'optional': {
112
+ const inner = getDefProp<z.ZodType>(def, 'innerType')
113
+ return inner ? `${zodToTypeString(inner, indent)} | undefined` : 'unknown'
114
+ }
115
+
116
+ case 'nullable': {
117
+ const inner = getDefProp<z.ZodType>(def, 'innerType')
118
+ return inner ? `${zodToTypeString(inner, indent)} | null` : 'unknown'
119
+ }
120
+
121
+ case 'default': {
122
+ const inner = getDefProp<z.ZodType>(def, 'innerType')
123
+ return inner ? zodToTypeString(inner, indent) : 'unknown'
124
+ }
125
+
126
+ // Zod 3 compatibility (with 'Zod' prefix)
127
+ case 'ZodObject':
128
+ case 'ZodArray':
129
+ case 'ZodString':
130
+ case 'ZodNumber':
131
+ case 'ZodBoolean':
132
+ case 'ZodDate':
133
+ case 'ZodNull':
134
+ case 'ZodUndefined':
135
+ case 'ZodAny':
136
+ case 'ZodUnknown':
137
+ case 'ZodRecord':
138
+ case 'ZodEnum':
139
+ case 'ZodLiteral':
140
+ case 'ZodUnion':
141
+ case 'ZodOptional':
142
+ case 'ZodNullable':
143
+ case 'ZodDefault':
144
+ // For Zod 3, recurse with the lowercase version
145
+ return zodToTypeString(schema, indent)
146
+
147
+ default:
148
+ return 'unknown'
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Generate example JSON from a Zod schema
154
+ */
155
+ export function zodToExample(schema: z.ZodType): unknown {
156
+ const def =
157
+ (schema as unknown as { _zod?: { def: unknown } })._zod?.def ??
158
+ (schema as unknown as { _def: unknown })._def
159
+ if (!def) return null
160
+
161
+ // Zod 4 uses 'type', Zod 3 uses 'typeName'
162
+ const schemaType =
163
+ getDefProp<string>(def, 'type') ?? getDefProp<string>(def, 'typeName')
164
+
165
+ switch (schemaType) {
166
+ case 'object': {
167
+ const shape = getDefProp<Record<string, z.ZodType>>(def, 'shape')
168
+ if (!shape) return {}
169
+ const result: Record<string, unknown> = {}
170
+ for (const [key, value] of Object.entries(shape)) {
171
+ const valueType = getDefProp<string>(
172
+ (value as unknown as { _zod?: { def: unknown } })._zod?.def ??
173
+ (value as unknown as { _def?: unknown })._def,
174
+ 'type'
175
+ )
176
+ const isOptional = valueType === 'optional'
177
+ if (!isOptional) {
178
+ result[key] = zodToExample(value)
179
+ }
180
+ }
181
+ return result
182
+ }
183
+
184
+ case 'array': {
185
+ // Zod 4 uses 'element', Zod 3 uses 'type'
186
+ const itemType =
187
+ getDefProp<z.ZodType>(def, 'element') ??
188
+ getDefProp<z.ZodType>(def, 'type')
189
+ // Always return array with one item for better example
190
+ return itemType ? [zodToExample(itemType)] : []
191
+ }
192
+
193
+ case 'string':
194
+ return ''
195
+
196
+ case 'number':
197
+ return 0
198
+
199
+ case 'boolean':
200
+ return false
201
+
202
+ case 'enum': {
203
+ const values = getDefProp<string[]>(def, 'values')
204
+ return values?.[0] ?? ''
205
+ }
206
+
207
+ case 'literal':
208
+ return getDefProp<unknown>(def, 'value')
209
+
210
+ case 'optional':
211
+ case 'default': {
212
+ const inner = getDefProp<z.ZodType>(def, 'innerType')
213
+ return inner ? zodToExample(inner) : null
214
+ }
215
+
216
+ case 'record':
217
+ return {}
218
+
219
+ default:
220
+ return null
221
+ }
222
+ }