Files
skills/docs/communication/emails/email-templates/src/template-registry/template-registry.md
2026-05-29 08:20:56 +01:00

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() / size accessor.
  • search(criteria) with name (case-insensitive substring), category (exact), tag (any-intersection), author (exact) filters — combinable.
  • remove(name) / clear() for lifecycle management.
  • Optional defaultTheme exposed 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.