Skip to content

Conversation

@DaxServer
Copy link
Owner

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.

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.
@coderabbitai
Copy link

coderabbitai bot commented Sep 2, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Improved code generation for large unions via automatic chunking and deep instantiation, producing unions and intersected records across chunks.
    • Context-aware type generation powered by a node graph for more accurate outputs.
  • Refactor
    • Consolidated utility helpers into a single generic utility for building TypeBox expressions and static aliases.
    • Broadened handler and parser signatures to accept context, aligning generation across types.
  • Tests
    • Added integration tests covering deep instantiation scenarios and formatting.
  • Chores
    • Minor configuration formatting updates.

Walkthrough

Introduces TypeBoxContext across handlers and utilities, replaces makeTypeCall with GenericTypeUtils, adds chunked-union support (chunk nodes, ChunkParser, printer wiring), threads NodeGraph through traversal → printer → parsers, updates getTypeBoxType to require context, adjusts parsers to use GenericTypeUtils and context, removes legacy helpers, and adds integration tests.

Changes

Cohort / File(s) Summary
Context propagation in handlers
src/handlers/typebox/base-type-handler.ts, .../collection/array-type-handler.ts, .../collection/collection-base-handler.ts, .../collection/intersection-type-handler.ts, .../collection/tuple-type-handler.ts, .../collection/union-type-handler.ts, .../function-type-handler.ts, .../indexed-access-type-handler.ts, .../object/interface-type-handler.ts, .../object/object-like-base-handler.ts, .../object/object-type-handler.ts, .../reference/omit-type-handler.ts, .../reference/partial-type-handler.ts, .../reference/pick-type-handler.ts, .../reference/readonly-type-handler.ts, .../reference/record-type-handler.ts, .../type-reference-handler.ts, .../type/type-operator-base-handler.ts
All handle/process methods updated to accept TypeBoxContext and pass it to getTypeBoxType; internal calls updated accordingly.
GenericTypeUtils migration
src/handlers/typebox/date-type-handler.ts, .../keyof-typeof-handler.ts, .../literal-type-handler.ts, .../simple-type-handler.ts, .../template-literal-type-handler.ts, .../template-literal-type-processor.ts, .../type-query-handler.ts, .../typeof-type-handler.ts, src/utils/key-extraction-utils.ts
Replaced makeTypeCall and related helpers with GenericTypeUtils.makeTypeCall; updated imports.
TypeBox core utility changes
src/utils/typebox-call.ts, src/utils/generic-type-utils.ts
Added TypeBoxContext and required it in getTypeBoxType; switched default to Any when context/node missing; added GenericTypeUtils.addStaticTypeAlias/makeTypeCall and optional export flag on addTypeBoxVariableStatement.
Removed legacy utilities
src/utils/typebox-codegen-utils.ts, src/utils/add-static-type-alias.ts
Deleted makeTypeCall and addStaticTypeAlias modules.
Chunked union feature: traversal/types
src/traverse/local-type-collector.ts, src/traverse/types.ts
Added chunk node type, metadata, and logic to split large unions into chunk nodes and references.
Chunked union feature: parser/printer
src/parsers/parse-chunks.ts, src/printer/typebox-printer.ts
New ChunkParser to emit chunk TypeBox vars and Static aliases; printer detects chunk and chunk-referenced type aliases and delegates to ChunkParser.
NodeGraph wiring
src/index.ts, src/parsers/base-parser.ts, src/traverse/dependency-traversal.ts
Threaded NodeGraph from DependencyTraversal via getNodeGraph into TypeBoxPrinter and BaseParser; added getter on traversal and field/option on parser.
Parsers updated to context + GenericTypeUtils
src/parsers/parse-enums.ts, src/parsers/parse-function-declarations.ts, src/parsers/parse-interfaces.ts, src/parsers/parse-type-aliases.ts
Switched to GenericTypeUtils helpers; passed nodeGraph context to getTypeBoxType; updated Static alias creation to GenericTypeUtils.addStaticTypeAlias.
Tests
tests/integration/deep-instantiation-integration.test.ts
Added integration tests for deep instantiation and chunked unions, including Intersect of per-chunk Records and Static aliases.
Config
tsconfig.build.json
Formatting-only: single-line include/exclude arrays.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant G as generateCode
  participant DT as DependencyTraversal
  participant P as TypeBoxPrinter
  participant CP as ChunkParser
  participant GU as GenericTypeUtils
  participant TBU as getTypeBoxType

  G->>DT: traverse()
  G->>P: printSortedNodes(traversal, printer)
  Note over P: nodeGraph = traversal.getNodeGraph()

  loop For each traversed node
    P->>P: detect 'chunk' or typeAlias with chunkReferences
    alt type == 'chunk' OR has chunkReferences
      P->>CP: parseChunk(traversedNode, aliasName)
      CP->>TBU: getTypeBoxType(node, { nodeGraph })
      TBU-->>CP: ts.Expression (TypeBox)
      CP->>GU: addTypeBoxVariableStatement(..., isExported: false/true)
      CP->>GU: addStaticTypeAlias(...)
    else Other node types
      P->>Parsers: parse with context { nodeGraph }
    end
  end
Loading
sequenceDiagram
  autonumber
  participant RH as RecordTypeHandler
  participant TBU as getTypeBoxType
  participant GU as GenericTypeUtils
  participant NG as NodeGraph

  RH->>NG: resolve key type reference
  alt key type has chunkReferences
    RH->>NG: validate referenced chunk nodes
    loop each chunk
      RH->>TBU: getTypeBoxType(valueType, ctx)
      TBU-->>RH: valueExpr
      RH->>GU: makeTypeCall('Record', [Identifier(chunk), valueExpr])
    end
    RH->>GU: makeTypeCall('Intersect', [ArrayLiteral(records)])
  else no chunks
    RH->>TBU: getTypeBoxType(keyType, ctx)
    RH->>TBU: getTypeBoxType(valueType, ctx)
    RH->>GU: makeTypeCall('Record', [keyExpr, valueExpr])
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch deep-instantiation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
src/handlers/typebox/simple-type-handler.ts (1)

31-38: handle signature must include TypeBoxContext (mismatch with BaseTypeHandler).
Current override omits context; this will fail to compile after the abstract signature change.

Apply:

+import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
@@
-  handle(node: Node): ts.Expression {
+  handle(node: Node, _context: TypeBoxContext): ts.Expression {
     return GenericTypeUtils.makeTypeCall(kindToTypeBox[node.getKind() as SimpleKind])
   }
src/handlers/typebox/template-literal-type-handler.ts (1)

11-12: Update handle signature to include context.
Must match BaseTypeHandler.

Apply:

+import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
@@
-  handle(node: TemplateLiteralTypeNode): ts.Expression {
+  handle(node: TemplateLiteralTypeNode, _context: TypeBoxContext): ts.Expression {
src/handlers/typebox/type-query-handler.ts (1)

21-27: QualifiedName replacement only replaces first dot; can emit invalid identifiers

fullName.replace('.', '_') misses nested qualifiers (e.g., a.b.c). Replace all dots.

-      const fullName = exprName.getText()
-      return ts.factory.createIdentifier(fullName.replace('.', '_'))
+      const fullName = exprName.getText()
+      return ts.factory.createIdentifier(fullName.replace(/\./g, '_'))
src/handlers/typebox/object/interface-type-handler.ts (1)

59-74: Bug: extended generic types can emit invalid identifiers (breaks nested/multi-arg generics).
When parseGenericTypeCall fails (e.g., Foo<Bar> or Map<K, V>), fallback uses ts.factory.createIdentifier(typeText). Identifiers cannot contain '<', '>', ',', or dots, yielding invalid code and wrong schemas.

Apply this minimal fix to resolve extended types via the central pipeline:

@@
-import { HeritageClause, InterfaceDeclaration, Node, ts } from 'ts-morph'
+import { HeritageClause, InterfaceDeclaration, Node, ts } from 'ts-morph'
+import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
@@
-  handle(node: InterfaceDeclaration, context: TypeBoxContext): ts.Expression {
+  handle(node: InterfaceDeclaration, context: TypeBoxContext): ts.Expression {
@@
-    const extendedTypes = this.collectExtendedTypes(heritageClauses)
+    const extendedTypes = this.collectExtendedTypes(heritageClauses, context)
@@
-  private collectExtendedTypes(heritageClauses: HeritageClause[]): ts.Expression[] {
+  private collectExtendedTypes(
+    heritageClauses: HeritageClause[],
+    context: TypeBoxContext,
+  ): ts.Expression[] {
@@
-      for (const typeNode of heritageClause.getTypeNodes()) {
-        const typeText = typeNode.getText()
-        extendedTypes.push(
-          this.parseGenericTypeCall(typeText) ?? ts.factory.createIdentifier(typeText),
-        )
-      }
+      for (const typeNode of heritageClause.getTypeNodes()) {
+        extendedTypes.push(getTypeBoxType(typeNode, context))
+      }

Follow-up: remove parseGenericTypeCall/createTypeExpression as dead code once this change lands.

src/handlers/typebox/object/object-like-base-handler.ts (2)

57-60: Numeric property keys emitted as identifiers (invalid).

123 cannot be an identifier. Return a NumericLiteral to preserve { 123: ... }.

Apply this diff:

-    } else if (Node.isNumericLiteral(nameNode)) {
-      // Numeric properties can be used as identifiers
-      propName = nameNode.getLiteralValue().toString()
-      shouldUseIdentifier = true
+    } else if (Node.isNumericLiteral(nameNode)) {
+      // Preserve numeric literal property names (e.g., { 1: ... })
+      return ts.factory.createNumericLiteral(nameNode.getLiteralValue().toString())

Also applies to: 66-69


53-56: Add reserved word validation to identifier checks
The isValidIdentifier utility only enforces character rules via ts.isIdentifierStart/isIdentifierPart and does not filter out JavaScript reserved keywords (e.g. default, class, function), which would currently pass and break unquoted key handling in object-like-base-handler.ts (lines 53–56). Introduce a reserved-words check (e.g. against a Set of ECMAScript/TypeScript keywords) before accepting an unquoted property name.

🧹 Nitpick comments (30)
tsconfig.build.json (1)

16-17: Consider trimming redundant excludes for simplicity.

With include restricted to "src//*", excluding "tests//*", "dist", and "node_modules" is redundant. Keep only test globs if you want to guard future include changes.

   "include": ["src/**/*"],
-  "exclude": ["tests/**/*", "**/*.test.ts", "**/*.spec.ts", "dist", "node_modules"]
+  "exclude": ["**/*.test.ts", "**/*.spec.ts"]
src/traverse/dependency-traversal.ts (1)

71-74: NodeGraph usage is read-only and safe Consumers (TypeBoxPrinter, GraphVisualizer) only read the graph, and getNodeGraph() is only called after startTraversal(). No external mutation detected. Optional: expose as Readonly<NodeGraph> or add a doc comment to signal immutability.

src/traverse/types.ts (1)

5-13: Prefer discriminated union over duplicate isChunk flag; make chunkReferences required for chunk nodes.

type: 'chunk' already discriminates; isChunk is redundant and invites drift. Also, chunkReferences should be required when type === 'chunk' for stronger typing.

Illustrative shape:

type NodeKind = 'interface' | 'typeAlias' | 'enum' | 'function' | 'chunk'

type TraversedNode =
  Base &
  (
    | { type: 'chunk'; chunkReferences: string[] }
    | { type: Exclude<NodeKind, 'chunk'> }
  )

If keeping the interface, at least deprecate isChunk and document chunkReferences semantics (e.g., qualified names of chunk members).

src/parsers/base-parser.ts (1)

1-22: Add runtime guard and JSDoc for nodeGraph in BaseParser

All parser subclasses and instantiations already forward nodeGraph, so adding a defensive check and documenting the option improves future safety:

  1. In src/parsers/base-parser.ts, prepend a JSDoc on BaseParserOptions.nodeGraph:
    /**
     * Graph of AST nodes to traverse.
     * @readonly
     */
    nodeGraph: NodeGraph
  2. Update the constructor to validate nodeGraph:
     constructor(options: BaseParserOptions) {
       this.newSourceFile = options.newSourceFile
       this.printer       = options.printer
       this.processedTypes= options.processedTypes
    -  this.nodeGraph    = options.nodeGraph
    +  if (!options.nodeGraph) throw new Error('BaseParser requires nodeGraph')
    +  this.nodeGraph    = options.nodeGraph
     }
src/utils/generic-type-utils.ts (1)

151-169: Default compilerNode to newSourceFile.compilerNode and reorder parameters
Change the signature to:

static addStaticTypeAlias(
  newSourceFile: SourceFile,
  name: string,
  printer: ts.Printer,
  compilerNode: ts.SourceFile = newSourceFile.compilerNode,
) {  }

Then update all call sites from:

GenericTypeUtils.addStaticTypeAlias(
  this.newSourceFile,
  typeName,
  this.compilerNode,
  this.printer,
)

to:

GenericTypeUtils.addStaticTypeAlias(
  this.newSourceFile,
  typeName,
  this.printer,
)
// (add a fourth argument only when you need a non‐default compilerNode)

Affected files:

  • src/parsers/parse-type-aliases.ts
  • src/parsers/parse-interfaces.ts
  • src/parsers/parse-chunks.ts (2 occurrences)
  • src/parsers/parse-function-declarations.ts
  • src/parsers/parse-enums.ts
  • docs/utilities.md
src/handlers/typebox/simple-type-handler.ts (1)

6-6: Unused constant.
TypeBoxType isn’t referenced; consider removing to avoid drift.

src/handlers/typebox/date-type-handler.ts (1)

6-10: Optional: support qualified names for Date.
If users reference globalThis.Date or a qualified Date, broaden the check.

src/index.ts (1)

101-103: Optionally decouple printSortedNodes from traversal

Consider passing NodeGraph directly instead of the whole DependencyTraversal to keep printSortedNodes focused on printing concerns.

-import type { SourceFile } from 'ts-morph'
+import type { SourceFile } from 'ts-morph'
+import type { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph'

-const printSortedNodes = (
-  sortedTraversedNodes: TraversedNode[],
-  newSourceFile: SourceFile,
-  dependencyTraversal: DependencyTraversal,
-) => {
+const printSortedNodes = (
+  sortedTraversedNodes: TraversedNode[],
+  newSourceFile: SourceFile,
+  nodeGraph: NodeGraph,
+) => {
   const printer = new TypeBoxPrinter({
     newSourceFile,
     printer: ts.createPrinter(),
-    nodeGraph: dependencyTraversal.getNodeGraph(),
+    nodeGraph,
   })
 }
 
-const result = printSortedNodes(traversedNodes, newSourceFile, dependencyTraversal)
+const result = printSortedNodes(traversedNodes, newSourceFile, dependencyTraversal.getNodeGraph())
src/parsers/parse-enums.ts (1)

22-31: Use GenericTypeUtils.addTypeBoxVariableStatement for consistency (optional)

Minor consistency win and less boilerplate.

Apply:

-    this.newSourceFile.addVariableStatement({
-      isExported: true,
-      declarationKind: VariableDeclarationKind.Const,
-      declarations: [
-        {
-          name: schemaName,
-          initializer: typeboxType,
-        },
-      ],
-    })
+    GenericTypeUtils.addTypeBoxVariableStatement(this.newSourceFile, schemaName, typeboxType, true)

If you adopt this, VariableDeclarationKind can be dropped from the imports.

src/handlers/typebox/indexed-access-type-handler.ts (2)

50-55: Handle const-array initializers for typeof A[number]

Variable declarations often lack an explicit type node (const A = ['a','b'] as const), so extract from the initializer to preserve literal unions.

Apply:

       const variableDeclaration = sourceFile.getVariableDeclaration(typeName)
       if (variableDeclaration) {
-        const tupleUnion = this.extractTupleUnion(variableDeclaration.getTypeNode())
+        const tupleUnion =
+          this.extractTupleUnion(variableDeclaration.getTypeNode()) ??
+          this.extractTupleUnionFromInitializer(variableDeclaration.getInitializer())
         if (tupleUnion) return tupleUnion
       }

Add this helper (outside the selected range):

private extractTupleUnionFromInitializer(expr: Node | undefined): ts.Expression | null {
  if (!expr) return null

  // Unwrap `as const`
  if (expr.isKind(ts.SyntaxKind.AsExpression)) {
    expr = expr.asKindOrThrow(ts.SyntaxKind.AsExpression).getExpression()
  }

  const arr = expr.asKind?.(ts.SyntaxKind.ArrayLiteralExpression)
  if (!arr) return null

  const literals: ts.Expression[] = []
  for (const el of arr.getElements()) {
    const str = el.asKind?.(ts.SyntaxKind.StringLiteral)
    if (str) {
      literals.push(GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createStringLiteral(str.getLiteralValue())]))
    }
  }
  if (literals.length === 0) return null

  // Consider integrating union chunking here (see comment above).
  return GenericTypeUtils.makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(literals)])
}

39-56: Resolve identifiers beyond same-file scope (optional)

Current lookup only checks same-file type aliases/variables and ignores QualifiedName/imported/namespace cases. Using the type checker to resolve the symbol (getSymbol → getDeclarations) would improve correctness for typeof ns.A[number] and imported const tuples.

src/handlers/typebox/object/interface-type-handler.ts (1)

44-57: Optional: remove ad-hoc generic parsing and primitive mapping.
After adopting getTypeBoxType for extends, parseGenericTypeCall/createTypeExpression become redundant and brittle (single type arg only). Safe to delete to reduce surface area.

-  private parseGenericTypeCall(typeText: string): ts.Expression | null { ... }
-
-  private createTypeExpression(typeArg: string): ts.Expression { ... }
src/handlers/typebox/collection/collection-base-handler.ts (1)

14-16: Formatting/readability for large unions.
Emit multi-line arrays to keep huge unions readable in generated code.

-    const arrayLiteral = ts.factory.createArrayLiteralExpression(typeBoxTypes)
+    const arrayLiteral = ts.factory.createArrayLiteralExpression(typeBoxTypes, true)
src/parsers/parse-type-aliases.ts (2)

26-29: Tiny cleanup: reuse a context const.

Avoids re-allocating the same object literal repeatedly.

-    const typeboxTypeNode = typeNode
-      ? getTypeBoxType(typeNode, { nodeGraph: this.nodeGraph })
-      : GenericTypeUtils.makeTypeCall('Any')
+    const ctx = { nodeGraph: this.nodeGraph }
+    const typeboxTypeNode = typeNode
+      ? getTypeBoxType(typeNode, ctx)
+      : GenericTypeUtils.makeTypeCall('Any')

49-52: Deduplicate fallback logic with a helper (optional).

Same Any-fallback pattern appears twice; consider a small helper to keep this DRY.

src/handlers/typebox/template-literal-type-processor.ts (1)

49-65: Broaden union literal handling by delegating to processType for each member.

Current logic only preserves string literals in unions; number/boolean literals downgrade to Type.String(). Prefer mapping each member through processType to retain numeric/boolean literal fidelity.

Apply:

-  private static processUnionType(unionType: ts.UnionTypeNode): ts.Expression {
-    const unionParts = unionType.types.map((t) => {
-      if (t.kind === ts.SyntaxKind.LiteralType) {
-        const literalType = t as ts.LiteralTypeNode
-        if (ts.isStringLiteral(literalType.literal)) {
-          return GenericTypeUtils.makeTypeCall('Literal', [
-            ts.factory.createStringLiteral(literalType.literal.text),
-          ])
-        }
-      }
-      return GenericTypeUtils.makeTypeCall('String') // fallback
-    })
-
-    return GenericTypeUtils.makeTypeCall('Union', [
-      ts.factory.createArrayLiteralExpression(unionParts),
-    ])
-  }
+  private static processUnionType(unionType: ts.UnionTypeNode): ts.Expression {
+    const unionParts = unionType.types.map((t) => this.processType(t))
+    return GenericTypeUtils.makeTypeCall('Union', [
+      ts.factory.createArrayLiteralExpression(unionParts),
+    ])
+  }
src/utils/typebox-call.ts (1)

12-20: All getTypeBoxType calls include a context argument; no missing contexts detected. Optional: add a dev-only guard to throw when context is absent and avoid silent fallback:

export const getTypeBoxType = (node?: Node, context?: TypeBoxContext): ts.Expression => {
-  if (!node || !context) return GenericTypeUtils.makeTypeCall('Any')
+  if (!node || !context) {
+    if (process.env.NODE_ENV !== 'production') {
+      throw new Error('getTypeBoxType: context is required')
+    }
+    return GenericTypeUtils.makeTypeCall('Any')
+  }
src/traverse/local-type-collector.ts (3)

8-13: Threshold semantics: use “> CHUNK_SIZE” to match “exceeding a threshold.”

>= CHUNK_SIZE will chunk exactly-20-member unions into a single chunk, adding overhead with no benefit and potentially producing Type.Union([OnlyOneChunk]).

-  return typeNode.getTypeNodes().length >= CHUNK_SIZE
+  return typeNode.getTypeNodes().length > CHUNK_SIZE

37-76: Optional: avoid accumulating many temp files or reuse a scratch file.

If this runs over many unions, virtual temp files will grow unbounded. Consider:

  • building the union TypeNode via ts.factory (no temp file), or
  • using a single per-source-file scratch with overwrite: true.

39-46: Name collision risk across modules.

Chunk variable names are plain ${parentTypeName}_Chunk${i+1}. If two files define the same parent type, collisions can occur in the single generated output file.

Consider suffixing with a short hash of sourceFile.getFilePath() when deriving chunkName (e.g., _h123abcde).

Also applies to: 69-76

src/handlers/typebox/function-type-handler.ts (1)

17-27: Clarify handling for rest parameters

Rest params aren’t represented; emitting them as ordinary params is incorrect. Either support them explicitly or fail fast with a clear error.

Do you want me to add a guard (e.g., throw with a helpful message) or prototype Type.Rest(...) support if your TypeBox version provides it?

src/handlers/typebox/reference/record-type-handler.ts (1)

52-56: Minor: compute value type once

getTypeBoxType(valueType, context) needn’t run per chunk; the diff above hoists it. Good for performance and clarity.

src/handlers/typebox/reference/omit-type-handler.ts (1)

17-28: Consider chunked key unions for Omit<> as well

With the fallback to getTypeBoxType(keysType, context), chunked key unions will now flow through correctly. Please add a test covering Omit<T, KeysChunked> to prevent regressions.

I can add an integration test mirroring the Record chunked-key path if helpful.

src/handlers/typebox/object/object-like-base-handler.ts (1)

21-29: Extract Optional-call detection into a small util.

Centralize “is Type.Optional(...)” detection to avoid copy/paste and handle edge cases (e.g., parenthesized expressions) consistently.

tests/integration/deep-instantiation-integration.test.ts (3)

5-11: Add threshold boundary test (exactly 20 members shouldn’t chunk).

Cover the no-chunk-at-threshold case to lock behavior.

I can add a test “should not chunk at exactly 20”.


47-63: Add an integration test for Pick with aliased keys.

Ensures the new fallback avoids Type.Union([]) and supports type Keys = ...; type C = Pick<A, Keys>.

I can draft the test case.


306-360: Consider asserting chunk name stability.

Add expectations ensuring chunk order/naming (A_Chunk1..n) is deterministic to prevent flaky diffs.

src/handlers/typebox/keyof-typeof-handler.ts (1)

75-89: Large keyof unions may bypass the new chunking path.

If objects can have > threshold keys, consider chunking here too (using the passed context) to keep memory usage predictable.

Would keyof targets ever exceed the union chunk threshold in your datasets? If yes, I can sketch a context-aware path that emits chunk aliases instead of a monolithic union literal array.

src/parsers/parse-chunks.ts (2)

71-82: Determinism and deduping for chunk references.

Guard against duplicates and ensure stable order for reproducible output.

-    const chunkIdentifiers = traversedNode.chunkReferences.map((qualifiedName) => {
+    const refs = Array.from(new Set(traversedNode.chunkReferences)).sort()
+    const chunkIdentifiers = refs.map((qualifiedName) => {
       const chunkNode = this.nodeGraph.getNode(qualifiedName)
       if (!chunkNode) {
         throw new Error(`Chunk node not found: ${qualifiedName}`)
       }
       // Use the original name of the chunk as an identifier
       return ts.factory.createIdentifier(chunkNode.originalName)
     })

10-14: Redundant re-declarations in ChunkParserOptions.

newSourceFile, printer, processedTypes already exist on BaseParserOptions. Remove to reduce noise.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0c4b9fc and 9ef28fe.

📒 Files selected for processing (45)
  • src/handlers/typebox/base-type-handler.ts (1 hunks)
  • src/handlers/typebox/collection/array-type-handler.ts (1 hunks)
  • src/handlers/typebox/collection/collection-base-handler.ts (1 hunks)
  • src/handlers/typebox/collection/intersection-type-handler.ts (1 hunks)
  • src/handlers/typebox/collection/tuple-type-handler.ts (1 hunks)
  • src/handlers/typebox/collection/union-type-handler.ts (1 hunks)
  • src/handlers/typebox/date-type-handler.ts (2 hunks)
  • src/handlers/typebox/function-type-handler.ts (1 hunks)
  • src/handlers/typebox/indexed-access-type-handler.ts (4 hunks)
  • src/handlers/typebox/keyof-typeof-handler.ts (3 hunks)
  • src/handlers/typebox/literal-type-handler.ts (2 hunks)
  • src/handlers/typebox/object/interface-type-handler.ts (3 hunks)
  • src/handlers/typebox/object/object-like-base-handler.ts (2 hunks)
  • src/handlers/typebox/object/object-type-handler.ts (1 hunks)
  • src/handlers/typebox/reference/omit-type-handler.ts (1 hunks)
  • src/handlers/typebox/reference/partial-type-handler.ts (1 hunks)
  • src/handlers/typebox/reference/pick-type-handler.ts (1 hunks)
  • src/handlers/typebox/reference/readonly-type-handler.ts (1 hunks)
  • src/handlers/typebox/reference/record-type-handler.ts (1 hunks)
  • src/handlers/typebox/reference/required-type-handler.ts (1 hunks)
  • src/handlers/typebox/simple-type-handler.ts (2 hunks)
  • src/handlers/typebox/template-literal-type-handler.ts (3 hunks)
  • src/handlers/typebox/template-literal-type-processor.ts (4 hunks)
  • src/handlers/typebox/type-query-handler.ts (2 hunks)
  • src/handlers/typebox/type-reference-handler.ts (3 hunks)
  • src/handlers/typebox/type/type-operator-base-handler.ts (2 hunks)
  • src/handlers/typebox/typeof-type-handler.ts (2 hunks)
  • src/index.ts (2 hunks)
  • src/parsers/base-parser.ts (1 hunks)
  • src/parsers/parse-chunks.ts (1 hunks)
  • src/parsers/parse-enums.ts (2 hunks)
  • src/parsers/parse-function-declarations.ts (4 hunks)
  • src/parsers/parse-interfaces.ts (2 hunks)
  • src/parsers/parse-type-aliases.ts (2 hunks)
  • src/printer/typebox-printer.ts (1 hunks)
  • src/traverse/dependency-traversal.ts (1 hunks)
  • src/traverse/local-type-collector.ts (2 hunks)
  • src/traverse/types.ts (1 hunks)
  • src/utils/add-static-type-alias.ts (0 hunks)
  • src/utils/generic-type-utils.ts (2 hunks)
  • src/utils/key-extraction-utils.ts (2 hunks)
  • src/utils/typebox-call.ts (1 hunks)
  • src/utils/typebox-codegen-utils.ts (0 hunks)
  • tests/integration/deep-instantiation-integration.test.ts (1 hunks)
  • tsconfig.build.json (1 hunks)
💤 Files with no reviewable changes (2)
  • src/utils/add-static-type-alias.ts
  • src/utils/typebox-codegen-utils.ts
🧰 Additional context used
🧬 Code graph analysis (40)
src/traverse/dependency-traversal.ts (1)
src/traverse/node-graph.ts (1)
  • NodeGraph (8-38)
src/handlers/typebox/date-type-handler.ts (1)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/type-query-handler.ts (1)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/collection/union-type-handler.ts (1)
src/utils/typebox-call.ts (1)
  • TypeBoxContext (8-10)
src/handlers/typebox/object/interface-type-handler.ts (2)
src/utils/typebox-call.ts (1)
  • TypeBoxContext (8-10)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/object/object-type-handler.ts (1)
src/utils/typebox-call.ts (1)
  • TypeBoxContext (8-10)
src/handlers/typebox/template-literal-type-handler.ts (1)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/utils/typebox-call.ts (3)
src/handlers/typebox/typebox-type-handlers.ts (1)
  • TypeBoxTypeHandlers (28-145)
src/traverse/node-graph.ts (1)
  • NodeGraph (8-38)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/template-literal-type-processor.ts (1)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/parsers/parse-type-aliases.ts (2)
src/utils/typebox-call.ts (1)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
tests/integration/deep-instantiation-integration.test.ts (1)
tests/utils.ts (3)
  • createSourceFile (12-14)
  • generateFormattedCode (26-37)
  • formatWithPrettier (16-24)
src/traverse/local-type-collector.ts (3)
src/traverse/node-graph.ts (1)
  • NodeGraph (8-38)
src/utils/resolver-store.ts (1)
  • resolverStore (174-174)
src/traverse/types.ts (1)
  • TraversedNode (3-14)
src/handlers/typebox/reference/omit-type-handler.ts (3)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/utils/key-extraction-utils.ts (2)
  • extractStringKeys (8-28)
  • createTypeBoxKeys (34-46)
src/handlers/typebox/type/type-operator-base-handler.ts (2)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/utils/key-extraction-utils.ts (1)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/parsers/base-parser.ts (1)
src/traverse/node-graph.ts (1)
  • NodeGraph (8-38)
src/handlers/typebox/reference/record-type-handler.ts (3)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/resolver-store.ts (1)
  • resolverStore (174-174)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/literal-type-handler.ts (1)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/index.ts (2)
src/traverse/dependency-traversal.ts (1)
  • DependencyTraversal (15-75)
src/printer/typebox-printer.ts (1)
  • TypeBoxPrinter (17-87)
src/handlers/typebox/collection/intersection-type-handler.ts (1)
src/utils/typebox-call.ts (1)
  • TypeBoxContext (8-10)
src/handlers/typebox/keyof-typeof-handler.ts (1)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/collection/collection-base-handler.ts (2)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/reference/required-type-handler.ts (2)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/reference/readonly-type-handler.ts (2)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/reference/pick-type-handler.ts (3)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/utils/key-extraction-utils.ts (2)
  • extractStringKeys (8-28)
  • createTypeBoxKeys (34-46)
src/handlers/typebox/simple-type-handler.ts (1)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/indexed-access-type-handler.ts (2)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/collection/array-type-handler.ts (2)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/base-type-handler.ts (1)
src/utils/typebox-call.ts (1)
  • TypeBoxContext (8-10)
src/parsers/parse-chunks.ts (4)
src/parsers/base-parser.ts (1)
  • BaseParserOptions (4-9)
src/traverse/types.ts (1)
  • TraversedNode (3-14)
src/utils/typebox-call.ts (1)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/function-type-handler.ts (2)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/collection/tuple-type-handler.ts (1)
src/utils/typebox-call.ts (1)
  • TypeBoxContext (8-10)
src/parsers/parse-enums.ts (1)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/type-reference-handler.ts (2)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/typeof-type-handler.ts (1)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/object/object-like-base-handler.ts (2)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/printer/typebox-printer.ts (3)
src/traverse/node-graph.ts (1)
  • NodeGraph (8-38)
src/parsers/parse-chunks.ts (1)
  • ChunkParser (16-97)
src/traverse/types.ts (1)
  • TraversedNode (3-14)
src/parsers/parse-interfaces.ts (2)
src/utils/typebox-call.ts (1)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/handlers/typebox/reference/partial-type-handler.ts (2)
src/utils/typebox-call.ts (2)
  • TypeBoxContext (8-10)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
src/parsers/parse-function-declarations.ts (2)
src/utils/typebox-call.ts (1)
  • getTypeBoxType (12-20)
src/utils/generic-type-utils.ts (1)
  • GenericTypeUtils (6-181)
🔇 Additional comments (38)
tsconfig.build.json (1)

16-17: No-op formatting change; config stays equivalent. LGTM.

Arrays reflowed to single-line without behavioral impact. Build semantics remain unchanged.

src/utils/generic-type-utils.ts (2)

14-18: Export-control switch is a welcome addition.

The isExported flag makes this helper more generally usable.


171-180: Allow optional type arguments in makeTypeCall

Small extension enables emitting calls like Type.Parameters<Foo<T>>() if needed later.

-  static makeTypeCall(method: string, args: ts.Expression[] = []) {
+  static makeTypeCall(method: string, args: ts.Expression[] = [], typeArgs?: ts.TypeNode[]) {
     return ts.factory.createCallExpression(
       ts.factory.createPropertyAccessExpression(
         ts.factory.createIdentifier('Type'),
         ts.factory.createIdentifier(method),
       ),
-      undefined,
+      typeArgs,
       args,
     )
   }

Verified no existing call site passes a third argument—this change is safe and compiles unchanged.

src/utils/key-extraction-utils.ts (2)

1-1: Refactor to GenericTypeUtils is consistent.


34-46: Confirm empty-keys fallback semantic
The revised createTypeBoxKeys now returns Type.Never() for an empty keys array. Verify that Never is the intended substitute for Type.Union([]). If you’d rather model “no keys” as an empty object (Type.Object({})), undefined (Type.Undefined()), or throw during codegen, please update accordingly.

src/handlers/typebox/simple-type-handler.ts (1)

2-2: Migration to GenericTypeUtils looks good.
Centralizing Type calls via GenericTypeUtils is consistent with the PR direction.

src/handlers/typebox/date-type-handler.ts (1)

2-2: Swapping to GenericTypeUtils is fine.
Keeps TypeBox call construction uniform.

src/handlers/typebox/collection/union-type-handler.ts (1)

10-13: Context-aware union handling LGTM.
Forwarding (types, 'Union', context) into processTypeCollection aligns with the new flow.

src/handlers/typebox/template-literal-type-handler.ts (2)

3-4: GenericTypeUtils migration is consistent and correct.
Literal/TemplateLiteral construction looks good and preserves behavior.

Also applies to: 19-21, 37-39, 45-46, 49-51


30-33: Verify whether TemplateLiteralTypeProcessor now requires context.
If the processor was updated to be context-aware, pass it through.

If needed, apply:

-      const processedType = TemplateLiteralTypeProcessor.processType(compilerNode.type)
+      const processedType = TemplateLiteralTypeProcessor.processType(compilerNode.type, _context)
src/index.ts (1)

48-57: Good wiring: printer now receives NodeGraph

Passing dependencyTraversal.getNodeGraph() into TypeBoxPrinter is correct and aligns with chunk parsing needs.

src/handlers/typebox/object/object-type-handler.ts (1)

2-3: Context threading looks correct

Type-only import and passing context into processProperties are consistent with the new API.

Also applies to: 10-13

src/handlers/typebox/collection/array-type-handler.ts (1)

2-4: LGTM on context-aware array handling

Uses getTypeBoxType with context and routes via GenericTypeUtils. Matches the new conventions.

Also applies to: 12-16

src/parsers/parse-enums.ts (1)

33-38: Switch to GenericTypeUtils.addStaticTypeAlias is correct

Printer-aware alias generation aligns with the refactor and keeps Static formation centralized.

src/handlers/typebox/type/type-operator-base-handler.ts (1)

20-25: Context-aware operator handling LGTM

Propagating context into getTypeBoxType and using GenericTypeUtils.makeTypeCall keeps this in line with the new API.

src/handlers/typebox/collection/intersection-type-handler.ts (1)

10-11: Context forwarded correctly to collection processor

Matches the collection-base signature; nothing else to change here.

src/handlers/typebox/object/interface-type-handler.ts (3)

2-3: LGTM: switched to GenericTypeUtils and context import.


26-27: LGTM: Composite constructed via GenericTypeUtils.


11-16: Approve: processProperties correctly accepts and propagates the context parameter. ObjectLikeBaseHandler.processProperties signature includes an optional context param and both interface-type-handler.ts and object-type-handler.ts invoke it with context as intended.

src/handlers/typebox/collection/collection-base-handler.ts (1)

8-17: LGTM: context-aware collection processing and GenericTypeUtils usage.

src/handlers/typebox/type-reference-handler.ts (1)

13-36: LGTM: context threading and arg conversion via getTypeBoxType.

src/handlers/typebox/reference/readonly-type-handler.ts (1)

13-18: Context-aware refactor looks correct.

Signature + context threading and the GenericTypeUtils call are consistent with the new handler contract.

src/handlers/typebox/reference/partial-type-handler.ts (1)

13-20: Good migration to context + shared TypeBox call utility.

Inner type resolution via getTypeBoxType(innerType, context) and wrapping with Type.Partial is correct.

src/handlers/typebox/reference/required-type-handler.ts (1)

11-16: LGTM on Required handler migration.

Contract matches the refactor and uses GenericTypeUtils consistently.

src/handlers/typebox/typeof-type-handler.ts (1)

2-3: Migration to GenericTypeUtils is consistent.

Import replacement is fine and matches repo-wide changes.

src/parsers/parse-type-aliases.ts (1)

36-41: Static alias emission path looks good.

Using GenericTypeUtils.addStaticTypeAlias keeps this consistent with other parsers.

src/printer/typebox-printer.ts (1)

11-21: Wiring NodeGraph through printer/parsers looks correct.

The additions to PrinterOptions, field initialization, and parserOptions propagation are coherent and consistent with the new ChunkParser needs.

Also applies to: 31-39, 45-46

src/parsers/parse-interfaces.ts (1)

25-39: LGTM on context-aware getTypeBoxType and GenericTypeUtils usage.

Passing { nodeGraph: this.nodeGraph } and centralizing via GenericTypeUtils aligns with the broader migration. Please ensure generated files import TSchema and Static where needed.

Also applies to: 45-73

src/handlers/typebox/reference/record-type-handler.ts (1)

37-41: No changes needed for chunk identifier fallback
The fallback to chunkQualifiedName is never executed—every chunkReference has a corresponding node with a valid originalName—so chunkIdentifier is always safe.

Likely an incorrect or invalid review comment.

src/handlers/typebox/object/object-like-base-handler.ts (2)

40-41: LGTM: switched to GenericTypeUtils.makeTypeCall('Object', ...).

Consistent with the utilities migration.


9-13: Require context parameter in processProperties
No call sites omit the second argument, so making context required won’t break existing code.

--- a/src/handlers/typebox/object/object-like-base-handler.ts
+++ b/src/handlers/typebox/object/object-like-base-handler.ts
@@ -9,7 +9,7 @@ export abstract class ObjectLikeBaseHandler extends BaseTypeHandler {
   protected processProperties(
     properties: PropertySignature[],
-    context?: TypeBoxContext,
+    context: TypeBoxContext,
   ): ts.PropertyAssignment[] {
     const propertyAssignments: ts.PropertyAssignment[] = []
src/handlers/typebox/reference/pick-type-handler.ts (1)

17-18: LGTM: handler now threads TypeBoxContext and uses GenericTypeUtils.

Aligns with the broader context-aware migration.

src/parsers/parse-function-declarations.ts (2)

23-26: Context correctly threaded into getTypeBoxType (params and return).

Migration to GenericTypeUtils.makeTypeCall('Any') for fallbacks is consistent and safe.

Also applies to: 44-47


75-80: Static alias via GenericTypeUtils.addStaticTypeAlias — LGTM.

Keeps alias generation centralized with the new utilities.

src/handlers/typebox/keyof-typeof-handler.ts (2)

16-16: Reasonable “Any” fallbacks.

Defensive defaults on invalid nodes are fine.

Also applies to: 19-19, 22-22


83-89: Utility migration (Literal/Union) is correct.

Construction via GenericTypeUtils.makeTypeCall is consistent with the new API.

src/parsers/parse-chunks.ts (2)

35-48: Ensure declaration order to avoid TDZ at module init.

Main union references chunk consts; if emitted before chunk consts, runtime will throw (TDZ). Verify the printer/traversal guarantees chunks are emitted first, or insert chunk consts before the main union explicitly.

I can add an insertion index or a two-pass emit (chunks → main) if ordering isn’t guaranteed.

Also applies to: 89-96


44-47: Non-exported chunk vars — LGTM.

Keeps internal chunks out of the public surface while allowing local reference.

Comment on lines +1 to 7
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { Node, ts } from 'ts-morph'

export abstract class BaseTypeHandler {
abstract canHandle(node: Node): boolean
abstract handle(node: Node): ts.Expression
abstract handle(node: Node, context: TypeBoxContext): ts.Expression
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Abstract signature update looks correct; this is a breaking change—verify all subclasses.
Good use of a type-only import. Please ensure all handlers now implement (node, context).

Run to find handlers missing a context parameter:


🏁 Script executed:

#!/bin/bash
# Handlers extending BaseTypeHandler
rg -nP --type ts -C2 'class\s+\w+\s+extends\s+BaseTypeHandler\b' src

# handle() overrides that don't include a `context:` parameter
rg -nP --type ts 'handle\s*\((?![^)]*context\s*:)' src/handlers/typebox

Length of output: 7830


Ensure all handlers implement the updated handle(node, context) signature
The following subclasses of BaseTypeHandler still use the old handle(...) signature and must be updated to handle(node: X, context: TypeBoxContext): ts.Expression:

  • src/handlers/typebox/typeof-type-handler.ts (line 10)
  • src/handlers/typebox/type-query-handler.ts (line 10)
  • src/handlers/typebox/template-literal-type-handler.ts (line 11)
  • src/handlers/typebox/literal-type-handler.ts (line 10)
  • src/handlers/typebox/keyof-typeof-handler.ts (line 14)
  • src/handlers/typebox/simple-type-handler.ts (line 36)
  • src/handlers/typebox/date-type-handler.ts (line 12)
  • src/handlers/typebox/type-operator-handler.ts (line 11)
🤖 Prompt for AI Agents
In src/handlers/typebox/base-type-handler.ts lines 1-7 and the listed
subclasses, the abstract handle signature was changed to handle(node: Node,
context: TypeBoxContext): ts.Expression but several subclasses still use the old
signature; update each subclass file
(src/handlers/typebox/typeof-type-handler.ts line 10, type-query-handler.ts line
10, template-literal-type-handler.ts line 11, literal-type-handler.ts line 10,
keyof-typeof-handler.ts line 14, simple-type-handler.ts line 36,
date-type-handler.ts line 12, type-operator-handler.ts line 11) so their class
method signature is handle(node: <specific Node type>, context: TypeBoxContext):
ts.Expression, import TypeBoxContext where needed, adjust any usages inside the
methods to use the new context parameter, and ensure the return type is
ts.Expression; also update any callers to pass the context if they currently
omit it.

Comment on lines +8 to 17
protected processTypeCollection(
nodes: Node[],
typeBoxFunction: string,
context: TypeBoxContext,
): ts.Expression {
const typeBoxTypes = nodes.map((node) => getTypeBoxType(node, context))
const arrayLiteral = ts.factory.createArrayLiteralExpression(typeBoxTypes)

return makeTypeCall(typeBoxFunction, [arrayLiteral])
return GenericTypeUtils.makeTypeCall(typeBoxFunction, [arrayLiteral])
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Guard against empty collections (defensive).
If nodes is empty, Type.Union([])/Type.Intersect([]) may not be meaningful. Consider returning Type.Never/Type.Object({}) per your conventions, or assert upstream.


🏁 Script executed:

#!/bin/bash
# List all call sites to ensure `nodes.length > 0` is enforced upstream
rg -nP --type=ts -C2 '\bprocessTypeCollection\s*\(' src/handlers

Length of output: 1954


Guard empty collections in processTypeCollection. Add at the top of processTypeCollection (in collection-base-handler.ts) a check for nodes.length === 0 that returns your agreed fallback instead of calling, for example, Type.Union([]) or Type.Intersect([]). For instance, return Type.Never() for unions/intersections or Type.Object({}) for empty tuples per project conventions.

🤖 Prompt for AI Agents
In src/handlers/typebox/collection/collection-base-handler.ts around lines 8 to
17, add a guard at the top of processTypeCollection to return the project
fallback when nodes.length === 0 instead of calling the TypeBox collection
functions with an empty array; specifically, detect empty nodes and return
Type.Never() for union/intersect cases or Type.Object({}) for empty tuple cases
(use the existing GenericTypeUtils.makeTypeCall or appropriate helper to
construct these fallback TypeBox calls), otherwise continue to build the
arrayLiteral and call the original typeBoxFunction.

Comment on lines +10 to +12
handle(node: TupleTypeNode, context: TypeBoxContext): ts.Expression {
const elements = node.getElements()
return this.processTypeCollection(elements, 'Tuple', context)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Context-plumbed Tuple handler looks correct; verify all call sites pass context

The signature and forwarding into processTypeCollection are consistent with the new context-aware API.

Run to catch any remaining handlers/call-sites missing the context param:


🏁 Script executed:

#!/bin/bash
# 1) Handlers without a `context: TypeBoxContext` param
rg -nP --type=ts '\bhandle\s*\(\s*[^)]*\)' src/handlers/typebox | rg -nPv 'context\s*:\s*TypeBoxContext' || true

# 2) processTypeCollection calls lacking the context arg
rg -nP --type=ts 'processTypeCollection\([^,]+,\s*["'\''](Tuple|Array|Intersect|Union)["'\'']\s*\)' src/handlers/typebox || true

Length of output: 995


Add missing context parameter to handlers
The following handle() methods lack the required context: TypeBoxContext parameter and need to forward it to their processing calls:

  • src/handlers/typebox/typeof-type-handler.ts:10
  • src/handlers/typebox/type-query-handler.ts:10
  • src/handlers/typebox/template-literal-type-handler.ts:11
  • src/handlers/typebox/simple-type-handler.ts:36
  • src/handlers/typebox/literal-type-handler.ts:10
  • src/handlers/typebox/keyof-typeof-handler.ts:14
  • src/handlers/typebox/date-type-handler.ts:12
  • src/handlers/typebox/type-operator-handler.ts:11

Update each signature to handle(..., context: TypeBoxContext) and pass context into the underlying process* calls.

🤖 Prompt for AI Agents
In src/handlers/typebox/collection/tuple-type-handler.ts around lines 10 to 12,
the handle method is missing the required context parameter and does not forward
it to the processing call; change the signature to include context:
TypeBoxContext and pass that context into this.processTypeCollection(elements,
'Tuple', context) so the handler matches the other TypeBox handlers and forwards
context to downstream processing.

Comment on lines 12 to 14
handle(): ts.Expression {
return makeTypeCall('Date')
return GenericTypeUtils.makeTypeCall('Date')
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

handle signature must accept (node, context).
It currently takes no params and will not satisfy BaseTypeHandler.

Apply:

+import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
@@
-  handle(): ts.Expression {
+  handle(_node: TypeReferenceNode, _context: TypeBoxContext): ts.Expression {
     return GenericTypeUtils.makeTypeCall('Date')
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
handle(): ts.Expression {
return makeTypeCall('Date')
return GenericTypeUtils.makeTypeCall('Date')
}
// at the top of src/handlers/typebox/date-type-handler.ts
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
// …within your handler class…
handle(_node: TypeReferenceNode, _context: TypeBoxContext): ts.Expression {
return GenericTypeUtils.makeTypeCall('Date')
}
🤖 Prompt for AI Agents
In src/handlers/typebox/date-type-handler.ts around lines 12 to 14, the handle
method signature currently has no parameters but must match BaseTypeHandler by
accepting (node, context); change the method signature to handle(node, context)
and update any references inside to use the provided parameters (or keep them
unused but present), update imports/types if necessary so the types for node and
context are correct, and ensure the file compiles and the class implements the
BaseTypeHandler interface with the new signature.


handle(node: FunctionTypeNode): ts.Expression {
handle(node: FunctionTypeNode, context: TypeBoxContext): ts.Expression {
const parameters = node.getParameters()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Exclude this parameter from runtime schema generation

this parameters are type-only and must not appear in Type.Function’s parameter list.

Apply this diff:

-    const parameters = node.getParameters()
+    const parameters = node.getParameters().filter((p) => !p.isThisParameter())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const parameters = node.getParameters()
const parameters = node.getParameters().filter((p) => !p.isThisParameter())
🤖 Prompt for AI Agents
In src/handlers/typebox/function-type-handler.ts around line 13, the code
collects parameters with node.getParameters() but must exclude the type-only
`this` parameter from the runtime schema; filter the parameters list to remove
any parameter that is the `this` parameter (e.g., using
parameter.isThisParameter() or checking the parameter name === 'this') before
generating the Type.Function parameter list so `this` does not appear in the
emitted TypeBox schema.

Comment on lines 10 to 14
handle(): ts.Expression {
// TypeQuery represents 'typeof' expressions in TypeScript
// For TypeBox, we'll return a String type as typeof returns string literals
return makeTypeCall('String')
return GenericTypeUtils.makeTypeCall('String')
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Bug: handle() signature mismatches the handler contract and logic is incorrect for TypeQuery.

  • getTypeBoxType calls handlers via handle(node, context); this handler exposes handle() with no params → compile/runtime break.
  • Returning Type.String for a TypeQuery is semantically wrong (TypeScript’s typeof type query does not yield “string literals”).

Proposed minimal fix (align signature and avoid wrong typing until proper support is implemented):

-import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
-import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
+import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
+import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
+import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'

 export class TypeofTypeHandler extends BaseTypeHandler {
   canHandle(node: Node): boolean {
     return Node.isTypeQuery(node)
   }

-  handle(): ts.Expression {
-    // TypeQuery represents 'typeof' expressions in TypeScript
-    // For TypeBox, we'll return a String type as typeof returns string literals
-    return GenericTypeUtils.makeTypeCall('String')
+  handle(_node: Node, _context: TypeBoxContext): ts.Expression {
+    // TODO: Implement proper TypeQuery handling by resolving the referenced symbol’s type.
+    // Fallback to Any to avoid misrepresenting the type.
+    return GenericTypeUtils.makeTypeCall('Any')
   }
 }

Run to confirm no other handlers still use the old handle() shape:


🏁 Script executed:

#!/bin/bash
rg -nP -C1 'class\s+\w+TypeHandler\b' src/handlers | sed -n 'p'
rg -nP 'handle\s*\(' src/handlers | sed -n 'p'

Length of output: 9016


🏁 Script executed:

#!/bin/bash
rg -nP -C3 'class\s+\w+TypeHandler\b' src/handlers/typebox/type-query-handler.ts
rg -nP -C3 'handle' src/handlers/typebox/type-query-handler.ts
rg -nP -C3 'class\s+\w+TypeHandler\b' src/handlers/typebox/date-type-handler.ts
rg -nP -C3 'handle' src/handlers/typebox/date-type-handler.ts
rg -nP -C3 'class\s+\w+TypeHandler\b' src/handlers/typebox/typeof-type-handler.ts
rg -nP -C3 'handle' src/handlers/typebox/typeof-type-handler.ts

Length of output: 2593


Refactor TypeBox handlers: align handle signatures and correct fallback types

  • In src/handlers/typebox/typeof-type-handler.ts, change
    handle(): ts.Expressionhandle(_node: Node, _context: TypeBoxContext): ts.Expression,
    import type { TypeBoxContext } and update the return to GenericTypeUtils.makeTypeCall('Any').
  • In src/handlers/typebox/date-type-handler.ts, change
    handle(): ts.Expressionhandle(_node: TypeReferenceNode, _context: TypeBoxContext): ts.Expression and import TypeBoxContext.
  • In src/handlers/typebox/type-query-handler.ts, change
    handle(node: TypeQueryNode): ts.Expressionhandle(_node: TypeQueryNode, _context: TypeBoxContext): ts.Expression, import TypeBoxContext, and return GenericTypeUtils.makeTypeCall('Any') as a fallback until proper resolution is implemented.
🤖 Prompt for AI Agents
In src/handlers/typebox/typeof-type-handler.ts around lines 10 to 14, update the
handler signature to match the refactor by changing handle(): ts.Expression to
handle(_node: Node, _context: TypeBoxContext): ts.Expression, add an import for
type { TypeBoxContext } from the appropriate module, and change the fallback
return from GenericTypeUtils.makeTypeCall('String') to
GenericTypeUtils.makeTypeCall('Any').

Comment on lines +51 to +59
if (!traversedNode.chunkReferences || traversedNode.chunkReferences.length === 0) {
// Fallback to empty union if no chunk references
const unionCall = GenericTypeUtils.makeTypeCall('Union', [])
const typeboxType = this.printer.printNode(
ts.EmitHint.Expression,
unionCall,
this.newSourceFile.compilerNode,
)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: calling Type.Union() with no arguments.

Type.Union expects an array; an empty call is invalid. For “no chunk refs,” emit Type.Never() (or Type.Union([]) if you explicitly want an empty union).

-      const unionCall = GenericTypeUtils.makeTypeCall('Union', [])
+      const unionCall = GenericTypeUtils.makeTypeCall('Never')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!traversedNode.chunkReferences || traversedNode.chunkReferences.length === 0) {
// Fallback to empty union if no chunk references
const unionCall = GenericTypeUtils.makeTypeCall('Union', [])
const typeboxType = this.printer.printNode(
ts.EmitHint.Expression,
unionCall,
this.newSourceFile.compilerNode,
)
if (!traversedNode.chunkReferences || traversedNode.chunkReferences.length === 0) {
// Fallback to empty union if no chunk references
const unionCall = GenericTypeUtils.makeTypeCall('Never')
const typeboxType = this.printer.printNode(
ts.EmitHint.Expression,
unionCall,
this.newSourceFile.compilerNode,
)
🤖 Prompt for AI Agents
In src/parsers/parse-chunks.ts around lines 51 to 59, the code calls
GenericTypeUtils.makeTypeCall('Union', []) when traversedNode.chunkReferences is
empty which results in an invalid zero-argument Union call; change this to emit
a Type.Never() instead (i.e., call GenericTypeUtils.makeTypeCall('Never', [])
and print that), so the no-chunk case produces a valid Type.Never() node
(alternatively, if you deliberately want an empty union, ensure Union is called
with an explicit empty-array AST representation, but prefer Never here).

Comment on lines +49 to 66
const { node, aliasName, type } = traversedNode

// Handle chunk nodes first
if (type === 'chunk') {
this.chunkParser.parseChunk(traversedNode, aliasName)
return
}

// Handle type aliases with chunk references using chunk parser
if (
type === 'typeAlias' &&
traversedNode.chunkReferences &&
traversedNode.chunkReferences.length > 0
) {
this.chunkParser.parseChunk(traversedNode, aliasName)
return
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Ensure chunk consts are emitted before main unions to avoid TDZ at runtime.

Type.Union([Chunk1, ...]) will reference chunk variables; if those consts are printed after the main alias, you risk a temporal-dead-zone ReferenceError. Please either:

  • guarantee traversal order (e.g., add graph dependencies from main alias → chunk nodes and topologically sort), or
  • have parseChunk for the main alias eagerly emit referenced chunks before emitting the union.

See my suggested dependency wiring in local-type-collector.ts.


🏁 Script executed:

#!/bin/bash
set -e

# Locate the typebox-printer.ts file
FILE=$(fd --full-path typebox-printer.ts | head -n1)
echo "Found file: $FILE"

# Display lines 1–200 for context
sed -n '1,200p' "$FILE"

Length of output: 3231


🏁 Script executed:

#!/bin/bash
set -e
# Find the parse-chunks implementation file
FILE=$(fd --full-path parse-chunks.ts | head -n1 || echo "")
echo "Parse-chunks file: $FILE"
# If found, show class and method definitions
if [ -f "$FILE" ]; then
  grep -R "class ChunkParser" -n "$FILE"
  grep -R "parseChunk" -n "$FILE"
  sed -n '1,200p' "$FILE"
fi

Length of output: 4215


Ensure chunk constants are declared before unions that reference them
In the typeAlias-with-chunks branch you call parseChunk(traversedNode) which emits a Union([…chunkIdentifiers]) before ever emitting the individual chunk const declarations—this will trigger a TDZ ReferenceError at runtime. Either:

  • In ChunkParser.parseChunk, iterate over traversedNode.chunkReferences and recursively call parseChunk on each before emitting the union, or
  • Adjust your traversal to topologically sort nodes so all chunk-type nodes are printed before any main union aliases.
🤖 Prompt for AI Agents
In src/printer/typebox-printer.ts around lines 49 to 66, the typeAlias branch
calls this.chunkParser.parseChunk(traversedNode, aliasName) which currently
emits a Union([...chunkIdentifiers]) before the individual chunk const
declarations, causing TDZ ReferenceErrors; update the implementation so chunk
declarations are produced before any unions that reference them — either modify
ChunkParser.parseChunk to first iterate over traversedNode.chunkReferences and
recursively call parseChunk for each referenced chunk (guarding against
cycles/duplication) before emitting the union, or change the traversal here to
topologically sort nodes so all chunk-type nodes are printed first; ensure
deduplication/visited checks to avoid infinite recursion.

Comment on lines +50 to +55
const project = node.getProject()
const tempSourceFile = project.createSourceFile(
`__temp_chunk_${i}.ts`,
`type TempChunk = ${chunkTypeNodes.map((node) => node.getText()).join(' | ')}`,
)
const tempTypeAlias = tempSourceFile.getTypeAliases()[0]!
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: temporary source file name collisions across chunked types.

project.createSourceFile("__temp_chunk_${i}.ts") will throw on the second union processed (same filename) unless overwrite: true is set. Also, filenames should be unique per (file, type, chunk).

Apply:

-    const tempSourceFile = project.createSourceFile(
-      `__temp_chunk_${i}.ts`,
-      `type TempChunk = ${chunkTypeNodes.map((node) => node.getText()).join(' | ')}`,
-    )
+    const fileStem = sourceFile.getBaseNameWithoutExtension()
+    const safeParent = parentTypeName.replace(/[^A-Za-z0-9_]/g, '_')
+    const tempFilePath = `__typebox_chunks/${fileStem}__${safeParent}__chunk_${i}.ts`
+    const tempSourceFile = project.createSourceFile(
+      tempFilePath,
+      `type TempChunk = ${chunkTypeNodes.map((n) => n.getText()).join(' | ')}`,
+      { overwrite: true },
+    )

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/traverse/local-type-collector.ts around lines 50 to 55, the temporary
source file name "__temp_chunk_${i}.ts" can collide across chunks and subsequent
createSourceFile calls will throw; make the filename unique per original
file/type/chunk and allow overwriting. Construct a deterministic unique name
(e.g. include the original source file path or basename, the parent type name or
id, and the chunk index) and call project.createSourceFile(uniqueFilename,
content, { overwrite: true }) so repeated runs or different chunks won’t error.

Comment on lines +126 to +145
const chunkReferences = createChunkNodes(
typeNode,
typeName,
nodeGraph,
maincodeNodeIds,
requiredNodeIds,
sourceFile,
)

// Add the main type as a regular type alias that will be handled by chunk parser
nodeGraph.addTypeNode(qualifiedName, {
node: typeAlias,
type: 'typeAlias',
originalName: typeName,
qualifiedName,
isImported: false,
isMainCode: true,
chunkReferences: chunkReferences,
})
} else {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add graph dependencies from main type → chunk nodes to enforce print order.

This guarantees chunks are emitted before the main union and avoids TDZ at runtime, regardless of set/map iteration order.

       const chunkReferences = createChunkNodes(
         typeNode,
         typeName,
         nodeGraph,
         maincodeNodeIds,
         requiredNodeIds,
         sourceFile,
       )
 
       // Add the main type as a regular type alias that will be handled by chunk parser
       nodeGraph.addTypeNode(qualifiedName, {
         node: typeAlias,
         type: 'typeAlias',
         originalName: typeName,
         qualifiedName,
         isImported: false,
         isMainCode: true,
         chunkReferences: chunkReferences,
       })
+      // Ensure chunks are processed before the main union
+      for (const chunkQn of chunkReferences) {
+        nodeGraph.addDependency(qualifiedName, chunkQn)
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const chunkReferences = createChunkNodes(
typeNode,
typeName,
nodeGraph,
maincodeNodeIds,
requiredNodeIds,
sourceFile,
)
// Add the main type as a regular type alias that will be handled by chunk parser
nodeGraph.addTypeNode(qualifiedName, {
node: typeAlias,
type: 'typeAlias',
originalName: typeName,
qualifiedName,
isImported: false,
isMainCode: true,
chunkReferences: chunkReferences,
})
} else {
const chunkReferences = createChunkNodes(
typeNode,
typeName,
nodeGraph,
maincodeNodeIds,
requiredNodeIds,
sourceFile,
)
// Add the main type as a regular type alias that will be handled by chunk parser
nodeGraph.addTypeNode(qualifiedName, {
node: typeAlias,
type: 'typeAlias',
originalName: typeName,
qualifiedName,
isImported: false,
isMainCode: true,
chunkReferences: chunkReferences,
})
// Ensure chunks are processed before the main union
for (const chunkQn of chunkReferences) {
nodeGraph.addDependency(qualifiedName, chunkQn)
}
} else {
🤖 Prompt for AI Agents
In src/traverse/local-type-collector.ts around lines 126 to 145, after adding
the main type node to nodeGraph, add explicit graph dependencies from the main
type (qualifiedName) to each chunk node returned in chunkReferences so chunks
are emitted first; iterate chunkReferences and for each chunk id call the
graph-edge API (e.g. nodeGraph.addEdge(qualifiedName, chunkId) or
nodeGraph.addDependency(qualifiedName, chunkId) depending on the existing API)
to create edges main→chunk, ensuring print order prevents TDZ.

@DaxServer DaxServer merged commit 89c7f23 into main Sep 4, 2025
7 checks passed
@DaxServer DaxServer deleted the deep-instantiation branch September 4, 2025 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants