mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 01:39:21 +02:00
refactor(struct): remove context interfaces
This commit is contained in:
parent
949aaab818
commit
08767c7b71
20 changed files with 429 additions and 333 deletions
78
common/config/rush/pnpm-lock.yaml
generated
78
common/config/rush/pnpm-lock.yaml
generated
|
@ -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
|
||||
|
|
|
@ -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<ArrayBuffer>;
|
||||
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<TExtra, TValue>
|
||||
: TPostDeserialized
|
||||
>;
|
||||
deserialize(
|
||||
stream: StructAsyncDeserializeStream,
|
||||
): Promise<
|
||||
TPostDeserialized extends undefined
|
||||
? Overwrite<TExtra, TValue>
|
||||
: 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<TFields, TOmitInitKey>,
|
||||
context: StructSerializationContext
|
||||
init: Omit<TFields, TOmitInitKey>
|
||||
): ArrayBuffer;
|
||||
```
|
||||
|
||||
|
@ -634,7 +642,6 @@ Actual size should be returned from `StructFieldValue#getSize`
|
|||
```ts
|
||||
abstract create(
|
||||
options: Readonly<StructOptions>,
|
||||
context: StructSerializationContext,
|
||||
struct: StructValue,
|
||||
value: TValue,
|
||||
): StructFieldValue<this>;
|
||||
|
@ -649,16 +656,21 @@ Derived classes must implement this method to create its own field value instanc
|
|||
```ts
|
||||
abstract deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
context: StructDeserializationContext,
|
||||
stream: StructDeserializeStream,
|
||||
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:
|
||||
|
||||
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;
|
||||
```
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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<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.');
|
||||
}
|
||||
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.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<StructOptions>,
|
||||
context: StructSerializationContext,
|
||||
struct: StructValue,
|
||||
value: TValue,
|
||||
): StructFieldValue<this>;
|
||||
|
@ -63,7 +61,12 @@ export abstract class StructFieldDefinition<
|
|||
*/
|
||||
public abstract deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
context: StructDeserializationContext,
|
||||
stream: StructDeserializeStream,
|
||||
struct: StructValue,
|
||||
): ValueOrPromise<StructFieldValue<this>>;
|
||||
): StructFieldValue<this>;
|
||||
public abstract deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: StructAsyncDeserializeStream,
|
||||
struct: StructValue,
|
||||
): Promise<StructFieldValue<this>>;
|
||||
}
|
||||
|
|
|
@ -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<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.');
|
||||
}
|
||||
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.');
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -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<StructOptions>;
|
||||
|
||||
/** 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<StructOptions>,
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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<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.');
|
||||
}
|
||||
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.');
|
||||
}
|
||||
}
|
||||
|
@ -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]));
|
||||
});
|
||||
|
|
|
@ -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<TValue> {
|
||||
deserialize(context: StructDeserializationContext): Promise<TValue>;
|
||||
deserialize(stream: StructDeserializeStream | StructAsyncDeserializeStream): Promise<TValue>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,7 +55,7 @@ interface ArrayBufferLikeFieldCreator<
|
|||
*/
|
||||
<
|
||||
TName extends PropertyKey,
|
||||
TType extends ArrayBufferLikeFieldType,
|
||||
TType extends ArrayBufferLikeFieldType<any, any>,
|
||||
TTypeScriptType = TType['TTypeScriptType'],
|
||||
>(
|
||||
name: TName,
|
||||
|
@ -77,7 +79,7 @@ interface ArrayBufferLikeFieldCreator<
|
|||
*/
|
||||
<
|
||||
TName extends PropertyKey,
|
||||
TType extends ArrayBufferLikeFieldType,
|
||||
TType extends ArrayBufferLikeFieldType<any, any>,
|
||||
TOptions extends VariableLengthArrayBufferLikeFieldOptions<TFields>,
|
||||
TTypeScriptType = TType['TTypeScriptType'],
|
||||
>(
|
||||
|
@ -106,7 +108,7 @@ interface BindedArrayBufferLikeFieldDefinitionCreator<
|
|||
TOmitInitKey extends PropertyKey,
|
||||
TExtra extends object,
|
||||
TPostDeserialized,
|
||||
TType extends ArrayBufferLikeFieldType
|
||||
TType extends ArrayBufferLikeFieldType<any, any>
|
||||
> {
|
||||
<
|
||||
TName extends PropertyKey,
|
||||
|
@ -520,33 +522,53 @@ export class Struct<
|
|||
return this as any;
|
||||
}
|
||||
|
||||
public async deserialize(
|
||||
context: StructDeserializationContext
|
||||
): Promise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>> {
|
||||
public deserialize(
|
||||
stream: StructDeserializeStream,
|
||||
): 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();
|
||||
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<StructValue> = () => {
|
||||
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<Omit<TFields, TOmitInitKey>>, context: StructSerializationContext): ArrayBuffer {
|
||||
public serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>): 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;
|
||||
}
|
||||
|
||||
|
|
40
libraries/struct/src/syncbird.ts
Normal file
40
libraries/struct/src/syncbird.ts
Normal 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>;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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<TValue = unknown, TTypeScriptType
|
|||
* This function should be "pure", i.e.,
|
||||
* 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 */
|
||||
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`.
|
||||
|
@ -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>
|
||||
extends ArrayBufferLikeFieldType<string, TTypeScriptType> {
|
||||
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<TTypeScriptType = string>
|
|||
const EmptyArrayBuffer = new ArrayBuffer(0);
|
||||
|
||||
export abstract class ArrayBufferLikeFieldDefinition<
|
||||
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
|
||||
TType extends ArrayBufferLikeFieldType<any, any> = ArrayBufferLikeFieldType<unknown, unknown>,
|
||||
TOptions = void,
|
||||
TOmitInitKey extends PropertyKey = never,
|
||||
> extends StructFieldDefinition<
|
||||
|
@ -124,47 +126,55 @@ export abstract class ArrayBufferLikeFieldDefinition<
|
|||
*/
|
||||
public create(
|
||||
options: Readonly<StructOptions>,
|
||||
context: StructSerializationContext,
|
||||
struct: StructValue,
|
||||
value: TType['TTypeScriptType'],
|
||||
arrayBuffer?: ArrayBuffer,
|
||||
): 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>,
|
||||
context: StructDeserializationContext,
|
||||
stream: StructDeserializeStream,
|
||||
struct: StructValue,
|
||||
): Promise<ArrayBufferLikeFieldValue<this>> {
|
||||
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<this>;
|
||||
public override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: StructAsyncDeserializeStream,
|
||||
struct: StructValue,
|
||||
): Promise<ArrayBufferLikeFieldValue<this>>;
|
||||
public override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: StructDeserializeStream | StructAsyncDeserializeStream,
|
||||
struct: StructValue,
|
||||
): ValueOrPromise<ArrayBufferLikeFieldValue<this>> {
|
||||
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<ArrayBufferLikeFieldType, any, any>,
|
||||
TDefinition extends ArrayBufferLikeFieldDefinition<ArrayBufferLikeFieldType<unknown, unknown>, any, any>,
|
||||
> extends StructFieldValue<TDefinition> {
|
||||
protected arrayBuffer: ArrayBuffer | undefined;
|
||||
|
||||
public constructor(
|
||||
definition: TDefinition,
|
||||
options: Readonly<StructOptions>,
|
||||
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)
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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<StructOptions>,
|
||||
context: StructSerializationContext,
|
||||
struct: StructValue,
|
||||
value: TTypeScriptType,
|
||||
): 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>,
|
||||
context: StructDeserializationContext,
|
||||
stream: StructDeserializeStream,
|
||||
struct: StructValue,
|
||||
): Promise<NumberFieldValue<this>> {
|
||||
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<this>;
|
||||
public override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: StructAsyncDeserializeStream,
|
||||
struct: StructValue,
|
||||
): Promise<NumberFieldValue<this>>;
|
||||
public override deserialize(
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ArrayBuffer> {
|
||||
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);
|
||||
|
|
|
@ -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<StructOptions>,
|
||||
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<StructOptions>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,3 +49,16 @@ export type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T;
|
|||
export function placeholder<T>(): 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);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
"compilerOptions": {
|
||||
"types": [
|
||||
"node",
|
||||
"jest"
|
||||
]
|
||||
"jest",
|
||||
],
|
||||
},
|
||||
"testTypes": [
|
||||
"node",
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue