AI-powered frontend-first development toolkit - Build frontends without waiting for backends
npm install @symulate/sdkAI-powered frontend-first development toolkit. Build your frontend without waiting for a backend.
- š¤ 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
``bash`
npm install @symulate/sdk
Perfect for CI/CD, testing, and basic prototyping.
`typescript
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
// Define endpoint
export const getUsers = defineEndpoint
path: "/api/users",
method: "GET",
schema: UserSchema,
mock: { count: 5 },
});
// Use it
const users = await getUsers(); // Fully typed as User[]
`
Get AI-powered realistic data with your own OpenAI API key.
`typescript`
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
Define collections with full CRUD operations and persistent state:
`typescript
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
export const products = defineCollection
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);
`
Configure where collection data is stored:
`typescript`
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
Define relationships between collections for realistic, interconnected data - just like a real database.
`typescript
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
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 }, ...]
`
belongsTo - One-to-one or many-to-one relationship
`typescript`
relations: {
user: {
type: 'belongsTo',
collection: 'users',
foreignKey: 'userId',
references: 'id'
}
}
hasMany - One-to-many relationship
`typescript`
relations: {
purchases: {
type: 'hasMany',
collection: 'purchases',
foreignKey: 'userId',
references: 'id'
}
}
hasOne - One-to-one relationship
`typescript`
relations: {
profile: {
type: 'hasOne',
collection: 'profiles',
foreignKey: 'userId',
references: 'id'
}
}
Access deeply nested related data using dot notation:
`typescript
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'
}
}
});
`
Control when related collections are loaded and cached:
`typescript`
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.
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
`typescript`
// 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
Important: Joined fields are resolved at query time, not stored in the database.
- Storage schema defines what's persisted (e.g., userId)userId
- Response schema defines what's returned (e.g., + userEmail)
- Joins happen when data is requested, keeping data normalized
- No data duplication - changes to user email automatically reflect in all purchases
Auto-generate collection schemas from your database tables - full type safety with zero manual mapping.
`typescript
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
name: "users",
basePath: "/api/users",
schema: UserSchema,
seedCount: 100,
});
`
Only include specific fields or exclude sensitive ones:
`typescript
// 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']
});
`
Add computed or virtual fields that don't exist in the database:
`typescript
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
`
Mix selective fields with extensions:
`typescript`
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 }),
});
TypeScript automatically catches database schema changes:
`typescript
// 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'
`
Nullable database columns automatically become optional TypeScript fields:
`typescript
// 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
Combine dbTable with relations for fully typed, interconnected collections:
`typescript
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'
}
}
});
`
ā
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)
`typescript
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")
},
},
});
`
`typescript`
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(),
});
`typescript
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(),
});
`
`typescript`
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()),
});
`typescript`
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"
}
}
Define typed parameters for your endpoints:
`typescript
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-..." });
`
Use Faker mode for fast, deterministic tests:
`typescript
// jest.setup.ts
import { configureSymulate } from "@symulate/sdk";
configureSymulate({
generateMode: "faker",
fakerSeed: 12345, // Same data every test run
environment: "development",
});
`
`yaml`.github/workflows/test.yml
- name: Run tests
run: npm test
env:
NODE_ENV: development
`bashList all collections
npx symulate collections list
$3
`bash
View cached data
npx symulate cacheClear all cache
npx symulate regenerateClear specific hash
npx symulate regenerate --hash abc123xyz
`$3
`bash
Generate OpenAPI spec
npx symulate openapi -o api-spec.json
`$3
`bash
Import database schema from platform
npx symulate import-schemaImport specific schema by name
npx symulate import-schema --schema-name auth
`Database Schema Sync
Reference backend database types directly:
`typescript
import type { User, Post } from "./types/database";const getUser = defineEndpoint({
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:
`typescript
// 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
`Error Handling
Define custom error responses:
`typescript
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:
`typescript
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
`bash
Development (uses mocks)
NODE_ENV=development npm run devProduction (uses real backend)
NODE_ENV=production npm run build
`Or override per-endpoint:
`typescript
export const getNewFeature = defineEndpoint({
path: "/api/new-feature",
method: "GET",
mode: "mock", // Force mock mode even in production
schema: FeatureSchema,
});
``- Full Documentation
- Quick Start Guide
- Collections Guide
- Interconnected Collections
- Database Table Schemas
- Tenant Demos Guide
- CLI Reference
- Database Schema Sync
- Discord Community
- GitHub Discussions
- GitHub Issues
MIT License - see LICENSE for details