@polyweave/smart-templates (1.0.6)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/smart-templates@1.0.6"@polyweave/smart-templates": "1.0.6"About this package
@polyweave/smart-templates
Smart template strings with validation, JSON Schema, and prompt formatting.
Synopsis
import {
SmartTemplateStringFromTemplate,
createTemplateNode,
composeTemplateNodes,
createPromptFormatter,
createStructuredFormatter,
createSmartRepairWrapper,
createSmartRepairRunner,
createTemplateRegistryDuplex,
createTemplateClient,
createTemplateTreeStoreDuplex,
createTemplateRestStoreDuplex,
createTemplateWebSocketStoreDuplex,
createTemplateCacheDuplex,
Validator,
Register,
AssertValid,
Is,
JSONSchema,
JSONSchemaValidatorConstructor,
tryRepairJson
} from '@polyweave/smart-templates';
const tmpl = SmartTemplateStringFromTemplate('Hello {{ name }}! The score is {{ score }}%.');
const node = createTemplateNode(tmpl);
console.log(node.render({ name: 'World', score: 95 }));
// Hello World! The score is 95%.
Install
npm install @polyweave/smart-templates
Why
Smart templates provide type-safe variable substitution via {{ var }} or ${var} syntax, combined with a registration-based validator catalog, JSON Schema generation and validation, and composable structural formatting. Every template string is parsed into segments (StringSegment + TemplateHole) with auto-detected variables and schema. TemplateNodes wrap these into reusable render(vars) functions. Formatters build prompt-engineering pipelines with JSON repair and optional LLM-driven smart repair. Use it whenever templates need validation guarantees, schema generation against prompts, or composable formatter pipelines.
API
Template String Subsystem (smartTemplateString.js)
SmartTemplateStringFromTemplate(template: string, path?: []) → SmartTemplateString
Parses a template string (supports {{ }} or ${} delimiters) into an AST of StringSegment and TemplateHole objects. Auto-detects variables and schema from path expressions and JavaScript expressions.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
template |
string |
Yes | — | Template string with {{ var }} or ${var} holes |
path |
(string|number)[] |
No | [] |
Validation path for error reporting |
Returns: A SmartTemplateString object:
{
type: 'SmartTemplateString',
template: [StringSegment | TemplateHole, ...],
variables: string[],
content: string,
schema: JSONSchema
}
Throws: Error if {{ delimiter has no matching }}.
SmartTemplateString (constructor)
Registered validator/constructor for SmartTemplateString objects. Accepts either a string (delegates to SmartTemplateStringFromTemplate) or an options object { template, variables, content, schema }.
StringSegment (constructor)
A literal text segment. Fields: { type: 'StringSegment', content: string, escaped: boolean }.
TemplateHole (constructor)
A variable substitution hole. Fields: { type: 'TemplateHole', content: string, variables: string[], schema: JSONSchema }.
findVariables(expressionInfo: { type, parts? }) → string[]
Extracts variable names from a parsed expression, filtering out reserved JavaScript tokens.
findSchema(expressionInfo: { type, parts? }) → JSONSchema
Generates a JSON Schema capturing the variables referenced by an expression.
GenericInterpreter(input: object, holes: (StringSegment|TemplateHole)[]) → string
Applies variable substitution: walks the hole array, evaluates TemplateHole expressions against input, concatenates literal content.
TemplateStringInterpreterConstructor(smartTemplate: SmartTemplateString) → (vars: object) → string
Creates a render function from a SmartTemplateString. Validates input against the template schema before rendering.
TemplateVariableValidatorConstructor(smartTemplate: SmartTemplateString) → (input: object, path?) → object
Creates a validator function from the template's schema. Throws via AssertValid if validation fails.
composeSmartTemplates(templates: (SmartTemplateString|string)[], options?: { separator?: string }) → SmartTemplateString
Combines multiple template strings. Merges variables, schemas, and segments. Inserts separator between templates.
deepMerge(target: object, source: object) → object
Deep merge utility used internally for schema merging.
Template Nodes Subsystem (templateNodes.js)
isTemplateNode(value: any) → boolean
Returns true if value is { type: 'TemplateNode', render: function }.
createTemplateNode(input: string | SmartTemplateString | function | TemplateNode, options?: object) → TemplateNode
Creates a renderable template node.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
input |
string |
SmartTemplateString | function | TemplateNode |
options |
object |
No | {} |
{ variables, schema, content } used when input is a function |
Throws: Error if input is not a string, SmartTemplateString, function, or TemplateNode.
Returns: { type: 'TemplateNode', content: string | null, variables: string[], schema: JSONSchema, render: (vars) => string }
createTemplateNodeFromSmartTemplate(smartTemplate: SmartTemplateString) → TemplateNode
Wraps a SmartTemplateString as a TemplateNode. The render function is built via TemplateStringInterpreterConstructor.
createTemplateNodeFromFunction(fn: (vars) => string, options?: object) → TemplateNode
Wraps a function as a TemplateNode. Variables and schema are taken from options.variables, options.schema, or fn.variables / fn.schema.
composeTemplateNodes(nodes: TemplateNode[], options?: { separator?: string }) → TemplateNode
Combines multiple TemplateNodes into one. Merges variables and schemas, concatenates rendered output with separator.
Validation Subsystem (validation.js)
Validate(object: any, parent?: ValidationContext, key?: string|number) → ValidationResult
Recursively validates objects through the registered validator catalog. If object.__type names a registered validator, that validator runs. Returns { __valid: boolean, object, comments, path }.
AssertValid(object: any) → void
Throws Error if the object (or its Validate result) has __valid === false.
Throws: Error('Invalid object: ...') with summarized validation comments.
Register(typeName: string, validatorFn: function, constructorFn: function) → ConstructorFn
Registers a type in the validator catalog. Returns a constructor function that can be used to create instances of the type.
Throws: Error if typeName is already registered.
Lookup(typeName: string) → ValidatorFn
Returns the registered validator for typeName.
Throws: Error if typeName is not registered.
Is(constructor: function) → (value: any, fast?: boolean) => boolean
Creates a type guard for a registered constructor. Fast path (fast=true, default) checks __type by name; slow path runs full validation.
Throws: Error if constructor has no __name.
PopType(x: object) → object
Pops the last type off x.__type and reconstructs using the next type's constructor.
Throws: Error if pop fails.
confirmKey(obj: any, prop: string) → boolean
Returns true if obj has own property prop.
confirmKeySet(obj: any, ...props: string[]) → boolean
Returns true if obj has all of the given own properties.
Summarize(object: ValidationResult) → string
Formats validation comments into a human-readable string.
withValidation(fn: function) → (...args) → any
Higher-order function: validates all object arguments, returns validation failures directly if any, otherwise calls fn and validates the result.
JSON Schema Subsystem (jsonSchema.js)
JSONSchema(schema: any, _path?: []) → JSONSchema
Normalizes a schema object. Returns { type: 'object', properties: {} } for null/undefined/non-object input.
JSONSchemaValidatorConstructor(schema: JSONSchema) → (input: any, path?: []) → ValidationResult
Creates a validator function from a JSON Schema. Validates types (object, array, string, number, boolean), required keys for objects, and recursively validates nested schemas. Returns { __valid, object, comments }.
Formatters Subsystem (formatters.js)
createPromptFormatter(options?: object) → (payload: object) → PromptResult
Creates a function that formats LLM prompts from system/user templates.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.systemTemplate |
string |
No | — | Template string for the system prompt |
options.userTemplate |
string |
No | — | Template string for the user prompt |
options.contextKey |
string |
No | 'context' |
Key under which context data is merged into variables |
Returns: (payload) → { systemPrompt: string, userPrompt: string, metadata: object, tokenCount: number }
The formatter accepts { variables, context, metadata }. If context is provided, it's merged under contextKey. Token count is estimated as ceil(length / 4).
tryRepairJson(text: string) → string | null
Attempts to repair malformed JSON by:
- Finding first structural
{or[and last}or](respecting string escapes) - Stripping BOM characters
- Removing trailing commas before
}or] - Adding missing closing braces/brackets
Returns null if the input is not a string or is empty after trimming.
createStructuredFormatter(options?: object) → (text: string) → ParseResult
Creates a JSON parser with schema validation and optional repair.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.schema |
JSONSchema |
No | { type: 'object', properties: {} } |
Schema to validate parsed JSON against |
options.tryRepair |
function |
No | tryRepairJson |
Custom JSON repair function |
Returns: (text: string) → { ok: boolean, value: any | null, error: Error | null, rawText: string, repairedText: string | null, validation?: ValidationResult }
createSmartRepairRunner(adapter: duplex, options?: object) → (payload: object, callback: function) → void
Creates an LLM-powered JSON repair runner. Uses a system prompt template to guide the model in repairing invalid JSON. The adapter must expose source and sink following the push-stream protocol.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.systemPromptTemplate |
string |
No | — | Custom repair prompt template |
options.templateLookup |
function |
No | — | Async template resolver: (name, callback) |
options.systemPromptTemplateName |
string |
No | — | Template name to resolve via templateLookup |
The repair runner sends a TEXT frame to the adapter and accumulates response TEXT frames until END is received.
createSmartRepairWrapper(options?: object) → (text: string, done: function) → void
Wraps a structured formatter with smart repair retry logic.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.formatter |
function |
Yes | — | The structured formatter to wrap |
options.smartRepair |
function |
Yes | — | The smart repair runner |
options.schema |
JSONSchema |
No | null |
Schema for repair context |
options.maxAttempts |
number |
No | 1 |
Max repair attempts before returning failure |
Throws: Error('SmartRepairWrapper requires formatter and smartRepair.') if either is missing.
Template Registry Subsystem (templates.js)
createTemplateRegistryDuplex(options?: object) → duplex
Creates a frame-based template registry with optional upstream fallback.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.registry |
Map |
No | new Map() |
Initial template map |
options.templates |
object |
No | {} |
Initial templates as plain object |
options.upstream |
duplex |
No | null |
Upstream registry for cache-miss fallback; must expose source and sink |
Frame protocol:
- Input:
META { action: 'template.lookup', name }orMETA { action: 'template.register', name, template } - Output:
META { action: 'template.result', name, template }orERROR { code: 'TEMPLATE_NOT_FOUND' } - Upstream fallback: On cache miss, forwards
template.lookupto upstream; caches result on return
createTemplateClient(duplex: duplex) → { lookup, register }
Creates a callback-based client for a template registry duplex.
Returns:
lookup(name: string, callback: (err: Error | null, template: any) => void)— Look up a templateregister(name: string, template: any, callback: (err: Error | null, template: any) => void)— Register a template
Template Store Adapters (templateStores.js)
createTemplateTreeStoreDuplex(options?: object) → duplex
Template registry backed by a tree store.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.treeStore |
duplex |
Yes | — | Tree store with sink and source |
options.treeId |
string |
No | 'templates' |
Tree identifier |
options.basePath |
string[] |
No | ['templates'] |
Base path prefix |
options.resolvePath |
function |
No | — | Custom path resolver: (name, basePath) → path[] |
Throws: Error('TemplateTreeStoreDuplex requires a treeStore duplex.') if treeStore is missing.
Frame protocol: Lookup uses TREE_QUERY/TREE_RESULT; register uses TREE_UPSERT.
createTemplateRestStoreDuplex(options?: object) → duplex
Template registry backed by a REST API.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.request |
function |
Yes | — | HTTP request function: (req, callback) |
options.baseUrl |
string |
No | '' |
Base URL for default request builders |
options.buildLookupRequest |
function |
No | (name) → req |
Request builder for lookups (default: GET {baseUrl}/templates/{name}) |
options.buildRegisterRequest |
function |
No | (name, template) → req |
Request builder for register (default: PUT {baseUrl}/templates/{name}) |
Throws: Error('TemplateRestStoreDuplex requires a request function.') if request is missing.
Error frames: Emits TEMPLATE_NOT_FOUND on 404, generic error on other 4xx/5xx.
createTemplateWebSocketStoreDuplex(options?: object) → duplex
Template registry backed by a WebSocket connection.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.socket |
WebSocket |
conditional | — | WebSocket instance with send(); alternative to connect |
options.connect |
function |
conditional | — | Connection function returning a socket |
options.encodeMessage |
function |
No | JSON.stringify |
Message serializer |
options.decodeMessage |
function |
No | JSON.parse |
Message deserializer |
Throws: Error('TemplateWebSocketStoreDuplex requires a socket with send().') if no socket found.
Message handling: Supports socket.on('message'), socket.addEventListener('message'), and socket.onmessage.
createTemplateCacheDuplex(options?: object) → duplex
Layered template cache with local + remote stores.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.local |
duplex |
Yes | — | Local (fast) template store |
options.remote |
duplex |
Yes | — | Remote (authoritative) template store |
options.propagateRegister |
boolean |
No | false |
If true, register writes propagate to remote |
Throws: Error if local or remote is missing.
Lookup: Tries local first, falls back to remote, caches remote result locally.
Register: Writes to local; optionally propagates to remote.
Tests
npm test
Covers: template string parsing ({{ }} and ${}), variable detection, schema generation, template interpretation, TemplateNode creation and composition, prompt formatting, JSON schema validation, JSON repair, structured formatting, smart repair runner and wrapper, template registry duplex (lookup/register/upstream fallback), template store adapters (tree, REST, WebSocket, cache).
Requirements
- Node.js 22+
- ESM only
- Peer dependency:
@polyweave/core
Dependencies
Development Dependencies
| ID | Version |
|---|---|
| @rigor/core | * |
Peer Dependencies
| ID | Version |
|---|---|
| @polyweave/core | * |
| @polyweave/errors | * |