Skip to content

Commit 33c87d6

Browse files
committed
feat(types): add type KeyFromValue<T, V>
1 parent 98043f9 commit 33c87d6

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

packages/types/src/lib/common.ts

+8
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,11 @@ export type AllValues<T extends Record<PropertyKey, PropertyKey>> = {
5454
[P in keyof T]: { key: P, value: T[P] }
5555
}[keyof T]
5656

57+
/**
58+
* @example `type R = KeyFromValue<{uid: 'tbUserUid'}, 'tbUserUid'>` got `uid`
59+
* @ref https://stackoverflow.com/a/57726844
60+
*/
61+
export type KeyFromValue<T extends Record<PropertyKey, PropertyKey>, V> = {
62+
[key in keyof T]: V extends T[key] ? key : never
63+
}[keyof T]
64+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/* eslint-disable node/no-extraneous-import */
2+
import {
3+
basename,
4+
join,
5+
} from '@waiting/shared-core'
6+
import * as assert from 'power-assert'
7+
8+
import { KeyFromValue } from '../../src/index'
9+
10+
import {
11+
Alias,
12+
AliasRecord,
13+
AliasRecord2,
14+
alias,
15+
alias2,
16+
} from './test-model'
17+
18+
19+
const filename = basename(__filename)
20+
21+
describe(filename, () => {
22+
23+
describe('should AllValues works', () => {
24+
it('type AliasRecord', () => {
25+
const ret: KeyFromValue<AliasRecord, 'tbUserUid'> = 'uid'
26+
assert(ret === 'uid')
27+
28+
const ret2: KeyFromValue<AliasRecord, 'tbUserUid' | 'tbUserName'> = 'uid'
29+
assert(ret2 === 'uid')
30+
31+
const ret3: KeyFromValue<AliasRecord, 'tbUserUid' | 'tbUserName'> = 'name'
32+
assert(ret3 === 'name')
33+
})
34+
it('type AliasRecord2 (dupliate value)', () => {
35+
const ret: KeyFromValue<AliasRecord2, 'tbUserUid'> = 'uid'
36+
assert(ret === 'uid')
37+
const ret2: KeyFromValue<AliasRecord2, 'tbUserUid'> = 'foo'
38+
assert(ret2 === 'foo')
39+
40+
const ret3: KeyFromValue<
41+
{uid: 'tbUserUid', name: 'tbUserName', foo: 'tbUserUid'},
42+
'tbUserUid'
43+
> = 'uid'
44+
assert(ret3 === 'uid')
45+
const ret4: KeyFromValue<
46+
{uid: 'tbUserUid', name: 'tbUserName', foo: 'tbUserUid'},
47+
'tbUserUid'
48+
> = 'foo'
49+
assert(ret4 === 'foo')
50+
})
51+
it('type inline', () => {
52+
const ret: KeyFromValue<
53+
{uid: 'tbUserUid', name: 'tbUserName'},
54+
'tbUserUid'
55+
> = 'uid'
56+
assert(ret === 'uid')
57+
58+
const ret2: KeyFromValue<
59+
{uid: 'tbUserUid', name: 'tbUserName'},
60+
'tbUserUid'
61+
> = 'uid'
62+
assert(ret2 === 'uid')
63+
64+
const ret3: KeyFromValue<
65+
{uid: 'tbUserUid', name: 'tbUserName'},
66+
'tbUserUid' | 'tbUserName'
67+
> = 'name'
68+
assert(ret3 === 'name')
69+
})
70+
it('type Alias (w/o auto-complete)', () => {
71+
const ret: KeyFromValue<Alias, 'tbUserUid'> = 'uid'
72+
assert(ret === 'uid')
73+
74+
const ret2: KeyFromValue<Alias, 'tbUserUid' | 'tbUserName'> = 'uid'
75+
assert(ret2 === 'uid')
76+
77+
const ret3: KeyFromValue<Alias, 'tbUserUid' | 'tbUserName'> = 'name'
78+
assert(ret3 === 'name')
79+
})
80+
81+
it('constant alias', () => {
82+
const ret: KeyFromValue<typeof alias, 'tbUserUid'> = 'uid'
83+
assert(ret === 'uid')
84+
85+
const ret2: KeyFromValue<typeof alias, 'tbUserUid' | 'tbUserName'> = 'uid'
86+
assert(ret2 === 'uid')
87+
const ret3: KeyFromValue<typeof alias, 'tbUserUid' | 'tbUserName'> = 'name'
88+
assert(ret3 === 'name')
89+
})
90+
it('constant alias2 (duplicate value)', () => {
91+
const ret: KeyFromValue<typeof alias2, 'tbUserUid'> = 'uid'
92+
assert(ret === 'uid')
93+
const ret2: KeyFromValue<typeof alias2, 'tbUserUid'> = 'foo'
94+
assert(ret2 === 'foo')
95+
96+
const ret3: KeyFromValue<typeof alias2, 'tbUserUid' | 'tbUserName'> = 'uid'
97+
assert(ret3 === 'uid')
98+
const ret4: KeyFromValue<typeof alias2, 'tbUserUid' | 'tbUserName'> = 'name'
99+
assert(ret4 === 'name')
100+
const ret5: KeyFromValue<typeof alias2, 'tbUserUid' | 'tbUserName'> = 'foo'
101+
assert(ret5 === 'foo')
102+
})
103+
104+
it('invalid type inline', () => {
105+
// @ts-expect-error
106+
const ret: KeyFromValue<{uid: 'tbUserUid', name: 'tbUserName'}, 'foo'> = 'uid' // never
107+
})
108+
it('invalid constant alias', () => {
109+
// @ts-expect-error
110+
const ret: KeyFromValue<typeof alias, 'foo'> = 'uid' // never
111+
})
112+
it('invalid constant alias2', () => {
113+
// @ts-expect-error
114+
const ret: KeyFromValue<typeof alias2, 'foo'> = 'uid' // never
115+
})
116+
})
117+
118+
119+
})
120+
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
export interface Alias {
3+
uid: 'tbUserUid'
4+
name: 'tbUserName'
5+
[k: string]: string
6+
}
7+
8+
interface AliasWithoutSign {
9+
uid: 'tbUserUid'
10+
name: 'tbUserName'
11+
}
12+
export type AliasRecord = Pick<AliasWithoutSign, keyof AliasWithoutSign>
13+
14+
interface Alias2 {
15+
uid: 'tbUserUid'
16+
name: 'tbUserName'
17+
foo: 'tbUserUid' // <-- duplicate value tbUserUid
18+
}
19+
export type AliasRecord2 = Pick<Alias2, keyof Alias2>
20+
21+
22+
export const alias = {
23+
uid: 'tbUserUid',
24+
name: 'tbUserName',
25+
} as const
26+
27+
export const alias2 = {
28+
uid: 'tbUserUid',
29+
name: 'tbUserName',
30+
foo: 'tbUserUid', // <-- duplicate value tbUserUid
31+
} as const
32+

0 commit comments

Comments
 (0)