-
Notifications
You must be signed in to change notification settings - Fork 2
/
lenses.ts
86 lines (76 loc) · 4.86 KB
/
lenses.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
type Fun<a,b> = (_:a) => b
type Without<T, K> = Pick<T, Exclude<keyof T, K>>
export const Without = <T, K extends keyof T>(key: K, { [key]: _, ...values }: T): Without<T, K> => values
export const Rename = <T, KOld extends keyof T, KNew extends string>(keyOld: KOld, keyNew: KNew, { [keyOld]: value, ...values }: T):
Without<T, KOld> & { [x in KNew]: T[KOld] } =>
({ ...values, ...{ [keyNew]: value } as { [x in KNew]: T[KOld] } })
export type SetField<fields, k extends keyof fields, v> = { [x in Exclude<keyof fields, k>]:fields[x] } & { [x in k]:v }
export type RenameField<fields, k extends keyof fields, newK extends string> = Without<fields, k> & { [x in newK]: fields[k] }
export type UpdateSort = "nested-eager" | "nested-lazy" | "inline-eager" | "inline-lazy"
export type UpdateInput<u extends UpdateSort, field> =
u extends "nested-eager" ? Entity<field> :
u extends "nested-lazy" ? Entity<field> :
u extends "inline-eager" ? field :
u extends "inline-lazy" ? field :
never
export type UpdateOutput<u extends UpdateSort, field> =
u extends "nested-eager" ? field :
u extends "inline-eager" ? field :
u extends "nested-lazy" ? Entity<field> :
u extends "inline-lazy" ? Entity<field> :
never
export interface Entity<fields extends Object> {
get: <k extends keyof fields>(...keys:k[]) => Pick<fields, k>
rename: <k extends keyof fields, newK extends string, v>(key:k, newKey:newK, f:Fun<fields[k], v>) => Entity<SetField<RenameField<fields, k, newK>, newK, v>>
set: <k extends keyof fields, v>(key:k, f:Fun<UpdateInput<"inline-lazy", fields[k]>, v>) => UpdateOutput<"inline-lazy", SetField<fields, k, v>>
setIn: <k extends keyof fields, v>(key:k, f:Fun<UpdateInput<"nested-lazy", fields[k]>, Entity<v>>) => UpdateOutput<"nested-lazy", SetField<fields, k, v>>
nested:NestedEntity<fields>,
inline:InlineEntity<fields>,
commit:() => fields
}
export interface NestedEntity<fields extends Object> {
eager: <k extends keyof fields, v>(key:k, f:Fun<UpdateInput<"nested-eager", fields[k]>, v>) => UpdateOutput<"nested-eager", SetField<fields, k, v>>,
lazy: <k extends keyof fields, v>(key:k, f:Fun<UpdateInput<"nested-lazy", fields[k]>, v>) => UpdateOutput<"nested-lazy", SetField<fields, k, v>>
}
export interface InlineEntity<fields extends Object> {
eager: <k extends keyof fields, v>(key:k, f:Fun<UpdateInput<"inline-eager", fields[k]>, v>) => UpdateOutput<"inline-eager", SetField<fields, k, v>>,
lazy: <k extends keyof fields, v>(key:k, f:Fun<UpdateInput<"inline-lazy", fields[k]>, v>) => UpdateOutput<"inline-lazy", SetField<fields, k, v>>
}
export const Entity = <fields extends Object>(fields:fields) : Entity<fields> => ({
get: <k extends keyof fields>(...keys:k[]) : Pick<fields, k> =>
keys.reduce((acc, k) => ({...acc, ...fields[k] }), {} as Pick<fields,k>),
nested:NestedEntity(fields),
inline:InlineEntity(fields),
rename: <k extends keyof fields, newK extends string, v>(key:k, newKey:newK, f:Fun<fields[k], v>) :
Entity<SetField<RenameField<fields, k, newK>, newK, v>> =>
Entity(Rename(key, newKey, {...fields, [key]:f(fields[key])})) as any,
set : function<k extends keyof fields, v>(this:Entity<fields>, key:k, f:Fun<UpdateInput<"inline-lazy", fields[k]>, v>)
: UpdateOutput<"inline-lazy", SetField<fields, k, v>> {
return this.inline.lazy(key, f)
},
setIn: function<k extends keyof fields, v>(this:Entity<fields>, key:k, f:Fun<UpdateInput<"nested-lazy", fields[k]>, Entity<v>>)
: UpdateOutput<"nested-lazy", SetField<fields, k, v>> {
return this.nested.lazy(key, x => f(x).commit())
},
commit:() : fields => fields,
})
export const NestedEntity = <fields extends Object>(fields:fields) : NestedEntity<fields> => ({
eager: <k extends keyof fields, v>(key:k, f:Fun<UpdateInput<"nested-eager", fields[k]>, v>)
: UpdateOutput<"nested-eager", SetField<fields, k, v>> =>
({...fields, [key]:f(Entity(fields[key]))}),
lazy: <k extends keyof fields, v>(key:k, f:Fun<UpdateInput<"nested-lazy", fields[k]>, v>)
: UpdateOutput<"nested-lazy", SetField<fields, k, v>> =>
Entity({...fields, [key]:f(Entity(fields[key]))})
})
export const InlineEntity = <fields extends Object>(fields:fields) : InlineEntity<fields> => ({
eager: <k extends keyof fields, v>(key:k, f:Fun<UpdateInput<"inline-eager", fields[k]>, v>)
: UpdateOutput<"inline-eager", SetField<fields, k, v>> =>
({...fields, [key]:f(fields[key])}),
lazy: <k extends keyof fields, v>(key:k, f:Fun<UpdateInput<"inline-lazy", fields[k]>, v>)
: UpdateOutput<"inline-lazy", SetField<fields, k, v>> =>
Entity({...fields, [key]:f(fields[key])})
})
export const setter = <fields extends Object, k extends keyof fields>(k:k, v:fields[k]) =>
(s:fields) => Entity<fields>(s).inline.eager<k, fields[k]>(k, _ => v)
export const Updater = <fields extends Object>() => <result>(f:Fun<Entity<fields>, result>) : Fun<fields, result> =>
fields => f(Entity(fields))