Code cleanup and add support for default values and nullable relationships (#7687)
This commit is contained in:
@@ -21,6 +21,35 @@ describe("Base relationship", () => {
|
|||||||
expect(relationship.parse("user")).toEqual({
|
expect(relationship.parse("user")).toEqual({
|
||||||
name: "user",
|
name: "user",
|
||||||
type: "hasOne",
|
type: "hasOne",
|
||||||
|
nullable: false,
|
||||||
|
entity: entityRef,
|
||||||
|
options: {
|
||||||
|
foreignKey: "user_id",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("mark relationship as nullable", () => {
|
||||||
|
class HasOne<T> extends BaseRelationship<T> {
|
||||||
|
protected relationshipType: "hasOne" | "hasMany" | "manyToMany" = "hasOne"
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = {
|
||||||
|
username: new TextSchema(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityRef = () => user
|
||||||
|
const relationship = new HasOne(entityRef, {
|
||||||
|
foreignKey: "user_id",
|
||||||
|
}).nullable()
|
||||||
|
|
||||||
|
expectTypeOf(relationship["$dataType"]).toEqualTypeOf<
|
||||||
|
(() => typeof user) | null
|
||||||
|
>()
|
||||||
|
expect(relationship.parse("user")).toEqual({
|
||||||
|
name: "user",
|
||||||
|
type: "hasOne",
|
||||||
|
nullable: true,
|
||||||
entity: entityRef,
|
entity: entityRef,
|
||||||
options: {
|
options: {
|
||||||
foreignKey: "user_id",
|
foreignKey: "user_id",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ describe("Base schema", () => {
|
|||||||
test("create a schema type from base schema", () => {
|
test("create a schema type from base schema", () => {
|
||||||
class StringSchema extends BaseSchema<string> {
|
class StringSchema extends BaseSchema<string> {
|
||||||
protected dataType: SchemaMetadata["dataType"] = {
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
name: "string",
|
name: "text",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,10 +16,9 @@ describe("Base schema", () => {
|
|||||||
expect(schema.parse("username")).toEqual({
|
expect(schema.parse("username")).toEqual({
|
||||||
fieldName: "username",
|
fieldName: "username",
|
||||||
dataType: {
|
dataType: {
|
||||||
name: "string",
|
name: "text",
|
||||||
},
|
},
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: false,
|
|
||||||
indexes: [],
|
indexes: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
})
|
})
|
||||||
@@ -28,7 +27,7 @@ describe("Base schema", () => {
|
|||||||
test("apply nullable modifier", () => {
|
test("apply nullable modifier", () => {
|
||||||
class StringSchema extends BaseSchema<string> {
|
class StringSchema extends BaseSchema<string> {
|
||||||
protected dataType: SchemaMetadata["dataType"] = {
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
name: "string",
|
name: "text",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,54 +37,31 @@ describe("Base schema", () => {
|
|||||||
expect(schema.parse("username")).toEqual({
|
expect(schema.parse("username")).toEqual({
|
||||||
fieldName: "username",
|
fieldName: "username",
|
||||||
dataType: {
|
dataType: {
|
||||||
name: "string",
|
name: "text",
|
||||||
},
|
},
|
||||||
nullable: true,
|
nullable: true,
|
||||||
optional: false,
|
|
||||||
indexes: [],
|
indexes: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("apply optional modifier", () => {
|
test("define default value", () => {
|
||||||
class StringSchema extends BaseSchema<string> {
|
class StringSchema extends BaseSchema<string> {
|
||||||
protected dataType: SchemaMetadata["dataType"] = {
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
name: "string",
|
name: "text",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = new StringSchema().optional()
|
const schema = new StringSchema().default("foo")
|
||||||
|
|
||||||
expectTypeOf(schema["$dataType"]).toEqualTypeOf<string | undefined>()
|
expectTypeOf(schema["$dataType"]).toEqualTypeOf<string>()
|
||||||
expect(schema.parse("username")).toEqual({
|
expect(schema.parse("username")).toEqual({
|
||||||
fieldName: "username",
|
fieldName: "username",
|
||||||
dataType: {
|
dataType: {
|
||||||
name: "string",
|
name: "text",
|
||||||
},
|
},
|
||||||
|
defaultValue: "foo",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: true,
|
|
||||||
indexes: [],
|
|
||||||
relationships: [],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test("apply optional + nullable modifier", () => {
|
|
||||||
class StringSchema extends BaseSchema<string> {
|
|
||||||
protected dataType: SchemaMetadata["dataType"] = {
|
|
||||||
name: "string",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = new StringSchema().optional().nullable()
|
|
||||||
|
|
||||||
expectTypeOf(schema["$dataType"]).toEqualTypeOf<string | undefined | null>()
|
|
||||||
expect(schema.parse("username")).toEqual({
|
|
||||||
fieldName: "username",
|
|
||||||
dataType: {
|
|
||||||
name: "string",
|
|
||||||
},
|
|
||||||
nullable: true,
|
|
||||||
optional: true,
|
|
||||||
indexes: [],
|
indexes: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ describe("Boolean schema", () => {
|
|||||||
name: "boolean",
|
name: "boolean",
|
||||||
},
|
},
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: false,
|
|
||||||
indexes: [],
|
indexes: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ describe("DateTime schema", () => {
|
|||||||
name: "dateTime",
|
name: "dateTime",
|
||||||
},
|
},
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: false,
|
|
||||||
indexes: [],
|
indexes: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -122,6 +122,56 @@ describe("Entity builder", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("define an entity with default value", () => {
|
||||||
|
const model = new EntityBuilder()
|
||||||
|
const user = model.define("user", {
|
||||||
|
id: model.number(),
|
||||||
|
username: model.text().default("foo"),
|
||||||
|
email: model.text(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const User = createMikrORMEntity(user)
|
||||||
|
expectTypeOf(new User()).toMatchTypeOf<{
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const metaData = MetadataStorage.getMetadataFromDecorator(User)
|
||||||
|
expect(metaData.className).toEqual("User")
|
||||||
|
expect(metaData.path).toEqual("User")
|
||||||
|
expect(metaData.properties).toEqual({
|
||||||
|
id: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "number",
|
||||||
|
columnType: "integer",
|
||||||
|
name: "id",
|
||||||
|
nullable: false,
|
||||||
|
getter: false,
|
||||||
|
setter: false,
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
reference: "scalar",
|
||||||
|
type: "string",
|
||||||
|
default: "foo",
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("define hasMany relationship", () => {
|
test("define hasMany relationship", () => {
|
||||||
const model = new EntityBuilder()
|
const model = new EntityBuilder()
|
||||||
const email = model.define("email", {
|
const email = model.define("email", {
|
||||||
|
|||||||
@@ -17,19 +17,16 @@ describe("Enum schema", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: false,
|
|
||||||
indexes: [],
|
indexes: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("apply nullable and optional modifiers", () => {
|
test("apply nullable modifier", () => {
|
||||||
const schema = new EnumSchema(["admin", "moderator", "editor"])
|
const schema = new EnumSchema(["admin", "moderator", "editor"]).nullable()
|
||||||
.nullable()
|
|
||||||
.optional()
|
|
||||||
|
|
||||||
expectTypeOf(schema["$dataType"]).toEqualTypeOf<
|
expectTypeOf(schema["$dataType"]).toEqualTypeOf<
|
||||||
"admin" | "moderator" | "editor" | null | undefined
|
"admin" | "moderator" | "editor" | null
|
||||||
>()
|
>()
|
||||||
|
|
||||||
expect(schema.parse("role")).toEqual({
|
expect(schema.parse("role")).toEqual({
|
||||||
@@ -41,7 +38,6 @@ describe("Enum schema", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
nullable: true,
|
nullable: true,
|
||||||
optional: true,
|
|
||||||
indexes: [],
|
indexes: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ describe("HasMany relationship", () => {
|
|||||||
expect(relationship.parse("user")).toEqual({
|
expect(relationship.parse("user")).toEqual({
|
||||||
name: "user",
|
name: "user",
|
||||||
type: "hasMany",
|
type: "hasMany",
|
||||||
|
nullable: false,
|
||||||
entity: entityRef,
|
entity: entityRef,
|
||||||
options: {},
|
options: {},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ describe("HasOne relationship", () => {
|
|||||||
expect(relationship.parse("user")).toEqual({
|
expect(relationship.parse("user")).toEqual({
|
||||||
name: "user",
|
name: "user",
|
||||||
type: "hasOne",
|
type: "hasOne",
|
||||||
|
nullable: false,
|
||||||
entity: entityRef,
|
entity: entityRef,
|
||||||
options: {},
|
options: {},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ describe("JSON schema", () => {
|
|||||||
name: "json",
|
name: "json",
|
||||||
},
|
},
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: false,
|
|
||||||
indexes: [],
|
indexes: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ describe("ManyToMany relationship", () => {
|
|||||||
expect(relationship.parse("user")).toEqual({
|
expect(relationship.parse("user")).toEqual({
|
||||||
name: "user",
|
name: "user",
|
||||||
type: "manyToMany",
|
type: "manyToMany",
|
||||||
|
nullable: false,
|
||||||
entity: entityRef,
|
entity: entityRef,
|
||||||
options: {},
|
options: {},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ describe("Number schema", () => {
|
|||||||
name: "number",
|
name: "number",
|
||||||
},
|
},
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: false,
|
|
||||||
indexes: [],
|
indexes: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ describe("String schema", () => {
|
|||||||
expect(schema.parse("username")).toEqual({
|
expect(schema.parse("username")).toEqual({
|
||||||
fieldName: "username",
|
fieldName: "username",
|
||||||
dataType: {
|
dataType: {
|
||||||
name: "string",
|
name: "text",
|
||||||
},
|
},
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: false,
|
|
||||||
indexes: [],
|
indexes: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { RelationshipType, SchemaType } from "./types"
|
import { RelationshipType, SchemaType } from "./types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dml entity is a representation of a DML model with a unique
|
||||||
|
* name, its schema and relationships.
|
||||||
|
*/
|
||||||
export class DmlEntity<
|
export class DmlEntity<
|
||||||
Schema extends Record<string, SchemaType<any> | RelationshipType<any>>
|
Schema extends Record<string, SchemaType<any> | RelationshipType<any>>
|
||||||
> {
|
> {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { DmlEntity } from "./entity"
|
import { DmlEntity } from "./entity"
|
||||||
import { TextSchema } from "./schema/text"
|
import { TextSchema } from "./schema/text"
|
||||||
|
import { EnumSchema } from "./schema/enum"
|
||||||
import { JSONSchema } from "./schema/json"
|
import { JSONSchema } from "./schema/json"
|
||||||
import { HasOne } from "./relations/has_one"
|
import { HasOne } from "./relations/has_one"
|
||||||
import { HasMany } from "./relations/has_many"
|
import { HasMany } from "./relations/has_many"
|
||||||
@@ -8,52 +9,114 @@ import { BooleanSchema } from "./schema/boolean"
|
|||||||
import { DateTimeSchema } from "./schema/date_time"
|
import { DateTimeSchema } from "./schema/date_time"
|
||||||
import { ManyToMany } from "./relations/many_to_many"
|
import { ManyToMany } from "./relations/many_to_many"
|
||||||
import { RelationshipType, SchemaType } from "./types"
|
import { RelationshipType, SchemaType } from "./types"
|
||||||
import { EnumSchema } from "./schema/enum"
|
|
||||||
import { HasOneThroughMany } from "./relations/has_one_through_many"
|
import { HasOneThroughMany } from "./relations/has_one_through_many"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity builder exposes the API to create an entity and define its
|
||||||
|
* schema using the shorthand methods.
|
||||||
|
*/
|
||||||
export class EntityBuilder {
|
export class EntityBuilder {
|
||||||
|
/**
|
||||||
|
* Define an entity or a model. The name should be unique across
|
||||||
|
* all the entities.
|
||||||
|
*/
|
||||||
define<
|
define<
|
||||||
Schema extends Record<string, SchemaType<any> | RelationshipType<any>>
|
Schema extends Record<string, SchemaType<any> | RelationshipType<any>>
|
||||||
>(name: string, schema: Schema) {
|
>(name: string, schema: Schema) {
|
||||||
return new DmlEntity(name, schema)
|
return new DmlEntity(name, schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a text/string based column
|
||||||
|
*/
|
||||||
text() {
|
text() {
|
||||||
return new TextSchema()
|
return new TextSchema()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a boolean column
|
||||||
|
*/
|
||||||
boolean() {
|
boolean() {
|
||||||
return new BooleanSchema()
|
return new BooleanSchema()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a numeric/integer column
|
||||||
|
*/
|
||||||
number() {
|
number() {
|
||||||
return new NumberSchema()
|
return new NumberSchema()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a timestampz column
|
||||||
|
*/
|
||||||
dateTime() {
|
dateTime() {
|
||||||
return new DateTimeSchema()
|
return new DateTimeSchema()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a JSON column to store data as a
|
||||||
|
* JSON string
|
||||||
|
*/
|
||||||
json() {
|
json() {
|
||||||
return new JSONSchema()
|
return new JSONSchema()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define an enum column where only a pre-defined set
|
||||||
|
* of values are allowed.
|
||||||
|
*/
|
||||||
enum<const Values extends unknown>(values: Values[]) {
|
enum<const Values extends unknown>(values: Values[]) {
|
||||||
return new EnumSchema<Values>(values)
|
return new EnumSchema<Values>(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has one relationship defines a relationship between two entities
|
||||||
|
* where the owner of the relationship has exactly one instance
|
||||||
|
* of the related entity.
|
||||||
|
*
|
||||||
|
* For example: A user "hasOne" profile
|
||||||
|
*
|
||||||
|
* You may use the "belongsTo" relationship to define the inverse
|
||||||
|
* of the "hasOne" relationship
|
||||||
|
*/
|
||||||
hasOne<T>(entityBuilder: T, options?: Record<string, any>) {
|
hasOne<T>(entityBuilder: T, options?: Record<string, any>) {
|
||||||
return new HasOne<T>(entityBuilder, options || {})
|
return new HasOne<T>(entityBuilder, options || {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has many relationship defines a relationship between two entities
|
||||||
|
* where the owner of the relationship has many instance of the
|
||||||
|
* related entity.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* - A user "hasMany" books
|
||||||
|
* - A user "hasMany" addresses
|
||||||
|
*/
|
||||||
hasMany<T>(entityBuilder: T, options?: Record<string, any>) {
|
hasMany<T>(entityBuilder: T, options?: Record<string, any>) {
|
||||||
return new HasMany<T>(entityBuilder, options || {})
|
return new HasMany<T>(entityBuilder, options || {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a hasOneThroughMany relationship between two entities.
|
||||||
|
* @todo Remove in favor of "belongTo"
|
||||||
|
*/
|
||||||
hasOneThroughMany<T>(entityBuilder: T, options?: Record<string, any>) {
|
hasOneThroughMany<T>(entityBuilder: T, options?: Record<string, any>) {
|
||||||
return new HasOneThroughMany<T>(entityBuilder, options || {})
|
return new HasOneThroughMany<T>(entityBuilder, options || {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ManyToMany relationship defines a relationship between two entities
|
||||||
|
* where the owner of the relationship has many instance of the
|
||||||
|
* related entity via a pivot table.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* - A user has many teams. But a team has many users as well. So this
|
||||||
|
* relationship requires a pivot table to establish a many to many
|
||||||
|
* relationship between two entities
|
||||||
|
*/
|
||||||
manyToMany<T>(entityBuilder: T, options?: Record<string, any>) {
|
manyToMany<T>(entityBuilder: T, options?: Record<string, any>) {
|
||||||
return new ManyToMany<T>(entityBuilder, options || {})
|
return new ManyToMany<T>(entityBuilder, options || {})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,32 +23,36 @@ import type {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* DML entity data types to PostgreSQL data types via
|
* DML entity data types to PostgreSQL data types via
|
||||||
* Mikro ORM
|
* Mikro ORM.
|
||||||
|
*
|
||||||
|
* We remove "enum" type from here, because we use a dedicated
|
||||||
|
* mikro orm decorator for that
|
||||||
*/
|
*/
|
||||||
const COLUMN_TYPES: {
|
const COLUMN_TYPES: {
|
||||||
[K in KnownDataTypes]: string
|
[K in Exclude<KnownDataTypes, "enum">]: string
|
||||||
} = {
|
} = {
|
||||||
boolean: "boolean",
|
boolean: "boolean",
|
||||||
dateTime: "timestamptz",
|
dateTime: "timestamptz",
|
||||||
number: "integer",
|
number: "integer",
|
||||||
string: "text",
|
text: "text",
|
||||||
json: "jsonb",
|
json: "jsonb",
|
||||||
enum: "enum", // ignore for now
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DML entity data types to Mikro ORM property
|
* DML entity data types to Mikro ORM property
|
||||||
* types
|
* types.
|
||||||
|
*
|
||||||
|
* We remove "enum" type from here, because we use a dedicated
|
||||||
|
* mikro orm decorator for that
|
||||||
*/
|
*/
|
||||||
const PROPERTY_TYPES: {
|
const PROPERTY_TYPES: {
|
||||||
[K in KnownDataTypes]: string
|
[K in Exclude<KnownDataTypes, "enum">]: string
|
||||||
} = {
|
} = {
|
||||||
boolean: "boolean",
|
boolean: "boolean",
|
||||||
dateTime: "date",
|
dateTime: "date",
|
||||||
number: "number",
|
number: "number",
|
||||||
string: "string",
|
text: "string",
|
||||||
json: "any",
|
json: "any",
|
||||||
enum: "enum", // ignore for now
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,20 +69,23 @@ function defineProperty(
|
|||||||
Enum({
|
Enum({
|
||||||
items: () => field.dataType.options!.choices,
|
items: () => field.dataType.options!.choices,
|
||||||
nullable: field.nullable,
|
nullable: field.nullable,
|
||||||
|
default: field.defaultValue,
|
||||||
})(MikroORMEntity.prototype, field.fieldName)
|
})(MikroORMEntity.prototype, field.fieldName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define rest of properties
|
||||||
|
*/
|
||||||
const columnType = COLUMN_TYPES[field.dataType.name]
|
const columnType = COLUMN_TYPES[field.dataType.name]
|
||||||
const propertyType = PROPERTY_TYPES[field.dataType.name]
|
const propertyType = PROPERTY_TYPES[field.dataType.name]
|
||||||
|
|
||||||
/**
|
Property({
|
||||||
* @todo: Add support for default value
|
columnType,
|
||||||
*/
|
type: propertyType,
|
||||||
Property({ columnType, type: propertyType, nullable: field.nullable })(
|
nullable: field.nullable,
|
||||||
MikroORMEntity.prototype,
|
default: field.defaultValue,
|
||||||
field.fieldName
|
})(MikroORMEntity.prototype, field.fieldName)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,7 +96,8 @@ function defineRelationship(
|
|||||||
relationship: RelationshipMetadata
|
relationship: RelationshipMetadata
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Defining relationships
|
* We expect the relationship.entity to be a function that
|
||||||
|
* lazily returns the related entity
|
||||||
*/
|
*/
|
||||||
const relatedEntity =
|
const relatedEntity =
|
||||||
typeof relationship.entity === "function"
|
typeof relationship.entity === "function"
|
||||||
@@ -115,6 +123,10 @@ function defineRelationship(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converting the related entity name (which should be in camelCase)
|
||||||
|
* to "PascalCase"
|
||||||
|
*/
|
||||||
const relatedModelName = upperCaseFirst(relatedEntity.name)
|
const relatedModelName = upperCaseFirst(relatedEntity.name)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { SchemaType } from "../types"
|
import { MaybeFieldMetadata } from "../types"
|
||||||
import { OptionalModifier } from "./optional"
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nullable modifier marks a schema node as nullable
|
||||||
|
*/
|
||||||
export class NullableModifier<T> {
|
export class NullableModifier<T> {
|
||||||
/**
|
/**
|
||||||
* A type-only property to infer the JavScript data-type
|
* A type-only property to infer the JavScript data-type
|
||||||
@@ -12,17 +14,12 @@ export class NullableModifier<T> {
|
|||||||
* The parent schema on which the nullable modifier is
|
* The parent schema on which the nullable modifier is
|
||||||
* applied
|
* applied
|
||||||
*/
|
*/
|
||||||
#schema: SchemaType<T>
|
#schema: {
|
||||||
|
parse(fieldName: string): MaybeFieldMetadata
|
||||||
constructor(schema: SchemaType<T>) {
|
|
||||||
this.#schema = schema
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
constructor(schema: { parse(fieldName: string): MaybeFieldMetadata }) {
|
||||||
* Apply optional modifier on the schema
|
this.#schema = schema
|
||||||
*/
|
|
||||||
optional() {
|
|
||||||
return new OptionalModifier<T | null>(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import { SchemaType } from "../types"
|
|
||||||
import { NullableModifier } from "./nullable"
|
|
||||||
|
|
||||||
export class OptionalModifier<T> {
|
|
||||||
/**
|
|
||||||
* A type-only property to infer the JavScript data-type
|
|
||||||
* of the schema property
|
|
||||||
*/
|
|
||||||
declare $dataType: T | undefined
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parent schema on which the nullable modifier is
|
|
||||||
* applied
|
|
||||||
*/
|
|
||||||
#schema: SchemaType<T>
|
|
||||||
|
|
||||||
constructor(schema: SchemaType<T>) {
|
|
||||||
this.#schema = schema
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply nullable modifier on the schema
|
|
||||||
*/
|
|
||||||
nullable() {
|
|
||||||
return new NullableModifier<T | undefined>(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the serialized metadata
|
|
||||||
*/
|
|
||||||
parse(fieldName: string) {
|
|
||||||
const schema = this.#schema.parse(fieldName)
|
|
||||||
schema.optional = true
|
|
||||||
return schema
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { NullableModifier } from "../modifiers/nullable"
|
||||||
import { RelationshipMetadata, RelationshipType } from "../types"
|
import { RelationshipMetadata, RelationshipType } from "../types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,12 +25,20 @@ export abstract class BaseRelationship<T> implements RelationshipType<T> {
|
|||||||
this.#options = options
|
this.#options = options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply nullable modifier on the schema
|
||||||
|
*/
|
||||||
|
nullable() {
|
||||||
|
return new NullableModifier<T>(this)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the parsed copy of the relationship
|
* Returns the parsed copy of the relationship
|
||||||
*/
|
*/
|
||||||
parse(relationshipName: string): RelationshipMetadata {
|
parse(relationshipName: string): RelationshipMetadata {
|
||||||
return {
|
return {
|
||||||
name: relationshipName,
|
name: relationshipName,
|
||||||
|
nullable: false,
|
||||||
entity: this.#referencedEntity,
|
entity: this.#referencedEntity,
|
||||||
options: this.#options,
|
options: this.#options,
|
||||||
type: this.relationshipType,
|
type: this.relationshipType,
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
import { BaseRelationship } from "./base"
|
import { BaseRelationship } from "./base"
|
||||||
import { RelationshipMetadata } from "../types"
|
import { RelationshipMetadata } from "../types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HasMany relationship defines a relationship between two entities
|
||||||
|
* where the owner of the relationship has many instance of the
|
||||||
|
* related entity.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* - A user HasMany books
|
||||||
|
* - A user HasMany addresses
|
||||||
|
*/
|
||||||
export class HasMany<T> extends BaseRelationship<T> {
|
export class HasMany<T> extends BaseRelationship<T> {
|
||||||
protected relationshipType: RelationshipMetadata["type"] = "hasMany"
|
protected relationshipType: RelationshipMetadata["type"] = "hasMany"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
import { BaseRelationship } from "./base"
|
import { BaseRelationship } from "./base"
|
||||||
import { RelationshipMetadata } from "../types"
|
import { RelationshipMetadata } from "../types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HasOne relationship defines a relationship between two entities
|
||||||
|
* where the owner of the relationship has exactly one instance
|
||||||
|
* of the related entity.
|
||||||
|
*
|
||||||
|
* For example: A user HasOne profile
|
||||||
|
*
|
||||||
|
* You may use the "BelongsTo" relationship to define the inverse
|
||||||
|
* of the "HasOne" relationship
|
||||||
|
*/
|
||||||
export class HasOne<T> extends BaseRelationship<T> {
|
export class HasOne<T> extends BaseRelationship<T> {
|
||||||
protected relationshipType: RelationshipMetadata["type"] = "hasOne"
|
protected relationshipType: RelationshipMetadata["type"] = "hasOne"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
import { BaseRelationship } from "./base"
|
import { BaseRelationship } from "./base"
|
||||||
import { RelationshipMetadata } from "../types"
|
import { RelationshipMetadata } from "../types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ManyToMany relationship defines a relationship between two entities
|
||||||
|
* where the owner of the relationship has many instance of the
|
||||||
|
* related entity via a pivot table.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* - A user has many teams. But a team has many users as well. So this
|
||||||
|
* relationship requires a pivot table to establish a many to many
|
||||||
|
* relationship between two entities
|
||||||
|
*/
|
||||||
export class ManyToMany<T> extends BaseRelationship<T> {
|
export class ManyToMany<T> extends BaseRelationship<T> {
|
||||||
protected relationshipType: RelationshipMetadata["type"] = "manyToMany"
|
protected relationshipType: RelationshipMetadata["type"] = "manyToMany"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { SchemaMetadata, SchemaType } from "../types"
|
import { SchemaMetadata, SchemaType } from "../types"
|
||||||
import { NullableModifier } from "../modifiers/nullable"
|
import { NullableModifier } from "../modifiers/nullable"
|
||||||
import { OptionalModifier } from "../modifiers/optional"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base schema class offers shared affordances to define
|
* The base schema class offers shared affordances to define
|
||||||
@@ -9,6 +8,7 @@ import { OptionalModifier } from "../modifiers/optional"
|
|||||||
export abstract class BaseSchema<T> implements SchemaType<T> {
|
export abstract class BaseSchema<T> implements SchemaType<T> {
|
||||||
#indexes: SchemaMetadata["indexes"] = []
|
#indexes: SchemaMetadata["indexes"] = []
|
||||||
#relationships: SchemaMetadata["relationships"] = []
|
#relationships: SchemaMetadata["relationships"] = []
|
||||||
|
#defaultValue?: T
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The runtime dataType for the schema. It is not the same as
|
* The runtime dataType for the schema. It is not the same as
|
||||||
@@ -30,10 +30,11 @@ export abstract class BaseSchema<T> implements SchemaType<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply optional modifier on the schema
|
* Define default value for the property
|
||||||
*/
|
*/
|
||||||
optional() {
|
default(value: T) {
|
||||||
return new OptionalModifier<T>(this)
|
this.#defaultValue = value
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +45,7 @@ export abstract class BaseSchema<T> implements SchemaType<T> {
|
|||||||
fieldName,
|
fieldName,
|
||||||
dataType: this.dataType,
|
dataType: this.dataType,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: false,
|
defaultValue: this.#defaultValue,
|
||||||
indexes: this.#indexes,
|
indexes: this.#indexes,
|
||||||
relationships: this.#relationships,
|
relationships: this.#relationships,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { SchemaMetadata } from "../types"
|
import { SchemaMetadata } from "../types"
|
||||||
import { BaseSchema } from "./base"
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BooleanSchema class is used to define a boolean
|
||||||
|
* property
|
||||||
|
*/
|
||||||
export class BooleanSchema extends BaseSchema<boolean> {
|
export class BooleanSchema extends BaseSchema<boolean> {
|
||||||
protected dataType: SchemaMetadata["dataType"] = {
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
name: "boolean",
|
name: "boolean",
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { SchemaMetadata } from "../types"
|
import { SchemaMetadata } from "../types"
|
||||||
import { BaseSchema } from "./base"
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DateTimeSchema class is used to define a timestampz
|
||||||
|
* property
|
||||||
|
*/
|
||||||
export class DateTimeSchema extends BaseSchema<Date> {
|
export class DateTimeSchema extends BaseSchema<Date> {
|
||||||
protected dataType: SchemaMetadata["dataType"] = {
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
name: "dateTime",
|
name: "dateTime",
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { SchemaMetadata } from "../types"
|
import { SchemaMetadata } from "../types"
|
||||||
import { BaseSchema } from "./base"
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EnumSchema is used to define a property with pre-defined
|
||||||
|
* list of choices.
|
||||||
|
*/
|
||||||
export class EnumSchema<
|
export class EnumSchema<
|
||||||
const Values extends unknown
|
const Values extends unknown
|
||||||
> extends BaseSchema<Values> {
|
> extends BaseSchema<Values> {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { SchemaMetadata } from "../types"
|
import { SchemaMetadata } from "../types"
|
||||||
import { BaseSchema } from "./base"
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The JSONSchema is used to define a property that stores
|
||||||
|
* data as a JSON string
|
||||||
|
*/
|
||||||
export class JSONSchema extends BaseSchema<string> {
|
export class JSONSchema extends BaseSchema<string> {
|
||||||
protected dataType: SchemaMetadata["dataType"] = {
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
name: "json",
|
name: "json",
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { SchemaMetadata } from "../types"
|
import { SchemaMetadata } from "../types"
|
||||||
import { BaseSchema } from "./base"
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The NumberSchema is used to define a numeric/integer
|
||||||
|
* property
|
||||||
|
*/
|
||||||
export class NumberSchema extends BaseSchema<number> {
|
export class NumberSchema extends BaseSchema<number> {
|
||||||
protected dataType: SchemaMetadata["dataType"] = {
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
name: "number",
|
name: "number",
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { SchemaMetadata } from "../types"
|
import { SchemaMetadata } from "../types"
|
||||||
import { BaseSchema } from "./base"
|
import { BaseSchema } from "./base"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The NumberSchema is used to define a textual property
|
||||||
|
*/
|
||||||
export class TextSchema extends BaseSchema<string> {
|
export class TextSchema extends BaseSchema<string> {
|
||||||
protected dataType: SchemaMetadata["dataType"] = {
|
protected dataType: SchemaMetadata["dataType"] = {
|
||||||
name: "string",
|
name: "text",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export const MIKRO_ORM_ENTITY_GENERATOR = Symbol.for(
|
|
||||||
"generate_mikro_orm_entity"
|
|
||||||
)
|
|
||||||
@@ -4,7 +4,7 @@ import { DmlEntity } from "./entity"
|
|||||||
* The supported data types
|
* The supported data types
|
||||||
*/
|
*/
|
||||||
export type KnownDataTypes =
|
export type KnownDataTypes =
|
||||||
| "string"
|
| "text"
|
||||||
| "boolean"
|
| "boolean"
|
||||||
| "enum"
|
| "enum"
|
||||||
| "number"
|
| "number"
|
||||||
@@ -12,23 +12,22 @@ export type KnownDataTypes =
|
|||||||
| "json"
|
| "json"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The meta-data returned by the relationship parse
|
* Any field that contains "nullable" and "optional" properties
|
||||||
* method
|
* in their metadata are qualified as maybe fields.
|
||||||
|
*
|
||||||
|
* This allows us to wrap them inside "NullableModifier" and
|
||||||
|
* "OptionalModifier" classes.
|
||||||
*/
|
*/
|
||||||
export type RelationshipMetadata = {
|
export type MaybeFieldMetadata = {
|
||||||
name: string
|
nullable: boolean
|
||||||
type: "hasOne" | "hasMany" | "hasOneThroughMany" | "manyToMany"
|
|
||||||
entity: unknown
|
|
||||||
options: Record<string, any>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The meta-data returned by the schema parse method
|
* The meta-data returned by the schema parse method
|
||||||
*/
|
*/
|
||||||
export type SchemaMetadata = {
|
export type SchemaMetadata = MaybeFieldMetadata & {
|
||||||
nullable: boolean
|
|
||||||
optional: boolean
|
|
||||||
fieldName: string
|
fieldName: string
|
||||||
|
defaultValue?: any
|
||||||
dataType: {
|
dataType: {
|
||||||
name: KnownDataTypes
|
name: KnownDataTypes
|
||||||
options?: Record<string, any>
|
options?: Record<string, any>
|
||||||
@@ -50,6 +49,17 @@ export type SchemaType<T> = {
|
|||||||
parse(fieldName: string): SchemaMetadata
|
parse(fieldName: string): SchemaMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The meta-data returned by the relationship parse
|
||||||
|
* method
|
||||||
|
*/
|
||||||
|
export type RelationshipMetadata = MaybeFieldMetadata & {
|
||||||
|
name: string
|
||||||
|
type: "hasOne" | "hasMany" | "hasOneThroughMany" | "manyToMany"
|
||||||
|
entity: unknown
|
||||||
|
options: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Definition of a relationship type. It should have a parse
|
* Definition of a relationship type. It should have a parse
|
||||||
* method to get the metadata and a type-only property
|
* method to get the metadata and a type-only property
|
||||||
|
|||||||
Reference in New Issue
Block a user