Stateful Collections (CRUD)
Full CRUD operations with persistent state management and server-side pagination
Overview
Stateful Collections provide a complete backend-like experience during frontend development. Unlike traditional endpoints that simulate responses, collections maintain state across requests with full CRUD operations, server-side pagination, and persistent storage.
Key Features
- ✅ Full CRUD Operations
- ✅ Server-Side Pagination
- ✅ Persistent State
- ✅ Branch Isolation
- ✅ AI-Generated Seeds
- ✅ Read-After-Write Consistency
Use Cases
- • Admin dashboards
- • E-commerce management
- • User management systems
- • Multi-tenant demos
- • Data-heavy applications
How It Works
Collections store data in Supabase via edge functions, providing true server-side operations. When you create, update, or delete items, changes are persisted and immediately reflected in subsequent requests.
Quick Start
import { defineCollection, m, type Infer } from '@symulate/sdk';
// Define schema
const ProductSchema = m.object({
id: m.uuid(),
name: m.commerce.productName(),
price: m.commerce.price(),
inStock: m.boolean(),
category: m.commerce.department(),
createdAt: m.date(),
});
export type Product = Infer<typeof ProductSchema>;
// Define collection
export const products = defineCollection<Product>({
name: 'products',
basePath: '/api/products',
schema: ProductSchema,
seedCount: 20, // Initial items to generate
seedInstruction: 'Generate realistic e-commerce products',
});That's it! The collection is now ready to use with full CRUD operations.
CRUD Operations
List Items
Retrieve paginated lists of items with optional filtering and sorting.
// Basic list
const response = await products.list();
console.log(response.data); // Product[]
console.log(response.pagination); // { page: 1, limit: 20, total: 20, totalPages: 1 }
// With pagination
const page2 = await products.list({ page: 2, limit: 10 });
// With sorting
const sorted = await products.list({
sortBy: 'price',
sortOrder: 'desc'
});
// With filtering
const filtered = await products.list({
filter: { category: 'Electronics' }
});Get Single Item
Retrieve a specific item by ID.
const product = await products.get('product-id-123');
console.log(product.name); // Fully typed!Create Item
Create new items with automatic ID and timestamp generation.
const newProduct = await products.create({
name: 'Wireless Mouse',
price: 29.99,
inStock: true,
category: 'Electronics'
// id, createdAt auto-generated
});
console.log(newProduct.id); // Auto-generated UUIDAuto-Generated Fields
Fields like id, createdAt, and updatedAt are automatically generated. You don't need to provide them when creating items.
Update Item (Partial)
Update specific fields while preserving others.
// Only update specified fields
const updated = await products.update('product-id-123', {
price: 24.99, // Only price changes
inStock: false
});
// Other fields remain unchanged
console.log(updated.name); // Original name preservedDelete Item
Permanently remove items from the collection.
await products.delete('product-id-123');
// Item is permanently removed
const response = await products.list();
// product-id-123 no longer in the listConfiguration
| Property | Type | Description |
|---|---|---|
| name | string | Collection identifier (required) |
| basePath | string | Base API path (required) |
| schema | BaseSchema<T> | Data schema (required) |
| seedCount | number? | Initial items to generate (default: 20) |
| seedInstruction | string? | AI instruction for seed data generation |
| operations | object? | Enable/disable specific CRUD operations |
Selective Operations
You can disable specific CRUD operations if needed:
export const products = defineCollection<Product>({
name: 'products',
basePath: '/api/products',
schema: ProductSchema,
operations: {
list: true, // Enable list
get: true, // Enable get
create: true, // Enable create
update: false, // Disable update
delete: false, // Disable delete
},
});Branch Isolation
Collections support branch isolation for multi-tenant demos, testing environments, and customer-specific data separation.
Global Configuration
configureSymulate({
collections: {
branch: 'customer-acme' // All operations use this branch
}
});
// All collection operations now use 'customer-acme' branch
await products.list(); // Only returns products from 'customer-acme'
await products.create(); // Creates product in 'customer-acme'Use Cases
Customer Demos
Create isolated datasets for each customer demo without interference.
// Demo for Customer A
configureSymulate({ collections: { branch: 'demo-customer-a' } });
// Demo for Customer B
configureSymulate({ collections: { branch: 'demo-customer-b' } });Environment Separation
Separate data for development, staging, and production environments.
const branch = {
development: 'dev',
staging: 'staging',
production: 'main'
}["production"];
configureSymulate({ collections: { branch } });Testing Isolation
Create temporary datasets for testing without affecting main data.
// Set up test data
configureSymulate({ collections: { branch: 'test-suite-1' } });
await products.create({ name: 'Test Product' });
// Run tests...
// Clean up
npx symulate collections delete -b test-suite-1Persistence Modes
Collections support three persistence modes for different use cases, from fast in-memory prototyping to full server-side state management.
Configuration
configureSymulate({
collections: {
persistence: {
mode: 'local' // 'memory', 'local', or 'cloud'
}
}
});Mode Comparison
In-memory only - Fastest performance for quick prototyping
- ✅ Fastest performance
- ✅ Perfect for quick prototyping
- ❌ Data lost on page refresh
Use for: Quick tests, demos where clean state is desired
localStorage (browser) or filesystem (Node.js) - Data survives refreshes
- ✅ Data survives page refreshes
- ✅ No backend required
- ✅ Works in both browser and Node.js
- ⚠️ ~5-10MB browser storage limit
Browser: Uses localStorage API
Node.js: Saves to .symulate-data.json file
Use for: Local development, offline testing, prototypes
Server-side persistence via Supabase - True backend simulation
- ✅ True server-side state
- ✅ Multi-tenant with branch isolation
- ✅ Shared across devices/sessions
- ✅ Server-side pagination
Requires: Symulate API key and project ID
Use for: Realistic backend simulation, customer demos, team collaboration
Example Configurations
Local Development with Browser Persistence
configureSymulate({
collections: {
persistence: {
mode: 'local' // Survives page refresh
}
}
});Production-like with Cloud Persistence
configureSymulate({
symulateApiKey: 'sym_live_xxx',
projectId: 'proj_xxx',
collections: {
persistence: {
mode: 'cloud' // Server-side with branch isolation
},
branch: 'customer-acme'
}
});Collections in Tenant Demos
Collections work seamlessly with Tenant Demos - isolated demo environments with pre-generated data that don't consume AI tokens during presentations. This is the recommended approach for customer demos, sales presentations, and trade shows.
Why Use Tenant Demos with Collections?
Traditional collections regenerate data on-demand using AI, which can be slow and inconsistent during live demos. Tenant demos pre-generate all collection data beforehand, ensuring instant responses and zero token consumption during presentations.
Traditional Collections vs. Tenant Demo Collections
| Aspect | Development Mode | Tenant Demo Mode |
|---|---|---|
| Configuration | symulateApiKey + projectId | demoApiKey only |
| Data Generation | On-demand (first request) | Pre-generated (before demo) |
| Token Usage | Consumes tokens on generation | Zero tokens during demo |
| Response Time | ~2-10s first request | Instant (<100ms) |
| Data Customization | seedInstruction in code | Custom instructions per demo |
| CRUD Operations | ✅ Full CRUD | ✅ Full CRUD (isolated) |
| Use Case | Local development | Customer demos, sales |
Using Collections with Demo API Keys
When you configure the SDK with a demoApiKey, all collection operations automatically route to pre-generated demo data:
import { configureSymulate } from '@symulate/sdk';
import { products, users } from './models';
// Configure with demo API key
configureSymulate({
demoApiKey: 'sym_demo_abc123xyz789def456ghi',
projectId: 'proj_xxx', // Still required for routing
});
// All collection operations now use pre-generated demo data
const productList = await products.list();
// ✓ Returns pre-generated products (instant response)
const newProduct = await products.create({
name: 'Demo Product',
price: 99.99
});
// ✓ Creates in demo data (isolated from other demos)
const userList = await users.list();
// ✓ Returns pre-generated users with custom instructionsHow It Works
When a demoApiKey is present, the SDK automatically routes all collection and endpoint calls to the tenant demo's pre-generated data instead of generating new data. This ensures consistent, fast responses perfect for live demonstrations.
Custom Instructions for Demo Collections
Tenant demos support a three-level custom instruction hierarchy, allowing you to generate customer-specific, localized, or industry-specific data for each demo.
1 General Instruction (Demo-Wide)
Applies to all collections and endpoints in the demo. Set when creating the demo in Platform UI.
Platform UI → Create Demo
General Instruction: "German automotive industry, luxury vehicles, Munich location"
Effect: All collections (products, users, orders) will use this context unless overridden.
2 Collection-Specific Instruction
Overrides general instruction for a specific collection. Set via Platform UI.
Collection: products
General: "German automotive industry, luxury vehicles"
Override: "Premium German car parts: BMWs, Mercedes, Audis, high-end accessories"
Effect: Products collection uses the specific instruction, while users and orders still use the general one.
3 Endpoint-Specific Instruction
Overrides both for a specific endpoint. Set via Platform UI.
Endpoint: GET /api/dashboard/stats
Collection: "Premium German car parts"
Override: "Show exceptional sales growth for Q4 2024, 45% increase YoY"
Effect: Only this endpoint uses the specific sales narrative, other endpoints use their respective instructions.
Instruction Priority
More specific instructions always override less specific ones. The hierarchy is: Endpoint-Specific > Collection-Specific > General. This allows maximum flexibility while maintaining consistency.
Creating Tenant Demos via Platform UI
The easiest way to create and manage tenant demos is through the Symulate Platform web interface:
- 1. Navigate to Dashboard → Demos
- 2. Click "Create Demo"
- 3. Fill in demo details:
- • Demo name (e.g., "Customer Demo - ACME Corp")
- • Description (optional)
- • General custom instruction (optional)
- 4. Configure collections:
- • Select which collections to include
- • Set collection-specific custom instructions
- • Adjust seed counts if needed
- 5. Configure endpoints:
- • Select which endpoints to include
- • Set endpoint-specific custom instructions
- 6. Generate demo data - The platform creates a generation job and pre-generates all data
- 7. Copy demo API key and use in your SDK configuration
Example SDK Configuration:
// After creating demo via Platform UI, use the generated API key:
configureSymulate({
demoApiKey: 'sym_demo_abc123xyz789def456ghi', // From Platform UI
projectId: 'proj_xxx',
});
// All calls now use pre-generated demo data
await products.list(); // Instant response with custom dataDemo Workflow Example
Before Customer Demo
Create tenant demo via Platform UI with customer-specific instructions:
- • Name: "Demo - Toyota Berlin"
- • Instruction: "Japanese automotive company, Toyota vehicles, Berlin Germany location"
- • Collections: products (50), users (20), orders (100)
Configure Application
configureSymulate({
demoApiKey: 'sym_demo_toyota_berlin_xxx',
projectId: 'proj_xxx',
});During Demo
All operations are instant and use customer-specific data:
- ✓ Products show Toyota-specific automotive parts
- ✓ Users have German names and Berlin addresses
- ✓ CRUD operations work in isolated demo environment
- ✓ Zero latency, zero token consumption
After Demo (Optional)
Regenerate with updated instructions via Platform UI:
- • Go to Dashboard → Demos
- • Click refresh icon on your demo
- • Update general instruction if needed
- • Trigger re-generation
Switching Between Development and Demo Mode
You can easily switch between development mode (with on-demand generation) and demo mode (with pre-generated data) by changing the SDK configuration:
// Development mode - generate data on-demand
configureSymulate({
symulateApiKey: 'sym_live_xxx',
projectId: 'proj_xxx',
collections: {
persistence: { mode: 'cloud' }
}
});
// Demo mode - use pre-generated data
configureSymulate({
demoApiKey: 'sym_demo_abc123xyz',
projectId: 'proj_xxx',
// collections config ignored when demoApiKey is present
});
// You can use environment variables for dynamic switching
const isDemoMode = process.env.DEMO_MODE === 'true';
configureSymulate(
isDemoMode
? {
demoApiKey: process.env.DEMO_API_KEY,
projectId: process.env.PROJECT_ID,
}
: {
symulateApiKey: process.env.SYMULATE_API_KEY,
projectId: process.env.PROJECT_ID,
collections: { persistence: { mode: 'cloud' } }
}
);Best Practices for Tenant Demo Collections
- ✓ Pre-generate before presentations - Don't rely on on-demand generation during live demos
- ✓ Use custom instructions - Tailor data to customer's industry, location, and use case
- ✓ Test CRUD operations - Ensure create/update/delete work as expected in demo environment
- ✓ Monitor generation jobs - Check status before demo to ensure all data is ready
- ✓ Create separate demos per customer - Avoid mixing data from different customers
- ✓ Regenerate periodically - Keep demo data fresh and relevant
Learn More
For more details on creating and managing tenant demos, see the Tenant Demos documentation.
Query Parameter Customization
Customize query parameter names and routing for pagination, sorting, and filtering to match your backend API conventions.
Default Behavior
By default, collections automatically add these query parameters to list operations:
- Pagination:
page,limit - Sorting:
sortBy,sortOrder - Filtering:
filter
// These work out of the box
await products.list({ page: 1, limit: 20, sortBy: 'price', sortOrder: 'desc' });
// GET /api/products?page=1&limit=20&sortBy=price&sortOrder=descRole-Based Parameter Customization
Use the params array with semantic roles to customize parameter names and routing:
defineCollection({
name: 'products',
basePath: '/api/products',
schema: ProductSchema,
operations: {
list: {
params: [
// Custom pagination parameter names
{ name: 'pageNumber', location: 'query', role: 'pagination.page', schema: m.number() },
{ name: 'pageSize', location: 'query', role: 'pagination.limit', schema: m.number() },
// Custom sorting parameter names
{ name: 'orderBy', location: 'query', role: 'sort.field', schema: m.string() },
{ name: 'direction', location: 'query', role: 'sort.order', schema: m.string() },
// Custom filter parameter name
{ name: 'search', location: 'query', role: 'filter', schema: m.object({ category: m.string() }) },
]
}
}
});
// Internal API stays the same
await products.list({ page: 1, limit: 20, sortBy: 'price', sortOrder: 'desc' });
// But generates URL with custom parameter names:
// GET /api/products?pageNumber=1&pageSize=20&orderBy=price&direction=descAvailable Parameter Roles
| Role | Purpose | Default Name |
|---|---|---|
| pagination.page | Current page number | page |
| pagination.limit | Items per page | limit |
| sort.field | Field to sort by | sortBy |
| sort.order | Sort direction (asc/desc) | sortOrder |
| filter | Filter criteria | filter |
Parameter Locations
Control WHERE parameters are sent using the location property:
defineCollection({
name: 'products',
operations: {
list: {
params: [
// Send in query string (default)
{ name: 'page', location: 'query', role: 'pagination.page', schema: m.number() },
// Send in request body (switches to POST)
{ name: 'filter', location: 'body', role: 'filter', schema: m.object({ category: m.string() }) },
// Send as HTTP header
{ name: 'X-Sort-By', location: 'header', role: 'sort.field', schema: m.string() },
]
}
}
});
// Usage
await products.list({ page: 1, filter: { category: 'Electronics' }, sortBy: 'price' });
// POST /api/products?page=1
// Headers: X-Sort-By: price
// Body: { "filter": { "category": "Electronics" } }Available Locations
Send as URL query string parameter (e.g., ?page=1&limit=20)
Send in request body. Automatically switches method from GET to POST.
Send as HTTP header (useful for custom headers like X-Page, X-Sort-By)
Disabling Query Parameters
You can disable automatic query parameters globally or per-operation:
Global Disable
configureSymulate({
collections: {
disableQueryParams: true // Disables for ALL collections
}
});
// No query parameters added
await products.list({ page: 1, limit: 20 });
// GET /api/products (no query params)Per-Operation Disable
defineCollection({
name: 'products',
operations: {
list: {
disableQueryParams: true // Disables only for this operation
}
}
});Common API Conventions
Laravel / Spring Boot Style
params: [
{ name: 'page', location: 'query', role: 'pagination.page', schema: m.number() },
{ name: 'per_page', location: 'query', role: 'pagination.limit', schema: m.number() },
{ name: 'sort', location: 'query', role: 'sort.field', schema: m.string() },
{ name: 'order', location: 'query', role: 'sort.order', schema: m.string() },
]ASP.NET Core Style
params: [
{ name: 'pageNumber', location: 'query', role: 'pagination.page', schema: m.number() },
{ name: 'pageSize', location: 'query', role: 'pagination.limit', schema: m.number() },
{ name: 'orderBy', location: 'query', role: 'sort.field', schema: m.string() },
{ name: 'sortOrder', location: 'query', role: 'sort.order', schema: m.string() },
]Django Style
params: [
{ name: 'page', location: 'query', role: 'pagination.page', schema: m.number() },
{ name: 'page_size', location: 'query', role: 'pagination.limit', schema: m.number() },
{ name: 'ordering', location: 'query', role: 'sort.field', schema: m.string() },
{ name: 'search', location: 'query', role: 'filter', schema: m.string() },
]Automatic POST Conversion
When any parameter uses location: 'body', the request method automatically changes from GET to POST. This is because GET requests with request bodies are non-standard and not supported by most APIs.
Production Mode Only
Query parameter customization currently applies only to production mode (environment: 'production') when calling your real backend API.
CLI Management
Manage collections via CLI commands for inspection, deletion, and pre-generation.
List Collections
# List all collections
npx symulate collections list
# List collections for specific branch
npx symulate collections list -b customer-acmeExample Output:
Found 2 collection(s):
• products
└─ main (20 items)
└─ customer-acme (15 items)
• users
└─ main (10 items)Delete Collections
# Delete specific collection
npx symulate collections delete -n products
# Delete from specific branch
npx symulate collections delete -n products -b dev
# Delete all collections (requires confirmation)
npx symulate collections delete --allSafety Confirmation
When using --all, you must type "DELETE ALL" to confirm deletion across all branches.
Pre-generate Collections
Generate all defined collections before starting your application.
# Pre-generate on main branch
npx symulate collections pregenerate
# Pre-generate for specific branch
npx symulate collections pregenerate -b demoExample Output:
Found 3 collection(s) to generate
Generating "products"...
✅ Generated 20 items
Generating "users"...
✅ Generated 10 items
Generating "orders"...
✅ Generated 15 itemsCustom Instructions for Multi-Tenant Demos
Custom instructions for data generation are ONLY available in Tenant Demos (created via Platform UI). Development pre-generation uses the seedInstruction defined in your collection schemas.
For customer-specific, localized, or industry-specific demo data, create a Tenant Demo with custom instructions via the Platform UI.
Pagination & Filtering
Pagination
Server-side pagination returns only the requested page, not all data.
const response = await products.list({
page: 2,
limit: 10
});
console.log(response.pagination);
// {
// page: 2,
// limit: 10,
// total: 50, // Total items across all pages
// totalPages: 5 // Total number of pages
// }Sorting
// Sort by price (descending)
const expensive = await products.list({
sortBy: 'price',
sortOrder: 'desc'
});
// Sort by name (ascending)
const alphabetical = await products.list({
sortBy: 'name',
sortOrder: 'asc'
});Filtering
// Filter by category
const electronics = await products.list({
filter: { category: 'Electronics' }
});
// Multiple filters
const filtered = await products.list({
filter: {
category: 'Electronics',
inStock: true
}
});Combined Query
const result = await products.list({
page: 1,
limit: 20,
sortBy: 'price',
sortOrder: 'desc',
filter: {
category: 'Electronics',
inStock: true
}
});Collection Metadata & Aggregates
Use m.collectionsMeta to include pagination metadata and aggregate functions in your response schemas. These fields are automatically calculated and populated by the server.
Pagination Metadata
Include pagination information in your list operation responses:
const ListResponseSchema = m.object({
data: m.array(ProductSchema),
pagination: m.object({
page: m.collectionsMeta.page(),
limit: m.collectionsMeta.limit(),
total: m.collectionsMeta.total(),
totalPages: m.collectionsMeta.totalPages(),
}),
});
export const products = defineCollection({
name: 'products',
schema: ProductSchema,
operations: {
list: {
responseSchema: ListResponseSchema
}
}
});
// Response automatically includes pagination metadata
const response = await products.list({ page: 2, limit: 20 });
console.log(response.pagination);
// {
// page: 2,
// limit: 20,
// total: 150,
// totalPages: 8
// }Aggregate Functions
Calculate statistics across all items in the collection (not just the current page):
Available Aggregates
m.collectionsMeta.avg(fieldName)- Average valuem.collectionsMeta.sum(fieldName)- Sum of all valuesm.collectionsMeta.min(fieldName)- Minimum valuem.collectionsMeta.max(fieldName)- Maximum valuem.collectionsMeta.count(fieldName, value)- Count items matching value
const ProductSchema = m.object({
id: m.uuid(),
name: m.string(),
price: m.number(),
category: m.string(),
inStock: m.boolean(),
});
const ProductStatsSchema = m.object({
data: m.array(ProductSchema),
pagination: m.object({
page: m.collectionsMeta.page(),
limit: m.collectionsMeta.limit(),
total: m.collectionsMeta.total(),
totalPages: m.collectionsMeta.totalPages(),
}),
stats: m.object({
averagePrice: m.collectionsMeta.avg("price"),
totalValue: m.collectionsMeta.sum("price"),
cheapest: m.collectionsMeta.min("price"),
mostExpensive: m.collectionsMeta.max("price"),
inStockCount: m.collectionsMeta.count("inStock", true),
}),
});
export const products = defineCollection({
name: 'products',
schema: ProductSchema,
seedCount: 100,
operations: {
list: {
responseSchema: ProductStatsSchema
}
}
});
// Aggregates calculated across ALL 100 products, not just current page
const response = await products.list({ page: 1, limit: 10 });
console.log(response.stats);
// {
// averagePrice: 89.99,
// totalValue: 8999.00,
// cheapest: 9.99,
// mostExpensive: 499.99,
// inStockCount: 67
// }Advanced Example: Analytics Dashboard
Combine metadata and aggregates for powerful analytics:
const OrderSchema = m.object({
id: m.uuid(),
customerId: m.string(),
total: m.number(),
status: m.string("pending, processing, completed, cancelled"),
createdAt: m.date(),
});
const OrderAnalyticsSchema = m.object({
orders: m.array(OrderSchema),
pagination: m.object({
page: m.collectionsMeta.page(),
limit: m.collectionsMeta.limit(),
total: m.collectionsMeta.total(),
totalPages: m.collectionsMeta.totalPages(),
}),
analytics: m.object({
// Revenue metrics
totalRevenue: m.collectionsMeta.sum("total"),
averageOrderValue: m.collectionsMeta.avg("total"),
largestOrder: m.collectionsMeta.max("total"),
// Order status counts
completedOrders: m.collectionsMeta.count("status", "completed"),
pendingOrders: m.collectionsMeta.count("status", "pending"),
cancelledOrders: m.collectionsMeta.count("status", "cancelled"),
}),
});
export const orders = defineCollection({
name: 'orders',
schema: OrderSchema,
seedCount: 500,
operations: {
list: {
responseSchema: OrderAnalyticsSchema
}
}
});
// Get page 1 with complete analytics
const dashboard = await orders.list({ page: 1, limit: 25 });
console.log(`Showing ${dashboard.orders.length} of ${dashboard.pagination.total} orders`);
console.log(`Total Revenue: $${dashboard.analytics.totalRevenue.toFixed(2)}`);
console.log(`Average Order: $${dashboard.analytics.averageOrderValue.toFixed(2)}`);
console.log(`Completed: ${dashboard.analytics.completedOrders}`);
console.log(`Pending: ${dashboard.analytics.pendingOrders}`);Key Features
- ✅ Aggregates across entire collection
- ✅ Auto-updated on create/update/delete
- ✅ Works with all persistence modes
- ✅ Zero configuration required
- ✅ Full TypeScript type inference
Common Use Cases
- • Dashboard statistics
- • E-commerce analytics
- • Reporting endpoints
- • Admin panels
- • Real-time metrics
Collections vs. Traditional Endpoints
| Feature | Traditional Endpoints | Stateful Collections |
|---|---|---|
| Purpose | Simulated responses | Full backend simulation |
| State | ❌ No persistence | ✅ Server-side persistence |
| CRUD | ❌ Static mock data | ✅ Full CRUD operations |
| Pagination | ❌ Client-side (all data loaded) | ✅ Server-side (only requested page) |
| Consistency | ❌ Data resets on reload | ✅ Data persists across requests |
| Branch Support | ❌ No | ✅ Multi-tenant isolation |
| Best For | Simple GET requests | Admin dashboards, data management |
When to Use Each
- Use Endpoints: Simple read-only data fetching, static content, landing pages
- Use Collections: Admin panels, CRUD operations, data management, multi-tenant demos
Complete Examples
E-Commerce Product Management
import { defineCollection, m, type Infer } from '@symulate/sdk';
const ProductSchema = m.object({
id: m.uuid(),
name: m.commerce.productName(),
description: m.lorem.paragraph(),
price: m.commerce.price(),
category: m.commerce.department(),
inStock: m.boolean(),
imageUrl: m.internet.avatar(),
createdAt: m.date(),
updatedAt: m.date(),
});
export type Product = Infer<typeof ProductSchema>;
export const products = defineCollection<Product>({
name: 'products',
basePath: '/api/products',
schema: ProductSchema,
seedCount: 50,
seedInstruction: 'Generate diverse tech products and gadgets',
});
// Usage in your app
async function productDashboard() {
// List with pagination
const page1 = await products.list({ page: 1, limit: 20 });
// Create new product
const newProduct = await products.create({
name: 'Wireless Headphones',
description: 'High-quality wireless headphones',
price: 79.99,
category: 'Audio',
inStock: true,
imageUrl: 'https://picsum.photos/200/300'
});
// Update stock status
await products.update(newProduct.id, {
inStock: false
});
// Delete product
await products.delete(newProduct.id);
}User Management System
const UserSchema = m.object({
id: m.uuid(),
name: m.person.fullName(),
email: m.email(),
role: m.string("Role: admin, user, or guest"),
status: m.string("Status: active, inactive, or suspended"),
lastLogin: m.date(),
createdAt: m.date(),
});
export type User = Infer<typeof UserSchema>;
export const users = defineCollection<User>({
name: 'users',
basePath: '/api/users',
schema: UserSchema,
seedCount: 100,
seedInstruction: 'Generate diverse company employees',
});
// Usage
async function userManagement() {
// Get active admins
const admins = await users.list({
filter: {
role: 'admin',
status: 'active'
},
sortBy: 'name'
});
// Suspend user
await users.update('user-id', {
status: 'suspended'
});
// Search by email (pagination)
const results = await users.list({
page: 1,
limit: 10,
filter: { email: 'john@example.com' }
});
}Multi-Tenant Customer Demos
// Configure for Customer A
configureSymulate({
symulateApiKey: 'sym_live_xxx',
projectId: 'proj_xxx',
collections: {
branch: 'demo-customer-a'
}
});
// All operations are isolated to Customer A's demo
await products.create({ name: 'Customer A Product' });
const customerAProducts = await products.list();
// Switch to Customer B
configureSymulate({
collections: {
branch: 'demo-customer-b'
}
});
// Completely separate data
await products.create({ name: 'Customer B Product' });
const customerBProducts = await products.list();
// Customer A and B data never mix!Ready to Build with Collections?
Check out our CLI reference or API documentation for more details.
