-
Notifications
You must be signed in to change notification settings - Fork 27
/
Struct.ts
210 lines (194 loc) · 5.17 KB
/
Struct.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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/**
* This module targets objects in the sense of product types. For objects in
* the sense of maps see the `Record` module.
*
* @since 0.14.0
*/
import * as A from "fp-ts/Array"
import { pipe } from "fp-ts/function"
import { uncurry2 } from "./Function"
import { fanout } from "./Tuple"
/**
* Merge two structs together. For merging many identical structs, instead
* consider defining a semigroup.
*
* @example
* import { merge } from 'fp-ts-std/Struct'
*
* assert.deepStrictEqual(merge({ a: 1, b: 2 })({ b: 'two', c: true }), { a: 1, b: 'two', c: true })
*
* @category 3 Functions
* @since 0.14.0
*/
// This combination of type arguments works with both partial application and
// the likes of `uncurry2`.
export const merge =
<A, B>(x: A) =>
<C extends B>(y: C): A & C => ({ ...x, ...y })
/**
* Pick a set of keys from a struct. The value-level equivalent of the `Pick`
* type.
*
* @example
* import { pick } from 'fp-ts-std/Struct'
* import { pipe } from 'fp-ts/function'
*
* const picked = pipe(
* { a: 1, b: 'two', c: [true] },
* pick(['a', 'c'])
* )
*
* assert.deepStrictEqual(picked, { a: 1, c: [true] })
*
* @category 3 Functions
* @since 0.14.0
*/
export const pick =
<A extends object, K extends keyof A>(ks: Array<K>) =>
(x: A): Pick<A, K> =>
// I don't believe there's any reasonable way to model this sort of
// transformation in the type system without an assertion - at least here
// it's in a single reused place.
pipe(
ks,
A.reduce({} as Pick<A, K>, (ys, k) =>
merge(ys)(k in x ? { [k]: x[k] } : {}),
),
)
/**
* Like `pick`, but allows you to specify the input struct upfront.
*
* @example
* import { pickFrom } from 'fp-ts-std/Struct'
*
* type MyType = { a: number; b: string; c: ReadonlyArray<boolean> }
* const picked = pickFrom<MyType>()(['a', 'c'])
*
* assert.deepStrictEqual(picked({ a: 1, b: 'two', c: [true] }), { a: 1, c: [true] })
*
* @category 3 Functions
* @since 0.14.0
*/
export const pickFrom = <A extends object>(): (<K extends keyof A>(
ks: Array<K>,
) => (x: A) => Pick<A, K>) => pick
/**
* Get the value for a key in a struct.
*
* @example
* import { get } from 'fp-ts-std/Struct'
*
* type Person = { name: string; age: number }
* const person: Person = { name: 'Albert', age: 76 }
*
* const getName = get('name')
*
* assert.strictEqual(getName(person), 'Albert')
*
* @category 3 Functions
* @since 0.17.0
*/
export const get =
<K extends string>(k: K) =>
<A>(x: Record<K, A>): A =>
x[k]
/**
* Omit a set of keys from a struct. The value-level equivalent of the `Omit`
* type.
*
* @example
* import { omit } from 'fp-ts-std/Struct'
*
* const sansB = omit(['b'])
*
* assert.deepStrictEqual(sansB({ a: 1, b: 'two', c: [true] }), { a: 1, c: [true] })
*
* @category 3 Functions
* @since 0.14.0
*/
export const omit =
<K extends string>(ks: Array<K>) =>
<V, A extends Record<K, V>>(x: A): Omit<A, K> => {
const y = { ...x }
for (const k of ks) {
delete y[k]
}
return y as Omit<A, K>
}
/**
* Like `omit`, but allows you to specify the input struct upfront.
*
* @example
* import { omitFrom } from 'fp-ts-std/Struct'
*
* type MyType = { a: number; b: string; c: ReadonlyArray<boolean> }
* const sansB = omitFrom<MyType>()(['b'])
*
* assert.deepStrictEqual(sansB({ a: 1, b: 'two', c: [true] }), { a: 1, c: [true] })
*
* @category 3 Functions
* @since 0.15.0
*/
export const omitFrom = <A>(): (<K extends keyof A & string>(
ks: Array<K>,
) => (x: A) => Omit<A, K>) => omit
type OptionalKeys<O extends object> = {
[K in keyof O]-?: Record<string, unknown> extends Pick<O, K> ? K : never
}[keyof O]
type Exact<A extends object, B extends A> = A &
Record<Exclude<keyof B, keyof A>, never>
/**
* Provide default values for an object with optional properties.
*
* @example
* import { withDefaults } from 'fp-ts-std/Struct'
* import { pipe } from 'fp-ts/function'
*
* const aOptB: { a: number; b?: string } = { a: 1 }
*
* assert.deepStrictEqual(pipe(aOptB, withDefaults({ b: 'foo' })), { a: 1, b: 'foo' })
*
* @category 3 Functions
* @since 0.15.0
*/
export const withDefaults: <
T extends object,
PT extends Exact<{ [K in OptionalKeys<T>]-?: Exclude<T[K], undefined> }, PT>,
>(
defaults: PT,
) => (t: T) => PT & T = merge
type MaybePartial<A> = A | Partial<A>
type RenameKey<
A extends Record<string, unknown>,
I extends keyof A,
J extends string,
> = {
[K in keyof A as K extends I ? J : K]: A[K]
}
/**
* Rename a key in a struct, preserving the value. If the new key already
* exists, the old key will be overwritten. Optionality is preserved.
*
* @example
* import { renameKey } from 'fp-ts-std/Struct'
*
* type Foo = { a: string; b: number }
* type Bar = { a: string; c: number }
*
* const fooBar: (x: Foo) => Bar = renameKey('b')('c')
*
* @category 3 Functions
* @since 0.15.0
*/
export const renameKey =
<I extends string>(oldK: I) =>
<J extends string>(newK: J) =>
// Can't be pointfree, need references to `A`.
<A extends MaybePartial<Record<I, unknown>>>(x: A): RenameKey<A, I, J> => {
const newO = (x: A) => (oldK in x ? { [newK]: x[oldK] } : {})
return pipe(
x,
fanout(omitFrom<A>()([oldK]))(newO),
uncurry2(merge),
) as unknown as RenameKey<A, I, J>
}