-
Notifications
You must be signed in to change notification settings - Fork 168
/
Copy pathExtensionCodec.ts
107 lines (93 loc) · 3.29 KB
/
ExtensionCodec.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
// ExtensionCodec to handle MessagePack extensions
import { ExtData } from "./ExtData.ts";
import { timestampExtension } from "./timestamp.ts";
export type ExtensionDecoderType<ContextType> = (
data: Uint8Array,
extensionType: number,
context: ContextType,
) => unknown;
export type ExtensionEncoderType<ContextType> = (
input: unknown,
context: ContextType,
) => Uint8Array | ((dataPos: number) => Uint8Array) | null;
// immutable interface to ExtensionCodec
export type ExtensionCodecType<ContextType> = {
// eslint-disable-next-line @typescript-eslint/naming-convention
__brand?: ContextType;
tryToEncode(object: unknown, context: ContextType): ExtData | null;
decode(data: Uint8Array, extType: number, context: ContextType): unknown;
};
export class ExtensionCodec<ContextType = undefined> implements ExtensionCodecType<ContextType> {
public static readonly defaultCodec: ExtensionCodecType<undefined> = new ExtensionCodec();
// ensures ExtensionCodecType<X> matches ExtensionCodec<X>
// this will make type errors a lot more clear
// eslint-disable-next-line @typescript-eslint/naming-convention
__brand?: ContextType;
// built-in extensions
private readonly builtInEncoders: Array<ExtensionEncoderType<ContextType> | undefined | null> = [];
private readonly builtInDecoders: Array<ExtensionDecoderType<ContextType> | undefined | null> = [];
// custom extensions
private readonly encoders: Array<ExtensionEncoderType<ContextType> | undefined | null> = [];
private readonly decoders: Array<ExtensionDecoderType<ContextType> | undefined | null> = [];
public constructor() {
this.register(timestampExtension);
}
public register({
type,
encode,
decode,
}: {
type: number;
encode: ExtensionEncoderType<ContextType>;
decode: ExtensionDecoderType<ContextType>;
}): void {
if (type >= 0) {
// custom extensions
this.encoders[type] = encode;
this.decoders[type] = decode;
} else {
// built-in extensions
const index = -1 - type;
this.builtInEncoders[index] = encode;
this.builtInDecoders[index] = decode;
}
}
public tryToEncode(object: unknown, context: ContextType): ExtData | null {
// built-in extensions
for (let i = 0; i < this.builtInEncoders.length; i++) {
const encodeExt = this.builtInEncoders[i];
if (encodeExt != null) {
const data = encodeExt(object, context);
if (data != null) {
const type = -1 - i;
return new ExtData(type, data);
}
}
}
// custom extensions
for (let i = 0; i < this.encoders.length; i++) {
const encodeExt = this.encoders[i];
if (encodeExt != null) {
const data = encodeExt(object, context);
if (data != null) {
const type = i;
return new ExtData(type, data);
}
}
}
if (object instanceof ExtData) {
// to keep ExtData as is
return object;
}
return null;
}
public decode(data: Uint8Array, type: number, context: ContextType): unknown {
const decodeExt = type < 0 ? this.builtInDecoders[-1 - type] : this.decoders[type];
if (decodeExt) {
return decodeExt(data, type, context);
} else {
// decode() does not fail, returns ExtData instead.
return new ExtData(type, data);
}
}
}