ADR-006: Unbuilt TypeScript Library
- Status: accepted
- Date: 2025-01-26
Context and Problem Statement
The package currently includes a build process that compiles TypeScript to JavaScript and generates declaration files. However, given the package's role as a meta-package providing configurations and utilities, and the need to maintain data structure consistency between the package and consumers, we need to evaluate whether a build process is necessary or beneficial.
Decision Drivers
- Need to maintain data structure consistency between package and consumers
- Requirement for type safety and compile-time validation
- Desire to avoid serialization/deserialization issues with complex data structures
- Need to provide consumers with exact types and interfaces
- Requirement for transparent dependency management
- Need to avoid build process complexity and maintenance overhead
- Desire to provide better IDE support and debugging experience
Considered Options
- Keep current build process (TypeScript → JavaScript + declarations)
- Remove build process and distribute TypeScript source directly
- Hybrid approach with both built and unbuilt options
- Use different build tools or configurations
Decision Outcome
Chosen option: "Remove build process and distribute TypeScript source directly", because it provides better data structure consistency, type safety, and developer experience for a meta-package.
Positive Consequences
- Data Structure Consistency: Consumers get direct access to the same objects and types used internally
- Type Safety: TypeScript compiler ensures compatibility at compile time, preventing runtime errors
- No Serialization Issues: Direct object references eliminate JSON serialization/deserialization problems
- Transparent Dependencies: Consumers see exactly what libraries and versions are being used
- Better IDE Support: Full IntelliSense, go-to-definition, and refactoring support
- Easier Debugging: Source code is directly accessible, no minified/obfuscated code
- Simplified Maintenance: No build artifacts to maintain or version
- Version Consistency: TypeScript ensures consumers use the exact same library versions
Negative Consequences
- Consumer Requirements: Consumers must have TypeScript in their build process
- Node.js Requirement: Consumers need Node.js build environment
- Slightly More Complex Setup: Consumers need to handle TypeScript compilation
- Source Code Exposure: Package source code is directly accessible (though this may be a feature)
Implementation Details
- Remove Build Process: Eliminate
tsccompilation and related scripts - Update Package Exports: Change exports to point to TypeScript source files
- Update Package Files: Include TypeScript source files instead of compiled JavaScript
- Update Documentation: Reflect the unbuilt approach in documentation
- Maintain TypeScript Configuration: Keep
tsconfig.base.jsonfor consumers to extend
Package Structure Changes
Before:
dist/
index.js
index.d.ts
types/
index.js
index.d.tsAfter:
src/
index.ts
types/
index.ts
tsconfig.base.jsonDependency Management
The package uses both dependencies and optionalDependencies for utility libraries to ensure version consistency:
{
"dependencies": {
"defu": "^6.1.4"
},
"optionalDependencies": {
"defu": "^6.1.4"
}
}This modern approach ensures consumers get the exact same version used internally, preventing version conflicts and ensuring predictable behavior.
Package.json Changes
Exports:
{
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
},
"./tsconfig": "./tsconfig.base.json",
"./eslint": "./eslint.js"
}
}Files:
{
"files": ["src", "eslint.js", "tsconfig.base.json"]
}Consumer Usage
TypeScript Configuration:
{
"extends": "@relational-fabric/canon/tsconfig"
}ESLint Configuration:
import createEslintConfig from '@relational-fabric/canon/eslint'
export default createEslintConfig()Utility Libraries:
import { defu } from '@relational-fabric/canon'
// Direct access to the same utility used internally