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:
Adrien de Peretti authored and GitHub committed 2024-06-25 18:00:39 +02:00
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(),
+47 -7
View File
@@ -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"]
/** /**