@bernierllc/template-variable-registry
v1.2.0
Published
Template variable registration, validation, and extraction system for email templates with Handlebars and Jinja2 support
Readme
@bernierllc/template-variable-registry
A comprehensive template variable registration, validation, and extraction system for email templates with support for both Handlebars and Jinja2 template engines.
Installation
npm install @bernierllc/template-variable-registryFeatures
- Variable Registration: Define available template variables with type information and validation rules
- Context Management: Organize variables into contexts with inheritance support
- Template Validation: Validate templates against registered variables
- Variable Extraction: Parse templates to extract all variable references
- Multi-Engine Support: Works with both Handlebars and Jinja2 templates
- Autocomplete Support: Generate variable suggestions for UI integration
- Syntax Validation: Detect template syntax errors
- Type Safety: Full TypeScript support with strict typing
Usage
Basic Variable Registration
import { VariableRegistry } from '@bernierllc/template-variable-registry';
const registry = new VariableRegistry();
// Register a context with variables
registry.registerContext('user_welcome', {
name: 'user_welcome',
description: 'Variables for welcome emails',
variables: {
user: {
type: 'object',
required: true,
description: 'User information',
children: {
name: {
type: 'string',
required: true,
description: 'User full name'
},
email: {
type: 'string',
required: true,
description: 'User email address'
},
joinDate: {
type: 'date',
required: false,
description: 'Account creation date'
}
}
},
company: {
type: 'object',
required: true,
children: {
name: { type: 'string', required: true },
logoUrl: { type: 'string', required: false }
}
}
}
});Template Validation
const template = `
<h1>Welcome {{user.name}}!</h1>
<p>Thanks for joining {{company.name}}.</p>
<p>Your account was created on {{user.joinDate}}.</p>
<p>Visit us at {{company.websiteUrl}}.</p>
`;
const validation = registry.validateTemplate('user_welcome', template);
if (!validation.isValid) {
console.log('Template errors:');
validation.errors.forEach(error => {
console.log(`Line ${error.line}: ${error.message}`);
});
// Output: Line 4: Undefined variable 'company.websiteUrl'
// Get suggestions for undefined variables
validation.undefinedVariables.forEach(undef => {
console.log(`Undefined: ${undef.name}`);
console.log(`Did you mean: ${undef.suggestions?.join(', ')}`);
});
// Output: Did you mean: company.logoUrl, company.name
}
// Check for unused required variables
if (validation.unusedVariables.length > 0) {
console.log('Warning: Required variables not used:');
validation.unusedVariables.forEach(path => {
console.log(` - ${path}`);
});
}Variable Extraction
// Extract all variables from a template
const variables = registry.extractVariables(template);
variables.forEach(variable => {
console.log(`Found: ${variable.path} at line ${variable.line}`);
console.log(` Context: ${variable.context}`);
console.log(` Is conditional: ${variable.isConditional}`);
console.log(` Is loop: ${variable.isLoop}`);
});Autocomplete Suggestions
// Get all top-level variables
const allSuggestions = registry.getVariableSuggestions('user_welcome');
// Get suggestions with prefix
const userSuggestions = registry.getVariableSuggestions('user_welcome', 'user.');
// Returns:
// [
// { path: 'user.name', type: 'string', description: 'User full name', required: true },
// { path: 'user.email', type: 'string', description: 'User email address', required: true },
// { path: 'user.joinDate', type: 'date', description: 'Account creation date', required: false }
// ]
// Get path completions for partial input
const completions = registry.getPathCompletions('user_welcome', 'user.');Context Inheritance
// Create base context
registry.registerContext('base_user', {
name: 'base_user',
variables: {
user: {
type: 'object',
required: true,
children: {
name: { type: 'string', required: true },
email: { type: 'string', required: true }
}
}
}
});
// Extend base context
registry.registerContext('user_welcome', {
name: 'user_welcome',
extends: ['base_user'],
variables: {
welcomeMessage: { type: 'string', required: false },
activationUrl: { type: 'string', required: true }
}
});
// user_welcome now has: user.name, user.email, welcomeMessage, activationUrlValidation Rules
import { lengthRule, patternRule, CommonRules } from '@bernierllc/template-variable-registry';
registry.registerContext('signup', {
name: 'signup',
variables: {
email: {
type: 'string',
required: true,
validation: [
CommonRules.email(),
lengthRule(5, 255)
]
},
password: {
type: 'string',
required: true,
validation: [
lengthRule(8, undefined, 'Password must be at least 8 characters'),
patternRule(/[A-Z]/, 'Must contain uppercase letter')
]
},
age: {
type: 'number',
required: true,
validation: [
CommonRules.positiveNumber()
]
}
}
});Jinja2 Template Support
const jinja2Template = `
<h1>Welcome {{ user.name }}!</h1>
{% if user.isPremium %}
<p>You have premium access!</p>
{% endif %}
<ul>
{% for item in items %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
`;
// Works the same way
const validation = registry.validateTemplate('user_welcome', jinja2Template);
const variables = registry.extractVariables(jinja2Template);API Reference
VariableRegistry
registerContext(contextName: string, context: VariableContext): void
Register a variable context with the registry.
getContext(contextName: string): VariableContext | null
Retrieve a registered context by name.
getAllContexts(): VariableContext[]
Get all registered contexts.
validateTemplate(contextName: string, template: string): ValidationResult
Validate a template against a registered context.
Returns:
isValid: boolean - Whether the template is validerrors: ValidationError[] - All validation errorswarnings: ValidationWarning[] - Validation warningsunusedVariables: string[] - Required variables not usedundefinedVariables: UndefinedVariable[] - Variables not in registry
extractVariables(template: string, engine?: TemplateEngine): ExtractedVariable[]
Extract all variables from a template. Auto-detects engine if not specified.
getVariableSuggestions(contextName: string, prefix?: string): VariableSuggestion[]
Get variable suggestions for autocomplete functionality.
getPathCompletions(contextName: string, partialPath: string): VariableSuggestion[]
Get completions for a partial variable path.
hasContext(contextName: string): boolean
Check if a context exists.
deleteContext(contextName: string): boolean
Remove a context from the registry.
clearAll(): void
Clear all registered contexts.
Type Definitions
VariableDefinition
interface VariableDefinition {
type: 'string' | 'number' | 'boolean' | 'date' | 'array' | 'object';
required: boolean;
description?: string;
defaultValue?: unknown;
validation?: ValidationRule[];
children?: Record<string, VariableDefinition>; // For objects
}VariableContext
interface VariableContext {
name: string;
description?: string;
variables: Record<string, VariableDefinition>;
extends?: string[]; // Parent context names
}ValidationResult
interface ValidationResult {
isValid: boolean;
errors: ValidationError[];
warnings: ValidationWarning[];
unusedVariables: string[];
undefinedVariables: UndefinedVariable[];
}ExtractedVariable
interface ExtractedVariable {
name: string;
path: string;
line: number;
column: number;
context: 'handlebars' | 'jinja2';
isConditional: boolean;
isLoop: boolean;
}Advanced Features
Nested Object Paths
// Supports deep nesting
variables: {
user: {
type: 'object',
required: true,
children: {
profile: {
type: 'object',
required: false,
children: {
address: {
type: 'object',
required: false,
children: {
street: { type: 'string', required: false },
city: { type: 'string', required: false }
}
}
}
}
}
}
}
// Use in templates: {{user.profile.address.street}}Array Access Patterns
variables: {
items: {
type: 'array',
required: true
}
}
// Handlebars: {{items[0].name}}
// Jinja2: {{ items[0].name }}Syntax Error Detection
const template = '{{#if condition}}test{{/unless}}'; // Mismatched blocks
const validation = registry.validateTemplate('context', template);
validation.errors.forEach(error => {
if (error.type === 'syntax_error') {
console.log(`Syntax error at line ${error.line}: ${error.message}`);
}
});Integration Status
Logger Integration
Status: Not applicable - Pure utility package with no runtime logging needs.
Justification: This is a pure utility package that performs template variable registration, validation, and extraction. It has no runtime logging requirements as it's designed to be embedded in other packages that handle logging at their level.
Docs-Suite Integration
Status: Ready - Full TypeDoc documentation exported
Format: TypeDoc with complete API documentation, usage examples, and type definitions. All public APIs are fully documented with JSDoc comments.
NeverHub Integration
Status: Not applicable - Core utility package with no external service dependencies
Justification: This is a core package that provides atomic template variable functionality. It has no need for service discovery, event bus communication, or dynamic configuration. NeverHub integration would be appropriate at the service/suite level that orchestrates this package.
Use Cases
This package is designed for:
- Email template systems - Validate email templates before sending
- Template editors - Provide autocomplete and validation in UI
- Template documentation - Generate documentation of available variables
- Multi-tenant systems - Different variable contexts per tenant
- Template testing - Ensure templates use valid variables
Package Type
Core Package - Atomic utility with zero external dependencies, designed for reuse across multiple service and suite packages.
Dependencies
Zero runtime dependencies - pure TypeScript implementation.
Security
Input Validation
All template inputs and variable definitions are thoroughly validated:
- Template syntax is parsed and validated before processing
- Variable paths are sanitized to prevent injection attacks
- Type checking ensures data integrity
Safe Defaults
- No external dependencies reduces attack surface
- No network calls or file system access
- Pure functions with no side effects
- All data structures are immutable where possible
Dependency Security
- Zero runtime dependencies means zero transitive dependency vulnerabilities
- Development dependencies are regularly audited with
npm audit - All dependencies pinned to specific versions for reproducibility
See Also
- @bernierllc/template-engine - Template rendering engine
- @bernierllc/email-validator - Email validation utilities
License
Copyright (c) 2025 Bernier LLC. All rights reserved.
