Skip to content

Conversation

@DaxServer
Copy link
Owner

  • Upgrade @eslint/js, @typescript-eslint/eslint-plugin, @typescript-eslint/parser, and eslint to their latest versions
  • Refactor the createChunkNodes function in local-type-collector.ts to use the new createChunkNodes and shouldChunkUnion functions from the chunk-large-types module
  • This change improves the handling of large union types by breaking them into smaller chunks, making the generated code more manageable and easier to work with

- Upgrade `@eslint/js`, `@typescript-eslint/eslint-plugin`, `@typescript-eslint/parser`, and `eslint` to their latest versions
- Refactor the `createChunkNodes` function in `local-type-collector.ts` to use the new `createChunkNodes` and `shouldChunkUnion` functions from the `chunk-large-types` module
- This change improves the handling of large union types by breaking them into smaller chunks, making the generated code more manageable and easier to work with
@coderabbitai
Copy link

coderabbitai bot commented Sep 6, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Improved handling of very large union types with automatic chunking for better performance and stability.
    • Ensures related chunks are included when resolving dependencies.
  • Tests

    • Added coverage for TypeBox code generation of as const expressions (arrays, literals, objects).
  • Refactor

    • Centralized large-type chunking logic and integrated it into import and dependency processing.
  • Chores

    • Updated linting/tooling dependencies and added supporting development utilities.

Walkthrough

Adds a new chunking module to split very large union types into smaller synthetic chunk types, integrates chunk creation into import and local-type collection, ensures chunks are marked required during dependency extraction, updates ImportCollector initialization to accept node ID sets, updates devDependencies, and adds TypeBox "as const" tests.

Changes

Cohort / File(s) Summary
Dev tooling updates
package.json
Bumps ESLint/TypeScript-related devDependencies and adds devDependencies: globals, graphology-types, jiti.
Chunking module (new)
src/traverse/chunk-large-types.ts
New helpers to detect large unions, create synthetic chunk TypeNodes/files, register chunk TraversedNodes and resolver mappings, and mark chunk dependencies as required. Exports: shouldChunkUnion, createChunkNodes, markChunksAsRequired.
Import collection integration
src/traverse/import-collector.ts
Constructor now accepts maincodeNodeIds and requiredNodeIds. When a type alias is a large union, calls chunk creation, attaches chunkReferences to the alias node, and adds dependencies from each chunk to the alias.
Local type collection refactor
src/traverse/local-type-collector.ts
Removes local chunking logic and delegates large-union detection and chunk creation to chunk-large-types helpers; public API unchanged.
Dependency traversal wiring
src/traverse/dependency-traversal.ts
Initializes ImportCollector with fileGraph, nodeGraph, maincodeNodeIds, and requiredNodeIds (constructor usage updated).
Dependency extraction linkage
src/traverse/dependency-extractor.ts
After discovering and adding a referenced type to required set, calls markChunksAsRequired to add any associated chunk nodes to required set.
Tests: TypeBox as const
tests/handlers/typebox/as-const.test.ts
Adds tests covering generation for as const arrays, literals, and objects; asserts TypeBox outputs and Static<> typings.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant IC as ImportCollector
  participant TS as ts-morph
  participant CLT as chunk-large-types
  participant NG as NodeGraph
  participant RS as resolverStore
  participant Sets as maincode/required Sets

  IC->>TS: Inspect type alias type node
  IC->>CLT: shouldChunkUnion(typeNode)?
  alt large union
    IC->>CLT: createChunkNodes(node, parentName, NG, Sets.maincode, Sets.required, sourceFile, addToRequired=false)
    CLT->>TS: Build synthetic chunk SourceFile & TypeNodes
    CLT->>RS: Register chunk qualified names & type mappings
    CLT->>NG: Add chunk TraversedNodes
    CLT-->>IC: return chunkReferences[]
    IC->>NG: Add dependencies (chunk -> alias) and store alias with chunkReferences
  else small/normal
    IC->>NG: Add alias node (no chunking)
  end
Loading
sequenceDiagram
  autonumber
  participant DE as DependencyExtractor
  participant RS as resolverStore
  participant NG as NodeGraph
  participant CLT as chunk-large-types
  participant Sets as requiredNodeIds

  loop traverse dependencies
    DE->>RS: hasQualifiedName(referencedType)?
    DE->>NG: hasNode(referencedType)?
    alt newly discovered type
      DE->>Sets: add(referencedType)
      DE->>CLT: markChunksAsRequired(referencedType, NG, Sets)
      CLT->>NG: read parent node.chunkReferences
      CLT->>Sets: add(each chunkReference)
      DE->>DE: queue nodesToProcess (including chunks)
    else already known
      DE-->>DE: continue
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54e7304 and 9275d52.

📒 Files selected for processing (1)
  • src/traverse/chunk-large-types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/traverse/chunk-large-types.ts
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chunks

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 3

Caution

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

⚠️ Outside diff range comments (1)
package.json (1)

41-58: Action: fix ESLint/TS-ESLint compatibility and devDeps

  • Node: ESLint 9.x and typescript-eslint 8.x require Node >= 18.18 — ensure CI/runtime uses Node >=18.18. (npmjs.com, tseslint.com)
  • TypeScript: package.json has no typescript devDependency (jq showed "typescript not listed"); add a TypeScript devDependency that is compatible with @typescript-eslint (typescript-eslint supported TS range per their docs). Also note the registry latest TypeScript is 5.9.2 — pin a supported TS version or ensure the workspace provides it. (tseslint.com, npmjs.com)
  • jiti: ESLint requires jiti to load eslint.config.ts (error seen when jiti missing). jiti is in devDependencies — ensure CI installs devDependencies before running ESLint and that jiti is available. (eslint.org)
  • Keep globals: eslint.config.ts imports globals (eslint.config.ts:4 and 13).
  • graphology-types: no references found in .ts/.js — remove if unused.

Fixes: add a compatible "typescript" devDependency to package.json, ensure CI uses Node >=18.18 and installs devDependencies before linting, and drop any truly-unused devDeps (graphology-types).

🧹 Nitpick comments (8)
tests/handlers/typebox/as-const.test.ts (2)

12-71: Reduce duplication via table-driven tests for tuple cases.

You can collapse the five array cases into a parametric loop to keep the suite terse.

Example:

-  describe('array literals with as const', () => {
-    test('string literals array', () => { /* ... */ })
-    test('numeric literals array', () => { /* ... */ })
-    test('boolean literals array', () => { /* ... */ })
-    test('mixed literals array', () => { /* ... */ })
-    test('empty array', () => { /* ... */ })
-  })
+  describe('array literals with as const', () => {
+    const cases: Array<[name: string, expr: string, tuple: string]> = [
+      ['string literals array', '["hello", "world"]', 'Type.Tuple([Type.Literal("hello"), Type.Literal("world")])'],
+      ['numeric literals array', '[1, 2, 3]', 'Type.Tuple([Type.Literal(1), Type.Literal(2), Type.Literal(3)])'],
+      ['boolean literals array', '[true, false]', 'Type.Tuple([Type.Literal(true), Type.Literal(false)])'],
+      ['mixed literals array', '["hello", 42, true]', 'Type.Tuple([Type.Literal("hello"), Type.Literal(42), Type.Literal(true)])'],
+      ['empty array', '[]', 'Type.Tuple([])'],
+    ]
+    for (const [name, value, tuple] of cases) {
+      test(name, () => {
+        const sf = createSourceFile(project, `type A = ${value} as const`)
+        expect(generateFormattedCode(sf)).toBe(
+          formatWithPrettier(`
+            export const A = ${tuple};
+            export type A = Static<typeof A>;
+          `),
+        )
+      })
+    }
+  })

111-124: Add a regression for “indexed access of const arrays” to guard chunking path.

Covers typeof arr[number] as const, which routes through the IndexedAccessType codepath used by the chunker.

Example test to append:

test('indexed access over const array', () => {
  const sf = createSourceFile(project, `
    const arr = ["a","b","c"] as const
    type A = (typeof arr)[number] as const
  `)
  expect(generateFormattedCode(sf)).toBe(
    formatWithPrettier(`
      export const A = Type.Union([Type.Literal("a"), Type.Literal("b"), Type.Literal("c")]);
      export type A = Static<typeof A>;
    `),
  )
})
src/traverse/chunk-large-types.ts (1)

6-6: Make CHUNK_SIZE configurable/exported.

Exporting CHUNK_SIZE helps targeted tests and tuning without touching internals.

-const CHUNK_SIZE = 20
+export const CHUNK_SIZE = 20
src/traverse/dependency-extractor.ts (2)

54-56: Also enqueue chunk nodes for dependency extraction

Marking chunk refs as required is good, but without enqueuing them, any nested refs inside chunk nodes won't be discovered. Add them to nodesToProcess too.

           requiredNodeIds.add(referencedType)
           nodesToProcess.add(referencedType)
           // Mark chunks as required if this type has chunks
           markChunksAsRequired(referencedType, nodeGraph, requiredNodeIds)
+          // Also enqueue chunk nodes so their internal references (if any) are extracted
+          const parentNode = nodeGraph.getNode(referencedType)
+          if (parentNode?.chunkReferences) {
+            for (const chunkRef of parentNode.chunkReferences) {
+              if (!requiredNodeIds.has(chunkRef)) {
+                nodesToProcess.add(chunkRef)
+              }
+            }
+          }

50-50: Fix misleading comment (processed → required)

This checks membership in requiredNodeIds, not whether the node was processed.

-        // Only add to required if not already processed
+        // Only add to required if not already required
src/traverse/import-collector.ts (1)

55-96: Chunking path looks solid; confirm intent for imported chunks to be treated as main code

  • Behavior makes sense: create chunks, attach refs to parent, add edges chunk→parent, and defer marking as required.
  • Confirm that chunk nodes created for imported types are intended to be emitted as main code (isMainCode: true in createChunkNodes). If that’s the design, all good; otherwise we may need a flag to control this.

Optional: consider guarding against project memory growth from many temp chunk files created in createChunkNodes (synthetic files accumulate). A reusable scratch file or cleanup strategy would help.

Also applies to: 81-85

src/traverse/local-type-collector.ts (2)

20-41: Empty-locals fallback only handles named imports; confirm default/namespace cases.

If a file exports types via a default or namespace import, they’ll be skipped here. If that’s intentional (handled elsewhere), all good; otherwise, consider extending coverage.

What scenarios do we expect here?

  • default export type alias (e.g., export default type Foo = ...)
  • namespace import (import * as X from ...) with types inside

If any should be treated as “required,” I can draft a safe extension that won’t break resolverStore.generateQualifiedName. Do you want that?


84-89: Use the same sourceFile accessor for consistency.

Elsewhere you pass decl.getSourceFile(). Here you pass the outer sourceFile. Same value, but consistency helps static analysis and future refactors.

Apply:

-    resolverStore.addTypeMapping({
-      originalName: typeName,
-      sourceFile,
-    })
+    resolverStore.addTypeMapping({
+      originalName: typeName,
+      sourceFile: typeAlias.getSourceFile(),
+    })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89c7f23 and 54e7304.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • package.json (2 hunks)
  • src/traverse/chunk-large-types.ts (1 hunks)
  • src/traverse/dependency-extractor.ts (2 hunks)
  • src/traverse/dependency-traversal.ts (1 hunks)
  • src/traverse/import-collector.ts (3 hunks)
  • src/traverse/local-type-collector.ts (1 hunks)
  • tests/handlers/typebox/as-const.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/traverse/chunk-large-types.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)
tests/handlers/typebox/as-const.test.ts (1)
tests/utils.ts (3)
  • createSourceFile (12-14)
  • generateFormattedCode (26-37)
  • formatWithPrettier (16-24)
src/traverse/dependency-extractor.ts (1)
src/traverse/chunk-large-types.ts (1)
  • markChunksAsRequired (106-117)
src/traverse/dependency-traversal.ts (1)
src/traverse/import-collector.ts (1)
  • ImportCollector (10-183)
src/traverse/import-collector.ts (1)
src/traverse/chunk-large-types.ts (2)
  • shouldChunkUnion (8-23)
  • createChunkNodes (25-104)
🔇 Additional comments (11)
tests/handlers/typebox/as-const.test.ts (1)

5-10: Test scaffolding is solid.

Project lifecycle with beforeEach is clean and isolation-friendly.

src/traverse/chunk-large-types.ts (2)

36-45: Confirm Type.getText() stability for IndexedAccess unions.

Type.getText() may emit import("...") references or contextual names; ensure the printer produces valid code in your generator.

Add a regression that chunks a large typeof arr[number] and validates emitted chunk text doesn’t include opaque import() types. If it does, consider resolving symbols to fully qualified names or using a printer with the parent source file’s context.


97-101: Double-check resolver mapping for synthetic chunks.

You map originalName to the parent’s sourceFile, while the node lives in a scratch file. Validate this doesn’t collide with real types and that imports won’t be generated for chunks (since isImported=false, isMainCode=true).

If collisions occur, pass the qualifiedName/alias explicitly to the resolver, or add a dedicated “synthetic” namespace to resolverStore.

src/traverse/dependency-extractor.ts (1)

1-1: LGTM: importing markChunksAsRequired integrates chunking into dependency marking

No issues.

src/traverse/dependency-traversal.ts (1)

20-25: LGTM: pass chunking context into ImportCollector

Constructor injection of the sets keeps state centralized and consistent across traversal phases.

src/traverse/import-collector.ts (2)

1-4: LGTM: imports for chunking utilities

Matches usage below.


14-16: LGTM: extended constructor signature

Passing maincodeNodeIds and requiredNodeIds aligns with downstream chunk creation.

src/traverse/local-type-collector.ts (4)

91-114: Interfaces collection looks solid.

IDs are added to both sets and resolver mapping is recorded. No issues spotted.


116-136: Enums collection LGTM.

Consistent with interfaces block; no functional concerns.


138-164: Functions collection LGTM, with a sensible guard for unnamed declarations.

No issues spotted.


50-73: NodeGraph already supports chunkReferences
The TraversedNode interface in src/traverse/types.ts declares chunkReferences?: string[], and addTypeNode stores the node object intact. Downstream parsers and printers already handle this field. No action required.

Comment on lines +106 to +117
export const markChunksAsRequired = (
parentQualifiedName: string,
nodeGraph: NodeGraph,
requiredNodeIds: Set<string>,
): void => {
const parentNode = nodeGraph.getNode(parentQualifiedName)
if (parentNode?.chunkReferences) {
for (const chunkRef of parentNode.chunkReferences) {
requiredNodeIds.add(chunkRef)
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

getNode() will throw when parent hasn’t been added — add a guard.

nodeGraph.getNode throws for unknown nodes; current optional chaining won’t help.

Apply:

 export const markChunksAsRequired = (
   parentQualifiedName: string,
   nodeGraph: NodeGraph,
   requiredNodeIds: Set<string>,
 ): void => {
-  const parentNode = nodeGraph.getNode(parentQualifiedName)
-  if (parentNode?.chunkReferences) {
-    for (const chunkRef of parentNode.chunkReferences) {
-      requiredNodeIds.add(chunkRef)
-    }
-  }
+  if (!nodeGraph.hasNode(parentQualifiedName)) return
+  const parentNode = nodeGraph.getNode(parentQualifiedName)
+  const refs = parentNode.chunkReferences
+  if (!refs?.length) return
+  for (const chunkRef of refs) {
+    requiredNodeIds.add(chunkRef)
+  }
 }
📝 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
export const markChunksAsRequired = (
parentQualifiedName: string,
nodeGraph: NodeGraph,
requiredNodeIds: Set<string>,
): void => {
const parentNode = nodeGraph.getNode(parentQualifiedName)
if (parentNode?.chunkReferences) {
for (const chunkRef of parentNode.chunkReferences) {
requiredNodeIds.add(chunkRef)
}
}
}
export const markChunksAsRequired = (
parentQualifiedName: string,
nodeGraph: NodeGraph,
requiredNodeIds: Set<string>,
): void => {
if (!nodeGraph.hasNode(parentQualifiedName)) return
const parentNode = nodeGraph.getNode(parentQualifiedName)
const refs = parentNode.chunkReferences
if (!refs?.length) return
for (const chunkRef of refs) {
requiredNodeIds.add(chunkRef)
}
}
🤖 Prompt for AI Agents
In src/traverse/chunk-large-types.ts around lines 106 to 117, nodeGraph.getNode
can throw for unknown nodes so the current optional chaining won’t prevent the
exception; add a guard before calling getNode (e.g. check
nodeGraph.hasNode(parentQualifiedName) or wrap getNode in a try/catch) and only
access chunkReferences when a node is confirmed to exist, then add its
chunkReferences to requiredNodeIds as before.

Comment on lines +1 to +4
import {
createChunkNodes,
shouldChunkUnion,
} from '@daxserver/validation-schema-codegen/traverse/chunk-large-types'
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

Confirm deep import path + named exports resolve correctly.

Verify that chunk-large-types actually exports createChunkNodes and shouldChunkUnion, and that the @daxserver/validation-schema-codegen/* path alias is configured (tsconfig paths or package exports) to avoid resolution/circular-import issues.

Run:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) File exists:"
fd -a 'chunk-large-types.ts' src

echo -e "\n2) Named exports present:"
rg -nP 'export\s+(?:const|function)\s+(createChunkNodes|shouldChunkUnion)\b' src

echo -e "\n3) Path alias configured:"
rg -nP '"paths"\s*:\s*{[^}]*"@daxserver/validation-schema-codegen/\*"' -n tsconfig*.json -C2 || true

echo -e "\n4) Check for potential circular imports:"
rg -nP 'from\s+[\'"]@daxserver/validation-schema-codegen/traverse/local-type-collector[\'"]' -n src || true

Length of output: 865


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "tsconfig files:"
fd -HI tsconfig*.json

echo -e "\nPaths config in tsconfig files:"
for f in $(fd -HI tsconfig*.json); do
  echo "File: $f"
  rg -nP '"paths"\s*:' -n "$f" -C2 || echo "No paths"
done

echo -e "\nExport fields in package.json:"
rg -nP '"exports"\s*:' -n package.json -C3 || echo "No exports field"

echo -e "\nImports in src/traverse/chunk-large-types.ts:"
rg -n '^import' -n src/traverse/chunk-large-types.ts

echo -e "\nCheck for circular import from chunk-large-types to local-type-collector via alias:"
rg -nP "from\s+['\"]@daxserver/validation-schema-codegen/traverse/local-type-collector['\"]" -n src || echo "None"

Length of output: 204


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Listing tsconfig files:"
ls tsconfig.json tsconfig.build.json 2>/dev/null || echo "No tsconfig*.json files found"

for file in tsconfig.json tsconfig.build.json; do
  if [ -f "$file" ]; then
    echo -e "\n== $file =="

    echo "1) 'paths' section:"
    rg -nP '"paths"\s*:' -n "$file" -C5 || echo "  – no 'paths' section"

    echo "2) '@daxserver/validation-schema-codegen/*' alias:"
    rg -nP '"@daxserver/validation-schema-codegen/\*"' -n "$file" -C5 || echo "  – no alias for validation-schema-codegen"
  else
    echo "$file not present"
  fi
done

echo -e "\nChecking package.json exports:"
if [ -f package.json ]; then
  rg -nP '"exports"\s*:' -n package.json -C5 || echo "  – no 'exports' field in package.json"
  rg -nP '"\./traverse/chunk-large-types"' -n package.json -C5 || echo "  – no direct export for chunk-large-types"
else
  echo "package.json not found"
fi

echo -e "\nSearching for circular import back to local-type-collector:"
rg -nP "from\s+['\"]@daxserver/validation-schema-codegen/traverse/local-type-collector['\"]" -n src || echo "  – no circular imports detected"

Length of output: 2842


Expose deep imports and include path aliases in build config

  • package.json: add exports for "./traverse/chunk-large-types" (and other deep paths) pointing to the corresponding ./dist/traverse/*.js and .d.ts files
  • tsconfig.build.json: extend tsconfig.json or replicate its "paths": { "@daxserver/validation-schema-codegen/*": ["./src/*"] } so deep imports resolve during build
🤖 Prompt for AI Agents
In src/traverse/local-type-collector.ts around lines 1 to 4, the reviewer asks
that deep imports like
"@daxserver/validation-schema-codegen/traverse/chunk-large-types" be exposed and
resolvable during build; update package.json to add exports entries for
"./traverse/chunk-large-types" (and any other deep traverse paths) mapping to
their built files under ./dist/traverse/*.js and corresponding .d.ts, and modify
tsconfig.build.json to extend or include the same "paths": {
"@daxserver/validation-schema-codegen/*": ["./src/*"] } (or replicate it) so the
deep import path aliases resolve when compiling the project.

@DaxServer DaxServer merged commit ff0901e into main Sep 7, 2025
7 checks passed
@DaxServer DaxServer deleted the chunks branch September 7, 2025 11:15
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