npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@noony-serverless/type-builder

v1.2.1

Published

Ultra-fast TypeScript builder library with auto-detection

Readme

@noony-serverless/type-builder

Ultra-fast TypeScript builder library with auto-detection. Build objects with zero boilerplate.

npm version TypeScript Test Coverage Tests License: MIT

Installation

npm install @noony-serverless/type-builder

Quick Start

import { builder, pipe, Maybe, lens } from '@noony-serverless/type-builder';
import { z } from 'zod';

// Zod schema - auto-detected with validation
const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

const createUser = builder(UserSchema);

const user = createUser().withName('John Doe').withEmail('[email protected]').build(); // ✅ Validated automatically

Unified Imports

Everything is available from a single import - no more subpath imports needed!

// ✅ Single import for everything
import {
  // Core builders
  builder,
  builderAsync,

  // Functional programming
  pipe,
  compose,
  createImmutableBuilder,
  partialApply,
  curriedBuilder,

  // Monads
  Maybe,
  Either,

  // Optics
  lens,
  prism,
  prop,
  path,
} from '@noony-serverless/type-builder';

// ❌ No more multiple imports needed
// import { pipe } from '@noony-serverless/type-builder';
// import { Maybe } from '@noony-serverless/type-builder/monads';
// import { lens } from '@noony-serverless/type-builder/optics';

Usage

Three Builder Modes

1. Zod Mode - Runtime validation (~100k ops/sec)

const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});
const createUser = builder(UserSchema);

2. Class Mode - Domain objects with methods (~300k ops/sec)

class User {
  name!: string;
  email!: string;
  constructor(data: Partial<User>) {
    Object.assign(this, data);
  }
}
const createUser = builder(User);

3. Interface Mode - Maximum performance (~400k ops/sec)

interface User {
  name: string;
  email: string;
}
const createUser = builder<User>(['name', 'email']);

Mixed Paradigm Examples

OOP + Functional Programming

import { builder, pipe, createImmutableBuilder } from '@noony-serverless/type-builder';

// OOP builder for validation
const UserSchema = z.object({ name: z.string(), email: z.string().email() });
const createUser = builder(UserSchema);

// Functional processing
const processUser = pipe(
  (user: any) => ({ ...user, processed: true }),
  (user: any) => ({ ...user, timestamp: Date.now() })
);

const user = createUser().withName('John').withEmail('[email protected]').build();

const processedUser = processUser(user);

Monads + Optics

import { Maybe, Either, lens, prism } from '@noony-serverless/type-builder';

const user = { name: 'John', email: '[email protected]' };

// Safe access with Maybe
const nameLens = lens(prop('name'));
const maybeName = Maybe.of(nameLens.view(user));

// Error handling with Either
const emailPrism = prism(prop('email'));
const eitherEmail = emailPrism
  .getOption(user)
  .map((email) => (email.includes('@') ? Either.right(email) : Either.left('Invalid email')))
  .getOrElse(Either.left('No email'));

Development

Setup

npm install
npm run build

Testing

npm run test              # Run tests
npm run test:watch        # Watch mode
npm run test:coverage     # Coverage report

Building

npm run build             # Build library
npm run dev               # Watch mode

Performance Testing

npm run benchmark         # Run benchmarks
npm run clinic            # Clinic.js analysis

API Reference

Core Functions

  • builder<T>(input) - Create builder with auto-detection
  • builderAsync<T>(input) - Async builder for Zod schemas
  • clearPools() - Clear object pools
  • getPoolStats() - Get performance stats

Builder Methods

  • .withX(value) - Set property value
  • .build() - Build final object (sync)
  • .buildAsync() - Build final object (async)

Performance

| Mode | Ops/Sec | Time/Op | Memory | Use Case | | --------- | ------- | ------- | -------- | -------------- | | Interface | 420,000 | 2.4μs | 60 bytes | Internal DTOs | | Class | 310,000 | 3.2μs | 80 bytes | Domain models | | Zod | 105,000 | 9.5μs | 90 bytes | API validation |

Documentation


🤔 Why Should You Care?

Let me paint you a picture. You're building an API. You need to:

  • ✅ Validate user input (hello, Zod!)
  • ✅ Build domain objects with methods
  • ✅ Transform DTOs at lightning speed
  • ✅ Keep everything type-safe

The old way? Write a manual builder class for every single type. Boilerplate city. 😴

The new way? One function. Auto-detection. Zero config. Fast as hell.

// One function to rule them all
const create = builder(anything); // Zod schema? Class? Interface? We got you.

🎨 TypeScript Autocomplete

Your IDE will autocomplete everything. The builder generates typed .withXYZ() methods automatically from your schemas, classes, and interfaces. Type-safe, with zero runtime cost.

✅ Full Type Inference

Zod Schema:

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

const createUser = builder(UserSchema);

// ✅ IDE autocompletes: .withId(), .withName(), .withEmail()
// ✅ TypeScript validates parameter types
const user = createUser()
  .withId(1) // Knows this is number
  .withName('John') // Knows this is string
  .withEmail('[email protected]') // Knows this is string
  .build();

Class:

class Product {
  id!: number;
  name!: string;
  price!: number;
}

const create = builder(Product);

// ✅ IDE autocompletes: .withId(), .withName(), .withPrice()
const product = create().withId(1).withName('Laptop').withPrice(999).build(); // Returns Product instance

Interface:

interface Order {
  id: string;
  total: number;
  status: 'pending' | 'completed';
}

const create = builder<Order>(['id', 'total', 'status']);

// ✅ IDE autocompletes: .withId(), .withTotal(), .withStatus()
const order = create()
  .withId('ORD-1')
  .withTotal(99.99)
  .withStatus('pending') // TypeScript knows: 'pending' | 'completed'
  .build();

❌ Type-Safe Errors

TypeScript catches mistakes at compile time:

// ❌ Error: Property 'withFoo' does not exist
createUser().withFoo('bar');

// ❌ Error: Argument of type 'string' not assignable to 'number'
createUser().withId('not-a-number');

// ❌ Error: Type '"invalid"' not assignable to '"pending" | "completed"'
createOrder().withStatus('invalid');

🚀 Zero Runtime Cost

All types are compile-time only. They disappear when compiled to JavaScript:

// TypeScript (with types)
const create = builder<User>(UserSchema);
const user = create().withName('John').build();

// JavaScript (after compilation - identical!)
const create = builder(UserSchema);
const user = create().withName('John').build();

No performance impact. No bundle size increase. Pure autocomplete magic.

🎯 Works with ANY Type

The type inference is 100% dynamic. Create a new interface, class, or schema—autocomplete just works:

// Brand new type you just created
const BlogPostSchema = z.object({
  title: z.string(),
  content: z.string(),
  author: z.object({ name: z.string(), email: z.string() }),
  tags: z.array(z.string()),
  publishedAt: z.date(),
});

const createPost = builder(BlogPostSchema);

// ✅ IDE immediately suggests all methods:
// .withTitle(), .withContent(), .withAuthor(), .withTags(), .withPublishedAt()

const post = createPost()
  .withTitle('My Post')
  .withContent('Hello world')
  .withAuthor({ name: 'John', email: '[email protected]' })
  .withTags(['typescript', 'builder'])
  .withPublishedAt(new Date())
  .build();

Zero configuration. No manual type definitions. It just works. 🎉

See typed-usage.ts for 10+ comprehensive examples.


🎯 The Magic: Auto-Detection

Here's where things get fun. You pass something to builder(), and it automatically figures out what to do:

🔮 Three Modes, Zero Config

import builder from '@noony-serverless/type-builder';
import { z } from 'zod';

// 1️⃣ Zod Schema → Validated builders (~100k ops/sec)
const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});
const createUser = builder(UserSchema); // 🔍 "Ah, that's a Zod schema!"

// 2️⃣ Class → Methods + type safety (~300k ops/sec)
class Product {
  name!: string;
  price!: number;
  getTax() {
    return this.price * 0.1;
  }
}
const createProduct = builder(Product); // 🔍 "Ah, that's a class constructor!"

// 3️⃣ Interface → Blazing fast DTOs (~400k ops/sec)
interface Order {
  id: string;
  total: number;
}
const createOrder = builder<Order>(['id', 'total']); // 🔍 "Ah, explicit keys!"

How does it work?

Under the hood, we use runtime type detection:

  • Zod schemas have .parse() and .safeParse() methods
  • Classes have Function.prototype and constructor properties
  • Arrays are... well, arrays

It's not rocket science, but it saves you a ton of typing. 🎯


📚 The Three Modes (Deep Dive)

Let's break down each mode and when to use it.

1️⃣ Zod Mode - Validated Builders

TL;DR: External input? Use Zod. Type safety + runtime validation = ❤️

import builder from '@noony-serverless/type-builder';
import { z } from 'zod';

const CreateUserDTO = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  name: z.string().min(2),
});

const validateUser = builder(CreateUserDTO);

// In your API endpoint
app.post('/api/users', async (req, res) => {
  try {
    const userData = validateUser()
      .withEmail(req.body.email)
      .withPassword(req.body.password)
      .withName(req.body.name)
      .build(); // ✅ Throws if validation fails

    const user = await db.users.create(userData);
    res.json(user);
  } catch (error) {
    res.status(400).json({ error: error.errors });
  }
});

How it works:

  1. You pass a Zod schema
  2. We detect it's a Zod schema (check for .parse() method)
  3. Extract keys from the schema shape
  4. Generate .withX() methods for each key
  5. On .build(), we call schema.parse(data) to validate

Performance: ~100,000 ops/sec (10μs per operation)

When to use:

  • ✅ API request validation
  • ✅ External data (user input, file uploads, etc.)
  • ✅ Anywhere you need runtime validation

Deep Dive: Schema Detection

function isZodSchema(input: any): boolean {
  return (
    input &&
    typeof input === 'object' &&
    typeof input.parse === 'function' &&
    typeof input.safeParse === 'function' &&
    input._def !== undefined
  );
}

We check for Zod's unique signature: parse(), safeParse(), and the internal _def property. This works with all Zod schema types (objects, arrays, unions, etc.).


2️⃣ Class Mode - Domain Objects with Methods

TL;DR: Need methods? Use classes. Full OOP, zero compromises.

class Order {
  id!: string;
  items!: OrderItem[];
  status!: OrderStatus;
  total!: number;

  constructor(data: Partial<Order>) {
    Object.assign(this, data);
  }

  // Business logic lives here
  canBeCancelled(): boolean {
    return this.status === 'pending';
  }

  calculateTax(rate: number): number {
    return this.total * rate;
  }

  applyDiscount(percent: number): void {
    this.total *= 1 - percent / 100;
  }
}

const createOrder = builder(Order);

const order = createOrder()
  .withId('ORD-001')
  .withItems([{ sku: 'LAPTOP', qty: 1 }])
  .withStatus('pending')
  .withTotal(999.99)
  .build();

// Use your methods!
if (order.canBeCancelled()) {
  order.applyDiscount(10);
}

console.log('Tax:', order.calculateTax(0.08)); // $79.99

How it works:

  1. You pass a class constructor
  2. We detect it's a class (check for .prototype.constructor)
  3. Create a proxy instance with an empty object
  4. Call the constructor to capture property assignments
  5. Generate .withX() methods for discovered properties
  6. On .build(), call new YourClass(data) to create the real instance

Performance: ~300,000 ops/sec (3.3μs per operation)

When to use:

  • ✅ Domain models with business logic
  • ✅ Rich objects with methods
  • ✅ OOP-style architecture
  • ✅ When you need instanceof checks

Pro tip: Your constructor should accept a Partial<YourClass> object and use Object.assign(this, data). This makes it work seamlessly with the builder.

Deep Dive: Class Property Detection

We use a Proxy set trap to capture property assignments in the constructor:

const capturedKeys = new Set<string>();
const proxyHandler = {
  set(target: any, prop: string | symbol, value: any) {
    if (typeof prop === 'string' && prop !== 'constructor') {
      capturedKeys.add(prop);
    }
    target[prop] = value;
    return true;
  },
};

const proxy = new Proxy({}, proxyHandler);
YourClass.call(proxy, {}); // Captures all `this.x = y` assignments

This way, we don't need decorators, reflect-metadata, or any TypeScript magic. Pure runtime goodness.


3️⃣ Interface Mode - Maximum Performance

TL;DR: Internal data? Use interfaces. 400k+ ops/sec, minimal memory.

interface UserDTO {
  id: number;
  name: string;
  email: string;
  createdAt: Date;
}

const createUserDTO = builder<UserDTO>(['id', 'name', 'email', 'createdAt']);

// In your data transformation pipeline
app.get('/api/users', async (req, res) => {
  const users = await db.users.findMany();

  const dtos = users.map((user) =>
    createUserDTO()
      .withId(user.id)
      .withName(user.name)
      .withEmail(user.email)
      .withCreatedAt(user.createdAt)
      .build()
  );

  res.json(dtos); // ⚡ Lightning fast
});

How it works:

  1. You pass an array of property keys
  2. We generate .withX() methods for each key
  3. On .build(), return the accumulated data object
  4. No validation, no class instantiation, just raw speed

Performance: ~400,000 ops/sec (2.5μs per operation)

When to use:

  • ✅ Internal DTOs (Data Transfer Objects)
  • ✅ High-throughput transformations
  • ✅ When validation already happened upstream
  • ✅ Maximum performance scenarios

Why so fast?

  • No validation overhead
  • No class instantiation
  • No method copying
  • Just plain object creation with object pooling

🏎️ Performance: Fast as Hell

Let's talk numbers. Here's how we stack up:

| Mode | Ops/Sec | Time/Op | Use Case | | ------------- | -------- | ------- | ------------------------------ | | Interface | ~400,000 | 2.5μs | Internal DTOs, max speed | | Class | ~300,000 | 3.3μs | Domain models with methods | | Zod | ~100,000 | 10μs | API validation, external input |

Memory:

  • 60-90 bytes per object (thanks to object pooling)
  • Zero blocking operations
  • GC-friendly (minimal allocations with pooling)

Real-world impact:

// Processing 10,000 API requests/sec?
// Interface mode: 25ms total
// Class mode: 33ms total
// Zod mode: 100ms total

// You're spending MORE time on database queries than object building. As it should be. 👍

Pro tip: Use interface mode for internal transformations, Zod mode for API boundaries. Best of both worlds.


🎓 Real-World Examples

Let's wire it up with real code you'd actually write.

Example 1: Express API with Validation

import express from 'express';
import builder from '@noony-serverless/type-builder';
import { z } from 'zod';

const app = express();

// Define your validation schema
const CreateUserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  name: z.string().min(2).max(50),
});

const validateCreateUser = builder(CreateUserSchema);

app.post('/api/users', async (req, res) => {
  try {
    // Validate and build in one step
    const userData = validateCreateUser()
      .withEmail(req.body.email)
      .withPassword(req.body.password)
      .withName(req.body.name)
      .build(); // ✅ Throws ZodError if invalid

    // Hash password
    const hashedPassword = await bcrypt.hash(userData.password, 10);

    // Save to database
    const user = await db.users.create({
      ...userData,
      password: hashedPassword,
    });

    res.status(201).json({ id: user.id, email: user.email, name: user.name });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return res.status(400).json({
        error: 'Validation failed',
        details: error.errors,
      });
    }
    res.status(500).json({ error: 'Internal server error' });
  }
});

Why this rocks:

  • ✅ Type-safe all the way through
  • ✅ Clear validation errors
  • ✅ No manual builder classes
  • ✅ Easy to test

Example 2: Domain-Driven Design with Classes

class Order {
  id!: string;
  customerId!: string;
  items!: OrderItem[];
  status!: OrderStatus;
  total!: number;
  createdAt!: Date;

  constructor(data: Partial<Order>) {
    Object.assign(this, data);
    this.status = this.status || 'pending';
    this.createdAt = this.createdAt || new Date();
  }

  // Business logic
  canBeCancelled(): boolean {
    return ['pending', 'processing'].includes(this.status);
  }

  cancel(): void {
    if (!this.canBeCancelled()) {
      throw new Error(`Cannot cancel order with status: ${this.status}`);
    }
    this.status = 'cancelled';
  }

  calculateTotal(): number {
    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }

  applyDiscount(percent: number): void {
    if (percent < 0 || percent > 100) {
      throw new Error('Discount must be between 0 and 100');
    }
    this.total *= 1 - percent / 100;
  }
}

const createOrder = builder(Order);

// In your service layer
class OrderService {
  async createOrder(customerId: string, items: OrderItem[]): Promise<Order> {
    const order = createOrder()
      .withId(generateId())
      .withCustomerId(customerId)
      .withItems(items)
      .withTotal(items.reduce((sum, i) => sum + i.price * i.quantity, 0))
      .build();

    // Domain logic is in the class
    if (order.total > 1000) {
      order.applyDiscount(10); // 10% off for big orders
    }

    await this.orderRepository.save(order);
    return order;
  }

  async cancelOrder(orderId: string): Promise<void> {
    const order = await this.orderRepository.findById(orderId);
    order.cancel(); // ✅ Business logic in the domain model
    await this.orderRepository.save(order);
  }
}

Why this rocks:

  • ✅ Rich domain models with behavior
  • ✅ Business logic lives in the class (not scattered in services)
  • ✅ Easy to test (just test the class methods)
  • ✅ Type-safe builders for free

Example 3: High-Performance Data Transformation

// Database entity (what comes from DB)
interface UserEntity {
  id: number;
  email: string;
  first_name: string;
  last_name: string;
  created_at: Date;
  updated_at: Date;
  password_hash: string;
  is_active: boolean;
}

// API DTO (what you send to clients)
interface UserDTO {
  id: number;
  email: string;
  name: string;
  createdAt: string;
  isActive: boolean;
}

const createUserDTO = builder<UserDTO>(['id', 'email', 'name', 'createdAt', 'isActive']);

// In your API endpoint
app.get('/api/users', async (req, res) => {
  const users = await db.users.findMany({ where: { is_active: true } });

  // Transform 10,000 users in ~25ms
  const dtos = users.map((user) =>
    createUserDTO()
      .withId(user.id)
      .withEmail(user.email)
      .withName(`${user.first_name} ${user.last_name}`)
      .withCreatedAt(user.created_at.toISOString())
      .withIsActive(user.is_active)
      .build()
  );

  res.json(dtos);
});

Why this rocks:

  • ✅ Blazing fast (400k+ ops/sec)
  • ✅ Type-safe transformation
  • ✅ Clear, readable code
  • ✅ No manual mapping logic

Example 4: Async Validation (Non-Blocking)

import { builderAsync } from '@noony-serverless/type-builder';
import { z } from 'zod';

const UserSchema = z.object({
  email: z.string().email(),
  username: z.string().min(3),
});

const createUser = builderAsync(UserSchema);

// Non-blocking validation (great for high-concurrency APIs)
app.post('/api/users', async (req, res) => {
  try {
    const user = await createUser()
      .withEmail(req.body.email)
      .withUsername(req.body.username)
      .buildAsync(); // ✅ Async validation (won't block event loop)

    await db.users.create(user);
    res.status(201).json(user);
  } catch (error) {
    res.status(400).json({ error: error.errors });
  }
});

When to use async:

  • Node.js servers handling thousands of concurrent requests
  • When validation might take > 1ms (complex Zod schemas)
  • You want to keep the event loop free

📖 API Reference

Main Functions

builder<T>(input, explicitKeys?): BuilderFunction<T>

Create a builder with automatic type detection.

TL;DR:

const create = builder(ZodSchema | Class | ['keys']);

Parameters:

  • input: Zod schema, class constructor, or array of keys
  • explicitKeys?: Optional explicit keys (for classes if auto-detection fails)

Returns: A function that creates a new builder instance

Examples:

// Zod
const createUser = builder(UserSchema);

// Class
const createProduct = builder(Product);

// Interface
const createOrder = builder<Order>(['id', 'total']);

// Class with explicit keys (rare, but possible)
const createThing = builder(Thing, ['prop1', 'prop2']);

builderAsync<T>(input, explicitKeys?): AsyncBuilderFunction<T>

Create an async builder (Zod only, for non-blocking validation).

TL;DR:

const create = builderAsync(ZodSchema);
const obj = await create().withX(1).buildAsync();

Parameters:

  • input: Zod schema (only Zod is supported for async)
  • explicitKeys?: Optional explicit keys

Returns: A function that creates a new async builder instance

Throws: Error if you pass anything other than a Zod schema

Examples:

const createUser = builderAsync(UserSchema);

const user = await createUser().withEmail('[email protected]').withName('John').buildAsync(); // ✅ Non-blocking validation

Builder Instance Methods

Every builder instance (the thing you get from createUser()) has:

.withX(value): BuilderInstance

Set a property value. Chainable.

const user = createUser()
  .withName('John') // Sets `name` property
  .withEmail('[email protected]') // Sets `email` property
  .build();

Note: The method name is generated from the property name:

  • name.withName()
  • email.withEmail()
  • firstName.withFirstName()

.build(): T

Build the final object (sync).

const user = createUser().withName('John').build(); // Returns validated/constructed object

Throws:

  • Zod mode: ZodError if validation fails
  • Class mode: Any error from your constructor
  • Interface mode: Never throws

.buildAsync(): Promise<T>

Build the final object (async, Zod only).

const user = await createUser().withName('John').buildAsync(); // Non-blocking validation

Only available when using builderAsync().


Utility Functions

clearPools(): void

Clear all object pools (release all pooled builders).

import { clearPools } from '@noony-serverless/type-builder';

clearPools(); // Releases memory from pooled objects

When to use: During tests, or if you're building millions of objects and want to free memory.


getPoolStats(): PoolStats

Get performance stats for all pools.

import { getPoolStats } from '@noony-serverless/type-builder';

const stats = getPoolStats();
console.log(stats);
// {
//   totalPools: 3,
//   totalObjects: 150,
//   totalHits: 10000,
//   totalMisses: 150,
//   averageHitRate: 0.985
// }

resetPoolStats(): void

Reset performance counters (hits/misses).

import { resetPoolStats } from '@noony-serverless/type-builder';

resetPoolStats(); // Resets hit/miss counters

🧠 Advanced Topics

Object Pooling (How We're So Fast)

You might be thinking: "How the hell are you building 400k objects per second?"

Answer: Object pooling. Here's the secret sauce:

// Behind the scenes:
const builderPool = new BuilderPool<UserBuilder>(() => new UserBuilder());

// When you call createUser():
const builder = builderPool.get(); // Reuses a pooled builder (if available)
builder.reset(); // Clears previous data
return builder; // You build your object

// After you're done, we return it to the pool automatically
builderPool.release(builder); // Ready for next request

Why this matters:

  • Without pooling: Create 400k objects → GC runs → pause → slowdown
  • With pooling: Reuse 100 objects → GC rarely runs → consistent performance

Pool stats:

import { getPoolStats } from '@noony-serverless/type-builder';

const stats = getPoolStats();
console.log(stats.averageHitRate); // ~98.5% (most builders are reused)

Deep Dive: Pool Configuration

We use a custom FastObjectPool with:

  • Max size: 1000 objects per pool (configurable)
  • Hit rate tracking: Monitors cache efficiency
  • Auto-reset: Clears builder state between uses
  • Memory-friendly: Only grows when needed
class BuilderPool<T> {
  private pool: T[] = [];
  private hits = 0;
  private misses = 0;

  get(): T {
    if (this.pool.length > 0) {
      this.hits++;
      return this.pool.pop()!; // Reuse existing
    }
    this.misses++;
    return this.createFn(); // Create new if pool is empty
  }

  release(obj: T): void {
    this.resetFn(obj); // Clear data
    if (this.pool.length < this.maxSize) {
      this.pool.push(obj); // Return to pool
    }
  }
}

In production, hit rates are typically 95-99%, meaning you're reusing objects constantly.


TypeScript Tips

Type Inference

The builder automatically infers types from your input:

const UserSchema = z.object({
  name: z.string(),
  age: z.number(),
});

const createUser = builder(UserSchema);

// TypeScript knows:
createUser().withName('John'); // ✅ OK
createUser().withName(123); // ❌ Error: Expected string
createUser().withFoo('bar'); // ❌ Error: Property 'withFoo' doesn't exist

Partial vs Full Objects

Builders always return complete objects, not partials:

const user = createUser()
  .withName('John')
  // .withAge() - forgot this!
  .build();

// user.age is undefined at runtime, but TypeScript thinks it's a number
// This is a limitation of the fluent API pattern

Pro tip: Use Zod mode for APIs (it'll catch missing fields). Use class mode for internal code (where you control the inputs).


Performance Tuning

When Speed Matters

  1. Use interface mode for internal transformations
  2. Use class mode when you need methods
  3. Use Zod mode only at API boundaries
// API boundary: Validate
const validateInput = builder(CreateUserSchema);

// Internal: Fast transformation
interface UserEntity {
  id: number;
  name: string;
}
const createEntity = builder<UserEntity>(['id', 'name']);

// Domain: Business logic
class User {
  notify() {
    /* ... */
  }
}
const createDomain = builder(User);

Benchmarking Your Own Code

Want to see how fast your builders are?

import { builder, getPoolStats, resetPoolStats } from '@noony-serverless/type-builder';

const createUser = builder(UserSchema);

// Warmup (fill the pool)
for (let i = 0; i < 100; i++) {
  createUser().withName('test').withEmail('[email protected]').build();
}

resetPoolStats();

// Benchmark
const start = performance.now();
for (let i = 0; i < 100000; i++) {
  createUser().withName('John').withEmail('[email protected]').build();
}
const end = performance.now();

console.log(`Time: ${end - start}ms`);
console.log(`Ops/sec: ${100000 / ((end - start) / 1000)}`);
console.log(`Pool hit rate: ${getPoolStats().averageHitRate * 100}%`);

🚀 Migration Guide

From Manual Builder Classes

Before:

class UserBuilder {
  private name?: string;
  private email?: string;

  withName(name: string): this {
    this.name = name;
    return this;
  }

  withEmail(email: string): this {
    this.email = email;
    return this;
  }

  build(): User {
    if (!this.name || !this.email) {
      throw new Error('Missing required fields');
    }
    return new User(this.name, this.email);
  }
}

const createUser = () => new UserBuilder();

After:

import builder from '@noony-serverless/type-builder';
import { z } from 'zod';

const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

const createUser = builder(UserSchema);

You just deleted 20 lines of boilerplate. You're welcome. 🎉


From Generic Builders

Before:

class GenericBuilder<T> {
  private data: Partial<T> = {};

  with<K extends keyof T>(key: K, value: T[K]): this {
    this.data[key] = value;
    return this;
  }

  build(): T {
    return this.data as T; // 😬 Type assertion
  }
}

const createUser = () => new GenericBuilder<User>();
createUser().with('name', 'John').with('email', '[email protected]').build();

After:

const createUser = builder<User>(['name', 'email']);
createUser().withName('John').withEmail('[email protected]').build();

Benefits:

  • ✅ Better autocomplete (.withName() vs .with('name'))
  • ✅ No type assertions
  • ✅ 10x faster (object pooling)

🐛 Troubleshooting

"Unable to detect builder type"

Problem:

const create = builder(myThing);
// Error: Unable to detect builder type. Expected Zod schema, class constructor, or array of keys.

Solution: Pass explicit keys or check your input:

// If it's supposed to be a class:
console.log(typeof myThing); // Should be 'function'
console.log(myThing.prototype); // Should exist

// If it's supposed to be Zod:
console.log(myThing.parse); // Should be a function

// Workaround: Pass explicit keys
const create = builder(myThing, ['key1', 'key2']);

"Property 'withX' does not exist"

Problem:

const create = builder(MyClass);
create().withFoo('bar'); // Error: Property 'withFoo' does not exist

Solution: Make sure your class actually has a foo property:

class MyClass {
  foo!: string; // ✅ Property exists

  constructor(data: Partial<MyClass>) {
    Object.assign(this, data);
  }
}

If your class doesn't assign properties in the constructor, we can't detect them. Use explicit keys:

const create = builder(MyClass, ['foo', 'bar']);

Zod Validation Errors

Problem:

const user = createUser().withEmail('invalid-email').build(); // Throws ZodError

Solution: Catch and handle Zod errors:

import { z } from 'zod';

try {
  const user = createUser().withEmail('invalid-email').build();
} catch (error) {
  if (error instanceof z.ZodError) {
    console.error('Validation failed:', error.errors);
    // [{ path: ['email'], message: 'Invalid email' }]
  }
}

🎨 Best Practices

1. Validate at Boundaries

// ✅ GOOD: Validate external input
const validateUser = builder(UserSchema);

app.post('/api/users', (req) => {
  const user = validateUser().withEmail(req.body.email).build(); // ✅ Throws if invalid

  // Now it's safe to use internally
  processUser(user);
});
// ❌ BAD: Validating internal data (slow)
function processUser(user: User) {
  const validated = validateUser().withEmail(user.email).build(); // ❌ Wasteful - already validated upstream
}

2. Use Interface Mode for Speed

// ✅ GOOD: Fast internal transformations
interface UserDTO {
  id: number;
  name: string;
}
const createDTO = builder<UserDTO>(['id', 'name']);

function mapToDTO(user: User): UserDTO {
  return createDTO().withId(user.id).withName(user.name).build(); // ⚡ 400k ops/sec
}
// ❌ BAD: Using Zod for internal data (slow)
const DTOSchema = z.object({ id: z.number(), name: z.string() });
const createDTO = builder(DTOSchema);

function mapToDTO(user: User): UserDTO {
  return createDTO().withId(user.id).withName(user.name).build(); // 🐌 100k ops/sec (4x slower for no reason)
}

3. Use Class Mode for Business Logic

// ✅ GOOD: Domain logic in the class
class Order {
  total!: number;

  applyDiscount(percent: number) {
    this.total *= 1 - percent / 100;
  }
}

const order = createOrder().withTotal(100).build();
order.applyDiscount(10); // ✅ Business logic where it belongs
// ❌ BAD: Business logic scattered in services
interface Order {
  total: number;
}

class OrderService {
  applyDiscount(order: Order, percent: number) {
    order.total *= 1 - percent / 100; // ❌ Logic in the wrong place
  }
}

📊 Benchmarks

Run the benchmarks yourself:

npm run benchmark

Or check out the live dashboard:

npm run serve
# Opens http://localhost:8080/builder_visual_dashboard.html

Results on M1 MacBook Pro:

🚀 Interface Builder: 420,000 ops/sec (2.4μs per operation)
🚀 Class Builder:     310,000 ops/sec (3.2μs per operation)
🚀 Zod Builder:       105,000 ops/sec (9.5μs per operation)

💾 Memory per object: 60-90 bytes
♻️  Pool hit rate:    98.5%
🚫 Blocking ops:      0

📚 Documentation

Complete Documentation Suite

This project follows the Diataxis framework for structured, high-quality documentation:

| Document | Type | Purpose | Start Here If... | | -------------------------------------------- | ------------- | ------------------------------------- | --------------------------------- | | 📖 Documentation Hub | Index | Navigate all docs | You want an overview | | 🎯 Explanation | Understanding | Learn WHY and HOW it works | You're evaluating the library | | 🎓 Tutorial | Learning | Step-by-step hands-on guide | You're learning to use it | | 🔧 How-To Guide | Tasks | Practical recipes for common problems | You need to solve a specific task | | 📋 API Reference | Information | Complete API documentation | You need to look up an API detail |

Quick Links

Learning Path:

  1. Why Use This Library? - Understand the problem
  2. Getting Started Tutorial - Build your first builder
  3. Common Recipes - Solve real-world problems
  4. API Reference - Look up function signatures

Popular Topics:

Additional Resources


🤝 Contributing

Found a bug? Have a feature idea? PRs welcome!

git clone https://github.com/your-org/typescript-bulder-lib.git
cd typescript-bulder-lib
npm install
npm run test

Running tests:

npm run test           # Run tests
npm run test:watch     # Watch mode
npm run test:coverage  # Generate coverage report (96.88% and counting!)

📄 License

MIT © [Your Name]


🙏 Acknowledgments

Built with:

  • Zod - Amazing TypeScript validation
  • Vitest - Blazing fast test runner
  • tsup - TypeScript bundler

Inspired by the builder pattern, but without the boilerplate.


Questions? Issues? Ideas?

Open an issue on GitHub or hit me up on Twitter @yourhandle.

Now go build something awesome! 🚀