Skip to content

Commit

Permalink
feat(JavaScript): Support oneof (#1348)
Browse files Browse the repository at this point in the history
### 1. Fixed a performance bug that caused writeInt32 to slow down
Before:
```JavaScript
// reader/index.ts
function writeVarInt32() {
    buffer.byteLength
}
```
After:
```JavaScript
// reader/index.ts
const byteLength = buffer.byteLength;
function writeVarInt32() {
    byteLength
}
```
The byteLength property in a Buffer is slow to access. It appears that
when we access byteLength, the V8 engine processes it using a hash
lookup. so we store it in closure.

### 2.  Support Oneof
Sometimes, the data we want to serialize does not have a confirmed type;
instead, it could be one of several confirmed types. If we use an object
to handle this situation, the size of the resulting binary will be too
large, as it will contain much unused information.

usage:
```JavaScript

      const oneOfThree = Type.oneof({
          option1: Type.string(),
          option2: Type.object("foo", {
              a: Type.int32()
          }),
          option3: Type.int32(),
      });
      const fury = new Fury({ refTracking: true });
      const { serialize, deserialize } = fury.registerSerializer(oneOfThree);
      const obj = {
          option1: "hello"
      }
      const input = serialize(obj);
      const result = deserialize(
          input
      );
      expect(result).toEqual(obj.option1)
```
  • Loading branch information
theweipeng authored Jan 26, 2024
1 parent cb49d11 commit 2f2e60f
Show file tree
Hide file tree
Showing 17 changed files with 467 additions and 100 deletions.
3 changes: 2 additions & 1 deletion javascript/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"scripts": {
"test": "npm run build && jest",
"build": "npm run build -w packages/fury -w packages/hps",
"clear": "rm -rf ./packages/fury/dist && rm -rf ./packages/hps/dist",
"build": "npm run clear && npm run build -w packages/fury -w packages/hps",
"lint": "eslint .",
"lint-fix": "eslint . --fix"
},
Expand Down
205 changes: 154 additions & 51 deletions javascript/packages/fury/lib/description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,112 +20,136 @@
import { InternalSerializerType } from "./type";

export interface TypeDescription {
type: InternalSerializerType
label?: string
type: InternalSerializerType;
label?: string;
}

export interface ObjectTypeDescription extends TypeDescription {
options: {
props: { [key: string]: TypeDescription }
tag: string
}
props: { [key: string]: TypeDescription };
tag: string;
};
}

export interface EnumTypeDescription extends TypeDescription {
options: {
inner: { [key: string]: any }
}
inner: { [key: string]: any };
};
}

export interface OneofTypeDescription extends TypeDescription {
options: {
inner: { [key: string]: TypeDescription };
};
}

export interface ArrayTypeDescription extends TypeDescription {
options: {
inner: TypeDescription
}
inner: TypeDescription;
};
}

export interface TupleTypeDescription extends TypeDescription {
options: {
inner: TypeDescription[]
}
inner: TypeDescription[];
};
}

export interface SetTypeDescription extends TypeDescription {
options: {
key: TypeDescription
}
key: TypeDescription;
};
}

export interface MapTypeDescription extends TypeDescription {
options: {
key: TypeDescription
value: TypeDescription
}
key: TypeDescription;
value: TypeDescription;
};
}

type Props<T> = T extends {
options: {
props?: infer T2 extends { [key: string]: any }
tag: string
}
props?: infer T2 extends { [key: string]: any };
tag: string;
};
}
? {
[P in keyof T2]?: (ToRecordType<T2[P]> | null);
[P in keyof T2]?: (InputType<T2[P]> | null);
}
: unknown;

type InnerProps<T> = T extends {
options: {
inner: infer T2 extends TypeDescription
}
inner: infer T2 extends TypeDescription;
};
}
? (ToRecordType<T2> | null)[]
? (InputType<T2> | null)[]
: unknown;

type MapProps<T> = T extends {
options: {
key: infer T2 extends TypeDescription
value: infer T3 extends TypeDescription
}
key: infer T2 extends TypeDescription;
value: infer T3 extends TypeDescription;
};
}
? Map<ToRecordType<T2>, ToRecordType<T3> | null>
? Map<InputType<T2>, InputType<T3> | null>
: unknown;

type TupleProps<T> = T extends {
options: {
inner: infer T2 extends readonly [...TypeDescription[]]
}
inner: infer T2 extends readonly [...TypeDescription[]];
};
}
? { [K in keyof T2]: ToRecordType<T2[K]> }
? { [K in keyof T2]: InputType<T2[K]> }
: unknown;

type Value<T> = T extends { [s: string]: infer T2 } ? T2 : unknown;

type EnumProps<T> = T extends {
options: {
inner: infer T2
}
inner: infer T2;
};
}
? Value<T2>
: unknown;

type OneofProps<T> = T extends {
options: {
inner?: infer T2 extends { [key: string]: any };
};
}
? {
[P in keyof T2]?: (InputType<T2[P]> | null);
}
: unknown;

type OneofResult<T> = T extends {
options: {
inner?: infer T2;
};
}
? ResultType<Value<T2>>
: unknown;

type SetProps<T> = T extends {
options: {
key: infer T2 extends TypeDescription
}
key: infer T2 extends TypeDescription;
};
}
? Set<(ToRecordType<T2> | null)>
? Set<(InputType<T2> | null)>
: unknown;

export type ToRecordType<T> = T extends {
type: InternalSerializerType.FURY_TYPE_TAG
export type InputType<T> = T extends {
type: InternalSerializerType.FURY_TYPE_TAG;
}
? Props<T>
: T extends {
type: InternalSerializerType.STRING
type: InternalSerializerType.STRING;
}
? string
: T extends {
type: InternalSerializerType.TUPLE
type: InternalSerializerType.TUPLE;
}
? TupleProps<T>
: T extends {
Expand All @@ -137,51 +161,122 @@ export type ToRecordType<T> = T extends {
| InternalSerializerType.INT16
| InternalSerializerType.INT32
| InternalSerializerType.FLOAT
| InternalSerializerType.DOUBLE
| InternalSerializerType.DOUBLE;
}
? number

: T extends {
type: InternalSerializerType.UINT64
| InternalSerializerType.INT64
| InternalSerializerType.INT64;
}
? bigint
: T extends {
type: InternalSerializerType.MAP
type: InternalSerializerType.MAP;
}
? MapProps<T>
: T extends {
type: InternalSerializerType.FURY_SET
type: InternalSerializerType.FURY_SET;
}
? SetProps<T>
: T extends {
type: InternalSerializerType.ARRAY
type: InternalSerializerType.ARRAY;
}
? InnerProps<T>
: T extends {
type: InternalSerializerType.BOOL
type: InternalSerializerType.BOOL;
}
? boolean
: T extends {
type: InternalSerializerType.DATE
type: InternalSerializerType.DATE;
}
? Date
: T extends {
type: InternalSerializerType.TIMESTAMP
type: InternalSerializerType.TIMESTAMP;
}
? number
: T extends {
type: InternalSerializerType.BINARY
type: InternalSerializerType.BINARY;
}
? Uint8Array
: T extends {
type: InternalSerializerType.ANY
type: InternalSerializerType.ANY;
}
? any
: T extends {
type: InternalSerializerType.ENUM
type: InternalSerializerType.ENUM;
}
? EnumProps<T> : unknown;
? EnumProps<T> : T extends {
type: InternalSerializerType.ONEOF;
} ? OneofProps<T> : unknown;

export type ResultType<T> = T extends {
type: InternalSerializerType.FURY_TYPE_TAG;
}
? Props<T>
: T extends {
type: InternalSerializerType.STRING;
}
? string
: T extends {
type: InternalSerializerType.TUPLE;
}
? TupleProps<T>
: T extends {
type:
| InternalSerializerType.UINT8
| InternalSerializerType.UINT16
| InternalSerializerType.UINT32
| InternalSerializerType.INT8
| InternalSerializerType.INT16
| InternalSerializerType.INT32
| InternalSerializerType.FLOAT
| InternalSerializerType.DOUBLE;
}
? number

: T extends {
type: InternalSerializerType.UINT64
| InternalSerializerType.INT64;
}
? bigint
: T extends {
type: InternalSerializerType.MAP;
}
? MapProps<T>
: T extends {
type: InternalSerializerType.FURY_SET;
}
? SetProps<T>
: T extends {
type: InternalSerializerType.ARRAY;
}
? InnerProps<T>
: T extends {
type: InternalSerializerType.BOOL;
}
? boolean
: T extends {
type: InternalSerializerType.DATE;
}
? Date
: T extends {
type: InternalSerializerType.TIMESTAMP;
}
? number
: T extends {
type: InternalSerializerType.BINARY;
}
? Uint8Array
: T extends {
type: InternalSerializerType.ANY;
}
? any
: T extends {
type: InternalSerializerType.ENUM;
}
? EnumProps<T> : T extends {
type: InternalSerializerType.ONEOF;
} ? OneofResult<T> : unknown;

export const Type = {
any() {
Expand All @@ -197,6 +292,14 @@ export const Type = {
},
};
},
oneof<T extends { [key: string]: TypeDescription }>(inner?: T) {
return {
type: InternalSerializerType.ONEOF as const,
options: {
inner,
},
};
},
string() {
return {
type: InternalSerializerType.STRING as const,
Expand Down
8 changes: 4 additions & 4 deletions javascript/packages/fury/lib/fury.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { BinaryReader } from "./reader";
import { ReferenceResolver } from "./referenceResolver";
import { ConfigFlags, Serializer, Config, Language, BinaryReader as BinaryReaderType, BinaryWriter as BinaryWriterType } from "./type";
import { OwnershipError } from "./error";
import { ToRecordType, TypeDescription } from "./description";
import { InputType, ResultType, TypeDescription } from "./description";
import { generateSerializer, AnySerializer } from "./gen";

export default class {
Expand All @@ -50,14 +50,14 @@ export default class {
const serializer = generateSerializer(this, description);
return {
serializer,
serialize: (data: ToRecordType<T>) => {
serialize: (data: InputType<T>) => {
return this.serialize(data, serializer);
},
serializeVolatile: (data: ToRecordType<T>) => {
serializeVolatile: (data: InputType<T>) => {
return this.serializeVolatile(data, serializer);
},
deserialize: (bytes: Uint8Array) => {
return this.deserialize(bytes, serializer) as ToRecordType<T>;
return this.deserialize(bytes, serializer) as ResultType<T>;
},
};
}
Expand Down
Loading

0 comments on commit 2f2e60f

Please sign in to comment.