Generate type-safe fluent builders for TypeScript with zero runtime dependencies
Automatically transform TypeScript interfaces into chainable builders with full IntelliSense, type safety, and smart defaults.
- π― Complete Type Safety - Full TypeScript support with type inference
- π§ Zero Runtime Dependencies - Generated builders are completely self-contained
- π Smart Defaults - Automatically generates sensible defaults for required fields
- π Nested Builder Support - Seamless composition of complex objects with deferred builds
- π§© Plugin System - Fluent API for creating powerful, type-safe plugins with advanced matching, deep type transformation, custom methods, and auxiliary data storage
- π¨ Flexible Naming Strategies - Configurable filename generation with predefined conventions or custom transform functions
- β‘ CLI & Programmatic API - Use via command line or integrate into your build process
- π JSDoc Preservation - Maintains your documentation and comments
- ποΈ Advanced Build Transforms - Insert custom logic before/after build with plugin system
- π¦ Monorepo Support - Intelligent dependency resolution for pnpm, yarn, and npm workspaces
Install as a dev dependency:
# npm
npm install -D fluent-gen-ts
# pnpm
pnpm add -D fluent-gen-ts
# yarn
yarn add -D fluent-gen-tsOr use directly with npx (no installation required):
npx fluent-gen-ts initGiven this TypeScript interface:
interface User {
id: string;
name: string;
email?: string;
role: 'admin' | 'user';
isActive: boolean;
}Generate a fluent builder:
npx fluent-gen-ts generate ./types.ts UserUse the generated builder:
import { user } from './user.builder.js';
const newUser = user()
.withId('123')
.withName('Alice')
.withEmail('alice@example.com')
.withRole('admin')
.withIsActive(true)
.build();Get started quickly with the interactive CLI:
npx fluent-gen-ts initThis will guide you through:
- π Scanning your TypeScript files
- π― Selecting interfaces to generate builders for
- βοΈ Configuring output and naming conventions
- π¦ Setting up monorepo configuration (if needed)
- π§ Setting up your configuration file
fluent-gen-ts provides intelligent dependency resolution for monorepo setups:
// fluentgen.config.js
module.exports = {
monorepo: {
enabled: true,
dependencyResolutionStrategy: 'auto', // or 'workspace-root' | 'hoisted' | 'local-only'
workspaceRoot: './path/to/workspace/root', // optional, for workspace-root strategy
customPaths: ['./custom/deps'], // optional, for custom dependency locations
},
// ... other config
};Supported Package Managers:
- pnpm workspaces - Automatically resolves from
.pnpmstore and symlinks - yarn workspaces - Finds hoisted dependencies and workspace root packages
- npm workspaces - Standard node_modules resolution with workspace support
Resolution Strategies:
auto- Try multiple strategies automatically (recommended)workspace-root- Look only in workspace root node_moduleshoisted- Walk up directory tree for hoisted dependencieslocal-only- Only check local node_modules directories
Every generated builder provides a chainable API:
const product = product()
.withId('P001')
.withName('Laptop')
.withPrice(999.99)
.withInStock(true)
.withCategories(['electronics', 'computers'])
.build();Builders automatically provide sensible defaults:
const user = user().withName('Alice').build();
// Result: { id: "", name: "Alice", role: "user", isActive: false }Build complex nested structures effortlessly:
const order = order()
.withCustomer(
customer().withName('John').withEmail('john@example.com'),
// No .build() needed - automatically handled!
)
.withItems([
item().withName('Laptop').withPrice(999),
item().withName('Mouse').withPrice(29),
])
.build();Use built-in utilities for conditional property setting:
const user = user()
.withName('Bob')
.if(u => !u.has('email'), 'email', 'default@example.com')
.ifElse(u => u.peek('email')?.includes('admin'), 'role', 'admin', 'user')
.build();Perfect for individual builders or maximum portability:
- β Self-contained with inlined utilities
- β No external dependencies
- β Easy to share across projects
npx fluent-gen-ts generate ./types.ts UserIdeal for generating multiple builders:
- β
Shared
common.tsfile with utilities - β DRY - utilities defined once
- β Consistent across all builders
// fluentgen.config.js
export default {
targets: [
{ file: './src/user.ts', types: ['User', 'UserProfile'] },
{ file: './src/product.ts', types: ['Product', 'Category'] },
],
output: { dir: './src/builders', mode: 'batch' },
};npx fluent-gen-ts batchCreate your own customizable utilities:
npx fluent-gen-ts setup-common --output ./src/common.tsExtend fluent-gen-ts with a plugin system featuring a fluent API, advanced type matching, custom naming strategies, and auxiliary data storage:
// my-plugin.ts
import { createPlugin, primitive, object, union } from 'fluent-gen-ts';
const plugin = createPlugin('comprehensive-plugin', '1.0.0')
.setDescription('Advanced transformations and custom methods')
// Advanced type matching and transformations
.transformPropertyMethods(builder =>
builder
// Handle primitive string types with custom logic
.when(ctx => ctx.type.isPrimitive('string'))
.setParameter('string | TaggedTemplateValue<string>')
.setExtractor('String(value)')
.setValidator(
`
if (value && value.length === 0) {
throw new Error('String cannot be empty');
}
`,
)
.done()
// Handle object types (e.g., AssetWrapper)
.when(ctx => ctx.type.matches(object('AssetWrapper')))
.setParameter('Asset | FluentBuilder<Asset>')
.setExtractor('{ asset: value }')
.done()
// Handle union types containing strings
.when(ctx => ctx.type.matches(union().containing(primitive('string'))))
.setParameter('string | TaggedTemplateValue<string>')
.setExtractor('String(value)')
.done(),
)
// Add custom methods with auxiliary data storage
.addMethod(method =>
method
.name('withTemplate')
.parameter('template', '(ctx: BaseBuildContext) => string')
.returns('this')
.implementation(
`
// Store template function for later processing
return this.pushAuxiliary('templates', template);
`,
)
.jsDoc('/**\\n * Add a template function processed during build\\n */'),
)
.addMethod(method =>
method
.name('withRandomId')
.parameter('prefix', 'string', { defaultValue: '"item"' })
.returns('this').implementation(`
const id = \`\${prefix}-\${Date.now()}-\${Math.random().toString(36).substr(2, 9)}\`;
return this.withId(id);
`),
)
// Transform build method with template processing
.transformBuildMethod(transform =>
transform.insertBefore(
'return this.buildWithDefaults',
`
// Process stored templates
const templates = this.getAuxiliaryArray('templates');
if (templates.length > 0 && context) {
for (const template of templates) {
try {
const result = template(context);
console.log('Template result:', result);
} catch (error) {
console.warn('Template execution failed:', error);
}
}
}
`,
),
)
.build();
export default plugin;{
"generator": {
"naming": {
// Predefined conventions: camelCase, kebab-case, snake_case, PascalCase
"convention": "camelCase",
// OR custom transform function for complete control
"transform": "(typeName) => typeName.replace(/Asset$/, '').toLowerCase()"
}
},
"plugins": ["./my-plugin.ts"],
"targets": [
{
"file": "src/types.ts",
"types": ["ActionAsset"],
"outputFile": "./src/builders/{type}.builder.ts"
}
]
}Transform types recursively at any depth with powerful utilities:
import {
createPlugin,
primitive,
transformTypeDeep,
containsTypeDeep,
TypeDeepTransformer,
} from 'fluent-gen-ts';
const deepTransformPlugin = createPlugin('deep-transform', '1.0.0')
.transformPropertyMethods(builder =>
builder
// Use fluent transformer API
.when(ctx => ctx.type.containsDeep(primitive('string')))
.setParameter(ctx =>
ctx.type
.transformDeep()
.replace(primitive('string'), 'string | { value: string }')
.replace(primitive('number'), 'number | { value: number }')
.toString(),
)
.done()
// Or use low-level API for advanced control
.when(ctx => ctx.type.containsDeep(primitive('Date')))
.setParameter(ctx =>
transformTypeDeep(ctx.propertyType, {
onPrimitive: type => {
if (primitive('Date').match(type)) {
return 'Date | string | number';
}
return null;
},
}),
)
.done(),
)
.build();Transformation Examples:
Array<string>βArray<string | { value: string }>{ name: string, tags: Array<string> }β{ name: string | { value: string }; tags: Array<string | { value: string }> }{ data: { nested: { value: string } } }β{ data: { nested: { value: string | { value: string } } } }
Key Plugin Features:
- π― Fluent Plugin Builder API - Chainable, type-safe plugin creation
- π Advanced Type Matching - Match primitives, objects, unions, arrays, generics
- π Deep Type Transformation - Recursively transform types at any depth
- π¦ Auxiliary Data Storage - Store templates, functions, and custom data
- ποΈ Build Method Transformation - Insert custom logic before/after build
- π Custom Method Generation - Add domain-specific methods to builders
- π§ Smart Import Management - Automatic handling of internal/external imports
- π¨ Flexible Naming Strategies - Custom filename transformations
fluent-gen-ts handles complex TypeScript patterns:
interface Container<T> {
value: T;
metadata: Record<string, unknown>;
}
// Generated: container<T>() buildertype PublicUser = Pick<User, 'id' | 'name'>;
type UpdateUser = Partial<Omit<User, 'id'>>;
// Fully supported with correct property methodsinterface Config {
mode: 'development' | 'production';
ssl: boolean | { cert: string; key: string };
}
// Generates type-safe methods for all variations| Command | Description |
|---|---|
init |
Interactive setup wizard |
generate <file> <type> |
Generate single builder |
batch |
Generate multiple builders from config |
scan <pattern> |
Scan and display found types |
setup-common |
Create customizable common utilities |
See the CLI documentation for detailed options.
fluent-gen-ts supports multiple configuration file formats:
fluentgen.config.jsor.fluentgenrc.js(ES modules or CommonJS).fluentgenrc.json,.fluentgenrc.yaml,.fluentgenrc.yml(JSON/YAML)package.json(under"fluentgen"key)
ES Modules (recommended):
/** @type {import('fluent-gen-ts').Config} */
export default {
// Input files and types
targets: [
{ file: './src/models/user.ts', types: ['User', 'UserProfile'] },
{ file: './src/models/product.ts', types: ['Product', 'Category'] },
],
// Output configuration (optional, used mainly in batch mode)
output: {
dir: './src/builders',
mode: 'batch', // or 'single'
},
// Generator options
generator: {
useDefaults: true, // Generate smart defaults
addComments: true, // Include JSDoc comments
// Advanced naming configuration
naming: {
convention: 'camelCase', // camelCase, kebab-case, snake_case, PascalCase
suffix: 'builder',
// OR custom transform function for complete control
transform: '(typeName) => typeName.replace(/DTO$/, "").toLowerCase()',
},
},
// TypeScript configuration
tsConfigPath: './tsconfig.json',
// Plugins
plugins: ['./plugins/validation.js', './plugins/custom-methods.js'],
};CommonJS:
/** @type {import('fluent-gen-ts').Config} */
module.exports = {
targets: [{ file: './src/models/user.ts', types: ['User', 'UserProfile'] }],
generator: {
naming: {
convention: 'camelCase',
suffix: 'builder',
},
},
plugins: ['./plugins/validation.js'],
};{
"scripts": {
"generate:builders": "fluent-gen-ts batch",
"prebuild": "npm run generate:builders"
}
}Generated builders are perfect for creating test data:
// Test factories
const testUser = user()
.withId('test-123')
.withName('Test User')
.withEmail('test@example.com')
.withRole('user')
.withIsActive(true)
.build();
// Property-based testing
import { fc } from 'fast-check';
const arbitraryUser = fc
.record({
name: fc.string(),
email: fc.emailAddress(),
role: fc.constantFrom('admin', 'user'),
})
.map(data => user(data).build());const successResponse = apiResponse()
.withData(
paginatedResult().withItems([user1, user2, user3]).withPagination({
page: 1,
total: 150,
hasNext: true,
}),
)
.withStatus(200)
.withMessage('Success')
.build();const config = appConfig()
.withEnv('production')
.withDatabase(
databaseConfig()
.withHost('prod-db.company.com')
.withSsl(true)
.withPool({ min: 5, max: 50 }),
)
.withCache(cacheConfig().withType('redis').withTtl(3600))
.build();- Getting Started - Your first builder in less than a minute
- Core Concepts - Understanding the fundamentals
- CLI Reference - Complete CLI reference
- Plugin System - Extending with plugins
- Advanced Usage - Complex scenarios and patterns
- API Reference - Complete API documentation
- Examples - Use patterns
Creating complex object structures in TypeScript often leads to:
- π Verbose object literals with repetitive property assignments
- π No IDE support while building objects incrementally
- π§ͺ Difficulty creating test data variations
- π Manual maintenance of builder patterns
fluent-gen-ts automatically generates builders that:
- βοΈ Provide chainable APIs for step-by-step object construction
- π‘ Offer full IntelliSense support at every step
- β Generate valid objects with smart defaults
- ποΈ Support complex nested objects and arrays
- π« Require zero runtime dependencies
fluent-gen-ts may not be the best fit if:
- β Simple DTOs - Objects with 2-3 properties are easier as plain literals
- β Immutable Data Structures - Libraries like Immer.js are better suited
- β Runtime Validation - Use Zod, io-ts, or class-validator for runtime checks
Consider instead:
- Plain object literals for simple cases
- Zod schemas for runtime validation
- Factory functions for one-off test data
We welcome contributions! Please see our Contributing Guide for details.
# Clone the repository
git clone https://github.com/rafbcampos/fluent-gen-ts.git
cd fluent-gen-ts
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build the project
pnpm build# Run all tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run tests with coverage
pnpm test:coverage
# Run only unit tests
pnpm test:unit
# Run only E2E tests
pnpm test:e2e- ts-morph - TypeScript compiler API wrapper
- TypeScript - The amazing type system that makes this possible
Documentation β’ Examples β’ GitHub β’ NPM
Made with β€οΈ by Rafael Campos