@amritk/generate-parsers 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/dist/generators/build-schema.d.ts +0 -1
  3. package/dist/generators/generate-default-from-pattern.d.ts +0 -1
  4. package/dist/generators/generate-discriminated-union-validation.d.ts +0 -1
  5. package/dist/generators/generate-enum-check.d.ts +0 -1
  6. package/dist/generators/generate-files.d.ts +0 -1
  7. package/dist/generators/generate-non-discriminated-union-validation.d.ts +0 -1
  8. package/dist/generators/generate-parser-function.d.ts +3 -3
  9. package/dist/generators/generate-parser-function.js +165 -37
  10. package/dist/generators/generate-schema-checks.d.ts +0 -1
  11. package/dist/generators/generate-schema-checks.js +2 -1
  12. package/dist/generators/generate-strict-assertion.d.ts +0 -1
  13. package/dist/generators/generate-strict-assertion.js +1 -6
  14. package/dist/generators/generate-validation-expression.d.ts +0 -1
  15. package/dist/generators/generate-validation-expression.js +2 -1
  16. package/dist/helpers/apply-schema-extensions.d.ts +0 -1
  17. package/dist/helpers/collect-helpers.d.ts +0 -1
  18. package/dist/helpers/collect-imports.d.ts +0 -1
  19. package/dist/helpers/find-discriminator.d.ts +0 -1
  20. package/dist/helpers/get-default-value.d.ts +0 -1
  21. package/dist/helpers/get-discriminator-value.d.ts +0 -1
  22. package/dist/index.d.ts +0 -1
  23. package/dist/types/schema-extensions.d.ts +0 -1
  24. package/package.json +3 -5
  25. package/dist/generators/build-schema.d.ts.map +0 -1
  26. package/dist/generators/generate-default-from-pattern.d.ts.map +0 -1
  27. package/dist/generators/generate-discriminated-union-validation.d.ts.map +0 -1
  28. package/dist/generators/generate-enum-check.d.ts.map +0 -1
  29. package/dist/generators/generate-files.d.ts.map +0 -1
  30. package/dist/generators/generate-non-discriminated-union-validation.d.ts.map +0 -1
  31. package/dist/generators/generate-parser-function.d.ts.map +0 -1
  32. package/dist/generators/generate-schema-checks.d.ts.map +0 -1
  33. package/dist/generators/generate-strict-assertion.d.ts.map +0 -1
  34. package/dist/generators/generate-validation-expression.d.ts.map +0 -1
  35. package/dist/helpers/apply-schema-extensions.d.ts.map +0 -1
  36. package/dist/helpers/collect-helpers.d.ts.map +0 -1
  37. package/dist/helpers/collect-imports.d.ts.map +0 -1
  38. package/dist/helpers/find-discriminator.d.ts.map +0 -1
  39. package/dist/helpers/get-default-value.d.ts.map +0 -1
  40. package/dist/helpers/get-discriminator-value.d.ts.map +0 -1
  41. package/dist/index.d.ts.map +0 -1
  42. package/dist/types/schema-extensions.d.ts.map +0 -1
  43. package/src/generators/build-schema.test.ts +0 -854
  44. package/src/generators/build-schema.ts +0 -147
  45. package/src/generators/generate-default-from-pattern.test.ts +0 -81
  46. package/src/generators/generate-default-from-pattern.ts +0 -39
  47. package/src/generators/generate-discriminated-union-validation.test.ts +0 -84
  48. package/src/generators/generate-discriminated-union-validation.ts +0 -54
  49. package/src/generators/generate-enum-check.test.ts +0 -45
  50. package/src/generators/generate-enum-check.ts +0 -8
  51. package/src/generators/generate-files.test.ts +0 -448
  52. package/src/generators/generate-files.ts +0 -155
  53. package/src/generators/generate-non-discriminated-union-validation.test.ts +0 -74
  54. package/src/generators/generate-non-discriminated-union-validation.ts +0 -40
  55. package/src/generators/generate-parser-function.test.ts +0 -1794
  56. package/src/generators/generate-parser-function.ts +0 -1348
  57. package/src/generators/generate-schema-checks.test.ts +0 -330
  58. package/src/generators/generate-schema-checks.ts +0 -127
  59. package/src/generators/generate-strict-assertion.test.ts +0 -67
  60. package/src/generators/generate-strict-assertion.ts +0 -249
  61. package/src/generators/generate-validation-expression.test.ts +0 -831
  62. package/src/generators/generate-validation-expression.ts +0 -326
  63. package/src/helpers/apply-schema-extensions.test.ts +0 -194
  64. package/src/helpers/apply-schema-extensions.ts +0 -63
  65. package/src/helpers/collect-helpers.test.ts +0 -115
  66. package/src/helpers/collect-helpers.ts +0 -79
  67. package/src/helpers/collect-imports.test.ts +0 -547
  68. package/src/helpers/collect-imports.ts +0 -275
  69. package/src/helpers/find-discriminator.test.ts +0 -88
  70. package/src/helpers/find-discriminator.ts +0 -39
  71. package/src/helpers/get-default-value.test.ts +0 -139
  72. package/src/helpers/get-default-value.ts +0 -95
  73. package/src/helpers/get-discriminator-value.test.ts +0 -83
  74. package/src/helpers/get-discriminator-value.ts +0 -21
  75. package/src/index.ts +0 -1
  76. package/src/types/schema-extensions.ts +0 -35
@@ -1,448 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { generateFile } from './generate-files'
4
-
5
- describe('generate-files', () => {
6
- it('generates combined import for $ref property', () => {
7
- const schema = {
8
- type: 'object',
9
- properties: {
10
- contact: { $ref: '#/$defs/contact' },
11
- },
12
- }
13
-
14
- const { content: result } = generateFile(schema, 'Document')
15
-
16
- expect(result).toContain("import { type Contact, parseContact, validateContactShape } from './contact';")
17
- })
18
-
19
- it('calls imported parser for optional $ref property', () => {
20
- const schema = {
21
- type: 'object',
22
- properties: {
23
- contact: { $ref: '#/$defs/contact' },
24
- },
25
- }
26
-
27
- const { content: result } = generateFile(schema, 'Document')
28
-
29
- // With local variable caching, optional $ref properties use the cached variable
30
- expect(result).toContain('_contact !== undefined && { contact: parseContact(_contact) }')
31
- })
32
-
33
- it('calls imported parser for required $ref property', () => {
34
- const schema = {
35
- type: 'object',
36
- properties: {
37
- contact: { $ref: '#/$defs/contact' },
38
- },
39
- required: ['contact'],
40
- }
41
-
42
- const { content: result } = generateFile(schema, 'Document')
43
-
44
- // With local variable caching, required $ref properties use the cached variable
45
- expect(result).toContain('contact: parseContact(_contact),')
46
- })
47
-
48
- it('generates combined imports for multiple $ref properties', () => {
49
- const schema = {
50
- type: 'object',
51
- properties: {
52
- contact: { $ref: '#/$defs/contact' },
53
- server: { $ref: '#/$defs/server' },
54
- },
55
- }
56
-
57
- const { content: result } = generateFile(schema, 'Document')
58
-
59
- expect(result).toContain("import { type Contact, parseContact, validateContactShape } from './contact';")
60
- expect(result).toContain("import { type Server, parseServer, validateServerShape } from './server';")
61
- })
62
-
63
- it('generates correct parser name for multi-word $ref', () => {
64
- const schema = {
65
- type: 'object',
66
- properties: {
67
- externalDoc: { $ref: '#/$defs/external-documentation' },
68
- },
69
- }
70
-
71
- const { content: result } = generateFile(schema, 'Document')
72
-
73
- expect(result).toContain(
74
- "import { type ExternalDocumentation, parseExternalDocumentation, validateExternalDocumentationShape } from './external-documentation';",
75
- )
76
- expect(result).toContain('parseExternalDocumentation(_externalDoc)')
77
- })
78
-
79
- it('does not generate ref imports when no $ref properties exist', () => {
80
- const schema = {
81
- type: 'object',
82
- properties: {
83
- name: { type: 'string' },
84
- age: { type: 'number' },
85
- },
86
- }
87
-
88
- const { content: result } = generateFile(schema, 'Simple')
89
-
90
- // Should still import isObject helper
91
- expect(result).toContain("import { isObject } from '@amritk/helpers/is-object';")
92
- // But should not import any ref types (check for type imports specifically)
93
- expect(result).not.toContain('import { type')
94
- })
95
-
96
- it('still validates non-ref properties inline', () => {
97
- const schema = {
98
- type: 'object',
99
- properties: {
100
- name: { type: 'string' },
101
- contact: { $ref: '#/$defs/contact' },
102
- },
103
- }
104
-
105
- const { content: result } = generateFile(schema, 'Document')
106
-
107
- // Non-ref property should have inline validation against the cached local
108
- expect(result).toContain('typeof _name === "string"')
109
- // Ref property should call imported parser using cached variable
110
- expect(result).toContain('parseContact(_contact)')
111
- })
112
-
113
- it('generates a correct import for -or-reference style $ref names', () => {
114
- // Without stripping, 'callbacks-or-reference' maps to the filename 'callbacks-or-reference'
115
- const schema = {
116
- type: 'object',
117
- properties: {
118
- callbacks: { $ref: '#/$defs/callbacks-or-reference' },
119
- },
120
- }
121
-
122
- const { content: result } = generateFile(schema, 'Document')
123
-
124
- expect(result).toContain(
125
- "import { type CallbacksOrReference, parseCallbacksOrReference, validateCallbacksOrReferenceShape } from './callbacks-or-reference';",
126
- )
127
- expect(result).toContain('parseCallbacksOrReference(_callbacks)')
128
- })
129
-
130
- it('generates arrow function parser', () => {
131
- const schema = {
132
- type: 'object',
133
- properties: {
134
- name: { type: 'string' },
135
- },
136
- }
137
-
138
- const { content: result } = generateFile(schema, 'Simple')
139
-
140
- expect(result).toContain('export const parseSimple = (input: unknown): Simple =>')
141
- expect(result).not.toContain('function parseSimple')
142
- })
143
-
144
- it('maps array items through imported parser for optional array with $ref items', () => {
145
- const schema = {
146
- type: 'object',
147
- properties: {
148
- servers: {
149
- type: 'array',
150
- items: { $ref: '#/$defs/server' },
151
- },
152
- },
153
- }
154
-
155
- const { content: result } = generateFile(schema, 'Document')
156
-
157
- expect(result).toContain("import { type Server, parseServer, validateServerShape } from './server';")
158
- // With local variable caching, array validators use the cached variable
159
- expect(result).toContain('validateArray(_servers, parseServer)')
160
- })
161
-
162
- it('maps array items through imported parser for required array with $ref items', () => {
163
- const schema = {
164
- type: 'object',
165
- properties: {
166
- servers: {
167
- type: 'array',
168
- items: { $ref: '#/$defs/server' },
169
- },
170
- },
171
- required: ['servers'],
172
- }
173
-
174
- const { content: result } = generateFile(schema, 'Document')
175
-
176
- // Check for required imports
177
- expect(result).toContain("import { type Server, parseServer, validateServerShape } from './server';")
178
- expect(result).toContain("import { validateArray } from '@amritk/helpers/validate-array';")
179
- expect(result).toContain("import { isObject } from '@amritk/helpers/is-object';")
180
-
181
- // Check for required array validation using cached variable
182
- expect(result).toContain('servers: validateArray(_servers, parseServer),')
183
-
184
- // Should not use spread operator for required fields
185
- expect(result).not.toContain('...(_servers')
186
- })
187
-
188
- it('generates type and parser for schema with extension properties', () => {
189
- const schema = {
190
- type: 'object',
191
- properties: {
192
- name: { type: 'string' },
193
- 'x-enabled': { type: 'boolean' },
194
- 'x-internal': { type: 'boolean' },
195
- },
196
- required: ['name'],
197
- }
198
-
199
- const { content: result } = generateFile(schema, 'Parameter')
200
-
201
- // Type should include extension properties as optional
202
- expect(result).toContain("'x-enabled'?: boolean")
203
- expect(result).toContain("'x-internal'?: boolean")
204
- // Parser should validate extension properties
205
- expect(result).toContain('x_enabled')
206
- expect(result).toContain('x_internal')
207
- })
208
-
209
- it('generates type and parser for schema with complex extension property', () => {
210
- const schema = {
211
- type: 'object',
212
- properties: {
213
- summary: { type: 'string' },
214
- 'x-codegen': {
215
- type: 'object',
216
- properties: {
217
- methodName: { type: 'string' },
218
- },
219
- },
220
- },
221
- }
222
-
223
- const { content: result } = generateFile(schema, 'Operation')
224
-
225
- // Type should include the complex extension
226
- expect(result).toContain("'x-codegen'")
227
- expect(result).toContain('methodName')
228
- })
229
-
230
- it('omits parser function in types-only mode', () => {
231
- const schema = {
232
- type: 'object',
233
- properties: {
234
- name: { type: 'string' },
235
- },
236
- }
237
-
238
- const { content: result } = generateFile(schema, 'Simple', { typesOnly: true })
239
-
240
- expect(result).toContain('export type Simple')
241
- expect(result).not.toContain('export const parseSimple')
242
- })
243
-
244
- it('uses type-only imports for $ref properties in types-only mode', () => {
245
- const schema = {
246
- type: 'object',
247
- properties: {
248
- contact: { $ref: '#/$defs/contact' },
249
- },
250
- }
251
-
252
- const { content: result } = generateFile(schema, 'Document', { typesOnly: true })
253
-
254
- expect(result).toContain("import type { Contact } from './contact';")
255
- expect(result).not.toContain('parseContact')
256
- })
257
-
258
- it('does not include runtime helper imports in types-only mode', () => {
259
- const schema = {
260
- type: 'object',
261
- properties: {
262
- name: { type: 'string' },
263
- },
264
- }
265
-
266
- const { content: result } = generateFile(schema, 'Simple', { typesOnly: true })
267
-
268
- // No parser helpers should be imported since there is no parser function
269
- expect(result).not.toContain('import { isObject }')
270
- expect(result).not.toContain('import { validateArray }')
271
- expect(result).not.toContain('import { validateRecord }')
272
- })
273
-
274
- it('still generates the type definition in types-only mode', () => {
275
- const schema = {
276
- type: 'object',
277
- properties: {
278
- name: { type: 'string' },
279
- age: { type: 'number' },
280
- },
281
- required: ['name'],
282
- }
283
-
284
- const { content: result } = generateFile(schema, 'User', { typesOnly: true })
285
-
286
- expect(result).toContain('export type User')
287
- expect(result).toContain('name: string')
288
- expect(result).toContain('age?: number')
289
- })
290
-
291
- it('generates type-only import for array items $ref in types-only mode', () => {
292
- const schema = {
293
- type: 'object',
294
- properties: {
295
- servers: {
296
- type: 'array',
297
- items: { $ref: '#/$defs/server' },
298
- },
299
- },
300
- }
301
-
302
- const { content: result } = generateFile(schema, 'Document', { typesOnly: true })
303
-
304
- expect(result).toContain("import type { Server } from './server';")
305
- expect(result).not.toContain('parseServer')
306
- expect(result).not.toContain('validateArray')
307
- })
308
-
309
- it('generates type-only import for additionalProperties $ref in types-only mode', () => {
310
- const schema = {
311
- type: 'object',
312
- additionalProperties: { $ref: '#/$defs/path-item' },
313
- }
314
-
315
- const { content: result } = generateFile(schema, 'Paths', { typesOnly: true })
316
-
317
- expect(result).toContain("import type { PathItem } from './path-item';")
318
- expect(result).not.toContain('parsePathItem')
319
- expect(result).not.toContain('validateRecord')
320
- })
321
-
322
- it('generates type-only imports for oneOf $ref variants in types-only mode', () => {
323
- const schema = {
324
- oneOf: [{ $ref: '#/$defs/cat' }, { $ref: '#/$defs/dog' }],
325
- }
326
-
327
- const { content: result } = generateFile(schema, 'Pet', { typesOnly: true })
328
-
329
- expect(result).toContain("import type { Cat } from './cat';")
330
- expect(result).toContain("import type { Dog } from './dog';")
331
- expect(result).not.toContain('parseCat')
332
- expect(result).not.toContain('parseDog')
333
- })
334
-
335
- it('generates type-only imports for anyOf $ref variants in types-only mode', () => {
336
- const schema = {
337
- type: 'object',
338
- properties: {
339
- value: {
340
- anyOf: [{ $ref: '#/$defs/string-value' }, { $ref: '#/$defs/number-value' }],
341
- },
342
- },
343
- }
344
-
345
- const { content: result } = generateFile(schema, 'Wrapper', { typesOnly: true })
346
-
347
- expect(result).toContain("import type { StringValue } from './string-value';")
348
- expect(result).toContain("import type { NumberValue } from './number-value';")
349
- expect(result).not.toContain('parseStringValue')
350
- expect(result).not.toContain('parseNumberValue')
351
- })
352
-
353
- it('does not change default behavior when typesOnly is false', () => {
354
- const schema = {
355
- type: 'object',
356
- properties: {
357
- contact: { $ref: '#/$defs/contact' },
358
- },
359
- }
360
-
361
- const { content: defaultResult } = generateFile(schema, 'Document')
362
- const { content: explicitFalseResult } = generateFile(schema, 'Document', { typesOnly: false })
363
-
364
- expect(defaultResult).toBe(explicitFalseResult)
365
- expect(defaultResult).toContain("import { type Contact, parseContact, validateContactShape } from './contact';")
366
- expect(defaultResult).toContain('export const parseDocument')
367
- })
368
-
369
- it('does not generate a self-import for recursive schemas in types-only mode', () => {
370
- // Mirrors the 3.2.0 encoding schema: Encoding has properties itemEncoding, prefixEncoding,
371
- // and encoding that all $ref back to #/$defs/encoding. The generated encoding.ts must not
372
- // import Encoding from itself.
373
- const schema = {
374
- type: 'object',
375
- properties: {
376
- itemEncoding: { $ref: '#/$defs/encoding' },
377
- prefixEncoding: {
378
- type: 'array',
379
- items: { $ref: '#/$defs/encoding' },
380
- },
381
- encoding: {
382
- type: 'object',
383
- additionalProperties: { $ref: '#/$defs/encoding' },
384
- },
385
- headers: {
386
- type: 'object',
387
- additionalProperties: { $ref: '#/$defs/header-or-reference' },
388
- },
389
- },
390
- }
391
-
392
- const { content: result } = generateFile(schema, 'Encoding', {
393
- typesOnly: true,
394
- selfRef: '#/$defs/encoding',
395
- })
396
-
397
- expect(result).not.toContain("import type { Encoding } from './encoding';")
398
- expect(result).toContain("import type { HeaderOrReference } from './header-or-reference';")
399
- expect(result).toContain('export type Encoding')
400
- })
401
-
402
- it('does not generate a self-import for recursive schemas in full parser mode', () => {
403
- const schema = {
404
- type: 'object',
405
- properties: {
406
- itemEncoding: { $ref: '#/$defs/encoding' },
407
- headers: {
408
- type: 'object',
409
- additionalProperties: { $ref: '#/$defs/header-or-reference' },
410
- },
411
- },
412
- }
413
-
414
- const { content: result } = generateFile(schema, 'Encoding', { selfRef: '#/$defs/encoding' })
415
-
416
- expect(result).not.toContain("import { type Encoding, parseEncoding, validateEncodingShape } from './encoding';")
417
- expect(result).toContain(
418
- "import { type HeaderOrReference, parseHeaderOrReference, validateHeaderOrReferenceShape } from './header-or-reference';",
419
- )
420
- })
421
-
422
- describe('typeSuffix', () => {
423
- const schema = {
424
- type: 'object',
425
- properties: {
426
- contact: { $ref: '#/$defs/contact' },
427
- },
428
- required: ['contact'],
429
- }
430
-
431
- it('appends the suffix to ref-derived type, parser, and import names', () => {
432
- const { content: result } = generateFile(schema, 'Document', { typeSuffix: 'Object' })
433
-
434
- expect(result).toContain(
435
- "import { type ContactObject, parseContactObject, validateContactObjectShape } from './contact';",
436
- )
437
- expect(result).toContain('contact: ContactObject;')
438
- expect(result).toContain('contact: parseContactObject(_contact)')
439
- })
440
-
441
- it('emits no suffix by default', () => {
442
- const { content: result } = generateFile(schema, 'Document')
443
-
444
- expect(result).toContain("import { type Contact, parseContact, validateContactShape } from './contact';")
445
- expect(result).not.toContain('ContactObject')
446
- })
447
- })
448
- })
@@ -1,155 +0,0 @@
1
- import { generateTypeDefinition } from '@amritk/helpers/generate-type-definition'
2
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
3
- import {
4
- type CollectedHelpers,
5
- collectHelpers,
6
- type HelpersMode,
7
- type RuntimeHelperName,
8
- } from '#helpers/collect-helpers'
9
- import { collectImports } from '#helpers/collect-imports'
10
-
11
- import { generateParserFunction, generateShapeValidator } from './generate-parser-function'
12
-
13
- /**
14
- * Options for controlling what gets generated in a file.
15
- */
16
- type GenerateFileOptions = {
17
- /**
18
- * When true, only emit the TypeScript type definition and skip the parser function.
19
- * Imports will be type-only as well, since there are no parsers to call.
20
- */
21
- readonly typesOnly?: boolean
22
- /**
23
- * The $ref path of the schema being generated (e.g. `#/$defs/encoding`).
24
- * When provided, any $ref that resolves to the same filename is excluded from
25
- * the import list, preventing a file from importing itself.
26
- */
27
- readonly selfRef?: string
28
- /**
29
- * The root schema document. When provided, URI refs that cannot be resolved
30
- * within the root schema's $defs are excluded from the import list.
31
- */
32
- readonly rootSchema?: Record<string, unknown>
33
- /**
34
- * When true, the generated parser emits a console.warn for every input key
35
- * that is not declared in the schema's properties.
36
- */
37
- readonly logWarnings?: boolean
38
- /**
39
- * When true, the generated parser throws on type/shape mismatches instead
40
- * of coercing invalid input to default values.
41
- */
42
- readonly strict?: boolean
43
- /**
44
- * Controls how runtime helpers are referenced.
45
- * - `'package'` (default): emit imports from `@amritk/helpers/...`.
46
- * - `'embedded'`: emit imports from `./_helpers/...` so the helper sources can
47
- * be shipped alongside the generated files.
48
- */
49
- readonly helpersMode?: HelpersMode
50
- /**
51
- * Relative path prefix to the shared `_helpers/` directory in embedded mode.
52
- * Defaults to `'./'`. The recursive multi-schema build passes `'../'`,
53
- * `'../../'`, etc. so a nested parser can reach a single `_helpers/` directory
54
- * at the output root.
55
- */
56
- readonly helpersImportPrefix?: string
57
- /**
58
- * When true, every property, array, and record in the generated type
59
- * definitions is emitted as `readonly`.
60
- */
61
- readonly readonly?: boolean
62
- /**
63
- * Suffix appended to every type/parser name derived from a `$ref`.
64
- * Defaults to `''` (no suffix). Set to e.g. `'Object'` to emit `ContactObject`.
65
- */
66
- readonly typeSuffix?: string
67
- }
68
-
69
- /** Result of generating a single parser file. */
70
- export type GeneratedFileContent = {
71
- readonly content: string
72
- readonly usedHelpers: ReadonlySet<RuntimeHelperName>
73
- }
74
-
75
- /**
76
- * Generates a complete TypeScript file from a JSON Schema.
77
- *
78
- * The generated file contains imports, type definition, and parser function.
79
- * When typesOnly is true, only the type definition is emitted without any parser.
80
- *
81
- * @param schema - The JSON Schema to generate code from
82
- * @param typeName - The name to use for the generated TypeScript type
83
- * @param options - Optional settings to control what gets generated
84
- * @returns The generated file content and the set of runtime helpers it references
85
- *
86
- * @example
87
- * ```typescript
88
- * const schema = {
89
- * type: "object",
90
- * properties: {
91
- * name: { type: "string" },
92
- * age: { type: "number" }
93
- * },
94
- * required: ["name"]
95
- * };
96
- *
97
- * const result = generateFile(schema, "User");
98
- * // result contains imports, type User, and parseUser function
99
- *
100
- * const typesOnly = generateFile(schema, "User", { typesOnly: true });
101
- * // typesOnly contains only type-only imports and type User (no parser)
102
- * ```
103
- */
104
- export const generateFile = (
105
- schema: JSONSchema,
106
- typeName: string,
107
- options?: GenerateFileOptions,
108
- ): GeneratedFileContent => {
109
- const typesOnly = options?.typesOnly === true
110
- const selfRef = options?.selfRef
111
- const rootSchema = options?.rootSchema
112
- const helpersMode: HelpersMode = options?.helpersMode ?? 'package'
113
- const typeSuffix = options?.typeSuffix ?? ''
114
- const typeDefinition = generateTypeDefinition(schema, typeName, { readonly: options?.readonly ?? false, typeSuffix })
115
-
116
- if (typesOnly) {
117
- // In types-only mode, skip the parser function and use type-only imports
118
- const imports = collectImports(schema, { typesOnly: true, selfRef, rootSchema, typeSuffix })
119
- let result = ''
120
-
121
- if (imports.length > 0) {
122
- result += imports[0]
123
- for (let i = 1; i < imports.length; i++) {
124
- result += '\n' + imports[i]
125
- }
126
- result += '\n\n'
127
- }
128
-
129
- return { content: result + typeDefinition, usedHelpers: new Set() }
130
- }
131
-
132
- const parserFunction = generateParserFunction(schema, typeName, {
133
- useRefImports: true,
134
- typeSuffix,
135
- ...(options?.logWarnings !== undefined ? { logWarnings: options.logWarnings } : {}),
136
- ...(options?.strict !== undefined ? { strict: options.strict } : {}),
137
- })
138
- const shapeValidator = generateShapeValidator(schema, typeName, true, typeSuffix)
139
- const combinedFunctions = `${shapeValidator}\n\n${parserFunction}`
140
- const helpers: CollectedHelpers = collectHelpers(combinedFunctions, helpersMode, options?.helpersImportPrefix)
141
- const imports = [...collectImports(schema, { selfRef, rootSchema, typeSuffix }), ...helpers.imports]
142
-
143
- // Build file output using string concatenation instead of array join for performance
144
- let result = ''
145
-
146
- if (imports.length > 0) {
147
- result += imports[0]
148
- for (let i = 1; i < imports.length; i++) {
149
- result += '\n' + imports[i]
150
- }
151
- result += '\n\n'
152
- }
153
-
154
- return { content: result + typeDefinition + '\n\n' + combinedFunctions, usedHelpers: helpers.used }
155
- }
@@ -1,74 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { generateNonDiscriminatedUnionValidation } from './generate-non-discriminated-union-validation'
4
-
5
- describe('generate-non-discriminated-union-validation', () => {
6
- it('generates OR chain for multiple schema checks', () => {
7
- const schemas = [{ type: 'string' as const }, { type: 'number' as const }]
8
- const result = generateNonDiscriminatedUnionValidation('value', schemas, '""', true)
9
- expect(result).toContain('typeof value === "string"')
10
- expect(result).toContain('typeof value === "number"')
11
- expect(result).toContain('||')
12
- })
13
-
14
- it('returns fallback with nullish coalescing when required and no checks', () => {
15
- const schemas = [{ description: 'no type' }]
16
- const result = generateNonDiscriminatedUnionValidation('value', schemas, '""', true)
17
- expect(result).toBe('value ?? ""')
18
- })
19
-
20
- it('returns accessor when not required and no checks', () => {
21
- const schemas = [{ description: 'no type' }]
22
- const result = generateNonDiscriminatedUnionValidation('value', schemas, '""', false)
23
- expect(result).toBe('value')
24
- })
25
-
26
- it('wraps valid check in ternary with default value when required', () => {
27
- const schemas = [{ type: 'string' as const }]
28
- const result = generateNonDiscriminatedUnionValidation('value', schemas, '""', true)
29
- expect(result).toBe('((typeof value === "string")) ? value : ""')
30
- })
31
-
32
- it('uses undefined as fallback when not required', () => {
33
- const schemas = [{ type: 'string' as const }]
34
- const result = generateNonDiscriminatedUnionValidation('value', schemas, '""', false)
35
- expect(result).toBe('((typeof value === "string")) ? value : undefined')
36
- })
37
-
38
- it('handles schemas with multiple constraints combined with &&', () => {
39
- const schemas = [{ type: 'string' as const, minLength: 1, maxLength: 100 }]
40
- const result = generateNonDiscriminatedUnionValidation('value', schemas, '""', true)
41
- expect(result).toContain('typeof value === "string"')
42
- expect(result).toContain('value.length >= 1')
43
- expect(result).toContain('value.length <= 100')
44
- expect(result).toContain('&&')
45
- })
46
-
47
- it('handles empty schemas array when required', () => {
48
- const result = generateNonDiscriminatedUnionValidation('value', [], '""', true)
49
- expect(result).toBe('value ?? ""')
50
- })
51
-
52
- it('handles empty schemas array when not required', () => {
53
- const result = generateNonDiscriminatedUnionValidation('value', [], '""', false)
54
- expect(result).toBe('value')
55
- })
56
-
57
- it('generates multiple OR groups for different types', () => {
58
- const schemas = [{ type: 'string' as const }, { type: 'number' as const }, { type: 'boolean' as const }]
59
- const result = generateNonDiscriminatedUnionValidation('value', schemas, 'null', true)
60
- expect(result).toContain('typeof value === "string"')
61
- expect(result).toContain('typeof value === "number"')
62
- expect(result).toContain('typeof value === "boolean"')
63
- // Should use || between groups
64
- const orCount = (result.match(/\|\|/g) || []).length
65
- expect(orCount).toBe(2)
66
- })
67
-
68
- it('uses the provided accessor throughout', () => {
69
- const schemas = [{ type: 'string' as const }]
70
- const result = generateNonDiscriminatedUnionValidation('input?.name', schemas, '""', true)
71
- expect(result).toContain('typeof input?.name === "string"')
72
- expect(result).toContain('input?.name :')
73
- })
74
- })