polen
Version:
A framework for delightful GraphQL developer portals
245 lines (222 loc) • 6.09 kB
text/typescript
//
//
//
//
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Mask
//
//
/**
* A mask that can either hide/show data entirely (BinaryMask) or
* selectively hide/show object properties (PropertiesMask).
*
* @template $Data - The data type being masked
*/
export type Mask<$Data = any> = BinaryMask<$Data> | PropertiesMask<$Data extends object ? $Data : object>
/**
* Create a mask based on the provided options.
*
* @param options - Mask configuration:
* - `boolean`: Creates a binary mask (true = show, false = hide)
* - `string[]`: Creates a properties mask that allows only the specified keys
* - `object`: Creates a properties mask based on true/false values per key
*
* @returns A mask that can be applied to data
*
* @example
* ```ts
* // Binary mask
* const showAll = create(true)
* const hideAll = create(false)
*
* // Properties mask with array
* const allowMask = create<User>(['name', 'email'])
*
* // Properties mask with object
* const objectMask = create<User>({
* name: true,
* email: true,
* password: false
* })
* ```
*/
export const create = <$Data = unknown>(
options: InferOptions<$Data>,
): Mask<$Data> => {
if (typeof options === `boolean`) {
return createBinary(options) as any
}
// Array input -> PropertiesMask with 'allow' mode
if (Array.isArray(options)) {
return createProperties(`allow`, options as any) as any
}
// Object input -> PropertiesMask based on true/false values
const entries = Object.entries(options)
const allowedKeys = entries
.filter(([_, include]) => include === true)
.map(([key]) => key)
const deniedKeys = entries
.filter(([_, include]) => include === false)
.map(([key]) => key)
// If we have denied keys, use deny mode
if (deniedKeys.length > 0 && allowedKeys.length === 0) {
return createProperties(`deny`, deniedKeys) as any
}
// Default to allow mode with allowed keys
return createProperties(`allow`, allowedKeys) as any
}
/**
* Valid options for creating a mask for the given data type.
*
* @template $Data - The data type to be masked
*/
export type InferOptions<$Data> = unknown extends $Data ? boolean | string[] | Record<string, boolean>
: $Data extends object ? (
| boolean
| (keyof $Data)[]
| Partial<
{
[K in keyof $Data]: boolean
}
>
)
: boolean
//
//
//
//
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • PropertiesMask
//
//
/**
* A mask that selectively shows or hides object properties.
*
* @template $Data - The object type being masked
*/
export interface PropertiesMask<$Data extends object = object> {
type: `properties`
/** Whether to allow only specified properties or deny them */
mode: `allow` | `deny`
/** The list of property keys to allow or deny */
properties: (keyof $Data)[]
}
/**
* Create a properties mask.
*
* @param mode - 'allow' to show only specified properties, 'deny' to hide them
* @param properties - Array of property keys to allow or deny
* @returns A PropertiesMask
*/
export const createProperties = <$Data extends object = object>(
mode: `allow` | `deny`,
properties: (keyof $Data)[],
): PropertiesMask<$Data> => ({
type: `properties`,
mode,
properties,
})
//
//
//
//
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • BinaryMask
//
//
/**
* A mask that either shows or hides data entirely.
*
* @template _$Data - The data type being masked (used for type inference)
*/
export interface BinaryMask<_$Data = any> {
type: `binary`
/** Whether to show (true) or hide (false) the data */
show: boolean
}
/**
* Create a binary mask.
*
* @param show - Whether to show (true) or hide (false) the data
* @returns A BinaryMask
*/
export const createBinary = <$Data = any>(show: boolean): BinaryMask<$Data> => ({
type: `binary`,
show,
})
//
//
//
//
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Convenience Constructors with Semantic Names
//
//
/**
* Create a mask that shows all data.
* @returns A BinaryMask with show=true
*/
export const show = (): BinaryMask => ({
type: `binary`,
show: true,
})
/**
* Create a mask that hides all data.
* @returns A BinaryMask with show=false
*/
export const hide = (): BinaryMask => ({
type: `binary`,
show: false,
})
/**
* Create a mask that shows only the specified properties.
*
* @param properties - Array of property keys to show
* @returns A PropertiesMask in 'allow' mode
*
* @example
* ```ts
* const userMask = pick<User>(['name', 'email'])
* // Only 'name' and 'email' will be shown
* ```
*/
export const pick = <$Data extends object = object>(
properties: (keyof $Data)[],
): PropertiesMask<$Data> => ({
type: `properties`,
mode: `allow`,
properties,
})
/**
* Create a mask that hides the specified properties.
*
* @param properties - Array of property keys to hide
* @returns A PropertiesMask in 'deny' mode
*
* @example
* ```ts
* const userMask = omit<User>(['password', 'ssn'])
* // Everything except 'password' and 'ssn' will be shown
* ```
*/
export const omit = <$Data extends object = object>(
properties: (keyof $Data)[],
): PropertiesMask<$Data> => ({
type: `properties`,
mode: `deny`,
properties,
})
//
//
//
//
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Utilities
//
//
/**
* Extract the data type from a mask.
*
* @template $Mask - The mask type
* @returns The data type the mask is designed for
*/
// dprint-ignore
export type GetDataType<$Mask extends Mask> =
$Mask extends BinaryMask<infer $Data> ? $Data :
$Mask extends PropertiesMask<infer $Data> ? $Data
: never