# 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 ```ts 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` for that key: ```ts 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(); registry.register("welcome", welcomeTemplate); const welcomeT = registry.get("welcome"); // type: EmailTemplate | undefined ``` Search by tag intersection and category: ```ts registry.search({ tags: ["onboarding"], category: "transactional" }); ``` ## API Reference | Symbol | Kind | Description | | ------------------------- | -------- | ---------------------------------------------------- | | `TemplateRegistry` | class | The registry itself; implements `TemplateRegistryApi`. | | `createRegistry` | 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` upper bound. | | `TemplateRegistryApi` | type | The interface implemented by `TemplateRegistry`. | ## Implementation Notes The registry stores its entries in a single `Map` — 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.