我是否可以创建一个TypeScript类型,其中某些属性是一个ID/ ID数组,可以扩展为引用其他类型



作为上下文,在我的数据库中,我有一对一和一对多的关系,我试图用typescript来表达。我不想创建一个类型的几个变体,而是想创建一个类型,允许我对展开使用我正在创建的实用程序类型(例如ExpandOne<>,ExpandAll<>)的关系。

简化工作例

规范

我想要的一切

这可能吗?

// UTILITIES
type ID = string
type Expandable<Entity> = ID | Entity
type ExpandableArray<Entity> = undefined | Entity[]
type Car<L extends any[]> = L extends [infer Head, ...infer _] ? Head : never
type Cdr<L extends any[]> = L extends [infer _, ...infer Rest] ? Rest : never
type ExpandOne<T extends Record<string, any>, Expand extends any[] | []> = {
[Key in keyof T]: T[Key] extends Expandable<infer Entity extends Record<string, any>>
? Key extends Car<Expand>
? ExpandOne<Entity, Cdr<Expand>>
: string
: T[Key]
}
// TODO: Call ExpandOne on multiple properties
// type ExpandAll
// DATABASE TYPES
interface PostCategory {
description: string
}
interface PostComment {
content: string
}
interface Post {
content: string
comments: ExpandableArray<PostComment>
category: Expandable<PostCategory>
}
interface User {
username: string
posts: Expandable<Post[]>
comments: ExpandableArray<PostComment>
}
// USAGE
// expand one level deep
const expandedUserPost: ExpandOne<User, ['posts']> = {} as any
expandedUserPost.posts
// expand two levels deep
const expandedUserPostComments: ExpandOne<User, ['posts', 'comments']> = {} as any
expandedUserPostComments.posts[0].comments
// expand multiple
const expandMultiple: ExpandAll<User, [['posts', 'comments'], ['comments']]> = {} as any
expandMultiple.posts[0].comments
expandMultiple.comments
// bonus!
// would be nice if ExpandAll<> assumed that any Expandable<> that hasn't been expanded is a string (database id) and any ExpandableArray<> that hasn't been expanded is undefined
const expandNonePost: ExpandAll<Post, []> = {} as any
expandNonePost.category // type should be string
// NOTE
// Expandable<> is similar to ExpandableArray<> 
// except when something is not exapnded instead of being undefined its a database id (string)
const expandPost: ExpandOne<Post, ['category']> = {} as any
expandPost.category.description

我想避免的

interface Posts {
content: string
}
interface User {
username: string
posts: undefined
}
// I would prefer not to create multiple variations of each type
interface UserExpandedPosts {
username: string
posts: Posts[]
}

大图

// Using this system I can create a JavaScript function that tells 
// my backend to expand relationships and my types at the same time
function fetchData(configFunction, expansions) {
return fetch()
}
const data = await fetchData(getUser(), [['posts'], ['somethingElse']])
// Assuming the function getUser() has an expandable database type 
// associated with it, fetchData would know how to both ask my 
// backend to expand the entity as well as expanding matching TypeScript type 
// In other words as long as my expansion doesn't fail, the return type of
// fetchData would always match the data (including expansions I asked for)
// UTILITIES
declare const explandBrand: unique symbol
type Expandable<Entity> = string & { [explandBrand]: Entity }
//   
type Expand1<T, E extends string[]> =
| T extends Expandable<infer X extends any[]> ? Expand1<X[number], E>[]
: T extends Expandable<infer X> ? Expand1<X, E>
: E extends [infer F extends keyof T, ...infer L extends string[]] ?
| {
[K in keyof T]: F extends K ? Expand1<T[K], L> : T[K]
}
: T;
type x1 = Expand1<User, ['posts']>
//   ^?
type x3 = Expand1<x1, ['comments']>
//   ^?
type x4 = Expand1<User, ['posts', 'comments']>
//   ^?

type Expand2<T, A extends string[][]> =
| A extends [infer F extends string[], ...infer L extends string[][]] ? Expand2<Expand1<T, F>, L>
: T
type x5 = Expand2<User, [['posts', 'comments'], ['comments']]>
//   ^?

游乐场

id是string,数组也是string,需要的话可以修改

相关内容

  • 没有找到相关文章

最新更新