import { BUILTIN_RULE, CodecError, parseRuleMessage } from "../../../Yonexus.Protocol/src/index.js"; export type ClientRuleProcessor = (message: string) => unknown; export class ClientRuleRegistryError extends Error { constructor(message: string) { super(message); this.name = "ClientRuleRegistryError"; } } export interface ClientRuleRegistry { readonly size: number; registerRule(rule: string, processor: ClientRuleProcessor): void; hasRule(rule: string): boolean; dispatch(raw: string): boolean; getRules(): readonly string[]; } export class YonexusClientRuleRegistry implements ClientRuleRegistry { private readonly rules = new Map(); get size(): number { return this.rules.size; } registerRule(rule: string, processor: ClientRuleProcessor): void { const normalizedRule = this.normalizeRule(rule); if (this.rules.has(normalizedRule)) { throw new ClientRuleRegistryError( `Rule '${normalizedRule}' is already registered` ); } this.rules.set(normalizedRule, processor); } hasRule(rule: string): boolean { return this.rules.has(rule.trim()); } dispatch(raw: string): boolean { const parsed = parseRuleMessage(raw); const processor = this.rules.get(parsed.ruleIdentifier); if (!processor) { return false; } processor(raw); return true; } getRules(): readonly string[] { return [...this.rules.keys()]; } private normalizeRule(rule: string): string { const normalizedRule = rule.trim(); if (!normalizedRule) { throw new ClientRuleRegistryError("Rule identifier must be a non-empty string"); } if (normalizedRule === BUILTIN_RULE) { throw new ClientRuleRegistryError( `Rule identifier '${BUILTIN_RULE}' is reserved` ); } try { parseRuleMessage(`${normalizedRule}::probe`); } catch (error) { if (error instanceof CodecError) { throw new ClientRuleRegistryError(error.message); } throw error; } return normalizedRule; } } export function createClientRuleRegistry(): ClientRuleRegistry { return new YonexusClientRuleRegistry(); }