Initial implementation of data types and base schema (#7644)
This commit is contained in:
@@ -24,6 +24,7 @@
|
|||||||
"@medusajs/types": "^1.11.16",
|
"@medusajs/types": "^1.11.16",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"cross-env": "^5.2.1",
|
"cross-env": "^5.2.1",
|
||||||
|
"expect-type": "^0.19.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"jest": "^29.6.3",
|
"jest": "^29.6.3",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
|
|||||||
@@ -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<T> extends BaseRelationship<T> {
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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<string> {
|
||||||
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
|
name: "string",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = new StringSchema()
|
||||||
|
|
||||||
|
expectTypeOf(schema["$dataType"]).toEqualTypeOf<string>()
|
||||||
|
expect(schema.parse("username")).toEqual({
|
||||||
|
fieldName: "username",
|
||||||
|
dataType: {
|
||||||
|
name: "string",
|
||||||
|
},
|
||||||
|
nullable: false,
|
||||||
|
optional: false,
|
||||||
|
indexes: [],
|
||||||
|
relationships: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("apply nullable modifier", () => {
|
||||||
|
class StringSchema extends BaseSchema<string> {
|
||||||
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
|
name: "string",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = new StringSchema().nullable()
|
||||||
|
|
||||||
|
expectTypeOf(schema["$dataType"]).toEqualTypeOf<string | null>()
|
||||||
|
expect(schema.parse("username")).toEqual({
|
||||||
|
fieldName: "username",
|
||||||
|
dataType: {
|
||||||
|
name: "string",
|
||||||
|
},
|
||||||
|
nullable: true,
|
||||||
|
optional: false,
|
||||||
|
indexes: [],
|
||||||
|
relationships: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("apply optional modifier", () => {
|
||||||
|
class StringSchema extends BaseSchema<string> {
|
||||||
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
|
name: "string",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = new StringSchema().optional()
|
||||||
|
|
||||||
|
expectTypeOf(schema["$dataType"]).toEqualTypeOf<string | undefined>()
|
||||||
|
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<string> {
|
||||||
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
|
name: "string",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = new StringSchema().optional().nullable()
|
||||||
|
|
||||||
|
expectTypeOf(schema["$dataType"]).toEqualTypeOf<string | undefined | null>()
|
||||||
|
expect(schema.parse("username")).toEqual({
|
||||||
|
fieldName: "username",
|
||||||
|
dataType: {
|
||||||
|
name: "string",
|
||||||
|
},
|
||||||
|
nullable: true,
|
||||||
|
optional: true,
|
||||||
|
indexes: [],
|
||||||
|
relationships: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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<boolean>()
|
||||||
|
expect(schema.parse("isAdmin")).toEqual({
|
||||||
|
fieldName: "isAdmin",
|
||||||
|
dataType: {
|
||||||
|
name: "boolean",
|
||||||
|
},
|
||||||
|
nullable: false,
|
||||||
|
optional: false,
|
||||||
|
indexes: [],
|
||||||
|
relationships: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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<Date>()
|
||||||
|
expect(schema.parse("created_at")).toEqual({
|
||||||
|
fieldName: "created_at",
|
||||||
|
dataType: {
|
||||||
|
name: "dateTime",
|
||||||
|
},
|
||||||
|
nullable: false,
|
||||||
|
optional: false,
|
||||||
|
indexes: [],
|
||||||
|
relationships: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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<InstanceType<Infer<typeof user>>>().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<InstanceType<Infer<typeof user>>>().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<InstanceType<Infer<typeof user>>>().toMatchTypeOf<{
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
orders: MikroORMEntity<{
|
||||||
|
amount: number
|
||||||
|
user: MikroORMEntity<{
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
}>
|
||||||
|
}>
|
||||||
|
}>()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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: {},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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: {},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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<string>()
|
||||||
|
expect(schema.parse("coordinates")).toEqual({
|
||||||
|
fieldName: "coordinates",
|
||||||
|
dataType: {
|
||||||
|
name: "json",
|
||||||
|
},
|
||||||
|
nullable: false,
|
||||||
|
optional: false,
|
||||||
|
indexes: [],
|
||||||
|
relationships: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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: {},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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<number>()
|
||||||
|
expect(schema.parse("age")).toEqual({
|
||||||
|
fieldName: "age",
|
||||||
|
dataType: {
|
||||||
|
name: "number",
|
||||||
|
},
|
||||||
|
nullable: false,
|
||||||
|
optional: false,
|
||||||
|
indexes: [],
|
||||||
|
relationships: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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<string>()
|
||||||
|
expect(schema.parse("username")).toEqual({
|
||||||
|
fieldName: "username",
|
||||||
|
dataType: {
|
||||||
|
name: "string",
|
||||||
|
},
|
||||||
|
nullable: false,
|
||||||
|
optional: false,
|
||||||
|
indexes: [],
|
||||||
|
relationships: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { RelationshipType, SchemaType } from "./types"
|
||||||
|
|
||||||
|
export class DmlEntity<
|
||||||
|
Schema extends Record<string, SchemaType<any> | RelationshipType<any>>
|
||||||
|
> {
|
||||||
|
#name: string
|
||||||
|
#schema: Schema
|
||||||
|
|
||||||
|
constructor(name: string, schema: Schema) {
|
||||||
|
this.#name = name
|
||||||
|
this.#schema = schema
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<string, SchemaType<any> | RelationshipType<any>>
|
||||||
|
>(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<T>(entityBuilder: T, options?: Record<string, any>) {
|
||||||
|
return new HasOne<T>(entityBuilder, options || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
hasMany<T>(entityBuilder: T, options?: Record<string, any>) {
|
||||||
|
return new HasMany<T>(entityBuilder, options || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
manyToMany<T>(entityBuilder: T, options?: Record<string, any>) {
|
||||||
|
return new ManyToMany<T>(entityBuilder, options || {})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { SchemaType } from "../types"
|
||||||
|
import { OptionalModifier } from "./optional"
|
||||||
|
|
||||||
|
export class NullableModifier<T> {
|
||||||
|
/**
|
||||||
|
* 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<T>
|
||||||
|
|
||||||
|
constructor(schema: SchemaType<T>) {
|
||||||
|
this.#schema = schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply optional modifier on the schema
|
||||||
|
*/
|
||||||
|
optional() {
|
||||||
|
return new OptionalModifier<T | null>(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the serialized metadata
|
||||||
|
*/
|
||||||
|
parse(fieldName: string) {
|
||||||
|
const schema = this.#schema.parse(fieldName)
|
||||||
|
schema.nullable = true
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { SchemaType } from "../types"
|
||||||
|
import { NullableModifier } from "./nullable"
|
||||||
|
|
||||||
|
export class OptionalModifier<T> {
|
||||||
|
/**
|
||||||
|
* 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<T>
|
||||||
|
|
||||||
|
constructor(schema: SchemaType<T>) {
|
||||||
|
this.#schema = schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply nullable modifier on the schema
|
||||||
|
*/
|
||||||
|
nullable() {
|
||||||
|
return new NullableModifier<T | undefined>(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the serialized metadata
|
||||||
|
*/
|
||||||
|
parse(fieldName: string) {
|
||||||
|
const schema = this.#schema.parse(fieldName)
|
||||||
|
schema.optional = true
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { RelationshipMetadata, RelationshipType } from "../types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BaseRelationship encapsulates the repetitive parts of defining
|
||||||
|
* a relationship
|
||||||
|
*/
|
||||||
|
export abstract class BaseRelationship<T> implements RelationshipType<T> {
|
||||||
|
#referencedEntity: T
|
||||||
|
#options: Record<string, any>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<string, any>) {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { BaseRelationship } from "./base"
|
||||||
|
import { RelationshipMetadata } from "../types"
|
||||||
|
|
||||||
|
export class HasMany<T> extends BaseRelationship<T> {
|
||||||
|
protected relationshipType: RelationshipMetadata["type"] = "hasMany"
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { BaseRelationship } from "./base"
|
||||||
|
import { RelationshipMetadata } from "../types"
|
||||||
|
|
||||||
|
export class HasOne<T> extends BaseRelationship<T> {
|
||||||
|
protected relationshipType: RelationshipMetadata["type"] = "hasOne"
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { BaseRelationship } from "./base"
|
||||||
|
import { RelationshipMetadata } from "../types"
|
||||||
|
|
||||||
|
export class ManyToMany<T> extends BaseRelationship<T> {
|
||||||
|
protected relationshipType: RelationshipMetadata["type"] = "manyToMany"
|
||||||
|
}
|
||||||
@@ -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<T> implements SchemaType<T> {
|
||||||
|
#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<T>(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply optional modifier on the schema
|
||||||
|
*/
|
||||||
|
optional() {
|
||||||
|
return new OptionalModifier<T>(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the serialized metadata
|
||||||
|
*/
|
||||||
|
parse(fieldName: string): SchemaMetadata {
|
||||||
|
return {
|
||||||
|
fieldName,
|
||||||
|
dataType: this.dataType,
|
||||||
|
nullable: false,
|
||||||
|
optional: false,
|
||||||
|
indexes: this.#indexes,
|
||||||
|
relationships: this.#relationships,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { SchemaMetadata } from "../types"
|
||||||
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
export class BooleanSchema extends BaseSchema<boolean> {
|
||||||
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
|
name: "boolean",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { SchemaMetadata } from "../types"
|
||||||
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
export class DateTimeSchema extends BaseSchema<Date> {
|
||||||
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
|
name: "dateTime",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { SchemaMetadata } from "../types"
|
||||||
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
export class EnumSchema<
|
||||||
|
const Values extends unknown
|
||||||
|
> extends BaseSchema<Values> {
|
||||||
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
|
name: "enum",
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(values: Values[]) {
|
||||||
|
super()
|
||||||
|
this.dataType.options = { choices: values }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { SchemaMetadata } from "../types"
|
||||||
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
export class JSONSchema extends BaseSchema<string> {
|
||||||
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
|
name: "json",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { SchemaMetadata } from "../types"
|
||||||
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
export class NumberSchema extends BaseSchema<number> {
|
||||||
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
|
name: "number",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { SchemaMetadata } from "../types"
|
||||||
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
export class TextSchema extends BaseSchema<string> {
|
||||||
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
|
name: "string",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const MIKRO_ORM_ENTITY_GENERATOR = Symbol.for(
|
||||||
|
"generate_mikro_orm_entity"
|
||||||
|
)
|
||||||
@@ -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<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The meta-data returned by the schema parse method
|
||||||
|
*/
|
||||||
|
export type SchemaMetadata = {
|
||||||
|
nullable: boolean
|
||||||
|
optional: boolean
|
||||||
|
fieldName: string
|
||||||
|
dataType: {
|
||||||
|
name: KnownDataTypes
|
||||||
|
options?: Record<string, any>
|
||||||
|
}
|
||||||
|
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<T> = {
|
||||||
|
$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<T> = {
|
||||||
|
$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<Props> extends Function {
|
||||||
|
new (): Props
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to infer the schema type of a DmlEntity
|
||||||
|
*/
|
||||||
|
export type Infer<T> = T extends DmlEntity<infer Schema>
|
||||||
|
? MikroORMEntity<{
|
||||||
|
[K in keyof Schema]: Schema[K]["$dataType"] extends () => infer R
|
||||||
|
? Infer<R>
|
||||||
|
: Schema[K]["$dataType"]
|
||||||
|
}>
|
||||||
|
: never
|
||||||
@@ -5826,6 +5826,7 @@ __metadata:
|
|||||||
bignumber.js: ^9.1.2
|
bignumber.js: ^9.1.2
|
||||||
cross-env: ^5.2.1
|
cross-env: ^5.2.1
|
||||||
dotenv: ^16.4.5
|
dotenv: ^16.4.5
|
||||||
|
expect-type: ^0.19.0
|
||||||
express: ^4.18.2
|
express: ^4.18.2
|
||||||
jest: ^29.6.3
|
jest: ^29.6.3
|
||||||
jsonwebtoken: ^9.0.2
|
jsonwebtoken: ^9.0.2
|
||||||
@@ -17233,6 +17234,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"expect@npm:^25.5.0":
|
||||||
version: 25.5.0
|
version: 25.5.0
|
||||||
resolution: "expect@npm:25.5.0"
|
resolution: "expect@npm:25.5.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user