diff --git a/src/describe/index.ts b/src/describe/index.ts index 53e2bb8..5f17cb1 100644 --- a/src/describe/index.ts +++ b/src/describe/index.ts @@ -7,7 +7,7 @@ import { ISchema, Ref, AnySchema, - UnwrapOf, + Unwrap, AnySchemaWithProperties, } from '../structure/types'; import { KeyedSchema } from '../structure/keyed'; @@ -47,15 +47,15 @@ export const tupleOf = ( export const optional = (innerType: TSchema) => new OptionalSchema(innerType); -export const keyed = >( +export const keyed = >( key: K, - inner: (ref: ISchema, never>) => P, + inner: (ref: ISchema>) => P, ) => new KeyedSchema(key, inner); export const concat = (objs: Objs) => { return new ObjectSchema( Object.fromEntries( objs.map(({ properties }) => Object.entries(properties)).flat(), - ) as unknown as MergeRecordUnion>, + ) as unknown as MergeRecordUnion>, ); }; diff --git a/src/index.ts b/src/index.ts index e0f8011..ec9b254 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,4 +2,4 @@ export * from './structure'; export * from './describe'; export * from './io'; -export type { Parsed } from './utilityTypes'; +export type { Parsed, ParseUnwrapped } from './utilityTypes'; diff --git a/src/structure/_internal.ts b/src/structure/_internal.ts index a53f219..b5f3eb7 100644 --- a/src/structure/_internal.ts +++ b/src/structure/_internal.ts @@ -1,7 +1,8 @@ -export * from './types'; +export * from './array'; export * from './baseTypes'; export * from './chars'; -export * from './optional'; -export * from './object'; -export * from './array'; export * from './keyed'; +export * from './object'; +export * from './optional'; +export * from './tuple'; +export * from './types'; diff --git a/src/structure/array.ts b/src/structure/array.ts index 601bd99..ba51304 100644 --- a/src/structure/array.ts +++ b/src/structure/array.ts @@ -12,12 +12,15 @@ import { MaxValue, AnySchema, PropertyDescription, + Unwrap, } from './types'; -export class ArraySchema extends Schema { - public elementType: TUnwrap; +export class ArraySchema extends Schema< + Unwrap[] +> { + public elementType: TElement; - constructor(private readonly _unstableElementType: TUnwrap) { + constructor(private readonly _unstableElementType: TElement) { super(); // In case this array isn't part of a keyed chain, @@ -29,7 +32,7 @@ export class ArraySchema extends Schema { this.elementType = ctx.resolve(this._unstableElementType); } - write(output: ISerialOutput, values: Parsed[]): void { + write(output: ISerialOutput, values: Parsed>[]): void { output.writeUint32(values.length); for (const value of values) { @@ -37,20 +40,20 @@ export class ArraySchema extends Schema { } } - read(input: ISerialInput): Parsed[] { - const array: Parsed[] = []; + read(input: ISerialInput): Parsed>[] { + const array: Parsed>[] = []; const len = input.readUint32(); for (let i = 0; i < len; ++i) { - array.push(this.elementType.read(input) as Parsed); + array.push(this.elementType.read(input) as Parsed>); } return array; } measure( - values: Parsed[] | typeof MaxValue, + values: Parsed>[] | typeof MaxValue, measurer: IMeasurer = new Measurer(), ): IMeasurer { if (values === MaxValue) { @@ -70,7 +73,7 @@ export class ArraySchema extends Schema { } seekProperty( - reference: Parsed[] | MaxValue, + reference: Parsed>[] | MaxValue, prop: number, ): PropertyDescription | null { if (typeof prop === 'symbol') { diff --git a/src/structure/index.ts b/src/structure/index.ts index 93bfe52..49495bc 100644 --- a/src/structure/index.ts +++ b/src/structure/index.ts @@ -10,14 +10,19 @@ export { IRefResolver, Schema, ISchema, + Unwrap, + UnwrapRecord, ISchemaWithProperties, AnySchema, - AnyObjectSchema, AnySchemaWithProperties, + + // Specific schemas + ArraySchema, KeyedSchema, CharsSchema, - ArraySchema, + TupleSchema, ObjectSchema, + AnyObjectSchema, GenericObjectSchema, OptionalSchema, SubTypeKey, diff --git a/src/structure/keyed.ts b/src/structure/keyed.ts index fc6af6a..39a38b5 100644 --- a/src/structure/keyed.ts +++ b/src/structure/keyed.ts @@ -1,6 +1,6 @@ import { TypedBinaryError } from '../error'; import { IMeasurer, ISerialInput, ISerialOutput, Measurer } from '../io'; -import { Parsed } from '../utilityTypes'; +import { ParseUnwrapped, Parsed } from '../utilityTypes'; import { IRefResolver, ISchema, @@ -8,11 +8,12 @@ import { MaxValue, PropertyDescription, AnySchema, + Unwrap, + IKeyedSchema, } from './types'; class RefSchema implements ISchema> { public readonly __unwrapped!: Ref; - public readonly __keyDefinition!: never; public readonly ref: Ref; constructor(key: TKeyDef) { @@ -82,17 +83,17 @@ class RefResolve implements IRefResolver { } export class KeyedSchema< - TUnwrap extends ISchema, + TInner extends ISchema, TKeyDef extends string, -> implements ISchema +> implements IKeyedSchema, TKeyDef> { - public readonly __unwrapped!: TUnwrap; + public readonly __unwrapped!: Unwrap; public readonly __keyDefinition!: TKeyDef; - public innerType: TUnwrap; + public innerType: TInner; constructor( public readonly key: TKeyDef, - innerResolver: (ref: ISchema, never>) => TUnwrap, + innerResolver: (ref: ISchema>) => TInner, ) { this.innerType = innerResolver(new RefSchema(key)); @@ -108,24 +109,24 @@ export class KeyedSchema< } } - read(input: ISerialInput): Parsed { - return this.innerType.read(input) as Parsed; + read(input: ISerialInput): ParseUnwrapped { + return this.innerType.read(input) as ParseUnwrapped; } - write(output: ISerialOutput, value: Parsed): void { + write(output: ISerialOutput, value: ParseUnwrapped): void { this.innerType.write(output, value); } measure( - value: Parsed | typeof MaxValue, + value: ParseUnwrapped | typeof MaxValue, measurer: IMeasurer = new Measurer(), ): IMeasurer { return this.innerType.measure(value, measurer); } seekProperty( - reference: Parsed | typeof MaxValue, - prop: keyof TUnwrap, + reference: ParseUnwrapped | typeof MaxValue, + prop: keyof Unwrap, ): PropertyDescription | null { return this.innerType.seekProperty(reference, prop as never); } diff --git a/src/structure/object.ts b/src/structure/object.ts index 7cdd7a7..de5319d 100644 --- a/src/structure/object.ts +++ b/src/structure/object.ts @@ -4,7 +4,7 @@ import { type ISerialInput, type ISerialOutput, } from '../io'; -import { Parsed } from '../utilityTypes'; +import { ParseUnwrappedRecord, Parsed } from '../utilityTypes'; import { byte, string } from './baseTypes'; import { Schema, @@ -13,9 +13,10 @@ import { MaxValue, AnySchema, AnySchemaWithProperties, - UnwrapOf, ISchema, PropertyDescription, + Unwrap, + UnwrapRecord, } from './types'; import { SubTypeKey } from './types'; @@ -40,13 +41,13 @@ export function resolveMap>( export type AnyObjectSchema = ObjectSchema>; -export class ObjectSchema> - extends Schema - implements ISchemaWithProperties +export class ObjectSchema> + extends Schema> + implements ISchemaWithProperties { - public properties: TUnwrap; + public properties: TProps; - constructor(private readonly _properties: TUnwrap) { + constructor(private readonly _properties: TProps) { super(); // In case this object isn't part of a keyed chain, @@ -58,33 +59,33 @@ export class ObjectSchema> this.properties = resolveMap(ctx, this._properties); } - write(output: ISerialOutput, value: Parsed): void { - type Property = keyof Parsed; + write(output: ISerialOutput, value: ParseUnwrappedRecord): void { + type Property = keyof ParseUnwrappedRecord; for (const [key, property] of exactEntries(this.properties)) { property.write(output, value[key as Property]); } } - read(input: ISerialInput): Parsed { - type Property = keyof Parsed; + read(input: ISerialInput): ParseUnwrappedRecord { + type Property = keyof ParseUnwrappedRecord; - const result = {} as Parsed; + const result = {} as ParseUnwrappedRecord; for (const [key, property] of exactEntries(this.properties)) { - result[key as Property] = property.read( - input, - ) as Parsed[Property]; + result[key as Property] = property.read(input) as Parsed< + UnwrapRecord + >[Property]; } return result; } measure( - value: Parsed | typeof MaxValue, + value: ParseUnwrappedRecord | typeof MaxValue, measurer: IMeasurer = new Measurer(), ): IMeasurer { - type Property = keyof Parsed; + type Property = keyof ParseUnwrappedRecord; for (const [key, property] of exactEntries(this.properties)) { property.measure( @@ -97,8 +98,8 @@ export class ObjectSchema> } seekProperty( - reference: Parsed | MaxValue, - prop: keyof TUnwrap, + reference: ParseUnwrappedRecord | MaxValue, + prop: keyof UnwrapRecord, ): PropertyDescription | null { let bufferOffset = 0; @@ -117,26 +118,16 @@ export class ObjectSchema> } } -export type AsSubTypes = { - [K in keyof T]: T[K] extends ISchemaWithProperties - ? P & { type: K } - : never; -}[keyof T]; - -export type StabilizedMap = { - [K in keyof T]: T[K] extends ISchemaWithProperties - ? ObjectSchema

- : never; -}; - -type UnwrapOfGeneric, Ext> = { - [TKey in keyof Ext]: ISchema>; +type UnwrapGeneric, Ext> = { + [TKey in keyof Ext]: ISchema< + UnwrapRecord & { type: TKey } & UnwrapRecord> + >; }[keyof Ext]; export class GenericObjectSchema< TUnwrapBase extends Record, // Base properties TUnwrapExt extends Record, // Sub type map -> extends Schema> { +> extends Schema> { private _baseObject: ObjectSchema; public subTypeMap: TUnwrapExt; @@ -161,7 +152,7 @@ export class GenericObjectSchema< write( output: ISerialOutput, - value: Parsed>, + value: Parsed>, ): void { // Figuring out sub-types @@ -183,7 +174,7 @@ export class GenericObjectSchema< } // Writing the base properties - this._baseObject.write(output, value as Parsed); + this._baseObject.write(output, value as ParseUnwrappedRecord); // Extra sub-type fields for (const [key, extraProp] of exactEntries( @@ -193,7 +184,7 @@ export class GenericObjectSchema< } } - read(input: ISerialInput): Parsed> { + read(input: ISerialInput): Parsed> { const subTypeKey = this.keyedBy === SubTypeKey.ENUM ? input.readByte() : input.readString(); @@ -208,7 +199,7 @@ export class GenericObjectSchema< } const result = this._baseObject.read(input) as Parsed< - UnwrapOfGeneric + UnwrapGeneric >; // Making the sub type key available to the result object. @@ -228,10 +219,13 @@ export class GenericObjectSchema< } measure( - value: Parsed> | MaxValue, + value: Parsed> | MaxValue, measurer: IMeasurer = new Measurer(), ): IMeasurer { - this._baseObject.measure(value as Parsed | MaxValue, measurer); + this._baseObject.measure( + value as Parsed> | MaxValue, + measurer, + ); // We're a generic object trying to encode a concrete value. if (this.keyedBy === SubTypeKey.ENUM) { diff --git a/src/structure/optional.ts b/src/structure/optional.ts index 6a28de7..b9aa6eb 100644 --- a/src/structure/optional.ts +++ b/src/structure/optional.ts @@ -4,15 +4,15 @@ import { type ISerialInput, type ISerialOutput, } from '../io'; -import { Parsed } from '../utilityTypes'; -import { IRefResolver, MaxValue, Schema, AnySchema } from './types'; +import { ParseUnwrapped } from '../utilityTypes'; +import { IRefResolver, MaxValue, Schema, AnySchema, Unwrap } from './types'; -export class OptionalSchema extends Schema< - TUnwrap | undefined +export class OptionalSchema extends Schema< + Unwrap | undefined > { - private innerSchema: TUnwrap; + private innerSchema: TInner; - constructor(private readonly _innerUnstableSchema: TUnwrap) { + constructor(private readonly _innerUnstableSchema: TInner) { super(); // In case this optional isn't part of a keyed chain, @@ -24,7 +24,10 @@ export class OptionalSchema extends Schema< this.innerSchema = ctx.resolve(this._innerUnstableSchema); } - write(output: ISerialOutput, value: Parsed | undefined): void { + write( + output: ISerialOutput, + value: ParseUnwrapped | undefined, + ): void { if (value !== undefined && value !== null) { output.writeBool(true); this.innerSchema.write(output, value); @@ -33,18 +36,18 @@ export class OptionalSchema extends Schema< } } - read(input: ISerialInput): Parsed | undefined { + read(input: ISerialInput): ParseUnwrapped | undefined { const valueExists = input.readBool(); if (valueExists) { - return this.innerSchema.read(input) as Parsed; + return this.innerSchema.read(input) as ParseUnwrapped; } return undefined; } measure( - value: Parsed | MaxValue | undefined, + value: ParseUnwrapped | MaxValue | undefined, measurer: IMeasurer = new Measurer(), ): IMeasurer { if (value !== undefined) { diff --git a/src/structure/types.ts b/src/structure/types.ts index 7ca2b65..2ce73b2 100644 --- a/src/structure/types.ts +++ b/src/structure/types.ts @@ -6,15 +6,59 @@ export const MaxValue = Symbol( 'The biggest (in amount of bytes needed) value a schema can represent', ); -export type UnwrapOf = T extends AnySchema ? T['__unwrapped'] : never; -export type KeyDefinitionOf = T extends AnySchema - ? T['__keyDefinition'] - : never; - -export interface ISchemaWithProperties< - TUnwrap extends Record, -> extends ISchema { - readonly properties: TUnwrap; +export interface IKeyedSchema + extends ISchema { + readonly __keyDefinition: TKeyDef; +} + +/** + * Removes one layer of schema wrapping. + * + * @example ``` + * Unwrap>> -> ISchema + * Unwrap> -> number + * ``` + * + * Keyed schemas are bypassed. + * + * @example ``` + * Unwrap>> -> IKeyedSchema + * ``` + */ +export type Unwrap = T extends IKeyedSchema + ? // bypassing keyed schemas, as that information has to be preserved for parsing + IKeyedSchema, TKeyDef> + : T extends ISchema + ? TInner + : T; + +/** + * Removes one layer of schema wrapping of record properties. + * + * @example ``` + * Unwrap<{ + * a: ISchema, + * b: ISchema> + * }> + * // <=> + * { + * a: number, + * b: ISchema + * } + * ``` + */ +export type UnwrapRecord = T extends IKeyedSchema< + Record, + infer TKeyDef +> + ? IKeyedSchema<{ [key in K]: Unwrap }, TKeyDef> + : T extends Record + ? { [key in K]: Unwrap } + : T; + +export interface ISchemaWithProperties> + extends ISchema> { + readonly properties: TProps; } export type AnySchemaWithProperties = ISchemaWithProperties< @@ -29,40 +73,42 @@ export type PropertyDescription = { /** * @param TUnwrap one level of unwrapping to the inferred type. */ -export interface ISchema { - readonly __unwrapped: TUnwrap; - readonly __keyDefinition: TKeyDef; +export interface ISchema { + readonly __unwrapped: TUnwrapped; + resolve(ctx: IRefResolver): void; - write(output: ISerialOutput, value: Parsed): void; - read(input: ISerialInput): Parsed; - measure(value: Parsed | MaxValue, measurer?: IMeasurer): IMeasurer; + write(output: ISerialOutput, value: Parsed): void; + read(input: ISerialInput): Parsed; + measure( + value: Parsed | MaxValue, + measurer?: IMeasurer, + ): IMeasurer; seekProperty( - reference: Parsed | MaxValue, - prop: keyof TUnwrap, + reference: Parsed | MaxValue, + prop: keyof TUnwrapped, ): PropertyDescription | null; } export type AnySchema = ISchema; -export abstract class Schema implements ISchema { - readonly __unwrapped!: TUnwrap; - readonly __keyDefinition!: never; +export abstract class Schema implements ISchema { + readonly __unwrapped!: TUnwrapped; // eslint-disable-next-line @typescript-eslint/no-unused-vars resolve(ctx: IRefResolver): void { // override this if you need to resolve internal references. } - abstract write(output: ISerialOutput, value: Parsed): void; - abstract read(input: ISerialInput): Parsed; + abstract write(output: ISerialOutput, value: Parsed): void; + abstract read(input: ISerialInput): Parsed; abstract measure( - value: Parsed | MaxValue, + value: Parsed | MaxValue, measurer?: IMeasurer, ): IMeasurer; seekProperty( // eslint-disable-next-line @typescript-eslint/no-unused-vars - _reference: Parsed | MaxValue, + _reference: Parsed | MaxValue, // eslint-disable-next-line @typescript-eslint/no-unused-vars - _prop: keyof TUnwrap, + _prop: keyof TUnwrapped, ): PropertyDescription | null { // override this if necessary. return null; @@ -86,7 +132,7 @@ export interface IRefResolver { hasKey(key: string): boolean; resolve(schemaOrRef: TSchema): TSchema; - register(key: K, schema: ISchema): void; + register(key: K, schema: ISchema): void; } //// diff --git a/src/utilityTypes.ts b/src/utilityTypes.ts index d73d0ca..100cb0d 100644 --- a/src/utilityTypes.ts +++ b/src/utilityTypes.ts @@ -1,4 +1,10 @@ -import { ISchema, Ref, UnwrapOf } from './structure/types'; +import { + IKeyedSchema, + ISchema, + Ref, + Unwrap, + UnwrapRecord, +} from './structure/types'; /** * @example ``` @@ -44,16 +50,16 @@ export type Parsed< string, never >, -> = T extends ISchema - ? // A non-keyed schema - Parsed - : T extends ISchema +> = T extends IKeyedSchema ? // A schema that defines themselves under a key in the dictionary - Parsed + Parsed + : T extends ISchema + ? // A non-keyed schema + Parsed : // A reference to a keyed schema T extends Ref ? K extends keyof TKeyDict - ? Parsed, TKeyDict> + ? Parsed, TKeyDict> : never : // Compound types T extends Record @@ -61,3 +67,7 @@ export type Parsed< : T extends (infer E)[] ? Parsed[] : T; + +export type ParseUnwrapped = Parsed>; + +export type ParseUnwrappedRecord = Parsed>;