mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 02:39:26 +02:00
refactor(struct): type plugin system rewrok v2
This commit is contained in:
parent
f9502f0e6f
commit
c37e3dd953
32 changed files with 865 additions and 729 deletions
|
@ -22,8 +22,9 @@ const AdbReverseStringResponse =
|
||||||
.string('content', { lengthField: 'length' });
|
.string('content', { lengthField: 'length' });
|
||||||
|
|
||||||
const AdbReverseErrorResponse =
|
const AdbReverseErrorResponse =
|
||||||
AdbReverseStringResponse
|
new Struct({ littleEndian: true })
|
||||||
.afterParsed((value) => {
|
.fields(AdbReverseStringResponse)
|
||||||
|
.postDeserialize((value) => {
|
||||||
throw new Error(value.content);
|
throw new Error(value.content);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { StructValueType } from '@yume-chan/struct';
|
import { Struct, StructValueType } from '@yume-chan/struct';
|
||||||
import { AdbBufferedStream } from '../../stream';
|
import { AdbBufferedStream } from '../../stream';
|
||||||
import { AdbSyncRequestId, adbSyncWriteRequest } from './request';
|
import { AdbSyncRequestId, adbSyncWriteRequest } from './request';
|
||||||
import { AdbSyncDoneResponse, adbSyncReadResponse, AdbSyncResponseId } from './response';
|
import { AdbSyncDoneResponse, adbSyncReadResponse, AdbSyncResponseId } from './response';
|
||||||
import { AdbSyncLstatResponse } from './stat';
|
import { AdbSyncLstatResponse } from './stat';
|
||||||
|
|
||||||
export const AdbSyncEntryResponse =
|
export const AdbSyncEntryResponse =
|
||||||
AdbSyncLstatResponse
|
new Struct({ littleEndian: true })
|
||||||
.afterParsed()
|
.fields(AdbSyncLstatResponse)
|
||||||
.uint32('nameLength')
|
.uint32('nameLength')
|
||||||
.string('name', { lengthField: 'nameLength' })
|
.string('name', { lengthField: 'nameLength' })
|
||||||
.extra({ id: AdbSyncResponseId.Entry as const });
|
.extra({ id: AdbSyncResponseId.Entry as const });
|
||||||
|
|
|
@ -18,7 +18,8 @@ export const AdbSyncNumberRequest =
|
||||||
.uint32('arg');
|
.uint32('arg');
|
||||||
|
|
||||||
export const AdbSyncDataRequest =
|
export const AdbSyncDataRequest =
|
||||||
AdbSyncNumberRequest
|
new Struct({ littleEndian: true })
|
||||||
|
.fields(AdbSyncNumberRequest)
|
||||||
.arrayBuffer('data', { lengthField: 'arg' });
|
.arrayBuffer('data', { lengthField: 'arg' });
|
||||||
|
|
||||||
export async function adbSyncWriteRequest(
|
export async function adbSyncWriteRequest(
|
||||||
|
|
|
@ -35,7 +35,7 @@ export const AdbSyncFailResponse =
|
||||||
new Struct({ littleEndian: true })
|
new Struct({ littleEndian: true })
|
||||||
.uint32('messageLength')
|
.uint32('messageLength')
|
||||||
.string('message', { lengthField: 'messageLength' })
|
.string('message', { lengthField: 'messageLength' })
|
||||||
.afterParsed(object => {
|
.postDeserialize(object => {
|
||||||
throw new Error(object.message);
|
throw new Error(object.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ export const AdbSyncLstatResponse =
|
||||||
get type() { return this.mode >> 12 as LinuxFileType; },
|
get type() { return this.mode >> 12 as LinuxFileType; },
|
||||||
get permission() { return this.mode & 0b00001111_11111111; },
|
get permission() { return this.mode & 0b00001111_11111111; },
|
||||||
})
|
})
|
||||||
.afterParsed((object) => {
|
.postDeserialize((object) => {
|
||||||
if (object.mode === 0 &&
|
if (object.mode === 0 &&
|
||||||
object.size === 0 &&
|
object.size === 0 &&
|
||||||
object.mtime === 0
|
object.mtime === 0
|
||||||
|
@ -56,7 +56,7 @@ export enum AdbSyncStatErrorCode {
|
||||||
|
|
||||||
export const AdbSyncStatResponse =
|
export const AdbSyncStatResponse =
|
||||||
new Struct({ littleEndian: true })
|
new Struct({ littleEndian: true })
|
||||||
.uint32('error', undefined, placeholder<AdbSyncStatErrorCode>())
|
.uint32('error', placeholder<AdbSyncStatErrorCode>())
|
||||||
.uint64('dev')
|
.uint64('dev')
|
||||||
.uint64('ino')
|
.uint64('ino')
|
||||||
.uint32('mode')
|
.uint32('mode')
|
||||||
|
@ -72,7 +72,7 @@ export const AdbSyncStatResponse =
|
||||||
get type() { return this.mode >> 12 as LinuxFileType; },
|
get type() { return this.mode >> 12 as LinuxFileType; },
|
||||||
get permission() { return this.mode & 0b00001111_11111111; },
|
get permission() { return this.mode & 0b00001111_11111111; },
|
||||||
})
|
})
|
||||||
.afterParsed((object) => {
|
.postDeserialize((object) => {
|
||||||
if (object.error) {
|
if (object.error) {
|
||||||
throw new Error(AdbSyncStatErrorCode[object.error]);
|
throw new Error(AdbSyncStatErrorCode[object.error]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export enum AdbCommand {
|
||||||
|
|
||||||
const AdbPacketHeader =
|
const AdbPacketHeader =
|
||||||
new Struct({ littleEndian: true })
|
new Struct({ littleEndian: true })
|
||||||
.uint32('command', undefined)
|
.uint32('command')
|
||||||
.uint32('arg0')
|
.uint32('arg0')
|
||||||
.uint32('arg1')
|
.uint32('arg1')
|
||||||
.uint32('payloadLength')
|
.uint32('payloadLength')
|
||||||
|
@ -21,7 +21,8 @@ const AdbPacketHeader =
|
||||||
.int32('magic');
|
.int32('magic');
|
||||||
|
|
||||||
const AdbPacketStruct =
|
const AdbPacketStruct =
|
||||||
AdbPacketHeader
|
new Struct({ littleEndian: true })
|
||||||
|
.fields(AdbPacketHeader)
|
||||||
.arrayBuffer('payload', { lengthField: 'payloadLength' });
|
.arrayBuffer('payload', { lengthField: 'payloadLength' });
|
||||||
|
|
||||||
export type AdbPacket = StructValueType<typeof AdbPacketStruct>;
|
export type AdbPacket = StructValueType<typeof AdbPacketStruct>;
|
||||||
|
|
|
@ -27,6 +27,7 @@ interface Progress {
|
||||||
export const Install = withDisplayName('Install')(({
|
export const Install = withDisplayName('Install')(({
|
||||||
device,
|
device,
|
||||||
}: RouteProps): JSX.Element => {
|
}: RouteProps): JSX.Element => {
|
||||||
|
const [installing, setInstalling] = useState(false);
|
||||||
const [progress, setProgress] = useState<Progress>();
|
const [progress, setProgress] = useState<Progress>();
|
||||||
|
|
||||||
const handleOpen = useCallback(async () => {
|
const handleOpen = useCallback(async () => {
|
||||||
|
@ -35,6 +36,7 @@ export const Install = withDisplayName('Install')(({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInstalling(true);
|
||||||
setProgress({
|
setProgress({
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
stage: Stage.Uploading,
|
stage: Stage.Uploading,
|
||||||
|
@ -70,13 +72,14 @@ export const Install = withDisplayName('Install')(({
|
||||||
totalSize: file.size,
|
totalSize: file.size,
|
||||||
value: 1,
|
value: 1,
|
||||||
});
|
});
|
||||||
|
setInstalling(false);
|
||||||
}, [device]);
|
}, [device]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
disabled={!device || !!progress}
|
disabled={!device || installing}
|
||||||
text="Open"
|
text="Open"
|
||||||
onClick={handleOpen}
|
onClick={handleOpen}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,7 +16,7 @@ export enum ScrcpyControlMessageType {
|
||||||
|
|
||||||
export const ScrcpySimpleControlMessage =
|
export const ScrcpySimpleControlMessage =
|
||||||
new Struct()
|
new Struct()
|
||||||
.uint8('type', undefined, placeholder<ScrcpyControlMessageType.BackOrScreenOn>());
|
.uint8('type', placeholder<ScrcpyControlMessageType.BackOrScreenOn>());
|
||||||
|
|
||||||
export type ScrcpySimpleControlMessage = StructInitType<typeof ScrcpySimpleControlMessage>;
|
export type ScrcpySimpleControlMessage = StructInitType<typeof ScrcpySimpleControlMessage>;
|
||||||
|
|
||||||
|
@ -38,8 +38,8 @@ export enum AndroidMotionEventAction {
|
||||||
|
|
||||||
export const ScrcpyInjectTouchControlMessage =
|
export const ScrcpyInjectTouchControlMessage =
|
||||||
new Struct()
|
new Struct()
|
||||||
.uint8('type', undefined, ScrcpyControlMessageType.InjectTouch as const)
|
.uint8('type', ScrcpyControlMessageType.InjectTouch as const)
|
||||||
.uint8('action', undefined, placeholder<AndroidMotionEventAction>())
|
.uint8('action', placeholder<AndroidMotionEventAction>())
|
||||||
.uint64('pointerId')
|
.uint64('pointerId')
|
||||||
.uint32('pointerX')
|
.uint32('pointerX')
|
||||||
.uint32('pointerY')
|
.uint32('pointerY')
|
||||||
|
@ -52,7 +52,7 @@ export type ScrcpyInjectTouchControlMessage = StructInitType<typeof ScrcpyInject
|
||||||
|
|
||||||
export const ScrcpyInjectTextControlMessage =
|
export const ScrcpyInjectTextControlMessage =
|
||||||
new Struct()
|
new Struct()
|
||||||
.uint8('type', undefined, ScrcpyControlMessageType.InjectText as const)
|
.uint8('type', ScrcpyControlMessageType.InjectText as const)
|
||||||
.uint32('length')
|
.uint32('length')
|
||||||
.string('text', { lengthField: 'length' });
|
.string('text', { lengthField: 'length' });
|
||||||
|
|
||||||
|
@ -99,8 +99,8 @@ export enum AndroidKeyCode {
|
||||||
|
|
||||||
export const ScrcpyInjectKeyCodeControlMessage =
|
export const ScrcpyInjectKeyCodeControlMessage =
|
||||||
new Struct()
|
new Struct()
|
||||||
.uint8('type', undefined, ScrcpyControlMessageType.InjectKeycode as const)
|
.uint8('type', ScrcpyControlMessageType.InjectKeycode as const)
|
||||||
.uint8('action', undefined, placeholder<AndroidKeyEventAction>())
|
.uint8('action', placeholder<AndroidKeyEventAction>())
|
||||||
.uint32('keyCode')
|
.uint32('keyCode')
|
||||||
.uint32('repeat')
|
.uint32('repeat')
|
||||||
.uint32('metaState');
|
.uint32('metaState');
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
const tslib_1 = require("tslib");
|
var tslib_1 = require("tslib");
|
||||||
const clean_webpack_plugin_1 = require("clean-webpack-plugin");
|
var clean_webpack_plugin_1 = require("clean-webpack-plugin");
|
||||||
const copy_webpack_plugin_1 = tslib_1.__importDefault(require("copy-webpack-plugin"));
|
var copy_webpack_plugin_1 = tslib_1.__importDefault(require("copy-webpack-plugin"));
|
||||||
const html_webpack_plugin_1 = tslib_1.__importDefault(require("html-webpack-plugin"));
|
var html_webpack_plugin_1 = tslib_1.__importDefault(require("html-webpack-plugin"));
|
||||||
const mini_css_extract_plugin_1 = tslib_1.__importDefault(require("mini-css-extract-plugin"));
|
var mini_css_extract_plugin_1 = tslib_1.__importDefault(require("mini-css-extract-plugin"));
|
||||||
const path_1 = tslib_1.__importDefault(require("path"));
|
var path_1 = tslib_1.__importDefault(require("path"));
|
||||||
const webpack_bundle_analyzer_1 = require("webpack-bundle-analyzer");
|
var webpack_bundle_analyzer_1 = require("webpack-bundle-analyzer");
|
||||||
const context = path_1.default.resolve(process.cwd());
|
var context = path_1.default.resolve(process.cwd());
|
||||||
const plugins = [
|
var plugins = [
|
||||||
new clean_webpack_plugin_1.CleanWebpackPlugin(),
|
new clean_webpack_plugin_1.CleanWebpackPlugin(),
|
||||||
new mini_css_extract_plugin_1.default({
|
new mini_css_extract_plugin_1.default({
|
||||||
filename: '[name].[contenthash].css',
|
filename: '[name].[contenthash].css',
|
||||||
|
@ -29,10 +29,10 @@ const plugins = [
|
||||||
if (process.env.ANALYZE) {
|
if (process.env.ANALYZE) {
|
||||||
plugins.push(new webpack_bundle_analyzer_1.BundleAnalyzerPlugin());
|
plugins.push(new webpack_bundle_analyzer_1.BundleAnalyzerPlugin());
|
||||||
}
|
}
|
||||||
const config = (env, argv) => ({
|
var config = function (env, argv) { return ({
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map',
|
devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map',
|
||||||
context,
|
context: context,
|
||||||
target: 'web',
|
target: 'web',
|
||||||
entry: {
|
entry: {
|
||||||
index: './src/index.tsx',
|
index: './src/index.tsx',
|
||||||
|
@ -43,10 +43,9 @@ const config = (env, argv) => ({
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.tsx', '.js'],
|
extensions: ['.ts', '.tsx', '.js'],
|
||||||
// @ts-expect-error typing is not up to date
|
|
||||||
fallback: { "path": require.resolve("path-browserify") },
|
fallback: { "path": require.resolve("path-browserify") },
|
||||||
},
|
},
|
||||||
plugins,
|
plugins: plugins,
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{ test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], },
|
{ test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], },
|
||||||
|
@ -59,5 +58,5 @@ const config = (env, argv) => ({
|
||||||
contentBase: path_1.default.resolve(context, 'lib'),
|
contentBase: path_1.default.resolve(context, 'lib'),
|
||||||
port: 9000,
|
port: 9000,
|
||||||
},
|
},
|
||||||
});
|
}); };
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
import { StructDefaultOptions } from './context';
|
import { StructDefaultOptions } from './context';
|
||||||
import { GlobalStructFieldRuntimeTypeRegistry } from './registry';
|
|
||||||
|
|
||||||
describe('Runtime', () => {
|
describe('Runtime', () => {
|
||||||
describe('StructDefaultOptions', () => {
|
describe('StructDefaultOptions', () => {
|
||||||
it('should have `littleEndian` equals to `false`', () => {
|
it('should have `littleEndian` that equals to `false`', () => {
|
||||||
expect(StructDefaultOptions.littleEndian).toBe(false);
|
expect(StructDefaultOptions).toHaveProperty('littleEndian', false);
|
||||||
});
|
|
||||||
|
|
||||||
it('should have `fieldRuntimeTypeRegistry` equals to `GlobalStructFieldRuntimeTypeRegistry`', () => {
|
|
||||||
expect(StructDefaultOptions.fieldRuntimeTypeRegistry).toBe(GlobalStructFieldRuntimeTypeRegistry);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
import type { StructFieldRuntimeTypeRegistry } from './registry';
|
|
||||||
import { GlobalStructFieldRuntimeTypeRegistry } from './registry';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context with enough methods to serialize a struct
|
* Context with enough methods to serialize a struct
|
||||||
*/
|
*/
|
||||||
|
@ -21,9 +18,10 @@ export interface StructDeserializationContext extends StructSerializationContext
|
||||||
decodeUtf8(buffer: ArrayBuffer): string;
|
decodeUtf8(buffer: ArrayBuffer): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read exactly `length` bytes of data from underlying storage.
|
* Read data from the underlying data source.
|
||||||
*
|
*
|
||||||
* Errors can be thrown to indicates end of file or other errors.
|
* Context should return exactly `length` bytes or data. If that's not possible
|
||||||
|
* (due to end of file or other error condition), it should throw an error.
|
||||||
*/
|
*/
|
||||||
read(length: number): ArrayBuffer | Promise<ArrayBuffer>;
|
read(length: number): ArrayBuffer | Promise<ArrayBuffer>;
|
||||||
}
|
}
|
||||||
|
@ -35,11 +33,8 @@ export interface StructOptions {
|
||||||
* Default to `false`
|
* Default to `false`
|
||||||
*/
|
*/
|
||||||
littleEndian: boolean;
|
littleEndian: boolean;
|
||||||
|
|
||||||
fieldRuntimeTypeRegistry: StructFieldRuntimeTypeRegistry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StructDefaultOptions: Readonly<StructOptions> = {
|
export const StructDefaultOptions: Readonly<StructOptions> = {
|
||||||
littleEndian: false,
|
littleEndian: false,
|
||||||
fieldRuntimeTypeRegistry: GlobalStructFieldRuntimeTypeRegistry,
|
|
||||||
};
|
};
|
||||||
|
|
22
packages/struct/src/basic/definition.spec.ts
Normal file
22
packages/struct/src/basic/definition.spec.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { FieldDefinition } from './definition';
|
||||||
|
import { FieldRuntimeValue } from './runtime-value';
|
||||||
|
|
||||||
|
describe('FieldDefinition', () => {
|
||||||
|
describe('new', () => {
|
||||||
|
it('should save the `options` parameter', () => {
|
||||||
|
class MockFieldDefinition extends FieldDefinition<number>{
|
||||||
|
public constructor(options: number) {
|
||||||
|
super(options);
|
||||||
|
}
|
||||||
|
public getSize(): number {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
public createValue(): FieldRuntimeValue<FieldDefinition<any, any, any>> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(new MockFieldDefinition(42)).toHaveProperty('options', 42);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
57
packages/struct/src/basic/definition.ts
Normal file
57
packages/struct/src/basic/definition.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import type { StructOptions, StructSerializationContext } from './context';
|
||||||
|
import type { FieldRuntimeValue } from './runtime-value';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A field definition is a bridge between its type and its runtime value.
|
||||||
|
*
|
||||||
|
* `Struct` record fields in a list of `FieldDefinition`s.
|
||||||
|
*
|
||||||
|
* When `Struct#create` or `Struct#deserialize` are called, each field's definition
|
||||||
|
* crates its own type of `FieldRuntimeValue` to manage the field value in that `Struct` instance.
|
||||||
|
*
|
||||||
|
* One `FieldDefinition` can represents multiple similar types, just returns the corresponding
|
||||||
|
* `FieldRuntimeValue` when `createValue` was called.
|
||||||
|
*
|
||||||
|
* @template TOptions TypeScript type of this definition's `options`.
|
||||||
|
* @template TValueType TypeScript type of this field.
|
||||||
|
* @template TRemoveFields Optional remove keys from current `Struct`. Should be a union of string literal types.
|
||||||
|
*/
|
||||||
|
export abstract class FieldDefinition<
|
||||||
|
TOptions = void,
|
||||||
|
TValueType = unknown,
|
||||||
|
TRemoveFields = never,
|
||||||
|
> {
|
||||||
|
public readonly options: TOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When `T` is a type initiated `FieldDefinition`,
|
||||||
|
* use `T['valueType']` to retrieve its `TValueType` type parameter
|
||||||
|
*/
|
||||||
|
public readonly valueType!: TValueType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When `T` is a type initiated `FieldDefinition`,
|
||||||
|
* use `T['removeFields']` to retrieve its `TRemoveFields` type parameter .
|
||||||
|
*/
|
||||||
|
public readonly removeFields!: TRemoveFields;
|
||||||
|
|
||||||
|
public constructor(options: TOptions) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When implemented in derived classes, returns the static size (or smallest size) of this field.
|
||||||
|
*
|
||||||
|
* Actual size can be retrieved from `FieldRuntimeValue#getSize`
|
||||||
|
*/
|
||||||
|
public abstract getSize(): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When implemented in derived classes, creates a `FieldRuntimeValue` for the current field definition.
|
||||||
|
*/
|
||||||
|
public abstract createValue(
|
||||||
|
options: Readonly<StructOptions>,
|
||||||
|
context: StructSerializationContext,
|
||||||
|
object: any
|
||||||
|
): FieldRuntimeValue;
|
||||||
|
}
|
|
@ -1,26 +0,0 @@
|
||||||
export enum BuiltInFieldType {
|
|
||||||
Number,
|
|
||||||
FixedLengthArrayBufferLike,
|
|
||||||
VariableLengthArrayBufferLike,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FieldDescriptorBaseOptions {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FieldDescriptorBase<
|
|
||||||
TName extends string = string,
|
|
||||||
TResultObject = {},
|
|
||||||
TInitObject = {},
|
|
||||||
TOptions extends FieldDescriptorBaseOptions = FieldDescriptorBaseOptions
|
|
||||||
> {
|
|
||||||
type: BuiltInFieldType | string;
|
|
||||||
|
|
||||||
name: TName;
|
|
||||||
|
|
||||||
options: TOptions;
|
|
||||||
|
|
||||||
resultObject?: TResultObject;
|
|
||||||
|
|
||||||
initObject?: TInitObject;
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
export * from './context';
|
export * from './context';
|
||||||
export * from './descriptor';
|
export * from './definition';
|
||||||
export * from './registry';
|
|
||||||
export * from './runtime-type';
|
|
||||||
export * from './runtime-value';
|
export * from './runtime-value';
|
||||||
|
export * from './runtime-object';
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
import { StructDeserializationContext, StructSerializationContext } from './context';
|
|
||||||
import { GlobalStructFieldRuntimeTypeRegistry, StructFieldRuntimeTypeRegistry } from './registry';
|
|
||||||
import { FieldRuntimeValue } from './runtime-type';
|
|
||||||
|
|
||||||
describe('Runtime', () => {
|
|
||||||
describe('StructFieldRuntimeTypeRegistry', () => {
|
|
||||||
it('should be able to get registered type', () => {
|
|
||||||
const registry = new StructFieldRuntimeTypeRegistry();
|
|
||||||
|
|
||||||
const type = 'mock';
|
|
||||||
const MockFieldRuntimeValue = class extends FieldRuntimeValue {
|
|
||||||
static getSize() { return 0; }
|
|
||||||
public deserialize(context: StructDeserializationContext): void | Promise<void> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
public get(): unknown {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
public set(value: unknown): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
registry.register(type, MockFieldRuntimeValue);
|
|
||||||
expect(registry.get(type)).toBe(MockFieldRuntimeValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error if same type been registered twice', () => {
|
|
||||||
const registry = new StructFieldRuntimeTypeRegistry();
|
|
||||||
|
|
||||||
const type = 'mock';
|
|
||||||
const MockFieldRuntimeValue = class extends FieldRuntimeValue {
|
|
||||||
static getSize() { return 0; }
|
|
||||||
public deserialize(context: StructDeserializationContext): void | Promise<void> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
public get(): unknown {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
public set(value: unknown): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
registry.register(type, MockFieldRuntimeValue);
|
|
||||||
expect(() => registry.register(type, MockFieldRuntimeValue)).toThrowError();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GlobalStructFieldRuntimeTypeRegistry', () => {
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(GlobalStructFieldRuntimeTypeRegistry).toBeInstanceOf(StructFieldRuntimeTypeRegistry);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,26 +0,0 @@
|
||||||
import type { BuiltInFieldType } from './descriptor';
|
|
||||||
import type { FieldRuntimeType } from './runtime-type';
|
|
||||||
|
|
||||||
export class StructFieldRuntimeTypeRegistry {
|
|
||||||
private store: Record<number | string, FieldRuntimeType> = {};
|
|
||||||
|
|
||||||
public get(
|
|
||||||
type: BuiltInFieldType | string
|
|
||||||
): FieldRuntimeType {
|
|
||||||
return this.store[type];
|
|
||||||
}
|
|
||||||
|
|
||||||
public register<
|
|
||||||
TConstructor extends FieldRuntimeType<any>
|
|
||||||
>(
|
|
||||||
type: BuiltInFieldType | string,
|
|
||||||
Constructor: TConstructor
|
|
||||||
): void {
|
|
||||||
if (type in this.store) {
|
|
||||||
throw new Error(`Struct field runtime type '${type}' has already been registered`);
|
|
||||||
}
|
|
||||||
this.store[type] = Constructor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GlobalStructFieldRuntimeTypeRegistry = new StructFieldRuntimeTypeRegistry();
|
|
38
packages/struct/src/basic/runtime-object.spec.ts
Normal file
38
packages/struct/src/basic/runtime-object.spec.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { createRuntimeObject, getRuntimeValue, setRuntimeValue } from './runtime-object';
|
||||||
|
|
||||||
|
describe('RuntimeObject', () => {
|
||||||
|
describe('createRuntimeObject', () => {
|
||||||
|
it('should create a special object', () => {
|
||||||
|
const object = createRuntimeObject();
|
||||||
|
expect(Object.getOwnPropertySymbols(object)).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getRuntimeValue', () => {
|
||||||
|
it('should return previously set value', () => {
|
||||||
|
const object = createRuntimeObject();
|
||||||
|
const field = 'foo';
|
||||||
|
const value = {} as any;
|
||||||
|
setRuntimeValue(object, field, value);
|
||||||
|
expect(getRuntimeValue(object, field)).toBe(value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setRuntimeValue', () => {
|
||||||
|
it('should define a proxy property to underlying `RuntimeValue`', () => {
|
||||||
|
const object = createRuntimeObject();
|
||||||
|
const field = 'foo';
|
||||||
|
const getter = jest.fn(() => 42);
|
||||||
|
const setter = jest.fn((value: number) => { });
|
||||||
|
const value = { get: getter, set: setter } as any;
|
||||||
|
setRuntimeValue(object, field, value);
|
||||||
|
|
||||||
|
expect((object as any)[field]).toBe(42);
|
||||||
|
expect(getter).toBeCalledTimes(1);
|
||||||
|
|
||||||
|
(object as any)[field] = 100;
|
||||||
|
expect(setter).toBeCalledTimes(1);
|
||||||
|
expect(setter).lastCalledWith(100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
36
packages/struct/src/basic/runtime-object.ts
Normal file
36
packages/struct/src/basic/runtime-object.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { FieldRuntimeValue } from './runtime-value';
|
||||||
|
|
||||||
|
const RuntimeValues = Symbol('RuntimeValues');
|
||||||
|
|
||||||
|
export interface RuntimeObject {
|
||||||
|
[RuntimeValues]: Record<PropertyKey, FieldRuntimeValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a new runtime object that can be used with `getRuntimeValue` and `setRuntimeValue` */
|
||||||
|
export function createRuntimeObject(): RuntimeObject {
|
||||||
|
return {
|
||||||
|
[RuntimeValues]: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the previously set `RuntimeValue` for specified `key` on `object` */
|
||||||
|
export function getRuntimeValue(object: RuntimeObject, key: PropertyKey): FieldRuntimeValue {
|
||||||
|
return object[RuntimeValues][key as any] as FieldRuntimeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the `RuntimeValue` for specified `key` on `object`,
|
||||||
|
* also sets up property accessors so reads/writes to `object`'s `key` will be forwarded to
|
||||||
|
* the underlying `RuntimeValue`
|
||||||
|
*/
|
||||||
|
export function setRuntimeValue(object: RuntimeObject, key: PropertyKey, runtimeValue: FieldRuntimeValue): void {
|
||||||
|
delete (object as any)[key];
|
||||||
|
|
||||||
|
object[RuntimeValues][key as any] = runtimeValue;
|
||||||
|
Object.defineProperty(object, key, {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get() { return runtimeValue.get(); },
|
||||||
|
set(value) { runtimeValue.set(value); },
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
import { StructDeserializationContext, StructSerializationContext } from './context';
|
|
||||||
import { FieldRuntimeValue } from './runtime-type';
|
|
||||||
|
|
||||||
describe('Runtime', () => {
|
|
||||||
describe('FieldRuntimeValue', () => {
|
|
||||||
it('`getSize` should return same value as static `getSize`', () => {
|
|
||||||
class MockFieldRuntimeValue extends FieldRuntimeValue {
|
|
||||||
static getSize() { return 42; }
|
|
||||||
public deserialize(context: StructDeserializationContext): void | Promise<void> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
public get(): unknown {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
public set(value: unknown): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const instance = new MockFieldRuntimeValue(undefined as any, undefined as any, undefined as any, undefined as any);
|
|
||||||
expect(instance.getSize()).toBe(42);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,45 +0,0 @@
|
||||||
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
|
||||||
import type { FieldDescriptorBase } from './descriptor';
|
|
||||||
|
|
||||||
export abstract class FieldRuntimeValue<TDescriptor extends FieldDescriptorBase = FieldDescriptorBase> {
|
|
||||||
public readonly descriptor: TDescriptor;
|
|
||||||
|
|
||||||
public readonly options: Readonly<StructOptions>;
|
|
||||||
|
|
||||||
public readonly context: StructSerializationContext;
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
descriptor: TDescriptor,
|
|
||||||
options: Readonly<StructOptions>,
|
|
||||||
context: StructSerializationContext,
|
|
||||||
object: any,
|
|
||||||
) {
|
|
||||||
this.descriptor = descriptor;
|
|
||||||
this.options = options;
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract deserialize(context: StructDeserializationContext, object: any): void | Promise<void>;
|
|
||||||
|
|
||||||
public getSize(): number {
|
|
||||||
const Constructor = Object.getPrototypeOf(this).constructor as FieldRuntimeType<TDescriptor>;
|
|
||||||
return Constructor.getSize(this.descriptor, this.options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract get(): unknown;
|
|
||||||
|
|
||||||
public abstract set(value: unknown): void;
|
|
||||||
|
|
||||||
public abstract serialize(dataView: DataView, offset: number, context: StructSerializationContext): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FieldRuntimeType<TDescriptor extends FieldDescriptorBase = FieldDescriptorBase> {
|
|
||||||
new(
|
|
||||||
descriptor: TDescriptor,
|
|
||||||
options: Readonly<StructOptions>,
|
|
||||||
context: StructSerializationContext,
|
|
||||||
object: any,
|
|
||||||
): FieldRuntimeValue<TDescriptor>;
|
|
||||||
|
|
||||||
getSize(descriptor: TDescriptor, options: Readonly<StructOptions>): number;
|
|
||||||
}
|
|
|
@ -1,34 +1,67 @@
|
||||||
import { createObjectWithRuntimeValues, getRuntimeValue, setRuntimeValue } from './runtime-value';
|
import { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
||||||
|
import { FieldDefinition } from './definition';
|
||||||
|
import { FieldRuntimeValue } from './runtime-value';
|
||||||
|
|
||||||
describe('Runtime', () => {
|
describe('FieldRuntimeValue', () => {
|
||||||
describe('RuntimeValue', () => {
|
describe('.constructor', () => {
|
||||||
it('`createObjectWithRuntimeValues` should create an object with symbol', () => {
|
it('should save parameters', () => {
|
||||||
const object = createObjectWithRuntimeValues();
|
class MockFieldRuntimeValue extends FieldRuntimeValue {
|
||||||
expect(Object.getOwnPropertySymbols(object)).toHaveLength(1);
|
public deserialize(context: StructDeserializationContext): void | Promise<void> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
public get(): unknown {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
public set(value: unknown): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const definition = 1 as any;
|
||||||
|
const options = 2 as any;
|
||||||
|
const context = 3 as any;
|
||||||
|
const object = 4 as any;
|
||||||
|
|
||||||
|
const fieldRuntimeValue = new MockFieldRuntimeValue(definition, options, context, object);
|
||||||
|
expect(fieldRuntimeValue).toHaveProperty('definition', definition);
|
||||||
|
expect(fieldRuntimeValue).toHaveProperty('options', options);
|
||||||
|
expect(fieldRuntimeValue).toHaveProperty('context', context);
|
||||||
|
expect(fieldRuntimeValue).toHaveProperty('object', object);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('`getRuntimeValue` should return previously set value', () => {
|
describe('#getSize', () => {
|
||||||
const object = createObjectWithRuntimeValues();
|
it('should return same value as definition\'s', () => {
|
||||||
const field = 'foo';
|
class MockFieldDefinition extends FieldDefinition {
|
||||||
const value = {} as any;
|
public getSize(): number {
|
||||||
setRuntimeValue(object, field, value);
|
return 42;
|
||||||
expect(getRuntimeValue(object, field)).toBe(value);
|
}
|
||||||
});
|
public createValue(options: Readonly<StructOptions>, context: StructSerializationContext, object: any): FieldRuntimeValue<FieldDefinition<any, any, any>> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
it('`setRuntimeValue` should define a proxy to underlying `RuntimeValue`', () => {
|
class MockFieldRuntimeValue extends FieldRuntimeValue {
|
||||||
const object = createObjectWithRuntimeValues();
|
public deserialize(context: StructDeserializationContext): void | Promise<void> {
|
||||||
const field = 'foo';
|
throw new Error('Method not implemented.');
|
||||||
const getter = jest.fn(() => 42);
|
}
|
||||||
const setter = jest.fn((value: number) => { });
|
public get(): unknown {
|
||||||
const value = { get: getter, set: setter } as any;
|
throw new Error('Method not implemented.');
|
||||||
setRuntimeValue(object, field, value);
|
}
|
||||||
|
public set(value: unknown): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
expect((object as any)[field]).toBe(42);
|
const fieldDefinition = new MockFieldDefinition();
|
||||||
expect(getter).toBeCalledTimes(1);
|
const fieldRuntimeValue = new MockFieldRuntimeValue(fieldDefinition, undefined as any, undefined as any, undefined as any);
|
||||||
|
expect(fieldRuntimeValue.getSize()).toBe(42);
|
||||||
(object as any)[field] = 100;
|
|
||||||
expect(setter).toBeCalledTimes(1);
|
|
||||||
expect(setter).lastCalledWith(100);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,28 +1,69 @@
|
||||||
import { FieldRuntimeValue } from './runtime-type';
|
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
|
||||||
|
import type { FieldDefinition } from './definition';
|
||||||
|
|
||||||
const RuntimeValues = Symbol('RuntimeValues');
|
/**
|
||||||
|
* Field runtime value manages one field of one `Struct` instance.
|
||||||
|
*
|
||||||
|
* If one `FieldDefinition` needs to change other field's semantics
|
||||||
|
* It can override other fields' `FieldRuntimeValue` in its own `FieldRuntimeValue`'s constructor
|
||||||
|
*/
|
||||||
|
export abstract class FieldRuntimeValue<
|
||||||
|
TDefinition extends FieldDefinition<any, any, any> = FieldDefinition<any, any, any>
|
||||||
|
> {
|
||||||
|
/** Gets the definition associated with this runtime value */
|
||||||
|
public readonly definition: TDefinition;
|
||||||
|
|
||||||
export interface WithRuntimeValues {
|
/** Gets the options of the associated `Struct` */
|
||||||
[RuntimeValues]: Record<string, FieldRuntimeValue>;
|
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 object: any;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
definition: TDefinition,
|
||||||
|
options: Readonly<StructOptions>,
|
||||||
|
context: StructSerializationContext,
|
||||||
|
object: any,
|
||||||
|
) {
|
||||||
|
this.definition = definition;
|
||||||
|
this.options = options;
|
||||||
|
this.context = context;
|
||||||
|
this.object = object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createObjectWithRuntimeValues(): WithRuntimeValues {
|
/** When implemented in derived classes, deserialize this field from the specified `context` */
|
||||||
const object = {} as any;
|
public abstract deserialize(
|
||||||
object[RuntimeValues] = {};
|
context: StructDeserializationContext
|
||||||
return object;
|
): void | Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the actual size of this field. By default, the return value of its `definition.getSize()`
|
||||||
|
*
|
||||||
|
* When overridden in derived classes, can have custom logic to calculate the actual size.
|
||||||
|
*/
|
||||||
|
public getSize(): number {
|
||||||
|
return this.definition.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRuntimeValue(object: WithRuntimeValues, field: string): FieldRuntimeValue {
|
/**
|
||||||
return (object as any)[RuntimeValues][field] as FieldRuntimeValue;
|
* When implemented in derived classes, returns the current value of this field
|
||||||
}
|
*/
|
||||||
|
public abstract get(): unknown;
|
||||||
|
|
||||||
export function setRuntimeValue(object: WithRuntimeValues, field: string, runtimeValue: FieldRuntimeValue): void {
|
/**
|
||||||
(object as any)[RuntimeValues][field] = runtimeValue;
|
* When implemented in derived classes, update the current value of this field
|
||||||
delete (object as any)[field];
|
*/
|
||||||
Object.defineProperty(object, field, {
|
public abstract set(value: unknown): void;
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
/**
|
||||||
get() { return runtimeValue.get(); },
|
* When implemented in derived classes, serializes this field into `dataView` at `offset`
|
||||||
set(value) { runtimeValue.set(value); },
|
*/
|
||||||
});
|
public abstract serialize(
|
||||||
|
dataView: DataView,
|
||||||
|
offset: number,
|
||||||
|
context: StructSerializationContext
|
||||||
|
): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export * from './runtime';
|
export * from './basic';
|
||||||
export * from './struct';
|
export * from './struct';
|
||||||
export { default as Struct } from './struct';
|
export { default as Struct } from './struct';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { BuiltInFieldType, createObjectWithRuntimeValues, FieldDescriptorBase, FieldDescriptorBaseOptions, FieldRuntimeValue, getRuntimeValue, GlobalStructFieldRuntimeTypeRegistry, setRuntimeValue, StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext } from './runtime';
|
import { createRuntimeObject, FieldDefinition, FieldRuntimeValue, getRuntimeValue, setRuntimeValue, StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext } from './basic';
|
||||||
import { ArrayBufferLikeFieldDescriptor, Evaluate, FixedLengthArrayBufferFieldDescriptor, Identity, KeysOfType, NumberFieldDescriptor, NumberFieldSubType, OmitNever, Overwrite, VariableLengthArrayBufferFieldDescriptor } from './types';
|
import { ArrayBufferFieldType, ArrayBufferLikeFieldType, Evaluate, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, Identity, KeysOfType, NumberFieldDefinition, NumberFieldType, Overwrite, StringFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the value type of the specified `Struct`
|
* Extract the value type of the specified `Struct`
|
||||||
|
@ -12,7 +12,7 @@ export type StructValueType<T> =
|
||||||
/**
|
/**
|
||||||
* Extract the init type of the specified `Struct`
|
* Extract the init type of the specified `Struct`
|
||||||
*/
|
*/
|
||||||
export type StructInitType<T extends Struct<object, object, object, unknown>> =
|
export type StructInitType<T extends Struct<any, object, object, any>> =
|
||||||
T extends { create(value: infer R, ...args: any): any; } ? Evaluate<R> : never;
|
T extends { create(value: infer R, ...args: any): any; } ? Evaluate<R> : never;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,35 +22,28 @@ type AddFieldDescriptor<
|
||||||
TValue extends object,
|
TValue extends object,
|
||||||
TInit extends object,
|
TInit extends object,
|
||||||
TExtra extends object,
|
TExtra extends object,
|
||||||
TAfterParsed,
|
TPostDeserialized,
|
||||||
TDescriptor extends FieldDescriptorBase> =
|
TFieldName extends PropertyKey,
|
||||||
|
TDefinition extends FieldDefinition<any, any, any>> =
|
||||||
Identity<Struct<
|
Identity<Struct<
|
||||||
// Merge two types
|
// Merge two types
|
||||||
Evaluate<
|
// Evaluate immediately to optimize editor hover tooltip
|
||||||
TValue &
|
Evaluate<TValue & Record<TFieldName, TDefinition['valueType']>>,
|
||||||
// `TDescriptor.resultObject` is optional, so remove `undefined` from its type
|
// There is no `Evaluate` here, because otherwise the type of a `Struct` with many fields
|
||||||
Exclude<TDescriptor['resultObject'], undefined>
|
// can become too complex for TypeScript to compute
|
||||||
>,
|
Evaluate<Omit<TInit, TDefinition['removeFields']> & Record<TFieldName, TDefinition['valueType']>>,
|
||||||
// `TDescriptor.initObject` signals removal of fields by setting its type to `never`
|
|
||||||
// I don't `Evaluate` here, because if I do, the result type will become too complex,
|
|
||||||
// and TypeScript will refuse to evaluate it.
|
|
||||||
OmitNever<
|
|
||||||
TInit &
|
|
||||||
// `TDescriptor.initObject` is optional, so remove `undefined` from its type
|
|
||||||
Exclude<TDescriptor['initObject'], undefined>
|
|
||||||
>,
|
|
||||||
TExtra,
|
TExtra,
|
||||||
TAfterParsed
|
TPostDeserialized
|
||||||
>>;
|
>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overload methods to add an array typed field
|
* Overload methods to add an array buffer like field
|
||||||
*/
|
*/
|
||||||
interface AddArrayBufferFieldDescriptor<
|
interface ArrayBufferLikeFieldCreator<
|
||||||
TValue extends object,
|
TValue extends object,
|
||||||
TInit extends object,
|
TInit extends object,
|
||||||
TExtra extends object,
|
TExtra extends object,
|
||||||
TAfterParsed
|
TPostDeserialized
|
||||||
> {
|
> {
|
||||||
/**
|
/**
|
||||||
* Append a fixed-length array to the `Struct`
|
* Append a fixed-length array to the `Struct`
|
||||||
|
@ -62,23 +55,23 @@ interface AddArrayBufferFieldDescriptor<
|
||||||
* For example, if this field is a string, you can declare it as a string enum or literal union.
|
* For example, if this field is a string, you can declare it as a string enum or literal union.
|
||||||
*/
|
*/
|
||||||
<
|
<
|
||||||
TName extends string,
|
TName extends PropertyKey,
|
||||||
TType extends ArrayBufferLikeFieldDescriptor.SubType,
|
TType extends ArrayBufferLikeFieldType,
|
||||||
TTypeScriptType extends ArrayBufferLikeFieldDescriptor.TypeScriptType<TType> = ArrayBufferLikeFieldDescriptor.TypeScriptType<TType>
|
TTypeScriptType = TType['valueType'],
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
type: TType,
|
type: TType,
|
||||||
options: FixedLengthArrayBufferFieldDescriptor.Options,
|
options: FixedLengthArrayBufferLikeFieldOptions,
|
||||||
typescriptType?: TTypeScriptType,
|
typescriptType?: TTypeScriptType,
|
||||||
): AddFieldDescriptor<
|
): AddFieldDescriptor<
|
||||||
TValue,
|
TValue,
|
||||||
TInit,
|
TInit,
|
||||||
TExtra,
|
TExtra,
|
||||||
TAfterParsed,
|
TPostDeserialized,
|
||||||
FixedLengthArrayBufferFieldDescriptor<
|
|
||||||
TName,
|
TName,
|
||||||
|
FixedLengthArrayBufferLikeFieldDefinition<
|
||||||
TType,
|
TType,
|
||||||
TTypeScriptType
|
FixedLengthArrayBufferLikeFieldOptions
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
@ -86,342 +79,350 @@ interface AddArrayBufferFieldDescriptor<
|
||||||
* Append a variable-length array to the `Struct`
|
* Append a variable-length array to the `Struct`
|
||||||
*/
|
*/
|
||||||
<
|
<
|
||||||
TName extends string,
|
TName extends PropertyKey,
|
||||||
TType extends ArrayBufferLikeFieldDescriptor.SubType,
|
TType extends ArrayBufferLikeFieldType,
|
||||||
TLengthField extends KeysOfType<TInit, number | string>,
|
TOptions extends VariableLengthArrayBufferLikeFieldOptions<TInit>,
|
||||||
TTypeScriptType = ArrayBufferLikeFieldDescriptor.TypeScriptType<TType>
|
TTypeScriptType = TType['valueType'],
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
type: TType,
|
type: TType,
|
||||||
options: VariableLengthArrayBufferFieldDescriptor.Options<TInit, TLengthField>,
|
options: TOptions,
|
||||||
typescriptType?: TTypeScriptType,
|
typescriptType?: TTypeScriptType,
|
||||||
): AddFieldDescriptor<
|
): AddFieldDescriptor<
|
||||||
TValue,
|
TValue,
|
||||||
TInit,
|
TInit,
|
||||||
TExtra,
|
TExtra,
|
||||||
TAfterParsed,
|
TPostDeserialized,
|
||||||
VariableLengthArrayBufferFieldDescriptor<
|
|
||||||
TName,
|
TName,
|
||||||
|
VariableLengthArrayBufferLikeFieldDefinition<
|
||||||
TType,
|
TType,
|
||||||
TInit,
|
TOptions
|
||||||
TLengthField,
|
|
||||||
TTypeScriptType
|
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddArrayBufferSubTypeFieldDescriptor<
|
/**
|
||||||
TResult extends object,
|
* Similar to `ArrayBufferLikeFieldCreator`, but bind to a `ArrayBufferLikeFieldType`
|
||||||
|
*/
|
||||||
|
interface ArrayBufferTypeFieldDefinitionCreator<
|
||||||
|
TValue extends object,
|
||||||
TInit extends object,
|
TInit extends object,
|
||||||
TExtra extends object,
|
TExtra extends object,
|
||||||
TAfterParsed,
|
TPostDeserialized,
|
||||||
TType extends ArrayBufferLikeFieldDescriptor.SubType
|
TType extends ArrayBufferLikeFieldType
|
||||||
> {
|
> {
|
||||||
<
|
<
|
||||||
TName extends string,
|
TName extends PropertyKey,
|
||||||
TTypeScriptType = ArrayBufferLikeFieldDescriptor.TypeScriptType<TType>
|
TTypeScriptType = TType['valueType'],
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
options: FixedLengthArrayBufferFieldDescriptor.Options,
|
options: FixedLengthArrayBufferLikeFieldOptions,
|
||||||
typescriptType?: TTypeScriptType,
|
typescriptType?: TTypeScriptType,
|
||||||
): AddFieldDescriptor<
|
): AddFieldDescriptor<
|
||||||
TResult,
|
TValue,
|
||||||
TInit,
|
TInit,
|
||||||
TExtra,
|
TExtra,
|
||||||
TAfterParsed,
|
TPostDeserialized,
|
||||||
FixedLengthArrayBufferFieldDescriptor<
|
|
||||||
TName,
|
TName,
|
||||||
|
FixedLengthArrayBufferLikeFieldDefinition<
|
||||||
TType,
|
TType,
|
||||||
TTypeScriptType
|
FixedLengthArrayBufferLikeFieldOptions
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
<
|
<
|
||||||
TName extends string,
|
TName extends PropertyKey,
|
||||||
TLengthField extends KeysOfType<TInit, number | string>,
|
TLengthField extends KeysOfType<TInit, number | string>,
|
||||||
TTypeScriptType = ArrayBufferLikeFieldDescriptor.TypeScriptType<TType>
|
TOptions extends VariableLengthArrayBufferLikeFieldOptions<TInit, TLengthField>,
|
||||||
|
TTypeScriptType = TType['valueType'],
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
options: VariableLengthArrayBufferFieldDescriptor.Options<TInit, TLengthField>,
|
options: TOptions,
|
||||||
_typescriptType?: TTypeScriptType,
|
typescriptType?: TTypeScriptType,
|
||||||
): AddFieldDescriptor<
|
): AddFieldDescriptor<
|
||||||
TResult,
|
TValue,
|
||||||
TInit,
|
TInit,
|
||||||
TExtra,
|
TExtra,
|
||||||
TAfterParsed,
|
TPostDeserialized,
|
||||||
VariableLengthArrayBufferFieldDescriptor<
|
|
||||||
TName,
|
TName,
|
||||||
|
VariableLengthArrayBufferLikeFieldDefinition<
|
||||||
TType,
|
TType,
|
||||||
TInit,
|
TOptions
|
||||||
TLengthField,
|
|
||||||
TTypeScriptType
|
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StructAfterParsed<TResult, TAfterParsed> =
|
export type StructPostDeserialized<TValue, TPostDeserialized> =
|
||||||
(this: TResult, object: TResult) => TAfterParsed;
|
(this: TValue, object: TValue) => TPostDeserialized;
|
||||||
|
|
||||||
export default class Struct<
|
export default class Struct<
|
||||||
TResult extends object = {},
|
TValue extends object = {},
|
||||||
TInit extends object = {},
|
TInit extends object = {},
|
||||||
TExtra extends object = {},
|
TExtra extends object = {},
|
||||||
TAfterParsed = undefined,
|
TPostDeserialized = undefined,
|
||||||
> {
|
> {
|
||||||
|
public readonly valueType!: TValue;
|
||||||
|
|
||||||
|
public readonly initType!: TInit;
|
||||||
|
|
||||||
|
public readonly extraType!: TExtra;
|
||||||
|
|
||||||
public readonly options: Readonly<StructOptions>;
|
public readonly options: Readonly<StructOptions>;
|
||||||
|
|
||||||
private _size = 0;
|
private _size = 0;
|
||||||
|
/**
|
||||||
|
* Get the static size (exclude fields that can change size at runtime)
|
||||||
|
*/
|
||||||
public get size() { return this._size; }
|
public get size() { return this._size; }
|
||||||
|
|
||||||
private fieldDescriptors: FieldDescriptorBase[] = [];
|
private _fields: [name: PropertyKey, definition: FieldDefinition<any, any, any>][] = [];
|
||||||
|
|
||||||
private _extra: PropertyDescriptorMap = {};
|
private _extra: PropertyDescriptorMap = {};
|
||||||
|
|
||||||
private _afterParsed?: StructAfterParsed<any, any>;
|
private _postDeserialized?: StructPostDeserialized<TValue, any>;
|
||||||
|
|
||||||
public constructor(options?: Partial<StructOptions>) {
|
public constructor(options?: Partial<StructOptions>) {
|
||||||
this.options = { ...StructDefaultOptions, ...options };
|
this.options = { ...StructDefaultOptions, ...options };
|
||||||
}
|
}
|
||||||
|
|
||||||
private clone(): Struct<any, any, any, any> {
|
public field<
|
||||||
const result = new Struct<any, any, any, any>(this.options);
|
TName extends PropertyKey,
|
||||||
result.fieldDescriptors = this.fieldDescriptors.slice();
|
TDefinition extends FieldDefinition<any, any, any>
|
||||||
result._size = this._size;
|
>(
|
||||||
result._extra = this._extra;
|
name: TName,
|
||||||
result._afterParsed = this._afterParsed;
|
definition: TDefinition,
|
||||||
return result;
|
): AddFieldDescriptor<
|
||||||
|
TValue,
|
||||||
|
TInit,
|
||||||
|
TExtra,
|
||||||
|
TPostDeserialized,
|
||||||
|
TName,
|
||||||
|
TDefinition
|
||||||
|
> {
|
||||||
|
this._fields.push([name, definition]);
|
||||||
|
|
||||||
|
const size = definition.getSize();
|
||||||
|
this._size += size;
|
||||||
|
|
||||||
|
// Force cast `this` to another type
|
||||||
|
return this as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
public field<TDescriptor extends FieldDescriptorBase>(
|
public fields<TOther extends Struct<any, any, any, any>>(
|
||||||
descriptor: TDescriptor,
|
struct: TOther
|
||||||
): AddFieldDescriptor<TResult, TInit, TExtra, TAfterParsed, TDescriptor> {
|
): Struct<
|
||||||
const result = this.clone();
|
TValue & TOther['valueType'],
|
||||||
result.fieldDescriptors.push(descriptor);
|
TInit & TOther['initType'],
|
||||||
|
TExtra & TOther['extraType'],
|
||||||
const Constructor = GlobalStructFieldRuntimeTypeRegistry.get(descriptor.type);
|
TPostDeserialized
|
||||||
const size = Constructor.getSize(descriptor, this.options);
|
> {
|
||||||
result._size += size;
|
for (const field of struct._fields) {
|
||||||
|
this._fields.push(field);
|
||||||
return result;
|
}
|
||||||
|
this._size += struct._size;
|
||||||
|
Object.assign(this._extra, struct._extra);
|
||||||
|
return this as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
private number<
|
private number<
|
||||||
TName extends string,
|
TName extends PropertyKey,
|
||||||
TType extends NumberFieldSubType = NumberFieldSubType,
|
TType extends NumberFieldType = NumberFieldType,
|
||||||
TTypeScriptType = TType['value']
|
TTypeScriptType = TType['valueType']
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
type: TType,
|
type: TType,
|
||||||
options: FieldDescriptorBaseOptions = {},
|
|
||||||
_typescriptType?: TTypeScriptType,
|
_typescriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.field<NumberFieldDescriptor<TName, TType, TTypeScriptType>>({
|
return this.field(
|
||||||
type: BuiltInFieldType.Number,
|
|
||||||
name,
|
name,
|
||||||
subType: type,
|
new NumberFieldDefinition(type, _typescriptType),
|
||||||
options,
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint8<
|
public uint8<
|
||||||
TName extends string,
|
TName extends PropertyKey,
|
||||||
TTypeScriptType = (typeof NumberFieldSubType)['Uint8']['value']
|
TTypeScriptType = (typeof NumberFieldType)['Uint8']['valueType']
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
options: FieldDescriptorBaseOptions = {},
|
|
||||||
_typescriptType?: TTypeScriptType,
|
_typescriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.number(
|
return this.number(
|
||||||
name,
|
name,
|
||||||
NumberFieldSubType.Uint8,
|
NumberFieldType.Uint8,
|
||||||
options,
|
|
||||||
_typescriptType
|
_typescriptType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint16<
|
public uint16<
|
||||||
TName extends string,
|
TName extends PropertyKey,
|
||||||
TTypeScriptType = (typeof NumberFieldSubType)['Uint16']['value']
|
TTypeScriptType = (typeof NumberFieldType)['Uint16']['valueType']
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
options: FieldDescriptorBaseOptions = {},
|
|
||||||
_typescriptType?: TTypeScriptType,
|
_typescriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.number(
|
return this.number(
|
||||||
name,
|
name,
|
||||||
NumberFieldSubType.Uint16,
|
NumberFieldType.Uint16,
|
||||||
options,
|
|
||||||
_typescriptType
|
_typescriptType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int32<
|
public int32<
|
||||||
TName extends string,
|
TName extends PropertyKey,
|
||||||
TTypeScriptType = (typeof NumberFieldSubType)['Int32']['value']
|
TTypeScriptType = (typeof NumberFieldType)['Int32']['valueType']
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
options: FieldDescriptorBaseOptions = {},
|
|
||||||
_typescriptType?: TTypeScriptType,
|
_typescriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.number(
|
return this.number(
|
||||||
name,
|
name,
|
||||||
NumberFieldSubType.Int32,
|
NumberFieldType.Int32,
|
||||||
options,
|
|
||||||
_typescriptType
|
_typescriptType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint32<
|
public uint32<
|
||||||
TName extends string,
|
TName extends PropertyKey,
|
||||||
TTypeScriptType = (typeof NumberFieldSubType)['Uint32']['value']
|
TTypeScriptType = (typeof NumberFieldType)['Uint32']['valueType']
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
options: FieldDescriptorBaseOptions = {},
|
|
||||||
typescriptType?: TTypeScriptType,
|
typescriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.number(
|
return this.number(
|
||||||
name,
|
name,
|
||||||
NumberFieldSubType.Uint32,
|
NumberFieldType.Uint32,
|
||||||
options,
|
|
||||||
typescriptType
|
typescriptType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int64<
|
public int64<
|
||||||
TName extends string,
|
TName extends PropertyKey,
|
||||||
TTypeScriptType = (typeof NumberFieldSubType)['Int64']['value']
|
TTypeScriptType = (typeof NumberFieldType)['Int64']['valueType']
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
options: FieldDescriptorBaseOptions = {},
|
|
||||||
_typescriptType?: TTypeScriptType,
|
_typescriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.number(
|
return this.number(
|
||||||
name,
|
name,
|
||||||
NumberFieldSubType.Int64,
|
NumberFieldType.Int64,
|
||||||
options,
|
|
||||||
_typescriptType
|
_typescriptType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint64<
|
public uint64<
|
||||||
TName extends string,
|
TName extends PropertyKey,
|
||||||
TTypeScriptType = (typeof NumberFieldSubType)['Uint64']['value']
|
TTypeScriptType = (typeof NumberFieldType)['Uint64']['valueType']
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
options: FieldDescriptorBaseOptions = {},
|
|
||||||
_typescriptType?: TTypeScriptType,
|
_typescriptType?: TTypeScriptType,
|
||||||
) {
|
) {
|
||||||
return this.number(
|
return this.number(
|
||||||
name,
|
name,
|
||||||
NumberFieldSubType.Uint64,
|
NumberFieldType.Uint64,
|
||||||
options,
|
|
||||||
_typescriptType
|
_typescriptType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private arrayBufferLike: AddArrayBufferFieldDescriptor<TResult, TInit, TExtra, TAfterParsed> = (
|
private arrayBufferLike: ArrayBufferLikeFieldCreator<TValue, TInit, TExtra, TPostDeserialized> = (
|
||||||
name: string,
|
name: PropertyKey,
|
||||||
type: ArrayBufferLikeFieldDescriptor.SubType,
|
type: ArrayBufferLikeFieldType,
|
||||||
options: FixedLengthArrayBufferFieldDescriptor.Options | VariableLengthArrayBufferFieldDescriptor.Options
|
options: FixedLengthArrayBufferLikeFieldOptions | VariableLengthArrayBufferLikeFieldOptions
|
||||||
): Struct<any, any, any, any> => {
|
): any => {
|
||||||
if ('length' in options) {
|
if ('length' in options) {
|
||||||
return this.field<FixedLengthArrayBufferFieldDescriptor>({
|
return this.field(
|
||||||
type: BuiltInFieldType.FixedLengthArrayBufferLike,
|
|
||||||
name,
|
name,
|
||||||
subType: type,
|
new FixedLengthArrayBufferLikeFieldDefinition(type, options),
|
||||||
options: options,
|
);
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return this.field<VariableLengthArrayBufferFieldDescriptor>({
|
return this.field(
|
||||||
type: BuiltInFieldType.VariableLengthArrayBufferLike,
|
|
||||||
name,
|
name,
|
||||||
subType: type,
|
new VariableLengthArrayBufferLikeFieldDefinition(type, options),
|
||||||
options: options,
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public arrayBuffer: AddArrayBufferSubTypeFieldDescriptor<
|
public arrayBuffer: ArrayBufferTypeFieldDefinitionCreator<
|
||||||
TResult,
|
TValue,
|
||||||
TInit,
|
TInit,
|
||||||
TExtra,
|
TExtra,
|
||||||
TAfterParsed,
|
TPostDeserialized,
|
||||||
ArrayBufferLikeFieldDescriptor.SubType.ArrayBuffer
|
ArrayBufferFieldType
|
||||||
> = <TName extends string>(
|
> = (
|
||||||
name: TName,
|
name: PropertyKey,
|
||||||
options: any
|
options: any
|
||||||
) => {
|
): any => {
|
||||||
return this.arrayBufferLike(name, ArrayBufferLikeFieldDescriptor.SubType.ArrayBuffer, options);
|
return this.arrayBufferLike(name, ArrayBufferFieldType.instance, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
public string: AddArrayBufferSubTypeFieldDescriptor<
|
public string: ArrayBufferTypeFieldDefinitionCreator<
|
||||||
TResult,
|
TValue,
|
||||||
TInit,
|
TInit,
|
||||||
TExtra,
|
TExtra,
|
||||||
TAfterParsed,
|
TPostDeserialized,
|
||||||
ArrayBufferLikeFieldDescriptor.SubType.String
|
StringFieldType
|
||||||
> = <TName extends string>(
|
> = (
|
||||||
name: TName,
|
name: PropertyKey,
|
||||||
options: any
|
options: any
|
||||||
) => {
|
): any => {
|
||||||
return this.arrayBufferLike(name, ArrayBufferLikeFieldDescriptor.SubType.String, options);
|
return this.arrayBufferLike(name, StringFieldType.instance, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
public extra<TValue extends Record<
|
public extra<T extends Record<
|
||||||
|
// This trick disallows any keys that are already in `TValue`
|
||||||
Exclude<
|
Exclude<
|
||||||
keyof TValue,
|
keyof T,
|
||||||
Exclude<keyof TValue, keyof TResult>>,
|
Exclude<keyof T, keyof TValue>
|
||||||
|
>,
|
||||||
never
|
never
|
||||||
>>(
|
>>(
|
||||||
value: TValue & ThisType<Overwrite<Overwrite<TExtra, TValue>, TResult>>
|
value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TValue>>
|
||||||
): Struct<
|
): Struct<
|
||||||
TResult,
|
TValue,
|
||||||
TInit,
|
TInit,
|
||||||
Overwrite<TExtra, TValue>,
|
Overwrite<TExtra, T>,
|
||||||
TAfterParsed
|
TPostDeserialized
|
||||||
> {
|
> {
|
||||||
const result = this.clone();
|
Object.assign(this._extra, Object.getOwnPropertyDescriptors(value));
|
||||||
result._extra = { ...result._extra, ...Object.getOwnPropertyDescriptors(value) };
|
return this as any;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public afterParsed(
|
/**
|
||||||
callback: StructAfterParsed<TResult, never>
|
*
|
||||||
): Struct<TResult, TInit, TExtra, never>;
|
*/
|
||||||
public afterParsed(
|
public postDeserialize(
|
||||||
callback?: StructAfterParsed<TResult, void>
|
callback: StructPostDeserialized<TValue, never>
|
||||||
): Struct<TResult, TInit, TExtra, undefined>;
|
): Struct<TValue, TInit, TExtra, never>;
|
||||||
public afterParsed<TAfterParsed>(
|
public postDeserialize(
|
||||||
callback?: StructAfterParsed<TResult, TAfterParsed>
|
callback?: StructPostDeserialized<TValue, void>
|
||||||
): Struct<TResult, TInit, TExtra, TAfterParsed>;
|
): Struct<TValue, TInit, TExtra, undefined>;
|
||||||
public afterParsed(
|
public postDeserialize<TPostSerialize>(
|
||||||
callback?: StructAfterParsed<TResult, any>
|
callback?: StructPostDeserialized<TValue, TPostSerialize>
|
||||||
|
): Struct<TValue, TInit, TExtra, TPostSerialize>;
|
||||||
|
public postDeserialize(
|
||||||
|
callback?: StructPostDeserialized<TValue, any>
|
||||||
) {
|
) {
|
||||||
const result = this.clone();
|
this._postDeserialized = callback;
|
||||||
result._afterParsed = callback;
|
return this as any;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeObject(context: StructSerializationContext) {
|
private initializeObject(context: StructSerializationContext) {
|
||||||
const object = createObjectWithRuntimeValues();
|
const object = createRuntimeObject();
|
||||||
Object.defineProperties(object, this._extra);
|
Object.defineProperties(object, this._extra);
|
||||||
|
|
||||||
for (const descriptor of this.fieldDescriptors) {
|
for (const [name, definition] of this._fields) {
|
||||||
const Constructor = GlobalStructFieldRuntimeTypeRegistry.get(descriptor.type);
|
const runtimeValue = definition.createValue(this.options, context, object);
|
||||||
|
setRuntimeValue(object, name, runtimeValue);
|
||||||
const runtimeValue = new Constructor(descriptor, this.options, context, object);
|
|
||||||
setRuntimeValue(object, descriptor.name, runtimeValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
public create(init: TInit, context: StructSerializationContext): Overwrite<TExtra, TResult> {
|
public create(init: TInit, context: StructSerializationContext): Overwrite<TExtra, TValue> {
|
||||||
const object = this.initializeObject(context);
|
const object = this.initializeObject(context);
|
||||||
|
|
||||||
for (const { name: fieldName } of this.fieldDescriptors) {
|
for (const [name] of this._fields) {
|
||||||
const runtimeValue = getRuntimeValue(object, fieldName);
|
const runtimeValue = getRuntimeValue(object, name);
|
||||||
runtimeValue.set((init as any)[fieldName]);
|
runtimeValue.set((init as any)[name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return object as any;
|
return object as any;
|
||||||
|
@ -429,16 +430,16 @@ export default class Struct<
|
||||||
|
|
||||||
public async deserialize(
|
public async deserialize(
|
||||||
context: StructDeserializationContext
|
context: StructDeserializationContext
|
||||||
): Promise<TAfterParsed extends undefined ? Overwrite<TExtra, TResult> : TAfterParsed> {
|
): Promise<TPostDeserialized extends undefined ? Overwrite<TExtra, TValue> : TPostDeserialized> {
|
||||||
const object = this.initializeObject(context);
|
const object = this.initializeObject(context);
|
||||||
|
|
||||||
for (const { name: fieldName } of this.fieldDescriptors) {
|
for (const [name] of this._fields) {
|
||||||
const runtimeValue = getRuntimeValue(object, fieldName);
|
const runtimeValue = getRuntimeValue(object, name);
|
||||||
await runtimeValue.deserialize(context, object);
|
await runtimeValue.deserialize(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._afterParsed) {
|
if (this._postDeserialized) {
|
||||||
const result = this._afterParsed.call(object, object);
|
const result = this._postDeserialized.call(object as TValue, object as TValue);
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -453,8 +454,8 @@ export default class Struct<
|
||||||
let structSize = 0;
|
let structSize = 0;
|
||||||
const fieldsInfo: { runtimeValue: FieldRuntimeValue, size: number; }[] = [];
|
const fieldsInfo: { runtimeValue: FieldRuntimeValue, size: number; }[] = [];
|
||||||
|
|
||||||
for (const { name: fieldName } of this.fieldDescriptors) {
|
for (const [name] of this._fields) {
|
||||||
const runtimeValue = getRuntimeValue(object, fieldName);
|
const runtimeValue = getRuntimeValue(object, name);
|
||||||
const size = runtimeValue.getSize();
|
const size = runtimeValue.getSize();
|
||||||
fieldsInfo.push({ runtimeValue, size });
|
fieldsInfo.push({ runtimeValue, size });
|
||||||
structSize += size;
|
structSize += size;
|
||||||
|
|
79
packages/struct/src/types/array-buffer.spec.ts
Normal file
79
packages/struct/src/types/array-buffer.spec.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import { StructDeserializationContext, StructSerializationContext } from '../basic';
|
||||||
|
import { ArrayBufferFieldType, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer';
|
||||||
|
|
||||||
|
describe('Types', () => {
|
||||||
|
describe('ArrayBufferFieldType', () => {
|
||||||
|
it('should have a static instance', () => {
|
||||||
|
expect(ArrayBufferFieldType.instance).toBeInstanceOf(ArrayBufferFieldType);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`toArrayBuffer` should return the same `ArrayBuffer`', () => {
|
||||||
|
const arrayBuffer = new ArrayBuffer(10);
|
||||||
|
expect(ArrayBufferFieldType.instance.toArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`fromArrayBuffer` should return the same `ArrayBuffer`', () => {
|
||||||
|
const arrayBuffer = new ArrayBuffer(10);
|
||||||
|
expect(ArrayBufferFieldType.instance.fromArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`getSize` should return the `byteLength` of the `ArrayBuffer`', () => {
|
||||||
|
const arrayBuffer = new ArrayBuffer(10);
|
||||||
|
expect(ArrayBufferFieldType.instance.getSize(arrayBuffer)).toBe(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Uint8ClampedArrayFieldType', () => {
|
||||||
|
it('should have a static instance', () => {
|
||||||
|
expect(Uint8ClampedArrayFieldType.instance).toBeInstanceOf(Uint8ClampedArrayFieldType);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`toArrayBuffer` should return its `buffer`', () => {
|
||||||
|
const array = new Uint8ClampedArray(10);
|
||||||
|
const buffer = array.buffer;
|
||||||
|
expect(Uint8ClampedArrayFieldType.instance.toArrayBuffer(array)).toBe(buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`fromArrayBuffer` should return a view of the `ArrayBuffer`', () => {
|
||||||
|
const arrayBuffer = new ArrayBuffer(10);
|
||||||
|
const array = Uint8ClampedArrayFieldType.instance.fromArrayBuffer(arrayBuffer);
|
||||||
|
expect(array).toHaveProperty('buffer', arrayBuffer);
|
||||||
|
expect(array).toHaveProperty('byteOffset', 0);
|
||||||
|
expect(array).toHaveProperty('byteLength', 10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('StringFieldType', () => {
|
||||||
|
it('should have a static instance', () => {
|
||||||
|
expect(StringFieldType.instance).toBeInstanceOf(StringFieldType);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`toArrayBuffer` should return the decoded string', () => {
|
||||||
|
const text = 'foo';
|
||||||
|
const arrayBuffer = Buffer.from(text, 'utf-8');
|
||||||
|
const context: StructSerializationContext = {
|
||||||
|
encodeUtf8(input) {
|
||||||
|
return Buffer.from(input, 'utf-8');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(StringFieldType.instance.toArrayBuffer(text, context)).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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,135 +1,159 @@
|
||||||
import { FieldDescriptorBase, FieldDescriptorBaseOptions, FieldRuntimeValue, StructDeserializationContext, StructSerializationContext } from '../runtime';
|
import { FieldDefinition, FieldRuntimeValue, StructDeserializationContext, StructSerializationContext } from '../basic';
|
||||||
|
|
||||||
export namespace ArrayBufferLikeFieldDescriptor {
|
/**
|
||||||
export enum SubType {
|
* Base class for all types that
|
||||||
ArrayBuffer,
|
* can be converted from an ArrayBuffer when deserialized,
|
||||||
Uint8ClampedArray,
|
* and need to be converted to an ArrayBuffer when serializing
|
||||||
String,
|
*
|
||||||
|
* @template TType The actual TypeScript type of this type
|
||||||
|
* @template TTypeScriptType Optional another type (should be compatible with `TType`)
|
||||||
|
* specified by user when creating field definitions.
|
||||||
|
*/
|
||||||
|
export abstract class ArrayBufferLikeFieldType<TType = unknown, TTypeScriptType = TType> {
|
||||||
|
public readonly valueType!: TTypeScriptType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When implemented in derived classes, converts the type-specific `value` to an `ArrayBuffer`
|
||||||
|
*
|
||||||
|
* This function should be "pure", i.e.,
|
||||||
|
* same `value` should always be converted to `ArrayBuffer`s that have same content.
|
||||||
|
*/
|
||||||
|
public abstract toArrayBuffer(value: TType, context: StructSerializationContext): ArrayBuffer;
|
||||||
|
|
||||||
|
/** When implemented in derived classes, converts the `ArrayBuffer` to a type-specific value */
|
||||||
|
public abstract fromArrayBuffer(arrayBuffer: ArrayBuffer, context: StructDeserializationContext): TType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When implemented in derived classes, gets the size in byte of the type-specific `value`.
|
||||||
|
*
|
||||||
|
* If the size can't be calculated without first converting the `value` back to an `ArrayBuffer`,
|
||||||
|
* implementer should returns `-1` so the caller will get its size by first converting it to
|
||||||
|
* an `ArrayBuffer` (and cache the result).
|
||||||
|
*/
|
||||||
|
public abstract getSize(value: TType): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TypeScriptType<TType extends SubType = SubType> =
|
/** An ArrayBufferLike type that's actually an `ArrayBuffer` */
|
||||||
TType extends SubType.ArrayBuffer ? ArrayBuffer :
|
export class ArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer> {
|
||||||
TType extends SubType.Uint8ClampedArray ? Uint8ClampedArray :
|
public static readonly instance = new ArrayBufferFieldType();
|
||||||
TType extends SubType.String ? string :
|
|
||||||
never;
|
protected constructor() {
|
||||||
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ArrayBufferLikeFieldDescriptor<
|
public toArrayBuffer(value: ArrayBuffer): ArrayBuffer {
|
||||||
TName extends string = string,
|
return value;
|
||||||
TType extends ArrayBufferLikeFieldDescriptor.SubType = ArrayBufferLikeFieldDescriptor.SubType,
|
}
|
||||||
TResultObject = {},
|
|
||||||
TInitObject = {},
|
public fromArrayBuffer(arrayBuffer: ArrayBuffer): ArrayBuffer {
|
||||||
TOptions extends FieldDescriptorBaseOptions = FieldDescriptorBaseOptions
|
return arrayBuffer;
|
||||||
> extends FieldDescriptorBase<
|
}
|
||||||
TName,
|
|
||||||
TResultObject,
|
public getSize(value: ArrayBuffer): number {
|
||||||
TInitObject,
|
return value.byteLength;
|
||||||
TOptions
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Am ArrayBufferLike type that converts to/from the `ArrayBuffer` from/to a `Uint8ClampedArray` */
|
||||||
|
export class Uint8ClampedArrayFieldType
|
||||||
|
extends ArrayBufferLikeFieldType<Uint8ClampedArray, Uint8ClampedArray> {
|
||||||
|
public static readonly instance = new Uint8ClampedArrayFieldType();
|
||||||
|
|
||||||
|
protected constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public toArrayBuffer(value: Uint8ClampedArray): ArrayBuffer {
|
||||||
|
return value.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public fromArrayBuffer(arrayBuffer: ArrayBuffer): Uint8ClampedArray {
|
||||||
|
return new Uint8ClampedArray(arrayBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSize(value: Uint8ClampedArray): number {
|
||||||
|
return value.byteLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Am ArrayBufferLike type that converts to/from the `ArrayBuffer` from/to a `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 fromArrayBuffer(arrayBuffer: ArrayBuffer, context: StructDeserializationContext): string {
|
||||||
|
return context.decodeUtf8(arrayBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSize(): number {
|
||||||
|
// Return `-1`, so `ArrayBufferLikeFieldDefinition` will
|
||||||
|
// convert this `value` into an `ArrayBuffer` (and cache the result),
|
||||||
|
// Then get the size from that `ArrayBuffer`
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class ArrayBufferLikeFieldDefinition<
|
||||||
|
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
|
||||||
|
TOptions = void,
|
||||||
|
TRemoveFields = never,
|
||||||
|
> extends FieldDefinition<
|
||||||
|
TOptions,
|
||||||
|
TType['valueType'],
|
||||||
|
TRemoveFields
|
||||||
>{
|
>{
|
||||||
subType: TType;
|
public readonly type: TType;
|
||||||
|
|
||||||
|
public constructor(type: TType, options: TOptions) {
|
||||||
|
super(options);
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EmptyArrayBuffer = new ArrayBuffer(0);
|
const EmptyArrayBuffer = new ArrayBuffer(0);
|
||||||
const EmptyUint8ClampedArray = new Uint8ClampedArray(EmptyArrayBuffer);
|
|
||||||
const EmptyString = '';
|
|
||||||
|
|
||||||
export abstract class ArrayBufferLikeFieldRuntimeValue<TDescriptor extends ArrayBufferLikeFieldDescriptor>
|
export abstract class ArrayBufferLikeFieldRuntimeValue<
|
||||||
extends FieldRuntimeValue<TDescriptor> {
|
TDefinition extends ArrayBufferLikeFieldDefinition<any, any, any>,
|
||||||
|
> extends FieldRuntimeValue<TDefinition> {
|
||||||
protected arrayBuffer: ArrayBuffer | undefined;
|
protected arrayBuffer: ArrayBuffer | undefined;
|
||||||
|
|
||||||
protected typedArray: ArrayBufferView | undefined;
|
protected typedValue: unknown;
|
||||||
|
|
||||||
protected string: string | undefined;
|
protected getDeserializeSize(): number {
|
||||||
|
|
||||||
protected getDeserializeSize(object: any): number {
|
|
||||||
return this.getSize();
|
return this.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deserialize(context: StructDeserializationContext, object: any): Promise<void> {
|
public async deserialize(context: StructDeserializationContext): Promise<void> {
|
||||||
const size = this.getDeserializeSize(object);
|
const size = this.getDeserializeSize();
|
||||||
|
|
||||||
this.arrayBuffer = undefined;
|
this.arrayBuffer = undefined;
|
||||||
this.typedArray = undefined;
|
this.typedValue = undefined;
|
||||||
this.string = undefined;
|
|
||||||
|
|
||||||
if (size === 0) {
|
if (size === 0) {
|
||||||
this.arrayBuffer = EmptyArrayBuffer;
|
this.arrayBuffer = EmptyArrayBuffer;
|
||||||
switch (this.descriptor.subType) {
|
} else {
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.Uint8ClampedArray:
|
this.arrayBuffer = await context.read(size);
|
||||||
this.typedArray = EmptyUint8ClampedArray;
|
|
||||||
break;
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.String:
|
|
||||||
this.string = EmptyString;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await context.read(size);
|
this.typedValue = this.definition.type.fromArrayBuffer(this.arrayBuffer, context);
|
||||||
switch (this.descriptor.subType) {
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.ArrayBuffer:
|
|
||||||
this.arrayBuffer = buffer;
|
|
||||||
break;
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.Uint8ClampedArray:
|
|
||||||
this.arrayBuffer = buffer;
|
|
||||||
this.typedArray = new Uint8ClampedArray(buffer);
|
|
||||||
break;
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.String:
|
|
||||||
this.arrayBuffer = buffer;
|
|
||||||
this.string = context.decodeUtf8(buffer);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error('Unknown type');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get(): unknown {
|
public get(): unknown {
|
||||||
switch (this.descriptor.subType) {
|
return this.typedValue;
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.ArrayBuffer:
|
|
||||||
return this.arrayBuffer;
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.Uint8ClampedArray:
|
|
||||||
return this.typedArray;
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.String:
|
|
||||||
return this.string;
|
|
||||||
default:
|
|
||||||
throw new Error('Unknown type');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public set(value: unknown): void {
|
public set(value: unknown): void {
|
||||||
|
this.typedValue = value;
|
||||||
this.arrayBuffer = undefined;
|
this.arrayBuffer = undefined;
|
||||||
this.typedArray = undefined;
|
|
||||||
this.string = undefined;
|
|
||||||
|
|
||||||
switch (this.descriptor.subType) {
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.ArrayBuffer:
|
|
||||||
this.arrayBuffer = value as ArrayBuffer;
|
|
||||||
break;
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.Uint8ClampedArray:
|
|
||||||
this.typedArray = value as Uint8ClampedArray;
|
|
||||||
this.arrayBuffer = this.typedArray.buffer;
|
|
||||||
break;
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.String:
|
|
||||||
this.string = value as string;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error('Unknown type');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
|
||||||
if (this.descriptor.subType !== ArrayBufferLikeFieldDescriptor.SubType.ArrayBuffer &&
|
if (!this.arrayBuffer) {
|
||||||
!this.arrayBuffer) {
|
this.arrayBuffer = this.definition.type.toArrayBuffer(this.typedValue, context);
|
||||||
switch (this.descriptor.subType) {
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.Uint8ClampedArray:
|
|
||||||
this.arrayBuffer = this.typedArray!.buffer;
|
|
||||||
break;
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.String:
|
|
||||||
this.arrayBuffer = context.encodeUtf8(this.string!);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error('Unknown type');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new Uint8Array(dataView.buffer)
|
new Uint8Array(dataView.buffer)
|
||||||
|
|
|
@ -1,37 +1,33 @@
|
||||||
import { BuiltInFieldType, FieldDescriptorBaseOptions, GlobalStructFieldRuntimeTypeRegistry } from '../runtime';
|
import { StructOptions, StructSerializationContext } from '../basic';
|
||||||
import { ArrayBufferLikeFieldDescriptor, ArrayBufferLikeFieldRuntimeValue } from './array-buffer';
|
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldRuntimeValue, ArrayBufferLikeFieldType } from './array-buffer';
|
||||||
|
|
||||||
export namespace FixedLengthArrayBufferFieldDescriptor {
|
export interface FixedLengthArrayBufferLikeFieldOptions {
|
||||||
export interface Options extends FieldDescriptorBaseOptions {
|
|
||||||
length: number;
|
length: number;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export interface FixedLengthArrayBufferFieldDescriptor<
|
export class FixedLengthArrayBufferLikeFieldDefinition<
|
||||||
TName extends string = string,
|
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
|
||||||
TType extends ArrayBufferLikeFieldDescriptor.SubType = ArrayBufferLikeFieldDescriptor.SubType,
|
TOptions extends FixedLengthArrayBufferLikeFieldOptions = FixedLengthArrayBufferLikeFieldOptions,
|
||||||
TTypeScriptType = ArrayBufferLikeFieldDescriptor.TypeScriptType<TType>,
|
> extends ArrayBufferLikeFieldDefinition<
|
||||||
TOptions extends FixedLengthArrayBufferFieldDescriptor.Options = FixedLengthArrayBufferFieldDescriptor.Options
|
|
||||||
> extends ArrayBufferLikeFieldDescriptor<
|
|
||||||
TName,
|
|
||||||
TType,
|
TType,
|
||||||
Record<TName, TTypeScriptType>,
|
|
||||||
Record<TName, TTypeScriptType>,
|
|
||||||
TOptions
|
TOptions
|
||||||
> {
|
> {
|
||||||
type: BuiltInFieldType.FixedLengthArrayBufferLike;
|
public getSize(): number {
|
||||||
|
return this.options.length;
|
||||||
|
}
|
||||||
|
|
||||||
options: TOptions;
|
public createValue(
|
||||||
|
options: Readonly<StructOptions>,
|
||||||
|
context: StructSerializationContext,
|
||||||
|
object: any
|
||||||
|
): FixedLengthArrayBufferFieldRuntimeValue {
|
||||||
|
return new FixedLengthArrayBufferFieldRuntimeValue(this, options, context, object);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class FixedLengthArrayBufferFieldRuntimeValue
|
class FixedLengthArrayBufferFieldRuntimeValue
|
||||||
extends ArrayBufferLikeFieldRuntimeValue<FixedLengthArrayBufferFieldDescriptor>{
|
extends ArrayBufferLikeFieldRuntimeValue<FixedLengthArrayBufferLikeFieldDefinition>{
|
||||||
public static getSize(descriptor: FixedLengthArrayBufferFieldDescriptor) {
|
public static getSize(descriptor: FixedLengthArrayBufferLikeFieldDefinition) {
|
||||||
return descriptor.options.length;
|
return descriptor.options.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalStructFieldRuntimeTypeRegistry.register(
|
|
||||||
BuiltInFieldType.FixedLengthArrayBufferLike,
|
|
||||||
FixedLengthArrayBufferFieldRuntimeValue,
|
|
||||||
);
|
|
||||||
|
|
|
@ -1,49 +1,49 @@
|
||||||
import { StructDefaultOptions, StructDeserializationContext } from '../runtime';
|
import { StructDefaultOptions, StructDeserializationContext } from '../basic';
|
||||||
import { NumberFieldSubType, NumberFieldRuntimeValue } from './number';
|
import { NumberFieldDefinition, NumberFieldRuntimeValue, NumberFieldType } from './number';
|
||||||
|
|
||||||
describe('Types', () => {
|
describe('Types', () => {
|
||||||
describe('Number', () => {
|
describe('Number', () => {
|
||||||
describe('NumberFieldSubType', () => {
|
describe('NumberFieldType', () => {
|
||||||
it('Uint8 validation', () => {
|
it('Uint8 validation', () => {
|
||||||
const key = 'Uint8';
|
const key = 'Uint8';
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('size', 1);
|
expect(NumberFieldType[key]).toHaveProperty('size', 1);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewGetter', 'get' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewGetter', 'get' + key);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewSetter', 'set' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewSetter', 'set' + key);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Uint16 validation', () => {
|
it('Uint16 validation', () => {
|
||||||
const key = 'Uint16';
|
const key = 'Uint16';
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('size', 2);
|
expect(NumberFieldType[key]).toHaveProperty('size', 2);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewGetter', 'get' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewGetter', 'get' + key);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewSetter', 'set' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewSetter', 'set' + key);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Int32 validation', () => {
|
it('Int32 validation', () => {
|
||||||
const key = 'Int32';
|
const key = 'Int32';
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('size', 4);
|
expect(NumberFieldType[key]).toHaveProperty('size', 4);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewGetter', 'get' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewGetter', 'get' + key);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewSetter', 'set' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewSetter', 'set' + key);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Uint32 validation', () => {
|
it('Uint32 validation', () => {
|
||||||
const key = 'Uint32';
|
const key = 'Uint32';
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('size', 4);
|
expect(NumberFieldType[key]).toHaveProperty('size', 4);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewGetter', 'get' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewGetter', 'get' + key);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewSetter', 'set' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewSetter', 'set' + key);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Int64 validation', () => {
|
it('Int64 validation', () => {
|
||||||
const key = 'Int64';
|
const key = 'Int64';
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('size', 8);
|
expect(NumberFieldType[key]).toHaveProperty('size', 8);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewGetter', 'getBig' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewGetter', 'getBig' + key);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewSetter', 'setBig' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewSetter', 'setBig' + key);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Uint64 validation', () => {
|
it('Uint64 validation', () => {
|
||||||
const key = 'Uint64';
|
const key = 'Uint64';
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('size', 8);
|
expect(NumberFieldType[key]).toHaveProperty('size', 8);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewGetter', 'getBig' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewGetter', 'getBig' + key);
|
||||||
expect(NumberFieldSubType[key]).toHaveProperty('dataViewSetter', 'setBig' + key);
|
expect(NumberFieldType[key]).toHaveProperty('dataViewSetter', 'setBig' + key);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ describe('Types', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const value = new NumberFieldRuntimeValue(
|
const value = new NumberFieldRuntimeValue(
|
||||||
{ subType: NumberFieldSubType.Uint8 } as any,
|
new NumberFieldDefinition(NumberFieldType.Uint8),
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
undefined as any,
|
undefined as any,
|
||||||
undefined as any,
|
undefined as any,
|
||||||
|
@ -66,7 +66,7 @@ describe('Types', () => {
|
||||||
|
|
||||||
expect(value.get()).toBe(1);
|
expect(value.get()).toBe(1);
|
||||||
expect(read).toBeCalledTimes(1);
|
expect(read).toBeCalledTimes(1);
|
||||||
expect(read).lastCalledWith(NumberFieldSubType.Uint8.size);
|
expect(read).lastCalledWith(NumberFieldType.Uint8.size);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should deserialize Uint16', async () => {
|
it('should deserialize Uint16', async () => {
|
||||||
|
@ -78,7 +78,7 @@ describe('Types', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const value = new NumberFieldRuntimeValue(
|
const value = new NumberFieldRuntimeValue(
|
||||||
{ subType: NumberFieldSubType.Uint16 } as any,
|
new NumberFieldDefinition(NumberFieldType.Uint16),
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
undefined as any,
|
undefined as any,
|
||||||
undefined as any,
|
undefined as any,
|
||||||
|
@ -87,7 +87,7 @@ describe('Types', () => {
|
||||||
|
|
||||||
expect(value.get()).toBe((1 << 8) | 2);
|
expect(value.get()).toBe((1 << 8) | 2);
|
||||||
expect(read).toBeCalledTimes(1);
|
expect(read).toBeCalledTimes(1);
|
||||||
expect(read).lastCalledWith(NumberFieldSubType.Uint16.size);
|
expect(read).lastCalledWith(NumberFieldType.Uint16.size);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should deserialize Uint16LE', async () => {
|
it('should deserialize Uint16LE', async () => {
|
||||||
|
@ -99,7 +99,7 @@ describe('Types', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const value = new NumberFieldRuntimeValue(
|
const value = new NumberFieldRuntimeValue(
|
||||||
{ subType: NumberFieldSubType.Uint16 } as any,
|
new NumberFieldDefinition(NumberFieldType.Uint16),
|
||||||
{ ...StructDefaultOptions, littleEndian: true },
|
{ ...StructDefaultOptions, littleEndian: true },
|
||||||
undefined as any,
|
undefined as any,
|
||||||
undefined as any,
|
undefined as any,
|
||||||
|
@ -108,7 +108,7 @@ describe('Types', () => {
|
||||||
|
|
||||||
expect(value.get()).toBe((2 << 8) | 1);
|
expect(value.get()).toBe((2 << 8) | 1);
|
||||||
expect(read).toBeCalledTimes(1);
|
expect(read).toBeCalledTimes(1);
|
||||||
expect(read).lastCalledWith(NumberFieldSubType.Uint16.size);
|
expect(read).lastCalledWith(NumberFieldType.Uint16.size);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { BuiltInFieldType, FieldDescriptorBase, FieldDescriptorBaseOptions, FieldRuntimeValue, GlobalStructFieldRuntimeTypeRegistry, StructDeserializationContext } from '../runtime';
|
import { FieldDefinition, FieldRuntimeValue, StructDeserializationContext, StructOptions, StructSerializationContext } from '../basic';
|
||||||
|
|
||||||
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];
|
||||||
|
@ -6,20 +6,20 @@ export type DataViewGetters =
|
||||||
export type DataViewSetters =
|
export type DataViewSetters =
|
||||||
{ [TKey in keyof DataView]: TKey extends `set${string}` ? TKey : never }[keyof DataView];
|
{ [TKey in keyof DataView]: TKey extends `set${string}` ? TKey : never }[keyof DataView];
|
||||||
|
|
||||||
export class NumberFieldSubType<TTypeScriptType extends number | bigint = number | bigint> {
|
export class NumberFieldType<TTypeScriptType extends number | bigint = number | bigint> {
|
||||||
public static readonly Uint8 = new NumberFieldSubType<number>(1, 'getUint8', 'setUint8');
|
public static readonly Uint8 = new NumberFieldType<number>(1, 'getUint8', 'setUint8');
|
||||||
|
|
||||||
public static readonly Uint16 = new NumberFieldSubType<number>(2, 'getUint16', 'setUint16');
|
public static readonly Uint16 = new NumberFieldType<number>(2, 'getUint16', 'setUint16');
|
||||||
|
|
||||||
public static readonly Int32 = new NumberFieldSubType<number>(4, 'getInt32', 'setInt32');
|
public static readonly Int32 = new NumberFieldType<number>(4, 'getInt32', 'setInt32');
|
||||||
|
|
||||||
public static readonly Uint32 = new NumberFieldSubType<number>(4, 'getUint32', 'setUint32');
|
public static readonly Uint32 = new NumberFieldType<number>(4, 'getUint32', 'setUint32');
|
||||||
|
|
||||||
public static readonly Int64 = new NumberFieldSubType<bigint>(8, 'getBigInt64', 'setBigInt64');
|
public static readonly Int64 = new NumberFieldType<bigint>(8, 'getBigInt64', 'setBigInt64');
|
||||||
|
|
||||||
public static readonly Uint64 = new NumberFieldSubType<bigint>(8, 'getBigUint64', 'setBigUint64');
|
public static readonly Uint64 = new NumberFieldType<bigint>(8, 'getBigUint64', 'setBigUint64');
|
||||||
|
|
||||||
public readonly value!: TTypeScriptType;
|
public readonly valueType!: TTypeScriptType;
|
||||||
|
|
||||||
public readonly size: number;
|
public readonly size: number;
|
||||||
|
|
||||||
|
@ -38,33 +38,44 @@ export class NumberFieldSubType<TTypeScriptType extends number | bigint = number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NumberFieldDescriptor<
|
export class NumberFieldDefinition<
|
||||||
TName extends string = string,
|
TType extends NumberFieldType = NumberFieldType,
|
||||||
TSubType extends NumberFieldSubType<any> = NumberFieldSubType<any>,
|
TTypeScriptType = TType['valueType'],
|
||||||
TTypeScriptType = TSubType['value'],
|
> extends FieldDefinition<
|
||||||
TOptions extends FieldDescriptorBaseOptions = FieldDescriptorBaseOptions
|
void,
|
||||||
> extends FieldDescriptorBase<
|
TTypeScriptType,
|
||||||
TName,
|
never
|
||||||
Record<TName, TTypeScriptType>,
|
|
||||||
Record<TName, TTypeScriptType>,
|
|
||||||
TOptions
|
|
||||||
> {
|
> {
|
||||||
type: BuiltInFieldType.Number;
|
public readonly type: TType;
|
||||||
|
|
||||||
subType: TSubType;
|
public constructor(type: TType, _typescriptType?: TTypeScriptType) {
|
||||||
|
super();
|
||||||
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NumberFieldRuntimeValue extends FieldRuntimeValue<NumberFieldDescriptor> {
|
public getSize(): number {
|
||||||
public static getSize(descriptor: NumberFieldDescriptor): number {
|
return this.type.size;
|
||||||
return descriptor.subType.size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public createValue(
|
||||||
|
options: Readonly<StructOptions>,
|
||||||
|
context: StructSerializationContext,
|
||||||
|
object: any
|
||||||
|
): NumberFieldRuntimeValue<TType, TTypeScriptType> {
|
||||||
|
return new NumberFieldRuntimeValue(this, options, context, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NumberFieldRuntimeValue<
|
||||||
|
TType extends NumberFieldType = NumberFieldType,
|
||||||
|
TTypeScriptType = TType['valueType'],
|
||||||
|
> extends FieldRuntimeValue<NumberFieldDefinition<TType, TTypeScriptType>> {
|
||||||
protected value: number | bigint | undefined;
|
protected value: number | bigint | undefined;
|
||||||
|
|
||||||
public async deserialize(context: StructDeserializationContext): Promise<void> {
|
public async deserialize(context: StructDeserializationContext): Promise<void> {
|
||||||
const buffer = await context.read(this.getSize());
|
const buffer = await context.read(this.getSize());
|
||||||
const view = new DataView(buffer);
|
const view = new DataView(buffer);
|
||||||
this.value = view[this.descriptor.subType.dataViewGetter](
|
this.value = view[this.definition.type.dataViewGetter](
|
||||||
0,
|
0,
|
||||||
this.options.littleEndian
|
this.options.littleEndian
|
||||||
);
|
);
|
||||||
|
@ -82,15 +93,10 @@ export class NumberFieldRuntimeValue extends FieldRuntimeValue<NumberFieldDescri
|
||||||
// `setBigInt64` requires a `bigint` while others require `number`
|
// `setBigInt64` requires a `bigint` while others require `number`
|
||||||
// So `dataView[DataViewSetters]` requires `bigint & number`
|
// So `dataView[DataViewSetters]` requires `bigint & number`
|
||||||
// and that is, `never`
|
// and that is, `never`
|
||||||
(dataView[this.descriptor.subType.dataViewSetter] as any)(
|
(dataView[this.definition.type.dataViewSetter] as any)(
|
||||||
offset,
|
offset,
|
||||||
this.value!,
|
this.value!,
|
||||||
this.options.littleEndian
|
this.options.littleEndian
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalStructFieldRuntimeTypeRegistry.register(
|
|
||||||
BuiltInFieldType.Number,
|
|
||||||
NumberFieldRuntimeValue,
|
|
||||||
);
|
|
||||||
|
|
|
@ -1,47 +1,47 @@
|
||||||
import { BuiltInFieldType, FieldDescriptorBaseOptions, getRuntimeValue, GlobalStructFieldRuntimeTypeRegistry, setRuntimeValue, StructOptions, StructSerializationContext } from '../runtime';
|
import { getRuntimeValue, setRuntimeValue, StructOptions, StructSerializationContext } from '../basic';
|
||||||
import { ArrayBufferLikeFieldDescriptor, ArrayBufferLikeFieldRuntimeValue } from './array-buffer';
|
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldRuntimeValue, ArrayBufferLikeFieldType } from './array-buffer';
|
||||||
import { NumberFieldDescriptor, NumberFieldRuntimeValue } from './number';
|
import { NumberFieldDefinition, NumberFieldRuntimeValue } from './number';
|
||||||
import { KeysOfType } from './utils';
|
import { KeysOfType } from './utils';
|
||||||
|
|
||||||
export namespace VariableLengthArrayBufferFieldDescriptor {
|
export interface VariableLengthArrayBufferLikeFieldOptions<
|
||||||
export interface Options<
|
|
||||||
TInit = object,
|
TInit = object,
|
||||||
TLengthField extends KeysOfType<TInit, number | string> = any,
|
TLengthField extends KeysOfType<TInit, number | string> = any,
|
||||||
> extends FieldDescriptorBaseOptions {
|
> {
|
||||||
lengthField: TLengthField;
|
lengthField: TLengthField;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export interface VariableLengthArrayBufferFieldDescriptor<
|
export class VariableLengthArrayBufferLikeFieldDefinition<
|
||||||
TName extends string = string,
|
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
|
||||||
TType extends ArrayBufferLikeFieldDescriptor.SubType = ArrayBufferLikeFieldDescriptor.SubType,
|
TOptions extends VariableLengthArrayBufferLikeFieldOptions = VariableLengthArrayBufferLikeFieldOptions
|
||||||
TInit = object,
|
> extends ArrayBufferLikeFieldDefinition<
|
||||||
TLengthField extends KeysOfType<TInit, number | string> = any,
|
|
||||||
TTypeScriptType = ArrayBufferLikeFieldDescriptor.TypeScriptType<TType>,
|
|
||||||
TOptions extends VariableLengthArrayBufferFieldDescriptor.Options<TInit, TLengthField> = VariableLengthArrayBufferFieldDescriptor.Options<TInit, TLengthField>
|
|
||||||
> extends ArrayBufferLikeFieldDescriptor<
|
|
||||||
TName,
|
|
||||||
TType,
|
TType,
|
||||||
Record<TName, TTypeScriptType>,
|
TOptions,
|
||||||
Record<TName, TTypeScriptType> & Record<TLengthField, never>,
|
TOptions['lengthField']
|
||||||
TOptions
|
|
||||||
> {
|
> {
|
||||||
type: BuiltInFieldType.VariableLengthArrayBufferLike;
|
public getSize(): number {
|
||||||
|
return 0;
|
||||||
options: TOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class VariableLengthArrayBufferLengthFieldRuntimeValue extends NumberFieldRuntimeValue {
|
public createValue(
|
||||||
protected arrayBufferValue: VariableLengthArrayBufferFieldRuntimeValue;
|
options: Readonly<StructOptions>,
|
||||||
|
context: StructSerializationContext,
|
||||||
|
object: any
|
||||||
|
): VariableLengthArrayBufferLikeFieldRuntimeValue {
|
||||||
|
return new VariableLengthArrayBufferLikeFieldRuntimeValue(this, options, context, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VariableLengthArrayBufferLikeLengthFieldRuntimeValue extends NumberFieldRuntimeValue {
|
||||||
|
protected arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
descriptor: NumberFieldDescriptor,
|
definition: NumberFieldDefinition,
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
context: StructSerializationContext,
|
context: StructSerializationContext,
|
||||||
object: any,
|
object: any,
|
||||||
arrayBufferValue: VariableLengthArrayBufferFieldRuntimeValue,
|
arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue,
|
||||||
) {
|
) {
|
||||||
super(descriptor, options, context, object);
|
super(definition, options, context, object);
|
||||||
this.arrayBufferValue = arrayBufferValue;
|
this.arrayBufferValue = arrayBufferValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,28 +61,28 @@ class VariableLengthArrayBufferLengthFieldRuntimeValue extends NumberFieldRuntim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class VariableLengthArrayBufferFieldRuntimeValue
|
class VariableLengthArrayBufferLikeFieldRuntimeValue
|
||||||
extends ArrayBufferLikeFieldRuntimeValue<VariableLengthArrayBufferFieldDescriptor> {
|
extends ArrayBufferLikeFieldRuntimeValue<VariableLengthArrayBufferLikeFieldDefinition> {
|
||||||
public static getSize() {
|
public static getSize() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected length: number | undefined;
|
protected length: number | undefined;
|
||||||
|
|
||||||
protected lengthFieldValue: VariableLengthArrayBufferLengthFieldRuntimeValue;
|
protected lengthFieldValue: VariableLengthArrayBufferLikeLengthFieldRuntimeValue;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
descriptor: VariableLengthArrayBufferFieldDescriptor,
|
descriptor: VariableLengthArrayBufferLikeFieldDefinition,
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
context: StructSerializationContext,
|
context: StructSerializationContext,
|
||||||
object: any
|
object: any
|
||||||
) {
|
) {
|
||||||
super(descriptor, options, context, object);
|
super(descriptor, options, context, object);
|
||||||
|
|
||||||
const lengthField = this.descriptor.options.lengthField;
|
const lengthField = this.definition.options.lengthField;
|
||||||
const oldValue = getRuntimeValue(object, lengthField) as NumberFieldRuntimeValue;
|
const oldValue = getRuntimeValue(object, lengthField) as NumberFieldRuntimeValue;
|
||||||
this.lengthFieldValue = new VariableLengthArrayBufferLengthFieldRuntimeValue(
|
this.lengthFieldValue = new VariableLengthArrayBufferLikeLengthFieldRuntimeValue(
|
||||||
oldValue.descriptor,
|
oldValue.definition,
|
||||||
this.options,
|
this.options,
|
||||||
this.context,
|
this.context,
|
||||||
object,
|
object,
|
||||||
|
@ -98,19 +98,17 @@ class VariableLengthArrayBufferFieldRuntimeValue
|
||||||
|
|
||||||
public getSize() {
|
public getSize() {
|
||||||
if (this.length === undefined) {
|
if (this.length === undefined) {
|
||||||
switch (this.descriptor.subType) {
|
if (this.arrayBuffer !== undefined) {
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.ArrayBuffer:
|
this.length = this.arrayBuffer.byteLength;
|
||||||
this.length = this.arrayBuffer!.byteLength;
|
} else {
|
||||||
break;
|
this.length = this.definition.type.getSize(this.typedValue);
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.Uint8ClampedArray:
|
if (this.length === -1) {
|
||||||
this.length = this.typedArray!.byteLength;
|
this.arrayBuffer = this.definition.type.toArrayBuffer(this.typedValue, this.context);
|
||||||
break;
|
|
||||||
case ArrayBufferLikeFieldDescriptor.SubType.String:
|
|
||||||
this.arrayBuffer = this.context.encodeUtf8(this.string!);
|
|
||||||
this.length = this.arrayBuffer.byteLength;
|
this.length = this.arrayBuffer.byteLength;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this.length;
|
return this.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,8 +117,3 @@ class VariableLengthArrayBufferFieldRuntimeValue
|
||||||
this.length = undefined;
|
this.length = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalStructFieldRuntimeTypeRegistry.register(
|
|
||||||
BuiltInFieldType.VariableLengthArrayBufferLike,
|
|
||||||
VariableLengthArrayBufferFieldRuntimeValue,
|
|
||||||
);
|
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"types": [
|
"types": [
|
||||||
"jest"
|
"@types/node",
|
||||||
|
"jest",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"exclude": []
|
"exclude": []
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue