From 08767c7b71b06c9602dd078813153b13d0e75b29 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sat, 8 Jan 2022 18:51:14 +0800 Subject: [PATCH] refactor(struct): remove context interfaces --- common/config/rush/pnpm-lock.yaml | 78 ++++++++++++++---- libraries/struct/README.md | 73 ++++++++++------- libraries/struct/package.json | 5 +- libraries/struct/src/basic/context.ts | 28 ++----- libraries/struct/src/basic/definition.spec.ts | 20 ++++- libraries/struct/src/basic/definition.ts | 13 +-- .../struct/src/basic/field-value.spec.ts | 43 ++++++---- libraries/struct/src/basic/field-value.ts | 8 +- libraries/struct/src/struct.spec.ts | 82 +++++++++++-------- libraries/struct/src/struct.ts | 68 +++++++++------ libraries/struct/src/syncbird.ts | 40 +++++++++ .../struct/src/types/array-buffer.spec.ts | 42 +++------- libraries/struct/src/types/array-buffer.ts | 70 +++++++++------- libraries/struct/src/types/number.spec.ts | 41 ++-------- libraries/struct/src/types/number.ts | 40 ++++++--- .../variable-length-array-buffer.spec.ts | 75 +++++------------ .../src/types/variable-length-array-buffer.ts | 15 ++-- libraries/struct/src/utils.ts | 13 +++ libraries/struct/tsconfig.json | 4 +- toolchain/ts-package-builder/package.json | 4 +- 20 files changed, 429 insertions(+), 333 deletions(-) create mode 100644 libraries/struct/src/syncbird.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 29c1e810..726cbfa0 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -23,12 +23,14 @@ specifiers: '@rush-temp/ts-package-builder': file:./projects/ts-package-builder.tgz '@rush-temp/unofficial-adb-book': file:./projects/unofficial-adb-book.tgz '@svgr/webpack': ^5.5.0 + '@types/bluebird': ^3.5.36 '@types/dom-webcodecs': ^0.1.3 - '@types/jest': ^26.0.23 - '@types/node': ^16.9.1 + '@types/jest': ^27.4.0 + '@types/node': ^16.11.19 '@types/react': 17.0.27 '@types/w3c-web-usb': ^1.0.4 '@yume-chan/async': ^2.1.4 + bluebird: ^3.7.2 clsx: ^1.1.1 eslint: 7.32.0 eslint-config-next: ^12.0.7 @@ -78,12 +80,14 @@ dependencies: '@rush-temp/ts-package-builder': file:projects/ts-package-builder.tgz '@rush-temp/unofficial-adb-book': file:projects/unofficial-adb-book.tgz_fe5950f86df08edf541b29db75ee5ce3 '@svgr/webpack': 5.5.0 + '@types/bluebird': 3.5.36 '@types/dom-webcodecs': 0.1.3 - '@types/jest': 26.0.24 - '@types/node': 16.11.17 + '@types/jest': 27.4.0 + '@types/node': 16.11.19 '@types/react': 17.0.27 '@types/w3c-web-usb': 1.0.5 '@yume-chan/async': 2.1.4 + bluebird: 3.7.2 clsx: 1.1.1 eslint: 7.32.0 eslint-config-next: 12.0.7_eslint@7.32.0+next@12.0.7 @@ -3480,6 +3484,10 @@ packages: '@babel/types': 7.16.0 dev: false + /@types/bluebird/3.5.36: + resolution: {integrity: sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==} + dev: false + /@types/body-parser/1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: @@ -3603,11 +3611,11 @@ packages: '@types/istanbul-lib-report': 3.0.0 dev: false - /@types/jest/26.0.24: - resolution: {integrity: sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==} + /@types/jest/27.4.0: + resolution: {integrity: sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==} dependencies: - jest-diff: 26.6.2 - pretty-format: 26.6.2 + jest-diff: 27.4.6 + pretty-format: 27.4.6 dev: false /@types/json-schema/7.0.9: @@ -3643,6 +3651,10 @@ packages: resolution: {integrity: sha512-C1vTZME8cFo8uxY2ui41xcynEotVkczIVI5AjLmy5pkpBv/FtG+jhtOlfcPysI8VRVwoOMv6NJm44LGnoMSWkw==} dev: false + /@types/node/16.11.19: + resolution: {integrity: sha512-BPAcfDPoHlRQNKktbsbnpACGdypPFBuX4xQlsWDE7B8XXcfII+SpOLay3/qZmCLb39kV5S1RTYwXdkx2lwLYng==} + dev: false + /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: false @@ -4204,6 +4216,11 @@ packages: color-convert: 2.0.1 dev: false + /ansi-styles/5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: false + /anymatch/2.0.0: resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==} dependencies: @@ -5981,6 +5998,11 @@ packages: engines: {node: '>= 10.14.2'} dev: false + /diff-sequences/27.4.0: + resolution: {integrity: sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dev: false + /diffie-hellman/5.0.3: resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} dependencies: @@ -8556,6 +8578,16 @@ packages: pretty-format: 26.6.2 dev: false + /jest-diff/27.4.6: + resolution: {integrity: sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 27.4.0 + jest-get-type: 27.4.0 + pretty-format: 27.4.6 + dev: false + /jest-docblock/26.0.0: resolution: {integrity: sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==} engines: {node: '>= 10.14.2'} @@ -8609,6 +8641,11 @@ packages: engines: {node: '>= 10.14.2'} dev: false + /jest-get-type/27.4.0: + resolution: {integrity: sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dev: false + /jest-haste-map/26.6.2: resolution: {integrity: sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==} engines: {node: '>= 10.14.2'} @@ -10909,6 +10946,15 @@ packages: react-is: 17.0.2 dev: false + /pretty-format/27.4.6: + resolution: {integrity: sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + dev: false + /pretty-time/1.1.0: resolution: {integrity: sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==} engines: {node: '>=4'} @@ -13991,11 +14037,12 @@ packages: dev: false file:projects/scrcpy.tgz: - resolution: {integrity: sha512-RhLyxPrPiFxJQ+Baq50eIebhA5txN5f+h4hIfFMcXQZzHFx7+HcdyZvUlDFS8Z5JngkwVGu5dD6lk59F78cCYA==, tarball: file:projects/scrcpy.tgz} + resolution: {integrity: sha512-ZoKLRmeOhzbvUl1tXGFjRmPKRFcGWQjkfocPpSRiZLvU0kdb62Plq65M2m0tnQuSsge+m1VcsbC7zf0fyMNmhQ==, tarball: file:projects/scrcpy.tgz} name: '@rush-temp/scrcpy' version: 0.0.0 dependencies: '@types/dom-webcodecs': 0.1.3 + '@types/jest': 27.4.0 '@yume-chan/async': 2.1.4 gh-release-fetch: 2.0.4 jest: 26.6.3 @@ -14013,11 +14060,14 @@ packages: dev: false file:projects/struct.tgz: - resolution: {integrity: sha512-j1oCxwpkmde9LO5iyZHD3ndKGeD8ECVh7n8wpm4mqiQmHzay++2ky0djoqL5j3+sMycUmQfMKq7llNvNN0tDHg==, tarball: file:projects/struct.tgz} + resolution: {integrity: sha512-bQp42RvxKuE6ql0/uinMby4wEgAWnpgnWrtd2+Z3QD+SSTLIc3HpoXuuahoeWnx6rIWjS3zYgA5FKijJZflyGQ==, tarball: file:projects/struct.tgz} name: '@rush-temp/struct' version: 0.0.0 dependencies: - '@types/node': 16.11.17 + '@types/bluebird': 3.5.36 + '@types/jest': 27.4.0 + '@types/node': 16.11.19 + bluebird: 3.7.2 jest: 26.6.3 tslib: 2.3.1 typescript: 4.5.4 @@ -14030,12 +14080,12 @@ packages: dev: false file:projects/ts-package-builder.tgz: - resolution: {integrity: sha512-M7CMjBTO+fqKzrmWZgx3veL5IqvlOvvjYRJsMe6l/ylsaApwrJthKN7QwHE1wcHm7M/cCbakhA8oucRMLSAqxw==, tarball: file:projects/ts-package-builder.tgz} + resolution: {integrity: sha512-EKE72WCmD/Gkx/d5IU0i1BApSo+DjvxTn9WyWeiq8VkVYw9MyXceD4Zio6Cntm7z2QHhaGkLkuCWhC0/h9XkQg==, tarball: file:projects/ts-package-builder.tgz} name: '@rush-temp/ts-package-builder' version: 0.0.0 dependencies: - '@types/jest': 26.0.24 - '@types/node': 16.11.17 + '@types/jest': 27.4.0 + '@types/node': 16.11.19 json5: 2.2.0 typescript: 4.5.4 dev: false diff --git a/libraries/struct/README.md b/libraries/struct/README.md index 97d8cf64..647aa1b7 100644 --- a/libraries/struct/README.md +++ b/libraries/struct/README.md @@ -8,6 +8,8 @@ A C-style structure serializer and deserializer. Written in TypeScript and highly takes advantage of its type system. +**WARNING:** The public API is UNSTABLE. If you have any questions, please open an issue. + ## Installation ```sh @@ -214,9 +216,9 @@ So it's technically possible to pass in an incompatible type (e.g. `string`). Bu const value = await struct.deserialize(stream); value.foo; // number - struct.serialize({ }, context) // error: 'foo' is required - struct.serialize({ foo: 'bar' }, context) // error: 'foo' must be a number - struct.serialize({ foo: 42 }, context) // ok + struct.serialize({ }) // error: 'foo' is required + struct.serialize({ foo: 'bar' }) // error: 'foo' must be a number + struct.serialize({ foo: 42 }) // ok ``` 2. Set fields' type (can be used with [`placeholder` method](#placeholder)) @@ -235,9 +237,9 @@ So it's technically possible to pass in an incompatible type (e.g. `string`). Bu value.foo; // MyEnum value.bar; // MyEnum.a - struct.serialize({ foo: 42, bar: MyEnum.a }, context); // error: 'foo' must be of type `MyEnum` - struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }, context); // error: 'bar' must be of type `MyEnum.a` - struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }, context); // ok + struct.serialize({ foo: 42, bar: MyEnum.a }); // error: 'foo' must be of type `MyEnum` + struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }); // error: 'bar' must be of type `MyEnum.a` + struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }); // ok ``` #### `int64`/`uint64` @@ -334,7 +336,7 @@ Merges (flats) another `Struct`'s fields and extra fields into the current one. .fields(MyStructV1) .int32('field2'); - const structV2 = await MyStructV2.deserialize(context); + const structV2 = await MyStructV2.deserialize(stream); structV2.field1; // number structV2.field2; // number // Fields are flatten @@ -352,7 +354,7 @@ Merges (flats) another `Struct`'s fields and extra fields into the current one. .int32('field2') .fields(MyStructV1); - const structV2 = await MyStructV2.deserialize(context); + const structV2 = await MyStructV2.deserialize(stream); structV2.field1; // number structV2.field2; // number // Same result as above, but serialize/deserialize order is reversed @@ -409,8 +411,8 @@ Multiple calls merge all extra fields together. value.foo; // number value.bar; // 'hello' - struct.serialize({ foo: 42 }, context); // ok - struct.serialize({ foo: 42, bar: 'hello' }, context); // error: 'bar' is redundant + struct.serialize({ foo: 42 }); // ok + struct.serialize({ foo: 42, bar: 'hello' }); // error: 'bar' is redundant ``` 2. Add getters and methods. `this` in functions refers to the result object. @@ -520,37 +522,43 @@ A callback returning anything other than `undefined` will cause `deserialize` to #### `deserialize` ```ts -interface StructDeserializationContext { - decodeUtf8(buffer: ArrayBuffer): string; - - read(length: number): ArrayBuffer | Promise; +interface StructDeserializeStream { + /** + * Read data from the underlying data source. + * + * Stream must return exactly `length` bytes or data. If that's not possible + * (due to end of file or other error condition), it must throw an error. + */ + read(length: number): ArrayBuffer; } deserialize( - context: StructDeserializationContext + stream: StructDeserializeStream, +): TPostDeserialized extends undefined + ? Overwrite + : TPostDeserialized +>; +deserialize( + stream: StructAsyncDeserializeStream, ): Promise< TPostDeserialized extends undefined ? Overwrite : TPostDeserialized - >; + > +>; ``` -Deserialize a Struct value from `context`. +Deserialize a Struct value from `stream`. As the signature shows, if the `postDeserialize` callback returns any value, `deserialize` will return that value instead. -The `read` method of `context`, when being called, should returns exactly `length` bytes of data (or throw an `Error` if it can't). +The `read` method of `stream`, when being called, should returns exactly `length` bytes of data (or throw an `Error` if it can't). #### `serialize` ```ts -interface StructSerializationContext { - encodeUtf8(input: string): ArrayBuffer; -} - serialize( - init: Omit, - context: StructSerializationContext + init: Omit ): ArrayBuffer; ``` @@ -634,7 +642,6 @@ Actual size should be returned from `StructFieldValue#getSize` ```ts abstract create( options: Readonly, - context: StructSerializationContext, struct: StructValue, value: TValue, ): StructFieldValue; @@ -649,16 +656,21 @@ Derived classes must implement this method to create its own field value instanc ```ts abstract deserialize( options: Readonly, - context: StructDeserializationContext, + stream: StructDeserializeStream, struct: StructValue, -): ValueOrPromise>; +): StructFieldValue; +abstract deserialize( + options: Readonly, + stream: StructAsyncDeserializeStream, + struct: StructValue, +): Promise>; ``` -Derived classes must implement this method to define how to deserialize a value from `context`. Can also return a `Promise`. +Derived classes must implement this method to define how to deserialize a value from `stream`. Can also return a `Promise`. Usually implementations should be: -1. Somehow parse the value from `context` +1. Somehow parse the value from `stream` 2. Pass the value into its `create` method Sometimes, some metadata is present when deserializing, but need to be calculated when serializing, for example a UTF-8 encoded string may have different length between itself (character count) and serialized form (byte length). So `deserialize` can save those metadata on the `StructFieldValue` instance for later use. @@ -701,8 +713,7 @@ If one needs to manipulate other states when getting/setting values, they can ov ```ts abstract serialize( dataView: DataView, - offset: number, - context: StructSerializationContext + offset: number ): void; ``` diff --git a/libraries/struct/package.json b/libraries/struct/package.json index 7f038132..5d7952ad 100644 --- a/libraries/struct/package.json +++ b/libraries/struct/package.json @@ -33,12 +33,15 @@ "prepublishOnly": "npm run build" }, "dependencies": { + "bluebird": "^3.7.2", "tslib": "^2.3.1" }, "devDependencies": { "jest": "^26.6.3", "typescript": "^4.4.3", "@yume-chan/ts-package-builder": "^1.0.0", - "@types/node": "^16.9.1" + "@types/jest": "^27.4.0", + "@types/node": "^16.11.19", + "@types/bluebird": "^3.5.36" } } diff --git a/libraries/struct/src/basic/context.ts b/libraries/struct/src/basic/context.ts index 654ffa19..d3d9b087 100644 --- a/libraries/struct/src/basic/context.ts +++ b/libraries/struct/src/basic/context.ts @@ -1,30 +1,14 @@ -import type { ValueOrPromise } from '../utils'; - -/** - * Context with enough methods to serialize a struct - */ -export interface StructSerializationContext { +export interface StructDeserializeStream { /** - * Encode the specified string into an ArrayBuffer using UTF-8 encoding + * Read data from the underlying data source. + * + * Stream must return exactly `length` bytes or data. If that's not possible + * (due to end of file or other error condition), it must throw an error. */ - encodeUtf8(input: string): ArrayBuffer; -} - -export interface StructSyncDeserializationContext extends StructSerializationContext { - decodeUtf8(buffer: ArrayBuffer): string; - read(length: number): ArrayBuffer; } -/** - * Context with enough methods to deserialize a struct - */ -export interface StructDeserializationContext extends StructSerializationContext { - /** - * Decode the specified `ArrayBuffer` using UTF-8 encoding - */ - decodeUtf8(buffer: ArrayBuffer): string; - +export interface StructAsyncDeserializeStream { /** * Read data from the underlying data source. * diff --git a/libraries/struct/src/basic/definition.spec.ts b/libraries/struct/src/basic/definition.spec.ts index 768d4955..2a904e74 100644 --- a/libraries/struct/src/basic/definition.spec.ts +++ b/libraries/struct/src/basic/definition.spec.ts @@ -1,5 +1,5 @@ import type { ValueOrPromise } from '../utils'; -import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context'; +import type { StructAsyncDeserializeStream, StructDeserializeStream, StructOptions } from './context'; import { StructFieldDefinition } from './definition'; import type { StructFieldValue } from './field-value'; import type { StructValue } from './struct-value'; @@ -14,10 +14,24 @@ describe('StructFieldDefinition', () => { public getSize(): number { throw new Error('Method not implemented.'); } - public create(options: Readonly, context: StructSerializationContext, struct: StructValue, value: unknown): StructFieldValue { + public create(options: Readonly, struct: StructValue, value: unknown): StructFieldValue { throw new Error('Method not implemented.'); } - public deserialize(options: Readonly, context: StructDeserializationContext, struct: StructValue): ValueOrPromise> { + public override deserialize( + options: Readonly, + stream: StructDeserializeStream, + struct: StructValue, + ): StructFieldValue; + public override deserialize( + options: Readonly, + stream: StructAsyncDeserializeStream, + struct: StructValue, + ): Promise>; + public deserialize( + options: Readonly, + stream: StructDeserializeStream | StructAsyncDeserializeStream, + struct: StructValue + ): ValueOrPromise> { throw new Error('Method not implemented.'); } } diff --git a/libraries/struct/src/basic/definition.ts b/libraries/struct/src/basic/definition.ts index b782f7c5..8052cce6 100644 --- a/libraries/struct/src/basic/definition.ts +++ b/libraries/struct/src/basic/definition.ts @@ -1,5 +1,4 @@ -import type { ValueOrPromise } from '../utils'; -import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context'; +import type { StructAsyncDeserializeStream, StructDeserializeStream, StructOptions } from './context'; import type { StructFieldValue } from './field-value'; import type { StructValue } from './struct-value'; @@ -53,7 +52,6 @@ export abstract class StructFieldDefinition< */ public abstract create( options: Readonly, - context: StructSerializationContext, struct: StructValue, value: TValue, ): StructFieldValue; @@ -63,7 +61,12 @@ export abstract class StructFieldDefinition< */ public abstract deserialize( options: Readonly, - context: StructDeserializationContext, + stream: StructDeserializeStream, struct: StructValue, - ): ValueOrPromise>; + ): StructFieldValue; + public abstract deserialize( + options: Readonly, + stream: StructAsyncDeserializeStream, + struct: StructValue, + ): Promise>; } diff --git a/libraries/struct/src/basic/field-value.spec.ts b/libraries/struct/src/basic/field-value.spec.ts index c5616e08..8087a950 100644 --- a/libraries/struct/src/basic/field-value.spec.ts +++ b/libraries/struct/src/basic/field-value.spec.ts @@ -1,5 +1,5 @@ import type { ValueOrPromise } from '../utils'; -import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context'; +import type { StructAsyncDeserializeStream, StructDeserializeStream, StructOptions } from './context'; import { StructFieldDefinition } from './definition'; import { StructFieldValue } from './field-value'; import type { StructValue } from './struct-value'; @@ -8,21 +8,19 @@ describe('StructFieldValue', () => { describe('.constructor', () => { it('should save parameters', () => { class MockStructFieldValue extends StructFieldValue { - public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void { + public serialize(dataView: DataView, offset: number): void { throw new Error('Method not implemented.'); } } - const definition = 1 as any; - const options = 2 as any; - const context = 3 as any; - const struct = 4 as any; - const value = 5 as any; + const definition = {} as any; + const options = {} as any; + const struct = {} as any; + const value = {} as any; - const fieldValue = new MockStructFieldValue(definition, options, context, struct, value); + const fieldValue = new MockStructFieldValue(definition, options, struct, value); expect(fieldValue).toHaveProperty('definition', definition); expect(fieldValue).toHaveProperty('options', options); - expect(fieldValue).toHaveProperty('context', context); expect(fieldValue).toHaveProperty('struct', struct); expect(fieldValue.get()).toBe(value); }); @@ -34,22 +32,37 @@ describe('StructFieldValue', () => { public getSize(): number { return 42; } - public create(options: Readonly, context: StructSerializationContext, struct: StructValue, value: unknown): StructFieldValue { + public create(options: Readonly, struct: StructValue, value: unknown): StructFieldValue { throw new Error('Method not implemented.'); } - public deserialize(options: Readonly, context: StructDeserializationContext, struct: StructValue): ValueOrPromise> { + + public override deserialize( + options: Readonly, + stream: StructDeserializeStream, + struct: StructValue, + ): StructFieldValue; + public override deserialize( + options: Readonly, + stream: StructAsyncDeserializeStream, + struct: StructValue, + ): Promise>; + public override deserialize( + options: Readonly, + stream: StructDeserializeStream | StructAsyncDeserializeStream, + struct: StructValue, + ): ValueOrPromise> { throw new Error('Method not implemented.'); } } class MockStructFieldValue extends StructFieldValue { - public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void { + public serialize(dataView: DataView, offset: number): void { throw new Error('Method not implemented.'); } } const fieldDefinition = new MockFieldDefinition(); - const fieldValue = new MockStructFieldValue(fieldDefinition, undefined as any, undefined as any, undefined as any, undefined as any); + const fieldValue = new MockStructFieldValue(fieldDefinition, undefined as any, undefined as any, undefined as any); expect(fieldValue.getSize()).toBe(42); }); }); @@ -57,12 +70,12 @@ describe('StructFieldValue', () => { describe('#set', () => { it('should update its internal value', () => { class MockStructFieldValue extends StructFieldValue { - public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void { + public serialize(dataView: DataView, offset: number): void { throw new Error('Method not implemented.'); } } - const fieldValue = new MockStructFieldValue(undefined as any, undefined as any, undefined as any, undefined as any, undefined as any); + const fieldValue = new MockStructFieldValue(undefined as any, undefined as any, undefined as any, undefined as any); fieldValue.set(1); expect(fieldValue.get()).toBe(1); diff --git a/libraries/struct/src/basic/field-value.ts b/libraries/struct/src/basic/field-value.ts index f89c9f30..8cc33e3e 100644 --- a/libraries/struct/src/basic/field-value.ts +++ b/libraries/struct/src/basic/field-value.ts @@ -1,4 +1,4 @@ -import type { StructOptions, StructSerializationContext } from './context'; +import type { StructOptions } from './context'; import type { StructFieldDefinition } from './definition'; import type { StructValue } from './struct-value'; @@ -17,9 +17,6 @@ export abstract class StructFieldValue< /** Gets the options of the associated `Struct` */ public readonly options: Readonly; - /** Gets the serialization context of the associated `Struct` instance */ - public readonly context: StructSerializationContext; - /** Gets the associated `Struct` instance */ public readonly struct: StructValue; @@ -28,13 +25,11 @@ export abstract class StructFieldValue< public constructor( definition: TDefinition, options: Readonly, - context: StructSerializationContext, struct: StructValue, value: TDefinition['TValue'], ) { this.definition = definition; this.options = options; - this.context = context; this.struct = struct; this.value = value; } @@ -68,6 +63,5 @@ export abstract class StructFieldValue< public abstract serialize( dataView: DataView, offset: number, - context: StructSerializationContext ): void; } diff --git a/libraries/struct/src/struct.spec.ts b/libraries/struct/src/struct.spec.ts index d1bfa3cc..a9a4bf12 100644 --- a/libraries/struct/src/struct.spec.ts +++ b/libraries/struct/src/struct.spec.ts @@ -1,16 +1,12 @@ -import { StructDefaultOptions, StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from './basic'; +import { StructAsyncDeserializeStream, StructDefaultOptions, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from './basic'; import { Struct } from './struct'; import { ArrayBufferFieldType, FixedLengthArrayBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition } from './types'; import { ValueOrPromise } from './utils'; -class MockDeserializationContext implements StructDeserializationContext { +class MockDeserializationStream implements StructDeserializeStream { public buffer = new ArrayBuffer(0); public read = jest.fn((length: number) => this.buffer); - - public encodeUtf8 = jest.fn((input: string) => Buffer.from(input, 'utf-8')); - - public decodeUtf8 = jest.fn((buffer: ArrayBuffer) => Buffer.from(buffer).toString('utf-8')); } describe('Struct', () => { @@ -32,10 +28,24 @@ describe('Struct', () => { return this.options; }); - public create(options: Readonly, context: StructSerializationContext, struct: StructValue, value: unknown): StructFieldValue { + public create(options: Readonly, struct: StructValue, value: unknown): StructFieldValue { throw new Error('Method not implemented.'); } - public deserialize(options: Readonly, context: StructDeserializationContext, struct: StructValue): ValueOrPromise> { + public override deserialize( + options: Readonly, + stream: StructDeserializeStream, + struct: StructValue, + ): StructFieldValue; + public override deserialize( + options: Readonly, + stream: StructAsyncDeserializeStream, + struct: StructValue, + ): Promise>; + public override deserialize( + options: Readonly, + stream: StructDeserializeStream | StructAsyncDeserializeStream, + struct: StructValue + ): ValueOrPromise> { throw new Error('Method not implemented.'); } } @@ -265,17 +275,17 @@ describe('Struct', () => { .int8('foo') .int16('bar'); - const context = new MockDeserializationContext(); - context.read + const stream = new MockDeserializationStream(); + stream.read .mockReturnValueOnce(new Uint8Array([2]).buffer) .mockReturnValueOnce(new Uint8Array([0, 16]).buffer); - const result = await struct.deserialize(context); + const result = await struct.deserialize(stream); expect(result).toEqual({ foo: 2, bar: 16 }); - expect(context.read).toBeCalledTimes(2); - expect(context.read).nthCalledWith(1, 1); - expect(context.read).nthCalledWith(2, 2); + expect(stream.read).toBeCalledTimes(2); + expect(stream.read).nthCalledWith(1, 1); + expect(stream.read).nthCalledWith(2, 2); }); it('should deserialize with dynamic size fields', async () => { @@ -283,16 +293,16 @@ describe('Struct', () => { .int8('fooLength') .uint8ClampedArray('foo', { lengthField: 'fooLength' }); - const context = new MockDeserializationContext(); - context.read + const stream = new MockDeserializationStream(); + stream.read .mockReturnValueOnce(new Uint8Array([2]).buffer) .mockReturnValueOnce(new Uint8Array([3, 4]).buffer); - const result = await struct.deserialize(context); + const result = await struct.deserialize(stream); expect(result).toEqual({ fooLength: 2, foo: new Uint8ClampedArray([3, 4]) }); - expect(context.read).toBeCalledTimes(2); - expect(context.read).nthCalledWith(1, 1); - expect(context.read).nthCalledWith(2, 2); + expect(stream.read).toBeCalledTimes(2); + expect(stream.read).nthCalledWith(1, 1); + expect(stream.read).nthCalledWith(2, 2); }); }); @@ -301,8 +311,8 @@ describe('Struct', () => { const struct = new Struct() .extra({ foo: 42, bar: true }); - const context = new MockDeserializationContext(); - const result = await struct.deserialize(context); + const stream = new MockDeserializationStream(); + const result = await struct.deserialize(stream); expect(Object.entries(Object.getOwnPropertyDescriptors(result))).toEqual([ ['foo', { configurable: true, enumerable: true, writable: true, value: 42 }], @@ -318,8 +328,8 @@ describe('Struct', () => { set bar(value) { }, }); - const context = new MockDeserializationContext(); - const result = await struct.deserialize(context); + const stream = new MockDeserializationStream(); + const result = await struct.deserialize(stream); expect(Object.entries(Object.getOwnPropertyDescriptors(result))).toEqual([ ['foo', { configurable: true, enumerable: true, get: expect.any(Function) }], @@ -334,8 +344,8 @@ describe('Struct', () => { const callback = jest.fn(() => { throw new Error('mock'); }); struct.postDeserialize(callback); - const context = new MockDeserializationContext(); - expect(struct.deserialize(context)).rejects.toThrowError('mock'); + const stream = new MockDeserializationStream(); + expect(struct.deserialize(stream)).rejects.toThrowError('mock'); expect(callback).toBeCalledTimes(1); }); @@ -344,8 +354,8 @@ describe('Struct', () => { const callback = jest.fn(() => 'mock'); struct.postDeserialize(callback); - const context = new MockDeserializationContext(); - expect(struct.deserialize(context)).resolves.toBe('mock'); + const stream = new MockDeserializationStream(); + expect(struct.deserialize(stream)).resolves.toBe('mock'); expect(callback).toBeCalledTimes(1); expect(callback).toBeCalledWith({}); }); @@ -355,8 +365,8 @@ describe('Struct', () => { const callback = jest.fn(); struct.postDeserialize(callback); - const context = new MockDeserializationContext(); - const result = await struct.deserialize(context); + const stream = new MockDeserializationStream(); + const result = await struct.deserialize(stream); expect(callback).toBeCalledTimes(1); expect(callback).toBeCalledWith(result); @@ -371,8 +381,8 @@ describe('Struct', () => { const callback2 = jest.fn(); struct.postDeserialize(callback2); - const context = new MockDeserializationContext(); - await struct.deserialize(context); + const stream = new MockDeserializationStream(); + await struct.deserialize(stream); expect(callback1).toBeCalledTimes(0); expect(callback2).toBeCalledTimes(1); @@ -386,8 +396,8 @@ describe('Struct', () => { .int8('foo') .int16('bar'); - const context = new MockDeserializationContext(); - const result = new Uint8Array(struct.serialize({ foo: 0x42, bar: 0x1024 }, context)); + const stream = new MockDeserializationStream(); + const result = new Uint8Array(struct.serialize({ foo: 0x42, bar: 0x1024 })); expect(result).toEqual(new Uint8Array([0x42, 0x10, 0x24])); }); @@ -397,8 +407,8 @@ describe('Struct', () => { .int8('fooLength') .arrayBuffer('foo', { lengthField: 'fooLength' }); - const context = new MockDeserializationContext(); - const result = new Uint8Array(struct.serialize({ foo: new Uint8Array([0x03, 0x04, 0x05]).buffer }, context)); + const stream = new MockDeserializationStream(); + const result = new Uint8Array(struct.serialize({ foo: new Uint8Array([0x03, 0x04, 0x05]).buffer })); expect(result).toEqual(new Uint8Array([0x03, 0x03, 0x04, 0x05])); }); diff --git a/libraries/struct/src/struct.ts b/libraries/struct/src/struct.ts index a5df1ba2..312f069e 100644 --- a/libraries/struct/src/struct.ts +++ b/libraries/struct/src/struct.ts @@ -1,9 +1,11 @@ -import { StructDefaultOptions, StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from './basic'; +import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions } from './basic'; +import { StructDefaultOptions, StructValue } from './basic'; +import { Syncbird } from "./syncbird"; import { ArrayBufferFieldType, ArrayBufferLikeFieldType, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, LengthField, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types'; -import { Awaited, Evaluate, Identity, Overwrite } from './utils'; +import { Awaited, Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils"; export interface StructLike { - deserialize(context: StructDeserializationContext): Promise; + deserialize(stream: StructDeserializeStream | StructAsyncDeserializeStream): Promise; } /** @@ -53,7 +55,7 @@ interface ArrayBufferLikeFieldCreator< */ < TName extends PropertyKey, - TType extends ArrayBufferLikeFieldType, + TType extends ArrayBufferLikeFieldType, TTypeScriptType = TType['TTypeScriptType'], >( name: TName, @@ -77,7 +79,7 @@ interface ArrayBufferLikeFieldCreator< */ < TName extends PropertyKey, - TType extends ArrayBufferLikeFieldType, + TType extends ArrayBufferLikeFieldType, TOptions extends VariableLengthArrayBufferLikeFieldOptions, TTypeScriptType = TType['TTypeScriptType'], >( @@ -106,7 +108,7 @@ interface BindedArrayBufferLikeFieldDefinitionCreator< TOmitInitKey extends PropertyKey, TExtra extends object, TPostDeserialized, - TType extends ArrayBufferLikeFieldType + TType extends ArrayBufferLikeFieldType > { < TName extends PropertyKey, @@ -520,33 +522,53 @@ export class Struct< return this as any; } - public async deserialize( - context: StructDeserializationContext - ): Promise> { + public deserialize( + stream: StructDeserializeStream, + ): StructDeserializedResult; + public deserialize( + stream: StructAsyncDeserializeStream, + ): Promise>; + public deserialize( + stream: StructDeserializeStream | StructAsyncDeserializeStream, + ): ValueOrPromise> { const value = new StructValue(); Object.defineProperties(value.value, this._extra); - for (const [name, definition] of this._fields) { - const fieldValue = await definition.deserialize(this.options, context, value); - value.set(name, fieldValue); - } + return Syncbird.try(() => { + const iterator = this._fields[Symbol.iterator](); + const iterate: () => StructValue | Syncbird = () => { + const result = iterator.next(); + if (result.done) { + return value; + } - if (this._postDeserialized) { - const object = value.value as TFields; - const result = this._postDeserialized.call(object, object); - if (result) { - return result; + const [name, definition] = result.value; + return Syncbird.resolve( + definition.deserialize(this.options, stream as any, value) + ).then(fieldValue => { + value.set(name, fieldValue); + return iterate(); + }); + }; + return iterate(); + }).then(value => { + if (this._postDeserialized) { + const object = value.value as TFields; + const result = this._postDeserialized.call(object, object); + if (result) { + return result; + } } - } - return value.value as any; + return value.value; + }).valueOrPromise(); } - public serialize(init: Evaluate>, context: StructSerializationContext): ArrayBuffer { + public serialize(init: Evaluate>): ArrayBuffer { const value = new StructValue(); for (const [name, definition] of this._fields) { - const fieldValue = definition.create(this.options, context, value, (init as any)[name]); + const fieldValue = definition.create(this.options, value, (init as any)[name]); value.set(name, fieldValue); } @@ -564,7 +586,7 @@ export class Struct< const dataView = new DataView(buffer); let offset = 0; for (const { fieldValue, size } of fieldsInfo) { - fieldValue.serialize(dataView, offset, context); + fieldValue.serialize(dataView, offset); offset += size; } diff --git a/libraries/struct/src/syncbird.ts b/libraries/struct/src/syncbird.ts new file mode 100644 index 00000000..aa386d46 --- /dev/null +++ b/libraries/struct/src/syncbird.ts @@ -0,0 +1,40 @@ +import Bluebird from 'bluebird'; + +export type Resolvable = R | PromiseLike; + +export class Syncbird extends Bluebird implements PromiseLike { + public static resolve(): Syncbird; + public static resolve(value: Resolvable): Syncbird; + public static resolve(value?: Resolvable): Syncbird { + return new Syncbird( + resolve => resolve(value) + ); + } + + static try(fn: () => Resolvable): Syncbird { + return Syncbird.resolve(fn()); + } + + public then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null + ): Syncbird { + if (this.isFulfilled()) { + if (!onfulfilled) { + return this as unknown as Syncbird; + } else { + return Syncbird.resolve(onfulfilled(this.value())); + } + } else { + return Syncbird.resolve(super.then(onfulfilled, onrejected)); + } + } + + public valueOrPromise(): T | Promise { + if (this.isFulfilled()) { + return this.value(); + } else { + return this as Promise; + } + } +} diff --git a/libraries/struct/src/types/array-buffer.spec.ts b/libraries/struct/src/types/array-buffer.spec.ts index 5b35c748..ca4aa822 100644 --- a/libraries/struct/src/types/array-buffer.spec.ts +++ b/libraries/struct/src/types/array-buffer.spec.ts @@ -1,14 +1,10 @@ -import { StructDefaultOptions, StructDeserializationContext, StructValue } from '../basic'; +import { StructDefaultOptions, StructDeserializeStream, StructValue } from '../basic'; import { ArrayBufferFieldType, ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer'; -class MockDeserializationContext implements StructDeserializationContext { +class MockDeserializationStream implements StructDeserializeStream { public buffer = new ArrayBuffer(0); public read = jest.fn((length: number) => this.buffer); - - public encodeUtf8 = jest.fn((input: string) => Buffer.from(input, 'utf-8')); - - public decodeUtf8 = jest.fn((buffer: ArrayBuffer) => Buffer.from(buffer).toString('utf-8')); } describe('Types', () => { @@ -67,27 +63,13 @@ describe('Types', () => { it('`#toArrayBuffer` should return the decoded string', () => { const text = 'foo'; const arrayBuffer = Buffer.from(text, 'utf-8'); - const context = new MockDeserializationContext(); - expect(StringFieldType.instance.toArrayBuffer(text, context)).toEqual(arrayBuffer); - expect(context.encodeUtf8).toBeCalledTimes(1); - expect(context.encodeUtf8).toBeCalledWith(text); + expect(StringFieldType.instance.toArrayBuffer(text)).toEqual(arrayBuffer); }); it('`#fromArrayBuffer` should return the encoded ArrayBuffer', () => { const text = 'foo'; const arrayBuffer = Buffer.from(text, 'utf-8'); - const context: StructDeserializationContext = { - decodeUtf8(arrayBuffer: ArrayBuffer): string { - return Buffer.from(arrayBuffer).toString('utf-8'); - }, - encodeUtf8(input) { - throw new Error('Method not implemented.'); - }, - read(length) { - throw new Error('Method not implemented.'); - }, - }; - expect(StringFieldType.instance.fromArrayBuffer(arrayBuffer, context)).toBe(text); + expect(StringFieldType.instance.fromArrayBuffer(arrayBuffer)).toBe(text); }); it('`#getSize` should return -1', () => { @@ -107,7 +89,7 @@ describe('Types', () => { const size = 10; const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); - const context = new MockDeserializationContext(); + const context = new MockDeserializationStream(); const buffer = new ArrayBuffer(size); context.buffer = buffer; const struct = new StructValue(); @@ -124,7 +106,7 @@ describe('Types', () => { const size = 10; const definition = new MockArrayBufferFieldDefinition(Uint8ClampedArrayFieldType.instance, size); - const context = new MockDeserializationContext(); + const context = new MockDeserializationStream(); const buffer = new ArrayBuffer(size); context.buffer = buffer; const struct = new StructValue(); @@ -143,7 +125,7 @@ describe('Types', () => { const size = 0; const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); - const context = new MockDeserializationContext(); + const context = new MockDeserializationStream(); const buffer = new ArrayBuffer(size); context.buffer = buffer; const struct = new StructValue(); @@ -165,7 +147,7 @@ describe('Types', () => { const size = 0; const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); - const context = new MockDeserializationContext(); + const context = new MockDeserializationStream(); const buffer = new ArrayBuffer(size); context.buffer = buffer; const struct = new StructValue(); @@ -184,7 +166,7 @@ describe('Types', () => { const size = 0; const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); - const context = new MockDeserializationContext(); + const context = new MockDeserializationStream(); const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i)); const buffer = sourceArray.buffer; context.buffer = buffer; @@ -194,7 +176,7 @@ describe('Types', () => { const targetArray = new Uint8Array(size); const targetView = new DataView(targetArray.buffer); - fieldValue.serialize(targetView, 0, context); + fieldValue.serialize(targetView, 0); expect(targetArray).toEqual(sourceArray); }); @@ -203,7 +185,7 @@ describe('Types', () => { const size = 0; const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); - const context = new MockDeserializationContext(); + const context = new MockDeserializationStream(); const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i)); const buffer = sourceArray.buffer; context.buffer = buffer; @@ -215,7 +197,7 @@ describe('Types', () => { const targetArray = new Uint8Array(size); const targetView = new DataView(targetArray.buffer); - fieldValue.serialize(targetView, 0, context); + fieldValue.serialize(targetView, 0); expect(targetArray).toEqual(sourceArray); }); diff --git a/libraries/struct/src/types/array-buffer.ts b/libraries/struct/src/types/array-buffer.ts index 062715b1..99e7f374 100644 --- a/libraries/struct/src/types/array-buffer.ts +++ b/libraries/struct/src/types/array-buffer.ts @@ -1,4 +1,6 @@ -import { StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic'; +import { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../basic'; +import { Syncbird } from "../syncbird"; +import { decodeUtf8, encodeUtf8, ValueOrPromise } from "../utils"; /** * Base class for all types that @@ -18,10 +20,10 @@ export abstract class ArrayBufferLikeFieldType extends ArrayBufferLikeFieldType { public static readonly instance = new StringFieldType(); - public toArrayBuffer(value: string, context: StructSerializationContext): ArrayBuffer { - return context.encodeUtf8(value); + public toArrayBuffer(value: string): ArrayBuffer { + return encodeUtf8(value); } - public fromArrayBuffer(arrayBuffer: ArrayBuffer, context: StructDeserializationContext): string { - return context.decodeUtf8(arrayBuffer); + public fromArrayBuffer(arrayBuffer: ArrayBuffer): string { + return decodeUtf8(arrayBuffer); } public getSize(): number { @@ -100,7 +102,7 @@ export class StringFieldType const EmptyArrayBuffer = new ArrayBuffer(0); export abstract class ArrayBufferLikeFieldDefinition< - TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType, + TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType, TOptions = void, TOmitInitKey extends PropertyKey = never, > extends StructFieldDefinition< @@ -124,47 +126,55 @@ export abstract class ArrayBufferLikeFieldDefinition< */ public create( options: Readonly, - context: StructSerializationContext, struct: StructValue, value: TType['TTypeScriptType'], arrayBuffer?: ArrayBuffer, ): ArrayBufferLikeFieldValue { - return new ArrayBufferLikeFieldValue(this, options, context, struct, value, arrayBuffer); + return new ArrayBufferLikeFieldValue(this, options, struct, value, arrayBuffer); } - public async deserialize( + public override deserialize( options: Readonly, - context: StructDeserializationContext, + stream: StructDeserializeStream, struct: StructValue, - ): Promise> { - const size = this.getDeserializeSize(struct); - - let arrayBuffer: ArrayBuffer; - if (size === 0) { - arrayBuffer = EmptyArrayBuffer; - } else { - arrayBuffer = await context.read(size); - } - - const value = this.type.fromArrayBuffer(arrayBuffer, context); - return this.create(options, context, struct, value, arrayBuffer); + ): ArrayBufferLikeFieldValue; + public override deserialize( + options: Readonly, + stream: StructAsyncDeserializeStream, + struct: StructValue, + ): Promise>; + public override deserialize( + options: Readonly, + stream: StructDeserializeStream | StructAsyncDeserializeStream, + struct: StructValue, + ): ValueOrPromise> { + return Syncbird.resolve().then(() => { + const size = this.getDeserializeSize(struct); + if (size === 0) { + return EmptyArrayBuffer; + } else { + return stream.read(size); + } + }).then(arrayBuffer => { + const value = this.type.fromArrayBuffer(arrayBuffer); + return this.create(options, struct, value, arrayBuffer); + }).valueOrPromise(); } } export class ArrayBufferLikeFieldValue< - TDefinition extends ArrayBufferLikeFieldDefinition, + TDefinition extends ArrayBufferLikeFieldDefinition, any, any>, > extends StructFieldValue { protected arrayBuffer: ArrayBuffer | undefined; public constructor( definition: TDefinition, options: Readonly, - context: StructSerializationContext, struct: StructValue, value: TDefinition['TValue'], arrayBuffer?: ArrayBuffer, ) { - super(definition, options, context, struct, value); + super(definition, options, struct, value); this.arrayBuffer = arrayBuffer; } @@ -173,9 +183,9 @@ export class ArrayBufferLikeFieldValue< this.arrayBuffer = undefined; } - public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void { + public serialize(dataView: DataView, offset: number): void { if (!this.arrayBuffer) { - this.arrayBuffer = this.definition.type.toArrayBuffer(this.value, context); + this.arrayBuffer = this.definition.type.toArrayBuffer(this.value); } new Uint8Array(dataView.buffer) diff --git a/libraries/struct/src/types/number.spec.ts b/libraries/struct/src/types/number.spec.ts index 46dcff98..b0eaa571 100644 --- a/libraries/struct/src/types/number.spec.ts +++ b/libraries/struct/src/types/number.spec.ts @@ -1,4 +1,4 @@ -import { StructDefaultOptions, StructDeserializationContext, StructSerializationContext, StructValue } from '../basic'; +import { StructDefaultOptions, StructDeserializeStream, StructValue } from '../basic'; import { NumberFieldDefinition, NumberFieldType } from './number'; describe('Types', () => { @@ -78,17 +78,13 @@ describe('Types', () => { describe('#deserialize', () => { it('should deserialize Uint8', async () => { const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer); - const context: StructDeserializationContext = { - read, - decodeUtf8(buffer) { throw new Error(''); }, - encodeUtf8(input) { throw new Error(''); }, - }; + const stream: StructDeserializeStream = { read }; const definition = new NumberFieldDefinition(NumberFieldType.Uint8); const struct = new StructValue(); const value = await definition.deserialize( StructDefaultOptions, - context, + stream, struct, ); @@ -99,17 +95,13 @@ describe('Types', () => { it('should deserialize Uint16', async () => { const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer); - const context: StructDeserializationContext = { - read, - decodeUtf8(buffer) { throw new Error(''); }, - encodeUtf8(input) { throw new Error(''); }, - }; + const stream: StructDeserializeStream = { read }; const definition = new NumberFieldDefinition(NumberFieldType.Uint16); const struct = new StructValue(); const value = await definition.deserialize( StructDefaultOptions, - context, + stream, struct, ); @@ -120,17 +112,13 @@ describe('Types', () => { it('should deserialize Uint16LE', async () => { const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer); - const context: StructDeserializationContext = { - read, - decodeUtf8(buffer) { throw new Error(''); }, - encodeUtf8(input) { throw new Error(''); }, - }; + const stream: StructDeserializeStream = { read }; const definition = new NumberFieldDefinition(NumberFieldType.Uint16); const struct = new StructValue(); const value = await definition.deserialize( { ...StructDefaultOptions, littleEndian: true }, - context, + stream, struct, ); @@ -144,16 +132,12 @@ describe('Types', () => { describe('NumberFieldValue', () => { describe('#getSize', () => { it('should return size of its definition', () => { - const context: StructSerializationContext = { - encodeUtf8(input) { throw new Error(''); }, - }; const struct = new StructValue(); expect( new NumberFieldDefinition(NumberFieldType.Int8) .create( StructDefaultOptions, - context, struct, 42, ) @@ -164,7 +148,6 @@ describe('Types', () => { new NumberFieldDefinition(NumberFieldType.Uint8) .create( StructDefaultOptions, - context, struct, 42, ) @@ -175,7 +158,6 @@ describe('Types', () => { new NumberFieldDefinition(NumberFieldType.Int16) .create( StructDefaultOptions, - context, struct, 42, ) @@ -186,7 +168,6 @@ describe('Types', () => { new NumberFieldDefinition(NumberFieldType.Uint16) .create( StructDefaultOptions, - context, struct, 42, ) @@ -197,7 +178,6 @@ describe('Types', () => { new NumberFieldDefinition(NumberFieldType.Int32) .create( StructDefaultOptions, - context, struct, 42, ) @@ -208,7 +188,6 @@ describe('Types', () => { new NumberFieldDefinition(NumberFieldType.Uint32) .create( StructDefaultOptions, - context, struct, 42, ) @@ -219,7 +198,6 @@ describe('Types', () => { new NumberFieldDefinition(NumberFieldType.Int64) .create( StructDefaultOptions, - context, struct, BigInt(100), ) @@ -230,7 +208,6 @@ describe('Types', () => { new NumberFieldDefinition(NumberFieldType.Uint64) .create( StructDefaultOptions, - context, struct, BigInt(100), ) @@ -241,14 +218,10 @@ describe('Types', () => { describe('#serialize', () => { it('should serialize uint8', () => { - const context: StructSerializationContext = { - encodeUtf8(input) { throw new Error(''); }, - }; const definition = new NumberFieldDefinition(NumberFieldType.Int8); const struct = new StructValue(); const value = definition.create( StructDefaultOptions, - context, struct, 42, ); diff --git a/libraries/struct/src/types/number.ts b/libraries/struct/src/types/number.ts index aa29aec4..ff1f9d70 100644 --- a/libraries/struct/src/types/number.ts +++ b/libraries/struct/src/types/number.ts @@ -1,4 +1,6 @@ -import { StructDeserializationContext, StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic'; +import { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../basic'; +import { Syncbird } from "../syncbird"; +import { ValueOrPromise } from "../utils"; export type DataViewGetters = { [TKey in keyof DataView]: TKey extends `get${string}` ? TKey : never }[keyof DataView]; @@ -62,25 +64,37 @@ export class NumberFieldDefinition< public create( options: Readonly, - context: StructSerializationContext, struct: StructValue, value: TTypeScriptType, ): NumberFieldValue { - return new NumberFieldValue(this, options, context, struct, value); + return new NumberFieldValue(this, options, struct, value); } - public async deserialize( + public override deserialize( options: Readonly, - context: StructDeserializationContext, + stream: StructDeserializeStream, struct: StructValue, - ): Promise> { - const buffer = await context.read(this.getSize()); - const view = new DataView(buffer); - const value = view[this.type.dataViewGetter]( - 0, - options.littleEndian - ) as any; - return this.create(options, context, struct, value); + ): NumberFieldValue; + public override deserialize( + options: Readonly, + stream: StructAsyncDeserializeStream, + struct: StructValue, + ): Promise>; + public override deserialize( + options: Readonly, + stream: StructDeserializeStream | StructAsyncDeserializeStream, + struct: StructValue, + ): ValueOrPromise> { + return Syncbird.try(() => { + return stream.read(this.getSize()); + }).then(buffer => { + const view = new DataView(buffer); + const value = view[this.type.dataViewGetter]( + 0, + options.littleEndian + ) as any; + return this.create(options, struct, value); + }).valueOrPromise(); } } diff --git a/libraries/struct/src/types/variable-length-array-buffer.spec.ts b/libraries/struct/src/types/variable-length-array-buffer.spec.ts index 038b3807..56db031b 100644 --- a/libraries/struct/src/types/variable-length-array-buffer.spec.ts +++ b/libraries/struct/src/types/variable-length-array-buffer.spec.ts @@ -1,20 +1,10 @@ -import { StructDefaultOptions, StructDeserializationContext, StructFieldValue, StructSerializationContext, StructValue } from '../basic'; +import { StructDefaultOptions, StructFieldValue, StructValue } from '../basic'; import { ArrayBufferFieldType, ArrayBufferLikeFieldType } from './array-buffer'; import { VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldLengthValue, VariableLengthArrayBufferLikeStructFieldValue } from './variable-length-array-buffer'; -class MockDeserializationContext implements StructDeserializationContext { - public buffer = new ArrayBuffer(0); - - public read = jest.fn((length: number) => this.buffer); - - public encodeUtf8 = jest.fn((input: string) => Buffer.from(input, 'utf-8')); - - public decodeUtf8 = jest.fn((buffer: ArrayBuffer) => Buffer.from(buffer).toString('utf-8')); -} - class MockOriginalFieldValue extends StructFieldValue { public constructor() { - super({} as any, {} as any, {} as any, {} as any, {}); + super({} as any, {} as any, {} as any, {}); } public value: string | number = 0; @@ -27,21 +17,21 @@ class MockOriginalFieldValue extends StructFieldValue { public set = jest.fn((value: string | number) => { }); - public serialize = jest.fn((dataView: DataView, offset: number, context: StructSerializationContext): void => { }); + public serialize = jest.fn((dataView: DataView, offset: number): void => { }); } describe('Types', () => { describe('VariableLengthArrayBufferLikeFieldLengthValue', () => { class MockArrayBufferFieldValue extends StructFieldValue { public constructor() { - super({ options: {} } as any, {} as any, {} as any, {} as any, {}); + super({ options: {} } as any, {} as any, {} as any, {}); } public size = 0; public getSize = jest.fn(() => this.size); - public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void { + public serialize(dataView: DataView, offset: number): void { throw new Error('Method not implemented.'); } } @@ -141,26 +131,25 @@ describe('Types', () => { let dataView = 0 as any; let offset = 1 as any; - let context = 2 as any; mockOriginalFieldValue.value = 10; mockArrayBufferFieldValue.size = 0; - lengthFieldValue.serialize(dataView, offset, context); + lengthFieldValue.serialize(dataView, offset); expect(mockOriginalFieldValue.get).toBeCalledTimes(1); expect(mockOriginalFieldValue.get).toReturnWith(10); expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledWith(0); expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); - expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset, context); + expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset); mockOriginalFieldValue.set.mockClear(); mockOriginalFieldValue.serialize.mockClear(); mockArrayBufferFieldValue.size = 100; - lengthFieldValue.serialize(dataView, offset, context); + lengthFieldValue.serialize(dataView, offset); expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledWith(100); expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); - expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset, context); + expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset); }); it('should stringify its length if `originalField` is a string', async () => { @@ -173,26 +162,25 @@ describe('Types', () => { let dataView = 0 as any; let offset = 1 as any; - let context = 2 as any; mockOriginalFieldValue.value = '10'; mockArrayBufferFieldValue.size = 0; - lengthFieldValue.serialize(dataView, offset, context); + lengthFieldValue.serialize(dataView, offset); expect(mockOriginalFieldValue.get).toBeCalledTimes(1); expect(mockOriginalFieldValue.get).toReturnWith('10'); expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledWith('0'); expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); - expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset, context); + expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset); mockOriginalFieldValue.set.mockClear(); mockOriginalFieldValue.serialize.mockClear(); mockArrayBufferFieldValue.size = 100; - lengthFieldValue.serialize(dataView, offset, context); + lengthFieldValue.serialize(dataView, offset); expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledWith('100'); expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); - expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset, context); + expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset); }); it('should stringify its length in specified base if `originalField` is a string', async () => { @@ -208,26 +196,25 @@ describe('Types', () => { let dataView = 0 as any; let offset = 1 as any; - let context = 2 as any; mockOriginalFieldValue.value = '10'; mockArrayBufferFieldValue.size = 0; - lengthFieldValue.serialize(dataView, offset, context); + lengthFieldValue.serialize(dataView, offset); expect(mockOriginalFieldValue.get).toBeCalledTimes(1); expect(mockOriginalFieldValue.get).toReturnWith('10'); expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledWith('0'); expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); - expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset, context); + expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset); mockOriginalFieldValue.set.mockClear(); mockOriginalFieldValue.serialize.mockClear(); mockArrayBufferFieldValue.size = 100; - lengthFieldValue.serialize(dataView, offset, context); + lengthFieldValue.serialize(dataView, offset); expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledWith((100).toString(base)); expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); - expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset, context); + expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset); }); }); }); @@ -246,20 +233,17 @@ describe('Types', () => { { lengthField }, ); - const context = new MockDeserializationContext(); const value = new ArrayBuffer(0); const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( arrayBufferFieldDefinition, StructDefaultOptions, - context, struct, value, ); expect(arrayBufferFieldValue).toHaveProperty('definition', arrayBufferFieldDefinition); expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions); - expect(arrayBufferFieldValue).toHaveProperty('context', context); expect(arrayBufferFieldValue).toHaveProperty('struct', struct); expect(arrayBufferFieldValue).toHaveProperty('value', value); expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined); @@ -278,13 +262,11 @@ describe('Types', () => { { lengthField }, ); - const context = new MockDeserializationContext(); const value = new ArrayBuffer(100); const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( arrayBufferFieldDefinition, StructDefaultOptions, - context, struct, value, value, @@ -292,7 +274,6 @@ describe('Types', () => { expect(arrayBufferFieldValue).toHaveProperty('definition', arrayBufferFieldDefinition); expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions); - expect(arrayBufferFieldValue).toHaveProperty('context', context); expect(arrayBufferFieldValue).toHaveProperty('struct', struct); expect(arrayBufferFieldValue).toHaveProperty('value', value); expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value); @@ -311,13 +292,11 @@ describe('Types', () => { { lengthField }, ); - const context = new MockDeserializationContext(); const value = new ArrayBuffer(0); const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( arrayBufferFieldDefinition, StructDefaultOptions, - context, struct, value, ); @@ -329,11 +308,11 @@ describe('Types', () => { describe('#getSize', () => { class MockArrayBufferFieldType extends ArrayBufferLikeFieldType { - public toArrayBuffer = jest.fn((value: ArrayBuffer, context: StructSerializationContext): ArrayBuffer => { + public toArrayBuffer = jest.fn((value: ArrayBuffer): ArrayBuffer => { return value; }); - public fromArrayBuffer = jest.fn((arrayBuffer: ArrayBuffer, context: StructDeserializationContext): ArrayBuffer => { + public fromArrayBuffer = jest.fn((arrayBuffer: ArrayBuffer): ArrayBuffer => { return arrayBuffer; }); @@ -357,13 +336,11 @@ describe('Types', () => { { lengthField }, ); - const context = new MockDeserializationContext(); const value = new ArrayBuffer(100); const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( arrayBufferFieldDefinition, StructDefaultOptions, - context, struct, value, value, @@ -388,13 +365,11 @@ describe('Types', () => { { lengthField }, ); - const context = new MockDeserializationContext(); const value = new ArrayBuffer(100); const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( arrayBufferFieldDefinition, StructDefaultOptions, - context, struct, value, ); @@ -421,13 +396,11 @@ describe('Types', () => { { lengthField }, ); - const context = new MockDeserializationContext(); const value = new ArrayBuffer(100); const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( arrayBufferFieldDefinition, StructDefaultOptions, - context, struct, value, ); @@ -455,13 +428,11 @@ describe('Types', () => { { lengthField }, ); - const context = new MockDeserializationContext(); const value = new ArrayBuffer(100); const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( arrayBufferFieldDefinition, StructDefaultOptions, - context, struct, value, value, @@ -485,13 +456,11 @@ describe('Types', () => { { lengthField }, ); - const context = new MockDeserializationContext(); const value = new ArrayBuffer(100); const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( arrayBufferFieldDefinition, StructDefaultOptions, - context, struct, value, value, @@ -597,18 +566,15 @@ describe('Types', () => { { lengthField }, ); - const context = new MockDeserializationContext(); const value = new ArrayBuffer(100); const arrayBufferFieldValue = definition.create( StructDefaultOptions, - context, struct, value, ); expect(arrayBufferFieldValue).toHaveProperty('definition', definition); expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions); - expect(arrayBufferFieldValue).toHaveProperty('context', context); expect(arrayBufferFieldValue).toHaveProperty('struct', struct); expect(arrayBufferFieldValue).toHaveProperty('value', value); expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined); @@ -627,11 +593,9 @@ describe('Types', () => { { lengthField }, ); - const context = new MockDeserializationContext(); const value = new ArrayBuffer(100); const arrayBufferFieldValue = definition.create( StructDefaultOptions, - context, struct, value, value, @@ -639,7 +603,6 @@ describe('Types', () => { expect(arrayBufferFieldValue).toHaveProperty('definition', definition); expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions); - expect(arrayBufferFieldValue).toHaveProperty('context', context); expect(arrayBufferFieldValue).toHaveProperty('struct', struct); expect(arrayBufferFieldValue).toHaveProperty('value', value); expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value); diff --git a/libraries/struct/src/types/variable-length-array-buffer.ts b/libraries/struct/src/types/variable-length-array-buffer.ts index 748a5ea3..5c1afef0 100644 --- a/libraries/struct/src/types/variable-length-array-buffer.ts +++ b/libraries/struct/src/types/variable-length-array-buffer.ts @@ -1,4 +1,4 @@ -import { StructFieldDefinition, StructFieldValue, StructOptions, StructSerializationContext, StructValue } from '../basic'; +import { StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../basic'; import type { KeysOfType } from '../utils'; import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue } from './array-buffer'; @@ -35,7 +35,6 @@ export class VariableLengthArrayBufferLikeFieldDefinition< public create( options: Readonly, - context: StructSerializationContext, struct: StructValue, value: TType['TTypeScriptType'], arrayBuffer?: ArrayBuffer @@ -43,7 +42,6 @@ export class VariableLengthArrayBufferLikeFieldDefinition< return new VariableLengthArrayBufferLikeStructFieldValue( this, options, - context, struct, value, arrayBuffer, @@ -61,12 +59,11 @@ export class VariableLengthArrayBufferLikeStructFieldValue< public constructor( definition: TDefinition, options: Readonly, - context: StructSerializationContext, struct: StructValue, value: TDefinition['TValue'], arrayBuffer?: ArrayBuffer, ) { - super(definition, options, context, struct, value, arrayBuffer); + super(definition, options, struct, value, arrayBuffer); if (arrayBuffer) { this.length = arrayBuffer.byteLength; @@ -87,7 +84,7 @@ export class VariableLengthArrayBufferLikeStructFieldValue< if (this.length === undefined) { this.length = this.definition.type.getSize(this.value); if (this.length === -1) { - this.arrayBuffer = this.definition.type.toArrayBuffer(this.value, this.context); + this.arrayBuffer = this.definition.type.toArrayBuffer(this.value); this.length = this.arrayBuffer.byteLength; } } @@ -116,7 +113,7 @@ export class VariableLengthArrayBufferLikeFieldLengthValue originalField: StructFieldValue, arrayBufferField: VariableLengthArrayBufferLikeFieldValueLike, ) { - super(originalField.definition, originalField.options, originalField.context, originalField.struct, 0); + super(originalField.definition, originalField.options, originalField.struct, 0); this.originalField = originalField; this.arrayBufferField = arrayBufferField; } @@ -138,8 +135,8 @@ export class VariableLengthArrayBufferLikeFieldLengthValue set() { } - serialize(dataView: DataView, offset: number, context: StructSerializationContext) { + serialize(dataView: DataView, offset: number) { this.originalField.set(this.get()); - this.originalField.serialize(dataView, offset, context); + this.originalField.serialize(dataView, offset); } } diff --git a/libraries/struct/src/utils.ts b/libraries/struct/src/utils.ts index 93f3e3b9..e60b817a 100644 --- a/libraries/struct/src/utils.ts +++ b/libraries/struct/src/utils.ts @@ -49,3 +49,16 @@ export type Awaited = T extends Promise ? Awaited : T; export function placeholder(): T { return undefined as unknown as T; } + +// @ts-expect-error @types/node missing `TextEncoder` +const Utf8Encoder = new TextEncoder(); +// @ts-expect-error @types/node missing `TextDecoder` +const Utf8Decoder = new TextDecoder(); + +export function encodeUtf8(input: string): ArrayBuffer { + return Utf8Encoder.encode(input).buffer; +} + +export function decodeUtf8(buffer: ArrayBuffer): string { + return Utf8Decoder.decode(buffer); +} diff --git a/libraries/struct/tsconfig.json b/libraries/struct/tsconfig.json index 61f72d2c..b00d3d6c 100644 --- a/libraries/struct/tsconfig.json +++ b/libraries/struct/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "types": [ "node", - "jest" - ] + "jest", + ], }, "testTypes": [ "node", diff --git a/toolchain/ts-package-builder/package.json b/toolchain/ts-package-builder/package.json index 3f4339af..8b0cd9d6 100644 --- a/toolchain/ts-package-builder/package.json +++ b/toolchain/ts-package-builder/package.json @@ -13,11 +13,11 @@ "author": "", "license": "MIT", "devDependencies": { - "@types/node": "^16.9.1" + "@types/node": "^16.11.19" }, "dependencies": { "json5": "^2.2.0", - "@types/jest": "^26.0.23" + "@types/jest": "^27.4.0" }, "peerDependencies": { "typescript": "^4.0.0"