@zudojs/serialization 1.1.0 → 1.1.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/dist/serializerEnvelope/envelope.core.d.ts +6 -2
- package/dist/serializerEnvelope/envelope.core.js +15 -10
- package/dist/serializerJson/jsonSerializer.core.js +5 -1
- package/dist/serializerJson/jsonSerializer.restore.js +18 -2
- package/dist/serializerTransforms/transformerRegistry.core.d.ts +5 -1
- package/dist/serializerTransforms/transformerRegistry.core.js +7 -3
- package/package.json +5 -5
|
@@ -30,7 +30,10 @@ export declare function contentTypeForFormat(format: string): string;
|
|
|
30
30
|
* Validates that a value received from the wire is a well-formed envelope.
|
|
31
31
|
*
|
|
32
32
|
* @param envelope - The candidate envelope.
|
|
33
|
-
* @throws {
|
|
33
|
+
* @throws {InvalidSerializedDataError} when the shape or schema version is
|
|
34
|
+
* unusable. Envelope payloads arrive from a queue or an RPC peer, so a
|
|
35
|
+
* rejection has to be distinguishable from an internal bug: a bare `Error`
|
|
36
|
+
* left callers unable to tell hostile input from a defect of their own.
|
|
34
37
|
*/
|
|
35
38
|
export declare function assertValidEnvelope(envelope: unknown): asserts envelope is SerializedEnvelope;
|
|
36
39
|
/**
|
|
@@ -39,7 +42,8 @@ export declare function assertValidEnvelope(envelope: unknown): asserts envelope
|
|
|
39
42
|
* @param envelope - The envelope to unwrap.
|
|
40
43
|
* @param expectedFormat - Optional format to validate against.
|
|
41
44
|
* @returns The raw serialized data.
|
|
42
|
-
* @throws {
|
|
45
|
+
* @throws {InvalidSerializedDataError} when the envelope is malformed or its
|
|
46
|
+
* format doesn't match expectations.
|
|
43
47
|
*/
|
|
44
48
|
export declare function unwrapEnvelope(envelope: SerializedEnvelope, expectedFormat?: string): SerializedValue;
|
|
45
49
|
/**
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* messaging, queues, RPC, and cross-service communication.
|
|
7
7
|
*/
|
|
8
8
|
import { SerializationFormat, SerializationContentType, SERIALIZATION_SCHEMA_VERSION, } from "@zudojs/constants";
|
|
9
|
+
import { InvalidSerializedDataError } from "@zudojs/errors";
|
|
9
10
|
import { decodeUtf8 } from "../serializerTransformsExt/index.js";
|
|
10
11
|
/**
|
|
11
12
|
* Create a serialization envelope wrapping data with metadata.
|
|
@@ -51,32 +52,35 @@ export function contentTypeForFormat(format) {
|
|
|
51
52
|
* Validates that a value received from the wire is a well-formed envelope.
|
|
52
53
|
*
|
|
53
54
|
* @param envelope - The candidate envelope.
|
|
54
|
-
* @throws {
|
|
55
|
+
* @throws {InvalidSerializedDataError} when the shape or schema version is
|
|
56
|
+
* unusable. Envelope payloads arrive from a queue or an RPC peer, so a
|
|
57
|
+
* rejection has to be distinguishable from an internal bug: a bare `Error`
|
|
58
|
+
* left callers unable to tell hostile input from a defect of their own.
|
|
55
59
|
*/
|
|
56
60
|
export function assertValidEnvelope(envelope) {
|
|
57
61
|
if (typeof envelope !== "object" || envelope === null) {
|
|
58
|
-
throw new
|
|
62
|
+
throw new InvalidSerializedDataError(`Malformed envelope: expected an object, got ${envelope === null ? "null" : typeof envelope}`, { format: "envelope" });
|
|
59
63
|
}
|
|
60
64
|
const candidate = envelope;
|
|
61
65
|
if (typeof candidate.metadata !== "object" || candidate.metadata === null) {
|
|
62
|
-
throw new
|
|
66
|
+
throw new InvalidSerializedDataError("Malformed envelope: missing metadata", { format: "envelope" });
|
|
63
67
|
}
|
|
64
68
|
const metadata = candidate.metadata;
|
|
65
69
|
if (typeof metadata.format !== "string" || metadata.format.length === 0) {
|
|
66
|
-
throw new
|
|
70
|
+
throw new InvalidSerializedDataError("Malformed envelope: metadata.format is missing", { format: "envelope" });
|
|
67
71
|
}
|
|
68
72
|
if (typeof candidate.data !== "string" &&
|
|
69
73
|
!(candidate.data instanceof Uint8Array)) {
|
|
70
|
-
throw new
|
|
74
|
+
throw new InvalidSerializedDataError("Malformed envelope: data must be a string or Uint8Array", { format: "envelope" });
|
|
71
75
|
}
|
|
72
76
|
// The version exists so a future producer can be detected rather than
|
|
73
77
|
// silently misread. Older versions stay readable; newer ones do not.
|
|
74
78
|
if (metadata.version !== undefined) {
|
|
75
79
|
if (!Number.isInteger(metadata.version)) {
|
|
76
|
-
throw new
|
|
80
|
+
throw new InvalidSerializedDataError(`Malformed envelope: metadata.version must be an integer, got ${String(metadata.version)}`, { format: "envelope" });
|
|
77
81
|
}
|
|
78
82
|
if (metadata.version > SERIALIZATION_SCHEMA_VERSION) {
|
|
79
|
-
throw new
|
|
83
|
+
throw new InvalidSerializedDataError(`Unsupported envelope schema version ${metadata.version}: this build understands up to ${SERIALIZATION_SCHEMA_VERSION}`, { format: "envelope" });
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
}
|
|
@@ -86,12 +90,13 @@ export function assertValidEnvelope(envelope) {
|
|
|
86
90
|
* @param envelope - The envelope to unwrap.
|
|
87
91
|
* @param expectedFormat - Optional format to validate against.
|
|
88
92
|
* @returns The raw serialized data.
|
|
89
|
-
* @throws {
|
|
93
|
+
* @throws {InvalidSerializedDataError} when the envelope is malformed or its
|
|
94
|
+
* format doesn't match expectations.
|
|
90
95
|
*/
|
|
91
96
|
export function unwrapEnvelope(envelope, expectedFormat) {
|
|
92
97
|
assertValidEnvelope(envelope);
|
|
93
98
|
if (expectedFormat && envelope.metadata.format !== expectedFormat) {
|
|
94
|
-
throw new
|
|
99
|
+
throw new InvalidSerializedDataError(`Envelope format mismatch: expected "${expectedFormat}", got "${envelope.metadata.format}"`, { format: expectedFormat });
|
|
95
100
|
}
|
|
96
101
|
return envelope.data;
|
|
97
102
|
}
|
|
@@ -132,7 +137,7 @@ export function deserializeFromEnvelope(envelope, deserializer, expectedFormat,
|
|
|
132
137
|
}
|
|
133
138
|
const encoding = (envelope.metadata.encoding ?? "utf-8").toLowerCase();
|
|
134
139
|
if (encoding !== "utf-8" && encoding !== "utf8") {
|
|
135
|
-
throw new
|
|
140
|
+
throw new InvalidSerializedDataError(`Unsupported envelope encoding "${envelope.metadata.encoding}": only UTF-8 is supported`, { format: envelope.metadata.format });
|
|
136
141
|
}
|
|
137
142
|
return deserializer.deserialize(decodeUtf8(data), options);
|
|
138
143
|
}
|
|
@@ -56,7 +56,11 @@ export class JSONSerializer {
|
|
|
56
56
|
const maxDepth = opts.maxDepth ?? SerializationLimits.MAX_DEPTH;
|
|
57
57
|
const maxSize = opts.maxSize ?? SerializationLimits.MAX_SIZE;
|
|
58
58
|
if (opts.preserveTypes === true) {
|
|
59
|
-
|
|
59
|
+
// The cycle check runs first and must therefore honour the caller's
|
|
60
|
+
// depth limit: with its own 512-level ceiling it halted on a deep but
|
|
61
|
+
// perfectly acyclic payload and reported a cycle that did not exist,
|
|
62
|
+
// disagreeing with the fast path below for the same input.
|
|
63
|
+
assertNoCircularReference(value, "root", maxDepth);
|
|
60
64
|
assertDepthWithinLimit(value, maxDepth);
|
|
61
65
|
const transformed = transformValue({ transformers: this.transformers, maxDepth, options: opts }, value, 0);
|
|
62
66
|
const json = opts.pretty
|
|
@@ -5,10 +5,18 @@
|
|
|
5
5
|
* plain objects (see `jsonSerializer.escape.ts`).
|
|
6
6
|
*/
|
|
7
7
|
import { InvalidSerializedDataError, SerializationDepthError, TransformerError, isSerializationError, } from "@zudojs/errors";
|
|
8
|
-
import { SerializationTags } from "@zudojs/constants";
|
|
8
|
+
import { SerializationLimits, SerializationTags } from "@zudojs/constants";
|
|
9
9
|
import { isPlainObject } from "@zudojs/types";
|
|
10
10
|
import { escapedBody } from "./jsonSerializer.escape.js";
|
|
11
11
|
import { defineKey } from "./jsonSerializer.keys.js";
|
|
12
|
+
/** Longest prefix of a rejected tag quoted back in an error message. */
|
|
13
|
+
const TAG_EXCERPT_LENGTH = 64;
|
|
14
|
+
/** Clips a wire-supplied tag so it cannot flood an error message or a log line. */
|
|
15
|
+
function describeTag(tag) {
|
|
16
|
+
return tag.length <= TAG_EXCERPT_LENGTH
|
|
17
|
+
? tag
|
|
18
|
+
: `${tag.slice(0, TAG_EXCERPT_LENGTH)}…`;
|
|
19
|
+
}
|
|
12
20
|
/** Rebuilds runtime values from their tagged JSON representation. */
|
|
13
21
|
export function restoreValue(walk, value, depth) {
|
|
14
22
|
if (value === null || typeof value !== "object")
|
|
@@ -29,11 +37,19 @@ export function restoreValue(walk, value, depth) {
|
|
|
29
37
|
}
|
|
30
38
|
const tag = obj[SerializationTags.TYPE];
|
|
31
39
|
if (body === undefined && typeof tag === "string") {
|
|
40
|
+
// The tag arrives from the wire, so bound it before it is looked up or
|
|
41
|
+
// quoted into a message: `SerializationLimits.MAX_TYPE_TAG_LENGTH` was
|
|
42
|
+
// exported and tested but never enforced, which let a 100 000-character
|
|
43
|
+
// tag be interpolated verbatim into an error and from there into a log.
|
|
44
|
+
if (tag.length > SerializationLimits.MAX_TYPE_TAG_LENGTH) {
|
|
45
|
+
throw new InvalidSerializedDataError(`Serialization type tag exceeds ${SerializationLimits.MAX_TYPE_TAG_LENGTH} characters ` +
|
|
46
|
+
`(got ${tag.length}): "${describeTag(tag)}".`, { format: "json" });
|
|
47
|
+
}
|
|
32
48
|
if (walk.transformers.has(tag))
|
|
33
49
|
return revive(walk, tag, obj, depth);
|
|
34
50
|
// An unknown tag is ordinary data unless the caller asked for strictness.
|
|
35
51
|
if (strict) {
|
|
36
|
-
throw new InvalidSerializedDataError(`Unknown serialization type tag: "${tag}". ` +
|
|
52
|
+
throw new InvalidSerializedDataError(`Unknown serialization type tag: "${describeTag(tag)}". ` +
|
|
37
53
|
"Register a transformer for it, or deserialize without strict mode.", { format: "json" });
|
|
38
54
|
}
|
|
39
55
|
}
|
|
@@ -13,7 +13,11 @@ import type { TypeTransformer } from "../serializerTypes/index.js";
|
|
|
13
13
|
*/
|
|
14
14
|
export declare class TransformerRegistry {
|
|
15
15
|
private readonly transformers;
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Register a transformer.
|
|
18
|
+
*
|
|
19
|
+
* @throws {TransformerError} when the registry is already full.
|
|
20
|
+
*/
|
|
17
21
|
register(transformer: TypeTransformer): void;
|
|
18
22
|
/** Unregister a transformer by type tag. */
|
|
19
23
|
unregister(type: string): boolean;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Manages type transformers that handle custom JS types during
|
|
5
5
|
* serialization and deserialization. Keyed by type tag string.
|
|
6
6
|
*/
|
|
7
|
-
import { TransformerNotFoundError } from "@zudojs/errors";
|
|
7
|
+
import { TransformerError, TransformerNotFoundError } from "@zudojs/errors";
|
|
8
8
|
import { SerializationLimits } from "@zudojs/constants";
|
|
9
9
|
/**
|
|
10
10
|
* Registry of type transformers keyed by their type tag.
|
|
@@ -14,10 +14,14 @@ import { SerializationLimits } from "@zudojs/constants";
|
|
|
14
14
|
*/
|
|
15
15
|
export class TransformerRegistry {
|
|
16
16
|
transformers = new Map();
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Register a transformer.
|
|
19
|
+
*
|
|
20
|
+
* @throws {TransformerError} when the registry is already full.
|
|
21
|
+
*/
|
|
18
22
|
register(transformer) {
|
|
19
23
|
if (this.transformers.size >= SerializationLimits.MAX_TRANSFORMERS) {
|
|
20
|
-
throw new
|
|
24
|
+
throw new TransformerError(transformer.type, `Maximum transformer limit (${SerializationLimits.MAX_TRANSFORMERS}) reached.`);
|
|
21
25
|
}
|
|
22
26
|
this.transformers.set(transformer.type, transformer);
|
|
23
27
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/serialization",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Data translation layer with JSON serializer, type transformers, envelopes, and registry for Zudojs applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@zudojs/constants": "1.1.
|
|
23
|
-
"@zudojs/errors": "1.
|
|
24
|
-
"@zudojs/types": "1.1.
|
|
25
|
-
"@zudojs/validation": "1.0.
|
|
22
|
+
"@zudojs/constants": "1.1.1",
|
|
23
|
+
"@zudojs/errors": "1.2.0",
|
|
24
|
+
"@zudojs/types": "1.1.1",
|
|
25
|
+
"@zudojs/validation": "1.0.3"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"typescript": "7.0.2",
|