UNPKG

polen

Version:

A framework for delightful GraphQL developer portals

135 lines (126 loc) 3.95 kB
import { type ExtendsExact, objPolicyFilter } from '#lib/kit-temp' import { Obj } from '@wollybeard/kit' import { never } from '@wollybeard/kit/language' import type { GetDataType, Mask } from './mask.js' /** * Type-level function that applies a mask to data. * * @template Data - The data type * @template M - The mask type * * Binary masks: * - show=true returns the data unchanged * - show=false returns undefined * * Properties masks: * - 'allow' mode returns Pick<Data, keys> * - 'deny' mode returns Omit<Data, keys> * - Non-objects throw an error at runtime */ // dprint-ignore export type Apply<Data, M extends Mask> = M extends { type: `binary`, show: boolean } ? M[`show`] extends true ? Data : undefined : M extends { type: `properties`, mode: string, properties: any[] } ? Data extends object ? M[`mode`] extends `allow` ? Pick<Data, Extract<M[`properties`][number], keyof Data>> : Omit<Data, Extract<M[`properties`][number], keyof Data>> : never // Non-objects not allowed with property masks : never /** * Apply mask to data with standard covariance. * * Data must be assignable to the mask's expected type (may have excess properties). * * @param data - The data to mask * @param mask - The mask to apply * @returns The masked data * * @example * ```ts * const user = { name: 'John', email: 'john@example.com', password: 'secret' } * const mask = Mask.pick<User>(['name', 'email']) * const safeUser = apply(user, mask) // { name: 'John', email: 'john@example.com' } * ``` */ export const apply = < data extends GetDataType<mask>, mask extends Mask, >(data: data, mask: mask): Apply<data, mask> => { return applyInternal(data, mask) as Apply<data, mask> } /** * Apply mask to partial data. * * Data may have only a subset of the mask's expected properties. * Useful when working with incomplete data or optional fields. * * @param data - The partial data to mask * @param mask - The mask to apply * @returns The masked data * * @example * ```ts * const partialUser = { name: 'John' } // missing email * const mask = Mask.pick<User>(['name', 'email']) * const result = applyPartial(partialUser, mask) // { name: 'John' } * ``` */ export const applyPartial = < data extends Partial<GetDataType<mask>>, mask extends Mask, >(data: data, mask: mask): Apply<data, mask> => { return applyInternal(data, mask) as Apply<data, mask> } /** * Apply mask to data with exact type matching. * * Data must exactly match the mask's expected type - no missing or excess properties. * Provides the strictest type checking. * * @param data - The data to mask (must exactly match expected type) * @param mask - The mask to apply * @returns The masked data * * @example * ```ts * type User = { name: string; email: string } * const mask = Mask.pick<User>(['name']) * * // This works - exact match * const user: User = { name: 'John', email: 'john@example.com' } * const result = applyExact(user, mask) * * // This fails - has extra property * const userWithExtra = { name: 'John', email: 'john@example.com', age: 30 } * const result2 = applyExact(userWithExtra, mask) // Type error! * ``` */ export const applyExact = < data, mask extends Mask, >( data: ExtendsExact<data, GetDataType<mask>>, mask: mask, ): Apply<data, mask> => { return applyInternal(data, mask) as Apply<data, mask> } // Internal implementation const applyInternal = (data: any, mask: Mask): any => { // ━ Handle binary mask if (mask.type === `binary`) { return mask.show ? data : undefined } // ━ Handle properties mask if (mask.type === `properties`) { // Properties mask requires object data if (!Obj.is(data)) { throw new Error(`Cannot apply properties mask to non-object data`) } return objPolicyFilter(mask.mode, data, mask.properties) } never() }