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.
- package/README.md +510 -0
- package/dist/api-wrapper/index.d.ts +84 -0
- package/dist/api-wrapper/index.js +250 -0
- package/dist/array/index.d.ts +22 -0
- package/dist/cache/index.d.ts +19 -0
- package/dist/date/index.d.ts +11 -0
- package/dist/gzip/index.d.ts +2 -0
- package/dist/gzip/index.js +16 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1565 -0
- package/dist/jwt/index.d.ts +3 -0
- package/dist/jwt/index.js +19 -0
- package/dist/math/index.d.ts +2 -0
- package/dist/math/linearRegressionTrend.d.ts +5 -0
- package/dist/math/metricsSeriesColors.d.ts +42 -0
- package/dist/p-queue/index.d.ts +95 -0
- package/dist/p-queue/index.js +439 -0
- package/dist/promise/index.d.ts +21 -0
- package/dist/sitemap/index.d.ts +14 -0
- package/dist/sitemap/index.js +552 -0
- package/dist/state/index.d.ts +101 -0
- package/dist/string/index.d.ts +8 -0
- package/dist/typedswitch/index.d.ts +124 -0
- package/dist/url/index.d.ts +1 -0
- package/dist/zod/index.d.ts +8 -0
- package/dist/zod/index.js +210 -0
- package/dist/zod/jsonToZodSchemaMapper.d.ts +6 -0
- package/dist/zod/zodMinMaxFilter.d.ts +14 -0
- package/dist/zod/zodToTypeString.d.ts +9 -0
- package/package.json +102 -0
- package/src/api-wrapper/index.ts +492 -0
- package/src/array/index.ts +187 -0
- package/src/cache/index.ts +154 -0
- package/src/date/index.ts +54 -0
- package/src/gzip/index.ts +29 -0
- package/src/index.ts +12 -0
- package/src/jwt/index.ts +25 -0
- package/src/math/index.ts +11 -0
- package/src/math/linearRegressionTrend.ts +36 -0
- package/src/math/metricsSeriesColors.ts +208 -0
- package/src/p-queue/index.ts +690 -0
- package/src/promise/index.ts +73 -0
- package/src/sitemap/index.ts +195 -0
- package/src/state/index.ts +331 -0
- package/src/string/index.ts +86 -0
- package/src/typedswitch/index.ts +309 -0
- package/src/url/index.ts +5 -0
- package/src/zod/index.ts +29 -0
- package/src/zod/jsonToZodSchemaMapper.ts +44 -0
- package/src/zod/zodMinMaxFilter.ts +28 -0
- 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
|
+
}
|
package/src/url/index.ts
ADDED
package/src/zod/index.ts
ADDED
|
@@ -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
|
+
}
|