Skip to content

Commit

Permalink
Improved type unwrapping, and keyed types.
Browse files Browse the repository at this point in the history
  • Loading branch information
iwoplaza committed Feb 19, 2024
1 parent 3f0c19b commit b349b5b
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 116 deletions.
8 changes: 4 additions & 4 deletions src/describe/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
ISchema,
Ref,
AnySchema,
UnwrapOf,
Unwrap,
AnySchemaWithProperties,
} from '../structure/types';
import { KeyedSchema } from '../structure/keyed';
Expand Down Expand Up @@ -47,15 +47,15 @@ export const tupleOf = <TSchema extends AnySchema>(
export const optional = <TSchema extends AnySchema>(innerType: TSchema) =>
new OptionalSchema(innerType);

export const keyed = <K extends string, P extends ISchema<unknown, never>>(
export const keyed = <K extends string, P extends ISchema<unknown>>(
key: K,
inner: (ref: ISchema<Ref<K>, never>) => P,
inner: (ref: ISchema<Ref<K>>) => P,
) => new KeyedSchema(key, inner);

export const concat = <Objs extends AnyObjectSchema[]>(objs: Objs) => {
return new ObjectSchema(
Object.fromEntries(
objs.map(({ properties }) => Object.entries(properties)).flat(),
) as unknown as MergeRecordUnion<UnwrapOf<Objs[number]>>,
) as unknown as MergeRecordUnion<Unwrap<Objs[number]>>,
);
};
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export * from './structure';
export * from './describe';
export * from './io';

export type { Parsed } from './utilityTypes';
export type { Parsed, ParseUnwrapped } from './utilityTypes';
9 changes: 5 additions & 4 deletions src/structure/_internal.ts
Original file line number Diff line number Diff line change
@@ -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';
21 changes: 12 additions & 9 deletions src/structure/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ import {
MaxValue,
AnySchema,
PropertyDescription,
Unwrap,
} from './types';

export class ArraySchema<TUnwrap extends AnySchema> extends Schema<TUnwrap[]> {
public elementType: TUnwrap;
export class ArraySchema<TElement extends AnySchema> extends Schema<
Unwrap<TElement>[]
> {
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,
Expand All @@ -29,28 +32,28 @@ export class ArraySchema<TUnwrap extends AnySchema> extends Schema<TUnwrap[]> {
this.elementType = ctx.resolve(this._unstableElementType);
}

write(output: ISerialOutput, values: Parsed<TUnwrap>[]): void {
write(output: ISerialOutput, values: Parsed<Unwrap<TElement>>[]): void {
output.writeUint32(values.length);

for (const value of values) {
this.elementType.write(output, value);
}
}

read(input: ISerialInput): Parsed<TUnwrap>[] {
const array: Parsed<TUnwrap>[] = [];
read(input: ISerialInput): Parsed<Unwrap<TElement>>[] {
const array: Parsed<Unwrap<TElement>>[] = [];

const len = input.readUint32();

for (let i = 0; i < len; ++i) {
array.push(this.elementType.read(input) as Parsed<TUnwrap>);
array.push(this.elementType.read(input) as Parsed<Unwrap<TElement>>);
}

return array;
}

measure(
values: Parsed<TUnwrap>[] | typeof MaxValue,
values: Parsed<Unwrap<TElement>>[] | typeof MaxValue,
measurer: IMeasurer = new Measurer(),
): IMeasurer {
if (values === MaxValue) {
Expand All @@ -70,7 +73,7 @@ export class ArraySchema<TUnwrap extends AnySchema> extends Schema<TUnwrap[]> {
}

seekProperty(
reference: Parsed<TUnwrap>[] | MaxValue,
reference: Parsed<Unwrap<TElement>>[] | MaxValue,
prop: number,
): PropertyDescription | null {
if (typeof prop === 'symbol') {
Expand Down
9 changes: 7 additions & 2 deletions src/structure/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
27 changes: 14 additions & 13 deletions src/structure/keyed.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { TypedBinaryError } from '../error';
import { IMeasurer, ISerialInput, ISerialOutput, Measurer } from '../io';
import { Parsed } from '../utilityTypes';
import { ParseUnwrapped, Parsed } from '../utilityTypes';
import {
IRefResolver,
ISchema,
Ref,
MaxValue,
PropertyDescription,
AnySchema,
Unwrap,
IKeyedSchema,
} from './types';

class RefSchema<TKeyDef extends string> implements ISchema<Ref<TKeyDef>> {
public readonly __unwrapped!: Ref<TKeyDef>;
public readonly __keyDefinition!: never;
public readonly ref: Ref<TKeyDef>;

constructor(key: TKeyDef) {
Expand Down Expand Up @@ -82,17 +83,17 @@ class RefResolve implements IRefResolver {
}

export class KeyedSchema<
TUnwrap extends ISchema<unknown>,
TInner extends ISchema<unknown>,
TKeyDef extends string,
> implements ISchema<TUnwrap, TKeyDef>
> implements IKeyedSchema<Unwrap<TInner>, TKeyDef>
{
public readonly __unwrapped!: TUnwrap;
public readonly __unwrapped!: Unwrap<TInner>;
public readonly __keyDefinition!: TKeyDef;
public innerType: TUnwrap;
public innerType: TInner;

constructor(
public readonly key: TKeyDef,
innerResolver: (ref: ISchema<Ref<TKeyDef>, never>) => TUnwrap,
innerResolver: (ref: ISchema<Ref<TKeyDef>>) => TInner,
) {
this.innerType = innerResolver(new RefSchema(key));

Expand All @@ -108,24 +109,24 @@ export class KeyedSchema<
}
}

read(input: ISerialInput): Parsed<TUnwrap> {
return this.innerType.read(input) as Parsed<TUnwrap>;
read(input: ISerialInput): ParseUnwrapped<TInner> {
return this.innerType.read(input) as ParseUnwrapped<TInner>;
}

write(output: ISerialOutput, value: Parsed<TUnwrap>): void {
write(output: ISerialOutput, value: ParseUnwrapped<TInner>): void {
this.innerType.write(output, value);
}

measure(
value: Parsed<TUnwrap> | typeof MaxValue,
value: ParseUnwrapped<TInner> | typeof MaxValue,
measurer: IMeasurer = new Measurer(),
): IMeasurer {
return this.innerType.measure(value, measurer);
}

seekProperty(
reference: Parsed<TUnwrap> | typeof MaxValue,
prop: keyof TUnwrap,
reference: ParseUnwrapped<TInner> | typeof MaxValue,
prop: keyof Unwrap<TInner>,
): PropertyDescription | null {
return this.innerType.seekProperty(reference, prop as never);
}
Expand Down
74 changes: 34 additions & 40 deletions src/structure/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -13,9 +13,10 @@ import {
MaxValue,
AnySchema,
AnySchemaWithProperties,
UnwrapOf,
ISchema,
PropertyDescription,
Unwrap,
UnwrapRecord,
} from './types';
import { SubTypeKey } from './types';

Expand All @@ -40,13 +41,13 @@ export function resolveMap<T extends Record<string, AnySchema>>(

export type AnyObjectSchema = ObjectSchema<Record<string, AnySchema>>;

export class ObjectSchema<TUnwrap extends Record<string, AnySchema>>
extends Schema<TUnwrap>
implements ISchemaWithProperties<TUnwrap>
export class ObjectSchema<TProps extends Record<string, AnySchema>>
extends Schema<UnwrapRecord<TProps>>
implements ISchemaWithProperties<TProps>
{
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,
Expand All @@ -58,33 +59,33 @@ export class ObjectSchema<TUnwrap extends Record<string, AnySchema>>
this.properties = resolveMap(ctx, this._properties);
}

write(output: ISerialOutput, value: Parsed<TUnwrap>): void {
type Property = keyof Parsed<TUnwrap>;
write(output: ISerialOutput, value: ParseUnwrappedRecord<TProps>): void {
type Property = keyof ParseUnwrappedRecord<TProps>;

for (const [key, property] of exactEntries(this.properties)) {
property.write(output, value[key as Property]);
}
}

read(input: ISerialInput): Parsed<TUnwrap> {
type Property = keyof Parsed<TUnwrap>;
read(input: ISerialInput): ParseUnwrappedRecord<TProps> {
type Property = keyof ParseUnwrappedRecord<TProps>;

const result = {} as Parsed<TUnwrap>;
const result = {} as ParseUnwrappedRecord<TProps>;

for (const [key, property] of exactEntries(this.properties)) {
result[key as Property] = property.read(
input,
) as Parsed<TUnwrap>[Property];
result[key as Property] = property.read(input) as Parsed<
UnwrapRecord<TProps>
>[Property];
}

return result;
}

measure(
value: Parsed<TUnwrap> | typeof MaxValue,
value: ParseUnwrappedRecord<TProps> | typeof MaxValue,
measurer: IMeasurer = new Measurer(),
): IMeasurer {
type Property = keyof Parsed<TUnwrap>;
type Property = keyof ParseUnwrappedRecord<TProps>;

for (const [key, property] of exactEntries(this.properties)) {
property.measure(
Expand All @@ -97,8 +98,8 @@ export class ObjectSchema<TUnwrap extends Record<string, AnySchema>>
}

seekProperty(
reference: Parsed<TUnwrap> | MaxValue,
prop: keyof TUnwrap,
reference: ParseUnwrappedRecord<TProps> | MaxValue,
prop: keyof UnwrapRecord<TProps>,
): PropertyDescription | null {
let bufferOffset = 0;

Expand All @@ -117,26 +118,16 @@ export class ObjectSchema<TUnwrap extends Record<string, AnySchema>>
}
}

export type AsSubTypes<T> = {
[K in keyof T]: T[K] extends ISchemaWithProperties<infer P>
? P & { type: K }
: never;
}[keyof T];

export type StabilizedMap<T> = {
[K in keyof T]: T[K] extends ISchemaWithProperties<infer P>
? ObjectSchema<P>
: never;
};

type UnwrapOfGeneric<Base extends Record<string, AnySchema>, Ext> = {
[TKey in keyof Ext]: ISchema<Base & { type: TKey } & UnwrapOf<Ext[TKey]>>;
type UnwrapGeneric<Base extends Record<string, AnySchema>, Ext> = {
[TKey in keyof Ext]: ISchema<
UnwrapRecord<Base> & { type: TKey } & UnwrapRecord<Unwrap<Ext[TKey]>>
>;
}[keyof Ext];

export class GenericObjectSchema<
TUnwrapBase extends Record<string, AnySchema>, // Base properties
TUnwrapExt extends Record<string, AnySchemaWithProperties>, // Sub type map
> extends Schema<UnwrapOfGeneric<TUnwrapBase, TUnwrapExt>> {
> extends Schema<UnwrapGeneric<TUnwrapBase, TUnwrapExt>> {
private _baseObject: ObjectSchema<TUnwrapBase>;
public subTypeMap: TUnwrapExt;

Expand All @@ -161,7 +152,7 @@ export class GenericObjectSchema<

write(
output: ISerialOutput,
value: Parsed<UnwrapOfGeneric<TUnwrapBase, TUnwrapExt>>,
value: Parsed<UnwrapGeneric<TUnwrapBase, TUnwrapExt>>,
): void {
// Figuring out sub-types

Expand All @@ -183,7 +174,7 @@ export class GenericObjectSchema<
}

// Writing the base properties
this._baseObject.write(output, value as Parsed<TUnwrapBase>);
this._baseObject.write(output, value as ParseUnwrappedRecord<TUnwrapBase>);

// Extra sub-type fields
for (const [key, extraProp] of exactEntries(
Expand All @@ -193,7 +184,7 @@ export class GenericObjectSchema<
}
}

read(input: ISerialInput): Parsed<UnwrapOfGeneric<TUnwrapBase, TUnwrapExt>> {
read(input: ISerialInput): Parsed<UnwrapGeneric<TUnwrapBase, TUnwrapExt>> {
const subTypeKey =
this.keyedBy === SubTypeKey.ENUM ? input.readByte() : input.readString();

Expand All @@ -208,7 +199,7 @@ export class GenericObjectSchema<
}

const result = this._baseObject.read(input) as Parsed<
UnwrapOfGeneric<TUnwrapBase, TUnwrapExt>
UnwrapGeneric<TUnwrapBase, TUnwrapExt>
>;

// Making the sub type key available to the result object.
Expand All @@ -228,10 +219,13 @@ export class GenericObjectSchema<
}

measure(
value: Parsed<UnwrapOfGeneric<TUnwrapBase, TUnwrapExt>> | MaxValue,
value: Parsed<UnwrapGeneric<TUnwrapBase, TUnwrapExt>> | MaxValue,
measurer: IMeasurer = new Measurer(),
): IMeasurer {
this._baseObject.measure(value as Parsed<TUnwrapBase> | MaxValue, measurer);
this._baseObject.measure(
value as Parsed<UnwrapRecord<TUnwrapBase>> | MaxValue,
measurer,
);

// We're a generic object trying to encode a concrete value.
if (this.keyedBy === SubTypeKey.ENUM) {
Expand Down
Loading

0 comments on commit b349b5b

Please sign in to comment.