Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7f84239

Browse filesBrowse files
committed
Refactor 'plugin' mechanism by giving behaviour to ExtData
1 parent ad28160 commit 7f84239
Copy full SHA for 7f84239

File tree

Expand file treeCollapse file tree

6 files changed

+91
-150
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+91
-150
lines changed

‎example/typed-arrays/example.ts

Copy file name to clipboardExpand all lines: example/typed-arrays/example.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { encode, decode, ExtensionCodec } from "../../src";
44
import { typedArrays } from "./plugin";
55

66
const extensionCodec = new ExtensionCodec();
7-
extensionCodec.registerPlugin(typedArrays({ type: 1 }));
7+
extensionCodec.register(typedArrays({ type: 1 }));
88

99
const int16Array = new Int16Array([-4, 1, 5]);
1010
const float32Array = new Float32Array([1, -2, 3, 1e-9, 5]);

‎example/typed-arrays/plugin.ts

Copy file name to clipboard
+71-63Lines changed: 71 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,52 @@
1+
import { ExtData } from "src/ExtData";
12
import type { Encoder } from "../../src/Encoder";
23
import { ensureUint8Array } from "../../src/utils/typedArrays";
34

4-
export function typedArrays<C>({type}: {type: number}) {
5-
const TypedArray = Object.getPrototypeOf(Int8Array);
6-
7-
const arrayConstructors = {
8-
Uint8Array,
9-
Int8Array,
10-
Uint16Array,
11-
Int16Array,
12-
Uint32Array,
13-
Int32Array,
14-
BigUint64Array,
15-
BigInt64Array,
16-
Float32Array,
17-
Float64Array,
18-
};
19-
20-
const arrayTypeNameToNumber: Map<string, number> = new Map([
21-
["Uint8Array", 1],
22-
["Int8Array", 255-1],
23-
["Uint16Array", 2],
24-
["Int16Array", 255-2],
25-
["Uint32Array", 3],
26-
["Int32Array", 255-3],
27-
["BigUint64Array", 4],
28-
["BigInt64Array", 255-4],
29-
["Float32Array", 9],
30-
["Float64Array", 10],
31-
]);
32-
33-
const arrayTypeNumberToName: Map<number, string> = new Map(
34-
[...arrayTypeNameToNumber.entries()]
35-
.map(entry => entry.reverse() as [number, string])
36-
);
37-
38-
const arrayHeaderSize = 2;
5+
const TypedArray = Object.getPrototypeOf(Int8Array);
6+
7+
const arrayConstructors = {
8+
Uint8Array,
9+
Int8Array,
10+
Uint16Array,
11+
Int16Array,
12+
Uint32Array,
13+
Int32Array,
14+
BigUint64Array,
15+
BigInt64Array,
16+
Float32Array,
17+
Float64Array,
18+
};
19+
20+
const arrayTypeNameToNumber: Map<string, number> = new Map([
21+
["Uint8Array", 1],
22+
["Int8Array", 255-1],
23+
["Uint16Array", 2],
24+
["Int16Array", 255-2],
25+
["Uint32Array", 3],
26+
["Int32Array", 255-3],
27+
["BigUint64Array", 4],
28+
["BigInt64Array", 255-4],
29+
["Float32Array", 9],
30+
["Float64Array", 10],
31+
]);
32+
33+
const arrayTypeNumberToName: Map<number, string> = new Map(
34+
[...arrayTypeNameToNumber.entries()]
35+
.map(entry => entry.reverse() as [number, string])
36+
);
37+
38+
const arrayHeaderSize = 2;
3939

40+
export function typedArrays<C>({type}: {type: number}) {
4041
return {
4142
type,
4243

43-
encode(encoder: Encoder, depth: number, object: unknown, context: C) {
44+
encode(object: unknown, context: C) {
4445
if (!(object instanceof TypedArray)) {
45-
return false;
46-
}
47-
48-
const array = object as ArrayBufferView;
49-
const alignment = (array as any).constructor.BYTES_PER_ELEMENT;
50-
const arrayType = arrayTypeNameToNumber.get((array as any).constructor.name)!;
51-
52-
// Always use ext32 to make things simpler for now
53-
const extHeaderSize = 6;
54-
const unalignedDataStart = encoder["pos"] + extHeaderSize + arrayHeaderSize;
55-
const alignBytes = alignment - (unalignedDataStart % alignment);
56-
const extDataSize = arrayHeaderSize + alignBytes + array.buffer.byteLength;
57-
58-
// Ext32 header
59-
encoder["writeU8"](0xc9);
60-
encoder["writeU32"](extDataSize);
61-
encoder["writeU8"](type);
62-
63-
// TypedArray header
64-
encoder["writeU8"](arrayType); // TODO: map typedarray types
65-
encoder["writeU8"](alignBytes);
66-
for (let i = 0; i < alignBytes; i += 1) {
67-
encoder["writeU8"](0);
46+
return null;
6847
}
6948

70-
const bytes = ensureUint8Array(array);
71-
encoder["writeU8a"](bytes);
72-
73-
return true;
49+
return new TypedArrayExtData(type, object as ArrayBufferView);
7450
},
7551

7652
decode(data: Uint8Array, extensionType: number, context: C) {
@@ -92,4 +68,36 @@ export function typedArrays<C>({type}: {type: number}) {
9268
);
9369
},
9470
};
71+
}
72+
73+
class TypedArrayExtData extends ExtData {
74+
constructor(type: number, private readonly array: ArrayBufferView) {
75+
super(type, new Uint8Array());
76+
}
77+
78+
override write<C>(encoder: Encoder<C>, depth: number, source: unknown) {
79+
const alignment = (this.array as any).constructor.BYTES_PER_ELEMENT;
80+
const arrayType = arrayTypeNameToNumber.get((this.array as any).constructor.name)!;
81+
82+
// Always use ext32 to make things simpler for now
83+
const extHeaderSize = 6;
84+
const unalignedDataStart = encoder["pos"] + extHeaderSize + arrayHeaderSize;
85+
const alignBytes = alignment - (unalignedDataStart % alignment);
86+
const extDataSize = arrayHeaderSize + alignBytes + this.array.buffer.byteLength;
87+
88+
// Ext32 header
89+
encoder["writeU8"](0xc9);
90+
encoder["writeU32"](extDataSize);
91+
encoder["writeU8"](this.type);
92+
93+
// TypedArray header
94+
encoder["writeU8"](arrayType); // TODO: map typedarray types
95+
encoder["writeU8"](alignBytes);
96+
for (let i = 0; i < alignBytes; i += 1) {
97+
encoder["writeU8"](0);
98+
}
99+
100+
const bytes = ensureUint8Array(this.array);
101+
encoder["writeU8a"](bytes);
102+
}
95103
}

‎src/Encoder.ts

Copy file name to clipboardExpand all lines: src/Encoder.ts
+1-6Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,10 @@ export class Encoder<ContextType = undefined> {
185185
}
186186

187187
private encodeObject(object: unknown, depth: number) {
188-
const pluginRan = this.extensionCodec.tryToEncodePlugin(this, depth, object, this.context);
189-
if (pluginRan) {
190-
return;
191-
}
192-
193188
// try to encode objects with custom codec first of non-primitives
194189
const ext = this.extensionCodec.tryToEncode(object, this.context);
195190
if (ext != null) {
196-
this.encodeExtension(ext);
191+
ext.write(this, depth, object);
197192
} else if (Array.isArray(object)) {
198193
this.encodeArray(object, depth);
199194
} else if (ArrayBuffer.isView(object)) {

‎src/ExtData.ts

Copy file name to clipboard
+7-1Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
import type { Encoder } from "./Encoder";
2+
13
/**
24
* ExtData is used to handle Extension Types that are not registered to ExtensionCodec.
35
*/
46
export class ExtData {
57
constructor(readonly type: number, readonly data: Uint8Array) {}
6-
}
8+
9+
write<ContextType>(encoder: Encoder<ContextType>, depth: number, source: unknown) {
10+
encoder["encodeExtension"](this);
11+
}
12+
}

‎src/ExtensionCodec.ts

Copy file name to clipboardExpand all lines: src/ExtensionCodec.ts
+10-40Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,20 @@
22

33
import { ExtData } from "./ExtData";
44
import { timestampExtension } from "./timestamp";
5-
import type { Encoder } from "./Encoder";
65

76
export type ExtensionDecoderType<ContextType> = (
87
data: Uint8Array,
98
extensionType: number,
109
context: ContextType,
1110
) => unknown;
1211

13-
export type ExtensionEncoderType<ContextType> = (input: unknown, context: ContextType) => Uint8Array | null;
14-
15-
export type ExtensionEncoderPluginType<ContextType> = (encoder: Encoder<ContextType>, depth: number, input: unknown, context: ContextType) => boolean;
12+
export type ExtensionEncoderType<ContextType> = (input: unknown, context: ContextType) => Uint8Array | ExtData | null;
1613

1714
// immutable interfce to ExtensionCodec
1815
export type ExtensionCodecType<ContextType> = {
1916
// eslint-disable-next-line @typescript-eslint/naming-convention
2017
__brand?: ContextType;
2118
tryToEncode(object: unknown, context: ContextType): ExtData | null;
22-
tryToEncodePlugin(encoder: Encoder<ContextType>, depth: number, object: unknown, context: ContextType): boolean;
2319
decode(data: Uint8Array, extType: number, context: ContextType): unknown;
2420
};
2521

@@ -38,7 +34,6 @@ export class ExtensionCodec<ContextType = undefined> implements ExtensionCodecTy
3834
// custom extensions
3935
private readonly encoders: Array<ExtensionEncoderType<ContextType> | undefined | null> = [];
4036
private readonly decoders: Array<ExtensionDecoderType<ContextType> | undefined | null> = [];
41-
private readonly rawEncoders: Array<ExtensionEncoderPluginType<ContextType> | undefined | null> = [];
4237

4338
public constructor() {
4439
this.register(timestampExtension);
@@ -65,24 +60,6 @@ export class ExtensionCodec<ContextType = undefined> implements ExtensionCodecTy
6560
}
6661
}
6762

68-
public registerPlugin({
69-
type,
70-
encode,
71-
decode,
72-
}: {
73-
type: number;
74-
encode: ExtensionEncoderPluginType<ContextType>;
75-
decode: ExtensionDecoderType<ContextType>;
76-
}): void {
77-
if (type >= 0) {
78-
// custom extensions
79-
this.rawEncoders[type] = encode;
80-
this.decoders[type] = decode;
81-
} else {
82-
throw new Error("cannot register plugin for builtin type");
83-
}
84-
}
85-
8663
public tryToEncode(object: unknown, context: ContextType): ExtData | null {
8764
// built-in extensions
8865
for (let i = 0; i < this.builtInEncoders.length; i++) {
@@ -91,7 +68,7 @@ export class ExtensionCodec<ContextType = undefined> implements ExtensionCodecTy
9168
const data = encodeExt(object, context);
9269
if (data != null) {
9370
const type = -1 - i;
94-
return new ExtData(type, data);
71+
return ensureExtData(type, data);
9572
}
9673
}
9774
}
@@ -103,7 +80,7 @@ export class ExtensionCodec<ContextType = undefined> implements ExtensionCodecTy
10380
const data = encodeExt(object, context);
10481
if (data != null) {
10582
const type = i;
106-
return new ExtData(type, data);
83+
return ensureExtData(type, data);
10784
}
10885
}
10986
}
@@ -115,20 +92,6 @@ export class ExtensionCodec<ContextType = undefined> implements ExtensionCodecTy
11592
return null;
11693
}
11794

118-
public tryToEncodePlugin(encoder: Encoder<ContextType>, depth: number, object: unknown, context: ContextType): boolean {
119-
for (let i = 0; i < this.rawEncoders.length; i++) {
120-
const encodeExt = this.rawEncoders[i];
121-
if (encodeExt != null) {
122-
const accepted = encodeExt(encoder, depth, object, context);
123-
if (accepted) {
124-
return true;
125-
}
126-
}
127-
}
128-
129-
return false;
130-
}
131-
13295
public decode(data: Uint8Array, type: number, context: ContextType): unknown {
13396
const decodeExt = type < 0 ? this.builtInDecoders[-1 - type] : this.decoders[type];
13497
if (decodeExt) {
@@ -139,3 +102,10 @@ export class ExtensionCodec<ContextType = undefined> implements ExtensionCodecTy
139102
}
140103
}
141104
}
105+
106+
function ensureExtData(type: number, ext: Uint8Array | ExtData) {
107+
if (ext instanceof Uint8Array) {
108+
return new ExtData(type, ext);
109+
}
110+
return ext;
111+
}

‎test/ExtensionCodecPlugin.test.ts

Copy file name to clipboardExpand all lines: test/ExtensionCodecPlugin.test.ts
+1-39Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,9 @@ import { encode, decode, Encoder, ExtensionCodec, ExtData, decodeAsync } from ".
33
import { typedArrays } from "../example/typed-arrays/plugin";
44

55
describe("ExtensionCodecPlugin", () => {
6-
context("custom extension plugin", () => {
7-
const extensionCodec = new ExtensionCodec();
8-
9-
// Set<T>
10-
extensionCodec.registerPlugin({
11-
type: 0,
12-
encode: (encoder: Encoder, depth: number, object: unknown): boolean => {
13-
if (object instanceof Set) {
14-
// This uses the plugin mechanism in a pointless way: simply encoding an extension
15-
// the same as it would have been normally.
16-
const extData = encode([...object]);
17-
encoder["encodeExtension"](new ExtData(0, extData));
18-
return true;
19-
}
20-
return false;
21-
},
22-
decode: (data: Uint8Array) => {
23-
const array = decode(data) as Array<unknown>;
24-
return new Set(array);
25-
},
26-
});
27-
28-
it("encodes and decodes custom data types (synchronously)", () => {
29-
const set = new Set([1, 2, 3]);
30-
const encoded = encode([set], { extensionCodec });
31-
assert.deepStrictEqual(decode(encoded, { extensionCodec }), [set]);
32-
});
33-
34-
it("encodes and decodes custom data types (asynchronously)", async () => {
35-
const set = new Set([1, 2, 3]);
36-
const encoded = encode([set], { extensionCodec });
37-
const createStream = async function* () {
38-
yield encoded;
39-
};
40-
assert.deepStrictEqual(await decodeAsync(createStream(), { extensionCodec }), [set]);
41-
});
42-
});
43-
446
context("typed-arrays-plugin example", () => {
457
const extensionCodec = new ExtensionCodec();
46-
extensionCodec.registerPlugin(typedArrays({type: 1}));
8+
extensionCodec.register(typedArrays({type: 1}));
479

4810
it("encodes and decodes a Float32Array (synchronously)", () => {
4911
const floatArray = new Float32Array([1, 2, 3, 4, 5]);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.