← Registry lesson
Lab

Registry

type Shortcode = (text: string) => string;

// The registry: shortcodes by name, filled at start-up, then sealed.
class ShortcodeRegistry {
  private entries = new Map<string, Shortcode>();
  private sealed = false;

  register(name: string, shortcode: Shortcode): void {
    if (this.sealed) throw new Error(`registration is closed: ${name}`);
    if (this.entries.has(name)) throw new Error(`already registered: ${name}`);
    this.entries.set(name, shortcode);
  }

  resolve(name: string): Shortcode {
    const shortcode = this.entries.get(name);
    if (!shortcode) throw new Error(`unknown shortcode: ${name}`);
    return shortcode;
  }

  names(): string[] {
    return [...this.entries.keys()].sort();
  }

  seal(): void {
    this.sealed = true;
  }
}

// What the theme and a plugin contribute. The renderer knows none of them.
function installTheme(registry: ShortcodeRegistry) {
  registry.register('note', (text) => `<aside>${text}</aside>`);
  registry.register('code', (text) => `<pre>${text}</pre>`);
}
function installKeyboardPlugin(registry: ShortcodeRegistry) {
  registry.register('kbd', (text) => `<kbd>${text}</kbd>`);
}

// The page renderer: one tag's name and inner text in, a fragment out.
function render(registry: ShortcodeRegistry, tag: string, text: string) {
  try {
    return registry.resolve(tag)(text);
  } catch (error) {
    return `[${(error as Error).message}]`;
  }
}

// Bootstrap: every contributor registers, then setup closes.
const registry = new ShortcodeRegistry();
installTheme(registry);
// A second theme, also enabled, ships its own note.
let clash = 'none';
try {
  registry.register('note', (text) => `<div class="callout">${text}</div>`);
} catch (error) {
  clash = (error as Error).message;
}
installKeyboardPlugin(registry);
registry.seal();
const picker = registry.names();

const noteHtml = render(registry, 'note', 'Back up first');
const kbdHtml = render(registry, 'kbd', 'Ctrl');
const missing = render(registry, 'quote', 'Hi');
console.log(`picker: ${picker.join(', ')}`);
console.log(`note: ${noteHtml}`);
console.log(`kbd: ${kbdHtml}`);
console.log(`quote: ${missing}`);
console.log(`second note: ${clash}`);