diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json index a5ca5afa1d..5d9a57b035 100644 --- a/packages/core/utils/package.json +++ b/packages/core/utils/package.json @@ -24,6 +24,7 @@ "@medusajs/types": "^1.11.16", "@types/express": "^4.17.17", "cross-env": "^5.2.1", + "expect-type": "^0.19.0", "express": "^4.18.2", "jest": "^29.6.3", "rimraf": "^5.0.1", diff --git a/packages/core/utils/src/dml/__tests__/base_relationship.spec.ts b/packages/core/utils/src/dml/__tests__/base_relationship.spec.ts new file mode 100644 index 0000000000..6e2773bbce --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/base_relationship.spec.ts @@ -0,0 +1,30 @@ +import { expectTypeOf } from "expect-type" +import { TextSchema } from "../schema/text" +import { BaseRelationship } from "../relations/base" + +describe("Base relationship", () => { + test("define a custom relationship", () => { + class HasOne extends BaseRelationship { + protected relationshipType: "hasOne" | "hasMany" | "manyToMany" = "hasOne" + } + + const user = { + username: new TextSchema(), + } + + const entityRef = () => user + const relationship = new HasOne(entityRef, { + foreignKey: "user_id", + }) + + expectTypeOf(relationship["$dataType"]).toEqualTypeOf<() => typeof user>() + expect(relationship.parse("user")).toEqual({ + name: "user", + type: "hasOne", + entity: entityRef, + options: { + foreignKey: "user_id", + }, + }) + }) +}) diff --git a/packages/core/utils/src/dml/__tests__/base_schema.spec.ts b/packages/core/utils/src/dml/__tests__/base_schema.spec.ts new file mode 100644 index 0000000000..6bede8522a --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/base_schema.spec.ts @@ -0,0 +1,93 @@ +import { expectTypeOf } from "expect-type" +import { BaseSchema } from "../schema/base" +import { SchemaMetadata } from "../types" + +describe("Base schema", () => { + test("create a schema type from base schema", () => { + class StringSchema extends BaseSchema { + protected dataType: SchemaMetadata["dataType"] = { + name: "string", + } + } + + const schema = new StringSchema() + + expectTypeOf(schema["$dataType"]).toEqualTypeOf() + expect(schema.parse("username")).toEqual({ + fieldName: "username", + dataType: { + name: "string", + }, + nullable: false, + optional: false, + indexes: [], + relationships: [], + }) + }) + + test("apply nullable modifier", () => { + class StringSchema extends BaseSchema { + protected dataType: SchemaMetadata["dataType"] = { + name: "string", + } + } + + const schema = new StringSchema().nullable() + + expectTypeOf(schema["$dataType"]).toEqualTypeOf() + expect(schema.parse("username")).toEqual({ + fieldName: "username", + dataType: { + name: "string", + }, + nullable: true, + optional: false, + indexes: [], + relationships: [], + }) + }) + + test("apply optional modifier", () => { + class StringSchema extends BaseSchema { + protected dataType: SchemaMetadata["dataType"] = { + name: "string", + } + } + + const schema = new StringSchema().optional() + + expectTypeOf(schema["$dataType"]).toEqualTypeOf() + expect(schema.parse("username")).toEqual({ + fieldName: "username", + dataType: { + name: "string", + }, + nullable: false, + optional: true, + indexes: [], + relationships: [], + }) + }) + + test("apply optional + nullable modifier", () => { + class StringSchema extends BaseSchema { + protected dataType: SchemaMetadata["dataType"] = { + name: "string", + } + } + + const schema = new StringSchema().optional().nullable() + + expectTypeOf(schema["$dataType"]).toEqualTypeOf() + expect(schema.parse("username")).toEqual({ + fieldName: "username", + dataType: { + name: "string", + }, + nullable: true, + optional: true, + indexes: [], + relationships: [], + }) + }) +}) diff --git a/packages/core/utils/src/dml/__tests__/boolean_schema.spec.ts b/packages/core/utils/src/dml/__tests__/boolean_schema.spec.ts new file mode 100644 index 0000000000..ab1ff798e3 --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/boolean_schema.spec.ts @@ -0,0 +1,20 @@ +import { expectTypeOf } from "expect-type" +import { BooleanSchema } from "../schema/boolean" + +describe("Boolean schema", () => { + test("create boolean schema type", () => { + const schema = new BooleanSchema() + + expectTypeOf(schema["$dataType"]).toEqualTypeOf() + expect(schema.parse("isAdmin")).toEqual({ + fieldName: "isAdmin", + dataType: { + name: "boolean", + }, + nullable: false, + optional: false, + indexes: [], + relationships: [], + }) + }) +}) diff --git a/packages/core/utils/src/dml/__tests__/date_time_schema.spec.ts b/packages/core/utils/src/dml/__tests__/date_time_schema.spec.ts new file mode 100644 index 0000000000..cebfad687a --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/date_time_schema.spec.ts @@ -0,0 +1,20 @@ +import { expectTypeOf } from "expect-type" +import { DateTimeSchema } from "../schema/date_time" + +describe("DateTime schema", () => { + test("create datetime schema type", () => { + const schema = new DateTimeSchema() + + expectTypeOf(schema["$dataType"]).toEqualTypeOf() + expect(schema.parse("created_at")).toEqual({ + fieldName: "created_at", + dataType: { + name: "dateTime", + }, + nullable: false, + optional: false, + indexes: [], + relationships: [], + }) + }) +}) diff --git a/packages/core/utils/src/dml/__tests__/entity_builder.spec.ts b/packages/core/utils/src/dml/__tests__/entity_builder.spec.ts new file mode 100644 index 0000000000..8de832eeb0 --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/entity_builder.spec.ts @@ -0,0 +1,66 @@ +import { expectTypeOf } from "expect-type" +import { Infer, MikroORMEntity } from "../types" +import { EntityBuilder } from "../entity_builder" + +describe("Entity builder", () => { + test("define an entity", () => { + const model = new EntityBuilder() + const user = model.define("user", { + id: model.number(), + username: model.text(), + email: model.text(), + }) + + expectTypeOf>>().toMatchTypeOf<{ + id: number + username: string + email: string + }>() + }) + + test("define an entity with relationships", () => { + const model = new EntityBuilder() + const email = model.define("email", { + email: model.text(), + isVerified: model.boolean(), + }) + + const user = model.define("user", { + id: model.number(), + username: model.text(), + emails: model.hasMany(() => email), + }) + + expectTypeOf>>().toEqualTypeOf<{ + id: number + username: string + emails: MikroORMEntity<{ email: string; isVerified: boolean }> + }>() + }) + + test("define an entity with recursive relationships", () => { + const model = new EntityBuilder() + const order = model.define("order", { + amount: model.number(), + user: model.hasOne(() => user), + }) + + const user = model.define("user", { + id: model.number(), + username: model.text(), + orders: model.hasMany(() => order), + }) + + expectTypeOf>>().toMatchTypeOf<{ + id: number + username: string + orders: MikroORMEntity<{ + amount: number + user: MikroORMEntity<{ + id: number + username: string + }> + }> + }>() + }) +}) diff --git a/packages/core/utils/src/dml/__tests__/enum_schema.spec.ts b/packages/core/utils/src/dml/__tests__/enum_schema.spec.ts new file mode 100644 index 0000000000..c4649a8496 --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/enum_schema.spec.ts @@ -0,0 +1,49 @@ +import { expectTypeOf } from "expect-type" +import { EnumSchema } from "../schema/enum" + +describe("Enum schema", () => { + test("create enum schema type", () => { + const schema = new EnumSchema(["admin", "moderator", "editor"]) + expectTypeOf(schema["$dataType"]).toEqualTypeOf< + "admin" | "moderator" | "editor" + >() + + expect(schema.parse("role")).toEqual({ + fieldName: "role", + dataType: { + name: "enum", + options: { + choices: ["admin", "moderator", "editor"], + }, + }, + nullable: false, + optional: false, + indexes: [], + relationships: [], + }) + }) + + test("apply nullable and optional modifiers", () => { + const schema = new EnumSchema(["admin", "moderator", "editor"]) + .nullable() + .optional() + + expectTypeOf(schema["$dataType"]).toEqualTypeOf< + "admin" | "moderator" | "editor" | null | undefined + >() + + expect(schema.parse("role")).toEqual({ + fieldName: "role", + dataType: { + name: "enum", + options: { + choices: ["admin", "moderator", "editor"], + }, + }, + nullable: true, + optional: true, + indexes: [], + relationships: [], + }) + }) +}) diff --git a/packages/core/utils/src/dml/__tests__/has_many_relationship.spec.ts b/packages/core/utils/src/dml/__tests__/has_many_relationship.spec.ts new file mode 100644 index 0000000000..2c3129337c --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/has_many_relationship.spec.ts @@ -0,0 +1,22 @@ +import { expectTypeOf } from "expect-type" +import { HasMany } from "../relations/has_many" +import { TextSchema } from "../schema/text" + +describe("HasMany relationship", () => { + test("define hasMany relationship", () => { + const user = { + username: new TextSchema(), + } + + const entityRef = () => user + const relationship = new HasMany(entityRef, {}) + + expectTypeOf(relationship["$dataType"]).toEqualTypeOf<() => typeof user>() + expect(relationship.parse("user")).toEqual({ + name: "user", + type: "hasMany", + entity: entityRef, + options: {}, + }) + }) +}) diff --git a/packages/core/utils/src/dml/__tests__/has_one_relationship.spec.ts b/packages/core/utils/src/dml/__tests__/has_one_relationship.spec.ts new file mode 100644 index 0000000000..d592f01aa2 --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/has_one_relationship.spec.ts @@ -0,0 +1,22 @@ +import { expectTypeOf } from "expect-type" +import { HasOne } from "../relations/has_one" +import { TextSchema } from "../schema/text" + +describe("HasOne relationship", () => { + test("define hasOne relationship", () => { + const user = { + username: new TextSchema(), + } + + const entityRef = () => user + const relationship = new HasOne(entityRef, {}) + + expectTypeOf(relationship["$dataType"]).toEqualTypeOf<() => typeof user>() + expect(relationship.parse("user")).toEqual({ + name: "user", + type: "hasOne", + entity: entityRef, + options: {}, + }) + }) +}) diff --git a/packages/core/utils/src/dml/__tests__/json_schema.spec.ts b/packages/core/utils/src/dml/__tests__/json_schema.spec.ts new file mode 100644 index 0000000000..a56b753c6c --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/json_schema.spec.ts @@ -0,0 +1,20 @@ +import { expectTypeOf } from "expect-type" +import { JSONSchema } from "../schema/json" + +describe("JSON schema", () => { + test("create json schema type", () => { + const schema = new JSONSchema() + + expectTypeOf(schema["$dataType"]).toEqualTypeOf() + expect(schema.parse("coordinates")).toEqual({ + fieldName: "coordinates", + dataType: { + name: "json", + }, + nullable: false, + optional: false, + indexes: [], + relationships: [], + }) + }) +}) diff --git a/packages/core/utils/src/dml/__tests__/many_to_many.spec.ts b/packages/core/utils/src/dml/__tests__/many_to_many.spec.ts new file mode 100644 index 0000000000..685650e4e8 --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/many_to_many.spec.ts @@ -0,0 +1,22 @@ +import { expectTypeOf } from "expect-type" +import { TextSchema } from "../schema/text" +import { ManyToMany } from "../relations/many_to_many" + +describe("ManyToMany relationship", () => { + test("define manyToMany relationship", () => { + const user = { + username: new TextSchema(), + } + + const entityRef = () => user + const relationship = new ManyToMany(entityRef, {}) + + expectTypeOf(relationship["$dataType"]).toEqualTypeOf<() => typeof user>() + expect(relationship.parse("user")).toEqual({ + name: "user", + type: "manyToMany", + entity: entityRef, + options: {}, + }) + }) +}) diff --git a/packages/core/utils/src/dml/__tests__/number_schema.spec.ts b/packages/core/utils/src/dml/__tests__/number_schema.spec.ts new file mode 100644 index 0000000000..8f2909aa86 --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/number_schema.spec.ts @@ -0,0 +1,20 @@ +import { expectTypeOf } from "expect-type" +import { NumberSchema } from "../schema/number" + +describe("Number schema", () => { + test("create number schema type", () => { + const schema = new NumberSchema() + + expectTypeOf(schema["$dataType"]).toEqualTypeOf() + expect(schema.parse("age")).toEqual({ + fieldName: "age", + dataType: { + name: "number", + }, + nullable: false, + optional: false, + indexes: [], + relationships: [], + }) + }) +}) diff --git a/packages/core/utils/src/dml/__tests__/text_schema.spec.ts b/packages/core/utils/src/dml/__tests__/text_schema.spec.ts new file mode 100644 index 0000000000..ef1b7b27f8 --- /dev/null +++ b/packages/core/utils/src/dml/__tests__/text_schema.spec.ts @@ -0,0 +1,20 @@ +import { expectTypeOf } from "expect-type" +import { TextSchema } from "../schema/text" + +describe("String schema", () => { + test("create string schema type", () => { + const schema = new TextSchema() + + expectTypeOf(schema["$dataType"]).toEqualTypeOf() + expect(schema.parse("username")).toEqual({ + fieldName: "username", + dataType: { + name: "string", + }, + nullable: false, + optional: false, + indexes: [], + relationships: [], + }) + }) +}) diff --git a/packages/core/utils/src/dml/entity.ts b/packages/core/utils/src/dml/entity.ts new file mode 100644 index 0000000000..6957bffa36 --- /dev/null +++ b/packages/core/utils/src/dml/entity.ts @@ -0,0 +1,13 @@ +import { RelationshipType, SchemaType } from "./types" + +export class DmlEntity< + Schema extends Record | RelationshipType> +> { + #name: string + #schema: Schema + + constructor(name: string, schema: Schema) { + this.#name = name + this.#schema = schema + } +} diff --git a/packages/core/utils/src/dml/entity_builder.ts b/packages/core/utils/src/dml/entity_builder.ts new file mode 100644 index 0000000000..ad11ae5597 --- /dev/null +++ b/packages/core/utils/src/dml/entity_builder.ts @@ -0,0 +1,50 @@ +import { DmlEntity } from "./entity" +import { TextSchema } from "./schema/text" +import { JSONSchema } from "./schema/json" +import { HasOne } from "./relations/has_one" +import { HasMany } from "./relations/has_many" +import { NumberSchema } from "./schema/number" +import { BooleanSchema } from "./schema/boolean" +import { DateTimeSchema } from "./schema/date_time" +import { ManyToMany } from "./relations/many_to_many" +import { RelationshipType, SchemaType } from "./types" + +export class EntityBuilder { + define< + Schema extends Record | RelationshipType> + >(name: string, schema: Schema) { + return new DmlEntity(name, schema) + } + + text() { + return new TextSchema() + } + + boolean() { + return new BooleanSchema() + } + + number() { + return new NumberSchema() + } + + dateTime() { + return new DateTimeSchema() + } + + json() { + return new JSONSchema() + } + + hasOne(entityBuilder: T, options?: Record) { + return new HasOne(entityBuilder, options || {}) + } + + hasMany(entityBuilder: T, options?: Record) { + return new HasMany(entityBuilder, options || {}) + } + + manyToMany(entityBuilder: T, options?: Record) { + return new ManyToMany(entityBuilder, options || {}) + } +} diff --git a/packages/core/utils/src/dml/modifiers/nullable.ts b/packages/core/utils/src/dml/modifiers/nullable.ts new file mode 100644 index 0000000000..2390c635b3 --- /dev/null +++ b/packages/core/utils/src/dml/modifiers/nullable.ts @@ -0,0 +1,36 @@ +import { SchemaType } from "../types" +import { OptionalModifier } from "./optional" + +export class NullableModifier { + /** + * A type-only property to infer the JavScript data-type + * of the schema property + */ + declare $dataType: T | null + + /** + * The parent schema on which the nullable modifier is + * applied + */ + #schema: SchemaType + + constructor(schema: SchemaType) { + this.#schema = schema + } + + /** + * Apply optional modifier on the schema + */ + optional() { + return new OptionalModifier(this) + } + + /** + * Returns the serialized metadata + */ + parse(fieldName: string) { + const schema = this.#schema.parse(fieldName) + schema.nullable = true + return schema + } +} diff --git a/packages/core/utils/src/dml/modifiers/optional.ts b/packages/core/utils/src/dml/modifiers/optional.ts new file mode 100644 index 0000000000..1fcb3cec38 --- /dev/null +++ b/packages/core/utils/src/dml/modifiers/optional.ts @@ -0,0 +1,36 @@ +import { SchemaType } from "../types" +import { NullableModifier } from "./nullable" + +export class OptionalModifier { + /** + * A type-only property to infer the JavScript data-type + * of the schema property + */ + declare $dataType: T | undefined + + /** + * The parent schema on which the nullable modifier is + * applied + */ + #schema: SchemaType + + constructor(schema: SchemaType) { + this.#schema = schema + } + + /** + * Apply nullable modifier on the schema + */ + nullable() { + return new NullableModifier(this) + } + + /** + * Returns the serialized metadata + */ + parse(fieldName: string) { + const schema = this.#schema.parse(fieldName) + schema.optional = true + return schema + } +} diff --git a/packages/core/utils/src/dml/relations/base.ts b/packages/core/utils/src/dml/relations/base.ts new file mode 100644 index 0000000000..b3a724dd56 --- /dev/null +++ b/packages/core/utils/src/dml/relations/base.ts @@ -0,0 +1,38 @@ +import { RelationshipMetadata, RelationshipType } from "../types" + +/** + * The BaseRelationship encapsulates the repetitive parts of defining + * a relationship + */ +export abstract class BaseRelationship implements RelationshipType { + #referencedEntity: T + #options: Record + + /** + * The relationship type. + */ + protected abstract relationshipType: RelationshipMetadata["type"] + + /** + * A type-only property to infer the JavScript data-type + * of the relationship property + */ + declare $dataType: T + + constructor(referencedEntity: T, options: Record) { + this.#referencedEntity = referencedEntity + this.#options = options + } + + /** + * Returns the parsed copy of the relationship + */ + parse(relationshipName: string): RelationshipMetadata { + return { + name: relationshipName, + entity: this.#referencedEntity, + options: this.#options, + type: this.relationshipType, + } + } +} diff --git a/packages/core/utils/src/dml/relations/has_many.ts b/packages/core/utils/src/dml/relations/has_many.ts new file mode 100644 index 0000000000..9f774c4086 --- /dev/null +++ b/packages/core/utils/src/dml/relations/has_many.ts @@ -0,0 +1,6 @@ +import { BaseRelationship } from "./base" +import { RelationshipMetadata } from "../types" + +export class HasMany extends BaseRelationship { + protected relationshipType: RelationshipMetadata["type"] = "hasMany" +} diff --git a/packages/core/utils/src/dml/relations/has_one.ts b/packages/core/utils/src/dml/relations/has_one.ts new file mode 100644 index 0000000000..2825d8ce21 --- /dev/null +++ b/packages/core/utils/src/dml/relations/has_one.ts @@ -0,0 +1,6 @@ +import { BaseRelationship } from "./base" +import { RelationshipMetadata } from "../types" + +export class HasOne extends BaseRelationship { + protected relationshipType: RelationshipMetadata["type"] = "hasOne" +} diff --git a/packages/core/utils/src/dml/relations/many_to_many.ts b/packages/core/utils/src/dml/relations/many_to_many.ts new file mode 100644 index 0000000000..85b6c1eb87 --- /dev/null +++ b/packages/core/utils/src/dml/relations/many_to_many.ts @@ -0,0 +1,6 @@ +import { BaseRelationship } from "./base" +import { RelationshipMetadata } from "../types" + +export class ManyToMany extends BaseRelationship { + protected relationshipType: RelationshipMetadata["type"] = "manyToMany" +} diff --git a/packages/core/utils/src/dml/schema/base.ts b/packages/core/utils/src/dml/schema/base.ts new file mode 100644 index 0000000000..387220d700 --- /dev/null +++ b/packages/core/utils/src/dml/schema/base.ts @@ -0,0 +1,52 @@ +import { SchemaMetadata, SchemaType } from "../types" +import { NullableModifier } from "../modifiers/nullable" +import { OptionalModifier } from "../modifiers/optional" + +/** + * The base schema class offers shared affordances to define + * schema classes + */ +export abstract class BaseSchema implements SchemaType { + #indexes: SchemaMetadata["indexes"] = [] + #relationships: SchemaMetadata["relationships"] = [] + + /** + * The runtime dataType for the schema. It is not the same as + * the "$dataType". + */ + protected abstract dataType: SchemaMetadata["dataType"] + + /** + * A type-only property to infer the JavScript data-type + * of the schema property + */ + declare $dataType: T + + /** + * Apply nullable modifier on the schema + */ + nullable() { + return new NullableModifier(this) + } + + /** + * Apply optional modifier on the schema + */ + optional() { + return new OptionalModifier(this) + } + + /** + * Returns the serialized metadata + */ + parse(fieldName: string): SchemaMetadata { + return { + fieldName, + dataType: this.dataType, + nullable: false, + optional: false, + indexes: this.#indexes, + relationships: this.#relationships, + } + } +} diff --git a/packages/core/utils/src/dml/schema/boolean.ts b/packages/core/utils/src/dml/schema/boolean.ts new file mode 100644 index 0000000000..530f42317f --- /dev/null +++ b/packages/core/utils/src/dml/schema/boolean.ts @@ -0,0 +1,8 @@ +import { SchemaMetadata } from "../types" +import { BaseSchema } from "./base" + +export class BooleanSchema extends BaseSchema { + protected dataType: SchemaMetadata["dataType"] = { + name: "boolean", + } +} diff --git a/packages/core/utils/src/dml/schema/date_time.ts b/packages/core/utils/src/dml/schema/date_time.ts new file mode 100644 index 0000000000..fb46fc435f --- /dev/null +++ b/packages/core/utils/src/dml/schema/date_time.ts @@ -0,0 +1,8 @@ +import { SchemaMetadata } from "../types" +import { BaseSchema } from "./base" + +export class DateTimeSchema extends BaseSchema { + protected dataType: SchemaMetadata["dataType"] = { + name: "dateTime", + } +} diff --git a/packages/core/utils/src/dml/schema/enum.ts b/packages/core/utils/src/dml/schema/enum.ts new file mode 100644 index 0000000000..bbe2ccbd8a --- /dev/null +++ b/packages/core/utils/src/dml/schema/enum.ts @@ -0,0 +1,15 @@ +import { SchemaMetadata } from "../types" +import { BaseSchema } from "./base" + +export class EnumSchema< + const Values extends unknown +> extends BaseSchema { + protected dataType: SchemaMetadata["dataType"] = { + name: "enum", + } + + constructor(values: Values[]) { + super() + this.dataType.options = { choices: values } + } +} diff --git a/packages/core/utils/src/dml/schema/json.ts b/packages/core/utils/src/dml/schema/json.ts new file mode 100644 index 0000000000..0ed61039c1 --- /dev/null +++ b/packages/core/utils/src/dml/schema/json.ts @@ -0,0 +1,8 @@ +import { SchemaMetadata } from "../types" +import { BaseSchema } from "./base" + +export class JSONSchema extends BaseSchema { + protected dataType: SchemaMetadata["dataType"] = { + name: "json", + } +} diff --git a/packages/core/utils/src/dml/schema/number.ts b/packages/core/utils/src/dml/schema/number.ts new file mode 100644 index 0000000000..bdff8a0032 --- /dev/null +++ b/packages/core/utils/src/dml/schema/number.ts @@ -0,0 +1,8 @@ +import { SchemaMetadata } from "../types" +import { BaseSchema } from "./base" + +export class NumberSchema extends BaseSchema { + protected dataType: SchemaMetadata["dataType"] = { + name: "number", + } +} diff --git a/packages/core/utils/src/dml/schema/text.ts b/packages/core/utils/src/dml/schema/text.ts new file mode 100644 index 0000000000..3a04b4f5c1 --- /dev/null +++ b/packages/core/utils/src/dml/schema/text.ts @@ -0,0 +1,8 @@ +import { SchemaMetadata } from "../types" +import { BaseSchema } from "./base" + +export class TextSchema extends BaseSchema { + protected dataType: SchemaMetadata["dataType"] = { + name: "string", + } +} diff --git a/packages/core/utils/src/dml/symbols.ts b/packages/core/utils/src/dml/symbols.ts new file mode 100644 index 0000000000..8e540cfd9f --- /dev/null +++ b/packages/core/utils/src/dml/symbols.ts @@ -0,0 +1,3 @@ +export const MIKRO_ORM_ENTITY_GENERATOR = Symbol.for( + "generate_mikro_orm_entity" +) diff --git a/packages/core/utils/src/dml/types.ts b/packages/core/utils/src/dml/types.ts new file mode 100644 index 0000000000..1bdf78e52c --- /dev/null +++ b/packages/core/utils/src/dml/types.ts @@ -0,0 +1,82 @@ +import { DmlEntity } from "./entity" + +/** + * The supported data types + */ +export type KnownDataTypes = + | "string" + | "boolean" + | "enum" + | "number" + | "dateTime" + | "json" + | "any" + +/** + * The meta-data returned by the relationship parse + * method + */ +export type RelationshipMetadata = { + name: string + type: "hasOne" | "hasMany" | "manyToMany" + entity: unknown + options: Record +} + +/** + * The meta-data returned by the schema parse method + */ +export type SchemaMetadata = { + nullable: boolean + optional: boolean + fieldName: string + dataType: { + name: KnownDataTypes + options?: Record + } + indexes: { + name: string + type: string + }[] + relationships: RelationshipMetadata[] +} + +/** + * Definition of a schema type. It should have a parse + * method to get the metadata and a type-only property + * to get its static type + */ +export type SchemaType = { + $dataType: T + parse(fieldName: string): SchemaMetadata +} + +/** + * Definition of a relationship type. It should have a parse + * method to get the metadata and a type-only property + * to get its static type + */ +export type RelationshipType = { + $dataType: T + parse(relationshipName: string): RelationshipMetadata +} + +/** + * A type-only representation of a MikroORM entity. Since we generate + * entities on the fly, we need a way to represent a type-safe + * constructor and its instance properties. + */ +export interface MikroORMEntity extends Function { + new (): Props +} + +/** + * Helper to infer the schema type of a DmlEntity + */ +export type Infer = T extends DmlEntity + ? MikroORMEntity<{ + [K in keyof Schema]: Schema[K]["$dataType"] extends () => infer R + ? Infer + : Schema[K]["$dataType"] + }> + : never diff --git a/yarn.lock b/yarn.lock index 989dab6f89..b0edc3d6de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5826,6 +5826,7 @@ __metadata: bignumber.js: ^9.1.2 cross-env: ^5.2.1 dotenv: ^16.4.5 + expect-type: ^0.19.0 express: ^4.18.2 jest: ^29.6.3 jsonwebtoken: ^9.0.2 @@ -17233,6 +17234,13 @@ __metadata: languageName: node linkType: hard +"expect-type@npm:^0.19.0": + version: 0.19.0 + resolution: "expect-type@npm:0.19.0" + checksum: 0a7305021c3e37bf024ce01f0a141de109435ac4225457c35fbd649a0d8cdc19b9078185fd9aaa9ebb136a88c5065d27a60883ab0f961ddc281c11851ed18097 + languageName: node + linkType: hard + "expect@npm:^25.5.0": version: 25.5.0 resolution: "expect@npm:25.5.0"