chore: various DML improvements (#7833)
* chore: various DML improvements * Check is something through static utils * Check is something through static utils * allow to define a schema with table name * restrict searchable to text only * rm searchable modifier * extract constructor logic into separate function
This commit is contained in:
1 parent
4a631e789d
commit
39038ddb0a
18 files changed
+563
-38
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
import { expectTypeOf } from "expect-type"
|
import { expectTypeOf } from "expect-type"
|
||||||
import { BaseProperty } from "../properties/base"
|
import { BaseProperty } from "../properties/base"
|
||||||
import { PropertyMetadata } from "@medusajs/types"
|
import { PropertyMetadata } from "@medusajs/types"
|
||||||
|
import { TextProperty } from "../properties/text"
|
||||||
|
|
||||||
describe("Base property", () => {
|
describe("Base property", () => {
|
||||||
test("create a property type from base property", () => {
|
test("create a property type from base property", () => {
|
||||||
@@ -24,6 +25,25 @@ describe("Base property", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("apply searchable modifier", () => {
|
||||||
|
const property = new TextProperty().searchable()
|
||||||
|
|
||||||
|
expectTypeOf(property["$dataType"]).toEqualTypeOf<string>()
|
||||||
|
expect(property.parse("username")).toEqual({
|
||||||
|
fieldName: "username",
|
||||||
|
dataType: {
|
||||||
|
name: "text",
|
||||||
|
options: {
|
||||||
|
primaryKey: false,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nullable: false,
|
||||||
|
indexes: [],
|
||||||
|
relationships: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("apply nullable modifier", () => {
|
test("apply nullable modifier", () => {
|
||||||
class StringProperty extends BaseProperty<string> {
|
class StringProperty extends BaseProperty<string> {
|
||||||
protected dataType: PropertyMetadata["dataType"] = {
|
protected dataType: PropertyMetadata["dataType"] = {
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ describe("Entity builder", () => {
|
|||||||
spend_limit: model.bigNumber(),
|
spend_limit: model.bigNumber(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(user.name).toEqual("user")
|
||||||
|
expect(user.parse().tableName).toEqual("user")
|
||||||
|
|
||||||
const User = toMikroORMEntity(user)
|
const User = toMikroORMEntity(user)
|
||||||
|
|
||||||
expectTypeOf(new User()).toMatchTypeOf<{
|
expectTypeOf(new User()).toMatchTypeOf<{
|
||||||
@@ -145,6 +148,250 @@ describe("Entity builder", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("define an entity with a table name and a name", () => {
|
||||||
|
const user = model.define(
|
||||||
|
{ name: "user", tableName: "user_table" },
|
||||||
|
{
|
||||||
|
id: model.number(),
|
||||||
|
username: model.text(),
|
||||||
|
email: model.text(),
|
||||||
|
spend_limit: model.bigNumber(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(user.name).toEqual("user")
|
||||||
|
expect(user.parse().tableName).toEqual("user_table")
|
||||||
|
|
||||||
|
const User = toMikroORMEntity(user)
|
||||||
|
|
||||||
|
expectTypeOf(new User()).toMatchTypeOf<{
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
spend_limit: number
|
||||||
|
raw_spend_limit: Record<string, unknown>
|
||||||
|
created_at: Date
|
||||||
|
updated_at: Date
|
||||||
|
deleted_at: Date | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
|
expect(metaData.className).toEqual("User")
|
||||||
|
expect(metaData.path).toEqual("User")
|
||||||
|
|
||||||
|
expect(metaData.filters).toEqual({
|
||||||
|
softDeletable: {
|
||||||
|
name: "softDeletable",
|
||||||
|
cond: expect.any(Function),
|
||||||
|
default: true,
|
||||||
|
args: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(metaData.properties).toEqual({
|
||||||
|
id: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "number",
|
||||||
|
columnType: "integer",
|
||||||
|
name: "id",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "username",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "email",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "created_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
spend_limit: {
|
||||||
|
columnType: "numeric",
|
||||||
|
getter: true,
|
||||||
|
name: "spend_limit",
|
||||||
|
nullable: false,
|
||||||
|
reference: "scalar",
|
||||||
|
setter: true,
|
||||||
|
trackChanges: false,
|
||||||
|
type: "any",
|
||||||
|
},
|
||||||
|
raw_spend_limit: {
|
||||||
|
columnType: "jsonb",
|
||||||
|
getter: false,
|
||||||
|
name: "raw_spend_limit",
|
||||||
|
nullable: false,
|
||||||
|
reference: "scalar",
|
||||||
|
setter: false,
|
||||||
|
type: "any",
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "updated_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
onUpdate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
deleted_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "deleted_at",
|
||||||
|
nullable: true,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("define an entity with a table name only", () => {
|
||||||
|
const user = model.define(
|
||||||
|
{ tableName: "user_role" },
|
||||||
|
{
|
||||||
|
id: model.number(),
|
||||||
|
username: model.text(),
|
||||||
|
email: model.text(),
|
||||||
|
spend_limit: model.bigNumber(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(user.name).toEqual("userRole")
|
||||||
|
expect(user.parse().tableName).toEqual("user_role")
|
||||||
|
|
||||||
|
const User = toMikroORMEntity(user)
|
||||||
|
|
||||||
|
expectTypeOf(new User()).toMatchTypeOf<{
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
spend_limit: number
|
||||||
|
raw_spend_limit: Record<string, unknown>
|
||||||
|
created_at: Date
|
||||||
|
updated_at: Date
|
||||||
|
deleted_at: Date | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
|
expect(metaData.className).toEqual("UserRole")
|
||||||
|
expect(metaData.path).toEqual("UserRole")
|
||||||
|
|
||||||
|
expect(metaData.filters).toEqual({
|
||||||
|
softDeletable: {
|
||||||
|
name: "softDeletable",
|
||||||
|
cond: expect.any(Function),
|
||||||
|
default: true,
|
||||||
|
args: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(metaData.properties).toEqual({
|
||||||
|
id: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "number",
|
||||||
|
columnType: "integer",
|
||||||
|
name: "id",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "username",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "email",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "created_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
spend_limit: {
|
||||||
|
columnType: "numeric",
|
||||||
|
getter: true,
|
||||||
|
name: "spend_limit",
|
||||||
|
nullable: false,
|
||||||
|
reference: "scalar",
|
||||||
|
setter: true,
|
||||||
|
trackChanges: false,
|
||||||
|
type: "any",
|
||||||
|
},
|
||||||
|
raw_spend_limit: {
|
||||||
|
columnType: "jsonb",
|
||||||
|
getter: false,
|
||||||
|
name: "raw_spend_limit",
|
||||||
|
nullable: false,
|
||||||
|
reference: "scalar",
|
||||||
|
setter: false,
|
||||||
|
type: "any",
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "updated_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
onUpdate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
deleted_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "deleted_at",
|
||||||
|
nullable: true,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("define a property with default value", () => {
|
test("define a property with default value", () => {
|
||||||
const user = model.define("user", {
|
const user = model.define("user", {
|
||||||
id: model.number(),
|
id: model.number(),
|
||||||
@@ -258,6 +505,119 @@ describe("Entity builder", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should mark a property as searchable", () => {
|
||||||
|
const user = model.define("user", {
|
||||||
|
id: model.number(),
|
||||||
|
username: model.text().searchable(),
|
||||||
|
email: model.text(),
|
||||||
|
spend_limit: model.bigNumber().default(500.4),
|
||||||
|
})
|
||||||
|
|
||||||
|
const User = toMikroORMEntity(user)
|
||||||
|
expectTypeOf(new User()).toMatchTypeOf<{
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
deleted_at: Date | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
|
expect(metaData.className).toEqual("User")
|
||||||
|
expect(metaData.path).toEqual("User")
|
||||||
|
|
||||||
|
expect(metaData.filters).toEqual({
|
||||||
|
softDeletable: {
|
||||||
|
name: "softDeletable",
|
||||||
|
cond: expect.any(Function),
|
||||||
|
default: true,
|
||||||
|
args: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(metaData.properties).toEqual({
|
||||||
|
id: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "number",
|
||||||
|
columnType: "integer",
|
||||||
|
name: "id",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "username",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "email",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
spend_limit: {
|
||||||
|
columnType: "numeric",
|
||||||
|
default: 500.4,
|
||||||
|
getter: true,
|
||||||
|
name: "spend_limit",
|
||||||
|
nullable: false,
|
||||||
|
reference: "scalar",
|
||||||
|
setter: true,
|
||||||
|
trackChanges: false,
|
||||||
|
type: "any",
|
||||||
|
},
|
||||||
|
raw_spend_limit: {
|
||||||
|
columnType: "jsonb",
|
||||||
|
getter: false,
|
||||||
|
name: "raw_spend_limit",
|
||||||
|
nullable: false,
|
||||||
|
reference: "scalar",
|
||||||
|
setter: false,
|
||||||
|
type: "any",
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "created_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "updated_at",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
onCreate: expect.any(Function),
|
||||||
|
onUpdate: expect.any(Function),
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
deleted_at: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "date",
|
||||||
|
columnType: "timestamptz",
|
||||||
|
name: "deleted_at",
|
||||||
|
nullable: true,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("mark property nullable", () => {
|
test("mark property nullable", () => {
|
||||||
const user = model.define("user", {
|
const user = model.define("user", {
|
||||||
id: model.number(),
|
id: model.number(),
|
||||||
@@ -2472,7 +2832,7 @@ describe("Entity builder", () => {
|
|||||||
email: model.hasOne(() => email),
|
email: model.hasOne(() => email),
|
||||||
})
|
})
|
||||||
|
|
||||||
const [User, Email] = [user, email].map(toMikroORMEntity)
|
const [User, Email] = toMikroOrmEntities([user, email])
|
||||||
|
|
||||||
expectTypeOf(new User()).toMatchTypeOf<{
|
expectTypeOf(new User()).toMatchTypeOf<{
|
||||||
id: number
|
id: number
|
||||||
|
|||||||
@@ -20,4 +20,19 @@ describe("HasMany relationship", () => {
|
|||||||
entity: entityRef,
|
entity: entityRef,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should identify has many relationship", () => {
|
||||||
|
const user = {
|
||||||
|
username: new TextProperty(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityRef = () => user
|
||||||
|
let relationship = new HasMany(entityRef, {})
|
||||||
|
|
||||||
|
expect(HasMany.isHasMany(relationship)).toEqual(true)
|
||||||
|
|
||||||
|
relationship = {} as any
|
||||||
|
|
||||||
|
expect(HasMany.isHasMany(relationship)).toEqual(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -40,4 +40,19 @@ describe("HasOne relationship", () => {
|
|||||||
entity: entityRef,
|
entity: entityRef,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should identify has one relationship", () => {
|
||||||
|
const user = {
|
||||||
|
username: new TextProperty(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityRef = () => user
|
||||||
|
let relationship = new HasOne(entityRef, {})
|
||||||
|
|
||||||
|
expect(HasOne.isHasOne(relationship)).toEqual(true)
|
||||||
|
|
||||||
|
relationship = {} as any
|
||||||
|
|
||||||
|
expect(HasOne.isHasOne(relationship)).toEqual(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -20,4 +20,19 @@ describe("ManyToMany relationship", () => {
|
|||||||
entity: entityRef,
|
entity: entityRef,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should identify many to many relationship", () => {
|
||||||
|
const user = {
|
||||||
|
username: new TextProperty(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityRef = () => user
|
||||||
|
let relationship = new ManyToMany(entityRef, {})
|
||||||
|
|
||||||
|
expect(ManyToMany.isManyToMany(relationship)).toEqual(true)
|
||||||
|
|
||||||
|
relationship = {} as any
|
||||||
|
|
||||||
|
expect(ManyToMany.isManyToMany(relationship)).toEqual(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -10,7 +10,7 @@ describe("Text property", () => {
|
|||||||
fieldName: "username",
|
fieldName: "username",
|
||||||
dataType: {
|
dataType: {
|
||||||
name: "text",
|
name: "text",
|
||||||
options: { primaryKey: false },
|
options: { primaryKey: false, searchable: false },
|
||||||
},
|
},
|
||||||
nullable: false,
|
nullable: false,
|
||||||
indexes: [],
|
indexes: [],
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export type DMLSchema = Record<
|
|||||||
PropertyType<any> | RelationshipType<any>
|
PropertyType<any> | RelationshipType<any>
|
||||||
>
|
>
|
||||||
|
|
||||||
|
type DefineOptions = string | { name?: string; tableName: string }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity builder exposes the API to create an entity and define its
|
* Entity builder exposes the API to create an entity and define its
|
||||||
* schema using the shorthand methods.
|
* schema using the shorthand methods.
|
||||||
@@ -52,10 +54,13 @@ export class EntityBuilder {
|
|||||||
* Define an entity or a model. The name should be unique across
|
* Define an entity or a model. The name should be unique across
|
||||||
* all the entities.
|
* all the entities.
|
||||||
*/
|
*/
|
||||||
define<Schema extends DMLSchema>(name: string, schema: Schema) {
|
define<Schema extends DMLSchema>(
|
||||||
|
nameOrConfig: DefineOptions,
|
||||||
|
schema: Schema
|
||||||
|
) {
|
||||||
this.#disallowImplicitProperties(schema)
|
this.#disallowImplicitProperties(schema)
|
||||||
|
|
||||||
return new DmlEntity(name, {
|
return new DmlEntity(nameOrConfig, {
|
||||||
...schema,
|
...schema,
|
||||||
...createBigNumberProperties(schema),
|
...createBigNumberProperties(schema),
|
||||||
...createDefaultProperties(),
|
...createDefaultProperties(),
|
||||||
|
|||||||
@@ -8,6 +8,40 @@ import {
|
|||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { DMLSchema } from "./entity-builder"
|
import { DMLSchema } from "./entity-builder"
|
||||||
import { BelongsTo } from "./relations/belongs-to"
|
import { BelongsTo } from "./relations/belongs-to"
|
||||||
|
import { isObject, isString, toCamelCase } from "../common"
|
||||||
|
|
||||||
|
type Config = string | { name?: string; tableName: string }
|
||||||
|
|
||||||
|
function extractNameAndTableName(nameOrConfig: Config) {
|
||||||
|
const result = {
|
||||||
|
name: "",
|
||||||
|
tableName: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isString(nameOrConfig)) {
|
||||||
|
const [schema, ...rest] = nameOrConfig.split(".")
|
||||||
|
const name = rest.length ? rest.join(".") : schema
|
||||||
|
result.name = toCamelCase(name)
|
||||||
|
result.tableName = nameOrConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isObject(nameOrConfig)) {
|
||||||
|
if (!nameOrConfig.tableName) {
|
||||||
|
throw new Error(
|
||||||
|
`Missing "tableName" property in the config object for "${nameOrConfig.name}" entity`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const potentialName = nameOrConfig.name ?? nameOrConfig.tableName
|
||||||
|
const [schema, ...rest] = potentialName.split(".")
|
||||||
|
const name = rest.length ? rest.join(".") : schema
|
||||||
|
|
||||||
|
result.name = toCamelCase(name)
|
||||||
|
result.tableName = nameOrConfig.tableName
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dml entity is a representation of a DML model with a unique
|
* Dml entity is a representation of a DML model with a unique
|
||||||
@@ -16,8 +50,16 @@ import { BelongsTo } from "./relations/belongs-to"
|
|||||||
export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
||||||
[IsDmlEntity]: true = true
|
[IsDmlEntity]: true = true
|
||||||
|
|
||||||
|
name: string
|
||||||
|
|
||||||
|
readonly #tableName: string
|
||||||
#cascades: EntityCascades<string[]> = {}
|
#cascades: EntityCascades<string[]> = {}
|
||||||
constructor(public name: string, public schema: Schema) {}
|
|
||||||
|
constructor(nameOrConfig: Config, public schema: Schema) {
|
||||||
|
const { name, tableName } = extractNameAndTableName(nameOrConfig)
|
||||||
|
this.name = name
|
||||||
|
this.#tableName = tableName
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A static method to check if an entity is an instance of DmlEntity.
|
* A static method to check if an entity is an instance of DmlEntity.
|
||||||
@@ -27,11 +69,7 @@ export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
|||||||
* @param entity
|
* @param entity
|
||||||
*/
|
*/
|
||||||
static isDmlEntity(entity: unknown): entity is DmlEntity<any> {
|
static isDmlEntity(entity: unknown): entity is DmlEntity<any> {
|
||||||
return (
|
return !!entity?.[IsDmlEntity]
|
||||||
!!entity &&
|
|
||||||
(entity instanceof DmlEntity ||
|
|
||||||
(typeof entity === "object" && entity[IsDmlEntity] === true))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,11 +77,13 @@ export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
|||||||
*/
|
*/
|
||||||
parse(): {
|
parse(): {
|
||||||
name: string
|
name: string
|
||||||
|
tableName: string
|
||||||
schema: PropertyType<any> | RelationshipType<any>
|
schema: PropertyType<any> | RelationshipType<any>
|
||||||
cascades: EntityCascades<string[]>
|
cascades: EntityCascades<string[]>
|
||||||
} {
|
} {
|
||||||
return {
|
return {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
|
tableName: this.#tableName,
|
||||||
schema: this.schema as unknown as
|
schema: this.schema as unknown as
|
||||||
| PropertyType<any>
|
| PropertyType<any>
|
||||||
| RelationshipType<any>,
|
| RelationshipType<any>,
|
||||||
@@ -63,7 +103,7 @@ export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
|||||||
>
|
>
|
||||||
) {
|
) {
|
||||||
const childToParentCascades = options.delete?.filter((relationship) => {
|
const childToParentCascades = options.delete?.filter((relationship) => {
|
||||||
return this.schema[relationship] instanceof BelongsTo
|
return BelongsTo.isBelongsTo(this.schema[relationship])
|
||||||
})
|
})
|
||||||
|
|
||||||
if (childToParentCascades?.length) {
|
if (childToParentCascades?.length) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { upperCaseFirst } from "../../common/upper-case-first"
|
|||||||
import {
|
import {
|
||||||
MikroOrmBigNumberProperty,
|
MikroOrmBigNumberProperty,
|
||||||
mikroOrmSoftDeletableFilterOptions,
|
mikroOrmSoftDeletableFilterOptions,
|
||||||
|
Searchable,
|
||||||
} from "../../dal"
|
} from "../../dal"
|
||||||
import { DmlEntity } from "../entity"
|
import { DmlEntity } from "../entity"
|
||||||
import { HasMany } from "../relations/has-many"
|
import { HasMany } from "../relations/has-many"
|
||||||
@@ -118,14 +119,16 @@ export function createMikrORMEntity() {
|
|||||||
* Parses entity name and returns model and table name from
|
* Parses entity name and returns model and table name from
|
||||||
* it
|
* it
|
||||||
*/
|
*/
|
||||||
function parseEntityName(entityName: string) {
|
function parseEntityName(entity: DmlEntity<any>) {
|
||||||
|
const parsedEntity = entity.parse()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name is going to be the snake case version of the entity name.
|
* Table name is going to be the snake case version of the entity name.
|
||||||
* Here we should preserve PG schema (if defined).
|
* Here we should preserve PG schema (if defined).
|
||||||
*
|
*
|
||||||
* For example: "platform.user" should stay as "platform.user"
|
* For example: "platform.user" should stay as "platform.user"
|
||||||
*/
|
*/
|
||||||
const tableName = camelToSnakeCase(entityName)
|
const tableName = camelToSnakeCase(parsedEntity.tableName)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity name is going to be the camelCase version of the
|
* Entity name is going to be the camelCase version of the
|
||||||
@@ -134,9 +137,7 @@ export function createMikrORMEntity() {
|
|||||||
const [pgSchema, ...rest] = tableName.split(".")
|
const [pgSchema, ...rest] = tableName.split(".")
|
||||||
return {
|
return {
|
||||||
tableName,
|
tableName,
|
||||||
modelName: upperCaseFirst(
|
modelName: upperCaseFirst(toCamelCase(parsedEntity.name)),
|
||||||
toCamelCase(rest.length ? rest.join("_") : pgSchema)
|
|
||||||
),
|
|
||||||
pgSchema: rest.length ? pgSchema : undefined,
|
pgSchema: rest.length ? pgSchema : undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,6 +309,20 @@ export function createMikrORMEntity() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the searchable decorator to the property marked as searchable to enable the free text search
|
||||||
|
*/
|
||||||
|
function applySearchable(
|
||||||
|
MikroORMEntity: EntityConstructor<any>,
|
||||||
|
field: PropertyMetadata
|
||||||
|
) {
|
||||||
|
if (!field.dataType.options?.searchable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Searchable()(MikroORMEntity.prototype, field.fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines has one relationship on the Mikro ORM entity.
|
* Defines has one relationship on the Mikro ORM entity.
|
||||||
*/
|
*/
|
||||||
@@ -412,8 +427,8 @@ export function createMikrORMEntity() {
|
|||||||
* Otherside is a has many. Hence we should defined a ManyToOne
|
* Otherside is a has many. Hence we should defined a ManyToOne
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
otherSideRelation instanceof HasMany ||
|
HasMany.isHasMany(otherSideRelation) ||
|
||||||
otherSideRelation instanceof DmlManyToMany
|
DmlManyToMany.isManyToMany(otherSideRelation)
|
||||||
) {
|
) {
|
||||||
const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`)
|
const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`)
|
||||||
|
|
||||||
@@ -426,7 +441,7 @@ export function createMikrORMEntity() {
|
|||||||
onDelete: shouldCascade ? "cascade" : undefined,
|
onDelete: shouldCascade ? "cascade" : undefined,
|
||||||
})(MikroORMEntity.prototype, camelToSnakeCase(`${relationship.name}Id`))
|
})(MikroORMEntity.prototype, camelToSnakeCase(`${relationship.name}Id`))
|
||||||
|
|
||||||
if (otherSideRelation instanceof DmlManyToMany) {
|
if (DmlManyToMany.isManyToMany(otherSideRelation)) {
|
||||||
Property({
|
Property({
|
||||||
type: relatedModelName,
|
type: relatedModelName,
|
||||||
persist: false,
|
persist: false,
|
||||||
@@ -448,7 +463,7 @@ export function createMikrORMEntity() {
|
|||||||
/**
|
/**
|
||||||
* Otherside is a has one. Hence we should defined a OneToOne
|
* Otherside is a has one. Hence we should defined a OneToOne
|
||||||
*/
|
*/
|
||||||
if (otherSideRelation instanceof HasOne) {
|
if (HasOne.isHasOne(otherSideRelation)) {
|
||||||
const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`)
|
const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`)
|
||||||
|
|
||||||
OneToOne({
|
OneToOne({
|
||||||
@@ -516,7 +531,7 @@ export function createMikrORMEntity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otherSideRelation instanceof DmlManyToMany === false) {
|
if (!DmlManyToMany.isManyToMany(otherSideRelation)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid relationship reference for "${mappedBy}" on "${relatedModelName}" entity. Make sure to define a manyToMany relationship`
|
`Invalid relationship reference for "${mappedBy}" on "${relatedModelName}" entity. Make sure to define a manyToMany relationship`
|
||||||
)
|
)
|
||||||
@@ -554,13 +569,13 @@ export function createMikrORMEntity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pivotEntity = relationship.options.pivotEntity()
|
const pivotEntity = relationship.options.pivotEntity()
|
||||||
if (!(pivotEntity instanceof DmlEntity)) {
|
if (!DmlEntity.isDmlEntity(pivotEntity)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid pivotEntity reference for "${MikroORMEntity.name}.${relationship.name}". Make sure to return a DML entity from the pivotEntity callback`
|
`Invalid pivotEntity reference for "${MikroORMEntity.name}.${relationship.name}". Make sure to return a DML entity from the pivotEntity callback`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pivotEntityName = parseEntityName(pivotEntity.parse().name).modelName
|
pivotEntityName = parseEntityName(pivotEntity).modelName
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pivotEntityName) {
|
if (!pivotEntityName) {
|
||||||
@@ -630,15 +645,13 @@ export function createMikrORMEntity() {
|
|||||||
/**
|
/**
|
||||||
* Ensure the return value is a DML entity instance
|
* Ensure the return value is a DML entity instance
|
||||||
*/
|
*/
|
||||||
if (!(relatedEntity instanceof DmlEntity)) {
|
if (!DmlEntity.isDmlEntity(relatedEntity)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid relationship reference for "${MikroORMEntity.name}.${relationship.name}". Make sure to return a DML entity from the relationship callback`
|
`Invalid relationship reference for "${MikroORMEntity.name}.${relationship.name}". Make sure to return a DML entity from the relationship callback`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { modelName, tableName, pgSchema } = parseEntityName(
|
const { modelName, tableName, pgSchema } = parseEntityName(relatedEntity)
|
||||||
relatedEntity.parse().name
|
|
||||||
)
|
|
||||||
const relatedEntityInfo = {
|
const relatedEntityInfo = {
|
||||||
relatedModelName: modelName,
|
relatedModelName: modelName,
|
||||||
relatedTableName: tableName,
|
relatedTableName: tableName,
|
||||||
@@ -691,8 +704,8 @@ export function createMikrORMEntity() {
|
|||||||
return function createEntity<T extends DmlEntity<any>>(entity: T): Infer<T> {
|
return function createEntity<T extends DmlEntity<any>>(entity: T): Infer<T> {
|
||||||
class MikroORMEntity {}
|
class MikroORMEntity {}
|
||||||
|
|
||||||
const { name, schema, cascades } = entity.parse()
|
const { schema, cascades } = entity.parse()
|
||||||
const { modelName, tableName } = parseEntityName(name)
|
const { modelName, tableName } = parseEntityName(entity)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assigning name to the class constructor, so that it matches
|
* Assigning name to the class constructor, so that it matches
|
||||||
@@ -713,6 +726,7 @@ export function createMikrORMEntity() {
|
|||||||
if ("fieldName" in field) {
|
if ("fieldName" in field) {
|
||||||
defineProperty(MikroORMEntity, field)
|
defineProperty(MikroORMEntity, field)
|
||||||
applyIndexes(MikroORMEntity, tableName, field)
|
applyIndexes(MikroORMEntity, tableName, field)
|
||||||
|
applySearchable(MikroORMEntity, field)
|
||||||
} else {
|
} else {
|
||||||
defineRelationship(MikroORMEntity, field, cascades)
|
defineRelationship(MikroORMEntity, field, cascades)
|
||||||
}
|
}
|
||||||
@@ -734,16 +748,14 @@ export function createMikrORMEntity() {
|
|||||||
*/
|
*/
|
||||||
export const toMikroORMEntity = <T>(
|
export const toMikroORMEntity = <T>(
|
||||||
entity: T
|
entity: T
|
||||||
): T extends DmlEntity<infer Schema> ? EntityConstructor<Schema> : T => {
|
): T extends DmlEntity<infer Schema> ? Infer<T> : T => {
|
||||||
let mikroOrmEntity: T | EntityConstructor<any> = entity
|
let mikroOrmEntity: T | EntityConstructor<any> = entity
|
||||||
|
|
||||||
if (DmlEntity.isDmlEntity(entity)) {
|
if (DmlEntity.isDmlEntity(entity)) {
|
||||||
mikroOrmEntity = createMikrORMEntity()(entity)
|
mikroOrmEntity = createMikrORMEntity()(entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
return mikroOrmEntity as T extends DmlEntity<infer Schema>
|
return mikroOrmEntity as T extends DmlEntity<infer Schema> ? Infer<T> : T
|
||||||
? EntityConstructor<Schema>
|
|
||||||
: T
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -761,6 +773,6 @@ export const toMikroOrmEntities = function <T extends any[]>(entities: T) {
|
|||||||
|
|
||||||
return entity
|
return entity
|
||||||
}) as {
|
}) as {
|
||||||
[K in keyof T]: T[K] extends DmlEntity<any> ? EntityConstructor<T[K]> : T[K]
|
[K in keyof T]: T[K] extends DmlEntity<any> ? Infer<T[K]> : T[K]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,8 +32,8 @@ export function createBigNumberProperties<Schema extends DMLSchema>(
|
|||||||
|
|
||||||
for (const [key, property] of Object.entries(schema)) {
|
for (const [key, property] of Object.entries(schema)) {
|
||||||
if (
|
if (
|
||||||
property instanceof BigNumberProperty ||
|
BigNumberProperty.isBigNumberProperty(property) ||
|
||||||
property instanceof NullableModifier
|
NullableModifier.isNullableModifier(property)
|
||||||
) {
|
) {
|
||||||
const parsed = property.parse(key)
|
const parsed = property.parse(key)
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,8 @@ export class BigNumberProperty extends BaseProperty<number> {
|
|||||||
protected dataType = {
|
protected dataType = {
|
||||||
name: "bigNumber",
|
name: "bigNumber",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
static isBigNumberProperty(obj: any): obj is BigNumberProperty {
|
||||||
|
return obj?.dataType?.name === "bigNumber"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
import { PropertyType } from "@medusajs/types"
|
import { PropertyType } from "@medusajs/types"
|
||||||
|
|
||||||
|
const IsNullableModifier = Symbol.for("isNullableModifier")
|
||||||
/**
|
/**
|
||||||
* Nullable modifier marks a schema node as nullable
|
* Nullable modifier marks a schema node as nullable
|
||||||
*/
|
*/
|
||||||
export class NullableModifier<T, Schema extends PropertyType<T>>
|
export class NullableModifier<T, Schema extends PropertyType<T>>
|
||||||
implements PropertyType<T | null>
|
implements PropertyType<T | null>
|
||||||
{
|
{
|
||||||
|
[IsNullableModifier]: true = true
|
||||||
|
|
||||||
|
static isNullableModifier(obj: any): obj is NullableModifier<any, any> {
|
||||||
|
return !!obj?.[IsNullableModifier]
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* A type-only property to infer the JavScript data-type
|
* A type-only property to infer the JavScript data-type
|
||||||
* of the schema property
|
* of the schema property
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export class TextProperty extends BaseProperty<string> {
|
|||||||
name: "text"
|
name: "text"
|
||||||
options: {
|
options: {
|
||||||
primaryKey: boolean
|
primaryKey: boolean
|
||||||
|
searchable: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,12 +18,18 @@ export class TextProperty extends BaseProperty<string> {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(options?: { primaryKey?: boolean }) {
|
searchable() {
|
||||||
|
this.dataType.options.searchable = true
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(options?: { primaryKey?: boolean; searchable?: boolean }) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.dataType = {
|
this.dataType = {
|
||||||
name: "text",
|
name: "text",
|
||||||
options: { primaryKey: false, ...options },
|
options: { primaryKey: false, searchable: false, ...options },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,10 @@ import { NullableModifier } from "./nullable"
|
|||||||
export class BelongsTo<T> extends BaseRelationship<T> {
|
export class BelongsTo<T> extends BaseRelationship<T> {
|
||||||
type = "belongsTo" as const
|
type = "belongsTo" as const
|
||||||
|
|
||||||
|
static isBelongsTo<T>(relationship: any): relationship is BelongsTo<T> {
|
||||||
|
return relationship?.type === "belongsTo"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply nullable modifier on the schema
|
* Apply nullable modifier on the schema
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,4 +12,8 @@ import { BaseRelationship } from "./base"
|
|||||||
*/
|
*/
|
||||||
export class HasMany<T> extends BaseRelationship<T> {
|
export class HasMany<T> extends BaseRelationship<T> {
|
||||||
type = "hasMany" as const
|
type = "hasMany" as const
|
||||||
|
|
||||||
|
static isHasMany<T>(relationship: any): relationship is HasMany<T> {
|
||||||
|
return relationship?.type === "hasMany"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,10 @@ import { NullableModifier } from "./nullable"
|
|||||||
export class HasOne<T> extends BaseRelationship<T> {
|
export class HasOne<T> extends BaseRelationship<T> {
|
||||||
type = "hasOne" as const
|
type = "hasOne" as const
|
||||||
|
|
||||||
|
static isHasOne<T>(relationship: any): relationship is HasOne<T> {
|
||||||
|
return relationship?.type === "hasOne"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply nullable modifier on the schema
|
* Apply nullable modifier on the schema
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,4 +13,8 @@ import { BaseRelationship } from "./base"
|
|||||||
*/
|
*/
|
||||||
export class ManyToMany<T> extends BaseRelationship<T> {
|
export class ManyToMany<T> extends BaseRelationship<T> {
|
||||||
type = "manyToMany" as const
|
type = "manyToMany" as const
|
||||||
|
|
||||||
|
static isManyToMany<T>(relationship: any): relationship is ManyToMany<T> {
|
||||||
|
return relationship?.type === "manyToMany"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,21 @@
|
|||||||
import { RelationshipType } from "@medusajs/types"
|
import { RelationshipType } from "@medusajs/types"
|
||||||
|
|
||||||
|
const IsNullableModifier = Symbol.for("isNullableModifier")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nullable modifier marks a schema node as nullable
|
* Nullable modifier marks a schema node as nullable
|
||||||
*/
|
*/
|
||||||
export class NullableModifier<T, Relation extends RelationshipType<T>>
|
export class NullableModifier<T, Relation extends RelationshipType<T>>
|
||||||
implements RelationshipType<T | null>
|
implements RelationshipType<T | null>
|
||||||
{
|
{
|
||||||
|
[IsNullableModifier]: true = true
|
||||||
|
|
||||||
|
static isNullableModifier<T>(
|
||||||
|
modifier: any
|
||||||
|
): modifier is NullableModifier<T, any> {
|
||||||
|
return !!modifier?.[IsNullableModifier]
|
||||||
|
}
|
||||||
|
|
||||||
declare type: RelationshipType<T>["type"]
|
declare type: RelationshipType<T>["type"]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user