4.0 KiB
template-registry
(no submodules)
Purpose
TemplateRegistry is a type-safe in-memory store for EmailTemplate
values keyed by name. It supports per-name lookup, listing, category /
tag / author search, and lifecycle removal — the surface a typical
email-orchestration service needs to dispatch the right template by
name without coupling to the template implementations themselves.
The registry is intentionally non-persistent: callers needing
cross-process state (DB-backed templates, CDN-hosted JSON, etc.) wire
their own store and register() into this registry on boot. Templates
remain plain EmailTemplate values; the registry never copies or
freezes them.
Features
register(name, template, metadata?)with duplicate detection and an OTel counter (registry_register_total{outcome}).get(name)/has(name)/list()/sizeaccessor.search(criteria)with name (case-insensitive substring), category (exact), tag (any-intersection), author (exact) filters — combinable.remove(name)/clear()for lifecycle management.- Optional
defaultThemeexposed via a getter, useful as a per-registry fallback for the renderer.
Usage: Basic
import { createRegistry } from "@suaveplan/email-templates/template-registry";
import {
invoiceTemplate,
welcomeTemplate,
} from "@suaveplan/email-templates/templates";
const registry = createRegistry();
registry.register("welcome", welcomeTemplate, {
category: "transactional",
tags: ["onboarding"],
});
registry.register("invoice", invoiceTemplate, { category: "billing" });
const t = registry.get("welcome");
Usage: Advanced
Wire a typed map so get(name) returns the exact EmailTemplate<TProps>
for that key:
import { createRegistry } from "@suaveplan/email-templates/template-registry";
import { welcomeTemplate } from "@suaveplan/email-templates/templates";
import type { WelcomeProps } from "@suaveplan/email-templates/templates";
interface MyMap {
welcome: WelcomeProps;
}
const registry = createRegistry<MyMap>();
registry.register("welcome", welcomeTemplate);
const welcomeT = registry.get("welcome"); // type: EmailTemplate<WelcomeProps> | undefined
Search by tag intersection and category:
registry.search({ tags: ["onboarding"], category: "transactional" });
API Reference
| Symbol | Kind | Description |
|---|---|---|
TemplateRegistry<TMap> |
class | The registry itself; implements TemplateRegistryApi. |
createRegistry<TMap> |
function | Convenience factory returning a new registry. |
RegistryOptions |
type | Constructor options. |
TemplateMetadata |
type | { category?, tags?, description?, author? }. |
RegistryEntry |
type | Internal entry shape (template + metadata + when). |
SearchCriteria |
type | Search input shape. |
SearchResult |
type | Search output shape. |
TemplateMap |
type | Record<string, unknown> upper bound. |
TemplateRegistryApi |
type | The interface implemented by TemplateRegistry. |
Implementation Notes
The registry stores its entries in a single Map<string, RegistryEntry>
— ordered insertion is preserved for list() and search(). Duplicate
registration raises EmailTemplatesError(DUPLICATE_NAME) deliberately:
silent overwrite is the most common cause of "wrong template ended up in
the inbox" bugs. Consumers needing replacement should remove(name)
first.
search uses simple JS Array.some / String.includes — no fuzzy
matching, no scoring. Tag matching is an OR (any tag in the criterion
must appear in the entry's tags); name matching is substring; category
and author are exact. The criteria are combined with logical AND.