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

Close #288, generated functions are optimized #289

Merged
merged 3 commits into from
Oct 31, 2022
Merged

Conversation

samchon
Copy link
Owner

@samchon samchon commented Oct 31, 2022

@sinclairzx81: found a million percent faster case in TypeBox.

Is Function Benchmark

Measured on AMD R9 5900HX

@samchon samchon added the enhancement New feature or request label Oct 31, 2022
@samchon samchon self-assigned this Oct 31, 2022
@samchon samchon changed the title Features/fuctional Close #288, generated functions are optimized Oct 31, 2022
@samchon samchon merged commit bab99c1 into master Oct 31, 2022
@sinclairzx81
Copy link
Contributor

sinclairzx81 commented Oct 31, 2022

@samchon That's cool :)

Btw, were you going to update these schemas?

Ajv

AjvArrayRecursiveUnionImplicit.ts
import { TSchema, Type } from "@sinclair/typebox";
import Ajv from "ajv";

const ImageFile = Type.Object(
    {
        id: Type.Number(),
        name: Type.String(),
        path: Type.String(),
        width: Type.Number(),
        height: Type.Number(),
        url: Type.String(),
        size: Type.Number(),
    },
    {
        additionalProperties: false,
    },
);

const TextFile = Type.Object(
    {
        id: Type.Number(),
        name: Type.String(),
        path: Type.String(),
        size: Type.Number(),
        content: Type.String(),
    },
    {
        additionalProperties: false,
    },
);

const ZipFile = Type.Object(
    {
        id: Type.Number(),
        name: Type.String(),
        path: Type.String(),
        size: Type.Number(),
        count: Type.Number(),
    },
    {
        additionalProperties: false,
    },
);

const Shortcut = <T extends TSchema>(bucket: T) =>
    Type.Object(
        {
            id: Type.Number(),
            name: Type.String(),
            path: Type.String(),
            target: bucket,
        },
        {
            additionalProperties: false,
        },
    );

const Directory = (bucket: TSchema) =>
    Type.Object(
        {
            id: Type.Number(),
            name: Type.String(),
            path: Type.String(),
            children: Type.Array(bucket),
        },
        {
            additionalProperties: false,
        },
    );

const SharedDirectory = (bucket: TSchema) =>
    Type.Object(
        {
            id: Type.Number(),
            name: Type.String(),
            path: Type.String(),
            children: Type.Array(bucket),
            access: Type.Union([Type.Literal("read"), Type.Literal("write")]),
        },
        {
            additionalProperties: false,
        },
    );

const Bucket = Type.Recursive((bucket) =>
    Type.Union([
        ImageFile,
        TextFile,
        ZipFile,
        Shortcut(bucket),
        Directory(bucket),
        SharedDirectory(bucket),
    ]),
);

export const __AjvArrayRecursiveUnionImplicit = Type.Array(Bucket);
const ajv = new Ajv();
const validate = ajv.compile(__AjvArrayRecursiveUnionImplicit);
export const AjvArrayRecursiveUnionImplicit = {
    Check: (input: unknown) => validate(input) as boolean,
};

TypeBox

npm install @sinclair/[email protected]

TypeBoxArrayRecursiveUnionImplicit.ts
import { TSchema, Type } from "@sinclair/typebox";
import { TypeCompiler } from "@sinclair/typebox/compiler";

const ImageFile = Type.Object(
    {
        id: Type.Number(),
        name: Type.String(),
        path: Type.String(),
        width: Type.Number(),
        height: Type.Number(),
        url: Type.String(),
        size: Type.Number(),
    },
    {
        additionalProperties: false,
    },
);

const TextFile = Type.Object(
    {
        id: Type.Number(),
        name: Type.String(),
        path: Type.String(),
        size: Type.Number(),
        content: Type.String(),
    },
    {
        additionalProperties: false,
    },
);

const ZipFile = Type.Object(
    {
        id: Type.Number(),
        name: Type.String(),
        path: Type.String(),
        size: Type.Number(),
        count: Type.Number(),
    },
    {
        additionalProperties: false,
    },
);

const Shortcut = <T extends TSchema>(bucket: T) =>
    Type.Object(
        {
            id: Type.Number(),
            name: Type.String(),
            path: Type.String(),
            target: bucket,
        },
        {
            additionalProperties: false,
        },
    );

const Directory = (bucket: TSchema) =>
    Type.Object(
        {
            id: Type.Number(),
            name: Type.String(),
            path: Type.String(),
            children: Type.Array(bucket),
        },
        {
            additionalProperties: false,
        },
    );

const SharedDirectory = (bucket: TSchema) =>
    Type.Object(
        {
            id: Type.Number(),
            name: Type.String(),
            path: Type.String(),
            children: Type.Array(bucket),
            access: Type.Union([Type.Literal("read"), Type.Literal("write")]),
        },
        {
            additionalProperties: false,
        },
    );

const Bucket = Type.Recursive((bucket) =>
    Type.Union([
        ImageFile,
        TextFile,
        ZipFile,
        Shortcut(bucket),
        Directory(bucket),
        SharedDirectory(bucket),
    ]),
);

export const __TypeBoxArrayRecursiveUnionImplicit = Type.Array(Bucket);
export const TypeBoxArrayRecursiveUnionImplicit = TypeCompiler.Compile(
    __TypeBoxArrayRecursiveUnionImplicit,
);
TypeBoxObjectUnionImplicit.ts
import { Type } from "@sinclair/typebox";
import { TypeCompiler } from "@sinclair/typebox/compiler";

const Point = Type.Object(
    {
        x: Type.Number(),
        y: Type.Number(),
        slope: Type.Optional(Type.Union([Type.Null(), Type.Number()])),
    },
    { additionalProperties: Type.Undefined() },
);
const Line = Type.Object(
    {
        p1: Point,
        p2: Point,
        distance: Type.Optional(Type.Union([Type.Null(), Type.Number()])),
    },
    { additionalProperties: Type.Undefined() },
);
const Triangle = Type.Object(
    {
        p1: Point,
        p2: Point,
        p3: Point,
        width: Type.Optional(Type.Union([Type.Null(), Type.Number()])),
        height: Type.Optional(Type.Union([Type.Null(), Type.Number()])),
        area: Type.Optional(Type.Union([Type.Null(), Type.Number()])),
    },
    { additionalProperties: Type.Undefined() },
);
const Rectangle = Type.Object(
    {
        p1: Point,
        p2: Point,
        p3: Point,
        p4: Point,
        width: Type.Optional(Type.Union([Type.Null(), Type.Number()])),
        height: Type.Optional(Type.Union([Type.Null(), Type.Number()])),
        area: Type.Optional(Type.Union([Type.Null(), Type.Number()])),
    },
    { additionalProperties: Type.Undefined() },
);
const Polyline = Type.Object(
    {
        points: Type.Array(Point),
        length: Type.Optional(Type.Union([Type.Null(), Type.Number()])),
    },
    { additionalProperties: Type.Undefined() },
);
const Polygon = Type.Object(
    {
        outer: Polyline,
        inner: Type.Optional(Type.Array(Polyline)),
        area: Type.Optional(Type.Union([Type.Null(), Type.Number()])),
    },
    { additionalProperties: Type.Undefined() },
);
const Circle = Type.Object(
    {
        centroid: Type.Optional(Point),
        radius: Type.Number(),
        area: Type.Optional(Type.Union([Type.Null(), Type.Number()])),
    },
    { additionalProperties: Type.Undefined() },
);

const Union = Type.Union([
    Point,
    Line,
    Triangle,
    Rectangle,
    Polyline,
    Polygon,
    Circle,
]);

export const __TypeBoxObjectUnionImplicit = Type.Array(Union);
export const TypeBoxObjectUnionImplicit = TypeCompiler.Compile(
    __TypeBoxObjectUnionImplicit,
);
TypeBoxUltimateUnion.ts
import { TSchema, Type } from "@sinclair/typebox";
import { TypeCompiler } from "@sinclair/typebox/compiler";

const Attribute = {
    description: Type.Optional(Type.String()),
    "x-tson-metaTags": Type.Optional(
        Type.Array(
            Type.Object({
                // @todo - must be specified, but too hard
                kind: Type.String(),
            }),
        ),
    ),
    "x-tson-jsDocTags": Type.Optional(
        Type.Array(
            Type.Object({
                name: Type.String(),
                text: Type.Optional(
                    Type.Array(
                        Type.Object({
                            text: Type.String(),
                            kind: Type.String(),
                        }),
                    ),
                ),
            }),
        ),
    ),
};

const Unknown = Type.Object(
    {
        ...Attribute,
    },
    { additionalProperties: Type.Undefined() },
);
const NullOnly = Type.Object(
    {
        type: Type.Literal("null"),
        ...Attribute,
    },
    { additionalProperties: Type.Undefined() },
);

const Atomic = (literal: string, type: () => any) => {
    return Type.Object(
        {
            type: Type.Literal(literal),
            nullable: Type.Boolean(),
            default: Type.Optional(type()),
            ...Attribute,
        },
        { additionalProperties: Type.Undefined() },
    );
};

const Constant = (literal: string, type: () => any) =>
    Type.Intersect(
        [
            Atomic(literal, type),
            Type.Object({
                enum: Type.Array(type()),
            }),
        ],
        { additionalProperties: Type.Undefined() },
    );

const Array = <T extends TSchema>(schema: T) =>
    Type.Object(
        {
            type: Type.Literal("array"),
            items: schema,
            nullable: Type.Boolean(),
            ...Attribute,
        },
        { additionalProperties: Type.Undefined() },
    );

const Tuple = <T extends TSchema>(schema: T) =>
    Type.Object(
        {
            type: Type.Literal("array"),
            items: Type.Array(schema),
            nullable: Type.Boolean(),
            ...Attribute,
        },
        { additionalProperties: Type.Undefined() },
    );

const Reference = Type.Object(
    {
        $ref: Type.String(),
        ...Attribute,
    },
    { additionalProperties: Type.Undefined() },
);

const RecursiveReference = Type.Object(
    {
        $recursiveRef: Type.String(),
        ...Attribute,
    },
    { additionalProperties: Type.Undefined() },
);

const OneOf = <T extends TSchema>(schema: T) =>
    Type.Object(
        {
            oneOf: Type.Array(schema),
            ...Attribute,
        },
        { additionalProperties: Type.Undefined() },
    );

const ObjectDef = <T extends TSchema>(schema: T) =>
    Type.Object(
        {
            $id: Type.Optional(Type.String()),
            type: Type.Literal("object"),
            nullable: Type.Boolean(),
            properties: Type.Record(Type.String(), schema),
            patternProperties: Type.Optional(
                Type.Record(Type.String(), schema),
            ),
            additionalProperties: Type.Optional(schema),
            required: Type.Optional(Type.Array(Type.String())),
            description: Type.Optional(Type.String()),
            "x-tson_jsDocTags": Type.Optional(Type.Array(Type.Any())),
            $recursiveAnchor: Type.Optional(Type.Boolean()),
        },
        { additionalProperties: Type.Undefined() },
    );

const Components = <T extends TSchema>(schema: T) =>
    Type.Object(
        {
            schemas: Type.Record(Type.String(), ObjectDef(schema)),
        },
        { additionalProperties: Type.Undefined() },
    );

const Application = <T extends TSchema>(schema: T) =>
    Type.Object(
        {
            schemas: Type.Array(schema),
            components: Components(schema),
            purpose: Type.Union([Type.Literal("swagger"), Type.Literal("ajv")]),
            prefix: Type.String(),
        },
        { additionalProperties: Type.Undefined() },
    );

const Schema = Type.Recursive((schema) =>
    Type.Union([
        Atomic("boolean", () => Type.Boolean()),
        Atomic("integer", () => Type.Number()),
        Atomic("bigint", () => Type.Number()),
        Atomic("number", () => Type.Number()),
        Atomic("string", () => Type.String()),
        Constant("boolean", () => Type.Boolean()),
        Constant("integer", () => Type.Number()),
        Constant("bigint", () => Type.Number()),
        Constant("number", () => Type.Number()),
        Constant("string", () => Type.String()),
        Array(schema),
        Tuple(schema),
        Reference,
        RecursiveReference,
        OneOf(schema),
        Unknown,
        NullOnly,
    ]),
);

export const __TypeBoxUltimateUnion = Type.Array(Application(Schema));
export const TypeBoxUltimateUnion = TypeCompiler.Compile(
    __TypeBoxUltimateUnion,
);

I'm actually super keen to see TSON / Ajv validation alignment most of all. Would be great get the schemas aligned as good as possible so they might serve as a good reference point for future updates to TSON.application<[T], 'ajv'> (or possibly a future TSON.application<T, 'json-schema'> which would be amazing)

See comment #258 (comment) for a possible Feature Request (and reference implementation TS compiler codegen project here which you're free to use and extend)

Cheers mate!
S

@samchon samchon deleted the features/fuctional branch November 7, 2022 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants