@geekmidas/schema 0.1.0 → 1.0.1

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/CHANGELOG.md +13 -0
  2. package/dist/{conversion-BJGLAqtd.mjs → conversion-CIBtqJkS.mjs} +20 -2
  3. package/dist/conversion-CIBtqJkS.mjs.map +1 -0
  4. package/dist/{conversion-CFlFrSru.cjs → conversion-Cf9R-jGc.cjs} +25 -1
  5. package/dist/conversion-Cf9R-jGc.cjs.map +1 -0
  6. package/dist/{conversion-CH_EmflL.d.cts → conversion-JY1Wixil.d.cts} +11 -2
  7. package/dist/conversion-JY1Wixil.d.cts.map +1 -0
  8. package/dist/{conversion-DUyZYTWO.d.mts → conversion-e4k9rTxy.d.mts} +11 -2
  9. package/dist/conversion-e4k9rTxy.d.mts.map +1 -0
  10. package/dist/conversion.cjs +2 -1
  11. package/dist/conversion.d.cts +2 -2
  12. package/dist/conversion.d.mts +2 -2
  13. package/dist/conversion.mjs +2 -2
  14. package/dist/index.cjs +1 -1
  15. package/dist/index.d.cts +3 -3
  16. package/dist/index.d.mts +3 -3
  17. package/dist/index.mjs +1 -1
  18. package/dist/{openapi-DR4_PG-e.d.cts → openapi-Aeb_1e9T.d.cts} +3 -1
  19. package/dist/openapi-Aeb_1e9T.d.cts.map +1 -0
  20. package/dist/openapi-DH5yCqKh.mjs.map +1 -1
  21. package/dist/openapi-DVvLYx-8.cjs.map +1 -1
  22. package/dist/{openapi-j01kFjpI.d.mts → openapi-Dxc4FuMP.d.mts} +3 -1
  23. package/dist/openapi-Dxc4FuMP.d.mts.map +1 -0
  24. package/dist/openapi.d.cts +1 -1
  25. package/dist/openapi.d.mts +1 -1
  26. package/dist/parser.cjs.map +1 -1
  27. package/dist/parser.d.cts +3 -1
  28. package/dist/parser.d.cts.map +1 -0
  29. package/dist/parser.d.mts +3 -1
  30. package/dist/parser.d.mts.map +1 -0
  31. package/dist/parser.mjs.map +1 -1
  32. package/dist/{types-ByLHeRGs.d.mts → types-D7ZRea_7.d.cts} +3 -1
  33. package/dist/types-D7ZRea_7.d.cts.map +1 -0
  34. package/dist/{types-DfcgE7cO.d.cts → types-o4dOkvCd.d.mts} +3 -1
  35. package/dist/types-o4dOkvCd.d.mts.map +1 -0
  36. package/dist/types.d.cts +1 -1
  37. package/dist/types.d.mts +1 -1
  38. package/package.json +1 -1
  39. package/src/__benchmarks__/conversion.bench.ts +67 -67
  40. package/src/__tests__/conversion.spec.ts +440 -257
  41. package/src/__tests__/openapi.spec.ts +390 -390
  42. package/src/__tests__/parser.spec.ts +202 -202
  43. package/src/conversion.ts +179 -154
  44. package/src/index.ts +8 -9
  45. package/src/openapi.ts +49 -49
  46. package/src/parser.ts +11 -11
  47. package/src/types.ts +16 -16
  48. package/tsconfig.json +9 -0
  49. package/tsdown.config.ts +1 -1
  50. package/dist/conversion-BJGLAqtd.mjs.map +0 -1
  51. package/dist/conversion-CFlFrSru.cjs.map +0 -1
@@ -1,319 +1,502 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
2
  import { z } from 'zod/v4';
3
3
  import {
4
- SchemaVendor,
5
- convertSchemaWithComponents,
6
- convertStandardSchemaToJsonSchema,
7
- getSchemaMetadata,
8
- getZodMetadata,
4
+ convertSchemaWithComponents,
5
+ convertStandardSchemaToJsonSchema,
6
+ getSchemaMetadata,
7
+ getZodMetadata,
8
+ SchemaVendor,
9
9
  } from '../conversion';
10
10
  import { createComponentCollector } from '../openapi';
11
11
 
12
12
  describe('Schema Conversion', () => {
13
- describe('convertStandardSchemaToJsonSchema', () => {
14
- it('should convert Zod object schema to JSON Schema', async () => {
15
- const schema = z.object({
16
- name: z.string(),
17
- age: z.number(),
18
- });
13
+ describe('convertStandardSchemaToJsonSchema', () => {
14
+ it('should convert Zod object schema to JSON Schema', async () => {
15
+ const schema = z.object({
16
+ name: z.string(),
17
+ age: z.number(),
18
+ });
19
19
 
20
- const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
20
+ const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
21
21
 
22
- expect(jsonSchema).toHaveProperty('type', 'object');
23
- expect(jsonSchema).toHaveProperty('properties');
24
- expect(jsonSchema.properties).toHaveProperty('name');
25
- expect(jsonSchema.properties).toHaveProperty('age');
26
- });
22
+ expect(jsonSchema).toHaveProperty('type', 'object');
23
+ expect(jsonSchema).toHaveProperty('properties');
24
+ expect(jsonSchema.properties).toHaveProperty('name');
25
+ expect(jsonSchema.properties).toHaveProperty('age');
26
+ });
27
27
 
28
- it('should return undefined for undefined schema', async () => {
29
- const jsonSchema = await convertStandardSchemaToJsonSchema(undefined);
28
+ it('should return undefined for undefined schema', async () => {
29
+ const jsonSchema = await convertStandardSchemaToJsonSchema(undefined);
30
30
 
31
- expect(jsonSchema).toBeUndefined();
32
- });
33
-
34
- it('should convert Zod string schema', async () => {
35
- const schema = z.string();
36
-
37
- const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
38
-
39
- expect(jsonSchema).toHaveProperty('type', 'string');
40
- });
41
-
42
- it('should convert Zod number schema', async () => {
43
- const schema = z.number();
44
-
45
- const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
46
-
47
- expect(jsonSchema).toHaveProperty('type', 'number');
48
- });
49
-
50
- it('should convert Zod array schema', async () => {
51
- const schema = z.array(z.string());
52
-
53
- const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
54
-
55
- expect(jsonSchema).toHaveProperty('type', 'array');
56
- expect(jsonSchema).toHaveProperty('items');
57
- });
31
+ expect(jsonSchema).toBeUndefined();
32
+ });
33
+
34
+ it('should convert Zod string schema', async () => {
35
+ const schema = z.string();
36
+
37
+ const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
38
+
39
+ expect(jsonSchema).toHaveProperty('type', 'string');
40
+ });
41
+
42
+ it('should convert Zod number schema', async () => {
43
+ const schema = z.number();
44
+
45
+ const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
46
+
47
+ expect(jsonSchema).toHaveProperty('type', 'number');
48
+ });
49
+
50
+ it('should convert Zod array schema', async () => {
51
+ const schema = z.array(z.string());
52
+
53
+ const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
54
+
55
+ expect(jsonSchema).toHaveProperty('type', 'array');
56
+ expect(jsonSchema).toHaveProperty('items');
57
+ });
58
58
 
59
- it('should convert Zod enum schema', async () => {
60
- const schema = z.enum(['active', 'inactive', 'pending']);
61
-
62
- const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
59
+ it('should convert Zod enum schema', async () => {
60
+ const schema = z.enum(['active', 'inactive', 'pending']);
61
+
62
+ const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
63
63
 
64
- expect(jsonSchema).toHaveProperty('enum');
65
- expect(jsonSchema.enum).toEqual(['active', 'inactive', 'pending']);
66
- });
64
+ expect(jsonSchema).toHaveProperty('enum');
65
+ expect(jsonSchema.enum).toEqual(['active', 'inactive', 'pending']);
66
+ });
67
67
 
68
- it('should convert nested object schema', async () => {
69
- const schema = z.object({
70
- user: z.object({
71
- name: z.string(),
72
- age: z.number(),
73
- }),
74
- });
68
+ it('should convert nested object schema', async () => {
69
+ const schema = z.object({
70
+ user: z.object({
71
+ name: z.string(),
72
+ age: z.number(),
73
+ }),
74
+ });
75
75
 
76
- const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
76
+ const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
77
77
 
78
- expect(jsonSchema.type).toBe('object');
79
- expect(jsonSchema.properties).toHaveProperty('user');
80
- expect(jsonSchema.properties.user).toHaveProperty('type', 'object');
81
- });
78
+ expect(jsonSchema.type).toBe('object');
79
+ expect(jsonSchema.properties).toHaveProperty('user');
80
+ expect(jsonSchema.properties.user).toHaveProperty('type', 'object');
81
+ });
82
82
 
83
- it('should handle optional fields', async () => {
84
- const schema = z.object({
85
- required: z.string(),
86
- optional: z.string().optional(),
87
- });
83
+ it('should handle optional fields', async () => {
84
+ const schema = z.object({
85
+ required: z.string(),
86
+ optional: z.string().optional(),
87
+ });
88
88
 
89
- const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
89
+ const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
90
90
 
91
- expect(jsonSchema.required).toContain('required');
92
- expect(jsonSchema.required).not.toContain('optional');
93
- });
91
+ expect(jsonSchema.required).toContain('required');
92
+ expect(jsonSchema.required).not.toContain('optional');
93
+ });
94
94
 
95
- it('should extract and convert $defs with component collector', async () => {
96
- const userSchema = z.object({
97
- name: z.string(),
98
- age: z.number(),
99
- });
95
+ it('should extract and convert $defs with component collector', async () => {
96
+ const userSchema = z.object({
97
+ name: z.string(),
98
+ age: z.number(),
99
+ });
100
100
 
101
- const schema = z.object({
102
- user: userSchema,
103
- });
101
+ const schema = z.object({
102
+ user: userSchema,
103
+ });
104
104
 
105
- const collector = createComponentCollector();
106
- const jsonSchema = await convertStandardSchemaToJsonSchema(
107
- schema,
108
- collector,
109
- );
105
+ const collector = createComponentCollector();
106
+ const jsonSchema = await convertStandardSchemaToJsonSchema(
107
+ schema,
108
+ collector,
109
+ );
110
110
 
111
- expect(jsonSchema).toBeDefined();
112
- // $defs should be removed from main schema
113
- expect(jsonSchema).not.toHaveProperty('$defs');
114
- });
111
+ expect(jsonSchema).toBeDefined();
112
+ // $defs should be removed from main schema
113
+ expect(jsonSchema).not.toHaveProperty('$defs');
114
+ });
115
115
 
116
- it('should throw error for unsupported vendor', async () => {
117
- const invalidSchema = {
118
- '~standard': {
119
- vendor: 'unsupported' as any,
120
- validate: vi.fn(),
121
- },
122
- };
116
+ it('should throw error for unsupported vendor', async () => {
117
+ const invalidSchema = {
118
+ '~standard': {
119
+ vendor: 'unsupported' as any,
120
+ validate: vi.fn(),
121
+ },
122
+ };
123
123
 
124
- await expect(
125
- convertStandardSchemaToJsonSchema(invalidSchema as any),
126
- ).rejects.toThrow(/Unsupported or missing vendor/);
127
- });
124
+ await expect(
125
+ convertStandardSchemaToJsonSchema(invalidSchema as any),
126
+ ).rejects.toThrow(/Unsupported or missing vendor/);
127
+ });
128
128
 
129
- it('should throw error for missing vendor', async () => {
130
- const invalidSchema = {
131
- '~standard': {
132
- vendor: undefined,
133
- validate: vi.fn(),
134
- },
135
- };
129
+ it('should throw error for missing vendor', async () => {
130
+ const invalidSchema = {
131
+ '~standard': {
132
+ vendor: undefined,
133
+ validate: vi.fn(),
134
+ },
135
+ };
136
136
 
137
- await expect(
138
- convertStandardSchemaToJsonSchema(invalidSchema as any),
139
- ).rejects.toThrow(/Unsupported or missing vendor/);
140
- });
137
+ await expect(
138
+ convertStandardSchemaToJsonSchema(invalidSchema as any),
139
+ ).rejects.toThrow(/Unsupported or missing vendor/);
140
+ });
141
141
 
142
- it('should handle schema with descriptions', async () => {
143
- const schema = z
144
- .object({
145
- name: z.string(),
146
- })
147
- .describe('User information');
142
+ it('should handle schema with descriptions', async () => {
143
+ const schema = z
144
+ .object({
145
+ name: z.string(),
146
+ })
147
+ .describe('User information');
148
148
 
149
- const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
149
+ const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
150
150
 
151
- expect(jsonSchema.description).toBe('User information');
152
- });
151
+ expect(jsonSchema.description).toBe('User information');
152
+ });
153
153
 
154
- it('should handle union types', async () => {
155
- const schema = z.union([z.string(), z.number()]);
154
+ it('should handle union types', async () => {
155
+ const schema = z.union([z.string(), z.number()]);
156
156
 
157
- const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
157
+ const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
158
158
 
159
- expect(jsonSchema).toHaveProperty('anyOf');
160
- });
161
- });
159
+ expect(jsonSchema).toHaveProperty('anyOf');
160
+ });
161
+ });
162
162
 
163
- describe('getZodMetadata', () => {
164
- it('should return undefined for non-Zod objects', async () => {
165
- const schema = z.string();
163
+ describe('getZodMetadata', () => {
164
+ it('should return undefined for non-Zod objects', async () => {
165
+ const schema = z.string();
166
166
 
167
- const metadata = await getZodMetadata(schema);
167
+ const metadata = await getZodMetadata(schema);
168
168
 
169
- expect(metadata).toBeUndefined();
170
- });
169
+ expect(metadata).toBeUndefined();
170
+ });
171
171
 
172
- it('should get metadata from Zod object with meta', async () => {
173
- const schema = z.object({ name: z.string() }).meta({ id: 'User' });
172
+ it('should get metadata from Zod object with meta', async () => {
173
+ const schema = z.object({ name: z.string() }).meta({ id: 'User' });
174
174
 
175
- const metadata = await getZodMetadata(schema);
175
+ const metadata = await getZodMetadata(schema);
176
176
 
177
- expect(metadata).toEqual({ id: 'User' });
178
- });
177
+ expect(metadata).toEqual({ id: 'User' });
178
+ });
179
179
 
180
- it('should return undefined for Zod object without meta', async () => {
181
- const schema = z.object({ name: z.string() });
180
+ it('should return undefined for Zod object without meta', async () => {
181
+ const schema = z.object({ name: z.string() });
182
182
 
183
- const metadata = await getZodMetadata(schema);
183
+ const metadata = await getZodMetadata(schema);
184
184
 
185
- // Returns undefined when no meta is set
186
- expect(metadata).toBeUndefined();
187
- });
188
- });
185
+ // Returns undefined when no meta is set
186
+ expect(metadata).toBeUndefined();
187
+ });
188
+ });
189
189
 
190
- describe('getSchemaMetadata', () => {
191
- it('should get metadata for Zod schema', async () => {
192
- const schema = z.object({ name: z.string() }).meta({ id: 'UserMeta' });
190
+ describe('getSchemaMetadata', () => {
191
+ it('should get metadata for Zod schema', async () => {
192
+ const schema = z.object({ name: z.string() }).meta({ id: 'UserMeta' });
193
193
 
194
- const metadata = await getSchemaMetadata(schema);
194
+ const metadata = await getSchemaMetadata(schema);
195
195
 
196
- expect(metadata).toEqual({ id: 'UserMeta' });
197
- });
196
+ expect(metadata).toEqual({ id: 'UserMeta' });
197
+ });
198
198
 
199
- it('should return undefined for non-Zod vendor', async () => {
200
- const schema = {
201
- '~standard': {
202
- vendor: 'valibot',
203
- validate: vi.fn(),
204
- },
205
- };
199
+ it('should return undefined for non-Zod vendor', async () => {
200
+ const schema = {
201
+ '~standard': {
202
+ vendor: 'valibot',
203
+ validate: vi.fn(),
204
+ },
205
+ };
206
206
 
207
- const metadata = await getSchemaMetadata(schema as any);
207
+ const metadata = await getSchemaMetadata(schema as any);
208
208
 
209
- expect(metadata).toBeUndefined();
210
- });
209
+ expect(metadata).toBeUndefined();
210
+ });
211
211
 
212
- it('should return undefined for schema without vendor', async () => {
213
- const schema = {
214
- '~standard': {
215
- vendor: undefined,
216
- validate: vi.fn(),
217
- },
218
- };
212
+ it('should return undefined for schema without vendor', async () => {
213
+ const schema = {
214
+ '~standard': {
215
+ vendor: undefined,
216
+ validate: vi.fn(),
217
+ },
218
+ };
219
219
 
220
- const metadata = await getSchemaMetadata(schema as any);
220
+ const metadata = await getSchemaMetadata(schema as any);
221
221
 
222
- expect(metadata).toBeUndefined();
223
- });
224
- });
222
+ expect(metadata).toBeUndefined();
223
+ });
224
+ });
225
225
 
226
- describe('convertSchemaWithComponents', () => {
227
- it('should convert schema without component collector', async () => {
228
- const schema = z.object({
229
- name: z.string(),
230
- age: z.number(),
231
- });
226
+ describe('convertSchemaWithComponents', () => {
227
+ it('should convert schema without component collector', async () => {
228
+ const schema = z.object({
229
+ name: z.string(),
230
+ age: z.number(),
231
+ });
232
232
 
233
- const jsonSchema = await convertSchemaWithComponents(schema);
233
+ const jsonSchema = await convertSchemaWithComponents(schema);
234
234
 
235
- expect(jsonSchema).toHaveProperty('type', 'object');
236
- expect(jsonSchema).toHaveProperty('properties');
237
- });
235
+ expect(jsonSchema).toHaveProperty('type', 'object');
236
+ expect(jsonSchema).toHaveProperty('properties');
237
+ });
238
238
 
239
- it('should return undefined for undefined schema', async () => {
240
- const jsonSchema = await convertSchemaWithComponents(undefined);
239
+ it('should return undefined for undefined schema', async () => {
240
+ const jsonSchema = await convertSchemaWithComponents(undefined);
241
241
 
242
- expect(jsonSchema).toBeUndefined();
243
- });
242
+ expect(jsonSchema).toBeUndefined();
243
+ });
244
244
 
245
- it('should add schema with ID to component collector', async () => {
246
- const schema = z.object({ name: z.string() }).meta({ id: 'UserComp' });
245
+ it('should add schema with ID to component collector', async () => {
246
+ const schema = z.object({ name: z.string() }).meta({ id: 'UserComp' });
247
247
 
248
- const collector = createComponentCollector();
249
- const result = await convertSchemaWithComponents(schema, collector);
248
+ const collector = createComponentCollector();
249
+ const result = await convertSchemaWithComponents(schema, collector);
250
250
 
251
- expect(result).toEqual({ $ref: '#/components/schemas/UserComp' });
252
- expect(collector.schemas).toHaveProperty('UserComp');
253
- expect(collector.schemas.UserComp).not.toHaveProperty('id');
254
- });
251
+ expect(result).toEqual({ $ref: '#/components/schemas/UserComp' });
252
+ expect(collector.schemas).toHaveProperty('UserComp');
253
+ expect(collector.schemas.UserComp).not.toHaveProperty('id');
254
+ });
255
255
 
256
- it('should not add schema without ID to component collector', async () => {
257
- const schema = z.object({ name: z.string() });
256
+ it('should not add schema without ID to component collector', async () => {
257
+ const schema = z.object({ name: z.string() });
258
258
 
259
- const collector = createComponentCollector();
260
- const result = await convertSchemaWithComponents(schema, collector);
261
-
262
- expect(result).not.toHaveProperty('$ref');
263
- expect(Object.keys(collector.schemas)).toHaveLength(0);
264
- });
265
-
266
- it('should handle schema with id in JSON Schema', async () => {
267
- const schema = z.object({ name: z.string() }).meta({ id: 'Person' });
268
-
269
- const collector = createComponentCollector();
270
- const result = await convertSchemaWithComponents(schema, collector);
271
-
272
- expect(result).toEqual({ $ref: '#/components/schemas/Person' });
273
- expect(collector.schemas).toHaveProperty('Person');
274
- });
275
-
276
- it('should convert multiple schemas with collector', async () => {
277
- const schema1 = z.object({ name: z.string() }).meta({ id: 'UserMulti' });
278
- const schema2 = z.object({ title: z.string() }).meta({ id: 'PostMulti' });
279
-
280
- const collector = createComponentCollector();
281
-
282
- await convertSchemaWithComponents(schema1, collector);
283
- await convertSchemaWithComponents(schema2, collector);
284
-
285
- expect(Object.keys(collector.schemas)).toHaveLength(2);
286
- expect(collector.schemas).toHaveProperty('UserMulti');
287
- expect(collector.schemas).toHaveProperty('PostMulti');
288
- });
289
-
290
- it('should handle nested objects with collector', async () => {
291
- const schema = z
292
- .object({
293
- user: z.object({
294
- name: z.string(),
295
- profile: z.object({
296
- bio: z.string(),
297
- }),
298
- }),
299
- })
300
- .meta({ id: 'UserData' });
301
-
302
- const collector = createComponentCollector();
303
- const result = await convertSchemaWithComponents(schema, collector);
304
-
305
- expect(result).toEqual({ $ref: '#/components/schemas/UserData' });
306
- expect(collector.schemas).toHaveProperty('UserData');
307
- });
308
- });
309
-
310
- describe('SchemaVendor enum', () => {
311
- it('should have zod vendor', () => {
312
- expect(SchemaVendor.zod).toBe('zod');
313
- });
314
-
315
- it('should have valibot vendor', () => {
316
- expect(SchemaVendor.valibot).toBe('valibot');
317
- });
318
- });
259
+ const collector = createComponentCollector();
260
+ const result = await convertSchemaWithComponents(schema, collector);
261
+
262
+ expect(result).not.toHaveProperty('$ref');
263
+ expect(Object.keys(collector.schemas)).toHaveLength(0);
264
+ });
265
+
266
+ it('should handle schema with id in JSON Schema', async () => {
267
+ const schema = z.object({ name: z.string() }).meta({ id: 'Person' });
268
+
269
+ const collector = createComponentCollector();
270
+ const result = await convertSchemaWithComponents(schema, collector);
271
+
272
+ expect(result).toEqual({ $ref: '#/components/schemas/Person' });
273
+ expect(collector.schemas).toHaveProperty('Person');
274
+ });
275
+
276
+ it('should convert multiple schemas with collector', async () => {
277
+ const schema1 = z.object({ name: z.string() }).meta({ id: 'UserMulti' });
278
+ const schema2 = z.object({ title: z.string() }).meta({ id: 'PostMulti' });
279
+
280
+ const collector = createComponentCollector();
281
+
282
+ await convertSchemaWithComponents(schema1, collector);
283
+ await convertSchemaWithComponents(schema2, collector);
284
+
285
+ expect(Object.keys(collector.schemas)).toHaveLength(2);
286
+ expect(collector.schemas).toHaveProperty('UserMulti');
287
+ expect(collector.schemas).toHaveProperty('PostMulti');
288
+ });
289
+
290
+ it('should handle nested objects with collector', async () => {
291
+ const schema = z
292
+ .object({
293
+ user: z.object({
294
+ name: z.string(),
295
+ profile: z.object({
296
+ bio: z.string(),
297
+ }),
298
+ }),
299
+ })
300
+ .meta({ id: 'UserData' });
301
+
302
+ const collector = createComponentCollector();
303
+ const result = await convertSchemaWithComponents(schema, collector);
304
+
305
+ expect(result).toEqual({ $ref: '#/components/schemas/UserData' });
306
+ expect(collector.schemas).toHaveProperty('UserData');
307
+ });
308
+ });
309
+
310
+ describe('SchemaVendor enum', () => {
311
+ it('should have zod vendor', () => {
312
+ expect(SchemaVendor.zod).toBe('zod');
313
+ });
314
+
315
+ it('should have valibot vendor', () => {
316
+ expect(SchemaVendor.valibot).toBe('valibot');
317
+ });
318
+ });
319
+
320
+ describe('extractAndConvertDefs edge cases', () => {
321
+ it('should handle $ref that does not start with #/$defs/', async () => {
322
+ // Create a schema with a $ref that doesn't use $defs pattern
323
+ const schema = z.object({
324
+ name: z.string(),
325
+ });
326
+
327
+ const collector = createComponentCollector();
328
+ // Add a schema first
329
+ collector.addSchema('ExternalRef', { type: 'string' });
330
+
331
+ const jsonSchema = await convertStandardSchemaToJsonSchema(
332
+ schema,
333
+ collector,
334
+ );
335
+
336
+ // Basic test - the schema should still convert properly
337
+ expect(jsonSchema).toHaveProperty('type', 'object');
338
+ });
339
+
340
+ it('should process arrays in schema correctly', async () => {
341
+ const schema = z.object({
342
+ tags: z.array(z.string()),
343
+ numbers: z.array(z.number()),
344
+ });
345
+
346
+ const collector = createComponentCollector();
347
+ const jsonSchema = await convertStandardSchemaToJsonSchema(
348
+ schema,
349
+ collector,
350
+ );
351
+
352
+ expect(jsonSchema.properties.tags).toHaveProperty('type', 'array');
353
+ expect(jsonSchema.properties.numbers).toHaveProperty('type', 'array');
354
+ });
355
+
356
+ it('should skip $defs key during processing and extract to components', async () => {
357
+ // Create a schema that produces $defs in JSON Schema output
358
+ const addressSchema = z.object({
359
+ street: z.string(),
360
+ city: z.string(),
361
+ });
362
+
363
+ const personSchema = z.object({
364
+ name: z.string(),
365
+ address: addressSchema,
366
+ });
367
+
368
+ const collector = createComponentCollector();
369
+ const jsonSchema = await convertStandardSchemaToJsonSchema(
370
+ personSchema,
371
+ collector,
372
+ );
373
+
374
+ // $defs should be removed from the output
375
+ expect(jsonSchema).not.toHaveProperty('$defs');
376
+ expect(jsonSchema).toHaveProperty('type', 'object');
377
+ });
378
+
379
+ it('should handle null/undefined values in schema processing', async () => {
380
+ const schema = z.object({
381
+ nullableField: z.string().nullable(),
382
+ optionalField: z.string().optional(),
383
+ });
384
+
385
+ const collector = createComponentCollector();
386
+ const jsonSchema = await convertStandardSchemaToJsonSchema(
387
+ schema,
388
+ collector,
389
+ );
390
+
391
+ expect(jsonSchema).toHaveProperty('properties');
392
+ expect(jsonSchema.properties).toHaveProperty('nullableField');
393
+ expect(jsonSchema.properties).toHaveProperty('optionalField');
394
+ });
395
+
396
+ it('should handle deeply nested objects with arrays', async () => {
397
+ const schema = z.object({
398
+ users: z.array(
399
+ z.object({
400
+ name: z.string(),
401
+ addresses: z.array(
402
+ z.object({
403
+ street: z.string(),
404
+ }),
405
+ ),
406
+ }),
407
+ ),
408
+ });
409
+
410
+ const collector = createComponentCollector();
411
+ const jsonSchema = await convertStandardSchemaToJsonSchema(
412
+ schema,
413
+ collector,
414
+ );
415
+
416
+ expect(jsonSchema).toHaveProperty('type', 'object');
417
+ expect(jsonSchema.properties.users).toHaveProperty('type', 'array');
418
+ });
419
+
420
+ it('should handle primitive non-object schema types', async () => {
421
+ const stringSchema = z.string();
422
+ const numberSchema = z.number();
423
+ const booleanSchema = z.boolean();
424
+
425
+ const collector = createComponentCollector();
426
+
427
+ const stringJson = await convertStandardSchemaToJsonSchema(
428
+ stringSchema,
429
+ collector,
430
+ );
431
+ const numberJson = await convertStandardSchemaToJsonSchema(
432
+ numberSchema,
433
+ collector,
434
+ );
435
+ const booleanJson = await convertStandardSchemaToJsonSchema(
436
+ booleanSchema,
437
+ collector,
438
+ );
439
+
440
+ expect(stringJson).toMatchObject({ type: 'string' });
441
+ expect(numberJson).toMatchObject({ type: 'number' });
442
+ expect(booleanJson).toMatchObject({ type: 'boolean' });
443
+ });
444
+
445
+ it('should handle schema with discriminated unions', async () => {
446
+ const catSchema = z.object({
447
+ type: z.literal('cat'),
448
+ meows: z.boolean(),
449
+ });
450
+
451
+ const dogSchema = z.object({
452
+ type: z.literal('dog'),
453
+ barks: z.boolean(),
454
+ });
455
+
456
+ const petSchema = z.discriminatedUnion('type', [catSchema, dogSchema]);
457
+
458
+ const collector = createComponentCollector();
459
+ const jsonSchema = await convertStandardSchemaToJsonSchema(
460
+ petSchema,
461
+ collector,
462
+ );
463
+
464
+ // Should have oneOf or anyOf for discriminated union
465
+ expect(
466
+ jsonSchema.anyOf || jsonSchema.oneOf || jsonSchema.discriminator,
467
+ ).toBeDefined();
468
+ });
469
+
470
+ it('should preserve $ref as-is when not matching $defs pattern', async () => {
471
+ // Test the extractAndConvertDefs function with a schema that has
472
+ // $ref not starting with #/$defs/
473
+ const schema = z.object({
474
+ name: z.string(),
475
+ });
476
+
477
+ // Without collector, refs should be preserved as-is
478
+ const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
479
+
480
+ expect(jsonSchema).toHaveProperty('type', 'object');
481
+ });
482
+
483
+ it('should convert $defs references to component references with collector', async () => {
484
+ // Use lazy schema to create a recursive structure that produces $defs
485
+ const nodeSchema: z.ZodTypeAny = z.lazy(() =>
486
+ z.object({
487
+ value: z.string(),
488
+ children: z.array(nodeSchema).optional(),
489
+ }),
490
+ );
491
+
492
+ const collector = createComponentCollector();
493
+ const jsonSchema = await convertStandardSchemaToJsonSchema(
494
+ nodeSchema,
495
+ collector,
496
+ );
497
+
498
+ // $defs should be processed and potentially converted
499
+ expect(jsonSchema).not.toHaveProperty('$defs');
500
+ });
501
+ });
319
502
  });