A Kotlin library that generates TypeScript API clients from Spring REST controllers.
typescript-generator2/
├── core/ # Main library
│ └── src/main/kotlin/com/iodesystems/ts/
│ ├── TypeScriptGenerator.kt # Entry point
│ ├── Config.kt # Configuration DSL
│ ├── Emitter.kt # TypeScript code generation
│ ├── Scanner.kt # ClassGraph scanning
│ ├── adapter/ # JSON/API framework adapters
│ │ ├── JsonAdapter.kt # Interface for JSON libs (Jackson)
│ │ ├── JacksonJsonAdapter.kt
│ │ ├── ApiAdapter.kt # Interface for API frameworks
│ │ └── SpringApiAdapter.kt
│ ├── extractor/ # Type extraction
│ │ ├── JvmExtractor.kt # Orchestrates extraction
│ │ ├── RegistrationContext.kt # Type registration & collision detection
│ │ ├── SpringApiExtractor.kt # Extracts Spring @RestController methods
│ │ └── extractors/
│ │ ├── ClassReference.kt # JVM Class -> TsType conversion
│ │ ├── JvmMethod.kt # Method parameter extraction
│ │ └── ...
│ ├── model/ # Data models
│ │ ├── TsType.kt # TypeScript type hierarchy
│ │ └── ApiModel.kt # API method model
│ └── lib/ # Utilities
├── test-project/ # Test subproject for external classpath testing
│ ├── test-core/ # Contains API controllers using test-dep types
│ └── test-dep/ # External dependency types
└── buildSrc/ # Gradle build logic
TsType.Inline- Inline type references (primitives, generics)TsType.Object- Object types with fieldsTsType.Union- Discriminated unions (sealed interfaces with @JsonTypeInfo)TsType.Enum- Enum typesTsType.Alias- Type aliases for mapped types (provides traceability)
Scanneruses ClassGraph to find Spring controllersSpringApiExtractorextracts API methods from controllersJvmExtractororchestrates type extraction usingClassReferenceClassReferenceconverts JVM types to TsType using:Class.forName()/scan.loadClass()for reflection- Kotlin metadata for nullability (
kotlinx-metadata-jvm)
RegistrationContextregisters types and detects collisionsEmittergenerates TypeScript code
- Mapped Types: User-defined JVM->TS mappings (e.g.,
OffsetDateTime -> "Dayjs | string") - Type Aliases: Mapped types emit as aliases for traceability
- Jackson Support: @JsonProperty, @JsonValue, @JsonTypeInfo, @JsonCreator
- Spring Support: @PathVariable, @RequestParam, @RequestBody
- Collision Detection: Throws error if custom naming causes duplicate type names
- External Classpath: Can extract types from jars not on compile classpath
./gradlew :core:test # All tests
./gradlew :core:test --tests "*.SomeTest" # Specific test@RestController
@RequestMapping("/api")
class MyController {
@PostMapping
fun post(@RequestBody req: MyRequest): MyResponse = error("stub")
}
class MyTest {
@Test
fun testSomething() {
val em = emitter(MyController::class)
val content = em.ts().content()
content.assertContains("expected output", "description")
}
}TypeScriptGenerator.build {
includeApi(MyController::class)
mappedType(OffsetDateTime::class, "Dayjs | string")
addTypeNameReplacement("\\$", "") // Remove $ from nested class names
outputDirectory("./generated")
}.generate().write()ClassReference.kt- Core type conversion logicEmitter.kt- TypeScript output generationRegistrationContext.kt- Type registration with collision detectionTsType.kt- Type model definitionsConfig.kt- All configuration options