| export type ChannelMatchSource = "direct" | "parent" | "wildcard"; |
|
|
| export type ChannelEntryMatch<T> = { |
| entry?: T; |
| key?: string; |
| wildcardEntry?: T; |
| wildcardKey?: string; |
| parentEntry?: T; |
| parentKey?: string; |
| matchKey?: string; |
| matchSource?: ChannelMatchSource; |
| }; |
|
|
| export function applyChannelMatchMeta< |
| TResult extends { matchKey?: string; matchSource?: ChannelMatchSource }, |
| >(result: TResult, match: ChannelEntryMatch<unknown>): TResult { |
| if (match.matchKey && match.matchSource) { |
| result.matchKey = match.matchKey; |
| result.matchSource = match.matchSource; |
| } |
| return result; |
| } |
|
|
| export function resolveChannelMatchConfig< |
| TEntry, |
| TResult extends { matchKey?: string; matchSource?: ChannelMatchSource }, |
| >(match: ChannelEntryMatch<TEntry>, resolveEntry: (entry: TEntry) => TResult): TResult | null { |
| if (!match.entry) { |
| return null; |
| } |
| return applyChannelMatchMeta(resolveEntry(match.entry), match); |
| } |
|
|
| export function normalizeChannelSlug(value: string): string { |
| return value |
| .trim() |
| .toLowerCase() |
| .replace(/^#/, "") |
| .replace(/[^a-z0-9]+/g, "-") |
| .replace(/^-+|-+$/g, ""); |
| } |
|
|
| export function buildChannelKeyCandidates(...keys: Array<string | undefined | null>): string[] { |
| const seen = new Set<string>(); |
| const candidates: string[] = []; |
| for (const key of keys) { |
| if (typeof key !== "string") { |
| continue; |
| } |
| const trimmed = key.trim(); |
| if (!trimmed || seen.has(trimmed)) { |
| continue; |
| } |
| seen.add(trimmed); |
| candidates.push(trimmed); |
| } |
| return candidates; |
| } |
|
|
| export function resolveChannelEntryMatch<T>(params: { |
| entries?: Record<string, T>; |
| keys: string[]; |
| wildcardKey?: string; |
| }): ChannelEntryMatch<T> { |
| const entries = params.entries ?? {}; |
| const match: ChannelEntryMatch<T> = {}; |
| for (const key of params.keys) { |
| if (!Object.prototype.hasOwnProperty.call(entries, key)) { |
| continue; |
| } |
| match.entry = entries[key]; |
| match.key = key; |
| break; |
| } |
| if (params.wildcardKey && Object.prototype.hasOwnProperty.call(entries, params.wildcardKey)) { |
| match.wildcardEntry = entries[params.wildcardKey]; |
| match.wildcardKey = params.wildcardKey; |
| } |
| return match; |
| } |
|
|
| export function resolveChannelEntryMatchWithFallback<T>(params: { |
| entries?: Record<string, T>; |
| keys: string[]; |
| parentKeys?: string[]; |
| wildcardKey?: string; |
| normalizeKey?: (value: string) => string; |
| }): ChannelEntryMatch<T> { |
| const direct = resolveChannelEntryMatch({ |
| entries: params.entries, |
| keys: params.keys, |
| wildcardKey: params.wildcardKey, |
| }); |
|
|
| if (direct.entry && direct.key) { |
| return { ...direct, matchKey: direct.key, matchSource: "direct" }; |
| } |
|
|
| const normalizeKey = params.normalizeKey; |
| if (normalizeKey) { |
| const normalizedKeys = params.keys.map((key) => normalizeKey(key)).filter(Boolean); |
| if (normalizedKeys.length > 0) { |
| for (const [entryKey, entry] of Object.entries(params.entries ?? {})) { |
| const normalizedEntry = normalizeKey(entryKey); |
| if (normalizedEntry && normalizedKeys.includes(normalizedEntry)) { |
| return { |
| ...direct, |
| entry, |
| key: entryKey, |
| matchKey: entryKey, |
| matchSource: "direct", |
| }; |
| } |
| } |
| } |
| } |
|
|
| const parentKeys = params.parentKeys ?? []; |
| if (parentKeys.length > 0) { |
| const parent = resolveChannelEntryMatch({ entries: params.entries, keys: parentKeys }); |
| if (parent.entry && parent.key) { |
| return { |
| ...direct, |
| entry: parent.entry, |
| key: parent.key, |
| parentEntry: parent.entry, |
| parentKey: parent.key, |
| matchKey: parent.key, |
| matchSource: "parent", |
| }; |
| } |
| if (normalizeKey) { |
| const normalizedParentKeys = parentKeys.map((key) => normalizeKey(key)).filter(Boolean); |
| if (normalizedParentKeys.length > 0) { |
| for (const [entryKey, entry] of Object.entries(params.entries ?? {})) { |
| const normalizedEntry = normalizeKey(entryKey); |
| if (normalizedEntry && normalizedParentKeys.includes(normalizedEntry)) { |
| return { |
| ...direct, |
| entry, |
| key: entryKey, |
| parentEntry: entry, |
| parentKey: entryKey, |
| matchKey: entryKey, |
| matchSource: "parent", |
| }; |
| } |
| } |
| } |
| } |
| } |
|
|
| if (direct.wildcardEntry && direct.wildcardKey) { |
| return { |
| ...direct, |
| entry: direct.wildcardEntry, |
| key: direct.wildcardKey, |
| matchKey: direct.wildcardKey, |
| matchSource: "wildcard", |
| }; |
| } |
|
|
| return direct; |
| } |
|
|
| export function resolveNestedAllowlistDecision(params: { |
| outerConfigured: boolean; |
| outerMatched: boolean; |
| innerConfigured: boolean; |
| innerMatched: boolean; |
| }): boolean { |
| if (!params.outerConfigured) { |
| return true; |
| } |
| if (!params.outerMatched) { |
| return false; |
| } |
| if (!params.innerConfigured) { |
| return true; |
| } |
| return params.innerMatched; |
| } |
|
|