Skip to content

NathanJAdams/fluent-builder

Repository files navigation

ts-fluent-builder

A powerful TypeScript library for building any complex type, 100% fluent within a single builder, arbitrarily nested, zero config, with full IntelliSense support and compile-time type & data safety mechanisms.

Features

  • Simple & Powerful: A single function to build any complex type, interface, array, tuple, record or object union
  • Type-Safe: Full TypeScript support with compile-time validation
  • Data-Safe: Prevents overwriting data or building incomplete objects
  • Fluent API: Intuitive method chaining for object construction
  • IntelliSense: Rich autocomplete and type hints in your IDE
  • Flexible: Works with any valid TypeScript type structure

Installation

npm install ts-fluent-builder
# or
yarn add ts-fluent-builder

Quick Start

import { fluentBuilder } from 'ts-fluent-builder';

// Define your types
interface User {
  id: number;
  name: string;
  email: string;
  addresses: Address[];
}

interface Address {
  street: string;
  city: string;
  country: string;
}

// Build objects fluently
const user = fluentBuilder<User>()
  .id(1)
  .name('John Doe')
  .email('john@example.com')
  .addressesArray()
    .pushObject()
      .street('123 Main St')
      .city('New York')
      .country('USA')
      .build()
    .push({
      street: '456 Oak Ave',
      city: 'Boston',
      country: 'USA'
    })
    .buildAddresses()
  .build();

API Reference

fluentBuilder<T>()

Creates a new fluent builder for the specified type.

Parameters:

  • T: The TypeScript type to build

Returns: A builder instance with functions ready to build the type

// objects
interface Person {
  name: string;
  age: number;
  address: Address;
}
const person = fluentBuilder<Person>()
  .name('Alice')
  .age(30)
  .addressObject()
    .street('123 Main St')
    .city('Springfield')
    .country('USA')
    .buildAddress()
  .build();
// arrays
const numbers = fluentBuilder<number[]>()
  .push(1)
  .push(2)
  .push(3)
  .build(); // [1, 2, 3]

interface TodoItem {
  id: number;
  title: string;
  completed: boolean;
}

interface TodoList {
  items: TodoItem[];
}

const todoList = fluentBuilder<TodoList>()
  .itemsArray()
    .pushObject()
      .id(1)
      .title('Buy groceries')
      .completed(false)
      .build()
    .pushObject()
      .id(2)
      .title('Walk the dog')
      .completed(true)
      .build()
    .buildItems()
  .build();
// tuples
const falsyValues = fluentBuilder<[boolean, number, bigint, string]>()
  .index0(false)
  .index1(0)
  .index2(0n)
  .index3('')
  .buildArray();
// polymorphic types via unions (currently only object unions are supported)
interface Shape {
  type: string;
  area: number;
}

interface Circle extends Shape {
  type: 'circle';
  radius: number;
}

interface Rectangle extends Shape {
  type: 'rectangle';
  width: number;
  height: number;
}

const shapes = fluentBuilder<(Circle | Rectangle)[]>()
  .pushObject()
    .type('circle')
    .area(Math.PI * 5 * 5)
    .radius(5)
    .buildElement()
  .pushObject()
    .type('rectangle')
    .area(20)
    .width(4)
    .height(5)
    .buildElement()
  .buildArray();
// records
interface Config {
  url: string;
  timeout: number;
  enabled: boolean;
}

const config = fluentBuilder<Record<string, Config>>()
  .set('production', {
    url: 'https://api.example.com',
    timeout: 5000,
    enabled: true
  })
  .setObject('development')
    .url('https://dev.api.example.com')
    .timeout(10000)
    .enabled(false)
    .buildDevelopment()
  .buildRecord();

Builder Methods

There are a few common patterns used:

Value Assignment

.propertyName(value)    // Set a property value on an object
.push(value)            // Append to an array
.set(key, value)        // Set a key value entry on a record
.indexN(value)          // Set indexed value on a tuple

Nested Builders

.propertyNameArray()    // Start building a nested array  
.propertyNameObject()   // Start building a nested object
.propertyNameRecord()   // Start building a nested record

.pushArray()            // Start building a nested array to push onto the array
.pushObject()           // Start building a nested object to push onto the array
.pushRecord()           // Start building a nested record to push onto the array

.indexNArray()          // Start building a nested array at the array index
.indexNObject()         // Start building a nested object at the array index
.indexNRecord()         // Start building a nested record at the array index

.setArray(name)         // Start building a nested array to set on the record
.setObject(name)        // Start building a nested sub-type to set on the record
.setRecord(name)        // Start building a nested record to set on the record

Termination

// Build and return either the result or the parent builder for continued chaining
.build()                

// all build...() functions are aliases of build() and can be used as hints to the developer, eg:
.buildPropertyName()
.buildRecordName()
.buildArray()
.buildObject()
.buildRecord()

Advanced Usage

Complex Nested Structures

interface Company {
  name: string;
  departments: Department[];
  settings: Record<string, any>;
}

interface Department {
  name: string;
  employees: Employee[];
  budget: number;
}

interface Employee {
  id: number;
  name: string;
  role: string;
}

const company = fluentBuilder<Company>()
  .name('TechCorp')
  .departmentsArray()
    .pushObject()
      .name('Engineering')
      .budget(500000)
      .employeesArray()
        .pushObject()
          .id(1)
          .name('John Doe')
          .role('Senior Developer')
          .build()
        .pushObject()
          .id(2)
          .name('Jane Smith')
          .role('Tech Lead')
          .build()
        .buildEmployees()
      .build()
    .buildDepartments()
  .settingsRecord()
    .set('theme', 'dark')
    .set('notifications', true)
    .set('autoSave', false)
    .buildSettings()
  .build();

Working with Optional Properties

The builder automatically handles optional properties and will only require you to set mandatory fields:

interface User {
  id: number;
  name: string;
  email?: string;  // optional
  phone?: string;  // optional
}

// This works - only required fields need to be set
const user = fluentBuilder<User>()
  .id(1)
  .name('John')
  .build();

// Optional fields can be added
const fullUser = fluentBuilder<User>()
  .id(1)
  .name('John')
  .email('john@example.com')
  .phone('+1234567890')
  .build();

Type Safety Features

Compile-Time Validation

ts-fluent-builder prevents common type and data mistakes at compile time:

interface User {
  id: number;
  name: string;
  email?: string;  // optional
  phone?: string;  // optional
}

// ❌ This won't compile - missing required property
const invalid = fluentBuilder<User>()
  .name('John')
  // .id(1) - missing required field
  .build();

// ❌ This won't compile - wrong type
const wrongType = fluentBuilder<User>()
  .id('not-a-number')  // id expects number
  .build();

// ❌ This won't compile - tuple not completely filled
const missingTupleElement = fluentBuilder<[boolean, string, number]>()
  .index0(true)
  .index1('abc')
  // .index2(123) - missing element
  .build();

// ❌ This won't compile - duplicate property
const duplicateProperty = fluentBuilder<User>()
  .id(1)
  .id(2)  // can't set id twice
  .build();

// ❌ This won't compile - duplicate record key
const duplicateKey = fluentBuilder<Record<string, boolean>>()
  .set('production', true)
  .set('production', false)  // can't set same key twice
  .build();

IntelliSense Support

  • Autocomplete for available properties
  • Type hints for expected values
  • Documentation in tooltips
  • Error highlighting for type mismatches

Best Practices

  1. Use TypeScript: ts-fluent-builder is designed for TypeScript and provides minimal value in plain JavaScript

  2. Define Interfaces or Types First: Always define your types before using the builders

  3. Leverage IntelliSense: Let your IDE guide you through the available methods

  4. Use the build...() Aliases: Help later developers easily see where they're looking in the build chain

Contributing

Contributions are very welcome! Please open an issue or submit a pull request on GitHub Issues.

License

This project is licensed under the MIT License - see the LICENSE file for details.


Happy Building!

About

A TypeScript library for building types - 100% fluent, arbitrarily nested, zero config with compile-time type & data safety mechanisms.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors