/** * Subscriber-match logic. Pure: given an event and a subscriber's * match rule, decide whether to deliver. Glob support is intentionally * tiny (`*` and `**`); the existing dot-glob matcher in pattern.ts * targets local-bus event names with strict segment semantics — a * different problem. */ import type { PublishEvent, Subscriber, SubscriberMatch } from './types'; /** * Test a glob against a literal package name. `*` matches zero or * more non-slash characters; `**` matches across slashes (useful for * scope walks like `@celilo/**` matching `@celilo/sub/util`). Other * regex meta-characters are escaped literally so package names can * contain `.`/`+`/etc. without surprise. * * Pure — no caching, regex is rebuilt per call. Subscriber lists are * small and matching is per-publish, so allocations don't matter. */ export function globMatchPackage(pattern: string, name: string): boolean { // Tokenize so `**` doesn't collide with `*` during escape. const tokens = pattern.split(/(\*\*|\*)/); let regex = '^'; for (const token of tokens) { if (token === '**') regex += '.*'; else if (token === '*') regex += '[^/]*'; else regex += escapeRegex(token); } regex += '$'; return new RegExp(regex).test(name); } function escapeRegex(s: string): string { return s.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); } /** * Match check for a single (event, match-rule) pair. Pure; every * non-undefined field in the rule must match the corresponding field * on the event. Absent fields are wildcards. */ export function matchesRule(event: PublishEvent, rule: SubscriberMatch): boolean { if (rule.registry !== undefined && event.registry !== rule.registry) return false; if (rule.tag !== undefined && event.tag !== rule.tag) return false; if ( rule.packagePattern !== undefined && !globMatchPackage(rule.packagePattern, event.package.name) ) { return false; } return true; } /** * Filter subscribers down to those whose match rules fire for this * event. Caller fans out to the result. */ export function subscribersFor(event: PublishEvent, subscribers: Subscriber[]): Subscriber[] { return subscribers.filter((s) => matchesRule(event, s.match)); }