@n8n/utils 1.41.0 → 1.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/json/json-size-exceeds.cjs +237 -0
- package/dist/json/json-size-exceeds.cjs.map +1 -0
- package/dist/json/json-size-exceeds.d.cts +5 -0
- package/dist/json/json-size-exceeds.d.mts +5 -0
- package/dist/json/json-size-exceeds.mjs +236 -0
- package/dist/json/json-size-exceeds.mjs.map +1 -0
- package/dist/sleep.cjs +26 -0
- package/dist/sleep.cjs.map +1 -0
- package/dist/sleep.d.cts +5 -0
- package/dist/sleep.d.mts +5 -0
- package/dist/sleep.mjs +25 -0
- package/dist/sleep.mjs.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/json/json-size-exceeds.ts
|
|
3
|
+
const QUOTES_SIZE = 2;
|
|
4
|
+
const COLON_SIZE = 1;
|
|
5
|
+
const COMMA_SIZE = 1;
|
|
6
|
+
const EMPTY_CONTAINER_SIZE = 2;
|
|
7
|
+
const NULL_SIZE = 4;
|
|
8
|
+
const TRUE_SIZE = 4;
|
|
9
|
+
const FALSE_SIZE = 5;
|
|
10
|
+
const SIGN_SIZE = 1;
|
|
11
|
+
/** Magnitude from which a number serializes in exponential notation. */
|
|
12
|
+
const EXPONENTIAL_NOTATION_THRESHOLD = 1e21;
|
|
13
|
+
/** `{"type":"Buffer","data":[]}` around the bytes of a Buffer. */
|
|
14
|
+
const BUFFER_ENVELOPE_SIZE = 27;
|
|
15
|
+
/** Longest a byte serializes to inside that envelope, as in `255,`. */
|
|
16
|
+
const MAX_BUFFER_BYTE_SIZE = 4;
|
|
17
|
+
/**
|
|
18
|
+
* Widest a number can serialize to, as in `-0.0000075911789601505095`. Bounds the
|
|
19
|
+
* elements of a binary view, which are counted without being visited.
|
|
20
|
+
*/
|
|
21
|
+
const MAX_NUMBER_SIZE = 25;
|
|
22
|
+
const SHORT_ESCAPE_SIZE = 2;
|
|
23
|
+
const UNICODE_ESCAPE_SIZE = 6;
|
|
24
|
+
const ONE_BYTE_SIZE = 1;
|
|
25
|
+
const TWO_BYTE_SIZE = 2;
|
|
26
|
+
const THREE_BYTE_SIZE = 3;
|
|
27
|
+
const SURROGATE_PAIR_SIZE = 4;
|
|
28
|
+
const CONTROL_MAX = 31;
|
|
29
|
+
const QUOTE = 34;
|
|
30
|
+
const BACKSLASH = 92;
|
|
31
|
+
const ASCII_MAX = 127;
|
|
32
|
+
const TWO_BYTE_MAX = 2047;
|
|
33
|
+
const HIGH_SURROGATE_MIN = 55296;
|
|
34
|
+
const HIGH_SURROGATE_MAX = 56319;
|
|
35
|
+
const LOW_SURROGATE_MIN = 56320;
|
|
36
|
+
const LOW_SURROGATE_MAX = 57343;
|
|
37
|
+
/** Control characters serialization escapes with a letter instead of a code point. */
|
|
38
|
+
const LETTER_ESCAPED_CONTROLS = /* @__PURE__ */ new Set([
|
|
39
|
+
8,
|
|
40
|
+
9,
|
|
41
|
+
10,
|
|
42
|
+
12,
|
|
43
|
+
13
|
|
44
|
+
]);
|
|
45
|
+
/** Key `JSON.stringify` hands to the `toJSON` of the value it is called on. */
|
|
46
|
+
const ROOT_KEY = "";
|
|
47
|
+
/**
|
|
48
|
+
* Tells whether a value exceeds a JSON size limit, without serializing it.
|
|
49
|
+
*
|
|
50
|
+
* The measure is an upper bound, so a value is never reported as fitting a size
|
|
51
|
+
* it does not fit. It overshoots by one byte per non-empty container, and counts
|
|
52
|
+
* binary data at the widest its bytes can serialize to.
|
|
53
|
+
*
|
|
54
|
+
* @param value Value to measure as if it were passed to `JSON.stringify`.
|
|
55
|
+
* @param maxBytes Limit the serialization must stay within.
|
|
56
|
+
* @returns `true` unless the serialization is certainly `maxBytes` or shorter.
|
|
57
|
+
*
|
|
58
|
+
* @remarks Time O(n) in the members and characters of `value`, memory O(depth).
|
|
59
|
+
* Calls `toJSON` on the members defining one, as serialization would.
|
|
60
|
+
*/
|
|
61
|
+
function jsonSizeExceeds(value, maxBytes) {
|
|
62
|
+
const walk = {
|
|
63
|
+
maxBytes,
|
|
64
|
+
frames: [],
|
|
65
|
+
ancestors: /* @__PURE__ */ new Set(),
|
|
66
|
+
size: 0
|
|
67
|
+
};
|
|
68
|
+
addValue(walk, replacedValue(value, ROOT_KEY));
|
|
69
|
+
while (walk.frames.length > 0 && walk.size <= maxBytes) advance(walk, walk.frames[walk.frames.length - 1]);
|
|
70
|
+
return walk.size > maxBytes;
|
|
71
|
+
}
|
|
72
|
+
/** Bytes left before the limit. Negative once the limit is crossed. */
|
|
73
|
+
function remaining(walk) {
|
|
74
|
+
return walk.maxBytes - walk.size;
|
|
75
|
+
}
|
|
76
|
+
/** Measures the next member of the innermost container, or closes it. */
|
|
77
|
+
function advance(walk, frame) {
|
|
78
|
+
if ("elements" in frame) advanceElements(walk, frame);
|
|
79
|
+
else advanceEntries(walk, frame);
|
|
80
|
+
}
|
|
81
|
+
function advanceElements(walk, frame) {
|
|
82
|
+
if (frame.index === frame.elements.length) close(walk, frame.elements);
|
|
83
|
+
else {
|
|
84
|
+
const index = frame.index;
|
|
85
|
+
frame.index += 1;
|
|
86
|
+
walk.size += COMMA_SIZE;
|
|
87
|
+
addValue(walk, replacedValue(frame.elements[index], index));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function advanceEntries(walk, frame) {
|
|
91
|
+
if (frame.index === frame.keys.length) close(walk, frame.entries);
|
|
92
|
+
else {
|
|
93
|
+
const key = frame.keys[frame.index];
|
|
94
|
+
frame.index += 1;
|
|
95
|
+
addEntry(walk, key, replacedValue(frame.entries[key], key));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Adds an entry, unless serialization drops it along with its key. */
|
|
99
|
+
function addEntry(walk, key, value) {
|
|
100
|
+
if (!isDroppedFromObjects(value)) {
|
|
101
|
+
walk.size += 2 + stringSize(key, remaining(walk));
|
|
102
|
+
addValue(walk, value);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Adds what a value occupies on its own, and opens it when it has members.
|
|
107
|
+
* Measuring stops once the limit is crossed, so a size cut short is still above it.
|
|
108
|
+
*/
|
|
109
|
+
function addValue(walk, value) {
|
|
110
|
+
if (isContainer(value)) open(walk, value);
|
|
111
|
+
else walk.size += leafSize(value, remaining(walk));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Adds a container's own delimiters and queues its members, or the whole of it
|
|
115
|
+
* when its size follows from its length alone.
|
|
116
|
+
*/
|
|
117
|
+
function open(walk, container) {
|
|
118
|
+
if (!walk.ancestors.has(container)) {
|
|
119
|
+
const binarySize = maxBinaryViewSize(container);
|
|
120
|
+
if (binarySize === void 0) {
|
|
121
|
+
walk.size += EMPTY_CONTAINER_SIZE;
|
|
122
|
+
walk.ancestors.add(container);
|
|
123
|
+
walk.frames.push(frameFor(container));
|
|
124
|
+
} else walk.size += binarySize;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function close(walk, container) {
|
|
128
|
+
walk.ancestors.delete(container);
|
|
129
|
+
walk.frames.pop();
|
|
130
|
+
}
|
|
131
|
+
function frameFor(container) {
|
|
132
|
+
return Array.isArray(container) ? {
|
|
133
|
+
elements: container,
|
|
134
|
+
index: 0
|
|
135
|
+
} : {
|
|
136
|
+
entries: container,
|
|
137
|
+
keys: Object.keys(container),
|
|
138
|
+
index: 0
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** The value serialization puts in place of this one, given the key holding it. */
|
|
142
|
+
function replacedValue(value, key) {
|
|
143
|
+
return isSelfSerializing(value) ? value.toJSON(String(key)) : value;
|
|
144
|
+
}
|
|
145
|
+
function isSelfSerializing(value) {
|
|
146
|
+
return isContainer(value) && !Buffer.isBuffer(value) && "toJSON" in value && typeof value.toJSON === "function";
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Bytes a Buffer or another binary view occupies serialized, or `undefined` for
|
|
150
|
+
* a container whose members have to be walked.
|
|
151
|
+
*/
|
|
152
|
+
function maxBinaryViewSize(container) {
|
|
153
|
+
if (Buffer.isBuffer(container)) return BUFFER_ENVELOPE_SIZE + MAX_BUFFER_BYTE_SIZE * container.length;
|
|
154
|
+
return isIndexedView(container) ? maxIndexedViewSize(container) : void 0;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Bytes an indexed view occupies as the object of index/element entries it
|
|
158
|
+
* serializes to. Derived from its length, because listing those keys would hold
|
|
159
|
+
* one string per element in memory.
|
|
160
|
+
*/
|
|
161
|
+
function maxIndexedViewSize(view) {
|
|
162
|
+
const lastIndex = Math.max(view.length - 1, 0);
|
|
163
|
+
const maxEntrySize = QUOTES_SIZE + decimalDigits(lastIndex) + COLON_SIZE + MAX_NUMBER_SIZE + COMMA_SIZE;
|
|
164
|
+
return EMPTY_CONTAINER_SIZE + view.length * maxEntrySize;
|
|
165
|
+
}
|
|
166
|
+
/** Bytes a value with no members occupies serialized. */
|
|
167
|
+
function leafSize(value, budget) {
|
|
168
|
+
switch (typeof value) {
|
|
169
|
+
case "string": return stringSize(value, budget);
|
|
170
|
+
case "number": return numberSize(value);
|
|
171
|
+
case "boolean": return value ? TRUE_SIZE : FALSE_SIZE;
|
|
172
|
+
default: return NULL_SIZE;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Bytes a string occupies serialized, escapes and quotes included. */
|
|
176
|
+
function stringSize(value, budget) {
|
|
177
|
+
return QUOTES_SIZE + escapedContentSize(value, budget - QUOTES_SIZE);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Bytes the escaped characters of a string occupy, quotes excluded. Reads the
|
|
181
|
+
* string one code unit at a time so that nothing is copied, and stops once
|
|
182
|
+
* `budget` is gone, since what is already counted then settles the answer.
|
|
183
|
+
*/
|
|
184
|
+
function escapedContentSize(value, budget) {
|
|
185
|
+
let size = 0;
|
|
186
|
+
let index = 0;
|
|
187
|
+
while (index < value.length && size <= budget) {
|
|
188
|
+
const code = value.charCodeAt(index);
|
|
189
|
+
const paired = isHighSurrogate(code) && isLowSurrogate(value.charCodeAt(index + 1));
|
|
190
|
+
size += paired ? SURROGATE_PAIR_SIZE : codeUnitSize(code);
|
|
191
|
+
index += paired ? 2 : 1;
|
|
192
|
+
}
|
|
193
|
+
return size;
|
|
194
|
+
}
|
|
195
|
+
/** Bytes a single code unit occupies, escaped and encoded as serialization would. */
|
|
196
|
+
function codeUnitSize(code) {
|
|
197
|
+
if (code === QUOTE || code === BACKSLASH) return SHORT_ESCAPE_SIZE;
|
|
198
|
+
if (code <= CONTROL_MAX) return LETTER_ESCAPED_CONTROLS.has(code) ? SHORT_ESCAPE_SIZE : UNICODE_ESCAPE_SIZE;
|
|
199
|
+
if (code <= ASCII_MAX) return ONE_BYTE_SIZE;
|
|
200
|
+
if (code <= TWO_BYTE_MAX) return TWO_BYTE_SIZE;
|
|
201
|
+
return isSurrogate(code) ? UNICODE_ESCAPE_SIZE : THREE_BYTE_SIZE;
|
|
202
|
+
}
|
|
203
|
+
/** Bytes a number occupies serialized. */
|
|
204
|
+
function numberSize(value) {
|
|
205
|
+
if (!Number.isFinite(value)) return NULL_SIZE;
|
|
206
|
+
const magnitude = Math.abs(value);
|
|
207
|
+
return Number.isInteger(value) && magnitude < EXPONENTIAL_NOTATION_THRESHOLD ? (value < 0 ? SIGN_SIZE : 0) + decimalDigits(magnitude) : String(value).length;
|
|
208
|
+
}
|
|
209
|
+
/** Digits the integer part of a magnitude is written with. */
|
|
210
|
+
function decimalDigits(magnitude) {
|
|
211
|
+
const digits = magnitude < 1 ? 1 : Math.floor(Math.log10(magnitude)) + 1;
|
|
212
|
+
return magnitude < 10 ** digits ? digits : digits + 1;
|
|
213
|
+
}
|
|
214
|
+
/** Whether serializing an object drops the entry holding this value, key included. */
|
|
215
|
+
function isDroppedFromObjects(value) {
|
|
216
|
+
const type = typeof value;
|
|
217
|
+
return type === "undefined" || type === "function" || type === "symbol";
|
|
218
|
+
}
|
|
219
|
+
function isContainer(value) {
|
|
220
|
+
return typeof value === "object" && value !== null;
|
|
221
|
+
}
|
|
222
|
+
function isIndexedView(value) {
|
|
223
|
+
return ArrayBuffer.isView(value) && "length" in value && typeof value.length === "number";
|
|
224
|
+
}
|
|
225
|
+
function isHighSurrogate(code) {
|
|
226
|
+
return code >= HIGH_SURROGATE_MIN && code <= HIGH_SURROGATE_MAX;
|
|
227
|
+
}
|
|
228
|
+
function isLowSurrogate(code) {
|
|
229
|
+
return code >= LOW_SURROGATE_MIN && code <= LOW_SURROGATE_MAX;
|
|
230
|
+
}
|
|
231
|
+
function isSurrogate(code) {
|
|
232
|
+
return code >= HIGH_SURROGATE_MIN && code <= LOW_SURROGATE_MAX;
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
exports.jsonSizeExceeds = jsonSizeExceeds;
|
|
236
|
+
|
|
237
|
+
//# sourceMappingURL=json-size-exceeds.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-size-exceeds.cjs","names":[],"sources":["../../src/json/json-size-exceeds.ts"],"sourcesContent":["type JsonContainer = Record<string, unknown> | unknown[];\n\n/** A value serialization replaces with the result of its own `toJSON`. */\ntype SelfSerializing = JsonContainer & { toJSON: (key: string) => unknown };\n\n/** A view over binary data, serialized as one entry per element. */\ntype IndexedView = ArrayBufferView & { length: number };\n\n/** An array being measured, and how far through its elements the walk is. */\ntype ElementsFrame = { readonly elements: unknown[]; index: number };\n\n/** An object being measured, and how far through its keys the walk is. */\ntype EntriesFrame = {\n\treadonly entries: Record<string, unknown>;\n\treadonly keys: string[];\n\tindex: number;\n};\n\ntype Frame = ElementsFrame | EntriesFrame;\n\n/** Bytes counted so far, and the containers the walk still has to finish. */\ntype Walk = {\n\treadonly maxBytes: number;\n\treadonly frames: Frame[];\n\treadonly ancestors: Set<JsonContainer>;\n\tsize: number;\n};\n\nconst QUOTES_SIZE = 2; // `\"\"` around a string or a key\nconst COLON_SIZE = 1;\nconst COMMA_SIZE = 1;\nconst EMPTY_CONTAINER_SIZE = 2; // `{}` or `[]`\nconst NULL_SIZE = 4;\nconst TRUE_SIZE = 4;\nconst FALSE_SIZE = 5;\nconst SIGN_SIZE = 1;\n\n/** Magnitude from which a number serializes in exponential notation. */\nconst EXPONENTIAL_NOTATION_THRESHOLD = 1e21;\n\n/** `{\"type\":\"Buffer\",\"data\":[]}` around the bytes of a Buffer. */\nconst BUFFER_ENVELOPE_SIZE = 27;\n\n/** Longest a byte serializes to inside that envelope, as in `255,`. */\nconst MAX_BUFFER_BYTE_SIZE = 4;\n\n/**\n * Widest a number can serialize to, as in `-0.0000075911789601505095`. Bounds the\n * elements of a binary view, which are counted without being visited.\n */\nconst MAX_NUMBER_SIZE = 25;\n\nconst SHORT_ESCAPE_SIZE = 2; // `\\n`, `\\\"`, `\\\\`\nconst UNICODE_ESCAPE_SIZE = 6; // `\\u001f`, and a lone surrogate\nconst ONE_BYTE_SIZE = 1;\nconst TWO_BYTE_SIZE = 2;\nconst THREE_BYTE_SIZE = 3;\nconst SURROGATE_PAIR_SIZE = 4; // one code point spread over two code units\n\nconst CONTROL_MAX = 0x1f;\nconst QUOTE = 0x22;\nconst BACKSLASH = 0x5c;\nconst ASCII_MAX = 0x7f;\nconst TWO_BYTE_MAX = 0x7ff;\nconst HIGH_SURROGATE_MIN = 0xd800;\nconst HIGH_SURROGATE_MAX = 0xdbff;\nconst LOW_SURROGATE_MIN = 0xdc00;\nconst LOW_SURROGATE_MAX = 0xdfff;\n\n/** Control characters serialization escapes with a letter instead of a code point. */\nconst LETTER_ESCAPED_CONTROLS = new Set([0x08, 0x09, 0x0a, 0x0c, 0x0d]);\n\n/** Key `JSON.stringify` hands to the `toJSON` of the value it is called on. */\nconst ROOT_KEY = '';\n\n/**\n * Tells whether a value exceeds a JSON size limit, without serializing it.\n *\n * The measure is an upper bound, so a value is never reported as fitting a size\n * it does not fit. It overshoots by one byte per non-empty container, and counts\n * binary data at the widest its bytes can serialize to.\n *\n * @param value Value to measure as if it were passed to `JSON.stringify`.\n * @param maxBytes Limit the serialization must stay within.\n * @returns `true` unless the serialization is certainly `maxBytes` or shorter.\n *\n * @remarks Time O(n) in the members and characters of `value`, memory O(depth).\n * Calls `toJSON` on the members defining one, as serialization would.\n */\nexport function jsonSizeExceeds(value: unknown, maxBytes: number): boolean {\n\tconst walk: Walk = { maxBytes, frames: [], ancestors: new Set(), size: 0 };\n\n\taddValue(walk, replacedValue(value, ROOT_KEY));\n\twhile (walk.frames.length > 0 && walk.size <= maxBytes) {\n\t\tadvance(walk, walk.frames[walk.frames.length - 1]);\n\t}\n\n\treturn walk.size > maxBytes;\n}\n\n/** Bytes left before the limit. Negative once the limit is crossed. */\nfunction remaining(walk: Walk): number {\n\treturn walk.maxBytes - walk.size;\n}\n\n/** Measures the next member of the innermost container, or closes it. */\nfunction advance(walk: Walk, frame: Frame): void {\n\tif ('elements' in frame) {\n\t\tadvanceElements(walk, frame);\n\t} else {\n\t\tadvanceEntries(walk, frame);\n\t}\n}\n\nfunction advanceElements(walk: Walk, frame: ElementsFrame): void {\n\tif (frame.index === frame.elements.length) {\n\t\tclose(walk, frame.elements);\n\t} else {\n\t\tconst index = frame.index;\n\t\tframe.index += 1;\n\t\twalk.size += COMMA_SIZE;\n\t\taddValue(walk, replacedValue(frame.elements[index], index));\n\t}\n}\n\nfunction advanceEntries(walk: Walk, frame: EntriesFrame): void {\n\tif (frame.index === frame.keys.length) {\n\t\tclose(walk, frame.entries);\n\t} else {\n\t\tconst key = frame.keys[frame.index];\n\t\tframe.index += 1;\n\t\taddEntry(walk, key, replacedValue(frame.entries[key], key));\n\t}\n}\n\n/** Adds an entry, unless serialization drops it along with its key. */\nfunction addEntry(walk: Walk, key: string, value: unknown): void {\n\tif (!isDroppedFromObjects(value)) {\n\t\twalk.size += COMMA_SIZE + COLON_SIZE + stringSize(key, remaining(walk));\n\t\taddValue(walk, value);\n\t}\n}\n\n/**\n * Adds what a value occupies on its own, and opens it when it has members.\n * Measuring stops once the limit is crossed, so a size cut short is still above it.\n */\nfunction addValue(walk: Walk, value: unknown): void {\n\tif (isContainer(value)) {\n\t\topen(walk, value);\n\t} else {\n\t\twalk.size += leafSize(value, remaining(walk));\n\t}\n}\n\n/**\n * Adds a container's own delimiters and queues its members, or the whole of it\n * when its size follows from its length alone.\n */\nfunction open(walk: Walk, container: JsonContainer): void {\n\t// A container reached from inside itself makes serialization fail, so it has\n\t// no size to answer with, and walking into it again would not end.\n\tif (!walk.ancestors.has(container)) {\n\t\tconst binarySize = maxBinaryViewSize(container);\n\n\t\tif (binarySize === undefined) {\n\t\t\twalk.size += EMPTY_CONTAINER_SIZE;\n\t\t\twalk.ancestors.add(container);\n\t\t\twalk.frames.push(frameFor(container));\n\t\t} else {\n\t\t\twalk.size += binarySize;\n\t\t}\n\t}\n}\n\nfunction close(walk: Walk, container: JsonContainer): void {\n\twalk.ancestors.delete(container);\n\twalk.frames.pop();\n}\n\nfunction frameFor(container: JsonContainer): Frame {\n\treturn Array.isArray(container)\n\t\t? { elements: container, index: 0 }\n\t\t: { entries: container, keys: Object.keys(container), index: 0 };\n}\n\n/** The value serialization puts in place of this one, given the key holding it. */\nfunction replacedValue(value: unknown, key: string | number): unknown {\n\treturn isSelfSerializing(value) ? value.toJSON(String(key)) : value;\n}\n\nfunction isSelfSerializing(value: unknown): value is SelfSerializing {\n\treturn (\n\t\tisContainer(value) &&\n\t\t// A Buffer would hand over an array of one number per byte, which its own\n\t\t// measure derives from its length instead.\n\t\t!Buffer.isBuffer(value) &&\n\t\t'toJSON' in value &&\n\t\ttypeof value.toJSON === 'function'\n\t);\n}\n\n/**\n * Bytes a Buffer or another binary view occupies serialized, or `undefined` for\n * a container whose members have to be walked.\n */\nfunction maxBinaryViewSize(container: JsonContainer): number | undefined {\n\tif (Buffer.isBuffer(container)) {\n\t\treturn BUFFER_ENVELOPE_SIZE + MAX_BUFFER_BYTE_SIZE * container.length;\n\t}\n\n\treturn isIndexedView(container) ? maxIndexedViewSize(container) : undefined;\n}\n\n/**\n * Bytes an indexed view occupies as the object of index/element entries it\n * serializes to. Derived from its length, because listing those keys would hold\n * one string per element in memory.\n */\nfunction maxIndexedViewSize(view: IndexedView): number {\n\tconst lastIndex = Math.max(view.length - 1, 0);\n\tconst maxEntrySize =\n\t\tQUOTES_SIZE + decimalDigits(lastIndex) + COLON_SIZE + MAX_NUMBER_SIZE + COMMA_SIZE;\n\n\treturn EMPTY_CONTAINER_SIZE + view.length * maxEntrySize;\n}\n\n/** Bytes a value with no members occupies serialized. */\nfunction leafSize(value: unknown, budget: number): number {\n\tswitch (typeof value) {\n\t\tcase 'string':\n\t\t\treturn stringSize(value, budget);\n\t\tcase 'number':\n\t\t\treturn numberSize(value);\n\t\tcase 'boolean':\n\t\t\treturn value ? TRUE_SIZE : FALSE_SIZE;\n\t\tdefault:\n\t\t\treturn NULL_SIZE;\n\t}\n}\n\n/** Bytes a string occupies serialized, escapes and quotes included. */\nfunction stringSize(value: string, budget: number): number {\n\treturn QUOTES_SIZE + escapedContentSize(value, budget - QUOTES_SIZE);\n}\n\n/**\n * Bytes the escaped characters of a string occupy, quotes excluded. Reads the\n * string one code unit at a time so that nothing is copied, and stops once\n * `budget` is gone, since what is already counted then settles the answer.\n */\nfunction escapedContentSize(value: string, budget: number): number {\n\tlet size = 0;\n\tlet index = 0;\n\n\twhile (index < value.length && size <= budget) {\n\t\tconst code = value.charCodeAt(index);\n\t\tconst paired = isHighSurrogate(code) && isLowSurrogate(value.charCodeAt(index + 1));\n\n\t\tsize += paired ? SURROGATE_PAIR_SIZE : codeUnitSize(code);\n\t\tindex += paired ? 2 : 1;\n\t}\n\n\treturn size;\n}\n\n/** Bytes a single code unit occupies, escaped and encoded as serialization would. */\nfunction codeUnitSize(code: number): number {\n\tif (code === QUOTE || code === BACKSLASH) {\n\t\treturn SHORT_ESCAPE_SIZE;\n\t}\n\n\tif (code <= CONTROL_MAX) {\n\t\treturn LETTER_ESCAPED_CONTROLS.has(code) ? SHORT_ESCAPE_SIZE : UNICODE_ESCAPE_SIZE;\n\t}\n\n\tif (code <= ASCII_MAX) {\n\t\treturn ONE_BYTE_SIZE;\n\t}\n\n\tif (code <= TWO_BYTE_MAX) {\n\t\treturn TWO_BYTE_SIZE;\n\t}\n\n\t// A surrogate left without its other half, which serialization escapes.\n\treturn isSurrogate(code) ? UNICODE_ESCAPE_SIZE : THREE_BYTE_SIZE;\n}\n\n/** Bytes a number occupies serialized. */\nfunction numberSize(value: number): number {\n\tif (!Number.isFinite(value)) {\n\t\treturn NULL_SIZE;\n\t}\n\n\tconst magnitude = Math.abs(value);\n\tconst isPlainInteger = Number.isInteger(value) && magnitude < EXPONENTIAL_NOTATION_THRESHOLD;\n\n\t// A plain integer has a length its magnitude gives away. Any other number has\n\t// to be formatted to be measured, and nothing shorter would be exact.\n\treturn isPlainInteger\n\t\t? (value < 0 ? SIGN_SIZE : 0) + decimalDigits(magnitude)\n\t\t: String(value).length;\n}\n\n/** Digits the integer part of a magnitude is written with. */\nfunction decimalDigits(magnitude: number): number {\n\tconst digits = magnitude < 1 ? 1 : Math.floor(Math.log10(magnitude)) + 1;\n\n\t// A rounding error in log10 costs a digit on some exact powers of ten.\n\treturn magnitude < 10 ** digits ? digits : digits + 1;\n}\n\n/** Whether serializing an object drops the entry holding this value, key included. */\nfunction isDroppedFromObjects(value: unknown): boolean {\n\tconst type = typeof value;\n\treturn type === 'undefined' || type === 'function' || type === 'symbol';\n}\n\nfunction isContainer(value: unknown): value is JsonContainer {\n\treturn typeof value === 'object' && value !== null;\n}\n\nfunction isIndexedView(value: object): value is IndexedView {\n\treturn ArrayBuffer.isView(value) && 'length' in value && typeof value.length === 'number';\n}\n\nfunction isHighSurrogate(code: number): boolean {\n\treturn code >= HIGH_SURROGATE_MIN && code <= HIGH_SURROGATE_MAX;\n}\n\nfunction isLowSurrogate(code: number): boolean {\n\treturn code >= LOW_SURROGATE_MIN && code <= LOW_SURROGATE_MAX;\n}\n\nfunction isSurrogate(code: number): boolean {\n\treturn code >= HIGH_SURROGATE_MIN && code <= LOW_SURROGATE_MAX;\n}\n"],"mappings":";;AA4BA,MAAM,cAAc;AACpB,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,uBAAuB;AAC7B,MAAM,YAAY;AAClB,MAAM,YAAY;AAClB,MAAM,aAAa;AACnB,MAAM,YAAY;;AAGlB,MAAM,iCAAiC;;AAGvC,MAAM,uBAAuB;;AAG7B,MAAM,uBAAuB;;;;;AAM7B,MAAM,kBAAkB;AAExB,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAC5B,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;AACtB,MAAM,kBAAkB;AACxB,MAAM,sBAAsB;AAE5B,MAAM,cAAc;AACpB,MAAM,QAAQ;AACd,MAAM,YAAY;AAClB,MAAM,YAAY;AAClB,MAAM,eAAe;AACrB,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;;AAG1B,MAAM,0CAA0B,IAAI,IAAI;CAAC;CAAM;CAAM;CAAM;CAAM;AAAI,CAAC;;AAGtE,MAAM,WAAW;;;;;;;;;;;;;;;AAgBjB,SAAgB,gBAAgB,OAAgB,UAA2B;CAC1E,MAAM,OAAa;EAAE;EAAU,QAAQ,CAAC;EAAG,2BAAW,IAAI,IAAI;EAAG,MAAM;CAAE;CAEzE,SAAS,MAAM,cAAc,OAAO,QAAQ,CAAC;CAC7C,OAAO,KAAK,OAAO,SAAS,KAAK,KAAK,QAAQ,UAC7C,QAAQ,MAAM,KAAK,OAAO,KAAK,OAAO,SAAS,EAAE;CAGlD,OAAO,KAAK,OAAO;AACpB;;AAGA,SAAS,UAAU,MAAoB;CACtC,OAAO,KAAK,WAAW,KAAK;AAC7B;;AAGA,SAAS,QAAQ,MAAY,OAAoB;CAChD,IAAI,cAAc,OACjB,gBAAgB,MAAM,KAAK;MAE3B,eAAe,MAAM,KAAK;AAE5B;AAEA,SAAS,gBAAgB,MAAY,OAA4B;CAChE,IAAI,MAAM,UAAU,MAAM,SAAS,QAClC,MAAM,MAAM,MAAM,QAAQ;MACpB;EACN,MAAM,QAAQ,MAAM;EACpB,MAAM,SAAS;EACf,KAAK,QAAQ;EACb,SAAS,MAAM,cAAc,MAAM,SAAS,QAAQ,KAAK,CAAC;CAC3D;AACD;AAEA,SAAS,eAAe,MAAY,OAA2B;CAC9D,IAAI,MAAM,UAAU,MAAM,KAAK,QAC9B,MAAM,MAAM,MAAM,OAAO;MACnB;EACN,MAAM,MAAM,MAAM,KAAK,MAAM;EAC7B,MAAM,SAAS;EACf,SAAS,MAAM,KAAK,cAAc,MAAM,QAAQ,MAAM,GAAG,CAAC;CAC3D;AACD;;AAGA,SAAS,SAAS,MAAY,KAAa,OAAsB;CAChE,IAAI,CAAC,qBAAqB,KAAK,GAAG;EACjC,KAAK,QAAQ,IAA0B,WAAW,KAAK,UAAU,IAAI,CAAC;EACtE,SAAS,MAAM,KAAK;CACrB;AACD;;;;;AAMA,SAAS,SAAS,MAAY,OAAsB;CACnD,IAAI,YAAY,KAAK,GACpB,KAAK,MAAM,KAAK;MAEhB,KAAK,QAAQ,SAAS,OAAO,UAAU,IAAI,CAAC;AAE9C;;;;;AAMA,SAAS,KAAK,MAAY,WAAgC;CAGzD,IAAI,CAAC,KAAK,UAAU,IAAI,SAAS,GAAG;EACnC,MAAM,aAAa,kBAAkB,SAAS;EAE9C,IAAI,eAAe,KAAA,GAAW;GAC7B,KAAK,QAAQ;GACb,KAAK,UAAU,IAAI,SAAS;GAC5B,KAAK,OAAO,KAAK,SAAS,SAAS,CAAC;EACrC,OACC,KAAK,QAAQ;CAEf;AACD;AAEA,SAAS,MAAM,MAAY,WAAgC;CAC1D,KAAK,UAAU,OAAO,SAAS;CAC/B,KAAK,OAAO,IAAI;AACjB;AAEA,SAAS,SAAS,WAAiC;CAClD,OAAO,MAAM,QAAQ,SAAS,IAC3B;EAAE,UAAU;EAAW,OAAO;CAAE,IAChC;EAAE,SAAS;EAAW,MAAM,OAAO,KAAK,SAAS;EAAG,OAAO;CAAE;AACjE;;AAGA,SAAS,cAAc,OAAgB,KAA+B;CACrE,OAAO,kBAAkB,KAAK,IAAI,MAAM,OAAO,OAAO,GAAG,CAAC,IAAI;AAC/D;AAEA,SAAS,kBAAkB,OAA0C;CACpE,OACC,YAAY,KAAK,KAGjB,CAAC,OAAO,SAAS,KAAK,KACtB,YAAY,SACZ,OAAO,MAAM,WAAW;AAE1B;;;;;AAMA,SAAS,kBAAkB,WAA8C;CACxE,IAAI,OAAO,SAAS,SAAS,GAC5B,OAAO,uBAAuB,uBAAuB,UAAU;CAGhE,OAAO,cAAc,SAAS,IAAI,mBAAmB,SAAS,IAAI,KAAA;AACnE;;;;;;AAOA,SAAS,mBAAmB,MAA2B;CACtD,MAAM,YAAY,KAAK,IAAI,KAAK,SAAS,GAAG,CAAC;CAC7C,MAAM,eACL,cAAc,cAAc,SAAS,IAAI,aAAa,kBAAkB;CAEzE,OAAO,uBAAuB,KAAK,SAAS;AAC7C;;AAGA,SAAS,SAAS,OAAgB,QAAwB;CACzD,QAAQ,OAAO,OAAf;EACC,KAAK,UACJ,OAAO,WAAW,OAAO,MAAM;EAChC,KAAK,UACJ,OAAO,WAAW,KAAK;EACxB,KAAK,WACJ,OAAO,QAAQ,YAAY;EAC5B,SACC,OAAO;CACT;AACD;;AAGA,SAAS,WAAW,OAAe,QAAwB;CAC1D,OAAO,cAAc,mBAAmB,OAAO,SAAS,WAAW;AACpE;;;;;;AAOA,SAAS,mBAAmB,OAAe,QAAwB;CAClE,IAAI,OAAO;CACX,IAAI,QAAQ;CAEZ,OAAO,QAAQ,MAAM,UAAU,QAAQ,QAAQ;EAC9C,MAAM,OAAO,MAAM,WAAW,KAAK;EACnC,MAAM,SAAS,gBAAgB,IAAI,KAAK,eAAe,MAAM,WAAW,QAAQ,CAAC,CAAC;EAElF,QAAQ,SAAS,sBAAsB,aAAa,IAAI;EACxD,SAAS,SAAS,IAAI;CACvB;CAEA,OAAO;AACR;;AAGA,SAAS,aAAa,MAAsB;CAC3C,IAAI,SAAS,SAAS,SAAS,WAC9B,OAAO;CAGR,IAAI,QAAQ,aACX,OAAO,wBAAwB,IAAI,IAAI,IAAI,oBAAoB;CAGhE,IAAI,QAAQ,WACX,OAAO;CAGR,IAAI,QAAQ,cACX,OAAO;CAIR,OAAO,YAAY,IAAI,IAAI,sBAAsB;AAClD;;AAGA,SAAS,WAAW,OAAuB;CAC1C,IAAI,CAAC,OAAO,SAAS,KAAK,GACzB,OAAO;CAGR,MAAM,YAAY,KAAK,IAAI,KAAK;CAKhC,OAJuB,OAAO,UAAU,KAAK,KAAK,YAAY,kCAK1D,QAAQ,IAAI,YAAY,KAAK,cAAc,SAAS,IACrD,OAAO,KAAK,CAAC,CAAC;AAClB;;AAGA,SAAS,cAAc,WAA2B;CACjD,MAAM,SAAS,YAAY,IAAI,IAAI,KAAK,MAAM,KAAK,MAAM,SAAS,CAAC,IAAI;CAGvE,OAAO,YAAY,MAAM,SAAS,SAAS,SAAS;AACrD;;AAGA,SAAS,qBAAqB,OAAyB;CACtD,MAAM,OAAO,OAAO;CACpB,OAAO,SAAS,eAAe,SAAS,cAAc,SAAS;AAChE;AAEA,SAAS,YAAY,OAAwC;CAC5D,OAAO,OAAO,UAAU,YAAY,UAAU;AAC/C;AAEA,SAAS,cAAc,OAAqC;CAC3D,OAAO,YAAY,OAAO,KAAK,KAAK,YAAY,SAAS,OAAO,MAAM,WAAW;AAClF;AAEA,SAAS,gBAAgB,MAAuB;CAC/C,OAAO,QAAQ,sBAAsB,QAAQ;AAC9C;AAEA,SAAS,eAAe,MAAuB;CAC9C,OAAO,QAAQ,qBAAqB,QAAQ;AAC7C;AAEA,SAAS,YAAY,MAAuB;CAC3C,OAAO,QAAQ,sBAAsB,QAAQ;AAC9C"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//#region src/json/json-size-exceeds.ts
|
|
2
|
+
const QUOTES_SIZE = 2;
|
|
3
|
+
const COLON_SIZE = 1;
|
|
4
|
+
const COMMA_SIZE = 1;
|
|
5
|
+
const EMPTY_CONTAINER_SIZE = 2;
|
|
6
|
+
const NULL_SIZE = 4;
|
|
7
|
+
const TRUE_SIZE = 4;
|
|
8
|
+
const FALSE_SIZE = 5;
|
|
9
|
+
const SIGN_SIZE = 1;
|
|
10
|
+
/** Magnitude from which a number serializes in exponential notation. */
|
|
11
|
+
const EXPONENTIAL_NOTATION_THRESHOLD = 1e21;
|
|
12
|
+
/** `{"type":"Buffer","data":[]}` around the bytes of a Buffer. */
|
|
13
|
+
const BUFFER_ENVELOPE_SIZE = 27;
|
|
14
|
+
/** Longest a byte serializes to inside that envelope, as in `255,`. */
|
|
15
|
+
const MAX_BUFFER_BYTE_SIZE = 4;
|
|
16
|
+
/**
|
|
17
|
+
* Widest a number can serialize to, as in `-0.0000075911789601505095`. Bounds the
|
|
18
|
+
* elements of a binary view, which are counted without being visited.
|
|
19
|
+
*/
|
|
20
|
+
const MAX_NUMBER_SIZE = 25;
|
|
21
|
+
const SHORT_ESCAPE_SIZE = 2;
|
|
22
|
+
const UNICODE_ESCAPE_SIZE = 6;
|
|
23
|
+
const ONE_BYTE_SIZE = 1;
|
|
24
|
+
const TWO_BYTE_SIZE = 2;
|
|
25
|
+
const THREE_BYTE_SIZE = 3;
|
|
26
|
+
const SURROGATE_PAIR_SIZE = 4;
|
|
27
|
+
const CONTROL_MAX = 31;
|
|
28
|
+
const QUOTE = 34;
|
|
29
|
+
const BACKSLASH = 92;
|
|
30
|
+
const ASCII_MAX = 127;
|
|
31
|
+
const TWO_BYTE_MAX = 2047;
|
|
32
|
+
const HIGH_SURROGATE_MIN = 55296;
|
|
33
|
+
const HIGH_SURROGATE_MAX = 56319;
|
|
34
|
+
const LOW_SURROGATE_MIN = 56320;
|
|
35
|
+
const LOW_SURROGATE_MAX = 57343;
|
|
36
|
+
/** Control characters serialization escapes with a letter instead of a code point. */
|
|
37
|
+
const LETTER_ESCAPED_CONTROLS = /* @__PURE__ */ new Set([
|
|
38
|
+
8,
|
|
39
|
+
9,
|
|
40
|
+
10,
|
|
41
|
+
12,
|
|
42
|
+
13
|
|
43
|
+
]);
|
|
44
|
+
/** Key `JSON.stringify` hands to the `toJSON` of the value it is called on. */
|
|
45
|
+
const ROOT_KEY = "";
|
|
46
|
+
/**
|
|
47
|
+
* Tells whether a value exceeds a JSON size limit, without serializing it.
|
|
48
|
+
*
|
|
49
|
+
* The measure is an upper bound, so a value is never reported as fitting a size
|
|
50
|
+
* it does not fit. It overshoots by one byte per non-empty container, and counts
|
|
51
|
+
* binary data at the widest its bytes can serialize to.
|
|
52
|
+
*
|
|
53
|
+
* @param value Value to measure as if it were passed to `JSON.stringify`.
|
|
54
|
+
* @param maxBytes Limit the serialization must stay within.
|
|
55
|
+
* @returns `true` unless the serialization is certainly `maxBytes` or shorter.
|
|
56
|
+
*
|
|
57
|
+
* @remarks Time O(n) in the members and characters of `value`, memory O(depth).
|
|
58
|
+
* Calls `toJSON` on the members defining one, as serialization would.
|
|
59
|
+
*/
|
|
60
|
+
function jsonSizeExceeds(value, maxBytes) {
|
|
61
|
+
const walk = {
|
|
62
|
+
maxBytes,
|
|
63
|
+
frames: [],
|
|
64
|
+
ancestors: /* @__PURE__ */ new Set(),
|
|
65
|
+
size: 0
|
|
66
|
+
};
|
|
67
|
+
addValue(walk, replacedValue(value, ROOT_KEY));
|
|
68
|
+
while (walk.frames.length > 0 && walk.size <= maxBytes) advance(walk, walk.frames[walk.frames.length - 1]);
|
|
69
|
+
return walk.size > maxBytes;
|
|
70
|
+
}
|
|
71
|
+
/** Bytes left before the limit. Negative once the limit is crossed. */
|
|
72
|
+
function remaining(walk) {
|
|
73
|
+
return walk.maxBytes - walk.size;
|
|
74
|
+
}
|
|
75
|
+
/** Measures the next member of the innermost container, or closes it. */
|
|
76
|
+
function advance(walk, frame) {
|
|
77
|
+
if ("elements" in frame) advanceElements(walk, frame);
|
|
78
|
+
else advanceEntries(walk, frame);
|
|
79
|
+
}
|
|
80
|
+
function advanceElements(walk, frame) {
|
|
81
|
+
if (frame.index === frame.elements.length) close(walk, frame.elements);
|
|
82
|
+
else {
|
|
83
|
+
const index = frame.index;
|
|
84
|
+
frame.index += 1;
|
|
85
|
+
walk.size += COMMA_SIZE;
|
|
86
|
+
addValue(walk, replacedValue(frame.elements[index], index));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function advanceEntries(walk, frame) {
|
|
90
|
+
if (frame.index === frame.keys.length) close(walk, frame.entries);
|
|
91
|
+
else {
|
|
92
|
+
const key = frame.keys[frame.index];
|
|
93
|
+
frame.index += 1;
|
|
94
|
+
addEntry(walk, key, replacedValue(frame.entries[key], key));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Adds an entry, unless serialization drops it along with its key. */
|
|
98
|
+
function addEntry(walk, key, value) {
|
|
99
|
+
if (!isDroppedFromObjects(value)) {
|
|
100
|
+
walk.size += 2 + stringSize(key, remaining(walk));
|
|
101
|
+
addValue(walk, value);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Adds what a value occupies on its own, and opens it when it has members.
|
|
106
|
+
* Measuring stops once the limit is crossed, so a size cut short is still above it.
|
|
107
|
+
*/
|
|
108
|
+
function addValue(walk, value) {
|
|
109
|
+
if (isContainer(value)) open(walk, value);
|
|
110
|
+
else walk.size += leafSize(value, remaining(walk));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Adds a container's own delimiters and queues its members, or the whole of it
|
|
114
|
+
* when its size follows from its length alone.
|
|
115
|
+
*/
|
|
116
|
+
function open(walk, container) {
|
|
117
|
+
if (!walk.ancestors.has(container)) {
|
|
118
|
+
const binarySize = maxBinaryViewSize(container);
|
|
119
|
+
if (binarySize === void 0) {
|
|
120
|
+
walk.size += EMPTY_CONTAINER_SIZE;
|
|
121
|
+
walk.ancestors.add(container);
|
|
122
|
+
walk.frames.push(frameFor(container));
|
|
123
|
+
} else walk.size += binarySize;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function close(walk, container) {
|
|
127
|
+
walk.ancestors.delete(container);
|
|
128
|
+
walk.frames.pop();
|
|
129
|
+
}
|
|
130
|
+
function frameFor(container) {
|
|
131
|
+
return Array.isArray(container) ? {
|
|
132
|
+
elements: container,
|
|
133
|
+
index: 0
|
|
134
|
+
} : {
|
|
135
|
+
entries: container,
|
|
136
|
+
keys: Object.keys(container),
|
|
137
|
+
index: 0
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** The value serialization puts in place of this one, given the key holding it. */
|
|
141
|
+
function replacedValue(value, key) {
|
|
142
|
+
return isSelfSerializing(value) ? value.toJSON(String(key)) : value;
|
|
143
|
+
}
|
|
144
|
+
function isSelfSerializing(value) {
|
|
145
|
+
return isContainer(value) && !Buffer.isBuffer(value) && "toJSON" in value && typeof value.toJSON === "function";
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Bytes a Buffer or another binary view occupies serialized, or `undefined` for
|
|
149
|
+
* a container whose members have to be walked.
|
|
150
|
+
*/
|
|
151
|
+
function maxBinaryViewSize(container) {
|
|
152
|
+
if (Buffer.isBuffer(container)) return BUFFER_ENVELOPE_SIZE + MAX_BUFFER_BYTE_SIZE * container.length;
|
|
153
|
+
return isIndexedView(container) ? maxIndexedViewSize(container) : void 0;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Bytes an indexed view occupies as the object of index/element entries it
|
|
157
|
+
* serializes to. Derived from its length, because listing those keys would hold
|
|
158
|
+
* one string per element in memory.
|
|
159
|
+
*/
|
|
160
|
+
function maxIndexedViewSize(view) {
|
|
161
|
+
const lastIndex = Math.max(view.length - 1, 0);
|
|
162
|
+
const maxEntrySize = QUOTES_SIZE + decimalDigits(lastIndex) + COLON_SIZE + MAX_NUMBER_SIZE + COMMA_SIZE;
|
|
163
|
+
return EMPTY_CONTAINER_SIZE + view.length * maxEntrySize;
|
|
164
|
+
}
|
|
165
|
+
/** Bytes a value with no members occupies serialized. */
|
|
166
|
+
function leafSize(value, budget) {
|
|
167
|
+
switch (typeof value) {
|
|
168
|
+
case "string": return stringSize(value, budget);
|
|
169
|
+
case "number": return numberSize(value);
|
|
170
|
+
case "boolean": return value ? TRUE_SIZE : FALSE_SIZE;
|
|
171
|
+
default: return NULL_SIZE;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** Bytes a string occupies serialized, escapes and quotes included. */
|
|
175
|
+
function stringSize(value, budget) {
|
|
176
|
+
return QUOTES_SIZE + escapedContentSize(value, budget - QUOTES_SIZE);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Bytes the escaped characters of a string occupy, quotes excluded. Reads the
|
|
180
|
+
* string one code unit at a time so that nothing is copied, and stops once
|
|
181
|
+
* `budget` is gone, since what is already counted then settles the answer.
|
|
182
|
+
*/
|
|
183
|
+
function escapedContentSize(value, budget) {
|
|
184
|
+
let size = 0;
|
|
185
|
+
let index = 0;
|
|
186
|
+
while (index < value.length && size <= budget) {
|
|
187
|
+
const code = value.charCodeAt(index);
|
|
188
|
+
const paired = isHighSurrogate(code) && isLowSurrogate(value.charCodeAt(index + 1));
|
|
189
|
+
size += paired ? SURROGATE_PAIR_SIZE : codeUnitSize(code);
|
|
190
|
+
index += paired ? 2 : 1;
|
|
191
|
+
}
|
|
192
|
+
return size;
|
|
193
|
+
}
|
|
194
|
+
/** Bytes a single code unit occupies, escaped and encoded as serialization would. */
|
|
195
|
+
function codeUnitSize(code) {
|
|
196
|
+
if (code === QUOTE || code === BACKSLASH) return SHORT_ESCAPE_SIZE;
|
|
197
|
+
if (code <= CONTROL_MAX) return LETTER_ESCAPED_CONTROLS.has(code) ? SHORT_ESCAPE_SIZE : UNICODE_ESCAPE_SIZE;
|
|
198
|
+
if (code <= ASCII_MAX) return ONE_BYTE_SIZE;
|
|
199
|
+
if (code <= TWO_BYTE_MAX) return TWO_BYTE_SIZE;
|
|
200
|
+
return isSurrogate(code) ? UNICODE_ESCAPE_SIZE : THREE_BYTE_SIZE;
|
|
201
|
+
}
|
|
202
|
+
/** Bytes a number occupies serialized. */
|
|
203
|
+
function numberSize(value) {
|
|
204
|
+
if (!Number.isFinite(value)) return NULL_SIZE;
|
|
205
|
+
const magnitude = Math.abs(value);
|
|
206
|
+
return Number.isInteger(value) && magnitude < EXPONENTIAL_NOTATION_THRESHOLD ? (value < 0 ? SIGN_SIZE : 0) + decimalDigits(magnitude) : String(value).length;
|
|
207
|
+
}
|
|
208
|
+
/** Digits the integer part of a magnitude is written with. */
|
|
209
|
+
function decimalDigits(magnitude) {
|
|
210
|
+
const digits = magnitude < 1 ? 1 : Math.floor(Math.log10(magnitude)) + 1;
|
|
211
|
+
return magnitude < 10 ** digits ? digits : digits + 1;
|
|
212
|
+
}
|
|
213
|
+
/** Whether serializing an object drops the entry holding this value, key included. */
|
|
214
|
+
function isDroppedFromObjects(value) {
|
|
215
|
+
const type = typeof value;
|
|
216
|
+
return type === "undefined" || type === "function" || type === "symbol";
|
|
217
|
+
}
|
|
218
|
+
function isContainer(value) {
|
|
219
|
+
return typeof value === "object" && value !== null;
|
|
220
|
+
}
|
|
221
|
+
function isIndexedView(value) {
|
|
222
|
+
return ArrayBuffer.isView(value) && "length" in value && typeof value.length === "number";
|
|
223
|
+
}
|
|
224
|
+
function isHighSurrogate(code) {
|
|
225
|
+
return code >= HIGH_SURROGATE_MIN && code <= HIGH_SURROGATE_MAX;
|
|
226
|
+
}
|
|
227
|
+
function isLowSurrogate(code) {
|
|
228
|
+
return code >= LOW_SURROGATE_MIN && code <= LOW_SURROGATE_MAX;
|
|
229
|
+
}
|
|
230
|
+
function isSurrogate(code) {
|
|
231
|
+
return code >= HIGH_SURROGATE_MIN && code <= LOW_SURROGATE_MAX;
|
|
232
|
+
}
|
|
233
|
+
//#endregion
|
|
234
|
+
export { jsonSizeExceeds };
|
|
235
|
+
|
|
236
|
+
//# sourceMappingURL=json-size-exceeds.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-size-exceeds.mjs","names":[],"sources":["../../src/json/json-size-exceeds.ts"],"sourcesContent":["type JsonContainer = Record<string, unknown> | unknown[];\n\n/** A value serialization replaces with the result of its own `toJSON`. */\ntype SelfSerializing = JsonContainer & { toJSON: (key: string) => unknown };\n\n/** A view over binary data, serialized as one entry per element. */\ntype IndexedView = ArrayBufferView & { length: number };\n\n/** An array being measured, and how far through its elements the walk is. */\ntype ElementsFrame = { readonly elements: unknown[]; index: number };\n\n/** An object being measured, and how far through its keys the walk is. */\ntype EntriesFrame = {\n\treadonly entries: Record<string, unknown>;\n\treadonly keys: string[];\n\tindex: number;\n};\n\ntype Frame = ElementsFrame | EntriesFrame;\n\n/** Bytes counted so far, and the containers the walk still has to finish. */\ntype Walk = {\n\treadonly maxBytes: number;\n\treadonly frames: Frame[];\n\treadonly ancestors: Set<JsonContainer>;\n\tsize: number;\n};\n\nconst QUOTES_SIZE = 2; // `\"\"` around a string or a key\nconst COLON_SIZE = 1;\nconst COMMA_SIZE = 1;\nconst EMPTY_CONTAINER_SIZE = 2; // `{}` or `[]`\nconst NULL_SIZE = 4;\nconst TRUE_SIZE = 4;\nconst FALSE_SIZE = 5;\nconst SIGN_SIZE = 1;\n\n/** Magnitude from which a number serializes in exponential notation. */\nconst EXPONENTIAL_NOTATION_THRESHOLD = 1e21;\n\n/** `{\"type\":\"Buffer\",\"data\":[]}` around the bytes of a Buffer. */\nconst BUFFER_ENVELOPE_SIZE = 27;\n\n/** Longest a byte serializes to inside that envelope, as in `255,`. */\nconst MAX_BUFFER_BYTE_SIZE = 4;\n\n/**\n * Widest a number can serialize to, as in `-0.0000075911789601505095`. Bounds the\n * elements of a binary view, which are counted without being visited.\n */\nconst MAX_NUMBER_SIZE = 25;\n\nconst SHORT_ESCAPE_SIZE = 2; // `\\n`, `\\\"`, `\\\\`\nconst UNICODE_ESCAPE_SIZE = 6; // `\\u001f`, and a lone surrogate\nconst ONE_BYTE_SIZE = 1;\nconst TWO_BYTE_SIZE = 2;\nconst THREE_BYTE_SIZE = 3;\nconst SURROGATE_PAIR_SIZE = 4; // one code point spread over two code units\n\nconst CONTROL_MAX = 0x1f;\nconst QUOTE = 0x22;\nconst BACKSLASH = 0x5c;\nconst ASCII_MAX = 0x7f;\nconst TWO_BYTE_MAX = 0x7ff;\nconst HIGH_SURROGATE_MIN = 0xd800;\nconst HIGH_SURROGATE_MAX = 0xdbff;\nconst LOW_SURROGATE_MIN = 0xdc00;\nconst LOW_SURROGATE_MAX = 0xdfff;\n\n/** Control characters serialization escapes with a letter instead of a code point. */\nconst LETTER_ESCAPED_CONTROLS = new Set([0x08, 0x09, 0x0a, 0x0c, 0x0d]);\n\n/** Key `JSON.stringify` hands to the `toJSON` of the value it is called on. */\nconst ROOT_KEY = '';\n\n/**\n * Tells whether a value exceeds a JSON size limit, without serializing it.\n *\n * The measure is an upper bound, so a value is never reported as fitting a size\n * it does not fit. It overshoots by one byte per non-empty container, and counts\n * binary data at the widest its bytes can serialize to.\n *\n * @param value Value to measure as if it were passed to `JSON.stringify`.\n * @param maxBytes Limit the serialization must stay within.\n * @returns `true` unless the serialization is certainly `maxBytes` or shorter.\n *\n * @remarks Time O(n) in the members and characters of `value`, memory O(depth).\n * Calls `toJSON` on the members defining one, as serialization would.\n */\nexport function jsonSizeExceeds(value: unknown, maxBytes: number): boolean {\n\tconst walk: Walk = { maxBytes, frames: [], ancestors: new Set(), size: 0 };\n\n\taddValue(walk, replacedValue(value, ROOT_KEY));\n\twhile (walk.frames.length > 0 && walk.size <= maxBytes) {\n\t\tadvance(walk, walk.frames[walk.frames.length - 1]);\n\t}\n\n\treturn walk.size > maxBytes;\n}\n\n/** Bytes left before the limit. Negative once the limit is crossed. */\nfunction remaining(walk: Walk): number {\n\treturn walk.maxBytes - walk.size;\n}\n\n/** Measures the next member of the innermost container, or closes it. */\nfunction advance(walk: Walk, frame: Frame): void {\n\tif ('elements' in frame) {\n\t\tadvanceElements(walk, frame);\n\t} else {\n\t\tadvanceEntries(walk, frame);\n\t}\n}\n\nfunction advanceElements(walk: Walk, frame: ElementsFrame): void {\n\tif (frame.index === frame.elements.length) {\n\t\tclose(walk, frame.elements);\n\t} else {\n\t\tconst index = frame.index;\n\t\tframe.index += 1;\n\t\twalk.size += COMMA_SIZE;\n\t\taddValue(walk, replacedValue(frame.elements[index], index));\n\t}\n}\n\nfunction advanceEntries(walk: Walk, frame: EntriesFrame): void {\n\tif (frame.index === frame.keys.length) {\n\t\tclose(walk, frame.entries);\n\t} else {\n\t\tconst key = frame.keys[frame.index];\n\t\tframe.index += 1;\n\t\taddEntry(walk, key, replacedValue(frame.entries[key], key));\n\t}\n}\n\n/** Adds an entry, unless serialization drops it along with its key. */\nfunction addEntry(walk: Walk, key: string, value: unknown): void {\n\tif (!isDroppedFromObjects(value)) {\n\t\twalk.size += COMMA_SIZE + COLON_SIZE + stringSize(key, remaining(walk));\n\t\taddValue(walk, value);\n\t}\n}\n\n/**\n * Adds what a value occupies on its own, and opens it when it has members.\n * Measuring stops once the limit is crossed, so a size cut short is still above it.\n */\nfunction addValue(walk: Walk, value: unknown): void {\n\tif (isContainer(value)) {\n\t\topen(walk, value);\n\t} else {\n\t\twalk.size += leafSize(value, remaining(walk));\n\t}\n}\n\n/**\n * Adds a container's own delimiters and queues its members, or the whole of it\n * when its size follows from its length alone.\n */\nfunction open(walk: Walk, container: JsonContainer): void {\n\t// A container reached from inside itself makes serialization fail, so it has\n\t// no size to answer with, and walking into it again would not end.\n\tif (!walk.ancestors.has(container)) {\n\t\tconst binarySize = maxBinaryViewSize(container);\n\n\t\tif (binarySize === undefined) {\n\t\t\twalk.size += EMPTY_CONTAINER_SIZE;\n\t\t\twalk.ancestors.add(container);\n\t\t\twalk.frames.push(frameFor(container));\n\t\t} else {\n\t\t\twalk.size += binarySize;\n\t\t}\n\t}\n}\n\nfunction close(walk: Walk, container: JsonContainer): void {\n\twalk.ancestors.delete(container);\n\twalk.frames.pop();\n}\n\nfunction frameFor(container: JsonContainer): Frame {\n\treturn Array.isArray(container)\n\t\t? { elements: container, index: 0 }\n\t\t: { entries: container, keys: Object.keys(container), index: 0 };\n}\n\n/** The value serialization puts in place of this one, given the key holding it. */\nfunction replacedValue(value: unknown, key: string | number): unknown {\n\treturn isSelfSerializing(value) ? value.toJSON(String(key)) : value;\n}\n\nfunction isSelfSerializing(value: unknown): value is SelfSerializing {\n\treturn (\n\t\tisContainer(value) &&\n\t\t// A Buffer would hand over an array of one number per byte, which its own\n\t\t// measure derives from its length instead.\n\t\t!Buffer.isBuffer(value) &&\n\t\t'toJSON' in value &&\n\t\ttypeof value.toJSON === 'function'\n\t);\n}\n\n/**\n * Bytes a Buffer or another binary view occupies serialized, or `undefined` for\n * a container whose members have to be walked.\n */\nfunction maxBinaryViewSize(container: JsonContainer): number | undefined {\n\tif (Buffer.isBuffer(container)) {\n\t\treturn BUFFER_ENVELOPE_SIZE + MAX_BUFFER_BYTE_SIZE * container.length;\n\t}\n\n\treturn isIndexedView(container) ? maxIndexedViewSize(container) : undefined;\n}\n\n/**\n * Bytes an indexed view occupies as the object of index/element entries it\n * serializes to. Derived from its length, because listing those keys would hold\n * one string per element in memory.\n */\nfunction maxIndexedViewSize(view: IndexedView): number {\n\tconst lastIndex = Math.max(view.length - 1, 0);\n\tconst maxEntrySize =\n\t\tQUOTES_SIZE + decimalDigits(lastIndex) + COLON_SIZE + MAX_NUMBER_SIZE + COMMA_SIZE;\n\n\treturn EMPTY_CONTAINER_SIZE + view.length * maxEntrySize;\n}\n\n/** Bytes a value with no members occupies serialized. */\nfunction leafSize(value: unknown, budget: number): number {\n\tswitch (typeof value) {\n\t\tcase 'string':\n\t\t\treturn stringSize(value, budget);\n\t\tcase 'number':\n\t\t\treturn numberSize(value);\n\t\tcase 'boolean':\n\t\t\treturn value ? TRUE_SIZE : FALSE_SIZE;\n\t\tdefault:\n\t\t\treturn NULL_SIZE;\n\t}\n}\n\n/** Bytes a string occupies serialized, escapes and quotes included. */\nfunction stringSize(value: string, budget: number): number {\n\treturn QUOTES_SIZE + escapedContentSize(value, budget - QUOTES_SIZE);\n}\n\n/**\n * Bytes the escaped characters of a string occupy, quotes excluded. Reads the\n * string one code unit at a time so that nothing is copied, and stops once\n * `budget` is gone, since what is already counted then settles the answer.\n */\nfunction escapedContentSize(value: string, budget: number): number {\n\tlet size = 0;\n\tlet index = 0;\n\n\twhile (index < value.length && size <= budget) {\n\t\tconst code = value.charCodeAt(index);\n\t\tconst paired = isHighSurrogate(code) && isLowSurrogate(value.charCodeAt(index + 1));\n\n\t\tsize += paired ? SURROGATE_PAIR_SIZE : codeUnitSize(code);\n\t\tindex += paired ? 2 : 1;\n\t}\n\n\treturn size;\n}\n\n/** Bytes a single code unit occupies, escaped and encoded as serialization would. */\nfunction codeUnitSize(code: number): number {\n\tif (code === QUOTE || code === BACKSLASH) {\n\t\treturn SHORT_ESCAPE_SIZE;\n\t}\n\n\tif (code <= CONTROL_MAX) {\n\t\treturn LETTER_ESCAPED_CONTROLS.has(code) ? SHORT_ESCAPE_SIZE : UNICODE_ESCAPE_SIZE;\n\t}\n\n\tif (code <= ASCII_MAX) {\n\t\treturn ONE_BYTE_SIZE;\n\t}\n\n\tif (code <= TWO_BYTE_MAX) {\n\t\treturn TWO_BYTE_SIZE;\n\t}\n\n\t// A surrogate left without its other half, which serialization escapes.\n\treturn isSurrogate(code) ? UNICODE_ESCAPE_SIZE : THREE_BYTE_SIZE;\n}\n\n/** Bytes a number occupies serialized. */\nfunction numberSize(value: number): number {\n\tif (!Number.isFinite(value)) {\n\t\treturn NULL_SIZE;\n\t}\n\n\tconst magnitude = Math.abs(value);\n\tconst isPlainInteger = Number.isInteger(value) && magnitude < EXPONENTIAL_NOTATION_THRESHOLD;\n\n\t// A plain integer has a length its magnitude gives away. Any other number has\n\t// to be formatted to be measured, and nothing shorter would be exact.\n\treturn isPlainInteger\n\t\t? (value < 0 ? SIGN_SIZE : 0) + decimalDigits(magnitude)\n\t\t: String(value).length;\n}\n\n/** Digits the integer part of a magnitude is written with. */\nfunction decimalDigits(magnitude: number): number {\n\tconst digits = magnitude < 1 ? 1 : Math.floor(Math.log10(magnitude)) + 1;\n\n\t// A rounding error in log10 costs a digit on some exact powers of ten.\n\treturn magnitude < 10 ** digits ? digits : digits + 1;\n}\n\n/** Whether serializing an object drops the entry holding this value, key included. */\nfunction isDroppedFromObjects(value: unknown): boolean {\n\tconst type = typeof value;\n\treturn type === 'undefined' || type === 'function' || type === 'symbol';\n}\n\nfunction isContainer(value: unknown): value is JsonContainer {\n\treturn typeof value === 'object' && value !== null;\n}\n\nfunction isIndexedView(value: object): value is IndexedView {\n\treturn ArrayBuffer.isView(value) && 'length' in value && typeof value.length === 'number';\n}\n\nfunction isHighSurrogate(code: number): boolean {\n\treturn code >= HIGH_SURROGATE_MIN && code <= HIGH_SURROGATE_MAX;\n}\n\nfunction isLowSurrogate(code: number): boolean {\n\treturn code >= LOW_SURROGATE_MIN && code <= LOW_SURROGATE_MAX;\n}\n\nfunction isSurrogate(code: number): boolean {\n\treturn code >= HIGH_SURROGATE_MIN && code <= LOW_SURROGATE_MAX;\n}\n"],"mappings":"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"}
|
package/dist/sleep.cjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/sleep.ts
|
|
3
|
+
async function sleepWithAbort(ms, abortSignal) {
|
|
4
|
+
return await new Promise((resolve, reject) => {
|
|
5
|
+
if (abortSignal.aborted) {
|
|
6
|
+
reject(/* @__PURE__ */ new Error("Aborted"));
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const timeout = setTimeout(resolve, ms);
|
|
10
|
+
abortSignal.addEventListener("abort", () => {
|
|
11
|
+
clearTimeout(timeout);
|
|
12
|
+
reject(/* @__PURE__ */ new Error("Aborted"));
|
|
13
|
+
}, { once: true });
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolves after `ms` milliseconds, or rejects early if `abortSignal` is aborted.
|
|
18
|
+
*/
|
|
19
|
+
async function sleep(ms, abortSignal) {
|
|
20
|
+
if (!abortSignal) return await new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
|
+
return await sleepWithAbort(ms, abortSignal);
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
exports.sleep = sleep;
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=sleep.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.cjs","names":[],"sources":["../src/sleep.ts"],"sourcesContent":["async function sleepWithAbort(ms: number, abortSignal: AbortSignal): Promise<void> {\n\treturn await new Promise((resolve, reject) => {\n\t\tif (abortSignal.aborted) {\n\t\t\treject(new Error('Aborted'));\n\t\t\treturn;\n\t\t}\n\n\t\tconst timeout = setTimeout(resolve, ms);\n\n\t\tabortSignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\treject(new Error('Aborted'));\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n}\n\n/**\n * Resolves after `ms` milliseconds, or rejects early if `abortSignal` is aborted.\n */\nexport async function sleep(ms: number, abortSignal?: AbortSignal): Promise<void> {\n\tif (!abortSignal) {\n\t\treturn await new Promise((resolve) => setTimeout(resolve, ms));\n\t}\n\n\treturn await sleepWithAbort(ms, abortSignal);\n}\n"],"mappings":";;AAAA,eAAe,eAAe,IAAY,aAAyC;CAClF,OAAO,MAAM,IAAI,SAAS,SAAS,WAAW;EAC7C,IAAI,YAAY,SAAS;GACxB,uBAAO,IAAI,MAAM,SAAS,CAAC;GAC3B;EACD;EAEA,MAAM,UAAU,WAAW,SAAS,EAAE;EAEtC,YAAY,iBACX,eACM;GACL,aAAa,OAAO;GACpB,uBAAO,IAAI,MAAM,SAAS,CAAC;EAC5B,GACA,EAAE,MAAM,KAAK,CACd;CACD,CAAC;AACF;;;;AAKA,eAAsB,MAAM,IAAY,aAA0C;CACjF,IAAI,CAAC,aACJ,OAAO,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;CAG9D,OAAO,MAAM,eAAe,IAAI,WAAW;AAC5C"}
|
package/dist/sleep.d.cts
ADDED
package/dist/sleep.d.mts
ADDED
package/dist/sleep.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/sleep.ts
|
|
2
|
+
async function sleepWithAbort(ms, abortSignal) {
|
|
3
|
+
return await new Promise((resolve, reject) => {
|
|
4
|
+
if (abortSignal.aborted) {
|
|
5
|
+
reject(/* @__PURE__ */ new Error("Aborted"));
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const timeout = setTimeout(resolve, ms);
|
|
9
|
+
abortSignal.addEventListener("abort", () => {
|
|
10
|
+
clearTimeout(timeout);
|
|
11
|
+
reject(/* @__PURE__ */ new Error("Aborted"));
|
|
12
|
+
}, { once: true });
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolves after `ms` milliseconds, or rejects early if `abortSignal` is aborted.
|
|
17
|
+
*/
|
|
18
|
+
async function sleep(ms, abortSignal) {
|
|
19
|
+
if (!abortSignal) return await new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
|
+
return await sleepWithAbort(ms, abortSignal);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { sleep };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=sleep.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.mjs","names":[],"sources":["../src/sleep.ts"],"sourcesContent":["async function sleepWithAbort(ms: number, abortSignal: AbortSignal): Promise<void> {\n\treturn await new Promise((resolve, reject) => {\n\t\tif (abortSignal.aborted) {\n\t\t\treject(new Error('Aborted'));\n\t\t\treturn;\n\t\t}\n\n\t\tconst timeout = setTimeout(resolve, ms);\n\n\t\tabortSignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\treject(new Error('Aborted'));\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n}\n\n/**\n * Resolves after `ms` milliseconds, or rejects early if `abortSignal` is aborted.\n */\nexport async function sleep(ms: number, abortSignal?: AbortSignal): Promise<void> {\n\tif (!abortSignal) {\n\t\treturn await new Promise((resolve) => setTimeout(resolve, ms));\n\t}\n\n\treturn await sleepWithAbort(ms, abortSignal);\n}\n"],"mappings":";AAAA,eAAe,eAAe,IAAY,aAAyC;CAClF,OAAO,MAAM,IAAI,SAAS,SAAS,WAAW;EAC7C,IAAI,YAAY,SAAS;GACxB,uBAAO,IAAI,MAAM,SAAS,CAAC;GAC3B;EACD;EAEA,MAAM,UAAU,WAAW,SAAS,EAAE;EAEtC,YAAY,iBACX,eACM;GACL,aAAa,OAAO;GACpB,uBAAO,IAAI,MAAM,SAAS,CAAC;EAC5B,GACA,EAAE,MAAM,KAAK,CACd;CACD,CAAC;AACF;;;;AAKA,eAAsB,MAAM,IAAY,aAA0C;CACjF,IAAI,CAAC,aACJ,OAAO,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;CAG9D,OAAO,MAAM,eAAe,IAAI,WAAW;AAC5C"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@n8n/utils",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.43.0",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
7
7
|
"LICENSE.md",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"nanoid": "3.3.8",
|
|
31
|
-
"@n8n/constants": "0.
|
|
31
|
+
"@n8n/constants": "0.34.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@testing-library/jest-dom": "^6.6.3",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"typescript": "7.0.2",
|
|
38
38
|
"vite": "^8.0.2",
|
|
39
39
|
"vitest": "^4.1.9",
|
|
40
|
-
"@n8n/typescript-config": "1.9.0",
|
|
41
40
|
"@n8n/eslint-config": "0.0.1",
|
|
42
|
-
"@n8n/
|
|
41
|
+
"@n8n/typescript-config": "1.9.0",
|
|
42
|
+
"@n8n/vitest-config": "1.20.0"
|
|
43
43
|
},
|
|
44
44
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
45
45
|
"homepage": "https://n8n.io",
|