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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@symulate/sdk

v1.4.0

Published

AI-powered frontend-first development toolkit - Build frontends without waiting for backends

Readme

Symulate SDK

AI-powered frontend-first development toolkit. Build your frontend without waiting for a backend.

📚 Full Documentation | 🚀 Quick Start | 💬 Discord Community

Features

  • 🤖 AI-Powered Mocks - Realistic data generated by OpenAI with intelligent caching
  • ⚡ Faker.js Mode - Offline mode for CI/CD pipelines - no AI dependency required
  • 🗄️ Stateful Collections - Full CRUD operations with persistent state
  • 🔗 Interconnected Collections - Database relationships with foreign keys (belongsTo, hasMany, hasOne)
  • 🔒 Type-Safe - Full TypeScript support with automatic type inference
  • 📊 Database Schema Sync - Auto-generate schemas from database tables with dbTable()
  • 🎯 Smart Caching - In-memory, localStorage, or file-based caching strategies
  • 🔄 Seamless Production Switch - One config change to switch from mocks to real backend
  • 📝 OpenAPI Generation - Auto-generate API specs for backend developers
  • ✅ CI/CD Ready - Deterministic Faker.js mode with seed support for testing

Installation

npm install @symulate/sdk

Quick Start

1. Faker Mode (100% Free, No API Key)

Perfect for CI/CD, testing, and basic prototyping.

import { defineEndpoint, configureSymulate, m, type Infer } from "@symulate/sdk";

// Configure - no API key needed!
configureSymulate({
  generateMode: "faker",
  environment: "development",
  backendBaseUrl: "https://api.myapp.com",
});

// Define schema
const UserSchema = m.object({
  id: m.uuid(),
  name: m.person.fullName(),
  email: m.email(),
});

// Infer TypeScript type
type User = Infer<typeof UserSchema>;

// Define endpoint
export const getUsers = defineEndpoint<User[]>({
  path: "/api/users",
  method: "GET",
  schema: UserSchema,
  mock: { count: 5 },
});

// Use it
const users = await getUsers(); // Fully typed as User[]

2. BYOK Mode (Bring Your Own OpenAI Key)

Get AI-powered realistic data with your own OpenAI API key.

configureSymulate({
  openaiApiKey: process.env.OPENAI_API_KEY,
  openaiModel: "gpt-4o-mini", // Optional - specify model (default: gpt-4o-mini)
  generateMode: "ai",
  environment: "development",
  backendBaseUrl: "https://api.myapp.com",
});

Supported models:

  • gpt-4o-mini (default) - $0.15/$0.60 per 1M tokens
  • gpt-4o - $2.50/$10.00 per 1M tokens
  • gpt-4-turbo - $10.00/$30.00 per 1M tokens
  • gpt-4 - $30.00/$60.00 per 1M tokens
  • gpt-3.5-turbo - $0.50/$1.50 per 1M tokens

3. Symulate Platform (Managed Service)

Get cloud hosting, team collaboration, and priority support.

configureSymulate({
  symulateApiKey: process.env.SYMULATE_API_KEY,
  projectId: process.env.SYMULATE_PROJECT_ID,
  environment: "development",
  backendBaseUrl: "https://api.myapp.com",
});

Get your API key at platform.symulate.dev

Stateful Collections

Define collections with full CRUD operations and persistent state:

import { defineCollection, m, type Infer } from '@symulate/sdk';

const ProductSchema = m.object({
  id: m.uuid(),
  name: m.string(),
  price: m.number({ min: 10, max: 1000 }),
  category: m.string(),
  inStock: m.boolean(),
});

export type Product = Infer<typeof ProductSchema>;

export const products = defineCollection<Product>({
  name: "products",
  basePath: "/api/products",
  schema: ProductSchema,
  seedCount: 20,
  seedInstruction: "Generate realistic e-commerce products",
});

// Use it with full CRUD
const { data } = await products.list({ page: 1, limit: 10 });
const newProduct = await products.create({
  name: "New Item",
  price: 29.99,
  category: "Electronics",
  inStock: true
});
await products.update(newProduct.id, { price: 24.99 });
await products.delete(newProduct.id);

Persistence Modes

Configure where collection data is stored:

configureSymulate({
  collections: {
    persistence: {
      mode: "memory", // "memory" (default), "local", or "cloud"
      filePath: ".symulate-data.json" // For Node.js file persistence
    }
  }
});
  • memory - In-memory only (data lost on refresh) - fastest
  • local - localStorage (browser) or file (Node.js) - persists across reloads
  • cloud - Server-side via Supabase (requires API key) - multi-tenant with branch isolation

Interconnected Collections

Define relationships between collections for realistic, interconnected data - just like a real database.

Basic Relationships

import { defineCollection, m, type Infer } from '@symulate/sdk';

// Define Users collection
const UserSchema = m.object({
  id: m.uuid(),
  name: m.person.fullName(),
  email: m.email(),
});

export const users = defineCollection<Infer<typeof UserSchema>>({
  name: "users",
  basePath: "/api/users",
  schema: UserSchema,
  seedCount: 50,
});

// Define Purchases collection with relation to Users
const PurchaseSchema = m.object({
  id: m.uuid(),
  userId: m.uuid(),
  amount: m.number({ min: 10, max: 500 }),
  createdAt: m.date(),
});

// Response schema with joined user data
const PurchaseResponseSchema = m.object({
  id: m.uuid(),
  userId: m.uuid(),
  amount: m.number(),
  createdAt: m.date(),
  userEmail: m.join('user', 'email'),  // Join user's email
  userName: m.join('user', 'name'),    // Join user's name
});

export const purchases = defineCollection({
  name: "purchases",
  basePath: "/api/purchases",
  schema: PurchaseSchema,               // Storage schema
  responseSchema: PurchaseResponseSchema, // Response includes joins
  seedCount: 200,
  relations: {
    user: {
      type: 'belongsTo',
      collection: 'users',
      foreignKey: 'userId',
      references: 'id'
    }
  }
});

// Use it - joins happen automatically
const { data } = await purchases.list();
// Returns: [{ id, userId, amount, createdAt, userEmail, userName }, ...]

Relation Types

belongsTo - One-to-one or many-to-one relationship

relations: {
  user: {
    type: 'belongsTo',
    collection: 'users',
    foreignKey: 'userId',
    references: 'id'
  }
}

hasMany - One-to-many relationship

relations: {
  purchases: {
    type: 'hasMany',
    collection: 'purchases',
    foreignKey: 'userId',
    references: 'id'
  }
}

hasOne - One-to-one relationship

relations: {
  profile: {
    type: 'hasOne',
    collection: 'profiles',
    foreignKey: 'userId',
    references: 'id'
  }
}

Nested Joins

Access deeply nested related data using dot notation:

const OrderSchema = m.object({
  id: m.uuid(),
  purchaseId: m.uuid(),
  status: m.string(),
});

const OrderResponseSchema = m.object({
  id: m.uuid(),
  purchaseId: m.uuid(),
  status: m.string(),
  // Nested join: order → purchase → user
  userEmail: m.join('purchase.user', 'email'),
  userName: m.join('purchase.user', 'name'),
});

export const orders = defineCollection({
  name: "orders",
  schema: OrderSchema,
  responseSchema: OrderResponseSchema,
  relations: {
    purchase: {
      type: 'belongsTo',
      collection: 'purchases',
      foreignKey: 'purchaseId',
      references: 'id'
    }
  }
});

Eager Loading

Control when related collections are loaded and cached:

configureSymulate({
  collections: {
    eagerLoading: true,  // Load all related collections recursively
    persistence: {
      mode: "local"
    }
  }
});
  • eagerLoading: true - Load all related collections recursively when any collection is first accessed. All data is cached together for maximum performance.
  • eagerLoading: false (default) - Only load related collections when they're explicitly requested. More memory efficient.

Foreign Key Integrity

Symulate automatically ensures referential integrity:

  • Collections are seeded in dependency order (referenced collections first)
  • Foreign key values always reference existing records
  • No orphaned references
  • Seed generation respects relation constraints
// Seed order is automatic:
// 1. Users collection seeded first (no dependencies)
// 2. Purchases collection seeded with valid userId references
// 3. Orders collection seeded with valid purchaseId references

How Joins Work

Important: Joined fields are resolved at query time, not stored in the database.

  • Storage schema defines what's persisted (e.g., userId)
  • Response schema defines what's returned (e.g., userId + userEmail)
  • Joins happen when data is requested, keeping data normalized
  • No data duplication - changes to user email automatically reflect in all purchases

Database Table Schemas

Auto-generate collection schemas from your database tables - full type safety with zero manual mapping.

Basic Usage

import { dbTable, defineCollection } from '@symulate/sdk';
import type { Database } from './types/database'; // Generated by Supabase

// Auto-generate schema from database table
const UserSchema = dbTable('users');

export const users = defineCollection<Database['public']['Tables']['users']['Row']>({
  name: "users",
  basePath: "/api/users",
  schema: UserSchema,
  seedCount: 100,
});

Selective Fields

Only include specific fields or exclude sensitive ones:

// Only include specific fields
const PublicUserSchema = dbTable('users', {
  only: ['id', 'name', 'email', 'avatar']
});

// Exclude sensitive fields
const SafeUserSchema = dbTable('users', {
  exclude: ['password_hash', 'reset_token', 'internal_notes']
});

Extend with Virtual Fields

Add computed or virtual fields that don't exist in the database:

const UserSchema = dbTable('users', {
  exclude: ['password_hash']
}).extend({
  // Virtual fields
  fullName: m.string(),
  isActive: m.boolean(),
  lastLoginRelative: m.string(),
  // Computed fields
  purchaseCount: m.number({ min: 0, max: 100 }),
});

export type User = Infer<typeof UserSchema>;

Combined Approach

Mix selective fields with extensions:

const ProductSchema = dbTable('products', {
  only: ['id', 'name', 'price', 'category_id']
}).extend({
  categoryName: m.join('category', 'name'),  // Join with relations
  inStock: m.boolean(),
  rating: m.number({ min: 1, max: 5 }),
});

Type Safety

TypeScript automatically catches database schema changes:

// If you remove 'email' from database, this will show TypeScript error
const schema = dbTable('users', {
  only: ['id', 'name', 'email']  // ❌ Error: 'email' does not exist on type 'users'
});

// If database type changes from string to number
const user = await users.getById('123');
user.age.toUpperCase(); // ❌ Error: Property 'toUpperCase' does not exist on type 'number'

Nullability & Optional Fields

Nullable database columns automatically become optional TypeScript fields:

// Database schema:
// users table
//   id: uuid (not null)
//   name: text (not null)
//   bio: text (nullable)
//   avatar: text (nullable)

const UserSchema = dbTable('users');

// Generated TypeScript type:
type User = {
  id: string;
  name: string;
  bio?: string;      // Optional because nullable in DB
  avatar?: string;   // Optional because nullable in DB
}

Mock Data Generation:

  • Optional fields have a 25% chance of being undefined
  • 75% chance they'll have realistic generated values
  • Applies to both Faker.js and AI generation modes
  • Tests your frontend's handling of missing data

Working with Relations

Combine dbTable with relations for fully typed, interconnected collections:

const UserSchema = dbTable('users');
const PurchaseSchema = dbTable('purchases');

const PurchaseResponseSchema = dbTable('purchases').extend({
  userEmail: m.join('user', 'email'),
  userName: m.join('user', 'name'),
});

export const purchases = defineCollection({
  name: "purchases",
  schema: PurchaseSchema,
  responseSchema: PurchaseResponseSchema,
  relations: {
    user: {
      type: 'belongsTo',
      collection: 'users',
      foreignKey: 'user_id',
      references: 'id'
    }
  }
});

Benefits

Zero Manual Mapping - Schema syncs automatically with database ✅ Type Safety - TypeScript errors when database changes ✅ Selective Fields - Only expose what you need ✅ Virtual Fields - Extend with computed properties ✅ Nullability Handling - Optional fields map correctly ✅ Realistic Testing - Optional fields sometimes undefined (25% chance)

Configuration Options

configureSymulate({
  // API Keys (choose one):
  openaiApiKey: string,          // BYOK: Your OpenAI API key
  openaiModel: string,           // BYOK: OpenAI model (default: "gpt-4o-mini")
  symulateApiKey: string,        // Platform API key (sym_live_xxx)

  // Optional:
  projectId: string,             // Project ID (required for platform)
  demoApiKey: string,            // Demo API key (sym_demo_xxx) for pre-generated demos
  backendBaseUrl: string,        // Real backend URL for production
  environment: "development" | "production",  // Default: "development"
  cacheEnabled: boolean,         // Enable caching (default: true)
  persistentCache: boolean,      // Enable localStorage in browser (default: false)
  generateMode: "ai" | "faker" | "auto",  // Default: "auto"
  fakerSeed: number,             // Seed for deterministic Faker.js
  language: string,              // Language for AI-generated data (e.g., "en", "de")
  regenerateOnConfigChange: boolean,  // Regenerate when endpoint config changes (default: true)

  // Collections:
  collections: {
    branch: string,              // Branch name for data isolation
    eagerLoading: boolean,       // Load related collections recursively (default: false)
    persistence: {
      mode: "memory" | "local" | "cloud",
      filePath: string,          // File path for Node.js (default: ".symulate-data.json")
    },
  },
});

Schema Builder

Basic Types

const schema = m.object({
  id: m.uuid(),
  text: m.string(),
  count: m.number({ min: 0, max: 100 }),
  active: m.boolean(),
  createdAt: m.date(),
  email: m.email(),
  website: m.url(),
  phone: m.phoneNumber(),
});

Faker.js Fields

const person = m.object({
  fullName: m.person.fullName(),
  firstName: m.person.firstName(),
  lastName: m.person.lastName(),
  jobTitle: m.person.jobTitle(),

  userName: m.internet.userName(),
  avatar: m.internet.avatar(),

  street: m.location.street(),
  city: m.location.city(),
  state: m.location.state(),
  zipCode: m.location.zipCode(),
  country: m.location.country(),

  productName: m.commerce.productName(),
  department: m.commerce.department(),
  price: m.commerce.price(),

  word: m.lorem.word(),
  sentence: m.lorem.sentence(),
  paragraph: m.lorem.paragraph(),
});

Nested Objects & Arrays

const user = m.object({
  id: m.uuid(),
  name: m.person.fullName(),
  address: m.object({
    street: m.location.street(),
    city: m.location.city(),
  }),
  tags: m.array(m.string()),
});

Mock Configuration

mock: {
  count: 10,                    // Generate array of 10 items
  instruction: "Generate premium users with verified accounts",
  delay: 500,                   // Simulate network latency (ms)
  metadata: {                   // Contextual data for AI
    industry: "Technology",
    region: "North America"
  }
}

Parameters

Define typed parameters for your endpoints:

const getUserById = defineEndpoint({
  path: "/api/users/:id",
  method: "GET",
  params: [
    {
      name: "id",
      location: "path",          // "path", "query", "header", or "body"
      required: true,
      schema: m.uuid(),
    },
  ],
  schema: UserSchema,
});

// Usage
const user = await getUserById({ id: "123e4567-..." });

CI/CD Example

Use Faker mode for fast, deterministic tests:

// jest.setup.ts
import { configureSymulate } from "@symulate/sdk";

configureSymulate({
  generateMode: "faker",
  fakerSeed: 12345,              // Same data every test run
  environment: "development",
});
# .github/workflows/test.yml
- name: Run tests
  run: npm test
  env:
    NODE_ENV: development

CLI Commands

Collections Management

# List all collections
npx symulate collections list

# Delete specific collection
npx symulate collections delete -n products

# Pre-generate all collections
npx symulate collections pregenerate

Cache Management

# View cached data
npx symulate cache

# Clear all cache
npx symulate regenerate

# Clear specific hash
npx symulate regenerate --hash abc123xyz

OpenAPI Generation

# Generate OpenAPI spec
npx symulate openapi -o api-spec.json

Database Schema Import

# Import database schema from platform
npx symulate import-schema

# Import specific schema by name
npx symulate import-schema --schema-name auth

Database Schema Sync

Reference backend database types directly:

import type { User, Post } from "./types/database";

const getUser = defineEndpoint<User>({
  path: "/api/users/:id",
  method: "GET",
  schema: m.object({
    id: m.db("users.id"),
    name: m.db("users.name", "German names"),
    email: m.db("users.email"),
    created_at: m.db("users.created_at"),
  }),
});

Learn more about Database Schema Sync

Tenant Demos

Create isolated demo environments with pre-generated AI data:

// Configure SDK with demo API key
configureSymulate({
  demoApiKey: 'sym_demo_your_key_here',
  projectId: 'proj_xxx',
});

// All calls now use pre-generated demo data
const products = await getProducts();       // Pre-generated
const users = await userCollection.list();  // Pre-generated

Learn more about Tenant Demos

Error Handling

Define custom error responses:

const getUser = defineEndpoint({
  path: "/api/users/:id",
  method: "GET",
  schema: UserSchema,
  errors: [
    {
      code: 404,
      description: "User not found",
      schema: m.object({
        error: m.object({
          message: m.string(),
          code: m.string(),
        }),
      }),
      failNow: true, // Simulate this error in mock mode
    },
  ],
});

Type Validation

Automatic runtime validation in production mode:

import { TypeValidationError } from "@symulate/sdk";

try {
  const users = await getUsers();
} catch (error) {
  if (error instanceof TypeValidationError) {
    console.error("Expected:", error.expected);
    console.error("Received:", error.received);
  }
}

Environment Switching

# Development (uses mocks)
NODE_ENV=development npm run dev

# Production (uses real backend)
NODE_ENV=production npm run build

Or override per-endpoint:

export const getNewFeature = defineEndpoint({
  path: "/api/new-feature",
  method: "GET",
  mode: "mock", // Force mock mode even in production
  schema: FeatureSchema,
});

Documentation

Community & Support

License

MIT License - see LICENSE for details