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,6 +1,7 @@
|
||||
import { expectTypeOf } from "expect-type"
|
||||
import { BaseProperty } from "../properties/base"
|
||||
import { PropertyMetadata } from "@medusajs/types"
|
||||
import { TextProperty } from "../properties/text"
|
||||
|
||||
describe("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", () => {
|
||||
class StringProperty extends BaseProperty<string> {
|
||||
protected dataType: PropertyMetadata["dataType"] = {
|
||||
|
||||
@@ -37,6 +37,9 @@ describe("Entity builder", () => {
|
||||
spend_limit: model.bigNumber(),
|
||||
})
|
||||
|
||||
expect(user.name).toEqual("user")
|
||||
expect(user.parse().tableName).toEqual("user")
|
||||
|
||||
const User = toMikroORMEntity(user)
|
||||
|
||||
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", () => {
|
||||
const user = model.define("user", {
|
||||
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", () => {
|
||||
const user = model.define("user", {
|
||||
id: model.number(),
|
||||
@@ -2472,7 +2832,7 @@ describe("Entity builder", () => {
|
||||
email: model.hasOne(() => email),
|
||||
})
|
||||
|
||||
const [User, Email] = [user, email].map(toMikroORMEntity)
|
||||
const [User, Email] = toMikroOrmEntities([user, email])
|
||||
|
||||
expectTypeOf(new User()).toMatchTypeOf<{
|
||||
id: number
|
||||
|
||||
@@ -20,4 +20,19 @@ describe("HasMany relationship", () => {
|
||||
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,
|
||||
})
|
||||
})
|
||||
|
||||
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,
|
||||
})
|
||||
})
|
||||
|
||||
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",
|
||||
dataType: {
|
||||
name: "text",
|
||||
options: { primaryKey: false },
|
||||
options: { primaryKey: false, searchable: false },
|
||||
},
|
||||
nullable: false,
|
||||
indexes: [],
|
||||
|
||||
@@ -29,6 +29,8 @@ export type DMLSchema = Record<
|
||||
PropertyType<any> | RelationshipType<any>
|
||||
>
|
||||
|
||||
type DefineOptions = string | { name?: string; tableName: string }
|
||||
|
||||
/**
|
||||
* Entity builder exposes the API to create an entity and define its
|
||||
* schema using the shorthand methods.
|
||||
@@ -52,10 +54,13 @@ export class EntityBuilder {
|
||||
* Define an entity or a model. The name should be unique across
|
||||
* all the entities.
|
||||
*/
|
||||
define<Schema extends DMLSchema>(name: string, schema: Schema) {
|
||||
define<Schema extends DMLSchema>(
|
||||
nameOrConfig: DefineOptions,
|
||||
schema: Schema
|
||||
) {
|
||||
this.#disallowImplicitProperties(schema)
|
||||
|
||||
return new DmlEntity(name, {
|
||||
return new DmlEntity(nameOrConfig, {
|
||||
...schema,
|
||||
...createBigNumberProperties(schema),
|
||||
...createDefaultProperties(),
|
||||
|
||||
@@ -8,6 +8,40 @@ import {
|
||||
} from "@medusajs/types"
|
||||
import { DMLSchema } from "./entity-builder"
|
||||
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
|
||||
@@ -16,8 +50,16 @@ import { BelongsTo } from "./relations/belongs-to"
|
||||
export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
||||
[IsDmlEntity]: true = true
|
||||
|
||||
name: string
|
||||
|
||||
readonly #tableName: 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.
|
||||
@@ -27,11 +69,7 @@ export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
||||
* @param entity
|
||||
*/
|
||||
static isDmlEntity(entity: unknown): entity is DmlEntity<any> {
|
||||
return (
|
||||
!!entity &&
|
||||
(entity instanceof DmlEntity ||
|
||||
(typeof entity === "object" && entity[IsDmlEntity] === true))
|
||||
)
|
||||
return !!entity?.[IsDmlEntity]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,11 +77,13 @@ export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
||||
*/
|
||||
parse(): {
|
||||
name: string
|
||||
tableName: string
|
||||
schema: PropertyType<any> | RelationshipType<any>
|
||||
cascades: EntityCascades<string[]>
|
||||
} {
|
||||
return {
|
||||
name: this.name,
|
||||
tableName: this.#tableName,
|
||||
schema: this.schema as unknown as
|
||||
| PropertyType<any>
|
||||
| RelationshipType<any>,
|
||||
@@ -63,7 +103,7 @@ export class DmlEntity<Schema extends DMLSchema> implements IDmlEntity<Schema> {
|
||||
>
|
||||
) {
|
||||
const childToParentCascades = options.delete?.filter((relationship) => {
|
||||
return this.schema[relationship] instanceof BelongsTo
|
||||
return BelongsTo.isBelongsTo(this.schema[relationship])
|
||||
})
|
||||
|
||||
if (childToParentCascades?.length) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import { upperCaseFirst } from "../../common/upper-case-first"
|
||||
import {
|
||||
MikroOrmBigNumberProperty,
|
||||
mikroOrmSoftDeletableFilterOptions,
|
||||
Searchable,
|
||||
} from "../../dal"
|
||||
import { DmlEntity } from "../entity"
|
||||
import { HasMany } from "../relations/has-many"
|
||||
@@ -118,14 +119,16 @@ export function createMikrORMEntity() {
|
||||
* Parses entity name and returns model and table name from
|
||||
* 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.
|
||||
* Here we should preserve PG schema (if defined).
|
||||
*
|
||||
* 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
|
||||
@@ -134,9 +137,7 @@ export function createMikrORMEntity() {
|
||||
const [pgSchema, ...rest] = tableName.split(".")
|
||||
return {
|
||||
tableName,
|
||||
modelName: upperCaseFirst(
|
||||
toCamelCase(rest.length ? rest.join("_") : pgSchema)
|
||||
),
|
||||
modelName: upperCaseFirst(toCamelCase(parsedEntity.name)),
|
||||
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.
|
||||
*/
|
||||
@@ -412,8 +427,8 @@ export function createMikrORMEntity() {
|
||||
* Otherside is a has many. Hence we should defined a ManyToOne
|
||||
*/
|
||||
if (
|
||||
otherSideRelation instanceof HasMany ||
|
||||
otherSideRelation instanceof DmlManyToMany
|
||||
HasMany.isHasMany(otherSideRelation) ||
|
||||
DmlManyToMany.isManyToMany(otherSideRelation)
|
||||
) {
|
||||
const foreignKeyName = camelToSnakeCase(`${relationship.name}Id`)
|
||||
|
||||
@@ -426,7 +441,7 @@ export function createMikrORMEntity() {
|
||||
onDelete: shouldCascade ? "cascade" : undefined,
|
||||
})(MikroORMEntity.prototype, camelToSnakeCase(`${relationship.name}Id`))
|
||||
|
||||
if (otherSideRelation instanceof DmlManyToMany) {
|
||||
if (DmlManyToMany.isManyToMany(otherSideRelation)) {
|
||||
Property({
|
||||
type: relatedModelName,
|
||||
persist: false,
|
||||
@@ -448,7 +463,7 @@ export function createMikrORMEntity() {
|
||||
/**
|
||||
* 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`)
|
||||
|
||||
OneToOne({
|
||||
@@ -516,7 +531,7 @@ export function createMikrORMEntity() {
|
||||
)
|
||||
}
|
||||
|
||||
if (otherSideRelation instanceof DmlManyToMany === false) {
|
||||
if (!DmlManyToMany.isManyToMany(otherSideRelation)) {
|
||||
throw new Error(
|
||||
`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()
|
||||
if (!(pivotEntity instanceof DmlEntity)) {
|
||||
if (!DmlEntity.isDmlEntity(pivotEntity)) {
|
||||
throw new Error(
|
||||
`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) {
|
||||
@@ -630,15 +645,13 @@ export function createMikrORMEntity() {
|
||||
/**
|
||||
* Ensure the return value is a DML entity instance
|
||||
*/
|
||||
if (!(relatedEntity instanceof DmlEntity)) {
|
||||
if (!DmlEntity.isDmlEntity(relatedEntity)) {
|
||||
throw new Error(
|
||||
`Invalid relationship reference for "${MikroORMEntity.name}.${relationship.name}". Make sure to return a DML entity from the relationship callback`
|
||||
)
|
||||
}
|
||||
|
||||
const { modelName, tableName, pgSchema } = parseEntityName(
|
||||
relatedEntity.parse().name
|
||||
)
|
||||
const { modelName, tableName, pgSchema } = parseEntityName(relatedEntity)
|
||||
const relatedEntityInfo = {
|
||||
relatedModelName: modelName,
|
||||
relatedTableName: tableName,
|
||||
@@ -691,8 +704,8 @@ export function createMikrORMEntity() {
|
||||
return function createEntity<T extends DmlEntity<any>>(entity: T): Infer<T> {
|
||||
class MikroORMEntity {}
|
||||
|
||||
const { name, schema, cascades } = entity.parse()
|
||||
const { modelName, tableName } = parseEntityName(name)
|
||||
const { schema, cascades } = entity.parse()
|
||||
const { modelName, tableName } = parseEntityName(entity)
|
||||
|
||||
/**
|
||||
* Assigning name to the class constructor, so that it matches
|
||||
@@ -713,6 +726,7 @@ export function createMikrORMEntity() {
|
||||
if ("fieldName" in field) {
|
||||
defineProperty(MikroORMEntity, field)
|
||||
applyIndexes(MikroORMEntity, tableName, field)
|
||||
applySearchable(MikroORMEntity, field)
|
||||
} else {
|
||||
defineRelationship(MikroORMEntity, field, cascades)
|
||||
}
|
||||
@@ -734,16 +748,14 @@ export function createMikrORMEntity() {
|
||||
*/
|
||||
export const toMikroORMEntity = <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
|
||||
|
||||
if (DmlEntity.isDmlEntity(entity)) {
|
||||
mikroOrmEntity = createMikrORMEntity()(entity)
|
||||
}
|
||||
|
||||
return mikroOrmEntity as T extends DmlEntity<infer Schema>
|
||||
? EntityConstructor<Schema>
|
||||
: T
|
||||
return mikroOrmEntity as T extends DmlEntity<infer Schema> ? Infer<T> : T
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -761,6 +773,6 @@ export const toMikroOrmEntities = function <T extends any[]>(entities: T) {
|
||||
|
||||
return entity
|
||||
}) 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)) {
|
||||
if (
|
||||
property instanceof BigNumberProperty ||
|
||||
property instanceof NullableModifier
|
||||
BigNumberProperty.isBigNumberProperty(property) ||
|
||||
NullableModifier.isNullableModifier(property)
|
||||
) {
|
||||
const parsed = property.parse(key)
|
||||
|
||||
|
||||
@@ -8,4 +8,8 @@ export class BigNumberProperty extends BaseProperty<number> {
|
||||
protected dataType = {
|
||||
name: "bigNumber",
|
||||
} as const
|
||||
|
||||
static isBigNumberProperty(obj: any): obj is BigNumberProperty {
|
||||
return obj?.dataType?.name === "bigNumber"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { PropertyType } from "@medusajs/types"
|
||||
|
||||
const IsNullableModifier = Symbol.for("isNullableModifier")
|
||||
/**
|
||||
* Nullable modifier marks a schema node as nullable
|
||||
*/
|
||||
export class NullableModifier<T, Schema extends PropertyType<T>>
|
||||
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
|
||||
* of the schema property
|
||||
|
||||
@@ -8,6 +8,7 @@ export class TextProperty extends BaseProperty<string> {
|
||||
name: "text"
|
||||
options: {
|
||||
primaryKey: boolean
|
||||
searchable: boolean
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +18,18 @@ export class TextProperty extends BaseProperty<string> {
|
||||
return this
|
||||
}
|
||||
|
||||
constructor(options?: { primaryKey?: boolean }) {
|
||||
searchable() {
|
||||
this.dataType.options.searchable = true
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
constructor(options?: { primaryKey?: boolean; searchable?: boolean }) {
|
||||
super()
|
||||
|
||||
this.dataType = {
|
||||
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> {
|
||||
type = "belongsTo" as const
|
||||
|
||||
static isBelongsTo<T>(relationship: any): relationship is BelongsTo<T> {
|
||||
return relationship?.type === "belongsTo"
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply nullable modifier on the schema
|
||||
*/
|
||||
|
||||
@@ -12,4 +12,8 @@ import { BaseRelationship } from "./base"
|
||||
*/
|
||||
export class HasMany<T> extends BaseRelationship<T> {
|
||||
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> {
|
||||
type = "hasOne" as const
|
||||
|
||||
static isHasOne<T>(relationship: any): relationship is HasOne<T> {
|
||||
return relationship?.type === "hasOne"
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply nullable modifier on the schema
|
||||
*/
|
||||
|
||||
@@ -13,4 +13,8 @@ import { BaseRelationship } from "./base"
|
||||
*/
|
||||
export class ManyToMany<T> extends BaseRelationship<T> {
|
||||
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"
|
||||
|
||||
const IsNullableModifier = Symbol.for("isNullableModifier")
|
||||
|
||||
/**
|
||||
* Nullable modifier marks a schema node as nullable
|
||||
*/
|
||||
export class NullableModifier<T, Relation extends RelationshipType<T>>
|
||||
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"]
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user