@symulate/sdk
v1.4.0
Published
AI-powered frontend-first development toolkit - Build frontends without waiting for backends
Maintainers
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/sdkQuick 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 tokensgpt-4o- $2.50/$10.00 per 1M tokensgpt-4-turbo- $10.00/$30.00 per 1M tokensgpt-4- $30.00/$60.00 per 1M tokensgpt-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) - fastestlocal- localStorage (browser) or file (Node.js) - persists across reloadscloud- 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 referencesHow 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: developmentCLI 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 pregenerateCache Management
# View cached data
npx symulate cache
# Clear all cache
npx symulate regenerate
# Clear specific hash
npx symulate regenerate --hash abc123xyzOpenAPI Generation
# Generate OpenAPI spec
npx symulate openapi -o api-spec.jsonDatabase Schema Import
# Import database schema from platform
npx symulate import-schema
# Import specific schema by name
npx symulate import-schema --schema-name authDatabase 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-generatedError 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 buildOr override per-endpoint:
export const getNewFeature = defineEndpoint({
path: "/api/new-feature",
method: "GET",
mode: "mock", // Force mock mode even in production
schema: FeatureSchema,
});Documentation
- Full Documentation
- Quick Start Guide
- Collections Guide
- Interconnected Collections
- Database Table Schemas
- Tenant Demos Guide
- CLI Reference
- Database Schema Sync
Community & Support
License
MIT License - see LICENSE for details
