定义一个嵌套的Record类型,它在遍历时缩小



我的系统权限定义为字符串[],如下所示

const stringVals = [
'create:user',
'update:user',
'delete:user',
'create:document',
'update:document',
'delete:document',
'delete:workflow',
'run:workflow',
] as const

我有以下类型

type StringVals = typeof stringVals[number]
type PermissionVerb<T extends string> = T extends `${infer Action}:${string}` ? Action : never
type PermissionKey<T extends string, V extends PermissionVerb<T>> = T extends `${V}:${infer Key}` ? Key : never

对于

这样的内容非常有用
type Verbs = PermissionVerbs<StringVals> // 'create' | 'update' | 'delete' | 'run'
type CreateKeys = PermissionKey<StringVals, 'create'> // 'user' | 'document'
type RunKeys = PermissionKey<StringVals, 'run'> // 'workflow'

我希望创建一个权限对象,看起来像这样

{
create: {
user: true,
document: true
},
update: {
user: true,
document: true
},
delete: {
user: true,
document: true,
workflow: true,
},
run: {
workflow: true,
}
}

它定义了我正在纠结的对象的正确类型。

type PermissionsObject<T extends string> = Record<PermissionVerb<T>, Record<PermissionKey<T, PermissionVerb<T>>, boolean>>
function createPermissionsObject<T extends string>(permissions: Readonly<T[]>): PermissionsObject<T> {
throw 'not implemented'
}

这个问题是第一个记录不会缩小内部记录。所以run的外部对象键,返回所有东西,而不是像我想的那样只返回workflow

const permissions = createPermissionsObject(stringVals)
permissions.run.user // expect error

打印稿操场

我们必须使用映射类型,以便每个键K可以用来计算其对应的类型。

type PermissionsObject<T extends string> = {
[K in PermissionVerb<T>]: 
Extract<T, `${K}${string}`> extends `${string}:${infer Key}`
? Record<Key, boolean>
: never
}

K可以与Extract一起使用,以获得T的完整路径,其中KAction。我们现在可以从这个结果中inferKey,并在Record<Key, boolean>中使用它。

const permissions = createPermissionsObject(stringVals)
permissions.run.workflow
permissions.create.user
permissions.run.user
//              ^^^^ Property 'user' does not exist on type 
//                   'Record<"workflow", boolean>'

游乐场

奖金:

短了几个字符,但结果相同。

type PermissionsObject<T extends string> = {
[K in T as PermissionVerb<K>]: 
[K] extends [`${string}:${infer Key}`]
? Record<Key, boolean>
: never
}

最新更新