Add support for id property type (#7775)
This commit is contained in:
1 parent
ef5719a3d8
commit
2895ccfba8
6 files changed
+420
-2
No files matched your search
@@ -744,6 +744,317 @@ describe("Entity builder", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("Entity builder | id", () => {
|
||||||
|
test("define an entity with id property", () => {
|
||||||
|
const model = new EntityBuilder()
|
||||||
|
const user = model.define("user", {
|
||||||
|
id: model.id(),
|
||||||
|
username: model.text(),
|
||||||
|
email: model.text(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const entityBuilder = createMikrORMEntity()
|
||||||
|
const User = entityBuilder(user)
|
||||||
|
expectTypeOf(new User()).toMatchTypeOf<{
|
||||||
|
id: string
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
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.hooks).toEqual({
|
||||||
|
beforeCreate: ["generateId"],
|
||||||
|
onInit: ["generateId"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(metaData.filters).toEqual({
|
||||||
|
softDeletable: {
|
||||||
|
name: "softDeletable",
|
||||||
|
cond: expect.any(Function),
|
||||||
|
default: true,
|
||||||
|
args: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(metaData.properties).toEqual({
|
||||||
|
id: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
name: "id",
|
||||||
|
nullable: false,
|
||||||
|
primary: true,
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
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 id as non-primary", () => {
|
||||||
|
const model = new EntityBuilder()
|
||||||
|
const user = model.define("user", {
|
||||||
|
id: model.id({ primaryKey: false }),
|
||||||
|
username: model.text(),
|
||||||
|
email: model.text(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const entityBuilder = createMikrORMEntity()
|
||||||
|
const User = entityBuilder(user)
|
||||||
|
expectTypeOf(new User()).toMatchTypeOf<{
|
||||||
|
id: string
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
created_at: Date
|
||||||
|
updated_at: Date
|
||||||
|
deleted_at: Date | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
|
const userInstance = new User()
|
||||||
|
userInstance["generateId"]()
|
||||||
|
|
||||||
|
expect(metaData.className).toEqual("User")
|
||||||
|
expect(metaData.path).toEqual("User")
|
||||||
|
|
||||||
|
expect(metaData.hooks).toEqual({
|
||||||
|
beforeCreate: ["generateId"],
|
||||||
|
onInit: ["generateId"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(metaData.filters).toEqual({
|
||||||
|
softDeletable: {
|
||||||
|
name: "softDeletable",
|
||||||
|
cond: expect.any(Function),
|
||||||
|
default: true,
|
||||||
|
args: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(metaData.properties).toEqual({
|
||||||
|
id: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(userInstance.id).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("define prefix for the id", () => {
|
||||||
|
const model = new EntityBuilder()
|
||||||
|
const user = model.define("user", {
|
||||||
|
id: model.id({ primaryKey: false, prefix: "us" }),
|
||||||
|
username: model.text(),
|
||||||
|
email: model.text(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const entityBuilder = createMikrORMEntity()
|
||||||
|
const User = entityBuilder(user)
|
||||||
|
expectTypeOf(new User()).toMatchTypeOf<{
|
||||||
|
id: string
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
created_at: Date
|
||||||
|
updated_at: Date
|
||||||
|
deleted_at: Date | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
|
const userInstance = new User()
|
||||||
|
userInstance["generateId"]()
|
||||||
|
|
||||||
|
expect(metaData.className).toEqual("User")
|
||||||
|
expect(metaData.path).toEqual("User")
|
||||||
|
|
||||||
|
expect(metaData.hooks).toEqual({
|
||||||
|
beforeCreate: ["generateId"],
|
||||||
|
onInit: ["generateId"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(metaData.filters).toEqual({
|
||||||
|
softDeletable: {
|
||||||
|
name: "softDeletable",
|
||||||
|
cond: expect.any(Function),
|
||||||
|
default: true,
|
||||||
|
args: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(metaData.properties).toEqual({
|
||||||
|
id: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
columnType: "text",
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(userInstance.id.startsWith("us_")).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("Entity builder | indexes", () => {
|
describe("Entity builder | indexes", () => {
|
||||||
test("define index on a field", () => {
|
test("define index on a field", () => {
|
||||||
const model = new EntityBuilder()
|
const model = new EntityBuilder()
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { expectTypeOf } from "expect-type"
|
||||||
|
import { IdProperty } from "../properties/id"
|
||||||
|
|
||||||
|
describe("Id property", () => {
|
||||||
|
test("create id property type", () => {
|
||||||
|
const property = new IdProperty()
|
||||||
|
|
||||||
|
expectTypeOf(property["$dataType"]).toEqualTypeOf<string>()
|
||||||
|
expect(property.parse("id")).toEqual({
|
||||||
|
fieldName: "id",
|
||||||
|
dataType: {
|
||||||
|
name: "id",
|
||||||
|
options: {
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nullable: false,
|
||||||
|
indexes: [],
|
||||||
|
relationships: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("create id property type with marking it as a primary key", () => {
|
||||||
|
const property = new IdProperty({ primaryKey: false })
|
||||||
|
|
||||||
|
expectTypeOf(property["$dataType"]).toEqualTypeOf<string>()
|
||||||
|
expect(property.parse("id")).toEqual({
|
||||||
|
fieldName: "id",
|
||||||
|
dataType: {
|
||||||
|
name: "id",
|
||||||
|
options: {
|
||||||
|
primaryKey: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nullable: false,
|
||||||
|
indexes: [],
|
||||||
|
relationships: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -15,6 +15,7 @@ import type {
|
|||||||
PropertyType,
|
PropertyType,
|
||||||
} from "./types"
|
} from "./types"
|
||||||
import { NullableModifier } from "./properties/nullable"
|
import { NullableModifier } from "./properties/nullable"
|
||||||
|
import { IdProperty } from "./properties/id"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The implicit properties added by EntityBuilder in every schema
|
* The implicit properties added by EntityBuilder in every schema
|
||||||
@@ -63,6 +64,14 @@ export class EntityBuilder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define an id property. Id properties are marked
|
||||||
|
* primary by default
|
||||||
|
*/
|
||||||
|
id(options: ConstructorParameters<typeof IdProperty>[0]) {
|
||||||
|
return new IdProperty(options)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define a text/string based column
|
* Define a text/string based column
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import {
|
|||||||
ManyToMany,
|
ManyToMany,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
Filter,
|
Filter,
|
||||||
|
PrimaryKey,
|
||||||
|
BeforeCreate,
|
||||||
|
OnInit,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
import { DmlEntity } from "../entity"
|
import { DmlEntity } from "../entity"
|
||||||
import {
|
import {
|
||||||
@@ -14,6 +17,7 @@ import {
|
|||||||
camelToSnakeCase,
|
camelToSnakeCase,
|
||||||
createPsqlIndexStatementHelper,
|
createPsqlIndexStatementHelper,
|
||||||
toCamelCase,
|
toCamelCase,
|
||||||
|
generateEntityId,
|
||||||
} from "../../common"
|
} from "../../common"
|
||||||
import { upperCaseFirst } from "../../common/upper-case-first"
|
import { upperCaseFirst } from "../../common/upper-case-first"
|
||||||
import type {
|
import type {
|
||||||
@@ -39,7 +43,7 @@ import { ManyToMany as DmlManyToMany } from "../relations/many-to-many"
|
|||||||
* mikro orm decorator for that
|
* mikro orm decorator for that
|
||||||
*/
|
*/
|
||||||
const COLUMN_TYPES: {
|
const COLUMN_TYPES: {
|
||||||
[K in Exclude<KnownDataTypes, "enum">]: string
|
[K in Exclude<KnownDataTypes, "enum" | "id">]: string
|
||||||
} = {
|
} = {
|
||||||
boolean: "boolean",
|
boolean: "boolean",
|
||||||
dateTime: "timestamptz",
|
dateTime: "timestamptz",
|
||||||
@@ -56,7 +60,7 @@ const COLUMN_TYPES: {
|
|||||||
* mikro orm decorator for that
|
* mikro orm decorator for that
|
||||||
*/
|
*/
|
||||||
const PROPERTY_TYPES: {
|
const PROPERTY_TYPES: {
|
||||||
[K in Exclude<KnownDataTypes, "enum">]: string
|
[K in Exclude<KnownDataTypes, "enum" | "id">]: string
|
||||||
} = {
|
} = {
|
||||||
boolean: "boolean",
|
boolean: "boolean",
|
||||||
dateTime: "date",
|
dateTime: "date",
|
||||||
@@ -170,6 +174,39 @@ export function createMikrORMEntity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defining an id property
|
||||||
|
*/
|
||||||
|
if (field.dataType.name === "id") {
|
||||||
|
const IdDecorator = field.dataType.options?.primaryKey
|
||||||
|
? PrimaryKey({
|
||||||
|
columnType: "text",
|
||||||
|
type: "string",
|
||||||
|
nullable: field.nullable,
|
||||||
|
})
|
||||||
|
: Property({
|
||||||
|
columnType: "text",
|
||||||
|
type: "string",
|
||||||
|
nullable: field.nullable,
|
||||||
|
})
|
||||||
|
|
||||||
|
IdDecorator(MikroORMEntity.prototype, field.fieldName)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to generate entity within the code
|
||||||
|
*/
|
||||||
|
MikroORMEntity.prototype.generateId = function () {
|
||||||
|
this.id = generateEntityId(this.id, field.dataType.options?.prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute hook via lifecycle decorators
|
||||||
|
*/
|
||||||
|
BeforeCreate()(MikroORMEntity.prototype, "generateId")
|
||||||
|
OnInit()(MikroORMEntity.prototype, "generateId")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define rest of properties
|
* Define rest of properties
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { BaseProperty } from "./base"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Id property defines a unique identifier for the schema.
|
||||||
|
* Most of the times it will be the primary as well.
|
||||||
|
*/
|
||||||
|
export class IdProperty extends BaseProperty<string> {
|
||||||
|
protected dataType: {
|
||||||
|
name: "id"
|
||||||
|
options: {
|
||||||
|
primaryKey: boolean
|
||||||
|
prefix?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(options?: { primaryKey?: boolean; prefix?: string }) {
|
||||||
|
super()
|
||||||
|
this.dataType = { name: "id", options: { primaryKey: true, ...options } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ export type KnownDataTypes =
|
|||||||
| "number"
|
| "number"
|
||||||
| "dateTime"
|
| "dateTime"
|
||||||
| "json"
|
| "json"
|
||||||
|
| "id"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of available relationships at DML level
|
* List of available relationships at DML level
|
||||||
|
|||||||
Reference in new issue
Block a user