first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
# `template-engine` — Template registry and renderer
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`template-engine.ts` ships the `TemplateEngine` registry that turns named templates into channel-specific rendered output. Templates carry a `defaultTemplate` (used when no channel variant matches) plus optional `channelTemplates` keyed by `NotificationChannel`, with `{{var}}` placeholders substituted from a flat `Record<string,string>`. Missing keys are left as-is so QA can spot them.
## Features
- Register / unregister / list templates by string id.
- Per-channel variants (`channelTemplates.email`, `.sms`, etc.) with fallback to `defaultTemplate`.
- `{{var}}` placeholder substitution against a flat string-valued record.
- Throws `TemplateNotFoundError` for unknown ids — no silent empty renders.
- Universal — no DOM/Node-specific APIs.
## Usage: Basic
```ts
import { TemplateEngine } from "@suaveplan/notification-channel";
const engine = new TemplateEngine();
engine.register({
id: "welcome",
name: "Welcome",
defaultTemplate: {
subject: "Welcome {{name}}",
bodyTemplate: "Hello {{name}}, welcome to {{app}}!",
},
});
const rendered = engine.render("welcome", { name: "Alice", app: "Suaveplan" });
```
## Usage: Advanced
```ts
engine.register({
id: "order-status",
name: "Order status",
defaultTemplate: {
subject: "Order {{orderId}} — {{status}}",
bodyTemplate: "Order #{{orderId}}: {{status}}",
},
channelTemplates: {
sms: { bodyTemplate: "Order {{orderId}}: {{status}}" },
email: {
subject: "Order {{orderId}} update",
bodyTemplate: "<h1>Order {{orderId}}</h1><p>{{status}}</p>",
},
},
});
const sms = engine.render(
"order-status",
{ orderId: "12345", status: "shipped" },
"sms"
);
const email = engine.render(
"order-status",
{ orderId: "12345", status: "shipped" },
"email"
);
```
## API Reference
- `TemplateEngine` class.
- `register(t: TemplateRegistration): void`
- `unregister(id: string): boolean`
- `has(id: string): boolean`
- `listIds(): string[]`
- `render(id, data?, channel?): RenderedTemplate`
- `TemplateRegistration`, `ChannelTemplateBody`, `ChannelTemplate`, `RenderedTemplate`.
## Implementation Notes
Substitution uses `String.prototype.split(...).join(...)` rather than `replace(/.../g, ...)` so dollar-escape sequences in the value (`$&`, `$1`) flow through verbatim. Subjects flow through the same substitution pass when set. Unknown placeholders are intentionally preserved — silently dropping them masks integration mismatches that QA would otherwise catch. The engine is intentionally simple: no conditionals, no loops, no template inheritance. Consumers needing those features should plug a real template language (Handlebars, Liquid, MJML) into a custom `TemplateEngine` decorator or render upstream.