Created
April 8, 2024 19:47
-
-
Save teyfix/96a007966547ceef0ea29f335d6dfffd to your computer and use it in GitHub Desktop.
Blueprint for exposing every possible path of an TypeScript interface
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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