refactor(struct): remove context interfaces

This commit is contained in:
Simon Chan 2022-01-08 18:51:14 +08:00
parent 949aaab818
commit 08767c7b71
20 changed files with 429 additions and 333 deletions

View file

@ -23,12 +23,14 @@ specifiers:
'@rush-temp/ts-package-builder': file:./projects/ts-package-builder.tgz '@rush-temp/ts-package-builder': file:./projects/ts-package-builder.tgz
'@rush-temp/unofficial-adb-book': file:./projects/unofficial-adb-book.tgz '@rush-temp/unofficial-adb-book': file:./projects/unofficial-adb-book.tgz
'@svgr/webpack': ^5.5.0 '@svgr/webpack': ^5.5.0
'@types/bluebird': ^3.5.36
'@types/dom-webcodecs': ^0.1.3 '@types/dom-webcodecs': ^0.1.3
'@types/jest': ^26.0.23 '@types/jest': ^27.4.0
'@types/node': ^16.9.1 '@types/node': ^16.11.19
'@types/react': 17.0.27 '@types/react': 17.0.27
'@types/w3c-web-usb': ^1.0.4 '@types/w3c-web-usb': ^1.0.4
'@yume-chan/async': ^2.1.4 '@yume-chan/async': ^2.1.4
bluebird: ^3.7.2
clsx: ^1.1.1 clsx: ^1.1.1
eslint: 7.32.0 eslint: 7.32.0
eslint-config-next: ^12.0.7 eslint-config-next: ^12.0.7
@ -78,12 +80,14 @@ dependencies:
'@rush-temp/ts-package-builder': file:projects/ts-package-builder.tgz '@rush-temp/ts-package-builder': file:projects/ts-package-builder.tgz
'@rush-temp/unofficial-adb-book': file:projects/unofficial-adb-book.tgz_fe5950f86df08edf541b29db75ee5ce3 '@rush-temp/unofficial-adb-book': file:projects/unofficial-adb-book.tgz_fe5950f86df08edf541b29db75ee5ce3
'@svgr/webpack': 5.5.0 '@svgr/webpack': 5.5.0
'@types/bluebird': 3.5.36
'@types/dom-webcodecs': 0.1.3 '@types/dom-webcodecs': 0.1.3
'@types/jest': 26.0.24 '@types/jest': 27.4.0
'@types/node': 16.11.17 '@types/node': 16.11.19
'@types/react': 17.0.27 '@types/react': 17.0.27
'@types/w3c-web-usb': 1.0.5 '@types/w3c-web-usb': 1.0.5
'@yume-chan/async': 2.1.4 '@yume-chan/async': 2.1.4
bluebird: 3.7.2
clsx: 1.1.1 clsx: 1.1.1
eslint: 7.32.0 eslint: 7.32.0
eslint-config-next: 12.0.7_eslint@7.32.0+next@12.0.7 eslint-config-next: 12.0.7_eslint@7.32.0+next@12.0.7
@ -3480,6 +3484,10 @@ packages:
'@babel/types': 7.16.0 '@babel/types': 7.16.0
dev: false dev: false
/@types/bluebird/3.5.36:
resolution: {integrity: sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==}
dev: false
/@types/body-parser/1.19.2: /@types/body-parser/1.19.2:
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
dependencies: dependencies:
@ -3603,11 +3611,11 @@ packages:
'@types/istanbul-lib-report': 3.0.0 '@types/istanbul-lib-report': 3.0.0
dev: false dev: false
/@types/jest/26.0.24: /@types/jest/27.4.0:
resolution: {integrity: sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==} resolution: {integrity: sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==}
dependencies: dependencies:
jest-diff: 26.6.2 jest-diff: 27.4.6
pretty-format: 26.6.2 pretty-format: 27.4.6
dev: false dev: false
/@types/json-schema/7.0.9: /@types/json-schema/7.0.9:
@ -3643,6 +3651,10 @@ packages:
resolution: {integrity: sha512-C1vTZME8cFo8uxY2ui41xcynEotVkczIVI5AjLmy5pkpBv/FtG+jhtOlfcPysI8VRVwoOMv6NJm44LGnoMSWkw==} resolution: {integrity: sha512-C1vTZME8cFo8uxY2ui41xcynEotVkczIVI5AjLmy5pkpBv/FtG+jhtOlfcPysI8VRVwoOMv6NJm44LGnoMSWkw==}
dev: false dev: false
/@types/node/16.11.19:
resolution: {integrity: sha512-BPAcfDPoHlRQNKktbsbnpACGdypPFBuX4xQlsWDE7B8XXcfII+SpOLay3/qZmCLb39kV5S1RTYwXdkx2lwLYng==}
dev: false
/@types/normalize-package-data/2.4.1: /@types/normalize-package-data/2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
dev: false dev: false
@ -4204,6 +4216,11 @@ packages:
color-convert: 2.0.1 color-convert: 2.0.1
dev: false dev: false
/ansi-styles/5.2.0:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
dev: false
/anymatch/2.0.0: /anymatch/2.0.0:
resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==} resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==}
dependencies: dependencies:
@ -5981,6 +5998,11 @@ packages:
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
dev: false 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: /diffie-hellman/5.0.3:
resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==}
dependencies: dependencies:
@ -8556,6 +8578,16 @@ packages:
pretty-format: 26.6.2 pretty-format: 26.6.2
dev: false 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: /jest-docblock/26.0.0:
resolution: {integrity: sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==} resolution: {integrity: sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==}
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
@ -8609,6 +8641,11 @@ packages:
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
dev: false 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: /jest-haste-map/26.6.2:
resolution: {integrity: sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==} resolution: {integrity: sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==}
engines: {node: '>= 10.14.2'} engines: {node: '>= 10.14.2'}
@ -10909,6 +10946,15 @@ packages:
react-is: 17.0.2 react-is: 17.0.2
dev: false 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: /pretty-time/1.1.0:
resolution: {integrity: sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==} resolution: {integrity: sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -13991,11 +14037,12 @@ packages:
dev: false dev: false
file:projects/scrcpy.tgz: 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' name: '@rush-temp/scrcpy'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
'@types/dom-webcodecs': 0.1.3 '@types/dom-webcodecs': 0.1.3
'@types/jest': 27.4.0
'@yume-chan/async': 2.1.4 '@yume-chan/async': 2.1.4
gh-release-fetch: 2.0.4 gh-release-fetch: 2.0.4
jest: 26.6.3 jest: 26.6.3
@ -14013,11 +14060,14 @@ packages:
dev: false dev: false
file:projects/struct.tgz: 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' name: '@rush-temp/struct'
version: 0.0.0 version: 0.0.0
dependencies: 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 jest: 26.6.3
tslib: 2.3.1 tslib: 2.3.1
typescript: 4.5.4 typescript: 4.5.4
@ -14030,12 +14080,12 @@ packages:
dev: false dev: false
file:projects/ts-package-builder.tgz: 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' name: '@rush-temp/ts-package-builder'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
'@types/jest': 26.0.24 '@types/jest': 27.4.0
'@types/node': 16.11.17 '@types/node': 16.11.19
json5: 2.2.0 json5: 2.2.0
typescript: 4.5.4 typescript: 4.5.4
dev: false dev: false

View file

@ -8,6 +8,8 @@
A C-style structure serializer and deserializer. Written in TypeScript and highly takes advantage of its type system. 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 ## Installation
```sh ```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); const value = await struct.deserialize(stream);
value.foo; // number value.foo; // number
struct.serialize({ }, context) // error: 'foo' is required struct.serialize({ }) // error: 'foo' is required
struct.serialize({ foo: 'bar' }, context) // error: 'foo' must be a number struct.serialize({ foo: 'bar' }) // error: 'foo' must be a number
struct.serialize({ foo: 42 }, context) // ok struct.serialize({ foo: 42 }) // ok
``` ```
2. Set fields' type (can be used with [`placeholder` method](#placeholder)) 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.foo; // MyEnum
value.bar; // MyEnum.a value.bar; // MyEnum.a
struct.serialize({ foo: 42, bar: MyEnum.a }, context); // error: 'foo' must be of type `MyEnum` struct.serialize({ foo: 42, bar: MyEnum.a }); // 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 }); // error: 'bar' must be of type `MyEnum.a`
struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }, context); // ok struct.serialize({ foo: MyEnum.a, bar: MyEnum.b }); // ok
``` ```
#### `int64`/`uint64` #### `int64`/`uint64`
@ -334,7 +336,7 @@ Merges (flats) another `Struct`'s fields and extra fields into the current one.
.fields(MyStructV1) .fields(MyStructV1)
.int32('field2'); .int32('field2');
const structV2 = await MyStructV2.deserialize(context); const structV2 = await MyStructV2.deserialize(stream);
structV2.field1; // number structV2.field1; // number
structV2.field2; // number structV2.field2; // number
// Fields are flatten // Fields are flatten
@ -352,7 +354,7 @@ Merges (flats) another `Struct`'s fields and extra fields into the current one.
.int32('field2') .int32('field2')
.fields(MyStructV1); .fields(MyStructV1);
const structV2 = await MyStructV2.deserialize(context); const structV2 = await MyStructV2.deserialize(stream);
structV2.field1; // number structV2.field1; // number
structV2.field2; // number structV2.field2; // number
// Same result as above, but serialize/deserialize order is reversed // 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.foo; // number
value.bar; // 'hello' value.bar; // 'hello'
struct.serialize({ foo: 42 }, context); // ok struct.serialize({ foo: 42 }); // ok
struct.serialize({ foo: 42, bar: 'hello' }, context); // error: 'bar' is redundant struct.serialize({ foo: 42, bar: 'hello' }); // error: 'bar' is redundant
``` ```
2. Add getters and methods. `this` in functions refers to the result object. 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` #### `deserialize`
```ts ```ts
interface StructDeserializationContext { interface StructDeserializeStream {
decodeUtf8(buffer: ArrayBuffer): string; /**
* Read data from the underlying data source.
read(length: number): ArrayBuffer | Promise<ArrayBuffer>; *
* 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( deserialize(
context: StructDeserializationContext stream: StructDeserializeStream,
): TPostDeserialized extends undefined
? Overwrite<TExtra, TValue>
: TPostDeserialized
>;
deserialize(
stream: StructAsyncDeserializeStream,
): Promise< ): Promise<
TPostDeserialized extends undefined TPostDeserialized extends undefined
? Overwrite<TExtra, TValue> ? Overwrite<TExtra, TValue>
: TPostDeserialized : 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. 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` #### `serialize`
```ts ```ts
interface StructSerializationContext {
encodeUtf8(input: string): ArrayBuffer;
}
serialize( serialize(
init: Omit<TFields, TOmitInitKey>, init: Omit<TFields, TOmitInitKey>
context: StructSerializationContext
): ArrayBuffer; ): ArrayBuffer;
``` ```
@ -634,7 +642,6 @@ Actual size should be returned from `StructFieldValue#getSize`
```ts ```ts
abstract create( abstract create(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext,
struct: StructValue, struct: StructValue,
value: TValue, value: TValue,
): StructFieldValue<this>; ): StructFieldValue<this>;
@ -649,16 +656,21 @@ Derived classes must implement this method to create its own field value instanc
```ts ```ts
abstract deserialize( abstract deserialize(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructDeserializationContext, stream: StructDeserializeStream,
struct: StructValue, struct: StructValue,
): ValueOrPromise<StructFieldValue<this>>; ): StructFieldValue<this>;
abstract deserialize(
options: Readonly<StructOptions>,
stream: StructAsyncDeserializeStream,
struct: StructValue,
): Promise<StructFieldValue<this>>;
``` ```
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: 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 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. 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 ```ts
abstract serialize( abstract serialize(
dataView: DataView, dataView: DataView,
offset: number, offset: number
context: StructSerializationContext
): void; ): void;
``` ```

View file

@ -33,12 +33,15 @@
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"dependencies": { "dependencies": {
"bluebird": "^3.7.2",
"tslib": "^2.3.1" "tslib": "^2.3.1"
}, },
"devDependencies": { "devDependencies": {
"jest": "^26.6.3", "jest": "^26.6.3",
"typescript": "^4.4.3", "typescript": "^4.4.3",
"@yume-chan/ts-package-builder": "^1.0.0", "@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"
} }
} }

View file

@ -1,30 +1,14 @@
import type { ValueOrPromise } from '../utils'; export interface StructDeserializeStream {
/**
* Context with enough methods to serialize a struct
*/
export interface StructSerializationContext {
/** /**
* 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; read(length: number): ArrayBuffer;
} }
/** export interface StructAsyncDeserializeStream {
* 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;
/** /**
* Read data from the underlying data source. * Read data from the underlying data source.
* *

View file

@ -1,5 +1,5 @@
import type { ValueOrPromise } from '../utils'; import type { ValueOrPromise } from '../utils';
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context'; import type { StructAsyncDeserializeStream, StructDeserializeStream, StructOptions } from './context';
import { StructFieldDefinition } from './definition'; import { StructFieldDefinition } from './definition';
import type { StructFieldValue } from './field-value'; import type { StructFieldValue } from './field-value';
import type { StructValue } from './struct-value'; import type { StructValue } from './struct-value';
@ -14,10 +14,24 @@ describe('StructFieldDefinition', () => {
public getSize(): number { public getSize(): number {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
public create(options: Readonly<StructOptions>, context: StructSerializationContext, struct: StructValue, value: unknown): StructFieldValue<this> { public create(options: Readonly<StructOptions>, struct: StructValue, value: unknown): StructFieldValue<this> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, struct: StructValue): ValueOrPromise<StructFieldValue<this>> { public override deserialize(
options: Readonly<StructOptions>,
stream: StructDeserializeStream,
struct: StructValue,
): StructFieldValue<this>;
public override deserialize(
options: Readonly<StructOptions>,
stream: StructAsyncDeserializeStream,
struct: StructValue,
): Promise<StructFieldValue<this>>;
public deserialize(
options: Readonly<StructOptions>,
stream: StructDeserializeStream | StructAsyncDeserializeStream,
struct: StructValue
): ValueOrPromise<StructFieldValue<this>> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
} }

View file

@ -1,5 +1,4 @@
import type { ValueOrPromise } from '../utils'; import type { StructAsyncDeserializeStream, StructDeserializeStream, StructOptions } from './context';
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
import type { StructFieldValue } from './field-value'; import type { StructFieldValue } from './field-value';
import type { StructValue } from './struct-value'; import type { StructValue } from './struct-value';
@ -53,7 +52,6 @@ export abstract class StructFieldDefinition<
*/ */
public abstract create( public abstract create(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext,
struct: StructValue, struct: StructValue,
value: TValue, value: TValue,
): StructFieldValue<this>; ): StructFieldValue<this>;
@ -63,7 +61,12 @@ export abstract class StructFieldDefinition<
*/ */
public abstract deserialize( public abstract deserialize(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructDeserializationContext, stream: StructDeserializeStream,
struct: StructValue, struct: StructValue,
): ValueOrPromise<StructFieldValue<this>>; ): StructFieldValue<this>;
public abstract deserialize(
options: Readonly<StructOptions>,
stream: StructAsyncDeserializeStream,
struct: StructValue,
): Promise<StructFieldValue<this>>;
} }

View file

@ -1,5 +1,5 @@
import type { ValueOrPromise } from '../utils'; import type { ValueOrPromise } from '../utils';
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context'; import type { StructAsyncDeserializeStream, StructDeserializeStream, StructOptions } from './context';
import { StructFieldDefinition } from './definition'; import { StructFieldDefinition } from './definition';
import { StructFieldValue } from './field-value'; import { StructFieldValue } from './field-value';
import type { StructValue } from './struct-value'; import type { StructValue } from './struct-value';
@ -8,21 +8,19 @@ describe('StructFieldValue', () => {
describe('.constructor', () => { describe('.constructor', () => {
it('should save parameters', () => { it('should save parameters', () => {
class MockStructFieldValue extends StructFieldValue { 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.'); throw new Error('Method not implemented.');
} }
} }
const definition = 1 as any; const definition = {} as any;
const options = 2 as any; const options = {} as any;
const context = 3 as any; const struct = {} as any;
const struct = 4 as any; const value = {} as any;
const value = 5 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('definition', definition);
expect(fieldValue).toHaveProperty('options', options); expect(fieldValue).toHaveProperty('options', options);
expect(fieldValue).toHaveProperty('context', context);
expect(fieldValue).toHaveProperty('struct', struct); expect(fieldValue).toHaveProperty('struct', struct);
expect(fieldValue.get()).toBe(value); expect(fieldValue.get()).toBe(value);
}); });
@ -34,22 +32,37 @@ describe('StructFieldValue', () => {
public getSize(): number { public getSize(): number {
return 42; return 42;
} }
public create(options: Readonly<StructOptions>, context: StructSerializationContext, struct: StructValue, value: unknown): StructFieldValue<this> { public create(options: Readonly<StructOptions>, struct: StructValue, value: unknown): StructFieldValue<this> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, struct: StructValue): ValueOrPromise<StructFieldValue<this>> {
public override deserialize(
options: Readonly<StructOptions>,
stream: StructDeserializeStream,
struct: StructValue,
): StructFieldValue<this>;
public override deserialize(
options: Readonly<StructOptions>,
stream: StructAsyncDeserializeStream,
struct: StructValue,
): Promise<StructFieldValue<this>>;
public override deserialize(
options: Readonly<StructOptions>,
stream: StructDeserializeStream | StructAsyncDeserializeStream,
struct: StructValue,
): ValueOrPromise<StructFieldValue<this>> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
} }
class MockStructFieldValue extends StructFieldValue { 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.'); throw new Error('Method not implemented.');
} }
} }
const fieldDefinition = new MockFieldDefinition(); 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); expect(fieldValue.getSize()).toBe(42);
}); });
}); });
@ -57,12 +70,12 @@ describe('StructFieldValue', () => {
describe('#set', () => { describe('#set', () => {
it('should update its internal value', () => { it('should update its internal value', () => {
class MockStructFieldValue extends StructFieldValue { 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.'); 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); fieldValue.set(1);
expect(fieldValue.get()).toBe(1); expect(fieldValue.get()).toBe(1);

View file

@ -1,4 +1,4 @@
import type { StructOptions, StructSerializationContext } from './context'; import type { StructOptions } from './context';
import type { StructFieldDefinition } from './definition'; import type { StructFieldDefinition } from './definition';
import type { StructValue } from './struct-value'; import type { StructValue } from './struct-value';
@ -17,9 +17,6 @@ export abstract class StructFieldValue<
/** Gets the options of the associated `Struct` */ /** Gets the options of the associated `Struct` */
public readonly options: Readonly<StructOptions>; public readonly options: Readonly<StructOptions>;
/** Gets the serialization context of the associated `Struct` instance */
public readonly context: StructSerializationContext;
/** Gets the associated `Struct` instance */ /** Gets the associated `Struct` instance */
public readonly struct: StructValue; public readonly struct: StructValue;
@ -28,13 +25,11 @@ export abstract class StructFieldValue<
public constructor( public constructor(
definition: TDefinition, definition: TDefinition,
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext,
struct: StructValue, struct: StructValue,
value: TDefinition['TValue'], value: TDefinition['TValue'],
) { ) {
this.definition = definition; this.definition = definition;
this.options = options; this.options = options;
this.context = context;
this.struct = struct; this.struct = struct;
this.value = value; this.value = value;
} }
@ -68,6 +63,5 @@ export abstract class StructFieldValue<
public abstract serialize( public abstract serialize(
dataView: DataView, dataView: DataView,
offset: number, offset: number,
context: StructSerializationContext
): void; ): void;
} }

View file

@ -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 { Struct } from './struct';
import { ArrayBufferFieldType, FixedLengthArrayBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition } from './types'; import { ArrayBufferFieldType, FixedLengthArrayBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition } from './types';
import { ValueOrPromise } from './utils'; import { ValueOrPromise } from './utils';
class MockDeserializationContext implements StructDeserializationContext { class MockDeserializationStream implements StructDeserializeStream {
public buffer = new ArrayBuffer(0); public buffer = new ArrayBuffer(0);
public read = jest.fn((length: number) => this.buffer); 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', () => { describe('Struct', () => {
@ -32,10 +28,24 @@ describe('Struct', () => {
return this.options; return this.options;
}); });
public create(options: Readonly<StructOptions>, context: StructSerializationContext, struct: StructValue, value: unknown): StructFieldValue<this> { public create(options: Readonly<StructOptions>, struct: StructValue, value: unknown): StructFieldValue<this> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
public deserialize(options: Readonly<StructOptions>, context: StructDeserializationContext, struct: StructValue): ValueOrPromise<StructFieldValue<this>> { public override deserialize(
options: Readonly<StructOptions>,
stream: StructDeserializeStream,
struct: StructValue,
): StructFieldValue<this>;
public override deserialize(
options: Readonly<StructOptions>,
stream: StructAsyncDeserializeStream,
struct: StructValue,
): Promise<StructFieldValue<this>>;
public override deserialize(
options: Readonly<StructOptions>,
stream: StructDeserializeStream | StructAsyncDeserializeStream,
struct: StructValue
): ValueOrPromise<StructFieldValue<this>> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
} }
@ -265,17 +275,17 @@ describe('Struct', () => {
.int8('foo') .int8('foo')
.int16('bar'); .int16('bar');
const context = new MockDeserializationContext(); const stream = new MockDeserializationStream();
context.read stream.read
.mockReturnValueOnce(new Uint8Array([2]).buffer) .mockReturnValueOnce(new Uint8Array([2]).buffer)
.mockReturnValueOnce(new Uint8Array([0, 16]).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(result).toEqual({ foo: 2, bar: 16 });
expect(context.read).toBeCalledTimes(2); expect(stream.read).toBeCalledTimes(2);
expect(context.read).nthCalledWith(1, 1); expect(stream.read).nthCalledWith(1, 1);
expect(context.read).nthCalledWith(2, 2); expect(stream.read).nthCalledWith(2, 2);
}); });
it('should deserialize with dynamic size fields', async () => { it('should deserialize with dynamic size fields', async () => {
@ -283,16 +293,16 @@ describe('Struct', () => {
.int8('fooLength') .int8('fooLength')
.uint8ClampedArray('foo', { lengthField: 'fooLength' }); .uint8ClampedArray('foo', { lengthField: 'fooLength' });
const context = new MockDeserializationContext(); const stream = new MockDeserializationStream();
context.read stream.read
.mockReturnValueOnce(new Uint8Array([2]).buffer) .mockReturnValueOnce(new Uint8Array([2]).buffer)
.mockReturnValueOnce(new Uint8Array([3, 4]).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(result).toEqual({ fooLength: 2, foo: new Uint8ClampedArray([3, 4]) });
expect(context.read).toBeCalledTimes(2); expect(stream.read).toBeCalledTimes(2);
expect(context.read).nthCalledWith(1, 1); expect(stream.read).nthCalledWith(1, 1);
expect(context.read).nthCalledWith(2, 2); expect(stream.read).nthCalledWith(2, 2);
}); });
}); });
@ -301,8 +311,8 @@ describe('Struct', () => {
const struct = new Struct() const struct = new Struct()
.extra({ foo: 42, bar: true }); .extra({ foo: 42, bar: true });
const context = new MockDeserializationContext(); const stream = new MockDeserializationStream();
const result = await struct.deserialize(context); const result = await struct.deserialize(stream);
expect(Object.entries(Object.getOwnPropertyDescriptors(result))).toEqual([ expect(Object.entries(Object.getOwnPropertyDescriptors(result))).toEqual([
['foo', { configurable: true, enumerable: true, writable: true, value: 42 }], ['foo', { configurable: true, enumerable: true, writable: true, value: 42 }],
@ -318,8 +328,8 @@ describe('Struct', () => {
set bar(value) { }, set bar(value) { },
}); });
const context = new MockDeserializationContext(); const stream = new MockDeserializationStream();
const result = await struct.deserialize(context); const result = await struct.deserialize(stream);
expect(Object.entries(Object.getOwnPropertyDescriptors(result))).toEqual([ expect(Object.entries(Object.getOwnPropertyDescriptors(result))).toEqual([
['foo', { configurable: true, enumerable: true, get: expect.any(Function) }], ['foo', { configurable: true, enumerable: true, get: expect.any(Function) }],
@ -334,8 +344,8 @@ describe('Struct', () => {
const callback = jest.fn(() => { throw new Error('mock'); }); const callback = jest.fn(() => { throw new Error('mock'); });
struct.postDeserialize(callback); struct.postDeserialize(callback);
const context = new MockDeserializationContext(); const stream = new MockDeserializationStream();
expect(struct.deserialize(context)).rejects.toThrowError('mock'); expect(struct.deserialize(stream)).rejects.toThrowError('mock');
expect(callback).toBeCalledTimes(1); expect(callback).toBeCalledTimes(1);
}); });
@ -344,8 +354,8 @@ describe('Struct', () => {
const callback = jest.fn(() => 'mock'); const callback = jest.fn(() => 'mock');
struct.postDeserialize(callback); struct.postDeserialize(callback);
const context = new MockDeserializationContext(); const stream = new MockDeserializationStream();
expect(struct.deserialize(context)).resolves.toBe('mock'); expect(struct.deserialize(stream)).resolves.toBe('mock');
expect(callback).toBeCalledTimes(1); expect(callback).toBeCalledTimes(1);
expect(callback).toBeCalledWith({}); expect(callback).toBeCalledWith({});
}); });
@ -355,8 +365,8 @@ describe('Struct', () => {
const callback = jest.fn(); const callback = jest.fn();
struct.postDeserialize(callback); struct.postDeserialize(callback);
const context = new MockDeserializationContext(); const stream = new MockDeserializationStream();
const result = await struct.deserialize(context); const result = await struct.deserialize(stream);
expect(callback).toBeCalledTimes(1); expect(callback).toBeCalledTimes(1);
expect(callback).toBeCalledWith(result); expect(callback).toBeCalledWith(result);
@ -371,8 +381,8 @@ describe('Struct', () => {
const callback2 = jest.fn(); const callback2 = jest.fn();
struct.postDeserialize(callback2); struct.postDeserialize(callback2);
const context = new MockDeserializationContext(); const stream = new MockDeserializationStream();
await struct.deserialize(context); await struct.deserialize(stream);
expect(callback1).toBeCalledTimes(0); expect(callback1).toBeCalledTimes(0);
expect(callback2).toBeCalledTimes(1); expect(callback2).toBeCalledTimes(1);
@ -386,8 +396,8 @@ describe('Struct', () => {
.int8('foo') .int8('foo')
.int16('bar'); .int16('bar');
const context = new MockDeserializationContext(); const stream = new MockDeserializationStream();
const result = new Uint8Array(struct.serialize({ foo: 0x42, bar: 0x1024 }, context)); const result = new Uint8Array(struct.serialize({ foo: 0x42, bar: 0x1024 }));
expect(result).toEqual(new Uint8Array([0x42, 0x10, 0x24])); expect(result).toEqual(new Uint8Array([0x42, 0x10, 0x24]));
}); });
@ -397,8 +407,8 @@ describe('Struct', () => {
.int8('fooLength') .int8('fooLength')
.arrayBuffer('foo', { lengthField: 'fooLength' }); .arrayBuffer('foo', { lengthField: 'fooLength' });
const context = new MockDeserializationContext(); const stream = new MockDeserializationStream();
const result = new Uint8Array(struct.serialize({ foo: new Uint8Array([0x03, 0x04, 0x05]).buffer }, context)); const result = new Uint8Array(struct.serialize({ foo: new Uint8Array([0x03, 0x04, 0x05]).buffer }));
expect(result).toEqual(new Uint8Array([0x03, 0x03, 0x04, 0x05])); expect(result).toEqual(new Uint8Array([0x03, 0x03, 0x04, 0x05]));
}); });

View file

@ -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 { 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<TValue> { export interface StructLike<TValue> {
deserialize(context: StructDeserializationContext): Promise<TValue>; deserialize(stream: StructDeserializeStream | StructAsyncDeserializeStream): Promise<TValue>;
} }
/** /**
@ -53,7 +55,7 @@ interface ArrayBufferLikeFieldCreator<
*/ */
< <
TName extends PropertyKey, TName extends PropertyKey,
TType extends ArrayBufferLikeFieldType, TType extends ArrayBufferLikeFieldType<any, any>,
TTypeScriptType = TType['TTypeScriptType'], TTypeScriptType = TType['TTypeScriptType'],
>( >(
name: TName, name: TName,
@ -77,7 +79,7 @@ interface ArrayBufferLikeFieldCreator<
*/ */
< <
TName extends PropertyKey, TName extends PropertyKey,
TType extends ArrayBufferLikeFieldType, TType extends ArrayBufferLikeFieldType<any, any>,
TOptions extends VariableLengthArrayBufferLikeFieldOptions<TFields>, TOptions extends VariableLengthArrayBufferLikeFieldOptions<TFields>,
TTypeScriptType = TType['TTypeScriptType'], TTypeScriptType = TType['TTypeScriptType'],
>( >(
@ -106,7 +108,7 @@ interface BindedArrayBufferLikeFieldDefinitionCreator<
TOmitInitKey extends PropertyKey, TOmitInitKey extends PropertyKey,
TExtra extends object, TExtra extends object,
TPostDeserialized, TPostDeserialized,
TType extends ArrayBufferLikeFieldType TType extends ArrayBufferLikeFieldType<any, any>
> { > {
< <
TName extends PropertyKey, TName extends PropertyKey,
@ -520,33 +522,53 @@ export class Struct<
return this as any; return this as any;
} }
public async deserialize( public deserialize(
context: StructDeserializationContext stream: StructDeserializeStream,
): Promise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>> { ): StructDeserializedResult<TFields, TExtra, TPostDeserialized>;
public deserialize(
stream: StructAsyncDeserializeStream,
): Promise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>>;
public deserialize(
stream: StructDeserializeStream | StructAsyncDeserializeStream,
): ValueOrPromise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>> {
const value = new StructValue(); const value = new StructValue();
Object.defineProperties(value.value, this._extra); Object.defineProperties(value.value, this._extra);
for (const [name, definition] of this._fields) { return Syncbird.try(() => {
const fieldValue = await definition.deserialize(this.options, context, value); const iterator = this._fields[Symbol.iterator]();
value.set(name, fieldValue); const iterate: () => StructValue | Syncbird<StructValue> = () => {
} const result = iterator.next();
if (result.done) {
return value;
}
if (this._postDeserialized) { const [name, definition] = result.value;
const object = value.value as TFields; return Syncbird.resolve(
const result = this._postDeserialized.call(object, object); definition.deserialize(this.options, stream as any, value)
if (result) { ).then(fieldValue => {
return result; 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<Omit<TFields, TOmitInitKey>>, context: StructSerializationContext): ArrayBuffer { public serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>): ArrayBuffer {
const value = new StructValue(); const value = new StructValue();
for (const [name, definition] of this._fields) { 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); value.set(name, fieldValue);
} }
@ -564,7 +586,7 @@ export class Struct<
const dataView = new DataView(buffer); const dataView = new DataView(buffer);
let offset = 0; let offset = 0;
for (const { fieldValue, size } of fieldsInfo) { for (const { fieldValue, size } of fieldsInfo) {
fieldValue.serialize(dataView, offset, context); fieldValue.serialize(dataView, offset);
offset += size; offset += size;
} }

View file

@ -0,0 +1,40 @@
import Bluebird from 'bluebird';
export type Resolvable<R> = R | PromiseLike<R>;
export class Syncbird<T> extends Bluebird<T> implements PromiseLike<T> {
public static resolve(): Syncbird<void>;
public static resolve<R>(value: Resolvable<R>): Syncbird<R>;
public static resolve<R>(value?: Resolvable<R>): Syncbird<R> {
return new Syncbird(
resolve => resolve(value)
);
}
static try<R>(fn: () => Resolvable<R>): Syncbird<R> {
return Syncbird.resolve(fn());
}
public then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
): Syncbird<TResult1 | TResult2> {
if (this.isFulfilled()) {
if (!onfulfilled) {
return this as unknown as Syncbird<TResult1>;
} else {
return Syncbird.resolve(onfulfilled(this.value()));
}
} else {
return Syncbird.resolve(super.then(onfulfilled, onrejected));
}
}
public valueOrPromise(): T | Promise<T> {
if (this.isFulfilled()) {
return this.value();
} else {
return this as Promise<T>;
}
}
}

View file

@ -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'; import { ArrayBufferFieldType, ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer';
class MockDeserializationContext implements StructDeserializationContext { class MockDeserializationStream implements StructDeserializeStream {
public buffer = new ArrayBuffer(0); public buffer = new ArrayBuffer(0);
public read = jest.fn((length: number) => this.buffer); 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', () => { describe('Types', () => {
@ -67,27 +63,13 @@ describe('Types', () => {
it('`#toArrayBuffer` should return the decoded string', () => { it('`#toArrayBuffer` should return the decoded string', () => {
const text = 'foo'; const text = 'foo';
const arrayBuffer = Buffer.from(text, 'utf-8'); const arrayBuffer = Buffer.from(text, 'utf-8');
const context = new MockDeserializationContext(); expect(StringFieldType.instance.toArrayBuffer(text)).toEqual(arrayBuffer);
expect(StringFieldType.instance.toArrayBuffer(text, context)).toEqual(arrayBuffer);
expect(context.encodeUtf8).toBeCalledTimes(1);
expect(context.encodeUtf8).toBeCalledWith(text);
}); });
it('`#fromArrayBuffer` should return the encoded ArrayBuffer', () => { it('`#fromArrayBuffer` should return the encoded ArrayBuffer', () => {
const text = 'foo'; const text = 'foo';
const arrayBuffer = Buffer.from(text, 'utf-8'); const arrayBuffer = Buffer.from(text, 'utf-8');
const context: StructDeserializationContext = { expect(StringFieldType.instance.fromArrayBuffer(arrayBuffer)).toBe(text);
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);
}); });
it('`#getSize` should return -1', () => { it('`#getSize` should return -1', () => {
@ -107,7 +89,7 @@ describe('Types', () => {
const size = 10; const size = 10;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const context = new MockDeserializationContext(); const context = new MockDeserializationStream();
const buffer = new ArrayBuffer(size); const buffer = new ArrayBuffer(size);
context.buffer = buffer; context.buffer = buffer;
const struct = new StructValue(); const struct = new StructValue();
@ -124,7 +106,7 @@ describe('Types', () => {
const size = 10; const size = 10;
const definition = new MockArrayBufferFieldDefinition(Uint8ClampedArrayFieldType.instance, size); const definition = new MockArrayBufferFieldDefinition(Uint8ClampedArrayFieldType.instance, size);
const context = new MockDeserializationContext(); const context = new MockDeserializationStream();
const buffer = new ArrayBuffer(size); const buffer = new ArrayBuffer(size);
context.buffer = buffer; context.buffer = buffer;
const struct = new StructValue(); const struct = new StructValue();
@ -143,7 +125,7 @@ describe('Types', () => {
const size = 0; const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const context = new MockDeserializationContext(); const context = new MockDeserializationStream();
const buffer = new ArrayBuffer(size); const buffer = new ArrayBuffer(size);
context.buffer = buffer; context.buffer = buffer;
const struct = new StructValue(); const struct = new StructValue();
@ -165,7 +147,7 @@ describe('Types', () => {
const size = 0; const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const context = new MockDeserializationContext(); const context = new MockDeserializationStream();
const buffer = new ArrayBuffer(size); const buffer = new ArrayBuffer(size);
context.buffer = buffer; context.buffer = buffer;
const struct = new StructValue(); const struct = new StructValue();
@ -184,7 +166,7 @@ describe('Types', () => {
const size = 0; const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); 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 sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
const buffer = sourceArray.buffer; const buffer = sourceArray.buffer;
context.buffer = buffer; context.buffer = buffer;
@ -194,7 +176,7 @@ describe('Types', () => {
const targetArray = new Uint8Array(size); const targetArray = new Uint8Array(size);
const targetView = new DataView(targetArray.buffer); const targetView = new DataView(targetArray.buffer);
fieldValue.serialize(targetView, 0, context); fieldValue.serialize(targetView, 0);
expect(targetArray).toEqual(sourceArray); expect(targetArray).toEqual(sourceArray);
}); });
@ -203,7 +185,7 @@ describe('Types', () => {
const size = 0; const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size); 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 sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
const buffer = sourceArray.buffer; const buffer = sourceArray.buffer;
context.buffer = buffer; context.buffer = buffer;
@ -215,7 +197,7 @@ describe('Types', () => {
const targetArray = new Uint8Array(size); const targetArray = new Uint8Array(size);
const targetView = new DataView(targetArray.buffer); const targetView = new DataView(targetArray.buffer);
fieldValue.serialize(targetView, 0, context); fieldValue.serialize(targetView, 0);
expect(targetArray).toEqual(sourceArray); expect(targetArray).toEqual(sourceArray);
}); });

View file

@ -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 * Base class for all types that
@ -18,10 +20,10 @@ export abstract class ArrayBufferLikeFieldType<TValue = unknown, TTypeScriptType
* This function should be "pure", i.e., * This function should be "pure", i.e.,
* same `value` should always be converted to `ArrayBuffer`s that have same content. * same `value` should always be converted to `ArrayBuffer`s that have same content.
*/ */
public abstract toArrayBuffer(value: TValue, context: StructSerializationContext): ArrayBuffer; public abstract toArrayBuffer(value: TValue): ArrayBuffer;
/** When implemented in derived classes, converts the `ArrayBuffer` to a type-specific value */ /** When implemented in derived classes, converts the `ArrayBuffer` to a type-specific value */
public abstract fromArrayBuffer(arrayBuffer: ArrayBuffer, context: StructDeserializationContext): TValue; public abstract fromArrayBuffer(arrayBuffer: ArrayBuffer): TValue;
/** /**
* When implemented in derived classes, gets the size in byte of the type-specific `value`. * When implemented in derived classes, gets the size in byte of the type-specific `value`.
@ -76,17 +78,17 @@ export class Uint8ClampedArrayFieldType
} }
} }
/** Am ArrayBufferLike type that converts between `ArrayBuffer` and `string` */ /** An ArrayBufferLike type that converts between `ArrayBuffer` and `string` */
export class StringFieldType<TTypeScriptType = string> export class StringFieldType<TTypeScriptType = string>
extends ArrayBufferLikeFieldType<string, TTypeScriptType> { extends ArrayBufferLikeFieldType<string, TTypeScriptType> {
public static readonly instance = new StringFieldType(); public static readonly instance = new StringFieldType();
public toArrayBuffer(value: string, context: StructSerializationContext): ArrayBuffer { public toArrayBuffer(value: string): ArrayBuffer {
return context.encodeUtf8(value); return encodeUtf8(value);
} }
public fromArrayBuffer(arrayBuffer: ArrayBuffer, context: StructDeserializationContext): string { public fromArrayBuffer(arrayBuffer: ArrayBuffer): string {
return context.decodeUtf8(arrayBuffer); return decodeUtf8(arrayBuffer);
} }
public getSize(): number { public getSize(): number {
@ -100,7 +102,7 @@ export class StringFieldType<TTypeScriptType = string>
const EmptyArrayBuffer = new ArrayBuffer(0); const EmptyArrayBuffer = new ArrayBuffer(0);
export abstract class ArrayBufferLikeFieldDefinition< export abstract class ArrayBufferLikeFieldDefinition<
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType, TType extends ArrayBufferLikeFieldType<any, any> = ArrayBufferLikeFieldType<unknown, unknown>,
TOptions = void, TOptions = void,
TOmitInitKey extends PropertyKey = never, TOmitInitKey extends PropertyKey = never,
> extends StructFieldDefinition< > extends StructFieldDefinition<
@ -124,47 +126,55 @@ export abstract class ArrayBufferLikeFieldDefinition<
*/ */
public create( public create(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext,
struct: StructValue, struct: StructValue,
value: TType['TTypeScriptType'], value: TType['TTypeScriptType'],
arrayBuffer?: ArrayBuffer, arrayBuffer?: ArrayBuffer,
): ArrayBufferLikeFieldValue<this> { ): ArrayBufferLikeFieldValue<this> {
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<StructOptions>, options: Readonly<StructOptions>,
context: StructDeserializationContext, stream: StructDeserializeStream,
struct: StructValue, struct: StructValue,
): Promise<ArrayBufferLikeFieldValue<this>> { ): ArrayBufferLikeFieldValue<this>;
const size = this.getDeserializeSize(struct); public override deserialize(
options: Readonly<StructOptions>,
let arrayBuffer: ArrayBuffer; stream: StructAsyncDeserializeStream,
if (size === 0) { struct: StructValue,
arrayBuffer = EmptyArrayBuffer; ): Promise<ArrayBufferLikeFieldValue<this>>;
} else { public override deserialize(
arrayBuffer = await context.read(size); options: Readonly<StructOptions>,
} stream: StructDeserializeStream | StructAsyncDeserializeStream,
struct: StructValue,
const value = this.type.fromArrayBuffer(arrayBuffer, context); ): ValueOrPromise<ArrayBufferLikeFieldValue<this>> {
return this.create(options, context, struct, value, arrayBuffer); 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< export class ArrayBufferLikeFieldValue<
TDefinition extends ArrayBufferLikeFieldDefinition<ArrayBufferLikeFieldType, any, any>, TDefinition extends ArrayBufferLikeFieldDefinition<ArrayBufferLikeFieldType<unknown, unknown>, any, any>,
> extends StructFieldValue<TDefinition> { > extends StructFieldValue<TDefinition> {
protected arrayBuffer: ArrayBuffer | undefined; protected arrayBuffer: ArrayBuffer | undefined;
public constructor( public constructor(
definition: TDefinition, definition: TDefinition,
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext,
struct: StructValue, struct: StructValue,
value: TDefinition['TValue'], value: TDefinition['TValue'],
arrayBuffer?: ArrayBuffer, arrayBuffer?: ArrayBuffer,
) { ) {
super(definition, options, context, struct, value); super(definition, options, struct, value);
this.arrayBuffer = arrayBuffer; this.arrayBuffer = arrayBuffer;
} }
@ -173,9 +183,9 @@ export class ArrayBufferLikeFieldValue<
this.arrayBuffer = undefined; this.arrayBuffer = undefined;
} }
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void { public serialize(dataView: DataView, offset: number): void {
if (!this.arrayBuffer) { if (!this.arrayBuffer) {
this.arrayBuffer = this.definition.type.toArrayBuffer(this.value, context); this.arrayBuffer = this.definition.type.toArrayBuffer(this.value);
} }
new Uint8Array(dataView.buffer) new Uint8Array(dataView.buffer)

View file

@ -1,4 +1,4 @@
import { StructDefaultOptions, StructDeserializationContext, StructSerializationContext, StructValue } from '../basic'; import { StructDefaultOptions, StructDeserializeStream, StructValue } from '../basic';
import { NumberFieldDefinition, NumberFieldType } from './number'; import { NumberFieldDefinition, NumberFieldType } from './number';
describe('Types', () => { describe('Types', () => {
@ -78,17 +78,13 @@ describe('Types', () => {
describe('#deserialize', () => { describe('#deserialize', () => {
it('should deserialize Uint8', async () => { it('should deserialize Uint8', async () => {
const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer); const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer);
const context: StructDeserializationContext = { const stream: StructDeserializeStream = { read };
read,
decodeUtf8(buffer) { throw new Error(''); },
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Uint8); const definition = new NumberFieldDefinition(NumberFieldType.Uint8);
const struct = new StructValue(); const struct = new StructValue();
const value = await definition.deserialize( const value = await definition.deserialize(
StructDefaultOptions, StructDefaultOptions,
context, stream,
struct, struct,
); );
@ -99,17 +95,13 @@ describe('Types', () => {
it('should deserialize Uint16', async () => { it('should deserialize Uint16', async () => {
const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer); const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer);
const context: StructDeserializationContext = { const stream: StructDeserializeStream = { read };
read,
decodeUtf8(buffer) { throw new Error(''); },
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Uint16); const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
const struct = new StructValue(); const struct = new StructValue();
const value = await definition.deserialize( const value = await definition.deserialize(
StructDefaultOptions, StructDefaultOptions,
context, stream,
struct, struct,
); );
@ -120,17 +112,13 @@ describe('Types', () => {
it('should deserialize Uint16LE', async () => { it('should deserialize Uint16LE', async () => {
const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer); const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]).buffer);
const context: StructDeserializationContext = { const stream: StructDeserializeStream = { read };
read,
decodeUtf8(buffer) { throw new Error(''); },
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Uint16); const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
const struct = new StructValue(); const struct = new StructValue();
const value = await definition.deserialize( const value = await definition.deserialize(
{ ...StructDefaultOptions, littleEndian: true }, { ...StructDefaultOptions, littleEndian: true },
context, stream,
struct, struct,
); );
@ -144,16 +132,12 @@ describe('Types', () => {
describe('NumberFieldValue', () => { describe('NumberFieldValue', () => {
describe('#getSize', () => { describe('#getSize', () => {
it('should return size of its definition', () => { it('should return size of its definition', () => {
const context: StructSerializationContext = {
encodeUtf8(input) { throw new Error(''); },
};
const struct = new StructValue(); const struct = new StructValue();
expect( expect(
new NumberFieldDefinition(NumberFieldType.Int8) new NumberFieldDefinition(NumberFieldType.Int8)
.create( .create(
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
42, 42,
) )
@ -164,7 +148,6 @@ describe('Types', () => {
new NumberFieldDefinition(NumberFieldType.Uint8) new NumberFieldDefinition(NumberFieldType.Uint8)
.create( .create(
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
42, 42,
) )
@ -175,7 +158,6 @@ describe('Types', () => {
new NumberFieldDefinition(NumberFieldType.Int16) new NumberFieldDefinition(NumberFieldType.Int16)
.create( .create(
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
42, 42,
) )
@ -186,7 +168,6 @@ describe('Types', () => {
new NumberFieldDefinition(NumberFieldType.Uint16) new NumberFieldDefinition(NumberFieldType.Uint16)
.create( .create(
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
42, 42,
) )
@ -197,7 +178,6 @@ describe('Types', () => {
new NumberFieldDefinition(NumberFieldType.Int32) new NumberFieldDefinition(NumberFieldType.Int32)
.create( .create(
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
42, 42,
) )
@ -208,7 +188,6 @@ describe('Types', () => {
new NumberFieldDefinition(NumberFieldType.Uint32) new NumberFieldDefinition(NumberFieldType.Uint32)
.create( .create(
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
42, 42,
) )
@ -219,7 +198,6 @@ describe('Types', () => {
new NumberFieldDefinition(NumberFieldType.Int64) new NumberFieldDefinition(NumberFieldType.Int64)
.create( .create(
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
BigInt(100), BigInt(100),
) )
@ -230,7 +208,6 @@ describe('Types', () => {
new NumberFieldDefinition(NumberFieldType.Uint64) new NumberFieldDefinition(NumberFieldType.Uint64)
.create( .create(
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
BigInt(100), BigInt(100),
) )
@ -241,14 +218,10 @@ describe('Types', () => {
describe('#serialize', () => { describe('#serialize', () => {
it('should serialize uint8', () => { it('should serialize uint8', () => {
const context: StructSerializationContext = {
encodeUtf8(input) { throw new Error(''); },
};
const definition = new NumberFieldDefinition(NumberFieldType.Int8); const definition = new NumberFieldDefinition(NumberFieldType.Int8);
const struct = new StructValue(); const struct = new StructValue();
const value = definition.create( const value = definition.create(
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
42, 42,
); );

View file

@ -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 = export type DataViewGetters =
{ [TKey in keyof DataView]: TKey extends `get${string}` ? TKey : never }[keyof DataView]; { [TKey in keyof DataView]: TKey extends `get${string}` ? TKey : never }[keyof DataView];
@ -62,25 +64,37 @@ export class NumberFieldDefinition<
public create( public create(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext,
struct: StructValue, struct: StructValue,
value: TTypeScriptType, value: TTypeScriptType,
): NumberFieldValue<this> { ): NumberFieldValue<this> {
return new NumberFieldValue(this, options, context, struct, value); return new NumberFieldValue(this, options, struct, value);
} }
public async deserialize( public override deserialize(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructDeserializationContext, stream: StructDeserializeStream,
struct: StructValue, struct: StructValue,
): Promise<NumberFieldValue<this>> { ): NumberFieldValue<this>;
const buffer = await context.read(this.getSize()); public override deserialize(
const view = new DataView(buffer); options: Readonly<StructOptions>,
const value = view[this.type.dataViewGetter]( stream: StructAsyncDeserializeStream,
0, struct: StructValue,
options.littleEndian ): Promise<NumberFieldValue<this>>;
) as any; public override deserialize(
return this.create(options, context, struct, value); options: Readonly<StructOptions>,
stream: StructDeserializeStream | StructAsyncDeserializeStream,
struct: StructValue,
): ValueOrPromise<NumberFieldValue<this>> {
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();
} }
} }

View file

@ -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 { ArrayBufferFieldType, ArrayBufferLikeFieldType } from './array-buffer';
import { VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldLengthValue, VariableLengthArrayBufferLikeStructFieldValue } from './variable-length-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 { class MockOriginalFieldValue extends StructFieldValue {
public constructor() { public constructor() {
super({} as any, {} as any, {} as any, {} as any, {}); super({} as any, {} as any, {} as any, {});
} }
public value: string | number = 0; public value: string | number = 0;
@ -27,21 +17,21 @@ class MockOriginalFieldValue extends StructFieldValue {
public set = jest.fn((value: string | number) => { }); 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('Types', () => {
describe('VariableLengthArrayBufferLikeFieldLengthValue', () => { describe('VariableLengthArrayBufferLikeFieldLengthValue', () => {
class MockArrayBufferFieldValue extends StructFieldValue { class MockArrayBufferFieldValue extends StructFieldValue {
public constructor() { public constructor() {
super({ options: {} } as any, {} as any, {} as any, {} as any, {}); super({ options: {} } as any, {} as any, {} as any, {});
} }
public size = 0; public size = 0;
public getSize = jest.fn(() => this.size); 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.'); throw new Error('Method not implemented.');
} }
} }
@ -141,26 +131,25 @@ describe('Types', () => {
let dataView = 0 as any; let dataView = 0 as any;
let offset = 1 as any; let offset = 1 as any;
let context = 2 as any;
mockOriginalFieldValue.value = 10; mockOriginalFieldValue.value = 10;
mockArrayBufferFieldValue.size = 0; mockArrayBufferFieldValue.size = 0;
lengthFieldValue.serialize(dataView, offset, context); lengthFieldValue.serialize(dataView, offset);
expect(mockOriginalFieldValue.get).toBeCalledTimes(1); expect(mockOriginalFieldValue.get).toBeCalledTimes(1);
expect(mockOriginalFieldValue.get).toReturnWith(10); expect(mockOriginalFieldValue.get).toReturnWith(10);
expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledTimes(1);
expect(mockOriginalFieldValue.set).toBeCalledWith(0); expect(mockOriginalFieldValue.set).toBeCalledWith(0);
expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1);
expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset, context); expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset);
mockOriginalFieldValue.set.mockClear(); mockOriginalFieldValue.set.mockClear();
mockOriginalFieldValue.serialize.mockClear(); mockOriginalFieldValue.serialize.mockClear();
mockArrayBufferFieldValue.size = 100; mockArrayBufferFieldValue.size = 100;
lengthFieldValue.serialize(dataView, offset, context); lengthFieldValue.serialize(dataView, offset);
expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledTimes(1);
expect(mockOriginalFieldValue.set).toBeCalledWith(100); expect(mockOriginalFieldValue.set).toBeCalledWith(100);
expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); 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 () => { it('should stringify its length if `originalField` is a string', async () => {
@ -173,26 +162,25 @@ describe('Types', () => {
let dataView = 0 as any; let dataView = 0 as any;
let offset = 1 as any; let offset = 1 as any;
let context = 2 as any;
mockOriginalFieldValue.value = '10'; mockOriginalFieldValue.value = '10';
mockArrayBufferFieldValue.size = 0; mockArrayBufferFieldValue.size = 0;
lengthFieldValue.serialize(dataView, offset, context); lengthFieldValue.serialize(dataView, offset);
expect(mockOriginalFieldValue.get).toBeCalledTimes(1); expect(mockOriginalFieldValue.get).toBeCalledTimes(1);
expect(mockOriginalFieldValue.get).toReturnWith('10'); expect(mockOriginalFieldValue.get).toReturnWith('10');
expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledTimes(1);
expect(mockOriginalFieldValue.set).toBeCalledWith('0'); expect(mockOriginalFieldValue.set).toBeCalledWith('0');
expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1);
expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset, context); expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset);
mockOriginalFieldValue.set.mockClear(); mockOriginalFieldValue.set.mockClear();
mockOriginalFieldValue.serialize.mockClear(); mockOriginalFieldValue.serialize.mockClear();
mockArrayBufferFieldValue.size = 100; mockArrayBufferFieldValue.size = 100;
lengthFieldValue.serialize(dataView, offset, context); lengthFieldValue.serialize(dataView, offset);
expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledTimes(1);
expect(mockOriginalFieldValue.set).toBeCalledWith('100'); expect(mockOriginalFieldValue.set).toBeCalledWith('100');
expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); 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 () => { 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 dataView = 0 as any;
let offset = 1 as any; let offset = 1 as any;
let context = 2 as any;
mockOriginalFieldValue.value = '10'; mockOriginalFieldValue.value = '10';
mockArrayBufferFieldValue.size = 0; mockArrayBufferFieldValue.size = 0;
lengthFieldValue.serialize(dataView, offset, context); lengthFieldValue.serialize(dataView, offset);
expect(mockOriginalFieldValue.get).toBeCalledTimes(1); expect(mockOriginalFieldValue.get).toBeCalledTimes(1);
expect(mockOriginalFieldValue.get).toReturnWith('10'); expect(mockOriginalFieldValue.get).toReturnWith('10');
expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledTimes(1);
expect(mockOriginalFieldValue.set).toBeCalledWith('0'); expect(mockOriginalFieldValue.set).toBeCalledWith('0');
expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1);
expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset, context); expect(mockOriginalFieldValue.serialize).toBeCalledWith(dataView, offset);
mockOriginalFieldValue.set.mockClear(); mockOriginalFieldValue.set.mockClear();
mockOriginalFieldValue.serialize.mockClear(); mockOriginalFieldValue.serialize.mockClear();
mockArrayBufferFieldValue.size = 100; mockArrayBufferFieldValue.size = 100;
lengthFieldValue.serialize(dataView, offset, context); lengthFieldValue.serialize(dataView, offset);
expect(mockOriginalFieldValue.set).toBeCalledTimes(1); expect(mockOriginalFieldValue.set).toBeCalledTimes(1);
expect(mockOriginalFieldValue.set).toBeCalledWith((100).toString(base)); expect(mockOriginalFieldValue.set).toBeCalledWith((100).toString(base));
expect(mockOriginalFieldValue.serialize).toBeCalledTimes(1); 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 }, { lengthField },
); );
const context = new MockDeserializationContext();
const value = new ArrayBuffer(0); const value = new ArrayBuffer(0);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
value, value,
); );
expect(arrayBufferFieldValue).toHaveProperty('definition', arrayBufferFieldDefinition); expect(arrayBufferFieldValue).toHaveProperty('definition', arrayBufferFieldDefinition);
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions); expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
expect(arrayBufferFieldValue).toHaveProperty('context', context);
expect(arrayBufferFieldValue).toHaveProperty('struct', struct); expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
expect(arrayBufferFieldValue).toHaveProperty('value', value); expect(arrayBufferFieldValue).toHaveProperty('value', value);
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined); expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined);
@ -278,13 +262,11 @@ describe('Types', () => {
{ lengthField }, { lengthField },
); );
const context = new MockDeserializationContext();
const value = new ArrayBuffer(100); const value = new ArrayBuffer(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
value, value,
value, value,
@ -292,7 +274,6 @@ describe('Types', () => {
expect(arrayBufferFieldValue).toHaveProperty('definition', arrayBufferFieldDefinition); expect(arrayBufferFieldValue).toHaveProperty('definition', arrayBufferFieldDefinition);
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions); expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
expect(arrayBufferFieldValue).toHaveProperty('context', context);
expect(arrayBufferFieldValue).toHaveProperty('struct', struct); expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
expect(arrayBufferFieldValue).toHaveProperty('value', value); expect(arrayBufferFieldValue).toHaveProperty('value', value);
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value); expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value);
@ -311,13 +292,11 @@ describe('Types', () => {
{ lengthField }, { lengthField },
); );
const context = new MockDeserializationContext();
const value = new ArrayBuffer(0); const value = new ArrayBuffer(0);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
value, value,
); );
@ -329,11 +308,11 @@ describe('Types', () => {
describe('#getSize', () => { describe('#getSize', () => {
class MockArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer> { class MockArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer> {
public toArrayBuffer = jest.fn((value: ArrayBuffer, context: StructSerializationContext): ArrayBuffer => { public toArrayBuffer = jest.fn((value: ArrayBuffer): ArrayBuffer => {
return value; return value;
}); });
public fromArrayBuffer = jest.fn((arrayBuffer: ArrayBuffer, context: StructDeserializationContext): ArrayBuffer => { public fromArrayBuffer = jest.fn((arrayBuffer: ArrayBuffer): ArrayBuffer => {
return arrayBuffer; return arrayBuffer;
}); });
@ -357,13 +336,11 @@ describe('Types', () => {
{ lengthField }, { lengthField },
); );
const context = new MockDeserializationContext();
const value = new ArrayBuffer(100); const value = new ArrayBuffer(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
value, value,
value, value,
@ -388,13 +365,11 @@ describe('Types', () => {
{ lengthField }, { lengthField },
); );
const context = new MockDeserializationContext();
const value = new ArrayBuffer(100); const value = new ArrayBuffer(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
value, value,
); );
@ -421,13 +396,11 @@ describe('Types', () => {
{ lengthField }, { lengthField },
); );
const context = new MockDeserializationContext();
const value = new ArrayBuffer(100); const value = new ArrayBuffer(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
value, value,
); );
@ -455,13 +428,11 @@ describe('Types', () => {
{ lengthField }, { lengthField },
); );
const context = new MockDeserializationContext();
const value = new ArrayBuffer(100); const value = new ArrayBuffer(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
value, value,
value, value,
@ -485,13 +456,11 @@ describe('Types', () => {
{ lengthField }, { lengthField },
); );
const context = new MockDeserializationContext();
const value = new ArrayBuffer(100); const value = new ArrayBuffer(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
value, value,
value, value,
@ -597,18 +566,15 @@ describe('Types', () => {
{ lengthField }, { lengthField },
); );
const context = new MockDeserializationContext();
const value = new ArrayBuffer(100); const value = new ArrayBuffer(100);
const arrayBufferFieldValue = definition.create( const arrayBufferFieldValue = definition.create(
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
value, value,
); );
expect(arrayBufferFieldValue).toHaveProperty('definition', definition); expect(arrayBufferFieldValue).toHaveProperty('definition', definition);
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions); expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
expect(arrayBufferFieldValue).toHaveProperty('context', context);
expect(arrayBufferFieldValue).toHaveProperty('struct', struct); expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
expect(arrayBufferFieldValue).toHaveProperty('value', value); expect(arrayBufferFieldValue).toHaveProperty('value', value);
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined); expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined);
@ -627,11 +593,9 @@ describe('Types', () => {
{ lengthField }, { lengthField },
); );
const context = new MockDeserializationContext();
const value = new ArrayBuffer(100); const value = new ArrayBuffer(100);
const arrayBufferFieldValue = definition.create( const arrayBufferFieldValue = definition.create(
StructDefaultOptions, StructDefaultOptions,
context,
struct, struct,
value, value,
value, value,
@ -639,7 +603,6 @@ describe('Types', () => {
expect(arrayBufferFieldValue).toHaveProperty('definition', definition); expect(arrayBufferFieldValue).toHaveProperty('definition', definition);
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions); expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
expect(arrayBufferFieldValue).toHaveProperty('context', context);
expect(arrayBufferFieldValue).toHaveProperty('struct', struct); expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
expect(arrayBufferFieldValue).toHaveProperty('value', value); expect(arrayBufferFieldValue).toHaveProperty('value', value);
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value); expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value);

View file

@ -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 type { KeysOfType } from '../utils';
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue } from './array-buffer'; import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue } from './array-buffer';
@ -35,7 +35,6 @@ export class VariableLengthArrayBufferLikeFieldDefinition<
public create( public create(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext,
struct: StructValue, struct: StructValue,
value: TType['TTypeScriptType'], value: TType['TTypeScriptType'],
arrayBuffer?: ArrayBuffer arrayBuffer?: ArrayBuffer
@ -43,7 +42,6 @@ export class VariableLengthArrayBufferLikeFieldDefinition<
return new VariableLengthArrayBufferLikeStructFieldValue( return new VariableLengthArrayBufferLikeStructFieldValue(
this, this,
options, options,
context,
struct, struct,
value, value,
arrayBuffer, arrayBuffer,
@ -61,12 +59,11 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
public constructor( public constructor(
definition: TDefinition, definition: TDefinition,
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
context: StructSerializationContext,
struct: StructValue, struct: StructValue,
value: TDefinition['TValue'], value: TDefinition['TValue'],
arrayBuffer?: ArrayBuffer, arrayBuffer?: ArrayBuffer,
) { ) {
super(definition, options, context, struct, value, arrayBuffer); super(definition, options, struct, value, arrayBuffer);
if (arrayBuffer) { if (arrayBuffer) {
this.length = arrayBuffer.byteLength; this.length = arrayBuffer.byteLength;
@ -87,7 +84,7 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
if (this.length === undefined) { if (this.length === undefined) {
this.length = this.definition.type.getSize(this.value); this.length = this.definition.type.getSize(this.value);
if (this.length === -1) { 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; this.length = this.arrayBuffer.byteLength;
} }
} }
@ -116,7 +113,7 @@ export class VariableLengthArrayBufferLikeFieldLengthValue
originalField: StructFieldValue, originalField: StructFieldValue,
arrayBufferField: VariableLengthArrayBufferLikeFieldValueLike, arrayBufferField: VariableLengthArrayBufferLikeFieldValueLike,
) { ) {
super(originalField.definition, originalField.options, originalField.context, originalField.struct, 0); super(originalField.definition, originalField.options, originalField.struct, 0);
this.originalField = originalField; this.originalField = originalField;
this.arrayBufferField = arrayBufferField; this.arrayBufferField = arrayBufferField;
} }
@ -138,8 +135,8 @@ export class VariableLengthArrayBufferLikeFieldLengthValue
set() { } set() { }
serialize(dataView: DataView, offset: number, context: StructSerializationContext) { serialize(dataView: DataView, offset: number) {
this.originalField.set(this.get()); this.originalField.set(this.get());
this.originalField.serialize(dataView, offset, context); this.originalField.serialize(dataView, offset);
} }
} }

View file

@ -49,3 +49,16 @@ export type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T;
export function placeholder<T>(): T { export function placeholder<T>(): T {
return undefined as unknown as 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);
}

View file

@ -3,8 +3,8 @@
"compilerOptions": { "compilerOptions": {
"types": [ "types": [
"node", "node",
"jest" "jest",
] ],
}, },
"testTypes": [ "testTypes": [
"node", "node",

View file

@ -13,11 +13,11 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/node": "^16.9.1" "@types/node": "^16.11.19"
}, },
"dependencies": { "dependencies": {
"json5": "^2.2.0", "json5": "^2.2.0",
"@types/jest": "^26.0.23" "@types/jest": "^27.4.0"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^4.0.0" "typescript": "^4.0.0"