Skip to content

Instantly share code, notes, and snippets.

@teyfix
Created April 8, 2024 19:47
Show Gist options
  • Save teyfix/96a007966547ceef0ea29f335d6dfffd to your computer and use it in GitHub Desktop.
Save teyfix/96a007966547ceef0ea29f335d6dfffd to your computer and use it in GitHub Desktop.
Blueprint for exposing every possible path of an TypeScript interface
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/ban-types */
import { Document } from 'mongoose';
/**
* Forbidden keys from mongoose Document type except _id.
*/
type _Forbidden = Exclude<keyof Document, '_id'>;
/**
* A string that starts with a dollar sign.
*/
type _Dollar = `$${string}`;
/**
* Whitelisted keys.
*/
type _Whitelist<K> = K extends string
? K extends _Forbidden
? never
: K extends _Dollar
? never
: K
: never;
/**
* Allowed keys from an object.
*/
type _Allowed<T> = _Whitelist<keyof T>;
/**
* Concatenates two strings with a dot.
*/
type _Concat<A, B> = A extends string
? B extends string
? `${A}.${B}`
: A
: B;
/**
* Recursively extracts possible paths from an object.
* It keeps track of the seen types to prevent infinite recursion.
* - Excluded properties:
* - Any property that is a function.
* - Any property that comes from mongoose's Document type, except _id.
* - Any property that starts with a dollar sign.
*
* *WARNING*
* This type is optimized for performance but still
* may cause performance issues on low-end devices.
*/
type _Paths<Root, TSeen = Root, Prefix = unknown> =
Root extends Array<infer Item>
? _Paths<Item, TSeen, Prefix>
: Root extends object
? {
[K in _Allowed<Root>]: Root[K] extends Function
? never
:
| _Concat<Prefix, K>
| (Root[K] extends TSeen
? never
: _Paths<Root[K], Root[K] | TSeen, _Concat<Prefix, K>>);
}[_Allowed<Root>]
: never;
/**
* Extracts possible paths from an object.
*
* @example
* ```ts
* interface SimpleUser {
* username: string;
* stats: {
* followers: number;
* following: number;
* };
* posts: Array<{
* likes: number;
* content: string;
* createdAt: Date;
* }>;
* updatedAt: Date;
* createdAt: Date;
* }
*
* type SimpleUserPaths = Blueprint<SimpleUser>;
* ```
* should be equivalent to:
* ```ts
* type SimpleUserPaths =
* | 'username'
* | 'stats'
* | 'posts'
* | 'updatedAt'
* | 'createdAt'
* | 'stats.followers'
* | 'stats.following'
* | 'posts.createdAt'
* | 'posts.likes'
* | 'posts.content';
* ```
*/
export type Blueprint<Root> = _Paths<Root>;
/**
* Recursively extracts possible paths from an object
* except the ones that does not match the filter.
* - It keeps track of the seen types to prevent infinite recursion.
* - Excluded properties:
* - Any property that is a function.
* - Any property that comes from mongoose's Document type, except _id.
* - Any property that starts with a dollar sign.
*
* *WARNING*
* This type is optimized for performance but still
* may cause performance issues on low-end devices.
*/
type _PathsOnly<Root, Filter, TSeen = Root, Prefix = unknown> =
Root extends Array<infer Item>
? _PathsOnly<Item, Filter, TSeen, Prefix>
: Root extends object
? {
[K in _Allowed<Root>]: Root[K] extends Function
? never
:
| (Root[K] extends TSeen
? never
: _PathsOnly<
Root[K],
Filter,
Root[K] | TSeen,
_Concat<Prefix, K>
>)
| (Root[K] extends Filter ? _Concat<Prefix, K> : never);
}[_Allowed<Root>]
: never;
/**
* Extracts possible paths from an object that the type matches the filter.
*
* @example
* ```ts
* interface SimpleUser {
* username: string;
* stats: {
* followers: number;
* following: number;
* };
* posts: Array<{
* likes: number;
* content: string;
* createdAt: Date;
* }>;
* updatedAt: Date;
* createdAt: Date;
* }
*
* type SimpleUserSortablePaths = Blueprint<SimpleUser, number | Date>;
* ```
* should be equivalent to:
* ```ts
* type SimpleUserSortablePaths =
* | 'updatedAt'
* | 'createdAt'
* | 'stats.followers'
* | 'stats.following'
* | 'posts.createdAt'
* | 'posts.likes';
* ```
*/
export type BlueprintOnly<Root, Filter> = _PathsOnly<Root, Filter>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment