Skip to content

[BUG] crashes (OOM) when used with schema.org types #1710

@HunterLarco

Description

@HunterLarco

Bug Report

📝 Summary

  • Node Version: 20.18.0
  • Typia Version: 11.0.0
  • Expected behavior: typia.createValidate<schema.Recipe>() successfully compiles
  • Actual behavior: OOM (out of memory crash)

schema.org defines a shared vocabulary for many common web applications and is often how social media and web scraping applications derive metadata from links. The official typescript types (schema-dts) are very deep and cause OOM when used with typia.

For example, this code runs for 33min before crashing (log):

import * as typia from "typia";
import * as schema from "schema-dts";

export const validateRecipe = typia.createValidate<schema.Recipe>();

I suspect that typia is missing opportunities to collapse generic trees and is actually running in an infinite loop. But I have not proven this.

⏯ Playground Link

Not applicable because this bug requires using the schema-dts npm package which is not available in the playground. Instead see the above detailed description and below code example.

💻 Code occurring the bug

import * as typia from "typia";
import * as schema from "schema-dts";

export const validateRecipe = typia.createValidate<schema.Recipe>();

👾 Debugging

I believe the culprit is this schema-dts type:

type SchemaValue<T, TProperty extends string> = T | Role<T, TProperty> | readonly (T | Role<T, TProperty>)[];

Because every value in a schema can also be a role (which opens up a huge subtree of types) and it's parameterized by a field, this blows up the type graph enormously. That said, typia should be able to improve execution here if it doesn't already:

interface RoleBase extends ThingBase {
    /** The end date and time of the item (in {@link http://en.wikipedia.org/wiki/ISO_8601 ISO 8601 date format}). */
    "endDate"?: SchemaValue<Date | DateTime, "endDate">;
    /**   
     * A position played, performed or filled by a person or organization, as part of an organization. For example, an athlete in a SportsTeam might play in the position named 'Quarterback'.
     *
     * @deprecated Consider using https://schema.org/roleName instead.
     */
    "namedPosition"?: SchemaValue<Text | URL, "namedPosition">;
    /** A role played, performed or filled by a person or organization. For example, the team of creators for a comic book might fill the roles named 'inker', 'penciller', and 'letterer'; or an athlete in a SportsTeam might play in the position named 'Quarterback'. */
    "roleName"?: SchemaValue<Text | URL, "roleName">;
    /** The start date and time of the item (in {@link http://en.wikipedia.org/wiki/ISO_8601 ISO 8601 date format}). */
    "startDate"?: SchemaValue<Date | DateTime, "startDate">;
}

type RoleLeaf<TContent, TProperty extends string> = RoleBase & {
    "@type": "Role";
} & {
    [key in TProperty]: TContent;
};

export type Role<TContent = never, TProperty extends string = never> = RoleLeaf<TContent, TProperty> | LinkRole<TContent, TProperty> | OrganizationRole<TContent, TProperty> | PerformanceRole<TContent, TProperty>;

The Role type looks like the above, in which TProperty is actually only used one of the intersection types, meaning that typia should be able to use a common validation helper for RoleBase. I suspect it's not doing this and instead if fully generating the entire validation tree from Role for every field which leads to exponential complexity.

Here's an example playground using the above types.

Note that you may run into "nonsensible intersection" when working on this. See google/schema-dts#205 which has been merged but not released yet to resolve this. In the example playground above I've resolved this by adopting the same technique as seen in google/schema-dts#205.

Metadata

Metadata

Assignees

Labels

help wantedExtra attention is needed

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions