/** * Object Visibility Membrane — per-object access filtering. * * Provides a soft membrane where individual items (tasks, docs, events, etc.) * can be restricted to specific role tiers. Items default to 'viewer' (visible * to everyone) when no visibility is set. * * Importable by both server and client code. */ export type ObjectVisibility = 'viewer' | 'member' | 'moderator' | 'admin'; export const DEFAULT_VISIBILITY: ObjectVisibility = 'viewer'; const ROLE_LEVELS: Record = { viewer: 0, member: 1, moderator: 2, admin: 3, }; /** Check if a caller with `callerRole` can see an item with `visibility`. */ export function isVisibleTo( visibility: ObjectVisibility | null | undefined, callerRole: string, ): boolean { const vis = visibility ?? DEFAULT_VISIBILITY; const required = ROLE_LEVELS[vis] ?? 0; const actual = ROLE_LEVELS[callerRole] ?? 0; return actual >= required; } /** * Filter a Record of items by visibility. * @param getVis - accessor for the visibility field (defaults to `item.visibility`) */ export function filterByVisibility( items: Record, callerRole: string, getVis: (item: T) => ObjectVisibility | null | undefined = (item) => (item as any).visibility, ): Record { const result: Record = {}; for (const [key, item] of Object.entries(items)) { if (isVisibleTo(getVis(item), callerRole)) { result[key] = item; } } return result; } /** * Filter an array of items by visibility. * @param getVis - accessor for the visibility field (defaults to `item.visibility`) */ export function filterArrayByVisibility( items: T[], callerRole: string, getVis: (item: T) => ObjectVisibility | null | undefined = (item) => (item as any).visibility, ): T[] { return items.filter((item) => isVisibleTo(getVis(item), callerRole)); }