-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpromise-object.ts
136 lines (126 loc) · 3.63 KB
/
promise-object.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
/**
* @file resolve-object.ts
* @author Stephen Belanger <[email protected]>
* @author Brandon Kalinowski
* @copyright 2020 Brandon Kalinowski (@brandonkal) and Stephen Belanger
* @description Recursively resolve any promises in an object to form a resulting JSON structure.
* @version 1.0.1
* @license MIT
*/
/**
* MVP or Maybe Value Promise is an object where values can
* either be a concrete value or a promise of that value.
*/
export type MVP<T> = {
[P in keyof T]: T[P] extends (infer U)[]
? MVP<U>[]
: T[P] extends object
? MVP<T[P]>
: T[P] extends PromiseLike<any>
? T[P]
: PromiseLike<T[P]> | T[P]
}
/**
* Unwraps a nested promise
*/
export type Unwrap<T> = T extends PromiseLike<infer U1>
? UnwrapSimple<U1>
: UnwrapSimple<T>
declare type primitive = Function | string | number | boolean | undefined | null
/**
* Handles encountering basic types when unwrapping.
*/
type UnwrapSimple<T> = T extends primitive
? T
: T extends Array<infer U>
? Unwrap<U>[]
: T extends object
? UnwrappedObject<T>
: never
type UnwrappedObject<T> = {
[P in keyof T]: Unwrap<T[P]>
}
/**
* MVPMap is an object where each key is a MPV
*/
export type MVPMap<T> = { [P in keyof T]: MVP<T[P]> }
/**
* Returns true if x is an object, false otherwise.
*/
const isObject = (x: any): boolean =>
x && typeof x === 'object' && x.constructor === Object
/* A well known Symbol. */
const $SELF =
typeof Symbol !== 'undefined' ? Symbol('SELF') : '[~~//-- SELF --//~~]'
/**
* Replaces values that match the query parameter
* with a reference to the parent parameter.
* @returns {Object}
*/
const makeCyclic = (object: any, query: any) => {
const start = (obj: typeof object): any =>
Object.keys(obj).reduce((acc: any, key) => {
const value = obj[key]
if (value === query) {
obj[key] = object
return [...acc, key]
}
if (isObject(value)) return [...acc, ...start(value)]
else return acc
}, [])
return start(object)
}
type Fn = (value: any) => any
/**
* Promise.map polyfill.
*/
const PromiseMap = (promises: PromiseLike<any>[], functor: Fn) =>
Promise.all(promises.map((x) => Promise.resolve(x).then(functor)))
/**
* Resolve a flat object's promises.
*/
const ResolveObject = (obj: Record<string, any>): Record<string, any> => {
// can possibly type as https://stackoverflow.com/questions/48944552/typescript-how-to-unwrap-remove-promise-from-a-type/48945362#48945362
return Promise.all(
Object.keys(obj).map((key) =>
Promise.resolve(obj[key]).then((val) => (obj[key] = val))
)
).then((_) => obj)
}
/**
* Recursively resolves deep objects with nested promises.
* @param {Object} obj Object or value to resolve.
* @returns {Object} Resolved object.
*/
export const PromiseObject = <T>(obj: MVP<T>): Promise<T> => {
let shouldReplaceSelf = false
const ResolveDeepObject = (record: any): any =>
Promise.resolve(record).then((resolvedObject) => {
if (Array.isArray(resolvedObject)) {
// Promise and map every item to recursively deep resolve.
return PromiseMap(resolvedObject, (obj) => ResolveDeepObject(obj))
} else if (isObject(resolvedObject)) {
return ResolveObject(
Object.keys(resolvedObject).reduce((acc, key) => {
if (resolvedObject[key] === obj) {
shouldReplaceSelf = true
return {
...acc,
[key]: $SELF, // Replace with resolved object.
}
}
return {
...acc,
[key]: ResolveDeepObject(resolvedObject[key]),
}
}, {})
)
}
return resolvedObject
})
return ResolveDeepObject(obj).then((record: any) => {
// Replace $SELF with reference to obj
if (shouldReplaceSelf) makeCyclic(record, $SELF)
return record
})
}