@kubb/plugin-ts 5.0.0-beta.22 → 5.0.0-beta.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-ts",
3
- "version": "5.0.0-beta.22",
3
+ "version": "5.0.0-beta.27",
4
4
  "description": "Generate TypeScript types, interfaces, and enums from your OpenAPI specification. The foundational plugin that powers type safety across the entire Kubb ecosystem.",
5
5
  "keywords": [
6
6
  "code-generation",
@@ -46,9 +46,9 @@
46
46
  "registry": "https://registry.npmjs.org/"
47
47
  },
48
48
  "dependencies": {
49
- "@kubb/core": "5.0.0-beta.22",
50
- "@kubb/parser-ts": "5.0.0-beta.22",
51
- "@kubb/renderer-jsx": "5.0.0-beta.22",
49
+ "@kubb/core": "5.0.0-beta.27",
50
+ "@kubb/parser-ts": "5.0.0-beta.27",
51
+ "@kubb/renderer-jsx": "5.0.0-beta.27",
52
52
  "remeda": "^2.34.1",
53
53
  "typescript": "^6.0.3"
54
54
  },
@@ -57,7 +57,7 @@
57
57
  "@internals/utils": "0.0.0"
58
58
  },
59
59
  "peerDependencies": {
60
- "@kubb/renderer-jsx": "5.0.0-beta.22"
60
+ "@kubb/renderer-jsx": "5.0.0-beta.27"
61
61
  },
62
62
  "size-limit": [
63
63
  {
@@ -1,6 +1,6 @@
1
1
  import { camelCase, trimQuotes } from '@internals/utils'
2
2
  import type { ast } from '@kubb/core'
3
- import { safePrint } from '@kubb/parser-ts'
3
+ import { parserTs } from '@kubb/parser-ts'
4
4
  import { File } from '@kubb/renderer-jsx'
5
5
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
6
6
  import { ENUM_TYPES_WITH_KEY_SUFFIX, ENUM_TYPES_WITH_RUNTIME_VALUE, ENUM_TYPES_WITH_TYPE_ONLY } from '../constants.ts'
@@ -72,11 +72,11 @@ export function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }
72
72
  <>
73
73
  {nameNode && (
74
74
  <File.Source name={enumName} isExportable isIndexable isTypeOnly={false}>
75
- {safePrint(nameNode)}
75
+ {parserTs.print(nameNode)}
76
76
  </File.Source>
77
77
  )}
78
78
  <File.Source name={typeName} isIndexable isExportable={ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumType)} isTypeOnly={ENUM_TYPES_WITH_TYPE_ONLY.has(enumType)}>
79
- {safePrint(typeNode)}
79
+ {parserTs.print(typeNode)}
80
80
  </File.Source>
81
81
  </>
82
82
  )
package/src/factory.ts CHANGED
@@ -198,7 +198,7 @@ export function createParameterSignature(
198
198
  * Creates a JSDoc comment node from an array of comment strings.
199
199
  * Returns null if no comments are provided.
200
200
  */
201
- export function createJSDoc({ comments }: { comments: string[] }) {
201
+ export function createJSDoc({ comments }: { comments: Array<string> }) {
202
202
  if (!comments.length) {
203
203
  return null
204
204
  }
@@ -338,7 +338,7 @@ export function createTypeDeclaration({
338
338
  /**
339
339
  * Creates a TypeScript namespace declaration (exported module).
340
340
  */
341
- export function createNamespaceDeclaration({ statements, name }: { name: string; statements: ts.Statement[] }) {
341
+ export function createNamespaceDeclaration({ statements, name }: { name: string; statements: Array<ts.Statement> }) {
342
342
  return factory.createModuleDeclaration(
343
343
  [factory.createToken(ts.SyntaxKind.ExportKeyword)],
344
344
  factory.createIdentifier(name),
@@ -519,7 +519,7 @@ export function createEnumDeclaration({
519
519
  * Enum name in PascalCase.
520
520
  */
521
521
  typeName: string
522
- enums: [key: string | number, value: string | number | boolean][]
522
+ enums: Array<[key: string | number, value: string | number | boolean]>
523
523
  /**
524
524
  * Choose the casing for enum key names.
525
525
  * @default 'none'
@@ -740,8 +740,8 @@ export function createUrlTemplateType(path: string): ts.TypeNode {
740
740
  }
741
741
 
742
742
  const segments = normalized.split(/(\{[^}]+\})/)
743
- const parts: string[] = []
744
- const parameterIndices: number[] = []
743
+ const parts: Array<string> = []
744
+ const parameterIndices: Array<number> = []
745
745
 
746
746
  segments.forEach((segment) => {
747
747
  if (segment.startsWith('{') && segment.endsWith('}')) {
@@ -753,7 +753,7 @@ export function createUrlTemplateType(path: string): ts.TypeNode {
753
753
  })
754
754
 
755
755
  const head = ts.factory.createTemplateHead(parts[0] || '')
756
- const templateSpans: ts.TemplateLiteralTypeSpan[] = []
756
+ const templateSpans: Array<ts.TemplateLiteralTypeSpan> = []
757
757
 
758
758
  parameterIndices.forEach((paramIndex, i) => {
759
759
  const isLast = i === parameterIndices.length - 1
@@ -24,6 +24,12 @@ function getPerContentTypeName(dataName: string, suffix: string): string {
24
24
  return dataName + suffix
25
25
  }
26
26
 
27
+ /**
28
+ * Built-in generator for `@kubb/plugin-ts`. Emits one TypeScript file per
29
+ * schema in the spec plus per-operation request, response, and parameter
30
+ * types. Drop-replace with a custom `Generator<PluginTs>` to change how
31
+ * TypeScript output is produced.
32
+ */
27
33
  export const typeGenerator = defineGenerator<PluginTs>({
28
34
  name: 'typescript',
29
35
  renderer: jsxRendererSync,
@@ -48,14 +54,14 @@ export const typeGenerator = defineGenerator<PluginTs>({
48
54
 
49
55
  const imports = adapter.getImports(node, (schemaName) => ({
50
56
  name: resolveImportName(schemaName),
51
- path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
57
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
52
58
  }))
53
59
 
54
60
  const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum)
55
61
 
56
62
  const meta = {
57
63
  name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolver.resolveTypeName(node.name),
58
- file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group }),
64
+ file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group: group ?? undefined }),
59
65
  } as const
60
66
 
61
67
  const schemaPrinter = printerTs({
@@ -104,7 +110,10 @@ export const typeGenerator = defineGenerator<PluginTs>({
104
110
  const params = ast.caseParams(node.parameters, paramsCasing)
105
111
 
106
112
  const meta = {
107
- file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
113
+ file: resolver.resolveFile(
114
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
115
+ { root, output, group: group ?? undefined },
116
+ ),
108
117
  } as const
109
118
 
110
119
  // Build a set of schema names that are enums so the ref handler and getImports
@@ -123,7 +132,7 @@ export const typeGenerator = defineGenerator<PluginTs>({
123
132
 
124
133
  const imports = adapter.getImports(schema, (schemaName) => ({
125
134
  name: resolveImportName(schemaName),
126
- path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
135
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
127
136
  }))
128
137
 
129
138
  const schemaPrinter = printerTs({
package/src/plugin.ts CHANGED
@@ -5,23 +5,32 @@ import { resolverTs } from './resolvers/resolverTs.ts'
5
5
  import type { PluginTs } from './types.ts'
6
6
 
7
7
  /**
8
- * Canonical plugin name for `@kubb/plugin-ts`, used to identify the plugin in driver lookups and warnings.
8
+ * Canonical plugin name for `@kubb/plugin-ts`. Used for driver lookups and
9
+ * cross-plugin dependency references.
9
10
  */
10
11
  export const pluginTsName = 'plugin-ts' satisfies PluginTs['name']
11
12
 
12
13
  /**
13
- * The `@kubb/plugin-ts` plugin factory.
14
- *
15
- * Generates TypeScript type declarations from an OpenAPI/AST `RootNode`.
16
- * Walks schemas and operations, delegates rendering to the active generators,
17
- * and writes barrel files based on `output.barrelType`.
14
+ * Generates TypeScript `type` aliases and `interface` declarations from an
15
+ * OpenAPI spec. The foundation that every other Kubb plugin builds on:
16
+ * clients, query hooks, mocks, and validators all reference the names this
17
+ * plugin produces.
18
18
  *
19
19
  * @example
20
20
  * ```ts
21
- * import pluginTs from '@kubb/plugin-ts'
21
+ * import { defineConfig } from 'kubb'
22
+ * import { pluginTs } from '@kubb/plugin-ts'
22
23
  *
23
24
  * export default defineConfig({
24
- * plugins: [pluginTs({ output: { path: 'types' }, enumType: 'asConst' })],
25
+ * input: { path: './petStore.yaml' },
26
+ * output: { path: './src/gen' },
27
+ * plugins: [
28
+ * pluginTs({
29
+ * output: { path: './types' },
30
+ * enumType: 'asConst',
31
+ * optionalType: 'questionTokenAndUndefined',
32
+ * }),
33
+ * ],
25
34
  * })
26
35
  * ```
27
36
  */
@@ -55,7 +64,7 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
55
64
  return `${camelCase(ctx.group)}Controller`
56
65
  },
57
66
  } satisfies Group)
58
- : undefined
67
+ : null
59
68
 
60
69
  return {
61
70
  name: pluginTsName,
@@ -1,5 +1,5 @@
1
1
  import { ast } from '@kubb/core'
2
- import { safePrint } from '@kubb/parser-ts'
2
+ import { parserTs } from '@kubb/parser-ts'
3
3
  import type ts from 'typescript'
4
4
  import { ENUM_TYPES_WITH_KEY_SUFFIX, OPTIONAL_ADDS_QUESTION_TOKEN, OPTIONAL_ADDS_UNDEFINED } from '../constants.ts'
5
5
  import * as factory from '../factory.ts'
@@ -292,7 +292,7 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
292
292
  (meta.nullish || meta.optional) && addsUndefined
293
293
  ? factory.createUnionDeclaration({ nodes: [withNullable, factory.keywordTypeNodes.undefined] })
294
294
  : withNullable
295
- return safePrint(result)
295
+ return parserTs.print(result)
296
296
  }
297
297
 
298
298
  // When keysToOmit is present, wrap with Omit first, then apply nullable/optional
@@ -320,7 +320,7 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
320
320
  }),
321
321
  })
322
322
 
323
- return safePrint(typeNode)
323
+ return parserTs.print(typeNode)
324
324
  },
325
325
  }
326
326
  })
@@ -1,22 +1,23 @@
1
- import { pascalCase } from '@internals/utils'
1
+ import { ensureValidVarName, pascalCase } from '@internals/utils'
2
2
  import { defineResolver } from '@kubb/core'
3
3
  import type { PluginTs } from '../types.ts'
4
4
 
5
5
  /**
6
- * Resolver for `@kubb/plugin-ts` that provides the default naming and path-resolution
7
- * helpers used by the plugin. Import this in other plugins to resolve the exact names and
8
- * paths that `plugin-ts` generates without hardcoding the conventions.
6
+ * Default resolver used by `@kubb/plugin-ts`. Decides the names and file paths
7
+ * for every generated TypeScript type. Import this in other plugins that need
8
+ * to reference the exact names `plugin-ts` produces without duplicating the
9
+ * casing/file-layout rules.
9
10
  *
10
- * The `default` method is automatically injected by `defineResolver` it uses `camelCase`
11
- * for identifiers/files and `pascalCase` for type names.
11
+ * The `default` method is supplied by `defineResolver`. It uses PascalCase for
12
+ * type names and PascalCase-with-isFile for files.
12
13
  *
13
- * @example
14
+ * @example Resolve a type and file name
14
15
  * ```ts
15
- * import { resolver } from '@kubb/plugin-ts'
16
+ * import { resolverTs } from '@kubb/plugin-ts'
16
17
  *
17
- * resolver.default('list pets', 'type') // 'ListPets'
18
- * resolver.resolveName('list pets status 200') // 'ListPetsStatus200'
19
- * resolver.resolvePathName('list pets', 'file') // 'listPets'
18
+ * resolverTs.default('list pets', 'type') // 'ListPets'
19
+ * resolverTs.resolvePathName('list pets', 'file') // 'ListPets'
20
+ * resolverTs.resolveResponseStatusName(node, 200) // 'ListPetsStatus200'
20
21
  * ```
21
22
  */
22
23
  export const resolverTs = defineResolver<PluginTs>(() => {
@@ -24,13 +25,15 @@ export const resolverTs = defineResolver<PluginTs>(() => {
24
25
  name: 'default',
25
26
  pluginName: 'plugin-ts',
26
27
  default(name, type) {
27
- return pascalCase(name, { isFile: type === 'file' })
28
+ const resolved = pascalCase(name, { isFile: type === 'file' })
29
+ return type === 'file' ? resolved : ensureValidVarName(resolved)
28
30
  },
29
31
  resolveTypeName(name) {
30
- return pascalCase(name)
32
+ return ensureValidVarName(pascalCase(name))
31
33
  },
32
34
  resolvePathName(name, type) {
33
- return pascalCase(name, { isFile: type === 'file' })
35
+ const resolved = pascalCase(name, { isFile: type === 'file' })
36
+ return type === 'file' ? resolved : ensureValidVarName(resolved)
34
37
  },
35
38
  resolveParamName(node, param) {
36
39
  return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`)
package/src/types.ts CHANGED
@@ -115,7 +115,7 @@ type EnumTypeOptions =
115
115
  /**
116
116
  * Suffix appended to the generated type alias name.
117
117
  *
118
- * Only affects the type alias the const object name is unchanged.
118
+ * Only affects the type alias; the const object name is unchanged.
119
119
  *
120
120
  * @default 'Key'
121
121
  * @example enumTypeSuffix: 'Value' → `export type PetStatusValue = …`
@@ -172,81 +172,89 @@ type EnumTypeOptions =
172
172
  enumTypeSuffix?: never
173
173
  /**
174
174
  * `enumKeyCasing` has no effect for this `enumType`.
175
- * Literal and inlineLiteral modes emit only values keys are discarded entirely.
175
+ * Literal and inlineLiteral modes emit only values; keys are discarded entirely.
176
176
  */
177
177
  enumKeyCasing?: never
178
178
  }
179
179
 
180
180
  export type Options = {
181
181
  /**
182
- * Specify the export location for the files and define the behavior of the output
183
- * @default { path: 'types', barrelType: 'named' }
182
+ * Where the generated `.ts` files are written and how they are exported.
183
+ *
184
+ * @default { path: 'types', barrel: { type: 'named' } }
184
185
  */
185
186
  output?: Output
186
187
  /**
187
- * Define which contentType should be used.
188
- * By default, uses the first valid JSON media type.
188
+ * Media type read from the OpenAPI spec when an operation defines several.
189
+ * Defaults to the first JSON-compatible media type Kubb finds.
189
190
  */
190
191
  contentType?: 'application/json' | (string & {})
191
192
  /**
192
- * Group the clients based on the provided name.
193
+ * Split generated files into subfolders based on the operation's tag.
193
194
  */
194
195
  group?: Group
195
196
  /**
196
- * Array containing exclude parameters to exclude/skip tags/operations/methods/paths.
197
+ * Skip operations matching at least one entry in the list.
197
198
  */
198
199
  exclude?: Array<Exclude>
199
200
  /**
200
- * Array containing include parameters to include tags/operations/methods/paths.
201
+ * Restrict generation to operations matching at least one entry in the list.
201
202
  */
202
203
  include?: Array<Include>
203
204
  /**
204
- * Array containing override parameters to override `options` based on tags/operations/methods/paths.
205
+ * Apply a different options object to operations matching a pattern.
205
206
  */
206
207
  override?: Array<Override<ResolvedOptions>>
207
208
  /**
208
- * Switch between type or interface for creating TypeScript types.
209
- * - 'type' generates type alias declarations.
210
- * - 'interface' generates interface declarations.
209
+ * Whether object schemas are emitted as `type` aliases or `interface` declarations.
210
+ * - `'type'` emits closed type aliases. Safer default for generated code.
211
+ * - `'interface'` emits interfaces. Useful when consumers rely on declaration merging.
212
+ *
211
213
  * @default 'type'
214
+ * @see https://www.totaltypescript.com/type-vs-interface-which-should-you-use
212
215
  */
213
216
  syntaxType?: 'type' | 'interface'
214
217
  /**
215
- * Choose what to use as mode for an optional value.
216
- * - 'questionToken' marks the property as optional with ? (e.g., type?: string).
217
- * - 'undefined' adds undefined to the type union (e.g., type: string | undefined).
218
- * - 'questionTokenAndUndefined' combines both approaches (e.g., type?: string | undefined).
218
+ * How optional properties are written in generated types.
219
+ * - `'questionToken'` `type?: string`. The property may be missing.
220
+ * - `'undefined'` `type: string | undefined`. Required to exist, may be `undefined`.
221
+ * - `'questionTokenAndUndefined'` `type?: string | undefined`. Strictest.
222
+ *
219
223
  * @default 'questionToken'
224
+ * @note Pick `'questionTokenAndUndefined'` when `exactOptionalPropertyTypes` is on in `tsconfig.json`.
220
225
  */
221
226
  optionalType?: 'questionToken' | 'undefined' | 'questionTokenAndUndefined'
222
227
  /**
223
- * Choose between Array<string> or string[] for array types.
224
- * - 'generic' generates Array<Type> syntax.
225
- * - 'array' generates Type[] syntax.
228
+ * Syntax used for array types.
229
+ * - `'array'` `Type[]`. Shorter.
230
+ * - `'generic'` `Array<Type>`. More readable for complex element types.
231
+ *
226
232
  * @default 'array'
227
233
  */
228
234
  arrayType?: 'generic' | 'array'
229
235
  /**
230
- * How to style your params, by default no casing is applied
231
- * - 'camelcase' uses camelCase for pathParams, queryParams and headerParams property names
232
- * @default undefined
233
- * @note response types (data/body) are NOT affected by this option
236
+ * Rename properties inside `PathParams`, `QueryParams`, and `HeaderParams` types.
237
+ * Response and request body types are not affected.
238
+ *
239
+ * @note Every plugin that touches operation parameters must use the same value.
234
240
  */
235
241
  paramsCasing?: 'camelcase'
236
242
  /**
237
- * Define some generators next to the ts generators
243
+ * Custom generators that run alongside the built-in TypeScript generators.
238
244
  */
239
245
  generators?: Array<Generator<PluginTs>>
240
246
  /**
241
- * Override naming conventions. When a method returns `null` or `undefined`, the preset
242
- * resolver (`resolverTs`) is used as fallback.
247
+ * Override how names and file paths are built for generated symbols.
248
+ * Methods you omit fall back to the default `resolverTs`. `this` is bound to the
249
+ * full resolver, so `this.default(name, 'function')` delegates to the built-in
250
+ * implementation.
243
251
  */
244
252
  resolver?: Partial<ResolverTs> & ThisType<ResolverTs>
245
253
  /**
246
- * AST visitor applied to each schema/operation node before printing.
247
- * Returning `null` or `undefined` from a visitor method falls back to the preset transformer.
254
+ * AST visitor applied to each schema or operation node before printing.
255
+ * Methods you omit fall back to the preset transformer.
248
256
  *
249
- * @example Remove writeOnly properties from response types
257
+ * @example Drop writeOnly properties from response types
250
258
  * ```ts
251
259
  * transformer: {
252
260
  * property(node) {
@@ -257,18 +265,17 @@ export type Options = {
257
265
  */
258
266
  transformer?: ast.Visitor
259
267
  /**
260
- * Override individual printer node handlers to customize rendering of specific schema types.
261
- *
262
- * Each key is a `SchemaType` (e.g. `'date'`, `'string'`). The function replaces the
263
- * built-in handler for that type. Use `this.transform` to recurse into nested schema nodes.
268
+ * Replace the TypeScript handler for a specific schema type (`'integer'`, `'date'`, ...).
269
+ * Each handler returns a TypeScript AST node for that schema type. Use `this.transform`
270
+ * to recurse into nested schema nodes.
264
271
  *
265
- * @example Override the `date` node to use the `Date` object type
272
+ * @example Use the JavaScript `Date` object for date schemas
266
273
  * ```ts
267
274
  * import ts from 'typescript'
268
275
  * pluginTs({
269
276
  * printer: {
270
277
  * nodes: {
271
- * date(node) {
278
+ * date() {
272
279
  * return ts.factory.createTypeReferenceNode('Date', [])
273
280
  * },
274
281
  * },
@@ -286,7 +293,7 @@ type ResolvedOptions = {
286
293
  exclude: Array<Exclude>
287
294
  include: Array<Include> | undefined
288
295
  override: Array<Override<ResolvedOptions>>
289
- group: Group | undefined
296
+ group: Group | null
290
297
  enumType: NonNullable<Options['enumType']>
291
298
  enumTypeSuffix: NonNullable<Options['enumTypeSuffix']>
292
299
  enumKeyCasing: EnumKeyCasing
package/src/utils.ts CHANGED
@@ -16,19 +16,19 @@ export function buildPropertyJSDocComments(schema: ast.SchemaNode): Array<string
16
16
  const isArray = meta?.primitive === 'array'
17
17
 
18
18
  return [
19
- meta && 'description' in meta && meta.description ? `@description ${jsStringEscape(meta.description)}` : undefined,
20
- meta && 'deprecated' in meta && meta.deprecated ? '@deprecated' : undefined,
19
+ meta && 'description' in meta && meta.description ? `@description ${jsStringEscape(meta.description)}` : null,
20
+ meta && 'deprecated' in meta && meta.deprecated ? '@deprecated' : null,
21
21
  // minItems/maxItems on arrays should not be emitted as @minLength/@maxLength
22
- !isArray && meta && 'min' in meta && meta.min !== undefined ? `@minLength ${meta.min}` : undefined,
23
- !isArray && meta && 'max' in meta && meta.max !== undefined ? `@maxLength ${meta.max}` : undefined,
24
- meta && 'pattern' in meta && meta.pattern ? `@pattern ${meta.pattern}` : undefined,
22
+ !isArray && meta && 'min' in meta && meta.min !== undefined ? `@minLength ${meta.min}` : null,
23
+ !isArray && meta && 'max' in meta && meta.max !== undefined ? `@maxLength ${meta.max}` : null,
24
+ meta && 'pattern' in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
25
25
  meta && 'default' in meta && meta.default !== undefined
26
26
  ? `@default ${'primitive' in meta && meta.primitive === 'string' ? stringify(meta.default as string) : meta.default}`
27
- : undefined,
28
- meta && 'example' in meta && meta.example !== undefined ? `@example ${meta.example}` : undefined,
27
+ : null,
28
+ meta && 'example' in meta && meta.example !== undefined ? `@example ${meta.example}` : null,
29
29
  meta && 'primitive' in meta && meta.primitive
30
- ? [`@type ${meta.primitive}`, 'optional' in schema && schema.optional ? ' | undefined' : undefined].filter(Boolean).join('')
31
- : undefined,
30
+ ? [`@type ${meta.primitive}`, 'optional' in schema && schema.optional ? ' | undefined' : null].filter(Boolean).join('')
31
+ : null,
32
32
  ].filter(Boolean)
33
33
  }
34
34