我的系统权限定义为字符串[],如下所示
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
的完整路径,其中K
是Action
。我们现在可以从这个结果中infer
Key
,并在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
}