Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
# template-registry
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<TProps>`
|
||||
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<MyMap>();
|
||||
registry.register("welcome", welcomeTemplate);
|
||||
const welcomeT = registry.get("welcome"); // type: EmailTemplate<WelcomeProps> | undefined
|
||||
```
|
||||
|
||||
Search by tag intersection and category:
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user