Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(shape): memorize shape definitions #44

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions packages/shape/src/dict.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AbstractConstructor,
Constructor,
Empty,
Definition,
} from "./_";

export type DictShorthand = { [key: string]: Shorthand };
Expand All @@ -20,6 +21,15 @@ type Internal<S extends DictShorthand, B extends AbstractConstructor<{}>> = {
};
};

function mapObject<T extends { [key: string]: any }, U>(
obj: T,
fn: (value: T[keyof T], key: keyof T) => U,
): { [key in keyof T]: U } {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, fn(value, key)]),
) as any;
}

export const Dict = <
const S extends { [key: string]: any },
B extends AbstractConstructor<{}> = typeof Empty,
Expand All @@ -28,6 +38,11 @@ export const Dict = <
of: S,
base: B = Empty as any,
) => {
const definition = mapObject(
of,
(child) => Shape(child as any) as Definition,
);

abstract class $Dict extends (base as any as AbstractConstructor<{}>) {
static $shape = "dict" as const;
static $of = of;
Expand All @@ -53,28 +68,18 @@ export const Dict = <
this: T,
value: Cache["Serialized"],
): Cache["Inline"] {
const split = Object.entries(of);
const transform = split.map(([key, child]) => {
const longhand = Shape(child) as any;
const deserialized = longhand.$deserialize((value as any)[key]);
return [key, deserialized];
});
const merge = Object.fromEntries(transform);
return merge;
return mapObject(definition, (child, key) =>
child.$deserialize(value[key]),
) as any;
}

static $serialize<T extends typeof $Dict>(
this: T,
value: InstanceType<T>,
): Cache["Serialized"] {
const split = Object.entries(of);
const transform = split.map(([key, child]) => {
const longhand = Shape(child as any) as any;
const serialized = longhand.$serialize((value as any)[key]);
return [key, serialized];
});
const merge = Object.fromEntries(transform);

const merge = mapObject(definition, (child, key) =>
child.$serialize((value as any)[key]),
);
if ("$name" in base) {
return { ...merge, $name: base.$name } as any;
}
Expand Down
25 changes: 12 additions & 13 deletions packages/shape/src/either.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
Empty,
} from "./_";
import { ClassShorthand } from "./class";
import { PrimitiveShorthand } from "./primitive";

type Config = { [key: string]: any };

Expand Down Expand Up @@ -71,22 +70,22 @@ export const Either = <
match<
M extends Matcher<S>,
F extends M extends ExhaustiveMatcher<S>
? []
: M extends UnsafeFallthroughMatcher<S>
? []
: M extends PartialMatcher<S>
? [
fallback: (
value: InstanceType<Omit<S, keyof M>[keyof Omit<S, keyof M>]>,
) => any,
]
: [],
? []
: M extends UnsafeFallthroughMatcher<S>
? []
: M extends PartialMatcher<S>
? [
fallback: (
value: InstanceType<Omit<S, keyof M>[keyof Omit<S, keyof M>]>,
) => any,
]
: [],
>(
...[matcher, fallback]: [matcher: M, ...F]
):
| (M[keyof M] extends (...args: any[]) => any
? ReturnType<M[keyof M]>
: never)
? ReturnType<M[keyof M]>
: never)
| (F[0] extends (...args: any[]) => any ? ReturnType<F[0]> : never) {
const key: any = Object.entries(of).find(
([_, v]) => v === ((this.value as any).constructor as any),
Expand Down
14 changes: 7 additions & 7 deletions packages/shape/src/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ export const Mapping = <

const { $key, $value } = { $key: _key, $value: _value };

type Definition = MappingOf<C>["value"];
type D = MappingOf<C>["value"];
type K = MappingOf<C>["key"];
type Key = MappingKeyRuntimeFromConstructor<K>;
type Serialized = Record<Key, ReturnType<Definition["$serialize"]>>;
type Inline = Record<Key, Definition["$inline"]>;
type Serialized = Record<Key, ReturnType<D["$serialize"]>>;
type Inline = Record<Key, D["$inline"]>;

const valueDefinition = Shape($value) as Definition;

abstract class $Mapping extends (base as any as Constructor<{}>) {
constructor(public value: Inline) {
Expand All @@ -76,8 +78,7 @@ export const Mapping = <
): Inline {
const split = Object.entries(value);
const transform = split.map(([key, child]) => {
const longhand = Shape(_value) as any;
const deserialized = longhand.$deserialize(child as any);
const deserialized = valueDefinition.$deserialize(child as any);
return [key, deserialized] as const;
});
return Object.fromEntries(transform) as any;
Expand All @@ -89,8 +90,7 @@ export const Mapping = <
): Serialized {
const split = Object.entries(value);
const transform = split.map(([key, child]) => {
const longhand = Shape($value) as any;
const serialized = longhand.$serialize(child as any);
const serialized = valueDefinition.$serialize(child as any);
return [key, serialized];
});
const merge = Object.fromEntries(transform);
Expand Down
8 changes: 4 additions & 4 deletions packages/shape/src/optional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const Optional = <
of: S,
base: B = Empty as any,
) => {
const definition = Shape(of) as Definition;

abstract class $Optional extends (base as any as Constructor<{}>) {
constructor(public value: Cache["Inline"]) {
super();
Expand Down Expand Up @@ -58,16 +60,14 @@ export const Optional = <
if (value === undefined) {
return undefined;
}
return (Shape(of) as any).$deserialize(value);
return definition.$deserialize(value);
}

static $serialize<T extends typeof $Optional>(
this: T,
value: Cache["Inline"],
): Cache["Serialized"] {
return value === undefined
? undefined
: (Shape(of) as any).$serialize(value);
return value === undefined ? undefined : definition.$serialize(value);
}

static $inline: Cache["Inline"];
Expand Down