-
Notifications
You must be signed in to change notification settings - Fork 6
/
main.d.ts
161 lines (149 loc) · 4.04 KB
/
main.d.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
/**
* `safe-json-value` options
*/
export interface Options {
/**
* Big JSON strings can make a process, filesystem operation or network
* request crash.
* `maxSize` prevents it by setting a maximum `JSON.stringify(value).length`.
*
* Additional properties beyond the size limit are omitted.
* They are completely removed, not truncated (including strings).
*
* @default 1e7
*
* @example
* ```js
* const input = { one: true, two: 'a'.repeat(1e6) }
* JSON.stringify(safeJsonValue(input, { maxSize: 1e5 }).value) // '{"one":true}"
* ```
*/
readonly maxSize?: number
/**
* If `false`, object/array properties are processed recursively.
* Please note that cycles are not removed when this is `true`.
*
* @default false
*/
readonly shallow?: boolean
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
type InvalidJSONValue = bigint | Function | undefined | symbol
type ReturnValue<T, Shallow extends boolean> = T extends (infer ArrayItem)[]
? Shallow extends true
? ArrayItem[]
: ReturnValue<ArrayItem, Shallow>[]
: T extends InvalidJSONValue
? undefined
: T extends Date
? string
: T extends { toJSON: () => unknown }
? ReturnType<T['toJSON']>
: T extends object
? {
[key in keyof T as T[key] extends InvalidJSONValue
? never
: Exclude<key, symbol>]?: Shallow extends true
? T[key]
: ReturnValue<T[key], Shallow>
}
: T
type ReasonWithError = 'unsafeException' | 'unsafeGetter' | 'unsafeToJSON'
type ReasonWithoutError =
| 'descriptorNotConfigurable'
| 'descriptorNotWritable'
| 'ignoredArrayProperty'
| 'ignoredFunction'
| 'ignoredNotEnumerable'
| 'ignoredSymbolKey'
| 'ignoredSymbolValue'
| 'ignoredUndefined'
| 'unresolvedClass'
| 'unresolvedGetter'
| 'unresolvedToJSON'
| 'unsafeBigInt'
| 'unsafeCycle'
| 'unsafeSize'
| 'unstableInfinite'
/**
* Reason why a property was changed.
*/
export type Reason = ReasonWithError | ReasonWithoutError
/**
* Change applied to [`value`](#value).
* Each item is an individual change to a specific property.
* A given property might have multiple changes, listed in order.
*/
export type Change<ReasonValue extends Reason = Reason> = {
/**
* Property path.
*/
path: PropertyKey[]
/**
* Property value before the change.
*/
oldValue: unknown
/**
* Property value after the change.
* `undefined` means the property was omitted.
*/
newValue: unknown
/**
* Reason for the change.
*/
reason: ReasonValue
} & (ReasonValue extends ReasonWithError
? {
/**
* Error that triggered the change.
*/
error: Error
}
: object)
/**
* Makes `value` JSON-safe by:
* - Omitting properties which would throw, change type unexpectedly or be
* filtered with `JSON.stringify()`
* - Resolving properties which would change value with `JSON.stringify()`
*
* Applied recursively on object/array properties. This never throws.
*
* @example
* ```js
* const input = { one: true }
* input.self = input
*
* JSON.stringify(input) // Throws due to cycle
* const { value, changes } = safeJsonValue(input)
* JSON.stringify(value) // '{"one":true}"
*
* console.log(changes) // List of changed properties
* // [
* // {
* // path: ['self'],
* // oldValue: <ref *1> { one: true, self: [Circular *1] },
* // newValue: undefined,
* // reason: 'unsafeCycle'
* // }
* // ]
* ```
*/
export default function safeJsonValue<T, OptionsArg extends Options = object>(
value: T,
options?: OptionsArg,
): {
/**
* Copy of the input `value` after applying all the changes to make
* it JSON-safe.
*
* The top-level `value` itself might be changed (including to `undefined`) if
* it is either invalid JSON or has a `toJSON()` method.
*/
value:
| ReturnValue<T, OptionsArg['shallow'] extends true ? true : false>
| undefined
/**
* List of changes applied to `value`.
*/
changes: Change[]
}