-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathsize.js
95 lines (86 loc) · 3.28 KB
/
size.js
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
import { safeGetChangeProp } from './get.js'
// Apply `maxSize`, which omits values if they their JSON size would be too
// high.
// This is based on the JSON size without any indentation because:
// - This removes the need for a `maxSizeIndentation` option
// - If value is likely to be big, it is also likely to be serialized without
// any indentation to be kept small
// - The `maxSize` option is likely to be more of a soft limit than a hard
// limit
// - A hard limit is more likely when the value is a network request payload
// (as opposed to be kept in-memory or as a file), but then it is likely
// to be compressed too
// Strings that are too long are completely omitted instead of being truncated:
// - This is more consistent with the rest of the library
// - The truncation might make the value syntactically invalid, e.g. if it is a
// serialized value
// - This allows checking for strings being too large with `=== undefined`
// instead of inspecting the `changes`
// The top-level value itself might become `undefined` if either:
// - The `maxSize` option is very low (which is unlikely)
// - The top-level value is a very long string
// This is applied incrementally, in a depth-first manner, so that omitted
// fields (due to being over `maxSize`) and their children are not processed
// at all, for performance reason.
export const addSize = ({ type, size, maxSize, changes, path, context }) => {
if (maxSize === SKIP_MAX_SIZE) {
return { size, stop: false }
}
const { getSize, getOldValue } = SIZED_TYPES[type]
const newSize = size + getSize(context)
const stop = newSize > maxSize
if (!stop) {
return { size: newSize, stop }
}
// eslint-disable-next-line fp/no-mutating-methods
changes.push({
path,
oldValue: getOldValue(context),
newValue: undefined,
reason: 'unsafeSize',
})
return { size, stop }
}
// Skip checking for size when `maxSize` option equals this value
const SKIP_MAX_SIZE = Number.POSITIVE_INFINITY
// Default value for `maxSize` option.
// Chosen based on v8 max string length, which is ~5e8, which is smaller than
// SpiderMonkey (~1e9) and SquirrelFish (~2e9).
export const DEFAULT_MAX_SIZE = 1e7
const SIZED_TYPES = {
value: {
getSize: (value) => {
if (value === undefined) {
return 0
}
return typeof value === 'object' && value !== null
? 2
: getJsonLength(value)
},
getOldValue: (value) => value,
},
arrayItem: {
getSize: ({ empty }) => (empty ? 0 : 1),
getOldValue: safeGetChangeProp,
},
objectProp: {
getSize: ({ key, empty }) =>
typeof key === 'symbol' ? 0 : getJsonLength(key) + (empty ? 1 : 2),
getOldValue: safeGetChangeProp,
},
}
// We use `JSON.stringify()` to compute the length of strings (including
// property keys) to take into account escaping, including:
// - Control characters and Unicode characters
// - Invalid Unicode sequences
// This can throw if `value` is a large strings with many backslash sequences.
// We use the character length instead of the UTF-8 bytes length:
// - This is less proper
// - However, this is much faster and is good enough for this specific purpose
const getJsonLength = (value) => {
try {
return JSON.stringify(value).length
} catch {
return Number.POSITIVE_INFINITY
}
}