mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-04 18:29:23 +02:00
feat(struct): reuse initialize in deserialize
This commit is contained in:
parent
13526e9020
commit
b1cc2ce9a2
13 changed files with 522 additions and 317 deletions
|
@ -1,19 +1,18 @@
|
|||
import { Array, BackingField, FieldDescriptorBase, FieldDescriptorBaseOptions, FieldType, FixedLengthArray, getFieldTypeDefinition, Number, StructDeserializationContext, StructOptions, StructSerializationContext, VariableLengthArray } from './field';
|
||||
import { Evaluate, Identity } from './utils';
|
||||
import { BackingField, defineSimpleAccessors, setBackingField, WithBackingField } from './backing-field';
|
||||
import { Array, FieldDescriptorBase, FieldDescriptorBaseOptions, FieldType, FieldTypeDefinition, FixedLengthArray, getFieldTypeDefinition, Number, VariableLengthArray } from './field';
|
||||
import { StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext } from './types';
|
||||
import { Evaluate, Identity, OmitNever, Overwrite } from './utils';
|
||||
|
||||
export type StructValueType<T extends Struct<object, object, unknown>> =
|
||||
export type StructValueType<T extends Struct<object, object, object, unknown>> =
|
||||
T extends { deserialize(reader: StructDeserializationContext): Promise<infer R>; } ? R : never;
|
||||
|
||||
export type StructInitType<T extends Struct<object, object, unknown>> =
|
||||
export type StructInitType<T extends Struct<object, object, object, unknown>> =
|
||||
T extends { create(value: infer R, ...args: any): any; } ? R : never;
|
||||
|
||||
export const StructDefaultOptions: Readonly<StructOptions> = {
|
||||
littleEndian: false,
|
||||
};
|
||||
|
||||
interface AddArrayFieldDescriptor<
|
||||
TResult extends object,
|
||||
TInit extends object,
|
||||
TExtra extends object,
|
||||
TAfterParsed
|
||||
> {
|
||||
<
|
||||
|
@ -28,6 +27,7 @@ interface AddArrayFieldDescriptor<
|
|||
): MergeStruct<
|
||||
TResult,
|
||||
TInit,
|
||||
TExtra,
|
||||
TAfterParsed,
|
||||
FixedLengthArray<
|
||||
TName,
|
||||
|
@ -50,6 +50,7 @@ interface AddArrayFieldDescriptor<
|
|||
): MergeStruct<
|
||||
TResult,
|
||||
TInit,
|
||||
TExtra,
|
||||
TAfterParsed,
|
||||
VariableLengthArray<
|
||||
TName,
|
||||
|
@ -65,6 +66,7 @@ interface AddArrayFieldDescriptor<
|
|||
interface AddArraySubTypeFieldDescriptor<
|
||||
TResult extends object,
|
||||
TInit extends object,
|
||||
TExtra extends object,
|
||||
TAfterParsed,
|
||||
TType extends Array.SubType
|
||||
> {
|
||||
|
@ -78,6 +80,7 @@ interface AddArraySubTypeFieldDescriptor<
|
|||
): MergeStruct<
|
||||
TResult,
|
||||
TInit,
|
||||
TExtra,
|
||||
TAfterParsed,
|
||||
FixedLengthArray<
|
||||
TName,
|
||||
|
@ -94,10 +97,11 @@ interface AddArraySubTypeFieldDescriptor<
|
|||
>(
|
||||
name: TName,
|
||||
options: VariableLengthArray.Options<TInit, TLengthField, TEmptyBehavior>,
|
||||
_typescriptType?: () => TTypeScriptType,
|
||||
_typescriptType?: TTypeScriptType,
|
||||
): MergeStruct<
|
||||
TResult,
|
||||
TInit,
|
||||
TExtra,
|
||||
TAfterParsed,
|
||||
VariableLengthArray<
|
||||
TName,
|
||||
|
@ -110,31 +114,27 @@ interface AddArraySubTypeFieldDescriptor<
|
|||
>;
|
||||
}
|
||||
|
||||
export type OmitNever<T> = Pick<T, { [K in keyof T]: [T[K]] extends [never] ? never : K }[keyof T]>;
|
||||
|
||||
type MergeStruct<
|
||||
TResult extends object,
|
||||
TInit extends object,
|
||||
TExtra extends object,
|
||||
TAfterParsed,
|
||||
TDescriptor extends FieldDescriptorBase
|
||||
> =
|
||||
Identity<Struct<
|
||||
Evaluate<TResult & Exclude<TDescriptor['resultObject'], undefined>>,
|
||||
Evaluate<OmitNever<TInit & Exclude<TDescriptor['initObject'], undefined>>>,
|
||||
TExtra,
|
||||
TAfterParsed
|
||||
>>;
|
||||
|
||||
type WithBackingField<T> = T & { [BackingField]: any; };
|
||||
|
||||
export type StructExtraResult<TResult, TExtra> =
|
||||
Evaluate<Omit<TResult, keyof TExtra> & TExtra>;
|
||||
|
||||
export type StructAfterParsed<TResult, TAfterParsed> =
|
||||
(this: WithBackingField<TResult>, object: WithBackingField<TResult>) => TAfterParsed;
|
||||
|
||||
export default class Struct<
|
||||
TResult extends object = {},
|
||||
TInit extends object = {},
|
||||
TExtra extends object = {},
|
||||
TAfterParsed = undefined,
|
||||
> {
|
||||
public readonly options: Readonly<StructOptions>;
|
||||
|
@ -152,8 +152,8 @@ export default class Struct<
|
|||
this.options = { ...StructDefaultOptions, ...options };
|
||||
}
|
||||
|
||||
private clone(): Struct<any, any, any> {
|
||||
const result = new Struct<any, any, any>(this.options);
|
||||
private clone(): Struct<any, any, any, any> {
|
||||
const result = new Struct<any, any, any, any>(this.options);
|
||||
result.fields = this.fields.slice();
|
||||
result._size = this._size;
|
||||
result._extra = this._extra;
|
||||
|
@ -163,7 +163,7 @@ export default class Struct<
|
|||
|
||||
public field<TDescriptor extends FieldDescriptorBase>(
|
||||
field: TDescriptor,
|
||||
): MergeStruct<TResult, TInit, TAfterParsed, TDescriptor> {
|
||||
): MergeStruct<TResult, TInit, TExtra, TAfterParsed, TDescriptor> {
|
||||
const result = this.clone();
|
||||
result.fields.push(field);
|
||||
|
||||
|
@ -176,14 +176,15 @@ export default class Struct<
|
|||
|
||||
private number<
|
||||
TName extends string,
|
||||
TTypeScriptType = Number.TypeScriptType
|
||||
TSubType extends Number.SubType = Number.SubType,
|
||||
TTypeScriptType = Number.TypeScriptType<TSubType>
|
||||
>(
|
||||
name: TName,
|
||||
type: Number.SubType,
|
||||
type: TSubType,
|
||||
options: FieldDescriptorBaseOptions = {},
|
||||
_typescriptType?: () => TTypeScriptType,
|
||||
_typescriptType?: TTypeScriptType,
|
||||
) {
|
||||
return this.field<Number<TName, TTypeScriptType>>({
|
||||
return this.field<Number<TName, TSubType, TTypeScriptType>>({
|
||||
type: FieldType.Number,
|
||||
name,
|
||||
subType: type,
|
||||
|
@ -193,11 +194,11 @@ export default class Struct<
|
|||
|
||||
public int32<
|
||||
TName extends string,
|
||||
TTypeScriptType = Number.TypeScriptType
|
||||
TTypeScriptType = Number.TypeScriptType<Number.SubType.Int32>
|
||||
>(
|
||||
name: TName,
|
||||
options: FieldDescriptorBaseOptions = {},
|
||||
_typescriptType?: () => TTypeScriptType,
|
||||
_typescriptType?: TTypeScriptType,
|
||||
) {
|
||||
return this.number(
|
||||
name,
|
||||
|
@ -209,11 +210,11 @@ export default class Struct<
|
|||
|
||||
public uint32<
|
||||
TName extends string,
|
||||
TTypeScriptType = Number.TypeScriptType
|
||||
TTypeScriptType = Number.TypeScriptType<Number.SubType.Uint32>
|
||||
>(
|
||||
name: TName,
|
||||
options: FieldDescriptorBaseOptions = {},
|
||||
_typescriptType?: () => TTypeScriptType,
|
||||
_typescriptType?: TTypeScriptType,
|
||||
) {
|
||||
return this.number(
|
||||
name,
|
||||
|
@ -223,11 +224,27 @@ export default class Struct<
|
|||
);
|
||||
}
|
||||
|
||||
private array: AddArrayFieldDescriptor<TResult, TInit, TAfterParsed> = (
|
||||
public uint64<
|
||||
TName extends string,
|
||||
TTypeScriptType = Number.TypeScriptType<Number.SubType.Uint64>
|
||||
>(
|
||||
name: TName,
|
||||
options: FieldDescriptorBaseOptions = {},
|
||||
_typescriptType?: TTypeScriptType,
|
||||
) {
|
||||
return this.number(
|
||||
name,
|
||||
Number.SubType.Uint64,
|
||||
options,
|
||||
_typescriptType
|
||||
);
|
||||
}
|
||||
|
||||
private array: AddArrayFieldDescriptor<TResult, TInit, TExtra, TAfterParsed> = (
|
||||
name: string,
|
||||
type: Array.SubType,
|
||||
options: FixedLengthArray.Options | VariableLengthArray.Options
|
||||
): Struct<any, any, any> => {
|
||||
): Struct<any, any, any, any> => {
|
||||
if ('length' in options) {
|
||||
return this.field<FixedLengthArray>({
|
||||
type: FieldType.FixedLengthArray,
|
||||
|
@ -248,6 +265,7 @@ export default class Struct<
|
|||
public arrayBuffer: AddArraySubTypeFieldDescriptor<
|
||||
TResult,
|
||||
TInit,
|
||||
TExtra,
|
||||
TAfterParsed,
|
||||
Array.SubType.ArrayBuffer
|
||||
> = <TName extends string>(
|
||||
|
@ -260,6 +278,7 @@ export default class Struct<
|
|||
public string: AddArraySubTypeFieldDescriptor<
|
||||
TResult,
|
||||
TInit,
|
||||
TExtra,
|
||||
TAfterParsed,
|
||||
Array.SubType.String
|
||||
> = <TName extends string>(
|
||||
|
@ -269,11 +288,12 @@ export default class Struct<
|
|||
return this.array(name, Array.SubType.String, options);
|
||||
};
|
||||
|
||||
public extra<TExtra extends object>(
|
||||
value: TExtra & ThisType<WithBackingField<StructExtraResult<TResult, TExtra>>>
|
||||
public extra<TValue extends object>(
|
||||
value: TValue & ThisType<WithBackingField<Overwrite<Overwrite<TExtra, TValue>, TResult>>>
|
||||
): Struct<
|
||||
StructExtraResult<TResult, TExtra>,
|
||||
Evaluate<Omit<TInit, keyof TExtra>>,
|
||||
TResult,
|
||||
TInit,
|
||||
Overwrite<TExtra, TValue>,
|
||||
TAfterParsed
|
||||
> {
|
||||
const result = this.clone();
|
||||
|
@ -283,69 +303,90 @@ export default class Struct<
|
|||
|
||||
public afterParsed(
|
||||
callback: StructAfterParsed<TResult, never>
|
||||
): Struct<TResult, TInit, never>;
|
||||
): Struct<TResult, TInit, TExtra, never>;
|
||||
public afterParsed(
|
||||
callback?: StructAfterParsed<TResult, void>
|
||||
): Struct<TResult, TInit, undefined>;
|
||||
): Struct<TResult, TInit, TExtra, undefined>;
|
||||
public afterParsed<TAfterParsed>(
|
||||
callback?: StructAfterParsed<TResult, TAfterParsed>
|
||||
): Struct<TResult, TInit, TAfterParsed>;
|
||||
): Struct<TResult, TInit, TExtra, TAfterParsed>;
|
||||
public afterParsed(
|
||||
callback?: StructAfterParsed<TResult, any>
|
||||
): Struct<any, any, any> {
|
||||
) {
|
||||
const result = this.clone();
|
||||
result._afterParsed = callback;
|
||||
return result;
|
||||
}
|
||||
|
||||
public create(init: TInit, context: StructSerializationContext): TResult {
|
||||
private initializeField(
|
||||
context: StructSerializationContext,
|
||||
field: FieldDescriptorBase,
|
||||
fieldTypeDefinition: FieldTypeDefinition<any, any>,
|
||||
object: any,
|
||||
value: any,
|
||||
extra?: any
|
||||
) {
|
||||
if (fieldTypeDefinition.initialize) {
|
||||
fieldTypeDefinition.initialize({
|
||||
context,
|
||||
extra,
|
||||
field,
|
||||
object,
|
||||
options: this.options,
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
setBackingField(object, field.name, value);
|
||||
defineSimpleAccessors(object, field.name);
|
||||
}
|
||||
}
|
||||
|
||||
public create(init: TInit, context: StructSerializationContext): Overwrite<TExtra, TResult> {
|
||||
const object: any = {
|
||||
[BackingField]: {},
|
||||
};
|
||||
Object.defineProperties(object, this._extra);
|
||||
|
||||
for (const field of this.fields) {
|
||||
const type = getFieldTypeDefinition(field.type);
|
||||
if (type.initialize) {
|
||||
type.initialize({
|
||||
context,
|
||||
field,
|
||||
init,
|
||||
object,
|
||||
options: this.options,
|
||||
});
|
||||
} else {
|
||||
object[BackingField][field.name] = (init as any)[field.name];
|
||||
Object.defineProperty(object, field.name, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() { return object[BackingField][field.name]; },
|
||||
set(value) { object[BackingField][field.name] = value; },
|
||||
});
|
||||
}
|
||||
const fieldTypeDefinition = getFieldTypeDefinition(field.type);
|
||||
this.initializeField(
|
||||
context,
|
||||
field,
|
||||
fieldTypeDefinition,
|
||||
object,
|
||||
(init as any)[field.name]
|
||||
);
|
||||
}
|
||||
|
||||
Object.defineProperties(object, this._extra);
|
||||
return object;
|
||||
}
|
||||
|
||||
public async deserialize(
|
||||
context: StructDeserializationContext
|
||||
): Promise<TAfterParsed extends undefined ? TResult : TAfterParsed> {
|
||||
): Promise<TAfterParsed extends undefined ? Overwrite<TExtra, TResult> : TAfterParsed> {
|
||||
const object: any = {
|
||||
[BackingField]: {},
|
||||
};
|
||||
Object.defineProperties(object, this._extra);
|
||||
|
||||
for (const field of this.fields) {
|
||||
await getFieldTypeDefinition(field.type).deserialize({
|
||||
const fieldTypeDefinition = getFieldTypeDefinition(field.type);
|
||||
const { value, extra } = await fieldTypeDefinition.deserialize({
|
||||
context,
|
||||
field,
|
||||
object,
|
||||
options: this.options,
|
||||
});
|
||||
this.initializeField(
|
||||
context,
|
||||
field,
|
||||
fieldTypeDefinition,
|
||||
object,
|
||||
value,
|
||||
extra
|
||||
);
|
||||
}
|
||||
|
||||
Object.defineProperties(object, this._extra);
|
||||
|
||||
if (this._afterParsed) {
|
||||
const result = this._afterParsed.call(object, object);
|
||||
if (result) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue