Skip to content

Commit c8e7409

Browse files
committed
Add type-safety to Record class
This commit adds ability to create type-safe Record entities by assigning type to Record's value. The implementation is based on adding type parameters to types, interfaces, and classes. It is implemented however in a backwards-compatible manner, so that opting in to type safety is optional. Examples: // before (unsafe) const record = new Record(['name'], ['Alice']) record.get('neam') // no errors record.toObject().neam[0] // run-time (late) error // after (safe) const record = new Record<Person>(['name'], ['Alice']) record.get('neam') // Error: does not exist record.toObject().neam[0] // design-time (early) error
1 parent f11494c commit c8e7409

File tree

2 files changed

+52
-15
lines changed

2 files changed

+52
-15
lines changed

test/types/record.test.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,25 @@
1919

2020
import Record from '../../types/record'
2121

22+
interface Person {
23+
name: string
24+
age: number
25+
}
26+
2227
const record1 = new Record(['name', 'age'], ['Alice', 20])
2328
const record2 = new Record(['name', 'age'], ['Bob', 22], { key: 'value' })
29+
const record3 = new Record<Person>(['name', 'age'], ['Carl', 24])
30+
2431
const isRecord1: boolean = record1 instanceof Record
2532
const isRecord2: boolean = record2 instanceof Record
33+
const isRecord3: boolean = record3 instanceof Record
2634

2735
const record1Keys: string[] = record1.keys
36+
const record3Keys: Array<keyof Person> = record3.keys
2837
const record1Length: number = record1.length
2938

3039
const record1Object: object = record1.toObject()
40+
const record3Object: Person = record3.toObject()
3141

3242
record1.forEach(() => {})
3343

@@ -37,11 +47,24 @@ record1.forEach((value: any, key: string) => {})
3747

3848
record1.forEach((value: any, key: string, record: Record) => {})
3949

50+
record3.forEach(
51+
(value: string | number, key: 'name' | 'age', record: Record<Person>) => {}
52+
)
53+
54+
const record3Mapped: [
55+
string | number,
56+
'name' | 'age',
57+
Record<Person>
58+
][] = record3.map((...args) => args)
59+
4060
const record1Has: boolean = record1.has(42)
4161
const record2Has: boolean = record1.has('key')
4262

43-
const record1Get1: any = record1.get(42)
44-
const record2Get1: any = record2.get('key')
63+
const record1Get1: any = record1.get('name')
64+
const record2Get1: any = record2.get('age')
65+
66+
const record1Get2: object = record1.get('name')
67+
const record2Get2: string[] = record2.get('age')
4568

46-
const record1Get2: object = record1.get(42)
47-
const record2Get2: string[] = record2.get('key')
69+
const record3Get1: string = record3.get('name')
70+
const record3Get2: number = record3.get('age')

types/record.d.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,43 @@
1717
* limitations under the License.
1818
*/
1919

20-
declare type Visitor = (value: any, key: string, record: Record) => void
21-
22-
declare type MapVisitor<T> = (value: any, key: string, record: Record) => T
20+
declare type Dict<Key extends PropertyKey = PropertyKey, Value = any> = {
21+
[K in Key]: Value
22+
}
2323

24-
declare class Record {
25-
keys: string[]
24+
declare type Visitor<
25+
Entries extends Dict = Dict,
26+
Key extends keyof Entries = keyof Entries
27+
> = MapVisitor<void, Entries, Key>
28+
29+
declare type MapVisitor<
30+
ReturnType,
31+
Entries extends Dict = Dict,
32+
Key extends keyof Entries = keyof Entries
33+
> = (value: Entries[Key], key: Key, record: Record<Entries>) => ReturnType
34+
35+
declare class Record<
36+
Entries extends Dict = Dict,
37+
Key extends keyof Entries = keyof Entries
38+
> {
39+
keys: Key[]
2640
length: number
2741

2842
constructor(
29-
keys: string[],
43+
keys: Key[],
3044
fields: any[],
3145
fieldLookup?: { [index: string]: string }
3246
)
3347

34-
forEach(visitor: Visitor): void
48+
forEach(visitor: Visitor<Entries, Key>): void
3549

36-
map<T>(visitor: MapVisitor<T>): T[]
50+
map<Value>(visitor: MapVisitor<Value, Entries, Key>): Value[]
3751

38-
toObject(): object
52+
toObject(): Entries
3953

40-
get(key: string | number): any
54+
get<K extends Key>(key: K): Entries[K]
4155

42-
has(key: string | number): boolean
56+
has(key: any): key is Key
4357
}
4458

4559
export default Record

0 commit comments

Comments
 (0)