@bolttech/templating-sdk
v0.2.7
Published
JavaScript SDK for Bolttech Templating Service - Create, manage and render templates with ease
Readme
Bolttech Templating SDK
A powerful JavaScript/TypeScript SDK for interacting with the Bolttech Templating Service. This SDK provides a simple and intuitive API for managing templates and rendering dynamic content with Handlebars syntax.
Features
- 🚀 Simple API - Easy-to-use TemplatingSdk interface
- 📝 Template Rendering - Built-in Handlebars template engine with helpers
- 🔄 Immutable Updates - Update template properties safely with immutable operations
- 🧠 Intelligent Change Detection - Smart tracking of modifications with reversion support
- 💾 Auto-Save - Save template changes directly to the service
- 🔒 TypeScript Support - Full TypeScript definitions included
- 🌐 HTTP Client - Built on Axios with automatic error handling
- 🎯 Workspace & Domain Support - Multi-workspace and domain filtering with flexible headers
- ⚡ Lightweight - Minimal dependencies, maximum performance
- 🛡️ Error Handling - Comprehensive error types and handlers
Installation
npm install @bolttech/templating-sdkQuick Start
import { TemplatingSdk } from '@bolttech/templating-sdk';
// Initialize the SDK with workspace and domain (optional)
const templateSdk = new TemplatingSdk('https://your-templating-service.com', {
workspace: 'your-workspace-id',
domain: 'example.com'
});
// Or without workspace/domain
const templateSdk = new TemplatingSdk('https://your-templating-service.com');
// Get a template by slug and render it
const template = await templateSdk.get('welcome-email');
const html = template.render({
name: 'John Doe',
email: '[email protected]',
});
console.log(html);
// Get by ObjectId
const templateById = await templateSdk.get('507f1f77bcf86cd799439011');
// Get with context and query filters
const filteredTemplate = await templateSdk.get('welcome-email', {
context: { workspace: 'production' },
query: { tags: { category: 'onboarding' } }
});
// Count templates in workspace
const totalTemplates = await templateSdk.count();
const prodTemplates = await templateSdk.count({ workspace: 'production' });
// Update and save template with intelligent change detection
let updatedTemplate = template
.update({ title: 'Welcome Email v2' })
.update({ content: '<h1>Welcome {{name}}!</h1>' });
console.log(updatedTemplate.isModified); // true
// Revert changes - intelligent detection knows it's back to original
updatedTemplate = updatedTemplate
.update({ title: template.title }) // back to original
.update({ content: template.content }); // back to original
console.log(updatedTemplate.isModified); // false - no real changes!
// Save only when there are actual changes
await updatedTemplate.save(); // Won't make API call since no changesAPI Reference
TemplatingSdk
The main SDK class for interacting with the Templating Service.
Constructor
new TemplatingSdk(baseUrl: string, options?: Partial<TemplatingSdkConfig>)Parameters:
baseUrl- The base URL of your templating serviceoptions- Optional configuration:workspace- Default workspace identifier (sent asx-workspaceheader)domain- Default domain (sent asx-domainheader)timeout- Request timeout in milliseconds (default: 30000)headers- Additional headers to include in requestsmode- SDK mode:SdkMode.INTEGRATION(default) orSdkMode.EDITORfor snapshot support
SDK Modes
The SDK supports two modes to optimize for different use cases:
Integration Mode (Default)
import { TemplatingSdk } from '@bolttech/templating-sdk';
// Default mode - optimized for backend integrations
const sdk = new TemplatingSdk('https://api.example.com');
// or explicitly
const sdk = new TemplatingSdk('https://api.example.com', {
mode: 'integration'
});
const template = await sdk.get('my-template');
console.log(template.snapshot); // undefined - not loadedEditor Mode
import { SdkMode, TemplatingSdk } from '@bolttech/templating-sdk';
// Editor mode - includes snapshot data automatically
const sdk = new TemplatingSdk('https://api.example.com', {
mode: SdkMode.EDITOR, // or 'editor'
});
const template = await sdk.get('my-template');
console.log(template.snapshot); // loaded with EDITOR projection
// Edit snapshot
const updated = template.update({
snapshot: { ...template.snapshot, newField: 'value' },
});
await updated.save(); // saves snapshot changesMode Comparison:
| Feature | Integration Mode | Editor Mode | | --------------- | ------------------------------- | ------------------------------------- | | Use Case | Backend integrations, rendering | Template editors, snapshot management | | Snapshot Loaded | ❌ No | ✅ Yes (automatic) | | Projection | FULL (default) | EDITOR (automatic) | | Performance | Faster (less data) | Slightly slower (more data) |
Methods
get(identifier: string, params?: GetTemplateParams): Promise<TemplateInstance>
Retrieve a template by ID (ObjectId) or slug with optional context and query filters.
Important: The SDK automatically detects whether the identifier is a MongoDB ObjectId (24 hex characters) or a slug, and routes to the appropriate endpoint:
- ObjectId:
GET /v1/templates/:id- Only supports workspace/domain headers - Slug:
GET /v1/templates/slug/:slug- Supports query filters (format, tags) + workspace/domain headers
// Get by ObjectId (24 hex characters)
const template = await templateSdk.get('507f1f77bcf86cd799439011');
// → Routes to: GET /v1/templates/507f1f77bcf86cd799439011
// Get by slug
const template = await templateSdk.get('welcome-email');
// → Routes to: GET /v1/templates/slug/welcome-email
// Get by slug with query filters (only works with slug, not ObjectId)
const template = await templateSdk.get('welcome-email', {
query: {
format: TemplateFormat.HTML,
tags: { category: 'email', type: 'welcome' }
}
});
// Get with workspace and domain context (works with both ObjectId and slug)
const template = await templateSdk.get('welcome-email', {
context: {
workspace: 'my-workspace',
domain: 'example.com'
}
});
// Get by slug with both context and query
const template = await templateSdk.get('welcome-email', {
context: {
workspace: 'production',
domain: 'example.com'
},
query: {
tags: { category: 'email' }
}
});Params Structure (GetTemplateParams):
{
context?: {
workspace?: string; // Sent as x-workspace header (both ObjectId & slug)
domain?: string; // Sent as x-domain header (both ObjectId & slug)
},
query?: {
format?: TemplateFormat; // Filter by format (slug only)
tags?: Record<string, string>; // Filter by tags (slug only, dot notation)
[key: string]: unknown; // Additional query params (slug only)
},
projection?: TEMPLATE_PROJECTION | string; // 'FULL', 'PREVIEW', 'EDITOR' (case-insensitive)
}Projection Options:
You can control which fields are returned from the API using projections:
import { TEMPLATE_PROJECTION } from '@bolttech/templating-sdk';
// Using enum (type-safe)
const template = await sdk.get('my-template', {
projection: TEMPLATE_PROJECTION.EDITOR
});
// Using string (case-insensitive)
const template = await sdk.get('my-template', {
projection: 'editor' // or 'EDITOR'
});
// Available projections:
// - 'FULL' or 'full' - Complete template data (default in INTEGRATION mode)
// - 'PREVIEW' or 'preview' - Minimal data for listings
// - 'EDITOR' or 'editor' - Includes snapshot data (default in EDITOR mode)Note:
- Query filters (
format,tags) are ignored when using ObjectId, as the ObjectId endpoint only supports workspace/domain filtering via headers. - Projection is automatically set to
EDITORwhen SDK mode isEDITOR, but can be overridden.
getAll(params?: QueryTemplatesParams, options?: RequestOptions): Promise<TemplateInstance[]>
Retrieve all templates with optional filtering, pagination, and sorting.
import { SortParam, TemplateFormat } from '@bolttech/templating-sdk';
// Get all templates
const templates = await templateSdk.getAll();
// With filters and pagination
const templates = await templateSdk.getAll({
format: TemplateFormat.HTML,
slug: 'welcome-email',
tags: { category: 'email', type: 'welcome' }, // Tags with dot notation support
page: 1,
limit: 10,
sort: SortParam.DESC,
sortBy: 'createdAt',
q: 'search term'
});
// With workspace and domain headers
const templates = await templateSdk.getAll({
format: TemplateFormat.HTML
}, {
workspace: 'production',
domain: 'example.com'
});Query Parameters:
format- Filter by template format (TemplateFormat enum)slug- Filter by slugtags- Filter by tags (automatically converted to dot notation:tags.key=value)page- Page number for paginationlimit- Number of items per page (max 500)q- Search querysort- Sort order (SortParam.ASC or SortParam.DESC, default: DESC)sortBy- Field to sort by (default: 'createdAt')projection- Fields to include in response
Options:
workspace- Filter by workspace (sent asx-workspaceheader)domain- Filter by domain (sent asx-domainheader)headers- Additional custom headers
count(options?: RequestOptions): Promise<number>
Get the total count of templates in a specific workspace/domain without retrieving the actual template data.
// Count all templates (uses default workspace/domain from SDK config)
const totalTemplates = await templateSdk.count();
// Count templates in specific workspace
const count = await templateSdk.count({
workspace: 'production'
});
// Count templates in specific workspace and domain
const count = await templateSdk.count({
workspace: 'production',
domain: 'example.com'
});Options:
workspace- Count templates in specific workspace (sent asx-workspaceheader)domain- Count templates in specific domain (sent asx-domainheader)headers- Additional custom headers
Note: This method uses limit=1 internally for efficient counting and returns the total count from pagination metadata.
setWorkspace(workspace: string | null | undefined): void
Set or update the default workspace dynamically (sets x-workspace header for all requests).
// Set workspace
templateSdk.setWorkspace('new-workspace');
// Remove workspace
templateSdk.setWorkspace(undefined);setDomain(domain: string | null | undefined): void
Set or update the default domain dynamically (sets x-domain header for all requests).
// Set domain
templateSdk.setDomain('example.com');
// Remove domain
templateSdk.setDomain(undefined);create(templateData: CreateTemplateDto, options?: RequestOptions): Promise<TemplateInstance>
Create a new template.
import { TemplateFormat } from '@bolttech/templating-sdk';
const template = await templateSdk.create(
{
title: 'Welcome Email',
format: TemplateFormat.HTML,
content: '<h1>Welcome {{name}}!</h1>',
tags: { category: 'email', type: 'welcome' }, // optional
},
{
workspace: 'my-workspace', // sent as x-workspace header
domain: 'example.com', // sent as x-domain header
}
);Note: workspace and domain are no longer part of the template body. They are sent as headers (x-workspace and x-domain) to set the context for the template.
update(identifier: string, templateData: UpdateTemplateDto, options?: RequestOptions): Promise<TemplateInstance>
Update an existing template.
// Update by slug or ID
const template = await templateSdk.update('welcome-email', {
content: '<h1>Welcome {{name}}!</h1><p>Thanks for joining us.</p>',
});
// Update with workspace/domain filters
const template = await templateSdk.update('welcome-email', {
title: 'New Title',
tags: { version: '2.0' }
}, {
workspace: 'production',
domain: 'example.com'
});Note: workspace and domain cannot be updated. They are used as filters to identify which template to update.
delete(identifier: string, options?: RequestOptions): Promise<boolean>
Delete a template.
// Delete by slug or ID
await templateSdk.delete('welcome-email');
// Delete with workspace/domain filters
await templateSdk.delete('welcome-email', {
workspace: 'production',
domain: 'example.com',
});render(identifier: string, data: Record<string, any>): Promise<string>
Get a template and render it in one call.
const html = await templateSdk.render('welcome-email', {
name: 'John Doe',
email: '[email protected]',
});healthCheck(): Promise<boolean>
Check if the templating service is healthy.
const isHealthy = await templateSdk.healthCheck();TemplateInstance
Represents a template with rendering capabilities.
Properties
id- Template IDslug- Template slugtitle- Template titleformat- Template format (TemplateFormat enum)content- Template contentworkspace- Workspace ID (string | null)domain- Domain (string | null)tags- Template tags (Record<string, string>)createdAt- Creation dateupdatedAt- Last update dateisModified- Boolean indicating if template has unsaved changes 🆕snapshot- Template snapshot data (only available in EDITOR mode) 🆕
Methods
render(placeholders?: TemplatePlaceholders, options?: RenderOptions): string
Render the template with provided placeholders and options.
const html = template.render({
name: 'John Doe',
items: ['apple', 'banana', 'orange'],
});
// With options
const html = template.render(
{ name: 'John Doe' },
{ escapeHtml: false }
);update(updates: TemplateUpdateData): Template 🆕
Create a new Template instance with updated properties (immutable operation) with intelligent change detection.
// Update multiple properties
const updatedTemplate = template.update({
title: 'New Title',
content: '<h1>Updated content</h1>',
slug: 'new-slug',
workspace: 'new-workspace',
domain: 'newdomain.com',
tags: { updated: 'true' },
});
console.log(updatedTemplate.isModified); // true
// Intelligent change detection - reverting to original values
const revertedTemplate = updatedTemplate.update({
title: template.title, // back to original
content: template.content, // back to original
});
console.log(revertedTemplate.isModified); // false - no real changes from original!
// No changes = same values as original
const sameTemplate = template.update({ title: template.title });
console.log(sameTemplate.isModified); // falseKey Features:
- Intelligent Change Detection - Only marks
isModified=truewhen actual changes exist from the original template - Reversion Support - Changing values back to original automatically sets
isModified=false - Efficient Updates -
updatedAttimestamp only changes when real modifications occur - Validation on Save - Data validation happens during
save(), notupdate()
save(): Promise<Template> 🆕
Save the current template state to the service with intelligent optimizations.
const template = await sdk.get('my-template');
const updatedTemplate = template.update({ title: 'New Title' });
console.log(updatedTemplate.isModified); // true
// Save changes to service (performs validation here)
const savedTemplate = await updatedTemplate.save();
console.log(savedTemplate.isModified); // false - saved successfully
// Optimization: No API call if no changes
const unchanged = template.update({ title: template.title });
console.log(unchanged.isModified); // false
await unchanged.save(); // Returns immediately, no API call madeKey Features:
- Smart Saving - Only makes API calls when
isModified=true - Validation on Save - Data validation happens here, not during
update() - Error Handling - Maintains
isModified=trueif save fails - State Reset - Sets
isModified=falseafter successful save
Note: The save() method only works on templates created through the TemplatingSdk.
getSnapshot(): Promise<any> 🆕
Get the template snapshot data (requires EDITOR mode).
import { SdkMode, TemplatingSdk } from '@bolttech/templating-sdk';
// Must use EDITOR mode
const sdk = new TemplatingSdk('https://api.example.com', {
mode: SdkMode.EDITOR,
});
const template = await sdk.get('my-template');
// Snapshot is automatically loaded in EDITOR mode
console.log(template.snapshot);
// Or fetch it explicitly (makes API call)
const snapshot = await template.getSnapshot();
// Edit snapshot
const updated = template.update({
snapshot: { ...snapshot, modified: true },
});
await updated.save(); // saves snapshot changesImportant:
- Only works when SDK is initialized with
mode: SdkMode.EDITOR - Throws an error if called in INTEGRATION mode
- Fetches fresh snapshot data from API with EDITOR projection
Chaining Operations & Change Tracking 🆕
You can chain multiple operations with intelligent change tracking:
// Complex change sequence with intelligent tracking
let currentTemplate = template;
currentTemplate = currentTemplate.update({ title: 'Changed Title' });
console.log(currentTemplate.isModified); // true
currentTemplate = currentTemplate.update({ content: 'New content' });
console.log(currentTemplate.isModified); // true
// Revert title back to original
currentTemplate = currentTemplate.update({ title: template.title });
console.log(currentTemplate.isModified); // true (still modified due to content)
// Revert content back to original too
currentTemplate = currentTemplate.update({ content: template.content });
console.log(currentTemplate.isModified); // false (all changes reverted!)
// Chain operations and save
const finalTemplate = await template
.update({ title: 'New Title' })
.update({ content: '<h1>Updated</h1>' })
.save(); // Only saves if there are actual changesIntelligent Change Detection System 🧠
The SDK features an advanced change detection system that tracks modifications against the original template state, providing smart behavior for common editing scenarios.
How It Works
const template = await templateSdk.get('my-template');
console.log(template.title); // "Original Title"
console.log(template.isModified); // false
// Make a change
const changed = template.update({ title: 'New Title' });
console.log(changed.isModified); // true
console.log(changed.updatedAt > template.updatedAt); // true
// Revert back to original
const reverted = changed.update({ title: 'Original Title' });
console.log(reverted.isModified); // false - smart detection!
console.log(reverted.updatedAt === changed.updatedAt); // true - no new timestampKey Benefits
- 🎯 Accurate State Tracking -
isModifiedonly reflects actual changes from the original template - ⚡ Performance Optimization -
save()skips API calls when no real changes exist - 🔄 Reversion Detection - Automatically detects when changes are reverted to original values
- ⏰ Smart Timestamps -
updatedAtonly changes when real modifications occur - ✅ Clean UX - Perfect for form interfaces where users might make and undo changes
Real-World Example
// User starts editing a template in a form
const template = await templateSdk.get('email-template');
// User types new title
let current = template.update({ title: 'Draft Title' });
console.log(current.isModified); // true - show "Save" button
// User changes their mind and reverts
current = current.update({ title: template.title });
console.log(current.isModified); // false - hide "Save" button
// User makes multiple changes
current = current
.update({ title: 'New Title' })
.update({ content: 'New content' })
.update({ title: template.title }); // revert title
console.log(current.isModified); // true - content still changed
// Save only when needed
if (current.isModified) {
await current.save(); // API call made
} else {
console.log('No changes to save'); // API call skipped
}Template Syntax
This SDK supports template syntax for creating dynamic content using standard placeholder replacement.
Error Handling
The SDK provides comprehensive error handling with specific error types:
import {
AuthenticationError,
NetworkError,
TemplateNotFoundError,
TemplateValidationError,
TemplatingError,
TemplatingSdk,
} from '@bolttech/templating-sdk';
try {
const template = await templateSdk.get('nonexistent-template');
} catch (error) {
if (error instanceof TemplateNotFoundError) {
console.log('Template not found:', error.message);
} else if (error instanceof AuthenticationError) {
console.log('Authentication failed:', error.message);
} else if (error instanceof TemplateValidationError) {
console.log('Validation error:', error.message);
} else if (error instanceof NetworkError) {
console.log('Network error:', error.message);
} else if (error instanceof TemplatingError) {
console.log('API Error:', error.message, 'Status:', error.statusCode);
}
}Available Error Types
TemplatingError- Base error for all templating operationsTemplateNotFoundError- Template not found (404)TemplateValidationError- Invalid template data (400, 422)AuthenticationError- Authentication failed (401)AuthorizationError- Access denied (403)NetworkError- Network/connection issuesTimeoutError- Request timeoutTemplateRenderError- Template rendering failedUnsupportedFormatError- Unsupported template format
TypeScript Types
The SDK exports the following TypeScript types for better development experience:
import type {
CreateTemplateDto,
EnumValue,
GetTemplateContext,
GetTemplateParams,
GetTemplateQuery,
QueryTemplatesParams,
RenderOptions,
RequestOptions,
TemplateDto,
TemplatePlaceholders,
TemplatingSdkConfig,
UpdateTemplateDto,
} from '@bolttech/templating-sdk';
import { SdkMode, SortParam, TEMPLATE_PROJECTION, TemplateFormat } from '@bolttech/templating-sdk';
// Template data structure
const template: TemplateDto = {
_id: '507f1f77bcf86cd799439011',
slug: 'welcome-email',
title: 'Welcome Email',
format: TemplateFormat.HTML,
content: '<h1>Hello {{name}}!</h1>',
workspace: 'my-workspace',
domain: null,
tags: { category: 'email' },
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z',
};
// Template placeholders
const placeholders: TemplatePlaceholders = {
name: 'John Doe',
email: '[email protected]',
items: ['apple', 'banana'],
};
// Render options
const renderOptions: RenderOptions = {
escapeHtml: true,
allowUnsafe: false,
strict: true,
};
// Request options (for workspace/domain filtering)
const requestOptions: RequestOptions = {
workspace: 'production',
domain: 'example.com',
headers: { 'Custom-Header': 'value' },
};
// Get template params (for get method)
const getParams: GetTemplateParams = {
context: {
workspace: 'production',
domain: 'example.com',
},
query: {
format: TemplateFormat.HTML,
tags: { category: 'email' },
},
};
// Get template context (workspace/domain only)
const context: GetTemplateContext = {
workspace: 'production',
domain: 'example.com',
};
// Get template query (filters only)
const query: GetTemplateQuery = {
format: TemplateFormat.HTML,
tags: { category: 'email', type: 'welcome' },
};Configuration
You can update the configuration after initialization:
// Update general config
templateSdk.updateConfig({
timeout: 60000,
workspace: 'new-workspace',
domain: 'example.com',
headers: {
'Custom-Header': 'value',
},
});
// Or update workspace/domain specifically
templateSdk.setWorkspace('new-workspace');
templateSdk.setDomain('example.com');Supported Template Formats
The SDK currently supports:
- html - HTML templates with Handlebars syntax (default)
- text - Plain text templates (raw content)
Basic Email Template
import { TemplateFormat } from '@bolttech/templating-sdk';
const emailTemplate = await templateSdk.create({
title: 'Welcome Email',
format: TemplateFormat.HTML,
content: `
<h1>Welcome {{user.name}}!</h1>
<p>Thank you for joining {{company.name}}.</p>
<p>Your account is now active.</p>
`,
});
const html = emailTemplate.render({
user: {
name: 'John Doe',
},
company: {
name: 'Acme Corp',
},
});Invoice Template
import { TemplateFormat } from '@bolttech/templating-sdk';
const invoiceTemplate = await templateSdk.create({
title: 'Invoice Template',
format: TemplateFormat.HTML,
content: `
<div class="invoice">
<h1>Invoice #{{invoice.number}}</h1>
<p>Date: {{invoice.date}}</p>
<div class="customer">
<h2>Bill To:</h2>
<p>{{customer.name}}</p>
<p>{{customer.address}}</p>
</div>
<table>
<thead>
<tr>
<th>Item</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{item.description}}</td>
<td>{{item.quantity}}</td>
<td>${{item.price}}</td>
<td>${{item.total}}</td>
</tr>
</tbody>
</table>
<div class="total">
<strong>Total: ${{invoice.total}}</strong>
</div>
</div>
`
});Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
For support, please contact [email protected].
