@ai-sdk/provider-utils 4.0.4 → 4.0.6

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 (165) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/index.js +6 -4
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +6 -4
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +4 -2
  7. package/src/__snapshots__/schema.test.ts.snap +346 -0
  8. package/src/add-additional-properties-to-json-schema.test.ts +289 -0
  9. package/src/add-additional-properties-to-json-schema.ts +53 -0
  10. package/src/combine-headers.ts +11 -0
  11. package/src/convert-async-iterator-to-readable-stream.test.ts +78 -0
  12. package/src/convert-async-iterator-to-readable-stream.ts +47 -0
  13. package/src/convert-image-model-file-to-data-uri.test.ts +85 -0
  14. package/src/convert-image-model-file-to-data-uri.ts +19 -0
  15. package/src/convert-to-form-data.test.ts +167 -0
  16. package/src/convert-to-form-data.ts +61 -0
  17. package/src/create-tool-name-mapping.test.ts +163 -0
  18. package/src/create-tool-name-mapping.ts +66 -0
  19. package/src/delay.test.ts +212 -0
  20. package/src/delay.ts +47 -0
  21. package/src/delayed-promise.test.ts +132 -0
  22. package/src/delayed-promise.ts +61 -0
  23. package/src/download-blob.test.ts +145 -0
  24. package/src/download-blob.ts +31 -0
  25. package/src/download-error.ts +39 -0
  26. package/src/extract-response-headers.ts +9 -0
  27. package/src/fetch-function.ts +4 -0
  28. package/src/generate-id.test.ts +31 -0
  29. package/src/generate-id.ts +57 -0
  30. package/src/get-error-message.ts +15 -0
  31. package/src/get-from-api.test.ts +199 -0
  32. package/src/get-from-api.ts +97 -0
  33. package/src/get-runtime-environment-user-agent.test.ts +47 -0
  34. package/src/get-runtime-environment-user-agent.ts +24 -0
  35. package/src/handle-fetch-error.ts +39 -0
  36. package/src/index.ts +67 -0
  37. package/src/inject-json-instruction.test.ts +404 -0
  38. package/src/inject-json-instruction.ts +63 -0
  39. package/src/is-abort-error.ts +8 -0
  40. package/src/is-async-iterable.ts +3 -0
  41. package/src/is-non-nullable.ts +12 -0
  42. package/src/is-url-supported.test.ts +282 -0
  43. package/src/is-url-supported.ts +40 -0
  44. package/src/load-api-key.ts +45 -0
  45. package/src/load-optional-setting.ts +30 -0
  46. package/src/load-setting.ts +62 -0
  47. package/src/maybe-promise-like.ts +3 -0
  48. package/src/media-type-to-extension.test.ts +26 -0
  49. package/src/media-type-to-extension.ts +22 -0
  50. package/src/normalize-headers.test.ts +64 -0
  51. package/src/normalize-headers.ts +38 -0
  52. package/src/parse-json-event-stream.ts +33 -0
  53. package/src/parse-json.test.ts +191 -0
  54. package/src/parse-json.ts +122 -0
  55. package/src/parse-provider-options.ts +32 -0
  56. package/src/post-to-api.ts +166 -0
  57. package/src/provider-tool-factory.ts +125 -0
  58. package/src/remove-undefined-entries.test.ts +57 -0
  59. package/src/remove-undefined-entries.ts +12 -0
  60. package/src/resolve.test.ts +125 -0
  61. package/src/resolve.ts +17 -0
  62. package/src/response-handler.test.ts +89 -0
  63. package/src/response-handler.ts +187 -0
  64. package/src/schema.test-d.ts +11 -0
  65. package/src/schema.test.ts +502 -0
  66. package/src/schema.ts +267 -0
  67. package/src/secure-json-parse.test.ts +59 -0
  68. package/src/secure-json-parse.ts +92 -0
  69. package/src/test/convert-array-to-async-iterable.ts +9 -0
  70. package/src/test/convert-array-to-readable-stream.ts +15 -0
  71. package/src/test/convert-async-iterable-to-array.ts +9 -0
  72. package/src/test/convert-readable-stream-to-array.ts +14 -0
  73. package/src/test/convert-response-stream-to-array.ts +9 -0
  74. package/src/test/index.ts +7 -0
  75. package/src/test/is-node-version.ts +4 -0
  76. package/src/test/mock-id.ts +8 -0
  77. package/src/to-json-schema/zod3-to-json-schema/LICENSE +16 -0
  78. package/src/to-json-schema/zod3-to-json-schema/README.md +24 -0
  79. package/src/to-json-schema/zod3-to-json-schema/get-relative-path.ts +7 -0
  80. package/src/to-json-schema/zod3-to-json-schema/index.ts +1 -0
  81. package/src/to-json-schema/zod3-to-json-schema/options.ts +98 -0
  82. package/src/to-json-schema/zod3-to-json-schema/parse-def.test.ts +224 -0
  83. package/src/to-json-schema/zod3-to-json-schema/parse-def.ts +109 -0
  84. package/src/to-json-schema/zod3-to-json-schema/parse-types.ts +57 -0
  85. package/src/to-json-schema/zod3-to-json-schema/parsers/any.ts +5 -0
  86. package/src/to-json-schema/zod3-to-json-schema/parsers/array.test.ts +98 -0
  87. package/src/to-json-schema/zod3-to-json-schema/parsers/array.ts +38 -0
  88. package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.test.ts +51 -0
  89. package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.ts +44 -0
  90. package/src/to-json-schema/zod3-to-json-schema/parsers/boolean.ts +7 -0
  91. package/src/to-json-schema/zod3-to-json-schema/parsers/branded.test.ts +16 -0
  92. package/src/to-json-schema/zod3-to-json-schema/parsers/branded.ts +7 -0
  93. package/src/to-json-schema/zod3-to-json-schema/parsers/catch.test.ts +15 -0
  94. package/src/to-json-schema/zod3-to-json-schema/parsers/catch.ts +7 -0
  95. package/src/to-json-schema/zod3-to-json-schema/parsers/date.test.ts +97 -0
  96. package/src/to-json-schema/zod3-to-json-schema/parsers/date.ts +64 -0
  97. package/src/to-json-schema/zod3-to-json-schema/parsers/default.test.ts +54 -0
  98. package/src/to-json-schema/zod3-to-json-schema/parsers/default.ts +14 -0
  99. package/src/to-json-schema/zod3-to-json-schema/parsers/effects.test.ts +41 -0
  100. package/src/to-json-schema/zod3-to-json-schema/parsers/effects.ts +14 -0
  101. package/src/to-json-schema/zod3-to-json-schema/parsers/enum.ts +13 -0
  102. package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.test.ts +92 -0
  103. package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.ts +52 -0
  104. package/src/to-json-schema/zod3-to-json-schema/parsers/literal.ts +29 -0
  105. package/src/to-json-schema/zod3-to-json-schema/parsers/map.test.ts +48 -0
  106. package/src/to-json-schema/zod3-to-json-schema/parsers/map.ts +47 -0
  107. package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.test.ts +102 -0
  108. package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.ts +31 -0
  109. package/src/to-json-schema/zod3-to-json-schema/parsers/never.ts +9 -0
  110. package/src/to-json-schema/zod3-to-json-schema/parsers/null.ts +9 -0
  111. package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.test.ts +67 -0
  112. package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.ts +42 -0
  113. package/src/to-json-schema/zod3-to-json-schema/parsers/number.test.ts +65 -0
  114. package/src/to-json-schema/zod3-to-json-schema/parsers/number.ts +44 -0
  115. package/src/to-json-schema/zod3-to-json-schema/parsers/object.test.ts +149 -0
  116. package/src/to-json-schema/zod3-to-json-schema/parsers/object.ts +88 -0
  117. package/src/to-json-schema/zod3-to-json-schema/parsers/optional.test.ts +147 -0
  118. package/src/to-json-schema/zod3-to-json-schema/parsers/optional.ts +23 -0
  119. package/src/to-json-schema/zod3-to-json-schema/parsers/pipe.test.ts +35 -0
  120. package/src/to-json-schema/zod3-to-json-schema/parsers/pipeline.ts +29 -0
  121. package/src/to-json-schema/zod3-to-json-schema/parsers/promise.test.ts +15 -0
  122. package/src/to-json-schema/zod3-to-json-schema/parsers/promise.ts +11 -0
  123. package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.test.ts +20 -0
  124. package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.ts +7 -0
  125. package/src/to-json-schema/zod3-to-json-schema/parsers/record.test.ts +108 -0
  126. package/src/to-json-schema/zod3-to-json-schema/parsers/record.ts +71 -0
  127. package/src/to-json-schema/zod3-to-json-schema/parsers/set.test.ts +20 -0
  128. package/src/to-json-schema/zod3-to-json-schema/parsers/set.ts +35 -0
  129. package/src/to-json-schema/zod3-to-json-schema/parsers/string.test.ts +438 -0
  130. package/src/to-json-schema/zod3-to-json-schema/parsers/string.ts +426 -0
  131. package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.test.ts +33 -0
  132. package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.ts +61 -0
  133. package/src/to-json-schema/zod3-to-json-schema/parsers/undefined.ts +11 -0
  134. package/src/to-json-schema/zod3-to-json-schema/parsers/union.test.ts +226 -0
  135. package/src/to-json-schema/zod3-to-json-schema/parsers/union.ts +144 -0
  136. package/src/to-json-schema/zod3-to-json-schema/parsers/unknown.ts +7 -0
  137. package/src/to-json-schema/zod3-to-json-schema/refs.test.ts +919 -0
  138. package/src/to-json-schema/zod3-to-json-schema/refs.ts +39 -0
  139. package/src/to-json-schema/zod3-to-json-schema/select-parser.ts +115 -0
  140. package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.test.ts +862 -0
  141. package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.ts +93 -0
  142. package/src/types/assistant-model-message.ts +39 -0
  143. package/src/types/content-part.ts +379 -0
  144. package/src/types/data-content.ts +4 -0
  145. package/src/types/execute-tool.ts +27 -0
  146. package/src/types/index.ts +40 -0
  147. package/src/types/model-message.ts +14 -0
  148. package/src/types/provider-options.ts +9 -0
  149. package/src/types/system-model-message.ts +20 -0
  150. package/src/types/tool-approval-request.ts +16 -0
  151. package/src/types/tool-approval-response.ts +27 -0
  152. package/src/types/tool-call.ts +31 -0
  153. package/src/types/tool-model-message.ts +23 -0
  154. package/src/types/tool-result.ts +35 -0
  155. package/src/types/tool.test-d.ts +193 -0
  156. package/src/types/tool.ts +324 -0
  157. package/src/types/user-model-message.ts +22 -0
  158. package/src/uint8-utils.ts +26 -0
  159. package/src/validate-types.test.ts +105 -0
  160. package/src/validate-types.ts +81 -0
  161. package/src/version.ts +6 -0
  162. package/src/with-user-agent-suffix.test.ts +84 -0
  163. package/src/with-user-agent-suffix.ts +27 -0
  164. package/src/without-trailing-slash.ts +3 -0
  165. package/LICENSE +0 -13
@@ -0,0 +1,862 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { JSONSchema7 } from '@ai-sdk/provider';
3
+ import { z } from 'zod/v3';
4
+ import {
5
+ ignoreOverride,
6
+ jsonDescription,
7
+ PostProcessCallback,
8
+ } from './options';
9
+ import { zod3ToJsonSchema } from './zod3-to-json-schema';
10
+
11
+ describe('zod3-to-json-schema', () => {
12
+ describe('override', () => {
13
+ it('the readme example', () => {
14
+ expect(
15
+ zod3ToJsonSchema(
16
+ z.object({
17
+ ignoreThis: z.string(),
18
+ overrideThis: z.string(),
19
+ removeThis: z.string(),
20
+ }),
21
+ {
22
+ override: (def, refs) => {
23
+ const path = refs.currentPath.join('/');
24
+
25
+ if (path === '#/properties/overrideThis') {
26
+ return {
27
+ type: 'integer',
28
+ };
29
+ }
30
+
31
+ if (path === '#/properties/removeThis') {
32
+ return undefined;
33
+ }
34
+
35
+ // Important! Do not return `undefined` or void unless you want to remove the property from the resulting schema completely.
36
+ return ignoreOverride;
37
+ },
38
+ },
39
+ ),
40
+ ).toStrictEqual({
41
+ $schema: 'http://json-schema.org/draft-07/schema#',
42
+ type: 'object',
43
+ required: ['ignoreThis', 'overrideThis'],
44
+ properties: {
45
+ ignoreThis: {
46
+ type: 'string',
47
+ },
48
+ overrideThis: {
49
+ type: 'integer',
50
+ },
51
+ },
52
+ additionalProperties: false,
53
+ });
54
+ });
55
+ });
56
+
57
+ describe('postProcess', () => {
58
+ it('the readme example', () => {
59
+ const zodSchema = z.object({
60
+ myString: z.string().describe(
61
+ JSON.stringify({
62
+ title: 'My string',
63
+ description: 'My description',
64
+ examples: ['Foo', 'Bar'],
65
+ }),
66
+ ),
67
+ myNumber: z.number(),
68
+ });
69
+
70
+ // Define the callback to be used to process the output using the PostProcessCallback type:
71
+ const postProcess: PostProcessCallback = (
72
+ // The original output produced by the package itself:
73
+ jsonSchema,
74
+ // The ZodSchema def used to produce the original schema:
75
+ def,
76
+ // The refs object containing the current path, passed options, etc.
77
+ refs,
78
+ ) => {
79
+ if (!jsonSchema) {
80
+ return jsonSchema;
81
+ }
82
+
83
+ // Try to expand description as JSON meta:
84
+ if (jsonSchema.description) {
85
+ try {
86
+ jsonSchema = {
87
+ ...jsonSchema,
88
+ ...JSON.parse(jsonSchema.description),
89
+ };
90
+ } catch {}
91
+ }
92
+
93
+ // Make all numbers nullable:
94
+ if ('type' in jsonSchema! && jsonSchema.type === 'number') {
95
+ jsonSchema.type = ['number', 'null'];
96
+ }
97
+
98
+ // Add the refs path, just because
99
+ (jsonSchema as any).path = refs.currentPath;
100
+
101
+ return jsonSchema;
102
+ };
103
+
104
+ const jsonSchemaResult = zod3ToJsonSchema(zodSchema, {
105
+ postProcess,
106
+ });
107
+
108
+ expect(jsonSchemaResult).toStrictEqual({
109
+ $schema: 'http://json-schema.org/draft-07/schema#',
110
+ type: 'object',
111
+ required: ['myString', 'myNumber'],
112
+ properties: {
113
+ myString: {
114
+ type: 'string',
115
+ title: 'My string',
116
+ description: 'My description',
117
+ examples: ['Foo', 'Bar'],
118
+ path: ['#', 'properties', 'myString'],
119
+ },
120
+ myNumber: {
121
+ type: ['number', 'null'],
122
+ path: ['#', 'properties', 'myNumber'],
123
+ },
124
+ },
125
+ additionalProperties: false,
126
+ path: ['#'],
127
+ });
128
+ });
129
+
130
+ it('expanding description json', () => {
131
+ const zodSchema = z.string().describe(
132
+ JSON.stringify({
133
+ title: 'My string',
134
+ description: 'My description',
135
+ examples: ['Foo', 'Bar'],
136
+ whatever: 123,
137
+ }),
138
+ );
139
+
140
+ const jsonSchemaResult = zod3ToJsonSchema(zodSchema, {
141
+ postProcess: jsonDescription,
142
+ });
143
+
144
+ expect(jsonSchemaResult).toStrictEqual({
145
+ $schema: 'http://json-schema.org/draft-07/schema#',
146
+ type: 'string',
147
+ title: 'My string',
148
+ description: 'My description',
149
+ examples: ['Foo', 'Bar'],
150
+ whatever: 123,
151
+ });
152
+ });
153
+ });
154
+
155
+ it('should return the schema directly in the root if no name is passed', () => {
156
+ expect(zod3ToJsonSchema(z.any())).toStrictEqual({
157
+ $schema: 'http://json-schema.org/draft-07/schema#',
158
+ } satisfies JSONSchema7);
159
+ });
160
+
161
+ it('should return the schema inside a named property in "definitions" if a name is passed', () => {
162
+ expect(zod3ToJsonSchema(z.any(), 'MySchema')).toStrictEqual({
163
+ $schema: 'http://json-schema.org/draft-07/schema#',
164
+ $ref: `#/definitions/MySchema`,
165
+ definitions: {
166
+ MySchema: {},
167
+ },
168
+ } satisfies JSONSchema7);
169
+ });
170
+
171
+ it('should return the schema inside a named property in "$defs" if a name and definitionPath is passed in options', () => {
172
+ expect(
173
+ zod3ToJsonSchema(z.any(), { name: 'MySchema', definitionPath: '$defs' }),
174
+ ).toStrictEqual({
175
+ $schema: 'http://json-schema.org/draft-07/schema#',
176
+ $ref: `#/$defs/MySchema`,
177
+ $defs: {
178
+ MySchema: {},
179
+ },
180
+ } satisfies JSONSchema7);
181
+ });
182
+
183
+ it("should not scrub 'any'-schemas from unions when strictUnions=false", () => {
184
+ expect(
185
+ zod3ToJsonSchema(
186
+ z.union([z.any(), z.instanceof(String), z.string(), z.number()]),
187
+ { strictUnions: false },
188
+ ),
189
+ ).toStrictEqual({
190
+ $schema: 'http://json-schema.org/draft-07/schema#',
191
+ anyOf: [{}, {}, { type: 'string' }, { type: 'number' }],
192
+ } satisfies JSONSchema7);
193
+ });
194
+
195
+ it("should scrub 'any'-schemas from unions when strictUnions=true", () => {
196
+ expect(
197
+ zod3ToJsonSchema(
198
+ z.union([z.any(), z.instanceof(String), z.string(), z.number()]),
199
+ { strictUnions: true },
200
+ ),
201
+ ).toStrictEqual({
202
+ $schema: 'http://json-schema.org/draft-07/schema#',
203
+ anyOf: [{ type: 'string' }, { type: 'number' }],
204
+ } satisfies JSONSchema7);
205
+ });
206
+
207
+ it("should scrub 'any'-schemas from unions when strictUnions=true in objects", () => {
208
+ expect(
209
+ zod3ToJsonSchema(
210
+ z.object({
211
+ field: z.union([
212
+ z.any(),
213
+ z.instanceof(String),
214
+ z.string(),
215
+ z.number(),
216
+ ]),
217
+ }),
218
+ { strictUnions: true },
219
+ ),
220
+ ).toStrictEqual({
221
+ $schema: 'http://json-schema.org/draft-07/schema#',
222
+ additionalProperties: false,
223
+ properties: {
224
+ field: { anyOf: [{ type: 'string' }, { type: 'number' }] },
225
+ },
226
+ type: 'object',
227
+ } satisfies JSONSchema7);
228
+ });
229
+
230
+ it('Definitions play nice with named schemas', () => {
231
+ const MySpecialStringSchema = z.string();
232
+ const MyArraySchema = z.array(MySpecialStringSchema);
233
+
234
+ const result = zod3ToJsonSchema(MyArraySchema, {
235
+ definitions: {
236
+ MySpecialStringSchema,
237
+ MyArraySchema,
238
+ },
239
+ });
240
+
241
+ expect(result).toStrictEqual({
242
+ $schema: 'http://json-schema.org/draft-07/schema#',
243
+ $ref: '#/definitions/MyArraySchema',
244
+ definitions: {
245
+ MySpecialStringSchema: { type: 'string' },
246
+ MyArraySchema: {
247
+ type: 'array',
248
+ items: {
249
+ $ref: '#/definitions/MySpecialStringSchema',
250
+ },
251
+ },
252
+ },
253
+ } satisfies JSONSchema7);
254
+ });
255
+
256
+ it('should be possible to add name as title instead of as ref', () => {
257
+ expect(
258
+ zod3ToJsonSchema(z.string(), { name: 'hello', nameStrategy: 'title' }),
259
+ ).toStrictEqual({
260
+ $schema: 'http://json-schema.org/draft-07/schema#',
261
+ type: 'string',
262
+ title: 'hello',
263
+ } satisfies JSONSchema7);
264
+ });
265
+
266
+ it('should be possible to use description', () => {
267
+ const parsedSchema = zod3ToJsonSchema(
268
+ z.string().describe('My neat string'),
269
+ );
270
+
271
+ expect(parsedSchema).toStrictEqual({
272
+ $schema: 'http://json-schema.org/draft-07/schema#',
273
+ type: 'string',
274
+ description: 'My neat string',
275
+ } satisfies JSONSchema7);
276
+ });
277
+
278
+ it('should handle optional schemas with different descriptions', () => {
279
+ const recurringSchema = z.object({});
280
+ const zodSchema = z
281
+ .object({
282
+ p1: recurringSchema.optional().describe('aaaaaaaaa'),
283
+ p2: recurringSchema.optional().describe('bbbbbbbbb'),
284
+ p3: recurringSchema.optional().describe('ccccccccc'),
285
+ })
286
+ .describe('sssssssss');
287
+
288
+ const jsonSchema = zod3ToJsonSchema(zodSchema, {
289
+ $refStrategy: 'none',
290
+ });
291
+
292
+ expect(jsonSchema).toStrictEqual({
293
+ $schema: 'http://json-schema.org/draft-07/schema#',
294
+ additionalProperties: false,
295
+ description: 'sssssssss',
296
+ properties: {
297
+ p1: {
298
+ additionalProperties: false,
299
+ description: 'aaaaaaaaa',
300
+ properties: {},
301
+ type: 'object',
302
+ },
303
+ p2: {
304
+ additionalProperties: false,
305
+ description: 'bbbbbbbbb',
306
+ properties: {},
307
+ type: 'object',
308
+ },
309
+ p3: {
310
+ additionalProperties: false,
311
+ description: 'ccccccccc',
312
+ properties: {},
313
+ type: 'object',
314
+ },
315
+ },
316
+ type: 'object',
317
+ } satisfies JSONSchema7);
318
+ });
319
+
320
+ it('should be possible to use superRefine', () => {
321
+ const schema = z.object({
322
+ test: z
323
+ .string()
324
+ .optional()
325
+ .superRefine(async (value, ctx) => {
326
+ await new Promise(resolve => setTimeout(resolve, 100));
327
+ if (value === 'fail') {
328
+ ctx.addIssue({
329
+ code: z.ZodIssueCode.custom,
330
+ message: 'This is a test error',
331
+ });
332
+ }
333
+ }),
334
+ });
335
+
336
+ const output = zod3ToJsonSchema(schema);
337
+
338
+ expect(output).toStrictEqual({
339
+ $schema: 'http://json-schema.org/draft-07/schema#',
340
+ type: 'object',
341
+ properties: { test: { type: 'string' } },
342
+ additionalProperties: false,
343
+ } satisfies JSONSchema7);
344
+ });
345
+
346
+ it('should be possible to use describe on arrays', () => {
347
+ const topicSchema = z.object({
348
+ topics: z
349
+ .array(
350
+ z.object({
351
+ topic: z.string().describe('The topic of the position'),
352
+ }),
353
+ )
354
+ .describe('An array of topics'),
355
+ });
356
+
357
+ const res = zod3ToJsonSchema(topicSchema);
358
+
359
+ expect(res).toStrictEqual({
360
+ $schema: 'http://json-schema.org/draft-07/schema#',
361
+ type: 'object',
362
+ required: ['topics'],
363
+ properties: {
364
+ topics: {
365
+ type: 'array',
366
+ items: {
367
+ type: 'object',
368
+ required: ['topic'],
369
+ properties: {
370
+ topic: {
371
+ type: 'string',
372
+ description: 'The topic of the position',
373
+ },
374
+ },
375
+ additionalProperties: false,
376
+ },
377
+ description: 'An array of topics',
378
+ },
379
+ },
380
+ additionalProperties: false,
381
+ } satisfies JSONSchema7);
382
+ });
383
+
384
+ it('should be possible to use regex with error messages', () => {
385
+ const urlRegex =
386
+ /^((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%,/.\w\-_]*)?\??(?:[-+=&;%@.\w:()_]*)#?(?:[.!/\\\w]*))?)/;
387
+
388
+ const URLSchema = z
389
+ .string()
390
+ .min(1)
391
+ .max(1000)
392
+ .regex(urlRegex, { message: 'Please enter a valid URL' })
393
+ .brand('url');
394
+
395
+ const jsonSchemaJs = zod3ToJsonSchema(URLSchema, { errorMessages: true });
396
+ const jsonSchema = JSON.parse(JSON.stringify(jsonSchemaJs));
397
+
398
+ expect(jsonSchema).toStrictEqual({
399
+ type: 'string',
400
+ minLength: 1,
401
+ maxLength: 1000,
402
+ pattern:
403
+ '^((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[-;:&=+$,\\w]+@)?[A-Za-z0-9.-]+|(?:www\\.|[-;:&=+$,\\w]+@)[A-Za-z0-9.-]+)((?:\\/[+~%,/.\\w\\-_]*)?\\??(?:[-+=&;%@.\\w:()_]*)#?(?:[.!/\\\\\\w]*))?)',
404
+ $schema: 'http://json-schema.org/draft-07/schema#',
405
+ } satisfies JSONSchema7);
406
+ });
407
+
408
+ it('should be possible to use lazy recursion @162', () => {
409
+ const A: any = z.object({
410
+ ref1: z.lazy(() => B),
411
+ });
412
+
413
+ const B = z.object({
414
+ ref1: A,
415
+ });
416
+
417
+ const result = zod3ToJsonSchema(A);
418
+
419
+ expect(result).toStrictEqual({
420
+ $schema: 'http://json-schema.org/draft-07/schema#',
421
+ type: 'object',
422
+ properties: {
423
+ ref1: {
424
+ type: 'object',
425
+ properties: {
426
+ ref1: {
427
+ $ref: '#',
428
+ },
429
+ },
430
+ required: ['ref1'],
431
+ additionalProperties: false,
432
+ },
433
+ },
434
+ required: ['ref1'],
435
+ additionalProperties: false,
436
+ } satisfies JSONSchema7);
437
+ });
438
+
439
+ it('should produce valid json schema for all parsers', () => {
440
+ enum nativeEnum {
441
+ 'a',
442
+ 'b',
443
+ 'c',
444
+ }
445
+
446
+ const allParsersSchema = z
447
+ .object({
448
+ any: z.any(),
449
+ array: z.array(z.any()),
450
+ arrayMin: z.array(z.any()).min(1),
451
+ arrayMax: z.array(z.any()).max(1),
452
+ arrayMinMax: z.array(z.any()).min(1).max(1),
453
+ bigInt: z.bigint(),
454
+ boolean: z.boolean(),
455
+ date: z.date(),
456
+ default: z.any().default(42),
457
+ effectRefine: z.string().refine(x => x + x),
458
+ effectTransform: z.string().transform(x => !!x),
459
+ effectPreprocess: z.preprocess(x => {
460
+ try {
461
+ return JSON.stringify(x);
462
+ } catch {
463
+ return 'wahh';
464
+ }
465
+ }, z.string()),
466
+ enum: z.enum(['hej', 'svejs']),
467
+ intersection: z.intersection(z.string().min(1), z.string().max(4)),
468
+ literal: z.literal('hej'),
469
+ map: z.map(z.string().uuid(), z.boolean()),
470
+ nativeEnum: z.nativeEnum(nativeEnum),
471
+ never: z.never() as any,
472
+ null: z.null(),
473
+ nullablePrimitive: z.string().nullable(),
474
+ nullableObject: z.object({ hello: z.string() }).nullable(),
475
+ number: z.number(),
476
+ numberGt: z.number().gt(1),
477
+ numberLt: z.number().lt(1),
478
+ numberGtLt: z.number().gt(1).lt(1),
479
+ numberGte: z.number().gte(1),
480
+ numberLte: z.number().lte(1),
481
+ numberGteLte: z.number().gte(1).lte(1),
482
+ numberMultipleOf: z.number().multipleOf(2),
483
+ numberInt: z.number().int(),
484
+ objectPasstrough: z
485
+ .object({ foo: z.string(), bar: z.number().optional() })
486
+ .passthrough(),
487
+ objectCatchall: z
488
+ .object({ foo: z.string(), bar: z.number().optional() })
489
+ .catchall(z.boolean()),
490
+ objectStrict: z
491
+ .object({ foo: z.string(), bar: z.number().optional() })
492
+ .strict(),
493
+ objectStrip: z
494
+ .object({ foo: z.string(), bar: z.number().optional() })
495
+ .strip(),
496
+ promise: z.promise(z.string()),
497
+ recordStringBoolean: z.record(z.string(), z.boolean()),
498
+ recordUuidBoolean: z.record(z.string().uuid(), z.boolean()),
499
+ recordBooleanBoolean: z.record(z.boolean(), z.boolean()),
500
+ set: z.set(z.string()),
501
+ string: z.string(),
502
+ stringMin: z.string().min(1),
503
+ stringMax: z.string().max(1),
504
+ stringEmail: z.string().email(),
505
+ stringEmoji: z.string().emoji(),
506
+ stringUrl: z.string().url(),
507
+ stringUuid: z.string().uuid(),
508
+ stringRegEx: z.string().regex(new RegExp('abc')),
509
+ stringCuid: z.string().cuid(),
510
+ tuple: z.tuple([z.string(), z.number(), z.boolean()]),
511
+ undefined: z.undefined(),
512
+ unionPrimitives: z.union([
513
+ z.string(),
514
+ z.number(),
515
+ z.boolean(),
516
+ z.bigint(),
517
+ z.null(),
518
+ ]),
519
+ unionPrimitiveLiterals: z.union([
520
+ z.literal(123),
521
+ z.literal('abc'),
522
+ z.literal(null),
523
+ z.literal(true),
524
+ // z.literal(1n), // target es2020
525
+ ]),
526
+ unionNonPrimitives: z.union([
527
+ z.string(),
528
+ z.object({
529
+ foo: z.string(),
530
+ bar: z.number().optional(),
531
+ }),
532
+ ]),
533
+ unknown: z.unknown(),
534
+ })
535
+ .partial()
536
+ .default({ string: 'hello' })
537
+ .describe('watup');
538
+
539
+ expect(zod3ToJsonSchema(allParsersSchema)).toStrictEqual({
540
+ $schema: 'http://json-schema.org/draft-07/schema#',
541
+ type: 'object',
542
+ properties: {
543
+ any: {},
544
+ array: {
545
+ type: 'array',
546
+ },
547
+ arrayMin: {
548
+ type: 'array',
549
+ minItems: 1,
550
+ },
551
+ arrayMax: {
552
+ type: 'array',
553
+ maxItems: 1,
554
+ },
555
+ arrayMinMax: {
556
+ type: 'array',
557
+ minItems: 1,
558
+ maxItems: 1,
559
+ },
560
+ bigInt: {
561
+ type: 'integer',
562
+ format: 'int64',
563
+ },
564
+ boolean: {
565
+ type: 'boolean',
566
+ },
567
+ date: {
568
+ type: 'string',
569
+ format: 'date-time',
570
+ },
571
+ default: {
572
+ default: 42,
573
+ },
574
+ effectRefine: {
575
+ type: 'string',
576
+ },
577
+ effectTransform: {
578
+ type: 'string',
579
+ },
580
+ effectPreprocess: {
581
+ type: 'string',
582
+ },
583
+ enum: {
584
+ type: 'string',
585
+ enum: ['hej', 'svejs'],
586
+ },
587
+ intersection: {
588
+ allOf: [
589
+ {
590
+ type: 'string',
591
+ minLength: 1,
592
+ },
593
+ {
594
+ type: 'string',
595
+ maxLength: 4,
596
+ },
597
+ ],
598
+ },
599
+ literal: {
600
+ type: 'string',
601
+ const: 'hej',
602
+ },
603
+ map: {
604
+ type: 'array',
605
+ maxItems: 125,
606
+ items: {
607
+ type: 'array',
608
+ items: [
609
+ {
610
+ type: 'string',
611
+ format: 'uuid',
612
+ },
613
+ {
614
+ type: 'boolean',
615
+ },
616
+ ],
617
+ minItems: 2,
618
+ maxItems: 2,
619
+ },
620
+ },
621
+ nativeEnum: {
622
+ type: 'number',
623
+ enum: [0, 1, 2],
624
+ },
625
+ never: {
626
+ not: {},
627
+ },
628
+ null: {
629
+ type: 'null',
630
+ },
631
+ nullablePrimitive: {
632
+ type: ['string', 'null'],
633
+ },
634
+ nullableObject: {
635
+ anyOf: [
636
+ {
637
+ type: 'object',
638
+ properties: {
639
+ hello: {
640
+ type: 'string',
641
+ },
642
+ },
643
+ required: ['hello'],
644
+ additionalProperties: false,
645
+ },
646
+ {
647
+ type: 'null',
648
+ },
649
+ ],
650
+ },
651
+ number: {
652
+ type: 'number',
653
+ },
654
+ numberGt: {
655
+ type: 'number',
656
+ exclusiveMinimum: 1,
657
+ },
658
+ numberLt: {
659
+ type: 'number',
660
+ exclusiveMaximum: 1,
661
+ },
662
+ numberGtLt: {
663
+ type: 'number',
664
+ exclusiveMinimum: 1,
665
+ exclusiveMaximum: 1,
666
+ },
667
+ numberGte: {
668
+ type: 'number',
669
+ minimum: 1,
670
+ },
671
+ numberLte: {
672
+ type: 'number',
673
+ maximum: 1,
674
+ },
675
+ numberGteLte: {
676
+ type: 'number',
677
+ minimum: 1,
678
+ maximum: 1,
679
+ },
680
+ numberMultipleOf: {
681
+ type: 'number',
682
+ multipleOf: 2,
683
+ },
684
+ numberInt: {
685
+ type: 'integer',
686
+ },
687
+ objectPasstrough: {
688
+ type: 'object',
689
+ properties: {
690
+ foo: {
691
+ type: 'string',
692
+ },
693
+ bar: {
694
+ type: 'number',
695
+ },
696
+ },
697
+ required: ['foo'],
698
+ additionalProperties: true,
699
+ },
700
+ objectCatchall: {
701
+ type: 'object',
702
+ properties: {
703
+ foo: {
704
+ type: 'string',
705
+ },
706
+ bar: {
707
+ type: 'number',
708
+ },
709
+ },
710
+ required: ['foo'],
711
+ additionalProperties: {
712
+ type: 'boolean',
713
+ },
714
+ },
715
+ objectStrict: {
716
+ type: 'object',
717
+ properties: {
718
+ foo: {
719
+ type: 'string',
720
+ },
721
+ bar: {
722
+ type: 'number',
723
+ },
724
+ },
725
+ required: ['foo'],
726
+ additionalProperties: false,
727
+ },
728
+ objectStrip: {
729
+ type: 'object',
730
+ properties: {
731
+ foo: {
732
+ type: 'string',
733
+ },
734
+ bar: {
735
+ type: 'number',
736
+ },
737
+ },
738
+ required: ['foo'],
739
+ additionalProperties: false,
740
+ },
741
+ promise: {
742
+ type: 'string',
743
+ },
744
+ recordStringBoolean: {
745
+ type: 'object',
746
+ additionalProperties: {
747
+ type: 'boolean',
748
+ },
749
+ },
750
+ recordUuidBoolean: {
751
+ type: 'object',
752
+ additionalProperties: {
753
+ type: 'boolean',
754
+ },
755
+ propertyNames: {
756
+ format: 'uuid',
757
+ },
758
+ },
759
+ recordBooleanBoolean: {
760
+ type: 'object',
761
+ additionalProperties: {
762
+ type: 'boolean',
763
+ },
764
+ },
765
+ set: {
766
+ type: 'array',
767
+ uniqueItems: true,
768
+ items: {
769
+ type: 'string',
770
+ },
771
+ },
772
+ string: {
773
+ type: 'string',
774
+ },
775
+ stringMin: {
776
+ type: 'string',
777
+ minLength: 1,
778
+ },
779
+ stringMax: {
780
+ type: 'string',
781
+ maxLength: 1,
782
+ },
783
+ stringEmail: {
784
+ type: 'string',
785
+ format: 'email',
786
+ },
787
+ stringEmoji: {
788
+ type: 'string',
789
+ pattern: '^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$',
790
+ },
791
+ stringUrl: {
792
+ type: 'string',
793
+ format: 'uri',
794
+ },
795
+ stringUuid: {
796
+ type: 'string',
797
+ format: 'uuid',
798
+ },
799
+ stringRegEx: {
800
+ type: 'string',
801
+ pattern: 'abc',
802
+ },
803
+ stringCuid: {
804
+ type: 'string',
805
+ pattern: '^[cC][^\\s-]{8,}$',
806
+ },
807
+ tuple: {
808
+ type: 'array',
809
+ minItems: 3,
810
+ maxItems: 3,
811
+ items: [
812
+ {
813
+ type: 'string',
814
+ },
815
+ {
816
+ type: 'number',
817
+ },
818
+ {
819
+ type: 'boolean',
820
+ },
821
+ ],
822
+ },
823
+ undefined: {
824
+ not: {},
825
+ },
826
+ unionPrimitives: {
827
+ type: ['string', 'number', 'boolean', 'integer', 'null'],
828
+ },
829
+ unionPrimitiveLiterals: {
830
+ type: ['number', 'string', 'null', 'boolean'],
831
+ enum: [123, 'abc', null, true],
832
+ },
833
+ unionNonPrimitives: {
834
+ anyOf: [
835
+ {
836
+ type: 'string',
837
+ },
838
+ {
839
+ type: 'object',
840
+ properties: {
841
+ foo: {
842
+ type: 'string',
843
+ },
844
+ bar: {
845
+ type: 'number',
846
+ },
847
+ },
848
+ required: ['foo'],
849
+ additionalProperties: false,
850
+ },
851
+ ],
852
+ },
853
+ unknown: {},
854
+ },
855
+ additionalProperties: false,
856
+ default: {
857
+ string: 'hello',
858
+ },
859
+ description: 'watup',
860
+ });
861
+ });
862
+ });