๐Ÿš€ Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more โ†’
Sign In

@cerios/cerios-builder

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cerios/cerios-builder

A TypeScript builder pattern library providing compile-time type safety for object construction with method chaining and required field validation

Source
npmnpm
Version
1.3.0
Version published
Weekly downloads
22
10%
Maintainers
1
Weekly downloads
ย 
Created
Source

@cerios/cerios-builder

A powerful TypeScript builder pattern library that provides compile-time type safety for object construction. Build complex objects with method chaining while ensuring all required properties are set at compile time.

๐Ÿš€ Features

  • Type-Safe Building: Compile-time validation ensures all required properties are set
  • Nested Properties Support: Build deeply nested objects with dot-notation paths
  • Flexible Runtime Validation: Choose between compile-time, runtime, or both validations
  • Dynamic Required Fields: Add required fields at runtime based on your business logic
  • Method Chaining: Fluent API for readable object construction
  • Partial Building: Build incomplete objects when needed
  • Zero Runtime Overhead: All type checking happens at compile time (unless using runtime validation)
  • Extensible: Easy to create custom builder methods
  • TypeScript First: Built with TypeScript, for TypeScript
  • Array Property Helpers: Easily add values to array properties with type safety

๐Ÿ“ฆ Installation

npm install @cerios/cerios-builder

dev install

npm install --save-dev @cerios/cerios-builder

๐ŸŽฏ Quick Start

import { CeriosBuilder, RequiredFieldsTemplate } from '@cerios/cerios-builder';

// 1. Define your types
type User = {
    id: string;
    name: string;
    email: string;
    age?: number;
};

// 2. Create your builder
class UserBuilder extends CeriosBuilder<User> {
    static create() {
        return new UserBuilder({});
    }

    id(value: string) {
        return this.setProperty('id', value);
    }

    name(value: string) {
        return this.setProperty('name', value);
    }

    email(value: string) {
        return this.setProperty('email', value);
    }

    age(value: number) {
        return this.setProperty('age', value);
    }
}

// 3. Build your object
const user = UserBuilder.create()
    .id('123')
    .name('John Doe')
    .email('john@example.com')
    .age(30)
    .build();

๐Ÿ“– Feature Overview

FeatureMethodUse Case
Simple PropertiessetProperty()Set flat object properties
Nested PropertiessetNestedProperty()Set deeply nested properties with dot notation
Array PropertiesaddToArrayProperty()Add values to array properties
Multiple PropertiessetProperties()Set multiple properties at once
Required Templatestatic requiredTemplateDefine required fields for runtime validation
Dynamic RequirementssetRequiredFields()Add required fields at runtime
Full Validationbuild()Build with both compile-time and runtime validation
Compile-Time OnlybuildWithoutRuntimeValidation()Build with TypeScript checking only
Runtime OnlybuildWithoutCompileTimeValidation()Build with runtime validation only
No ValidationbuildUnsafe()Build without any validation
Partial BuildbuildPartial()Build incomplete objects

๐Ÿ”ง Basic Usage

1. Define Your Type and Builder

import { CeriosBuilder } from '@cerios/cerios-builder';

// Define your data type
type User = {
    id: string;
    name: string;
    email: string;
    age?: number; // Optional property
    roles?: string[]; // Optional array property
};

// Create your builder class
class UserBuilder extends CeriosBuilder<User> {
    static create() {
        return new UserBuilder({});
    }

    id(value: string) {
        return this.setProperty('id', value);
    }

    name(value: string) {
        return this.setProperty('name', value);
    }

    email(value: string) {
        return this.setProperty('email', value);
    }

    age(value: number) {
        return this.setProperty('age', value);
    }

    addRole(role: string) {
        return this.addToArrayProperty('roles', role);
    }

    // Custom methods for common patterns
    withRandomId() {
        return this.setProperty('id', crypto.randomUUID());
    }

    withAdminEmail(username: string) {
        return this.setProperty('email', `${username}@admin.company.com`);
    }
}

2. Build Objects Safely

// โœ… This compiles - all required fields are set
const user = UserBuilder.create()
    .id("123")
    .name("John Doe")
    .email("john@example.com")
    .age(30)
    .addRole("admin")
    .addRole("editor")
    .build();

// โœ… Optional fields and arrays can be omitted
const basicUser = UserBuilder.create()
    .id("456")
    .name("Jane Doe")
    .email("jane@example.com")
    .build(); // age and roles are optional

// โŒ This won't compile - missing required 'email' field
const incompleteUser = UserBuilder.create()
    .id("789")
    .name("Bob Smith")
    .build(); // ๐Ÿ’ฅ TypeScript error!

3. Use Custom Methods

const adminUser = UserBuilder.create()
    .withRandomId()
    .name("Admin User")
    .withAdminEmail("admin")
    .addRole("admin")
    .age(25)
    .build();

console.log(adminUser);
// Output: { id: "550e8400-...", name: "Admin User", email: "admin@admin.company.com", roles: ["admin"], age: 25 }

๐ŸŽฏ Advanced Examples

Deeply Nested Properties

Build complex nested structures with type-safe dot-notation paths:

import { CeriosBuilder, RequiredFieldsTemplate } from '@cerios/cerios-builder';

type Address = {
    Street: string;
    City: string;
    PostalCode?: string;
    Country: string;
};

type OrderDetails = {
    OrderNumber?: string;
    CustomerId: string;
    TotalAmount: number;
    Status: string;
    ShippingAddress: Address;
};

type Order = {
    Details?: OrderDetails;
};

type OrderRequest = {
    Order: Order;
};

class OrderRequestBuilder extends CeriosBuilder<OrderRequest> {
    static requiredTemplate: RequiredFieldsTemplate<OrderRequest> = [
        'Order.Details.CustomerId',
        'Order.Details.TotalAmount',
        'Order.Details.Status',
        'Order.Details.ShippingAddress.Street',
        'Order.Details.ShippingAddress.City',
        'Order.Details.ShippingAddress.Country',
    ];

    static create() {
        return new OrderRequestBuilder({});
    }

    static createWithDefaults() {
        return this.create().status('pending');
    }

    orderNumber(value: string) {
        return this.setNestedProperty('Order.Details.OrderNumber', value);
    }

    customerId(value: string) {
        return this.setNestedProperty('Order.Details.CustomerId', value);
    }

    totalAmount(value: number) {
        return this.setNestedProperty('Order.Details.TotalAmount', value);
    }

    status(value: string) {
        return this.setNestedProperty('Order.Details.Status', value);
    }

    shippingStreet(value: string) {
        return this.setNestedProperty('Order.Details.ShippingAddress.Street', value);
    }

    shippingCity(value: string) {
        return this.setNestedProperty('Order.Details.ShippingAddress.City', value);
    }

    shippingPostalCode(value: string) {
        return this.setNestedProperty('Order.Details.ShippingAddress.PostalCode', value);
    }

    shippingCountry(value: string) {
        return this.setNestedProperty('Order.Details.ShippingAddress.Country', value);
    }
}

// Usage with full validation (both compile-time and runtime)
const order = OrderRequestBuilder.createWithDefaults()
    .customerId('CUST-001')
    .totalAmount(299.99)
    .orderNumber('ORD-12345')
    .shippingStreet('123 Main St')
    .shippingCity('New York')
    .shippingCountry('USA')
    .build(); // โœ… TypeScript + runtime validation

// โŒ This will throw an error at runtime
try {
    const invalidOrder = OrderRequestBuilder.createWithDefaults()
        .customerId('CUST-002')
        .buildWithoutCompileTimeValidation(); // Missing TotalAmount and shipping address
} catch (error) {
    console.error(error.message);
    // "Missing required fields: Order.Details.TotalAmount, Order.Details.ShippingAddress.Street, ..."
}

Runtime Validation with Required Templates

Define which fields are required and validate them at runtime:

type Product = {
    id: string;
    name: string;
    price: number;
    description?: string;
};

class ProductBuilder extends CeriosBuilder<Product> {
    static requiredTemplate: RequiredFieldsTemplate<Product> = [
        'id',
        'name',
        'price',
    ];

    static create() {
        return new ProductBuilder({});
    }

    id(value: string) {
        return this.setProperty('id', value);
    }

    name(value: string) {
        return this.setProperty('name', value);
    }

    price(value: number) {
        return this.setProperty('price', value);
    }

    description(value: string) {
        return this.setProperty('description', value);
    }
}

// Full validation (compile-time + runtime)
const product = ProductBuilder.create()
    .id('PROD-001')
    .name('Laptop')
    .price(999.99)
    .build(); // โœ… All required fields are set

// Runtime-only validation (useful for dynamic data)
const dynamicProduct = ProductBuilder.create()
    .id('PROD-002')
    .name('Mouse')
    .price(29.99)
    .buildWithoutCompileTimeValidation(); // โœ… Runtime check passes

// โŒ This will throw an error at runtime
try {
    const invalidProduct = ProductBuilder.create()
        .id('PROD-003')
        .name('Keyboard')
        .buildWithoutCompileTimeValidation(); // Missing price
} catch (error) {
    console.error(error.message);
    // "Missing required fields: price"
}

Dynamic Required Fields

Add required fields at runtime based on business logic:

type Employee = {
    firstName: string;
    lastName: string;
    email: string;
    employeeId?: string;
    phone?: string;
};

class EmployeeBuilder extends CeriosBuilder<Employee> {
    static requiredTemplate: RequiredFieldsTemplate<Employee> = [
        'firstName',
        'lastName',
        'email',
    ];

    static create() {
        return new EmployeeBuilder({});
    }

    firstName(value: string) {
        return this.setProperty('firstName', value);
    }

    lastName(value: string) {
        return this.setProperty('lastName', value);
    }

    email(value: string) {
        return this.setProperty('email', value);
    }

    employeeId(value: string) {
        return this.setProperty('employeeId', value);
    }

    phone(value: string) {
        return this.setProperty('phone', value);
    }
}

// Scenario 1: New employee (only basic fields required)
const newEmployee = EmployeeBuilder.create()
    .firstName('John')
    .lastName('Doe')
    .email('john.doe@company.com')
    .build(); // โœ… Only validates firstName, lastName, email

// Scenario 2: Existing employee (ID required)
const existingEmployee = EmployeeBuilder.create()
    .setRequiredFields(['employeeId']) // Add employeeId as required dynamically
    .firstName('Jane')
    .lastName('Smith')
    .email('jane.smith@company.com')
    .employeeId('EMP-12345')
    .buildWithoutCompileTimeValidation(); // โœ… Runtime validates all fields including employeeId

// Scenario 3: Employee with contact requirement
const contactEmployee = EmployeeBuilder.create()
    .setRequiredFields(['phone', 'employeeId']) // Add multiple dynamic fields
    .firstName('Bob')
    .lastName('Johnson')
    .email('bob.johnson@company.com')
    .phone('+1-555-0123')
    .employeeId('EMP-67890')
    .buildWithoutCompileTimeValidation(); // โœ… Validates all including phone and employeeId

// โŒ This will fail - missing dynamically required field
try {
    const invalidEmployee = EmployeeBuilder.create()
        .setRequiredFields(['employeeId'])
        .firstName('Alice')
        .lastName('Brown')
        .email('alice.brown@company.com')
        .buildWithoutCompileTimeValidation(); // Missing employeeId
} catch (error) {
    console.error(error.message);
    // "Missing required fields: employeeId"
}

Four Ways to Set Up Required Fields for Deeply Nested Properties

When working with deeply nested structures, you have multiple approaches to configure required field validation:

type Address = {
    Street: string;
    City: string;
    Country: string;
};

type OrderDetails = {
    CustomerId: string;
    TotalAmount: number;
    ShippingAddress: Address;
};

type Order = {
    Details: OrderDetails;
};

class OrderBuilder extends CeriosBuilder<Order> {
    // Method 1: Static template defined at class level
    static requiredTemplate: RequiredFieldsTemplate<Order> = [
        'Details.CustomerId',
        'Details.TotalAmount',
        'Details.ShippingAddress.Street',
        'Details.ShippingAddress.City',
        'Details.ShippingAddress.Country',
    ];

    static create() {
        return new OrderBuilder({});
    }

    customerId(value: string) {
        return this.setNestedProperty('Details.CustomerId', value);
    }

    totalAmount(value: number) {
        return this.setNestedProperty('Details.TotalAmount', value);
    }

    shippingStreet(value: string) {
        return this.setNestedProperty('Details.ShippingAddress.Street', value);
    }

    shippingCity(value: string) {
        return this.setNestedProperty('Details.ShippingAddress.City', value);
    }

    shippingCountry(value: string) {
        return this.setNestedProperty('Details.ShippingAddress.Country', value);
    }
}

// Usage: Runtime validation checks all required fields
const order = OrderBuilder.create()
    .customerId('CUST-001')
    .totalAmount(299.99)
    .shippingStreet('123 Main St')
    .shippingCity('New York')
    .shippingCountry('USA')
    .buildWithoutCompileTimeValidation(); // โœ… Validates all 5 required nested fields

2. Dynamic Required Fields via setRequiredFields() (Best for Conditional Logic)

class OrderBuilder extends CeriosBuilder<Order> {
    static requiredTemplate: RequiredFieldsTemplate<Order> = [
        'Details.CustomerId',
        'Details.TotalAmount',
    ];

    static create() {
        return new OrderBuilder({});
    }

    static createInternational() {
        return this.create()
            .setRequiredFields([
                'Details.ShippingAddress.Street',
                'Details.ShippingAddress.City',
                'Details.ShippingAddress.Country',
                'Details.ShippingAddress.PostalCode', // Extra requirement for international
            ]);
    }

    static createDomestic() {
        return this.create()
            .setRequiredFields([
                'Details.ShippingAddress.Street',
                'Details.ShippingAddress.City',
                'Details.ShippingAddress.Country',
            ]); // No postal code required
    }

    customerId(value: string) {
        return this.setNestedProperty('Details.CustomerId', value);
    }

    totalAmount(value: number) {
        return this.setNestedProperty('Details.TotalAmount', value);
    }

    shippingStreet(value: string) {
        return this.setNestedProperty('Details.ShippingAddress.Street', value);
    }

    shippingCity(value: string) {
        return this.setNestedProperty('Details.ShippingAddress.City', value);
    }

    shippingCountry(value: string) {
        return this.setNestedProperty('Details.ShippingAddress.Country', value);
    }

    shippingPostalCode(value: string) {
        return this.setNestedProperty('Details.ShippingAddress.PostalCode', value);
    }
}

// Domestic order: postal code is optional
const domesticOrder = OrderBuilder.createDomestic()
    .customerId('CUST-001')
    .totalAmount(99.99)
    .shippingStreet('123 Main St')
    .shippingCity('New York')
    .shippingCountry('USA')
    .buildWithoutCompileTimeValidation(); // โœ… Valid without postal code

// International order: postal code is required
const internationalOrder = OrderBuilder.createInternational()
    .customerId('CUST-002')
    .totalAmount(299.99)
    .shippingStreet('10 Downing Street')
    .shippingCity('London')
    .shippingCountry('UK')
    .shippingPostalCode('SW1A 2AA')
    .buildWithoutCompileTimeValidation(); // โœ… All fields including postal code validated

// โŒ This will fail - missing postal code for international
try {
    const invalidOrder = OrderBuilder.createInternational()
        .customerId('CUST-003')
        .totalAmount(150.00)
        .shippingStreet('Rue de Rivoli')
        .shippingCity('Paris')
        .shippingCountry('France')
        .buildWithoutCompileTimeValidation(); // Missing PostalCode
} catch (error) {
    console.error(error.message);
    // "Missing required fields: Details.ShippingAddress.PostalCode"
}

Building Test Data

Perfect for creating test fixtures with sensible defaults:

type Product = {
    name: string;
    price: number;
    category: string;
    tags?: string[];
};

class ProductBuilder extends CeriosBuilder<Product> {
    static create() {
        return new ProductBuilder({});
    }

    name(value: string) {
        return this.setProperty('name', value);
    }

    price(value: number) {
        return this.setProperty('price', value);
    }

    category(value: string) {
        return this.setProperty('category', value);
    }

    addTag(tag: string) {
        return this.addToArrayProperty('tags', tag);
    }

    // Custom methods for testing
    asElectronics() {
        return this.category('Electronics').price(299.99);
    }

    asFreeProduct() {
        return this.price(0);
    }

    withRandomName() {
        const names = ['Widget', 'Gadget', 'Tool', 'Device'];
        return this.name(names[Math.floor(Math.random() * names.length)]);
    }
}

// In your tests
const testProduct = ProductBuilder.create()
    .withRandomName()
    .asElectronics()
    .addTag("featured")
    .addTag("sale")
    .build();

Building Complex Nested Objects

type Address = {
    street: string;
    city: string;
    country: string;
    zipCode?: string;
};

type Customer = {
    id: string;
    name: string;
    address: Address;
    phoneNumber?: string;
    notes?: string[];
};

class AddressBuilder extends CeriosBuilder<Address> {
    static create() {
        return new AddressBuilder({});
    }

    static createWithDefaults() {
        return this.create().setProperties({
            city: "Othertown",
            country: "United States",
        });
    }

    street(value: string) {
        return this.setProperty('street', value);
    }

    city(value: string) {
        return this.setProperty('city', value);
    }

    country(value: string) {
        return this.setProperty('country', value);
    }

    zipCode(value: string) {
        return this.setProperty('zipCode', value);
    }

    asUSAddress() {
        return this.country('United States');
    }
}

class CustomerBuilder extends CeriosBuilder<Customer> {
    static create() {
        return new CustomerBuilder({});
    }

    id(value: string) {
        return this.setProperty('id', value);
    }

    name(value: string) {
        return this.setProperty('name', value);
    }

    address(value: Address) {
        return this.setProperty('address', value);
    }

    phoneNumber(value: string) {
        return this.setProperty('phoneNumber', value);
    }

    addNote(note: string) {
        return this.addToArrayProperty('notes', note);
    }

    withAddress(builderFn: (builder: AddressBuilder) => AddressBuilder & CeriosBrand<Address>) {
        const address = builderFn(AddressBuilder.create()).build();
        return this.setProperty('address', address);
    }

    withAddressDefaults(
        builderFn: (
            builder: AddressBuilder & CeriosBrand<Pick<Address, "city" | "country">>
        ) => AddressBuilder & CeriosBrand<Address>
    ) {
        const address = builderFn(AddressBuilder.createWithDefaults()).build();
        return this.setProperty('address', address);
    }
}

// Usage with full address
const customer = CustomerBuilder.create()
    .id('CUST-001')
    .name('Alice Johnson')
    .withAddress(addr => addr
        .street('123 Main St')
        .city('New York')
        .asUSAddress()
        .zipCode('10001')
    )
    .addNote("VIP customer")
    .addNote("Prefers email contact")
    .phoneNumber('+1-555-0123')
    .build();

// Usage with defaults
const customerWithDefaults = CustomerBuilder.create()
    .id('CUST-002')
    .name('Bob Smith')
    .withAddressDefaults(addr => addr.street('456 Elm St'))
    .addNote("New customer")
    .build();

console.log(customerWithDefaults);
// Output: { id: 'CUST-002', name: 'Bob Smith', address: { street: '456 Elm St', city: 'Othertown', country: 'United States' }, notes: ['New customer'] }

Partial Building for Flexibility

Sometimes you need to build incomplete objects:

// Build partial objects when not all data is available
const partialUser = UserBuilder.create()
    .name("Unknown User")
    .addRole("guest")
    .buildPartial(); // Returns Partial<User>

// This is useful for:
// - Progressive form filling
// - API responses with optional fields
// - Template objects

๐Ÿงช Testing Integration

Cerios Builder is perfect for test data creation:

describe('User Service', () => {
    test('should create user with valid data', () => {
        const userData = UserBuilder.create()
            .withRandomId()
            .name('Test User')
            .email('test@example.com')
            .addRole('tester')
            .age(25)
            .build();

        const result = userService.createUser(userData);

        expect(result.success).toBe(true);
        expect(result.user.name).toBe('Test User');
        expect(result.user.roles).toContain('tester');
    });

    test('should handle users without age', () => {
        const userData = UserBuilder.create()
            .withRandomId()
            .name('Ageless User')
            .email('ageless@example.com')
            .addRole('guest')
            .build();

        const result = userService.createUser(userData);

        expect(result.success).toBe(true);
        expect(result.user.age).toBeUndefined();
        expect(result.user.roles).toContain('guest');
    });
});

๐Ÿ†š Why Cerios Builder?

Traditional Object Creation

// โŒ No compile-time safety
const order = {
    Order: {
        Details: {
            CustomerId: "CUST-001",
            TotalAmount: 299.99,
            // Oops! Forgot required Status field
            ShippingAddress: {
                Street: "123 Main St",
                // Oops! Forgot required City and Country
            }
        }
    }
};

// โŒ Runtime error waiting to happen
orderService.createOrder(order);

With Cerios Builder

// โœ… Type-safe nested property building
const order = OrderRequestBuilder.createWithDefaults()
    .customerId('CUST-001')
    .totalAmount(299.99)
    .shippingStreet('123 Main St')
    .shippingCity('New York')
    .shippingCountry('USA')
    .build(); // โœ… Both compile-time and runtime validation

// โœ… Clear error messages at runtime
try {
    const invalid = OrderRequestBuilder.createWithDefaults()
        .customerId('CUST-002')
        .buildWithoutCompileTimeValidation();
} catch (error) {
    console.error(error.message);
    // "Missing required fields: Order.Details.TotalAmount,
    //  Order.Details.ShippingAddress.Street, ..."
}

Benefits

  • Type-Safe Paths: Dot notation with full IntelliSense support
  • Flexible Validation: Choose compile-time, runtime, both, or neither
  • Dynamic Requirements: Static templates + dynamic required fields
  • Deep Nesting: Handle complex nested structures easily
  • Developer Experience: Better autocomplete and error messages

๐Ÿ“š API Reference

CeriosBuilder

Base class for all builders.

Static Properties

  • requiredTemplate?: RequiredFieldsTemplate<T> - Optional array of required field paths for runtime validation

Instance Methods

  • setProperty<K>(key: K, value: T[K]) - Sets a property and returns a new builder instance
  • setProperties<K>(props: Pick<T, K>) - Sets multiple properties at once and returns a new builder instance
  • setNestedProperty<P>(path: P, value: PathValue<T, P>) - Sets a deeply nested property using dot notation
  • addToArrayProperty<K, V>(key: K, value: V) - Adds a value to an array property and returns a new builder instance
  • setRequiredFields(fields: ReadonlyArray<Path<T>>) - Sets required fields dynamically for this instance

Build Methods

  • build() - Builds with both compile-time and runtime validation (recommended)

    • Compile-time: TypeScript enforces all required properties are set
    • Runtime: Validates all fields in the requiredTemplate
    • Throws error if any required field is missing
  • buildWithoutRuntimeValidation() - Builds with compile-time validation only

    • Compile-time: TypeScript enforces all required properties are set
    • Runtime: No validation (better performance)
    • Use when you trust the type system and need speed
  • buildWithoutCompileTimeValidation() - Builds with runtime validation only

    • Compile-time: No TypeScript enforcement
    • Runtime: Validates all fields in the requiredTemplate
    • Use when building from external data where compile-time checks aren't possible
    • Throws error if any required field is missing
  • buildUnsafe() - Builds without any validation

    • Compile-time: No TypeScript enforcement
    • Runtime: No validation
    • Use only when you're certain the object is valid and need maximum performance
    • Returns potentially incomplete object
  • buildPartial() - Builds a partial object

    • Returns Partial<T> with currently set properties
    • Useful for progressive form filling or template objects

RequiredFieldsTemplate

Type-safe array of paths for defining required fields.

type RequiredFieldsTemplate<T> = ReadonlyArray<Path<T>>;

Example:

static requiredTemplate: RequiredFieldsTemplate<OrderRequest> = [
    'Order.Details.CustomerId',
    'Order.Details.TotalAmount',
    'Order.Details.ShippingAddress.Street',
];

Path

Type utility that generates all valid dot-notation paths for a type.

// For a type like:
type User = {
    profile: {
        name: string;
        email: string;
    };
};

// Path<User> includes:
// 'profile' | 'profile.name' | 'profile.email'

CeriosBrand

Type utility that tracks which properties have been set at the type level.

๐Ÿ› ๏ธ Build Methods Explained

Use build() when you want both compile-time and runtime validation. This is the safest option.

class UserBuilder extends CeriosBuilder<User> {
    static requiredTemplate: RequiredFieldsTemplate<User> = ['id', 'name', 'email'];
    // ... methods
}

const user = UserBuilder.create()
    .id('123')
    .name('John')
    .email('john@example.com')
    .build(); // โœ… TypeScript + runtime validation

// โŒ TypeScript error - missing email
const invalid1 = UserBuilder.create()
    .id('123')
    .name('John')
    .build();

// โŒ Runtime error - missing email
try {
    const invalid2 = UserBuilder.create()
        .id('123')
        .name('John')
        .build();
} catch (error) {
    console.error(error.message); // "Missing required fields: email"
}

Use when:

  • You want maximum safety
  • Building critical business objects
  • You have a required template defined

buildWithoutRuntimeValidation() - Compile-Time Only

Use when you want TypeScript safety but need to skip runtime validation for performance.

const user = UserBuilder.create()
    .id('123')
    .name('John')
    .email('john@example.com')
    .buildWithoutRuntimeValidation(); // โœ… Fast build, no runtime check

// โŒ Still won't compile - TypeScript enforces types
const invalid = UserBuilder.create()
    .id('123')
    .name('John')
    .buildWithoutRuntimeValidation();

Use when:

  • Performance is critical
  • You trust the type system
  • Building many objects in loops

buildWithoutCompileTimeValidation() - Runtime Only

Use when building from external data where compile-time checks aren't possible.

function buildFromAPI(data: any) {
    const builder = UserBuilder.create();

    if (data.id) builder = builder.id(data.id);
    if (data.name) builder = builder.name(data.name);
    if (data.email) builder = builder.email(data.email);

    return builder.buildWithoutCompileTimeValidation(); // Runtime validation
}

// โœ… Valid data passes
const user = buildFromAPI({ id: '123', name: 'John', email: 'john@example.com' });

// โŒ Invalid data throws
try {
    const invalid = buildFromAPI({ id: '123', name: 'John' }); // Missing email
} catch (error) {
    console.error(error.message); // "Missing required fields: email"
}

Use when:

  • Building from API responses
  • Processing user input
  • Working with dynamic data
  • Need runtime validation without TypeScript constraints

buildUnsafe() - No Validation

Use only when you're certain the object is valid and need maximum performance.

const user = UserBuilder.create()
    .id('123')
    .buildUnsafe(); // โš ๏ธ No checks at all

console.log(user); // { id: '123' } - potentially incomplete!

Use when:

  • You're absolutely certain the data is valid
  • Maximum performance is required
  • Building throwaway test objects

buildPartial() - Partial Objects

Use when you need incomplete objects.

const partial = UserBuilder.create()
    .name('Incomplete User')
    .buildPartial(); // Returns Partial<User>

console.log(partial); // { name: 'Incomplete User' }

Use when:

  • Progressive form filling
  • Template objects
  • Partial updates
  • Inspecting builder state

Comparison Table

MethodCompile-Time CheckRuntime CheckPerformanceSafetyUse Case
build()โœ…โœ…MediumHighestProduction code
buildWithoutRuntimeValidation()โœ…โŒFastHighPerformance-critical
buildWithoutCompileTimeValidation()โŒโœ…MediumMediumExternal data
buildUnsafe()โŒโŒFastestLowestTrusted scenarios only
buildPartial()โŒโŒFastN/AIncomplete objects

๐Ÿ’ก Best Practices

  • Use setNestedProperty() for deep structures: Instead of building nested objects separately, use dot notation for better type safety and cleaner code.

  • Define requiredTemplate for runtime validation: When working with external data or complex validation rules, define a required template.

  • Choose the right build method:

    • Default to build() for safety
    • Use buildWithoutRuntimeValidation() for performance
    • Use buildWithoutCompileTimeValidation() for external data
    • Use buildUnsafe() only when absolutely necessary
  • Use setRequiredFields() for conditional requirements: When requirements change based on context (e.g., new vs existing records), use dynamic required fields.

  • Create factory methods: Use static create() and createWithDefaults() methods for better ergonomics:

    static createWithDefaults() {
        return this.create()
            .status('pending')
            .createdAt(new Date());
    }
    
  • Combine with validation libraries: Use runtime validation with libraries like Zod for comprehensive validation:

    const data = builder.buildWithoutCompileTimeValidation();
    const validated = orderSchema.parse(data); // Zod validation
    
  • Keep builders focused: One builder per entity type - don't try to handle multiple unrelated types in one builder.

  • Use type-safe paths: The RequiredFieldsTemplate type ensures you can only specify valid paths:

    static requiredTemplate: RequiredFieldsTemplate<Order> = [
        'Details.CustomerId', // โœ… Valid path
        'Details.InvalidField', // โŒ TypeScript error
    ];
    

๐Ÿ“„ License

MIT ยฉ Cerios

Keywords

typescript

FAQs

Package last updated on 03 Nov 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts