@quiltr/sdk
v1.14.0
Published
TypeScript SDK for the Quiltr NestJS Multitenant API
Readme
Quiltr SDK
TypeScript SDK for the Quiltr NestJS Multitenant API. Zero-dependency (uses native fetch), type-safe, and built on a Result<T, E> pattern for predictable error handling.
Installation
npm install @quiltr/sdkQuick Start
import { QuiltrClient } from '@quiltr/sdk';
const client = new QuiltrClient({ baseUrl: 'https://api.example.com' });
// Authenticate
const login = await client.auth.login({ username: 'admin', password: 'secret' });
if (!login.success) throw new Error(login.error.message);
// CRUD operations
const products = await client.products.findAll({ page: 1, limit: 20 });
if (products.success) {
console.log(products.data.data); // Product[]
console.log(products.data.meta.total); // total count
}
// Create
const result = await client.products.create({
sku: 'WIDGET-001',
priceInCents: 1999,
name: { en: 'Widget' },
});Result Pattern
Every SDK method returns Result<T, SdkError> — a discriminated union that forces you to handle errors:
const result = await client.products.findOne('abc123');
if (result.success) {
// result.data is fully typed
console.log(result.data.sku);
} else {
// result.error is SdkError
console.log(result.error.message, result.error.code);
}Feature Clients
All clients are accessible as properties on QuiltrClient:
| Client | Path | Description |
|--------|------|-------------|
| client.auth | /api/v1/auth | Login, register, token refresh, password flows |
| client.tenants | /api/v1/tenants | Tenant management (global admin) |
| client.apiUsers | /api/v1/api-users | API user management (global admin) |
| client.products | /api/v1/products | Product catalog (tenant-scoped CRUD) |
| client.customers | /api/v1/customers | Customer profiles (tenant-scoped CRUD) |
| client.baskets | /api/v1/baskets | Shopping baskets (tenant-scoped CRUD) |
| client.locations | /api/v1/locations | Physical locations (tenant-scoped CRUD) |
| client.fascias | /api/v1/fascias | Brands/sub-brands (tenant-scoped CRUD) |
| client.priceEntries | /api/v1/price-entries | Price entries (tenant-scoped CRUD) |
| client.pricing | /api/v1/prices | Dynamic price resolution |
| client.tasks | /api/v1/tasks | Task management (tenant-scoped CRUD) |
| client.emailTemplates | /api/v1/email-templates | Email templates (tenant-scoped CRUD) |
| client.users | /api/v1/users | Tenant staff/employees (tenant-scoped CRUD) |
| client.workflows | /api/v1/workflow-* | Workflow definitions + instances |
| client.scripts | /api/v1/scripts | Script CRUD + async execution |
| client.appSessions | /api/v1/app-sessions | Workflow-driven UI sessions |
| client.jobs | /api/v1/jobs | Background job tracking |
| client.payments | /api/v1/payments | Payment processing |
| client.search | /api/v1/search | Full-text search, facets, suggestions |
| client.uploads | /api/v1/uploads | File uploads |
| client.websocket | WebSocket | Real-time events |
CRUD Operations
All tenant-scoped entities share the same base operations via BaseCrudClient:
// List with pagination
await client.products.findAll({ page: 1, limit: 20, sortBy: 'createdAt', sortOrder: 'desc' });
// Get by ID
await client.products.findOne('id');
// Create
await client.products.create({ sku: 'SKU-001', priceInCents: 999 });
// Update (PATCH)
await client.products.update('id', { priceInCents: 1299 });
// Soft delete + restore
await client.products.delete('id');
await client.products.restore('id');
// Hard delete (permanent)
await client.products.hardDelete('id');
// Count
await client.products.count({ category: 'electronics' });
// Batch operations
await client.products.batchCreate([...]);
await client.products.batchUpdate([{ id: '...', data: { ... } }]);
await client.products.batchDelete({ ids: ['...'] });Workflows
// Create a definition
const def = await client.workflows.createDefinition({
name: 'Order Processing',
stages: [
{ name: 'received', label: 'Received', order: 0, isInitial: true, transitions: [{ targetStage: 'processing', label: 'Start' }] },
{ name: 'processing', label: 'Processing', order: 1, transitions: [{ targetStage: 'shipped', label: 'Ship' }] },
{ name: 'shipped', label: 'Shipped', order: 2, isFinal: true, transitions: [] },
],
});
// Activate and run
await client.workflows.updateDefinition(def.data._id, { status: 'active' });
const run = await client.workflows.runDefinition(def.data._id, { orderId: '123' });
// Transition an instance
await client.workflows.transitionInstance(instanceId, { targetStage: 'processing' });
// Full instance lifecycle
await client.workflows.pauseInstance(instanceId);
await client.workflows.resumeInstance(instanceId);
await client.workflows.cancelInstance(instanceId);
await client.workflows.getInstanceHistory(instanceId);Scripts
// Create and execute a script
const script = await client.scripts.create({
name: 'Calculate Tax',
description: 'Calculates tax by region',
language: 'javascript',
code: 'return amount * taxRate;',
status: 'active',
});
// Execute asynchronously (returns a jobId)
const { data } = await client.scripts.execute(script.data._id, {
parameters: { amount: 1000, taxRate: 0.15 },
});
// Poll the job for results
const job = await client.jobs.waitForCompletion(data.jobId);Pricing
// Resolve price for a single product
const price = await client.pricing.resolvePrice('product-id', {
country: 'US',
channel: 'web',
customerGroup: 'vip',
});
// Batch resolve (up to 200 products)
const prices = await client.pricing.resolvePrices(
['prod-1', 'prod-2', 'prod-3'],
{ country: 'GB', storeCode: 'LON-001' },
);App Sessions
// Start a workflow-driven UI session
const screen = await client.appSessions.create({
appId: 'checkout-flow',
initialData: { cartId: '123' },
});
// Navigate between stages
await client.appSessions.navigate(screen.data.sessionId, {
targetStage: 'payment',
data: { selectedMethod: 'card' },
});
// Execute an action
await client.appSessions.executeAction(screen.data.sessionId, {
actionName: 'processPayment',
params: { amount: 5000 },
});Authentication
// Login
await client.auth.login({ username: 'user', password: 'pass' });
// Register
await client.auth.register({ username: 'new', password: 'Pass123!', email: '[email protected]', tenantId: 'my-tenant' });
// Token refresh (automatic by default)
await client.auth.refresh();
// Get current user from JWT
const user = client.auth.getCurrentUser();
// Set tokens from external source
client.setTokens(accessToken, refreshToken);
// Password flows
await client.auth.forgotPassword({ email: '[email protected]' });
await client.auth.changePassword({ currentPassword: 'old', newPassword: 'new' });WebSocket (Real-time)
// Connect after authentication
client.websocket.connect();
// Listen for events
client.websocket.on('job:completed', (data) => console.log('Job done:', data));
client.websocket.on('workflow:transition', (data) => console.log('Transition:', data));
// Disconnect
client.websocket.disconnect();Health Checks
const health = await client.healthCheck(); // GET /health
const ready = await client.readinessCheck(); // GET /health/readyExtending the SDK
Create custom clients using BaseCrudClient:
import { BaseCrudClient, HttpClient } from '@quiltr/sdk';
interface Widget { _id: string; name: string; }
interface CreateWidgetDto { name: string; }
class WidgetsClient extends BaseCrudClient<Widget, CreateWidgetDto> {
constructor(http: HttpClient) {
super(http, '/api/v1/widgets');
}
}Configuration
const client = new QuiltrClient({
baseUrl: 'https://api.example.com',
timeout: 30000, // Request timeout (ms)
maxRetries: 2, // Retry transient failures
headers: { // Custom headers
'X-Custom': 'value',
},
onTokenRefresh: async (refreshToken) => {
// Custom refresh logic (default: uses client.auth.refresh)
return { accessToken: '...', refreshToken: '...' };
},
onAuthExpired: () => {
// Called when refresh fails — redirect to login, etc.
window.location.href = '/login';
},
});Seed Project
The seed toolkit has been consolidated into @quiltr/seed at packages/seed/. It combines Faker generators with static vertical data and supports bootstrap, global, and tenant seeding phases.
# From the repo root:
npm run seed -- --all --vertical specialty
npm run seed -- --tenant tenant-123 --vertical charity
npm run seed -- --count 100