@zudojs/serialization 1.2.0 → 1.2.1
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/README.md
CHANGED
|
@@ -98,8 +98,11 @@ picked up.
|
|
|
98
98
|
or `createSerializer`) to use only your registry. A value that no transformer
|
|
99
99
|
handles and that JSON would write as `{}` (a `Map`, `Set`, `WeakMap`, `Error`,
|
|
100
100
|
`RegExp`, `ArrayBuffer`, ...) then throws `SerializeError` under
|
|
101
|
-
`preserveTypes` instead of being silently emptied.
|
|
102
|
-
|
|
101
|
+
`preserveTypes` instead of being silently emptied. A type no built-in covers
|
|
102
|
+
(`RegExp`, `WeakMap`, `ArrayBuffer`, `DataView`, ...) throws the same way with
|
|
103
|
+
the built-ins on. The message names the fix: register a transformer, or, when
|
|
104
|
+
a built-in handles the type and you turned them off, keep them enabled. To
|
|
105
|
+
compose a registry by hand, start from `createBuiltinTransformers()`.
|
|
103
106
|
|
|
104
107
|
## Untrusted input
|
|
105
108
|
|
|
@@ -150,8 +153,19 @@ serializer.serialize({ error }, { preserveTypes: true }); // name, message, code
|
|
|
150
153
|
serializer.serialize({ error }, { preserveTypes: true, includeStack: true });
|
|
151
154
|
```
|
|
152
155
|
|
|
153
|
-
On the way back
|
|
154
|
-
|
|
156
|
+
On the way back:
|
|
157
|
+
|
|
158
|
+
- A built-in subclass (`TypeError`, `RangeError`, `SyntaxError`,
|
|
159
|
+
`ReferenceError`, `EvalError`, `URIError`, `AggregateError`) is rebuilt with
|
|
160
|
+
its own constructor, so `instanceof TypeError` still holds. Any other name
|
|
161
|
+
is rebuilt as a plain `Error` with `name` set. An `AggregateError` comes back
|
|
162
|
+
with an empty `errors` array, because the inner errors are not serialized.
|
|
163
|
+
- `.stack` is only the header line (`"TypeError: bad input"`) with no frames.
|
|
164
|
+
The reader's own frames would point at the deserializer, not at where the
|
|
165
|
+
error was thrown.
|
|
166
|
+
- A wire-supplied stack (sent with `includeStack`) is attached as a
|
|
167
|
+
non-enumerable `originalStack`. It never overwrites `.stack`, because it is
|
|
168
|
+
text the sender controls.
|
|
155
169
|
|
|
156
170
|
## Envelopes
|
|
157
171
|
|
|
@@ -18,6 +18,9 @@ export declare function toTaggedOutput(transformer: TypeTransformer, raw: unknow
|
|
|
18
18
|
* contents. Only reachable when the built-in transformers are disabled or
|
|
19
19
|
* a type has no transformer.
|
|
20
20
|
*
|
|
21
|
+
* Suggests re-enabling the built-ins only when one of them handles the
|
|
22
|
+
* type; otherwise the fix is registering a transformer.
|
|
23
|
+
*
|
|
21
24
|
* @throws {SerializeError} naming the type and how to fix it.
|
|
22
25
|
*/
|
|
23
26
|
export declare function assertNotLossy(value: object): void;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { SerializationTags } from "@zudojs/constants";
|
|
5
5
|
import { SerializeError } from "@zudojs/errors";
|
|
6
6
|
import { isPlainObject } from "@zudojs/types";
|
|
7
|
+
import { createBuiltinTransformers, } from "./transformerLookup.core.js";
|
|
7
8
|
/**
|
|
8
9
|
* Normalises what a transformer's `serialize` returned into the tagged
|
|
9
10
|
* `{ $type, $value }` form that is written to the wire.
|
|
@@ -37,21 +38,40 @@ const LOSSY_TYPES = [
|
|
|
37
38
|
["ArrayBuffer", (value) => value instanceof ArrayBuffer],
|
|
38
39
|
["DataView", (value) => value instanceof DataView],
|
|
39
40
|
];
|
|
41
|
+
let builtins;
|
|
42
|
+
/**
|
|
43
|
+
* Whether a built-in transformer handles `value`. When one does and the
|
|
44
|
+
* value still reached {@link assertNotLossy}, the built-ins were disabled.
|
|
45
|
+
*/
|
|
46
|
+
function hasBuiltinTransformer(value) {
|
|
47
|
+
builtins ??= createBuiltinTransformers();
|
|
48
|
+
return builtins.findForValue(value) !== undefined;
|
|
49
|
+
}
|
|
50
|
+
/** `"a Map"`, `"an Error"`. */
|
|
51
|
+
function withArticle(name) {
|
|
52
|
+
return `${/^[AEIOU]/.test(name) ? "an" : "a"} ${name}`;
|
|
53
|
+
}
|
|
40
54
|
/**
|
|
41
55
|
* Throws when a value that no transformer handles would be written as `{}`
|
|
42
56
|
* (a `Map`, `Set`, `Error`, `RegExp`, ...), instead of silently losing its
|
|
43
57
|
* contents. Only reachable when the built-in transformers are disabled or
|
|
44
58
|
* a type has no transformer.
|
|
45
59
|
*
|
|
60
|
+
* Suggests re-enabling the built-ins only when one of them handles the
|
|
61
|
+
* type; otherwise the fix is registering a transformer.
|
|
62
|
+
*
|
|
46
63
|
* @throws {SerializeError} naming the type and how to fix it.
|
|
47
64
|
*/
|
|
48
65
|
export function assertNotLossy(value) {
|
|
49
66
|
for (const [name, matches] of LOSSY_TYPES) {
|
|
50
67
|
if (matches(value)) {
|
|
51
|
-
|
|
52
|
-
`
|
|
53
|
-
|
|
54
|
-
`
|
|
68
|
+
const fix = hasBuiltinTransformer(value)
|
|
69
|
+
? `Register a transformer for ${name}, or keep the built-in ` +
|
|
70
|
+
`transformers enabled (do not pass builtins: false).`
|
|
71
|
+
: `Register a transformer for ${name}.`;
|
|
72
|
+
throw new SerializeError(`Cannot serialize ${withArticle(name)} with preserveTypes: no ` +
|
|
73
|
+
`transformer is registered for it, and JSON would write it as {} ` +
|
|
74
|
+
`and lose its contents. ${fix}`, { format: "json" });
|
|
55
75
|
}
|
|
56
76
|
}
|
|
57
77
|
}
|
|
@@ -9,6 +9,33 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { SerializationTags } from "@zudojs/constants";
|
|
11
11
|
const ERROR_TYPE = "Error";
|
|
12
|
+
/**
|
|
13
|
+
* Built-in Error subclasses rebuilt with their own constructor, so a
|
|
14
|
+
* deserialized `TypeError` is still `instanceof TypeError`. Any other name
|
|
15
|
+
* falls back to `Error` with `name` set.
|
|
16
|
+
*/
|
|
17
|
+
const BUILT_IN_ERRORS = Object.freeze({
|
|
18
|
+
TypeError: (message) => new TypeError(message),
|
|
19
|
+
RangeError: (message) => new RangeError(message),
|
|
20
|
+
SyntaxError: (message) => new SyntaxError(message),
|
|
21
|
+
ReferenceError: (message) => new ReferenceError(message),
|
|
22
|
+
EvalError: (message) => new EvalError(message),
|
|
23
|
+
URIError: (message) => new URIError(message),
|
|
24
|
+
AggregateError: (message) => new AggregateError([], message),
|
|
25
|
+
});
|
|
26
|
+
/** Creates the error for a serialized name, preferring a built-in subclass. */
|
|
27
|
+
function createError(name, message) {
|
|
28
|
+
const build = typeof name === "string" && Object.hasOwn(BUILT_IN_ERRORS, name)
|
|
29
|
+
? BUILT_IN_ERRORS[name]
|
|
30
|
+
: undefined;
|
|
31
|
+
if (build !== undefined)
|
|
32
|
+
return build(message);
|
|
33
|
+
const error = new Error(message);
|
|
34
|
+
if (typeof name === "string" && name !== "Error") {
|
|
35
|
+
error.name = name;
|
|
36
|
+
}
|
|
37
|
+
return error;
|
|
38
|
+
}
|
|
12
39
|
/** Transformer that handles Error round-trips. */
|
|
13
40
|
export const ErrorTransformer = {
|
|
14
41
|
type: ERROR_TYPE,
|
|
@@ -33,11 +60,10 @@ export const ErrorTransformer = {
|
|
|
33
60
|
deserialize(value) {
|
|
34
61
|
const data = value;
|
|
35
62
|
const message = typeof data.message === "string" ? data.message : "";
|
|
36
|
-
const error =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
63
|
+
const error = createError(data.name, message);
|
|
64
|
+
// The reader's own frames point at this deserializer, not at where the
|
|
65
|
+
// error was thrown, so the rebuilt stack is the header line only.
|
|
66
|
+
error.stack = message === "" ? error.name : `${error.name}: ${message}`;
|
|
41
67
|
// A stack from the wire is attacker-controlled text. Restoring it over the
|
|
42
68
|
// real one would make the reconstructed error lie about where it came
|
|
43
69
|
// from, so it is carried as a separate, clearly-named field instead.
|
package/package.json
CHANGED