@forgehive/schema 0.1.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.
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const index_1 = __importDefault(require("../index"));
7
+ describe('Schema Record Types', () => {
8
+ describe('String Record', () => {
9
+ it('should validate a record with string values', () => {
10
+ const schema = new index_1.default({
11
+ data: index_1.default.stringRecord(),
12
+ });
13
+ const result = schema.validate({
14
+ data: {
15
+ firstName: 'John',
16
+ lastName: 'Doe',
17
+ occupation: 'Developer'
18
+ }
19
+ });
20
+ expect(result).toBe(true);
21
+ });
22
+ it('should reject a record with non-string values', () => {
23
+ const schema = new index_1.default({
24
+ data: index_1.default.stringRecord(),
25
+ });
26
+ const result = schema.validate({
27
+ data: {
28
+ name: 'John',
29
+ age: 30, // Number is not allowed
30
+ isActive: true // Boolean is not allowed
31
+ }
32
+ });
33
+ expect(result).toBe(false);
34
+ });
35
+ it('should create a schema from description with string record type', () => {
36
+ const description = {
37
+ data: {
38
+ type: 'stringRecord',
39
+ optional: false
40
+ }
41
+ };
42
+ const schema = index_1.default.from(description);
43
+ const validData = {
44
+ data: {
45
+ firstName: 'John',
46
+ lastName: 'Doe'
47
+ }
48
+ };
49
+ const result = schema.validate(validData);
50
+ expect(result).toBe(true);
51
+ });
52
+ });
53
+ describe('Number Record', () => {
54
+ it('should validate a record with number values', () => {
55
+ const schema = new index_1.default({
56
+ data: index_1.default.numberRecord(),
57
+ });
58
+ const result = schema.validate({
59
+ data: {
60
+ age: 30,
61
+ experience: 5,
62
+ salary: 100000
63
+ }
64
+ });
65
+ expect(result).toBe(true);
66
+ });
67
+ it('should reject a record with non-number values', () => {
68
+ const schema = new index_1.default({
69
+ data: index_1.default.numberRecord(),
70
+ });
71
+ const result = schema.validate({
72
+ data: {
73
+ age: 30,
74
+ name: 'John', // String is not allowed
75
+ isActive: true // Boolean is not allowed
76
+ }
77
+ });
78
+ expect(result).toBe(false);
79
+ });
80
+ it('should create a schema from description with number record type', () => {
81
+ const description = {
82
+ data: {
83
+ type: 'numberRecord',
84
+ optional: false
85
+ }
86
+ };
87
+ const schema = index_1.default.from(description);
88
+ const validData = {
89
+ data: {
90
+ age: 30,
91
+ experience: 5
92
+ }
93
+ };
94
+ const result = schema.validate(validData);
95
+ expect(result).toBe(true);
96
+ });
97
+ });
98
+ describe('Boolean Record', () => {
99
+ it('should validate a record with boolean values', () => {
100
+ const schema = new index_1.default({
101
+ data: index_1.default.booleanRecord(),
102
+ });
103
+ const result = schema.validate({
104
+ data: {
105
+ isActive: true,
106
+ isAdmin: false,
107
+ hasAccess: true
108
+ }
109
+ });
110
+ expect(result).toBe(true);
111
+ });
112
+ it('should reject a record with non-boolean values', () => {
113
+ const schema = new index_1.default({
114
+ data: index_1.default.booleanRecord(),
115
+ });
116
+ const result = schema.validate({
117
+ data: {
118
+ isActive: true,
119
+ name: 'John', // String is not allowed
120
+ age: 30 // Number is not allowed
121
+ }
122
+ });
123
+ expect(result).toBe(false);
124
+ });
125
+ it('should create a schema from description with boolean record type', () => {
126
+ const description = {
127
+ data: {
128
+ type: 'booleanRecord',
129
+ optional: false
130
+ }
131
+ };
132
+ const schema = index_1.default.from(description);
133
+ const validData = {
134
+ data: {
135
+ isActive: true,
136
+ isAdmin: false
137
+ }
138
+ };
139
+ const result = schema.validate(validData);
140
+ expect(result).toBe(true);
141
+ });
142
+ });
143
+ describe('Mixed Record', () => {
144
+ it('should validate a record with mixed values (string, number, boolean)', () => {
145
+ const schema = new index_1.default({
146
+ data: index_1.default.mixedRecord(),
147
+ });
148
+ const result = schema.validate({
149
+ data: {
150
+ name: 'John',
151
+ age: 30,
152
+ isActive: true
153
+ }
154
+ });
155
+ expect(result).toBe(true);
156
+ });
157
+ it('should validate a record with only string values', () => {
158
+ const schema = new index_1.default({
159
+ data: index_1.default.mixedRecord(),
160
+ });
161
+ const result = schema.validate({
162
+ data: {
163
+ firstName: 'John',
164
+ lastName: 'Doe',
165
+ occupation: 'Developer'
166
+ }
167
+ });
168
+ expect(result).toBe(true);
169
+ });
170
+ it('should validate a record with only number values', () => {
171
+ const schema = new index_1.default({
172
+ data: index_1.default.mixedRecord(),
173
+ });
174
+ const result = schema.validate({
175
+ data: {
176
+ age: 30,
177
+ experience: 5,
178
+ salary: 100000
179
+ }
180
+ });
181
+ expect(result).toBe(true);
182
+ });
183
+ it('should validate a record with only boolean values', () => {
184
+ const schema = new index_1.default({
185
+ data: index_1.default.mixedRecord(),
186
+ });
187
+ const result = schema.validate({
188
+ data: {
189
+ isActive: true,
190
+ isAdmin: false,
191
+ hasAccess: true
192
+ }
193
+ });
194
+ expect(result).toBe(true);
195
+ });
196
+ it('should reject a record with invalid value types', () => {
197
+ const schema = new index_1.default({
198
+ data: index_1.default.mixedRecord(),
199
+ });
200
+ const result = schema.validate({
201
+ data: {
202
+ name: 'John',
203
+ createdAt: new Date(), // Date is not allowed
204
+ items: [1, 2, 3] // Array is not allowed
205
+ }
206
+ });
207
+ expect(result).toBe(false);
208
+ });
209
+ it('should create a schema from description with mixed record type', () => {
210
+ const description = {
211
+ data: {
212
+ type: 'mixedRecord',
213
+ optional: false
214
+ }
215
+ };
216
+ const schema = index_1.default.from(description);
217
+ const validData = {
218
+ data: {
219
+ name: 'John',
220
+ age: 30,
221
+ isActive: true
222
+ }
223
+ };
224
+ const result = schema.validate(validData);
225
+ expect(result).toBe(true);
226
+ });
227
+ });
228
+ describe('Common Record Functionality', () => {
229
+ it('should reject a record with non-string keys', () => {
230
+ const schema = new index_1.default({
231
+ data: index_1.default.stringRecord(),
232
+ });
233
+ // TypeScript would catch this at compile time, but we're testing runtime behavior
234
+ const invalidData = {
235
+ data: {
236
+ name: 'John',
237
+ }
238
+ };
239
+ // Add a non-string key using Object.defineProperty
240
+ Object.defineProperty(invalidData.data, 123, {
241
+ value: 'test',
242
+ enumerable: true
243
+ });
244
+ // Note: Zod's record validation doesn't actually check for non-string keys at runtime
245
+ // This is a limitation of JavaScript/TypeScript, as all object keys are converted to strings
246
+ // So this test will actually pass, not fail
247
+ const result = schema.validate(invalidData);
248
+ expect(result).toBe(true);
249
+ });
250
+ it('should create a schema from description with optional record type', () => {
251
+ const description = {
252
+ data: {
253
+ type: 'stringRecord',
254
+ optional: true
255
+ }
256
+ };
257
+ const schema = index_1.default.from(description);
258
+ // Test with record present
259
+ const validData1 = {
260
+ data: {
261
+ name: 'John'
262
+ }
263
+ };
264
+ // Test with record missing
265
+ const validData2 = {};
266
+ expect(schema.validate(validData1)).toBe(true);
267
+ expect(schema.validate(validData2)).toBe(true);
268
+ });
269
+ it('should describe a schema with string record type', () => {
270
+ const schema = new index_1.default({
271
+ data: index_1.default.stringRecord(),
272
+ });
273
+ const description = schema.describe();
274
+ expect(description).toHaveProperty('data');
275
+ expect(description.data).toHaveProperty('type', 'stringRecord');
276
+ // The optional property is only included when it's true, not when it's false
277
+ });
278
+ it('should describe a schema with number record type', () => {
279
+ const schema = new index_1.default({
280
+ data: index_1.default.numberRecord(),
281
+ });
282
+ const description = schema.describe();
283
+ expect(description).toHaveProperty('data');
284
+ expect(description.data).toHaveProperty('type', 'numberRecord');
285
+ });
286
+ it('should describe a schema with boolean record type', () => {
287
+ const schema = new index_1.default({
288
+ data: index_1.default.booleanRecord(),
289
+ });
290
+ const description = schema.describe();
291
+ expect(description).toHaveProperty('data');
292
+ expect(description.data).toHaveProperty('type', 'booleanRecord');
293
+ });
294
+ it('should describe a schema with mixed record type', () => {
295
+ const schema = new index_1.default({
296
+ data: index_1.default.mixedRecord(),
297
+ });
298
+ const description = schema.describe();
299
+ expect(description).toHaveProperty('data');
300
+ expect(description.data).toHaveProperty('type', 'mixedRecord');
301
+ });
302
+ });
303
+ });
package/jest.config.js ADDED
@@ -0,0 +1,11 @@
1
+ /** @type {import('jest').Config} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+ roots: ['<rootDir>/src'],
6
+ testMatch: ['**/src/test/**/*.ts', '**/?(*.)+(spec|test).ts'],
7
+ transform: {
8
+ '^.+\\.ts$': 'ts-jest'
9
+ },
10
+ moduleFileExtensions: ['ts', 'js', 'json', 'node']
11
+ }
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@forgehive/schema",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "devDependencies": {
8
+ "@types/jest": "^29.5.14",
9
+ "jest": "^29.7.0",
10
+ "ts-jest": "^29.1.2"
11
+ },
12
+ "dependencies": {
13
+ "zod": "^3.24.2"
14
+ },
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "test": "jest",
18
+ "test:watch": "jest --watch"
19
+ }
20
+ }
package/src/index.ts ADDED
@@ -0,0 +1,361 @@
1
+ import { z } from 'zod'
2
+
3
+ // Export a type alias for Schema fields
4
+ export type SchemaType = z.ZodType<string | boolean | number | Date | string[] | boolean[] | number[] | Date[] | Record<string, string | number | boolean>> | z.ZodOptional<z.ZodType<string | boolean | number | Date | string[] | boolean[] | number[] | Date[] | Record<string, string | number | boolean>>>;
5
+
6
+ type AllowedBaseTypes = 'string' | 'boolean' | 'number' | 'date' | 'stringRecord' | 'numberRecord' | 'booleanRecord' | 'mixedRecord'
7
+ type ArrayTypes = z.ZodString | z.ZodBoolean | z.ZodNumber | z.ZodDate
8
+
9
+ type NumberValidations = {
10
+ min?: number
11
+ max?: number
12
+ }
13
+
14
+ type StringValidations = {
15
+ email?: boolean
16
+ minLength?: number
17
+ maxLength?: number
18
+ regex?: string
19
+ }
20
+
21
+ // Export extended Zod types for use throughout the app
22
+ export type ShadowString = z.ZodString
23
+ export type ShadowBoolean = z.ZodBoolean
24
+ export type ShadowNumber = z.ZodNumber
25
+ export type ShadowDate = z.ZodDate
26
+ export type ShadowArray<T extends ArrayTypes> = z.ZodArray<T>
27
+ export type ShadowStringRecord = z.ZodRecord<z.ZodString, z.ZodString>
28
+ export type ShadowNumberRecord = z.ZodRecord<z.ZodString, z.ZodNumber>
29
+ export type ShadowBooleanRecord = z.ZodRecord<z.ZodString, z.ZodBoolean>
30
+ export type ShadowMixedRecord = z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>
31
+
32
+ // Export inferred types for use throughout the app
33
+ export type InferShadowString = z.infer<ShadowString>
34
+ export type InferShadowBoolean = z.infer<ShadowBoolean>
35
+ export type InferShadowNumber = z.infer<ShadowNumber>
36
+ export type InferShadowDate = z.infer<ShadowDate>
37
+ export type InferShadowArray<T extends ArrayTypes> = z.infer<ShadowArray<T>>
38
+ export type InferShadowStringRecord = z.infer<ShadowStringRecord>
39
+ export type InferShadowNumberRecord = z.infer<ShadowNumberRecord>
40
+ export type InferShadowBooleanRecord = z.infer<ShadowBooleanRecord>
41
+ export type InferShadowMixedRecord = z.infer<ShadowMixedRecord>
42
+
43
+ // Export a type utility for inferring schema types
44
+ export type InferSchema<S extends Schema<Record<string, SchemaType>>> = z.infer<S['schema']>
45
+
46
+ type BaseSchemaDescription = {
47
+ type: AllowedBaseTypes
48
+ optional?: boolean
49
+ validations?: NumberValidations | StringValidations
50
+ }
51
+
52
+ type ArraySchemaDescription = {
53
+ type: 'array'
54
+ items: {
55
+ type: AllowedBaseTypes
56
+ }
57
+ optional?: boolean
58
+ }
59
+
60
+ export type SchemaDescription = Record<string, BaseSchemaDescription | ArraySchemaDescription>
61
+
62
+ // Static methods for type definitions
63
+ export class Schema<T extends Record<string, z.ZodType<string | boolean | number | Date | string[] | boolean[] | number[] | Date[] | Record<string, string | number | boolean>> | z.ZodOptional<z.ZodType<string | boolean | number | Date | string[] | boolean[] | number[] | Date[] | Record<string, string | number | boolean>>>>> {
64
+ readonly schema: z.ZodObject<T>
65
+
66
+ constructor(fields: T) {
67
+ this.schema = z.object(fields)
68
+ }
69
+
70
+ /**
71
+ * Creates a string schema
72
+ * @returns A string schema
73
+ */
74
+ static string(): ShadowString {
75
+ return z.string()
76
+ }
77
+
78
+ /**
79
+ * Creates a boolean schema
80
+ * @returns A boolean schema
81
+ */
82
+ static boolean(): ShadowBoolean {
83
+ return z.boolean()
84
+ }
85
+
86
+ /**
87
+ * Creates a number schema
88
+ * @returns A number schema
89
+ */
90
+ static number(): ShadowNumber {
91
+ return z.number()
92
+ }
93
+
94
+ /**
95
+ * Creates a date schema
96
+ * @returns A date schema
97
+ */
98
+ static date(): ShadowDate {
99
+ return z.date()
100
+ }
101
+
102
+ /**
103
+ * Creates a record schema with string keys and string values
104
+ * @returns A record schema with string values
105
+ */
106
+ static stringRecord(): ShadowStringRecord {
107
+ return z.record(z.string(), z.string())
108
+ }
109
+
110
+ /**
111
+ * Creates a record schema with string keys and number values
112
+ * @returns A record schema with number values
113
+ */
114
+ static numberRecord(): ShadowNumberRecord {
115
+ return z.record(z.string(), z.number())
116
+ }
117
+
118
+ /**
119
+ * Creates a record schema with string keys and boolean values
120
+ * @returns A record schema with boolean values
121
+ */
122
+ static booleanRecord(): ShadowBooleanRecord {
123
+ return z.record(z.string(), z.boolean())
124
+ }
125
+
126
+ /**
127
+ * Creates a record schema with string keys and mixed values (string, number, or boolean)
128
+ * @returns A record schema with mixed values
129
+ */
130
+ static mixedRecord(): ShadowMixedRecord {
131
+ return z.record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
132
+ }
133
+
134
+ /**
135
+ * Creates an array schema
136
+ * @param type The type of items in the array
137
+ * @returns An array schema
138
+ */
139
+ static array<T extends ArrayTypes>(type: T): ShadowArray<T> {
140
+ return z.array(type) as ShadowArray<T>
141
+ }
142
+
143
+ /**
144
+ * Infers the TypeScript type from a Schema instance
145
+ * @template S The Schema type
146
+ * @returns The inferred TypeScript type
147
+ */
148
+ static infer<S extends Schema<Record<string, z.ZodTypeAny>>>(_schema: S): z.infer<S['schema']> {
149
+ // This is a type-level utility, the implementation is not used at runtime
150
+ return {} as z.infer<S['schema']>
151
+ }
152
+
153
+ /**
154
+ * Creates a Schema instance from a description object
155
+ * @param description Object describing the schema structure with type information
156
+ * @returns A new Schema instance
157
+ */
158
+ static from(description: SchemaDescription): Schema<Record<string, z.ZodType<string | boolean | number | Date | string[] | boolean[] | number[] | Date[] | Record<string, string | number | boolean>> | z.ZodOptional<z.ZodType<string | boolean | number | Date | string[] | boolean[] | number[] | Date[] | Record<string, string | number | boolean>>>>> {
159
+ const fields: Record<string, z.ZodType<string | number | boolean | Date | string[] | number[] | boolean[] | Date[] | Record<string, string | number | boolean>> | z.ZodOptional<z.ZodType<string | number | boolean | Date | string[] | number[] | boolean[] | Date[] | Record<string, string | number | boolean>>>> = {}
160
+
161
+ for (const [key, field] of Object.entries(description)) {
162
+ const fieldType = field.type
163
+ let fieldSchema: z.ZodType<string | boolean | number | Date | string[] | boolean[] | number[] | Date[] | Record<string, string | number | boolean>>
164
+
165
+ switch (fieldType) {
166
+ case 'string': {
167
+ let stringSchema = Schema.string()
168
+ if (field.validations) {
169
+ const validations = field.validations as StringValidations
170
+ if (validations.email) {
171
+ stringSchema = stringSchema.email()
172
+ }
173
+ if (validations.minLength !== undefined) {
174
+ stringSchema = stringSchema.min(validations.minLength)
175
+ }
176
+ if (validations.maxLength !== undefined) {
177
+ stringSchema = stringSchema.max(validations.maxLength)
178
+ }
179
+ if (validations.regex !== undefined) {
180
+ stringSchema = stringSchema.regex(new RegExp(validations.regex))
181
+ }
182
+ }
183
+ fieldSchema = stringSchema
184
+ break
185
+ }
186
+ case 'boolean':
187
+ fieldSchema = Schema.boolean()
188
+ break
189
+ case 'number': {
190
+ let numberSchema = Schema.number()
191
+ if (field.validations) {
192
+ const validations = field.validations as NumberValidations
193
+ if (validations.min !== undefined) {
194
+ numberSchema = numberSchema.min(validations.min)
195
+ }
196
+ if (validations.max !== undefined) {
197
+ numberSchema = numberSchema.max(validations.max)
198
+ }
199
+ }
200
+ fieldSchema = numberSchema
201
+ break
202
+ }
203
+ case 'date':
204
+ fieldSchema = Schema.date()
205
+ break
206
+ case 'stringRecord':
207
+ fieldSchema = Schema.stringRecord()
208
+ break
209
+ case 'numberRecord':
210
+ fieldSchema = Schema.numberRecord()
211
+ break
212
+ case 'booleanRecord':
213
+ fieldSchema = Schema.booleanRecord()
214
+ break
215
+ case 'mixedRecord':
216
+ fieldSchema = Schema.mixedRecord()
217
+ break
218
+ case 'array': {
219
+ const arrayField = field as ArraySchemaDescription
220
+ switch (arrayField.items.type) {
221
+ case 'string':
222
+ fieldSchema = Schema.array(Schema.string())
223
+ break
224
+ case 'boolean':
225
+ fieldSchema = Schema.array(Schema.boolean())
226
+ break
227
+ case 'number':
228
+ fieldSchema = Schema.array(Schema.number())
229
+ break
230
+ case 'date':
231
+ fieldSchema = Schema.array(Schema.date())
232
+ break
233
+ default:
234
+ throw new Error(`Unsupported array item type: ${arrayField.items.type}`)
235
+ }
236
+ break
237
+ }
238
+ default:
239
+ throw new Error(`Unsupported type: ${fieldType}`)
240
+ }
241
+
242
+ fields[key] = field.optional ? fieldSchema.optional() : fieldSchema
243
+ }
244
+
245
+ return new Schema(fields)
246
+ }
247
+
248
+ /**
249
+ * Validates the provided data against the schema
250
+ * @param data The data to validate
251
+ * @returns A boolean indicating whether the data is valid
252
+ */
253
+ validate(data: unknown): boolean {
254
+ const result = this.schema.safeParse(data)
255
+
256
+ return result.success
257
+ }
258
+
259
+ /**
260
+ * Parses and validates the provided data against the schema
261
+ * @param data The data to parse and validate
262
+ * @returns The parsed and typed data
263
+ * @throws {z.ZodError} If the data is invalid
264
+ */
265
+ parse(data: unknown): z.infer<z.ZodObject<T>> {
266
+ return this.schema.parse(data)
267
+ }
268
+
269
+ /**
270
+ * Safely parses and validates the provided data against the schema
271
+ * @param data The data to parse and validate
272
+ * @returns An object containing either the successfully parsed data or error information
273
+ */
274
+ safeParse(data: unknown): z.SafeParseReturnType<z.infer<z.ZodObject<T>>, z.infer<z.ZodObject<T>>> {
275
+ return this.schema.safeParse(data)
276
+ }
277
+
278
+ /**
279
+ * Describes the schema structure and allowed types
280
+ * @returns An object describing the schema structure with type information
281
+ */
282
+ describe(): SchemaDescription {
283
+ const shape = this.schema.shape
284
+ const description: SchemaDescription = {}
285
+
286
+ for (const [key, value] of Object.entries(shape)) {
287
+ const isOptional = value instanceof z.ZodOptional
288
+ const baseValue = isOptional ? value.unwrap() : value
289
+
290
+ if (baseValue instanceof z.ZodString) {
291
+ const validations: StringValidations = {}
292
+ if (baseValue._def.checks) {
293
+ for (const check of baseValue._def.checks) {
294
+ if (check.kind === 'email') {
295
+ validations.email = true
296
+ } else if (check.kind === 'min') {
297
+ validations.minLength = check.value
298
+ } else if (check.kind === 'max') {
299
+ validations.maxLength = check.value
300
+ } else if (check.kind === 'regex') {
301
+ validations.regex = check.regex.toString().replace(/^\/|\/$/g, '').replace(/\\\//g, '/')
302
+ }
303
+ }
304
+ }
305
+ description[key] = {
306
+ type: 'string',
307
+ ...(isOptional && { optional: true }),
308
+ ...(Object.keys(validations).length > 0 && { validations })
309
+ }
310
+ } else if (baseValue instanceof z.ZodBoolean) {
311
+ description[key] = { type: 'boolean', ...(isOptional && { optional: true }) }
312
+ } else if (baseValue instanceof z.ZodNumber) {
313
+ const validations: NumberValidations = {}
314
+ if (baseValue._def.checks) {
315
+ for (const check of baseValue._def.checks) {
316
+ if (check.kind === 'min') {
317
+ validations.min = check.value
318
+ } else if (check.kind === 'max') {
319
+ validations.max = check.value
320
+ }
321
+ }
322
+ }
323
+ description[key] = {
324
+ type: 'number',
325
+ ...(isOptional && { optional: true }),
326
+ ...(Object.keys(validations).length > 0 && { validations })
327
+ }
328
+ } else if (baseValue instanceof z.ZodDate) {
329
+ description[key] = { type: 'date', ...(isOptional && { optional: true }) }
330
+ } else if (baseValue instanceof z.ZodArray) {
331
+ const element = baseValue.element
332
+ if (element instanceof z.ZodString) {
333
+ description[key] = { type: 'array', items: { type: 'string' }, ...(isOptional && { optional: true }) }
334
+ } else if (element instanceof z.ZodBoolean) {
335
+ description[key] = { type: 'array', items: { type: 'boolean' }, ...(isOptional && { optional: true }) }
336
+ } else if (element instanceof z.ZodNumber) {
337
+ description[key] = { type: 'array', items: { type: 'number' }, ...(isOptional && { optional: true }) }
338
+ } else if (element instanceof z.ZodDate) {
339
+ description[key] = { type: 'array', items: { type: 'date' }, ...(isOptional && { optional: true }) }
340
+ }
341
+ } else if (baseValue instanceof z.ZodRecord) {
342
+ // Check the value type of the record
343
+ const valueType = baseValue._def.valueType
344
+ if (valueType instanceof z.ZodString) {
345
+ description[key] = { type: 'stringRecord', ...(isOptional && { optional: true }) }
346
+ } else if (valueType instanceof z.ZodNumber) {
347
+ description[key] = { type: 'numberRecord', ...(isOptional && { optional: true }) }
348
+ } else if (valueType instanceof z.ZodBoolean) {
349
+ description[key] = { type: 'booleanRecord', ...(isOptional && { optional: true }) }
350
+ } else if (valueType instanceof z.ZodUnion) {
351
+ description[key] = { type: 'mixedRecord', ...(isOptional && { optional: true }) }
352
+ }
353
+ }
354
+ }
355
+
356
+ return description
357
+ }
358
+ }
359
+
360
+ export default Schema
361
+