rspace-online/shared/membrane.ts

63 lines
1.8 KiB
TypeScript

/**
* 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<string, number> = {
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<T>(
items: Record<string, T>,
callerRole: string,
getVis: (item: T) => ObjectVisibility | null | undefined = (item) =>
(item as any).visibility,
): Record<string, T> {
const result: Record<string, T> = {};
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<T>(
items: T[],
callerRole: string,
getVis: (item: T) => ObjectVisibility | null | undefined = (item) =>
(item as any).visibility,
): T[] {
return items.filter((item) => isVisibleTo(getVis(item), callerRole));
}