@bolttech/templating-sdk
v0.2.9
Published
JavaScript SDK for Bolttech Templating Service - Create, manage and render templates with ease
Downloads
525
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 changesTokenization Support
The SDK supports automatic detokenization of sensitive data before rendering templates. This feature protects PII (Personally Identifiable Information) and other sensitive data.
How It Works
- Create template with tokenization config:
const template = await sdk.create({
title: 'User Report',
format: TemplateFormat.HTML,
workspace: 'sandbox',
content: '<h1>User Report</h1><p>Name: {{name}}</p><p>Email: {{email}}</p>',
variables: {
tokenization: 'pii-config' // Enables data protection
}
});- Render with tokenized data:
const result = await sdk.render(template.id, {
name: 'John Doe',
email: 'token:sandbox:pii:email_hash123'
});
// Service automatically detokenizes before renderingKey Points
variablesfield stores configuration metadatatokenizationvariable enables automatic data protection- Workspace is used as tenant identifier
- Token format:
token:tenant:type:hash - Service handles all detokenization automatically
- No special configuration needed in SDK - just pass tokenized data
API 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].
