@kubb/plugin-ts 5.0.0-beta.4 → 5.0.0-beta.56

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,19 +1,16 @@
1
1
  {
2
2
  "name": "@kubb/plugin-ts",
3
- "version": "5.0.0-beta.4",
4
- "description": "TypeScript code generation plugin for Kubb, transforming OpenAPI schemas into TypeScript interfaces, types, and utility functions.",
3
+ "version": "5.0.0-beta.56",
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
- "code-generator",
6
+ "code-generation",
7
7
  "codegen",
8
+ "enums",
8
9
  "interfaces",
9
10
  "kubb",
10
- "oas",
11
11
  "openapi",
12
- "plugins",
13
12
  "swagger",
14
- "type-definitions",
15
13
  "type-generation",
16
- "type-safe",
17
14
  "types",
18
15
  "typescript"
19
16
  ],
@@ -27,7 +24,6 @@
27
24
  "files": [
28
25
  "src",
29
26
  "dist",
30
- "extension.yaml",
31
27
  "!/**/**.test.**",
32
28
  "!/**/__tests__/**",
33
29
  "!/**/__snapshots__/**"
@@ -49,35 +45,30 @@
49
45
  "registry": "https://registry.npmjs.org/"
50
46
  },
51
47
  "dependencies": {
52
- "@kubb/core": "5.0.0-beta.4",
53
- "@kubb/parser-ts": "5.0.0-beta.4",
54
- "@kubb/renderer-jsx": "5.0.0-beta.4",
55
- "remeda": "^2.34.0",
48
+ "@kubb/ast": "5.0.0-beta.55",
49
+ "@kubb/core": "5.0.0-beta.55",
50
+ "@kubb/parser-ts": "5.0.0-beta.55",
51
+ "@kubb/renderer-jsx": "5.0.0-beta.55",
56
52
  "typescript": "^6.0.3"
57
53
  },
58
54
  "devDependencies": {
55
+ "@internals/shared": "0.0.0",
59
56
  "@internals/utils": "0.0.0"
60
57
  },
61
58
  "peerDependencies": {
62
- "@kubb/renderer-jsx": "5.0.0-beta.4"
59
+ "@kubb/renderer-jsx": "5.0.0-beta.55"
63
60
  },
64
- "size-limit": [
65
- {
66
- "path": "./dist/*.js",
67
- "limit": "510 KiB",
68
- "gzip": true
69
- }
70
- ],
71
61
  "engines": {
72
62
  "node": ">=22"
73
63
  },
74
64
  "scripts": {
75
- "build": "tsdown && size-limit",
76
- "clean": "npx rimraf ./dist",
65
+ "build": "tsdown",
66
+ "clean": "node -e \"require('node:fs').rmSync('./dist', {recursive:true,force:true})\"",
77
67
  "lint": "oxlint .",
78
68
  "lint:fix": "oxlint --fix .",
79
69
  "release": "pnpm publish --no-git-check",
80
70
  "release:canary": "bash ../../.github/canary.sh && node ../../scripts/build.js canary && pnpm publish --no-git-check",
71
+ "release:stage": "pnpm stage publish --no-git-check",
81
72
  "start": "tsdown --watch",
82
73
  "test": "vitest --passWithNoTests",
83
74
  "typecheck": "tsc -p ./tsconfig.json --noEmit --emitDeclarationOnly false"
@@ -1,17 +1,18 @@
1
- import { camelCase, trimQuotes } from '@internals/utils'
1
+ import { camelCase } from '@internals/utils'
2
+ import { trimQuotes } from '@kubb/ast/utils'
2
3
  import type { ast } from '@kubb/core'
3
- import { safePrint } from '@kubb/parser-ts'
4
+ import { parserTs } from '@kubb/parser-ts'
4
5
  import { File } from '@kubb/renderer-jsx'
5
6
  import type { KubbReactNode } from '@kubb/renderer-jsx/types'
6
7
  import { ENUM_TYPES_WITH_KEY_SUFFIX, ENUM_TYPES_WITH_RUNTIME_VALUE, ENUM_TYPES_WITH_TYPE_ONLY } from '../constants.ts'
7
8
  import * as factory from '../factory.ts'
8
9
  import type { PluginTs, ResolverTs } from '../types.ts'
9
10
 
11
+ type EnumOptions = PluginTs['resolvedOptions']['enum']
12
+
10
13
  type Props = {
11
14
  node: ast.EnumSchemaNode
12
- enumType: PluginTs['resolvedOptions']['enumType']
13
- enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
14
- enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
15
+ enum: EnumOptions
15
16
  resolver: ResolverTs
16
17
  key?: string | number | null
17
18
  }
@@ -20,26 +21,19 @@ type Props = {
20
21
  * Resolves the runtime identifier name and the TypeScript type name for an enum schema node.
21
22
  *
22
23
  * The raw `node.name` may be a YAML key such as `"enumNames.Type"` which is not a
23
- * valid TypeScript identifier. The resolver normalizes it; for inline enum
24
- * properties the adapter already emits a PascalCase+suffix name so resolution is typically a no-op.
24
+ * valid TypeScript identifier. The resolver normalizes it. For inline enum properties the adapter
25
+ * already emits a PascalCase+suffix name, so resolution is typically a no-op.
26
+ *
27
+ * When `constCasing` is `'pascalCase'` and `typeSuffix` is empty, the const and the type
28
+ * resolve to the same name, which TypeScript merges into a single value+type declaration.
25
29
  */
26
- export function getEnumNames({
27
- node,
28
- enumType,
29
- enumTypeSuffix,
30
- resolver,
31
- }: {
32
- node: ast.EnumSchemaNode
33
- enumType: PluginTs['resolvedOptions']['enumType']
34
- enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
35
- resolver: ResolverTs
36
- }): {
30
+ export function getEnumNames({ node, enum: enumOptions, resolver }: { node: ast.EnumSchemaNode; enum: EnumOptions; resolver: ResolverTs }): {
37
31
  enumName: string
38
32
  typeName: string
39
33
  } {
40
34
  const resolved = resolver.default(node.name!, 'type')
41
- const enumName = enumType === 'asPascalConst' ? resolved : camelCase(node.name!)
42
- const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolved
35
+ const enumName = enumOptions.constCasing === 'pascalCase' ? resolved : camelCase(node.name!)
36
+ const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolved
43
37
 
44
38
  return { enumName, typeName }
45
39
  }
@@ -47,16 +41,16 @@ export function getEnumNames({
47
41
  /**
48
42
  * Renders the enum declaration(s) for a single named `EnumSchemaNode`.
49
43
  *
50
- * Depending on `enumType` this may emit:
51
- * - A runtime object (`asConst` / `asPascalConst`) plus a `typeof` type alias
44
+ * Depending on `enum.type` this may emit:
45
+ * - A runtime object (`asConst`) plus a `typeof` type alias
52
46
  * - A `const enum` or plain `enum` declaration (`constEnum` / `enum`)
53
47
  * - A union literal type alias (`literal`)
54
48
  *
55
49
  * The emitted `File.Source` nodes carry the resolved names so that the barrel
56
50
  * index picks up the correct export identifiers.
57
51
  */
58
- export function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }: Props): KubbReactNode {
59
- const { enumName, typeName } = getEnumNames({ node, enumType, enumTypeSuffix, resolver })
52
+ export function Enum({ node, enum: enumOptions, resolver }: Props): KubbReactNode {
53
+ const { enumName, typeName } = getEnumNames({ node, enum: enumOptions, resolver })
60
54
 
61
55
  const [nameNode, typeNode] = factory.createEnumDeclaration({
62
56
  name: enumName,
@@ -64,19 +58,29 @@ export function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }
64
58
  enums: (node.namedEnumValues?.map((v) => [trimQuotes(v.name.toString()), v.value]) ??
65
59
  node.enumValues?.filter((v): v is NonNullable<typeof v> => v !== null && v !== undefined).map((v) => [trimQuotes(v.toString()), v]) ??
66
60
  []) as Array<[string | number, string | number | boolean]>,
67
- type: enumType,
68
- enumKeyCasing,
61
+ type: enumOptions.type,
62
+ enumKeyCasing: enumOptions.keyCasing,
69
63
  })
70
64
 
65
+ // When the const and the type share a name (pascalCase const + empty typeSuffix) they merge into
66
+ // one declaration. The const carries the barrel export, so keep the type alias in the file but out
67
+ // of the barrel to avoid re-exporting the name a second time as `export type { … }`.
68
+ const namesMerge = !!nameNode && enumName === typeName
69
+
71
70
  return (
72
71
  <>
73
72
  {nameNode && (
74
73
  <File.Source name={enumName} isExportable isIndexable isTypeOnly={false}>
75
- {safePrint(nameNode)}
74
+ {parserTs.print(nameNode)}
76
75
  </File.Source>
77
76
  )}
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)}
77
+ <File.Source
78
+ name={typeName}
79
+ isIndexable={!namesMerge}
80
+ isExportable={!namesMerge && ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumOptions.type)}
81
+ isTypeOnly={ENUM_TYPES_WITH_TYPE_ONLY.has(enumOptions.type)}
82
+ >
83
+ {parserTs.print(typeNode)}
80
84
  </File.Source>
81
85
  </>
82
86
  )
@@ -13,13 +13,11 @@ type Props = {
13
13
  * Created with `printerTs({ ..., nodes: options.printer?.nodes })`.
14
14
  */
15
15
  printer: ast.Printer<PrinterTsFactory>
16
- enumType: PluginTs['resolvedOptions']['enumType']
17
- enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
18
- enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
16
+ enum: PluginTs['resolvedOptions']['enum']
19
17
  resolver: ResolverTs
20
18
  }
21
19
 
22
- export function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, resolver }: Props): KubbReactNode {
20
+ export function Type({ name, node, printer, enum: enumOptions, resolver }: Props): KubbReactNode {
23
21
  const enumSchemaNodes = ast.collect<ast.EnumSchemaNode>(node, {
24
22
  schema(n): ast.EnumSchemaNode | undefined {
25
23
  const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum)
@@ -36,20 +34,17 @@ export function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCas
36
34
  const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
37
35
  return {
38
36
  node,
39
- ...getEnumNames({ node, enumType, enumTypeSuffix, resolver }),
37
+ ...getEnumNames({ node, enum: enumOptions, resolver }),
40
38
  }
41
39
  })
42
40
 
43
41
  // Skip enum exports when using inlineLiteral
44
- const shouldExportEnums = enumType !== 'inlineLiteral'
45
- const shouldExportType = enumType === 'inlineLiteral' || enums.every((item) => item.typeName !== name)
42
+ const shouldExportEnums = enumOptions.type !== 'inlineLiteral'
43
+ const shouldExportType = enumOptions.type === 'inlineLiteral' || enums.every((item) => item.typeName !== name)
46
44
 
47
45
  return (
48
46
  <>
49
- {shouldExportEnums &&
50
- enums.map(({ node }) => (
51
- <Enum key={node.name} node={node} enumType={enumType} enumTypeSuffix={enumTypeSuffix} enumKeyCasing={enumKeyCasing} resolver={resolver} />
52
- ))}
47
+ {shouldExportEnums && enums.map(({ node }) => <Enum key={node.name} node={node} enum={enumOptions} resolver={resolver} />)}
53
48
  {shouldExportType && (
54
49
  <File.Source name={name} isTypeOnly isExportable isIndexable>
55
50
  {output}
package/src/constants.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { PluginTs } from './types.ts'
2
2
 
3
3
  type OptionalType = PluginTs['resolvedOptions']['optionalType']
4
- type EnumType = PluginTs['resolvedOptions']['enumType']
4
+ type EnumType = PluginTs['resolvedOptions']['enum']['type']
5
5
 
6
6
  /**
7
7
  * `optionalType` values that cause a property's type to include `| undefined`.
@@ -14,19 +14,19 @@ export const OPTIONAL_ADDS_UNDEFINED = new Set<OptionalType>(['undefined', 'ques
14
14
  export const OPTIONAL_ADDS_QUESTION_TOKEN = new Set<OptionalType>(['questionToken', 'questionTokenAndUndefined'] as const)
15
15
 
16
16
  /**
17
- * `enumType` values that append a `Key` suffix to the generated enum type alias.
17
+ * `enum.type` values that append a `typeSuffix` to the generated enum type alias.
18
18
  */
19
- export const ENUM_TYPES_WITH_KEY_SUFFIX = new Set<EnumType>(['asConst', 'asPascalConst'] as const)
19
+ export const ENUM_TYPES_WITH_KEY_SUFFIX = new Set<EnumType>(['asConst'] as const)
20
20
 
21
21
  /**
22
- * `enumType` values that require a runtime value declaration (object, enum, or literal).
22
+ * `enum.type` values that require a runtime value declaration (object, enum, or literal).
23
23
  */
24
- export const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set<EnumType | undefined>(['enum', 'asConst', 'asPascalConst', 'constEnum', 'literal', undefined] as const)
24
+ export const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set<EnumType | undefined>(['enum', 'asConst', 'constEnum', 'literal', undefined] as const)
25
25
 
26
26
  /**
27
- * `enumType` values whose type declaration is type-only (no runtime value emitted for the type alias).
27
+ * `enum.type` values whose type declaration is type-only (no runtime value emitted for the type alias).
28
28
  */
29
- export const ENUM_TYPES_WITH_TYPE_ONLY = new Set<EnumType | undefined>(['asConst', 'asPascalConst', 'literal', undefined] as const)
29
+ export const ENUM_TYPES_WITH_TYPE_ONLY = new Set<EnumType | undefined>(['asConst', 'literal', undefined] as const)
30
30
 
31
31
  /**
32
32
  * Ordering priority for function parameters: lower = sorted earlier.
package/src/factory.ts CHANGED
@@ -1,11 +1,24 @@
1
1
  import { camelCase, pascalCase, screamingSnakeCase, snakeCase } from '@internals/utils'
2
2
  import { ast } from '@kubb/core'
3
- import { isNumber, sortBy } from 'remeda'
4
3
  import ts from 'typescript'
5
4
  import { OPTIONAL_ADDS_UNDEFINED } from './constants.ts'
6
5
 
7
6
  const { SyntaxKind, factory } = ts
8
7
 
8
+ /**
9
+ * Compares two strings by UTF-16 code unit, keeping sorted output identical across platforms
10
+ * regardless of locale.
11
+ */
12
+ function compareStrings(a: string, b: string): number {
13
+ if (a < b) return -1
14
+ if (a > b) return 1
15
+ return 0
16
+ }
17
+
18
+ function isNumber(value: unknown): value is number {
19
+ return typeof value === 'number' && !Number.isNaN(value)
20
+ }
21
+
9
22
  // https://ts-ast-viewer.com/
10
23
 
11
24
  /**
@@ -27,13 +40,30 @@ export const syntaxKind = {
27
40
  stringLiteral: SyntaxKind.StringLiteral,
28
41
  } as const
29
42
 
43
+ function isNonNullable<T>(value: T | null | undefined): value is T {
44
+ return value !== null && value !== undefined
45
+ }
46
+
30
47
  function isValidIdentifier(str: string): boolean {
31
48
  if (!str.length || str.trim() !== str) {
32
49
  return false
33
50
  }
34
- const node = ts.parseIsolatedEntityName(str, ts.ScriptTarget.Latest)
35
51
 
36
- return !!node && node.kind === ts.SyntaxKind.Identifier && ts.identifierToKeywordKind(node.kind as unknown as ts.Identifier) === undefined
52
+ // Mirrors `ts.isIdentifierText`, which is not in the public type declarations.
53
+ // Walking by code point with `isIdentifierStart`/`isIdentifierPart` rejects
54
+ // invalid names such as private identifiers (`#FOO`), forcing `propertyName`
55
+ // to quote them.
56
+ let ch = str.codePointAt(0)!
57
+ if (!ts.isIdentifierStart(ch, ts.ScriptTarget.Latest)) {
58
+ return false
59
+ }
60
+ for (let i = ch > 0xffff ? 2 : 1; i < str.length; i += ch > 0xffff ? 2 : 1) {
61
+ ch = str.codePointAt(i)!
62
+ if (!ts.isIdentifierPart(ch, ts.ScriptTarget.Latest)) {
63
+ return false
64
+ }
65
+ }
66
+ return true
37
67
  }
38
68
 
39
69
  function propertyName(name: string | ts.PropertyName): ts.PropertyName {
@@ -158,7 +188,9 @@ export function createPropertySignature({
158
188
  type?: ts.TypeNode
159
189
  }) {
160
190
  return factory.createPropertySignature(
161
- [...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : undefined].filter(Boolean),
191
+ [...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : undefined].filter(
192
+ (modifier): modifier is ts.Modifier => modifier !== undefined,
193
+ ),
162
194
  propertyName(name),
163
195
  createQuestionToken(questionToken),
164
196
  type,
@@ -192,7 +224,7 @@ export function createParameterSignature(
192
224
  * Creates a JSDoc comment node from an array of comment strings.
193
225
  * Returns null if no comments are provided.
194
226
  */
195
- export function createJSDoc({ comments }: { comments: string[] }) {
227
+ export function createJSDoc({ comments }: { comments: Array<string> }) {
196
228
  if (!comments.length) {
197
229
  return null
198
230
  }
@@ -329,18 +361,6 @@ export function createTypeDeclaration({
329
361
  })
330
362
  }
331
363
 
332
- /**
333
- * Creates a TypeScript namespace declaration (exported module).
334
- */
335
- export function createNamespaceDeclaration({ statements, name }: { name: string; statements: ts.Statement[] }) {
336
- return factory.createModuleDeclaration(
337
- [factory.createToken(ts.SyntaxKind.ExportKeyword)],
338
- factory.createIdentifier(name),
339
- factory.createModuleBlock(statements),
340
- ts.NodeFlags.Namespace,
341
- )
342
- }
343
-
344
364
  /**
345
365
  * Creates an import declaration with support for default imports, named imports, namespace imports, and type-only imports.
346
366
  * Optionally rename imported members with `propertyName` and `name` pairs.
@@ -366,13 +386,8 @@ export function createImportDeclaration({
366
386
  isNameSpace?: boolean
367
387
  }) {
368
388
  if (!Array.isArray(name)) {
369
- let importPropertyName: ts.Identifier | undefined = factory.createIdentifier(name)
370
- let importName: ts.NamedImportBindings | undefined
371
-
372
- if (isNameSpace) {
373
- importPropertyName = undefined
374
- importName = factory.createNamespaceImport(factory.createIdentifier(name))
375
- }
389
+ const importPropertyName = isNameSpace ? undefined : factory.createIdentifier(name)
390
+ const importName = isNameSpace ? factory.createNamespaceImport(factory.createIdentifier(name)) : undefined
376
391
 
377
392
  return factory.createImportDeclaration(
378
393
  undefined,
@@ -383,7 +398,7 @@ export function createImportDeclaration({
383
398
  }
384
399
 
385
400
  // Sort the imports alphabetically for consistent output across platforms
386
- const sortedName = sortBy(name, [(item) => (typeof item === 'object' ? item.propertyName : item), 'asc'])
401
+ const sortedName = name.toSorted((a, b) => compareStrings(typeof a === 'object' ? a.propertyName : a, typeof b === 'object' ? b.propertyName : b))
387
402
 
388
403
  return factory.createImportDeclaration(
389
404
  undefined,
@@ -442,7 +457,7 @@ export function createExportDeclaration({
442
457
  }
443
458
 
444
459
  // Sort the exports alphabetically for consistent output across platforms
445
- const sortedName = sortBy(name, [(propertyName) => (typeof propertyName === 'string' ? propertyName : propertyName.text), 'asc'])
460
+ const sortedName = name.toSorted((a, b) => compareStrings(typeof a === 'string' ? a : a.text, typeof b === 'string' ? b : b.text))
446
461
 
447
462
  return factory.createExportDeclaration(
448
463
  undefined,
@@ -501,15 +516,14 @@ export function createEnumDeclaration({
501
516
  enumKeyCasing = 'none',
502
517
  }: {
503
518
  /**
504
- * Choose to use `enum`, `asConst`, `asPascalConst`, `constEnum`, or `literal` for enums.
519
+ * Choose to use `enum`, `asConst`, `constEnum`, or `literal` for enums.
505
520
  * - `enum`: TypeScript enum
506
- * - `asConst`: const with camelCase name (e.g., `petType`)
507
- * - `asPascalConst`: const with PascalCase name (e.g., `PetType`)
521
+ * - `asConst`: const object asserted with `as const` (the caller decides the const name casing)
508
522
  * - `constEnum`: const enum
509
523
  * - `literal`: literal union type
510
524
  * @default `'enum'`
511
525
  */
512
- type?: 'enum' | 'asConst' | 'asPascalConst' | 'constEnum' | 'literal' | 'inlineLiteral'
526
+ type?: 'enum' | 'asConst' | 'constEnum' | 'literal' | 'inlineLiteral'
513
527
  /**
514
528
  * Enum name in camelCase.
515
529
  */
@@ -518,7 +532,7 @@ export function createEnumDeclaration({
518
532
  * Enum name in PascalCase.
519
533
  */
520
534
  typeName: string
521
- enums: [key: string | number, value: string | number | boolean][]
535
+ enums: Array<[key: string | number, value: string | number | boolean]>
522
536
  /**
523
537
  * Choose the casing for enum key names.
524
538
  * @default 'none'
@@ -553,7 +567,7 @@ export function createEnumDeclaration({
553
567
 
554
568
  return undefined
555
569
  })
556
- .filter(Boolean),
570
+ .filter((node): node is ts.LiteralTypeNode => node !== undefined),
557
571
  ),
558
572
  ),
559
573
  ]
@@ -563,7 +577,9 @@ export function createEnumDeclaration({
563
577
  return [
564
578
  undefined,
565
579
  factory.createEnumDeclaration(
566
- [factory.createToken(ts.SyntaxKind.ExportKeyword), type === 'constEnum' ? factory.createToken(ts.SyntaxKind.ConstKeyword) : undefined].filter(Boolean),
580
+ [factory.createToken(ts.SyntaxKind.ExportKeyword), type === 'constEnum' ? factory.createToken(ts.SyntaxKind.ConstKeyword) : undefined].filter(
581
+ (modifier): modifier is ts.ModifierToken<ts.SyntaxKind.ExportKeyword> | ts.ModifierToken<ts.SyntaxKind.ConstKeyword> => modifier !== undefined,
582
+ ),
567
583
  factory.createIdentifier(typeName),
568
584
  enums
569
585
  .map(([key, value]) => {
@@ -594,14 +610,14 @@ export function createEnumDeclaration({
594
610
 
595
611
  return undefined
596
612
  })
597
- .filter(Boolean),
613
+ .filter((member): member is ts.EnumMember => member !== undefined),
598
614
  ),
599
615
  ]
600
616
  }
601
617
 
602
618
  // used when using `as const` instead of an TypeScript enum.
603
- // name is already PascalCase for asPascalConst and camelCase for asConst (set in Type.tsx)
604
- // typeName has the Key suffix for type alias, so we use name for the const identifier
619
+ // name is already cased by the caller (camelCase or pascalCase, driven by enum.constCasing).
620
+ // typeName carries the typeSuffix for the type alias, so we use name for the const identifier.
605
621
  const identifierName = name
606
622
 
607
623
  // When there are no enum items (empty or all-null enum), don't generate a runtime const.
@@ -657,7 +673,7 @@ export function createEnumDeclaration({
657
673
 
658
674
  return undefined
659
675
  })
660
- .filter(Boolean),
676
+ .filter((property): property is ts.PropertyAssignment => property !== undefined),
661
677
  true,
662
678
  ),
663
679
  factory.createTypeReferenceNode(factory.createIdentifier('const'), undefined),
@@ -737,8 +753,8 @@ export function createUrlTemplateType(path: string): ts.TypeNode {
737
753
  }
738
754
 
739
755
  const segments = normalized.split(/(\{[^}]+\})/)
740
- const parts: string[] = []
741
- const parameterIndices: number[] = []
756
+ const parts: Array<string> = []
757
+ const parameterIndices: Array<number> = []
742
758
 
743
759
  segments.forEach((segment) => {
744
760
  if (segment.startsWith('{') && segment.endsWith('}')) {
@@ -750,7 +766,7 @@ export function createUrlTemplateType(path: string): ts.TypeNode {
750
766
  })
751
767
 
752
768
  const head = ts.factory.createTemplateHead(parts[0] || '')
753
- const templateSpans: ts.TemplateLiteralTypeSpan[] = []
769
+ const templateSpans: Array<ts.TemplateLiteralTypeSpan> = []
754
770
 
755
771
  parameterIndices.forEach((paramIndex, i) => {
756
772
  const isLast = i === parameterIndices.length - 1
@@ -787,21 +803,11 @@ export const createStringLiteral = factory.createStringLiteral
787
803
  */
788
804
  export const createArrayTypeNode = factory.createArrayTypeNode
789
805
 
790
- /**
791
- * Creates a parenthesized type node to control operator precedence.
792
- */
793
- export const createParenthesizedType = factory.createParenthesizedType
794
-
795
806
  /**
796
807
  * Creates a literal type node (e.g., `'hello'`, `42`, `true`).
797
808
  */
798
809
  export const createLiteralTypeNode = factory.createLiteralTypeNode
799
810
 
800
- /**
801
- * Creates a null literal type node.
802
- */
803
- export const createNull = factory.createNull
804
-
805
811
  /**
806
812
  * Creates an identifier node.
807
813
  */
@@ -832,16 +838,6 @@ export const createTrue = factory.createTrue
832
838
  */
833
839
  export const createFalse = factory.createFalse
834
840
 
835
- /**
836
- * Creates an indexed access type node (e.g., `T[K]`).
837
- */
838
- export const createIndexedAccessTypeNode = factory.createIndexedAccessTypeNode
839
-
840
- /**
841
- * Creates a type operator node (e.g., `keyof T`, `readonly T[]`).
842
- */
843
- export const createTypeOperatorNode = factory.createTypeOperatorNode
844
-
845
841
  /**
846
842
  * Creates a prefix unary expression (e.g., negative numbers, logical not).
847
843
  */
@@ -885,7 +881,7 @@ export function buildMemberNodes(
885
881
  members: Array<ast.SchemaNode> | undefined,
886
882
  print: (node: ast.SchemaNode) => ts.TypeNode | null | undefined,
887
883
  ): Array<ts.TypeNode> {
888
- return (members ?? []).map(print).filter(Boolean)
884
+ return (members ?? []).map(print).filter(isNonNullable)
889
885
  }
890
886
 
891
887
  /**
@@ -893,7 +889,7 @@ export function buildMemberNodes(
893
889
  * applying min/max slice and optional/rest element rules.
894
890
  */
895
891
  export function buildTupleNode(node: ast.ArraySchemaNode, print: (node: ast.SchemaNode) => ts.TypeNode | null | undefined): ts.TypeNode | undefined {
896
- let items = (node.items ?? []).map(print).filter(Boolean)
892
+ let items = (node.items ?? []).map(print).filter(isNonNullable)
897
893
 
898
894
  const restNode = node.rest ? (print(node.rest) ?? undefined) : undefined
899
895
  const { min, max } = node