Skip to content

Commit 89c7f23

Browse files
authored
feat(typebox): improve handling of large union types (#24)
This commit introduces a new feature to handle large union types more efficiently in the TypeBox schema codegen. The key changes are: - Introduce a `shouldChunkUnion` function to check if a union type has more than a certain number of members (20 in this case). - Implement the `createChunkNodes` function to split large union types into smaller "chunk" types, each containing a subset of the original union members. - Update the `addLocalTypes` function in `local-type-collector.ts` to detect large unions and create the corresponding chunk types. - The chunk types are added to the `NodeGraph` and `ResolverStore` to be processed by the schema codegen. This change improves the performance and memory usage of the schema codegen when dealing with large union types, which can be common in complex validation schemas.
1 parent 0c4b9fc commit 89c7f23

45 files changed

Lines changed: 916 additions & 211 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
12
import { Node, ts } from 'ts-morph'
23

34
export abstract class BaseTypeHandler {
45
abstract canHandle(node: Node): boolean
5-
abstract handle(node: Node): ts.Expression
6+
abstract handle(node: Node, context: TypeBoxContext): ts.Expression
67
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
2+
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
3+
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
24
import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
3-
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
45
import { ArrayTypeNode, Node, ts } from 'ts-morph'
56

67
export class ArrayTypeHandler extends CollectionBaseHandler {
78
canHandle(node: Node): boolean {
89
return Node.isArrayTypeNode(node)
910
}
1011

11-
handle(node: ArrayTypeNode): ts.Expression {
12-
const typeboxType = getTypeBoxType(node.getElementTypeNode())
12+
handle(node: ArrayTypeNode, context: TypeBoxContext): ts.Expression {
13+
const typeboxType = getTypeBoxType(node.getElementTypeNode(), context)
1314

14-
return makeTypeCall('Array', [typeboxType])
15+
return GenericTypeUtils.makeTypeCall('Array', [typeboxType])
1516
}
1617
}
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
2+
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
3+
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
24
import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
3-
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
45
import { Node, ts } from 'ts-morph'
56

67
export abstract class CollectionBaseHandler extends BaseTypeHandler {
7-
protected processTypeCollection(nodes: Node[], typeBoxFunction: string): ts.Expression {
8-
const typeBoxTypes = nodes.map((node) => getTypeBoxType(node))
8+
protected processTypeCollection(
9+
nodes: Node[],
10+
typeBoxFunction: string,
11+
context: TypeBoxContext,
12+
): ts.Expression {
13+
const typeBoxTypes = nodes.map((node) => getTypeBoxType(node, context))
914
const arrayLiteral = ts.factory.createArrayLiteralExpression(typeBoxTypes)
1015

11-
return makeTypeCall(typeBoxFunction, [arrayLiteral])
16+
return GenericTypeUtils.makeTypeCall(typeBoxFunction, [arrayLiteral])
1217
}
1318
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
2+
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
23
import { IntersectionTypeNode, Node, ts } from 'ts-morph'
34

45
export class IntersectionTypeHandler extends CollectionBaseHandler {
56
canHandle(node: Node): boolean {
67
return Node.isIntersectionTypeNode(node)
78
}
89

9-
handle(node: IntersectionTypeNode): ts.Expression {
10-
return this.processTypeCollection(node.getTypeNodes(), 'Intersect')
10+
handle(node: IntersectionTypeNode, context: TypeBoxContext): ts.Expression {
11+
return this.processTypeCollection(node.getTypeNodes(), 'Intersect', context)
1112
}
1213
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
2+
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
23
import { Node, ts, TupleTypeNode } from 'ts-morph'
34

45
export class TupleTypeHandler extends CollectionBaseHandler {
56
canHandle(node: Node): boolean {
67
return Node.isTupleTypeNode(node)
78
}
89

9-
handle(node: TupleTypeNode): ts.Expression {
10-
return this.processTypeCollection(node.getElements(), 'Tuple')
10+
handle(node: TupleTypeNode, context: TypeBoxContext): ts.Expression {
11+
const elements = node.getElements()
12+
return this.processTypeCollection(elements, 'Tuple', context)
1113
}
1214
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
2+
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
23
import { Node, ts, UnionTypeNode } from 'ts-morph'
34

45
export class UnionTypeHandler extends CollectionBaseHandler {
56
canHandle(node: Node): boolean {
67
return Node.isUnionTypeNode(node)
78
}
89

9-
handle(node: UnionTypeNode): ts.Expression {
10-
return this.processTypeCollection(node.getTypeNodes(), 'Union')
10+
handle(node: UnionTypeNode, context: TypeBoxContext): ts.Expression {
11+
const types = node.getTypeNodes()
12+
return this.processTypeCollection(types, 'Union', context)
1113
}
1214
}

src/handlers/typebox/date-type-handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
2-
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
2+
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
33
import { Node, ts, TypeReferenceNode } from 'ts-morph'
44

55
export class DateTypeHandler extends BaseTypeHandler {
@@ -10,6 +10,6 @@ export class DateTypeHandler extends BaseTypeHandler {
1010
}
1111

1212
handle(): ts.Expression {
13-
return makeTypeCall('Date')
13+
return GenericTypeUtils.makeTypeCall('Date')
1414
}
1515
}

src/handlers/typebox/function-type-handler.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,36 @@
11
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
2+
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
3+
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
24
import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
3-
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
45
import { FunctionTypeNode, Node, ts } from 'ts-morph'
56

67
export class FunctionTypeHandler extends BaseTypeHandler {
78
canHandle(node: Node): boolean {
89
return Node.isFunctionTypeNode(node)
910
}
1011

11-
handle(node: FunctionTypeNode): ts.Expression {
12+
handle(node: FunctionTypeNode, context: TypeBoxContext): ts.Expression {
1213
const parameters = node.getParameters()
1314
const returnType = node.getReturnTypeNode()
1415

1516
// Convert parameters to TypeBox types
1617
const parameterTypes = parameters.map((param) => {
1718
const paramTypeNode = param.getTypeNode()
18-
const paramType = getTypeBoxType(paramTypeNode)
19+
const paramType = getTypeBoxType(paramTypeNode, context)
1920

2021
// Check if parameter is optional
2122
if (param.hasQuestionToken()) {
22-
return makeTypeCall('Optional', [paramType])
23+
return GenericTypeUtils.makeTypeCall('Optional', [paramType])
2324
}
2425

2526
return paramType
2627
})
2728

2829
// Convert return type to TypeBox type
29-
const returnTypeBox = getTypeBoxType(returnType)
30+
const returnTypeBox = getTypeBoxType(returnType, context)
3031

3132
// Create TypeBox Function call with parameters array and return type
32-
return makeTypeCall('Function', [
33+
return GenericTypeUtils.makeTypeCall('Function', [
3334
ts.factory.createArrayLiteralExpression(parameterTypes),
3435
returnTypeBox,
3536
])

src/handlers/typebox/indexed-access-type-handler.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
2+
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
3+
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
24
import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
3-
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
45
import { IndexedAccessTypeNode, Node, ts, TypeNode } from 'ts-morph'
56

67
export class IndexedAccessTypeHandler extends BaseTypeHandler {
78
canHandle(node: Node): boolean {
89
return node.isKind(ts.SyntaxKind.IndexedAccessType)
910
}
1011

11-
handle(node: IndexedAccessTypeNode): ts.Expression {
12+
handle(node: IndexedAccessTypeNode, context: TypeBoxContext): ts.Expression {
1213
const objectType = node.getObjectTypeNode()
1314
const indexType = node.getIndexTypeNode()
1415

@@ -17,18 +18,19 @@ export class IndexedAccessTypeHandler extends BaseTypeHandler {
1718
objectType.isKind(ts.SyntaxKind.TypeQuery) &&
1819
indexType.isKind(ts.SyntaxKind.NumberKeyword)
1920
) {
20-
return this.handleTypeofArrayAccess(objectType, node)
21+
return this.handleTypeofArrayAccess(objectType, node, context)
2122
}
2223

23-
const typeboxObjectType = getTypeBoxType(objectType)
24-
const typeboxIndexType = getTypeBoxType(indexType)
24+
const typeboxObjectType = getTypeBoxType(objectType, context)
25+
const typeboxIndexType = getTypeBoxType(indexType, context)
2526

26-
return makeTypeCall('Index', [typeboxObjectType, typeboxIndexType])
27+
return GenericTypeUtils.makeTypeCall('Index', [typeboxObjectType, typeboxIndexType])
2728
}
2829

2930
private handleTypeofArrayAccess(
3031
typeQuery: Node,
3132
indexedAccessType: IndexedAccessTypeNode,
33+
context: TypeBoxContext,
3234
): ts.Expression {
3335
const typeQueryNode = typeQuery.asKindOrThrow(ts.SyntaxKind.TypeQuery)
3436
const exprName = typeQueryNode.getExprName()
@@ -54,10 +56,10 @@ export class IndexedAccessTypeHandler extends BaseTypeHandler {
5456
}
5557

5658
// Fallback to default Index behavior
57-
const typeboxObjectType = getTypeBoxType(typeQuery)
58-
const typeboxIndexType = getTypeBoxType(indexedAccessType.getIndexTypeNode())
59+
const typeboxObjectType = getTypeBoxType(typeQuery, context)
60+
const typeboxIndexType = getTypeBoxType(indexedAccessType.getIndexTypeNode(), context)
5961

60-
return makeTypeCall('Index', [typeboxObjectType, typeboxIndexType])
62+
return GenericTypeUtils.makeTypeCall('Index', [typeboxObjectType, typeboxIndexType])
6163
}
6264

6365
private extractTupleUnion(typeNode: TypeNode | undefined): ts.Expression | null {
@@ -86,14 +88,18 @@ export class IndexedAccessTypeHandler extends BaseTypeHandler {
8688
if (literal.isKind(ts.SyntaxKind.StringLiteral)) {
8789
const stringLiteral = literal.asKindOrThrow(ts.SyntaxKind.StringLiteral)
8890
const value = stringLiteral.getLiteralValue()
89-
literalTypes.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(value)]))
91+
literalTypes.push(
92+
GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createStringLiteral(value)]),
93+
)
9094
}
9195
}
9296
}
9397

9498
// Return union of literal types if we found any
9599
if (literalTypes.length > 0) {
96-
return makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(literalTypes)])
100+
return GenericTypeUtils.makeTypeCall('Union', [
101+
ts.factory.createArrayLiteralExpression(literalTypes),
102+
])
97103
}
98104
}
99105

src/handlers/typebox/keyof-typeof-handler.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
2-
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
2+
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
33
import { Node, ts, TypeOperatorTypeNode, VariableDeclaration } from 'ts-morph'
44

55
export class KeyOfTypeofHandler extends BaseTypeHandler {
@@ -13,13 +13,13 @@ export class KeyOfTypeofHandler extends BaseTypeHandler {
1313

1414
handle(node: TypeOperatorTypeNode): ts.Expression {
1515
const typeQuery = node.getTypeNode()
16-
if (!Node.isTypeQuery(typeQuery)) return makeTypeCall('Any')
16+
if (!Node.isTypeQuery(typeQuery)) return GenericTypeUtils.makeTypeCall('Any')
1717

1818
const exprName = typeQuery.getExprName()
19-
if (!Node.isIdentifier(exprName)) return makeTypeCall('Any')
19+
if (!Node.isIdentifier(exprName)) return GenericTypeUtils.makeTypeCall('Any')
2020

2121
const keys = this.getObjectKeys(exprName)
22-
return keys.length > 0 ? this.createUnion(keys) : makeTypeCall('Any')
22+
return keys.length > 0 ? this.createUnion(keys) : GenericTypeUtils.makeTypeCall('Any')
2323
}
2424

2525
private getObjectKeys(node: Node): string[] {
@@ -80,11 +80,11 @@ export class KeyOfTypeofHandler extends BaseTypeHandler {
8080
? ts.factory.createNumericLiteral(num)
8181
: ts.factory.createStringLiteral(key)
8282

83-
return makeTypeCall('Literal', [literal])
83+
return GenericTypeUtils.makeTypeCall('Literal', [literal])
8484
})
8585

8686
return literals.length === 1
8787
? literals[0]!
88-
: makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(literals)])
88+
: GenericTypeUtils.makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(literals)])
8989
}
9090
}

0 commit comments

Comments
 (0)