-
Notifications
You must be signed in to change notification settings - Fork 0
/
defaultStrategies.ts
316 lines (276 loc) · 9.83 KB
/
defaultStrategies.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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
type EachType<T extends object> = {
+readonly [Property in keyof T]-?: strat<T[Property]>
}
type strat<T> = (data: any) => data is T
/**
* A set of common strategies that you can input into {@link MakeSendable} so you don't have to constantly retype the same long functions
*/
class Strategies {
/**
* Returns a strategy requiring all the strategies inputted to be true
* @param strats The strategies to check
* @returns A function representing all the strategies inputted being true
* @typeParam `T` - The type you're checking for
*/
all<T>(...strats: ((data: any) => boolean)[]) {
return function (data: any): data is T {
for (const strat of strats) {
if (!strat(data)) {
return false
}
}
return true
}
}
/**
* Returns a strategy requiring any the strategies inputted to be true
* @param strats The strategies to check
* @returns A function representing any the strategies inputted being true
* @typeParam `T` - The type you're checking for
*/
anyOf<T>(...strats: ((data: any) => boolean)[]) {
return function (data: any): data is T {
for (const strat of strats) {
if (strat(data)) {
return true
}
}
return false
}
}
/**
* Takes in an object type, and an object of strategies for each key in the object type, and returns a strategy for that object type
* @param strats The strategies for checking each object property
* @returns A function representing a strategy for checking the object type
* @typeParam `T` - The object type you're checking for
*/
class<T extends { [key: string]: any }>(strats: EachType<T>) {
return function (data: any): data is T {
for (const key in strats) {
let value
try {
value = data[key]
} catch {
return false
}
if (!strats[key](value)) {
return false
}
}
return true
}
}
/**
* Takes in a tuple type, and a tuple of strategies for each item, and returns a strategy for checking the tuple type
* @param strats A tuple of strategies for each item in the tuple type
* @returns A strategy for type checking the tuple type
* @typeParam `T` - The tuple type you're checking for
*/
tuple<T extends Array<any>>(strats: EachType<T>) {
return function (data: any): data is T {
if (!Array.isArray(data) || data.length !== strats.length) {
return false
}
for (let i = 0; i < data.length; i++) {
if (!strats[i](data[i])) {
return false
}
}
return true
}
}
/**
* Takes in an array of keys and strategies, and returns a strategy that checks if the map includes every key and passes all the strategies
* @param strats
* @returns
*/
mapEach<K, V>(strats: [K, (data: any) => data is V][]) {
return function (data: any): data is Map<K, V> {
if (!(data instanceof Map)) {
return false
}
for (const [key, checker] of strats) {
if (!data.has(key) || !checker(data.get(key))) return false
}
return true
}
}
/**
* Generates a strategy for type checking an array by applying a strategy to each element in the array
* @param strat The strategy to apply to each element in the array
* @returns A strategy for type checking the whole array
* @typeParam `T` - The type you expect to be contained by the array
*/
Array<T>(strat: strat<T>) {
return function (data: any): data is Array<T> {
if (!Array.isArray(data)) {
return false
}
for (const v of data) {
if (!strat(v)) {
return false
}
}
return true
}
}
/**
* Takes in a strategy for checking the type of the keys, and a strat for checking the values, and returns a strategy ensuring the value is a map, and each key/value pair in the map passes the key and value strats
* @param keyStrat A strategy for checking the keys in the map
* @param valueStrat A strategy for checking the values in the map
* @returns A strategy for checking that a value is a map of the given keys and values
*/
Map<K, V>(
keyStrat: (data: any) => data is K,
valueStrat: (data: any) => data is V
) {
return function (data: any): data is Map<K, V> {
if (!(data instanceof Map)) return false
for (const [key, value] of data) {
if (!keyStrat(key) || !valueStrat(value)) return false
}
return true
}
}
/**
* Takes in a strategy for checking each value in a set and returns a strategy checking if a value is a set of the given type
* @param strat A strategy for checking each value in the set
* @returns A strategy checking if a value is a set with the given type
*/
Set<V>(strat: (data: any) => data is V) {
return function (data: any): data is Set<V> {
if (!(data instanceof Set)) return false
for (const value of data) {
if (!strat(value)) return false
}
return true
}
}
/**
* Returns a strategy that ensures that the value being checked is triple equal to the value passed into this function
* @param value The value to check against
* @returns A strategy for type checking T
* @typeParam `T` - The type you're checking for
*/
value<T>(value: T) {
return function (data: any): data is T {
return data === value
}
}
/**
* Returns a strategy that ensures that a value doesn't exist, useful if you know the value is contained in the class prototype
* @returns A strategy ensuring type T doesn't exist
* @typeParam `T` - The type you're checking for
*/
doesntExist<T>() {
return function (data: any): data is T {
return data === undefined
}
}
/**
* Don't type check a value, good for saving effort type checking stuff when there's no network or untrusted actors to mess up data, or when the contents of the data doesn't matter
*
* Can also be used on the `channel` property for {@link Sendable}, since it's type checked by the library itself
*
* @returns A strategy always returning true
* @typeParam `T` - The type you're checking for
*/
dontCheck<T>() {
return function (data: any): data is T {
return true
}
}
/**
* Return a strategy that type checks a value and applies a filter to ensure the value is valid
* @param strat A strategy to ensure the value is the correct type
* @param filter A filter to narrow down the allowed values
* @returns A strategy checking the type and applying the filter
* @typeParam `T` - The type you're checking for
*/
checkWithFilter<T>(strat: strat<T>, filter: (data: T) => boolean) {
return function (data: any): data is T {
return strat(data) && filter(data)
}
}
/**
* Returns a strategy that checks if a value is a T1 and then if T1 is a T2
* @param strat1 The strategy for checking if the value is a T1
* @param strat2 The strategy for checking if the T1 is a T2
* @returns A strategy checking that the value is T2
* @typeParam `T1` - The intermediate type you're checking for first
* @typeParam `T2` - The type you're checking for
*/
narrow<T1, T2 extends T1>(
strat1: strat<T1>,
strat2: (data: T1) => data is T2
) {
return function (data: any): data is T2 {
return strat1(data) && strat2(data)
}
}
/**
* Returns a strategy that checks if a value is a string and matches a regex
* @param regex The regex to test against
* @returns The strategy testing if a value matches the regex
*/
match(regex: RegExp) {
return function (data: any): data is string {
return typeof data === "string" && regex.test(data)
}
}
/**
* A strategy checking if the value is a number
*/
isNumber(data: any): data is number {
return typeof data === "number"
}
/**
* A strategy checking if the value is a string
*/
isString(data: any): data is string {
return typeof data === "string"
}
/**
* A strategy checking if the value is a boolean
*/
isBoolean(data: any): data is boolean {
return typeof data === "boolean"
}
/**
* A strategy checking if the value is a bigint
*/
isBigint(data: any): data is bigint {
return typeof data === "bigint"
}
/**
* A strategy checking if the value is a symbol
*/
isSymbol(data: any): data is symbol {
return typeof data === "symbol"
}
/**
* A strategy checking if the value is a function
*/
isFunction(data: any): data is Function {
return typeof data === "function"
}
/**
* A strategy checking if the value is an object
*/
isObject(data: any): data is object {
return typeof data === "object"
}
/**
* A strategy checking if the value is null
*/
isNull(data: any): data is null {
return data === null
}
/**
* A strategy checking if the value is undefined
*/
isUndefined(data: any): data is undefined {
return data === undefined
}
}
export const strats = new Strategies()