Skip to content

Commit 887a71a

Browse files
committed
feat(rpc): better protocol
1 parent 9a1df9e commit 887a71a

File tree

20 files changed

+590
-361
lines changed

20 files changed

+590
-361
lines changed

packages/bson/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
export * from './src/model.js';
1212
export * from './src/bson-parser.js';
1313
export { BaseParser } from './src/bson-parser.js';
14+
export { seekElementSize } from './src/continuation.js';
15+
export { BSONType } from './src/utils.js';
1416
export * from './src/bson-deserializer.js';
1517
export * from './src/bson-serializer.js';
1618
export * from './src/strings.js';

packages/bson/src/bson-deserializer-templates.ts

Lines changed: 65 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
} from '@deepkit/type';
4141
import { seekElementSize } from './continuation.js';
4242
import { BSONType, digitByteSize, isSerializable } from './utils.js';
43+
import { BaseParser } from './bson-parser.js';
4344

4445
function getNameComparator(name: string): string {
4546
//todo: support utf8 names
@@ -74,55 +75,49 @@ export function deserializeAny(type: Type, state: TemplateState) {
7475
`);
7576
}
7677

77-
export function deserializeNumber(type: Type, state: TemplateState) {
78-
const readBigInt = type.kind === ReflectionKind.bigint ? `state.parser.parseBinaryBigInt()` : `Number(state.parser.parseBinaryBigInt())`;
78+
const numberParsers = createParserLookup(() => 0, [
79+
[BSONType.INT, parser => parser.parseInt()],
80+
[BSONType.NUMBER, parser => parser.parseNumber()],
81+
[BSONType.LONG, parser => parser.parseLong()],
82+
[BSONType.TIMESTAMP, parser => parser.parseLong()],
83+
[BSONType.BOOLEAN, parser => parser.parseBoolean() ? 1 : 0],
84+
[BSONType.BINARY, parser => Number(parser.parseBinaryBigInt())],
85+
[BSONType.STRING, parser => Number(parser.parseString())],
86+
]);
7987

88+
export function deserializeNumber(type: Type, state: TemplateState) {
89+
state.setContext({ numberParsers });
8090
state.addCode(`
81-
if (state.elementType === ${BSONType.INT}) {
82-
${state.setter} = state.parser.parseInt();
83-
} else if (state.elementType === ${BSONType.NULL} || state.elementType === ${BSONType.UNDEFINED}) {
84-
${state.setter} = 0;
85-
} else if (state.elementType === ${BSONType.NUMBER}) {
86-
${state.setter} = state.parser.parseNumber();
87-
} else if (state.elementType === ${BSONType.LONG} || state.elementType === ${BSONType.TIMESTAMP}) {
88-
${state.setter} = state.parser.parseLong();
89-
} else if (state.elementType === ${BSONType.BOOLEAN}) {
90-
${state.setter} = state.parser.parseBoolean() ? 1 : 0;
91-
} else if (state.elementType === ${BSONType.BINARY}) {
92-
${state.setter} = ${readBigInt};
93-
} else if (state.elementType === ${BSONType.STRING}) {
94-
${state.setter} = Number(state.parser.parseString());
95-
if (isNaN(${state.setter})) {
96-
${throwInvalidBsonType(type, state)}
97-
}
98-
} else {
91+
${state.setter} = numberParsers[state.elementType](state.parser);
92+
if (isNaN(${state.setter})) {
9993
${throwInvalidBsonType(type, state)}
10094
}
10195
`);
10296
}
10397

98+
const bigIntParsers = createParserLookup(() => 0n, [
99+
[BSONType.INT, parser => BigInt(parser.parseInt())],
100+
[BSONType.NUMBER, parser => BigInt(parser.parseNumber())],
101+
[BSONType.LONG, parser => BigInt(parser.parseLong())],
102+
[BSONType.TIMESTAMP, parser => BigInt(parser.parseLong())],
103+
[BSONType.BOOLEAN, parser => BigInt(parser.parseBoolean() ? 1 : 0)],
104+
[BSONType.BINARY, parser => parser.parseBinaryBigInt()],
105+
[BSONType.STRING, parser => BigInt(parser.parseString())],
106+
]);
107+
104108
export function deserializeBigInt(type: Type, state: TemplateState) {
105109
const binaryBigInt = binaryBigIntAnnotation.getFirst(type);
106-
const parseBigInt = binaryBigInt === BinaryBigIntType.signed ? 'parseSignedBinaryBigInt' : 'parseBinaryBigInt';
110+
111+
state.setContext({ bigIntParsers });
112+
let lookup = 'bigIntParsers';
113+
if (binaryBigInt === BinaryBigIntType.signed) {
114+
const customLookup = bigIntParsers.slice();
115+
customLookup[BSONType.BINARY] = parser => parser.parseSignedBinaryBigInt();
116+
lookup = state.setVariable('lookup', customLookup);
117+
}
107118

108119
state.addCode(`
109-
if (state.elementType === ${BSONType.INT}) {
110-
${state.setter} = BigInt(state.parser.parseInt());
111-
} else if (state.elementType === ${BSONType.NULL} || state.elementType === ${BSONType.UNDEFINED}) {
112-
${state.setter} = 0n;
113-
} else if (state.elementType === ${BSONType.NUMBER}) {
114-
${state.setter} = BigInt(state.parser.parseNumber());
115-
} else if (state.elementType === ${BSONType.LONG} || state.elementType === ${BSONType.TIMESTAMP}) {
116-
${state.setter} = BigInt(state.parser.parseLong());
117-
} else if (state.elementType === ${BSONType.BOOLEAN}) {
118-
${state.setter} = BigInt(state.parser.parseBoolean() ? 1 : 0);
119-
} else if (state.elementType === ${BSONType.BINARY} && ${binaryBigInt} !== undefined) {
120-
${state.setter} = state.parser.${parseBigInt}();
121-
} else if (state.elementType === ${BSONType.STRING}) {
122-
${state.setter} = BigInt(state.parser.parseString());
123-
} else {
124-
${throwInvalidBsonType(type, state)}
125-
}
120+
${state.setter} = ${lookup}[state.elementType](state.parser);
126121
`);
127122
}
128123

@@ -205,21 +200,36 @@ export function deserializeUndefined(type: Type, state: TemplateState) {
205200
`);
206201
}
207202

203+
type Parse = (parser: BaseParser) => any;
204+
205+
function createParserLookup(defaultParse: Parse, parsers: [elementType: BSONType, fn: Parse][]): Parse[] {
206+
const result = [
207+
defaultParse, defaultParse, defaultParse, defaultParse, defaultParse,
208+
defaultParse, defaultParse, defaultParse, defaultParse, defaultParse,
209+
defaultParse, defaultParse, defaultParse, defaultParse, defaultParse,
210+
defaultParse, defaultParse, defaultParse, defaultParse, defaultParse,
211+
];
212+
for (const [index, parse] of parsers) {
213+
result[index] = parse;
214+
}
215+
return result;
216+
}
217+
218+
const booleanParsers = createParserLookup(() => 0, [
219+
[BSONType.BOOLEAN, parser => parser.parseBoolean()],
220+
[BSONType.NULL, parser => 0],
221+
[BSONType.UNDEFINED, parser => 0],
222+
[BSONType.INT, parser => !!parser.parseInt()],
223+
[BSONType.NUMBER, parser => !!parser.parseNumber()],
224+
[BSONType.LONG, parser => !!parser.parseLong()],
225+
[BSONType.TIMESTAMP, parser => !!parser.parseLong()],
226+
[BSONType.STRING, parser => !!Number(parser.parseString())],
227+
]);
228+
208229
export function deserializeBoolean(type: Type, state: TemplateState) {
230+
state.setContext({ booleanParsers });
209231
state.addCode(`
210-
if (state.elementType === ${BSONType.BOOLEAN}) {
211-
${state.setter} = state.parser.parseBoolean();
212-
} else if (state.elementType === ${BSONType.NULL} || state.elementType === ${BSONType.UNDEFINED}) {
213-
${state.setter} = false;
214-
} else if (state.elementType === ${BSONType.INT}) {
215-
${state.setter} = state.parser.parseInt() ? true : false;
216-
} else if (state.elementType === ${BSONType.NUMBER}) {
217-
${state.setter} = state.parser.parseNumber() ? true : false;
218-
} else if (state.elementType === ${BSONType.LONG} || state.elementType === ${BSONType.TIMESTAMP}) {
219-
${state.setter} = state.parser.parseLong() ? true : false;
220-
} else {
221-
${throwInvalidBsonType(type, state)}
222-
}
232+
${state.setter} = booleanParsers[state.elementType](state.parser);
223233
`);
224234
}
225235

@@ -538,7 +548,10 @@ export function deserializeArray(type: TypeArray, state: TemplateState) {
538548
state.setContext({ digitByteSize });
539549

540550
state.addCode(`
541-
if (state.elementType && state.elementType !== ${BSONType.ARRAY}) ${throwInvalidBsonType({ kind: ReflectionKind.array, type: elementType }, state)}
551+
if (state.elementType && state.elementType !== ${BSONType.ARRAY}) ${throwInvalidBsonType({
552+
kind: ReflectionKind.array,
553+
type: elementType,
554+
}, state)}
542555
{
543556
var ${result} = [];
544557
const end = state.parser.eatUInt32() + state.parser.offset;

packages/bson/src/bson-parser.ts

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@
88
* You should have received a copy of the MIT License along with this program.
99
*/
1010

11-
import {
12-
BSON_BINARY_SUBTYPE_BYTE_ARRAY,
13-
BSON_BINARY_SUBTYPE_UUID,
14-
BSONType,
15-
digitByteSize,
16-
TWO_PWR_32_DBL_N,
17-
} from './utils.js';
11+
import { BSON_BINARY_SUBTYPE_BYTE_ARRAY, BSON_BINARY_SUBTYPE_UUID, BSONType, digitByteSize, TWO_PWR_32_DBL_N } from './utils.js';
1812
import { decodeUTF8 } from './strings.js';
1913
import { nodeBufferToArrayBuffer, ReflectionKind, SerializationError, Type } from '@deepkit/type';
2014
import { hexTable } from './model.js';
@@ -32,16 +26,53 @@ export function decodeUTF8Parser(parser: BaseParser, size: number = parser.size
3226
return s;
3327
}
3428

29+
export function readUint32LE(buffer: Uint8Array, offset: number): number {
30+
return (
31+
buffer[offset] |
32+
(buffer[offset + 1] << 8) |
33+
(buffer[offset + 2] << 16) |
34+
(buffer[offset + 3] << 24) >>> 0
35+
);
36+
}
37+
38+
export function readInt32LE(buffer: Uint8Array, offset: number): number {
39+
return (
40+
buffer[offset] |
41+
(buffer[offset + 1] << 8) |
42+
(buffer[offset + 2] << 16) |
43+
(buffer[offset + 3] << 24)
44+
);
45+
}
46+
47+
const float64Buffer = new ArrayBuffer(8);
48+
const u32 = new Uint32Array(float64Buffer);
49+
const f64 = new Float64Array(float64Buffer);
50+
51+
export function readFloat64LE(buffer: Uint8Array, offset: number): number {
52+
u32[0] =
53+
buffer[offset] |
54+
(buffer[offset + 1] << 8) |
55+
(buffer[offset + 2] << 16) |
56+
(buffer[offset + 3] << 24);
57+
u32[1] =
58+
buffer[offset + 4] |
59+
(buffer[offset + 5] << 8) |
60+
(buffer[offset + 6] << 16) |
61+
(buffer[offset + 7] << 24);
62+
return f64[0];
63+
}
64+
3565
/**
3666
* This is the (slowest) base parser which parses all property names as utf8.
3767
*/
3868
export class BaseParser {
3969
public size: number;
40-
public dataView: DataView;
4170

42-
constructor(public buffer: Uint8Array, public offset: number = 0) {
71+
constructor(
72+
public buffer: Uint8Array,
73+
public offset: number = 0,
74+
) {
4375
this.size = buffer.byteLength;
44-
this.dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
4576
}
4677

4778
peek(elementType: number, type?: Type) {
@@ -271,7 +302,7 @@ export class BaseParser {
271302
}
272303

273304
peekUInt32(): number {
274-
return this.dataView.getUint32(this.offset, true);
305+
return readUint32LE(this.buffer, this.offset);
275306
}
276307

277308
/**
@@ -304,18 +335,20 @@ export class BaseParser {
304335
}
305336

306337
eatInt32(): number {
338+
const value = readInt32LE(this.buffer, this.offset);
307339
this.offset += 4;
308-
return this.dataView.getInt32(this.offset - 4, true);
340+
return value;
309341
}
310342

311343
eatUInt32(): number {
344+
const value = readUint32LE(this.buffer, this.offset);
312345
this.offset += 4;
313-
return this.dataView.getUint32(this.offset - 4, true);
346+
return value;
314347
}
315348

316349
eatDouble(): number {
350+
const value = readFloat64LE(this.buffer, this.offset);
317351
this.offset += 8;
318-
const value = this.dataView.getFloat64(this.offset - 8, true);
319352
if (isNaN(value)) return 0;
320353
return value;
321354
}

packages/bson/tests/bson-parser.spec.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
import { expect, test } from '@jest/globals';
22
import bson, { Binary } from 'bson';
33
import { deserializeBSON, getBSONDeserializer } from '../src/bson-deserializer.js';
4-
import { BinaryBigInt, copyAndSetParent, MongoId, nodeBufferToArrayBuffer, PrimaryKey, Reference, ReflectionKind, SignedBinaryBigInt, TypeObjectLiteral, typeOf, uuid, UUID } from '@deepkit/type';
4+
import {
5+
BinaryBigInt,
6+
copyAndSetParent,
7+
MongoId,
8+
nodeBufferToArrayBuffer,
9+
PrimaryKey,
10+
Reference,
11+
ReflectionKind,
12+
SignedBinaryBigInt,
13+
TypeObjectLiteral,
14+
typeOf,
15+
uuid,
16+
UUID,
17+
} from '@deepkit/type';
518
import { getClassName } from '@deepkit/core';
619
import { serializeBSONWithoutOptimiser } from '../src/bson-serializer.js';
720
import { BSONType } from '../src/utils';
@@ -21,7 +34,7 @@ test('basic number', () => {
2134
expect(getBSONDeserializer(undefined, schema)(serialize({ v: true }))).toEqual({ v: 1 });
2235
expect(getBSONDeserializer(undefined, schema)(serialize({ v: false }))).toEqual({ v: 0 });
2336
expect(getBSONDeserializer(undefined, schema)(serialize({ v: -1234 }))).toEqual({ v: -1234 });
24-
expect(() => getBSONDeserializer(undefined, schema)(serialize({ v: {} }))).toThrow(`Cannot convert bson type OBJECT to number`);
37+
expect(getBSONDeserializer(undefined, schema)(serialize({ v: {} }))).toEqual({ v: 0 });
2538
});
2639

2740
test('basic bigint', () => {
@@ -35,7 +48,7 @@ test('basic bigint', () => {
3548
expect(getBSONDeserializer(undefined, schema)(serialize({ v: '123' }))).toEqual(obj);
3649
expect(getBSONDeserializer(undefined, schema)(serialize({ v: true }))).toEqual({ v: 1n });
3750
expect(getBSONDeserializer(undefined, schema)(serialize({ v: false }))).toEqual({ v: 0n });
38-
expect(() => getBSONDeserializer(undefined, schema)(serialize({ v: {} }))).toThrow(`Cannot convert bson type OBJECT to bigint`);
51+
expect(getBSONDeserializer(undefined, schema)(serialize({ v: {} }))).toEqual({ v: 0n });
3952
});
4053

4154
test('basic null', () => {
@@ -156,7 +169,7 @@ test('basic binary bigint', () => {
156169
expect(getBSONDeserializer(undefined, schema)(serialize({ v: '123' }))).toEqual({ v: 123n });
157170
expect(getBSONDeserializer(undefined, schema)(serialize({ v: true }))).toEqual({ v: 1n });
158171
expect(getBSONDeserializer(undefined, schema)(serialize({ v: false }))).toEqual({ v: 0n });
159-
expect(() => getBSONDeserializer(undefined, schema)(serialize({ v: {} }))).toThrow(`Cannot convert bson type OBJECT to BinaryBigInt`);
172+
expect(getBSONDeserializer(undefined, schema)(serialize({ v: {} }))).toEqual({ v: 0n });
160173
});
161174

162175
test('basic signed binary bigint', () => {
@@ -173,7 +186,7 @@ test('basic signed binary bigint', () => {
173186
expect(getBSONDeserializer(undefined, schema)(serialize({ v: '123' }))).toEqual({ v: 123n });
174187
expect(getBSONDeserializer(undefined, schema)(serialize({ v: true }))).toEqual({ v: 1n });
175188
expect(getBSONDeserializer(undefined, schema)(serialize({ v: false }))).toEqual({ v: 0n });
176-
expect(() => getBSONDeserializer(undefined, schema)(serialize({ v: {} }))).toThrow(`Cannot convert bson type OBJECT to SignedBinaryBigInt`);
189+
expect(getBSONDeserializer(undefined, schema)(serialize({ v: {} }))).toEqual({ v: 0n });
177190
});
178191

179192
test('basic string', () => {
@@ -198,7 +211,7 @@ test('basic boolean', () => {
198211
expect(getBSONDeserializer(undefined, schema)(bson)).toEqual(obj);
199212
expect(getBSONDeserializer(undefined, schema)(serialize({ v: 123 }))).toEqual({ v: true });
200213
expect(getBSONDeserializer(undefined, schema)(serialize({ v: 0 }))).toEqual({ v: false });
201-
expect(() => getBSONDeserializer(undefined, schema)(serialize({ v: '123' }))).toThrow(`Cannot convert bson type STRING to boolean`);
214+
expect(getBSONDeserializer(undefined, schema)(serialize({ v: '123' }))).toEqual({ v: true });
202215
});
203216

204217
test('basic array buffer', () => {

packages/framework/src/worker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export class RpcServer implements RpcServerInterface {
114114

115115
server.on('connection', (ws, req: HttpRequest) => {
116116
const connection = createRpcConnection({
117-
writeBinary(message) {
117+
write(message) {
118118
ws.send(message);
119119
},
120120
close() {

0 commit comments

Comments
 (0)