@tsed/objection 8.0.1 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -13
- package/src/components/createBooleanColumn.spec.ts +20 -0
- package/src/components/createBooleanColumn.ts +12 -0
- package/src/components/createIdColumn.spec.ts +45 -0
- package/src/components/createIdColumn.ts +23 -0
- package/src/components/createNumberColumn.spec.ts +34 -0
- package/src/components/createNumberColumn.ts +13 -0
- package/src/components/createStringColumn.spec.ts +34 -0
- package/src/components/createStringColumn.ts +13 -0
- package/src/decorators/belongsToOne.spec.ts +60 -0
- package/src/decorators/belongsToOne.ts +14 -0
- package/src/decorators/columnOptions.ts +13 -0
- package/src/decorators/decimal.spec.ts +52 -0
- package/src/decorators/decimal.ts +35 -0
- package/src/decorators/entity.spec.ts +24 -0
- package/src/decorators/entity.ts +29 -0
- package/src/decorators/hasMany.spec.ts +60 -0
- package/src/decorators/hasMany.ts +14 -0
- package/src/decorators/hasOne.spec.ts +60 -0
- package/src/decorators/hasOne.ts +14 -0
- package/src/decorators/hasOneThroughRelation.spec.ts +72 -0
- package/src/decorators/hasOneThroughRelation.ts +14 -0
- package/src/decorators/idColumn.spec.ts +31 -0
- package/src/decorators/idColumn.ts +26 -0
- package/src/decorators/manyToMany.spec.ts +72 -0
- package/src/decorators/manyToMany.ts +14 -0
- package/src/decorators/relatesTo.spec.ts +34 -0
- package/src/decorators/relatesTo.ts +26 -0
- package/src/domain/ColumnOpts.ts +4 -0
- package/src/domain/RelationshipOpts.ts +14 -0
- package/src/domain/interfaces.ts +9 -0
- package/src/index.ts +29 -0
- package/src/services/ColumnTypesContainer.ts +8 -0
- package/src/services/ObjectionConnection.ts +26 -0
- package/src/utils/connect.ts +9 -0
- package/src/utils/createColumns.spec.ts +79 -0
- package/src/utils/createColumns.ts +39 -0
- package/src/utils/createJoinKeys.ts +38 -0
- package/src/utils/createRelationshipMapping.ts +21 -0
- package/src/utils/defineStaticGetter.ts +10 -0
- package/src/utils/getColumnCtx.ts +21 -0
- package/src/utils/getJsonEntityRelationships.ts +16 -0
package/package.json
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
"name": "@tsed/objection",
|
|
3
3
|
"description": "Objection.js package for Ts.ED framework",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "8.0.
|
|
5
|
+
"version": "8.0.2",
|
|
6
6
|
"source": "./src/index.ts",
|
|
7
7
|
"main": "./lib/esm/index.js",
|
|
8
8
|
"module": "./lib/esm/index.js",
|
|
9
9
|
"typings": "./lib/types/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
+
"@tsed/source": "./src/index.ts",
|
|
12
13
|
"types": "./lib/types/index.d.ts",
|
|
13
14
|
"import": "./lib/esm/index.js",
|
|
14
15
|
"default": "./lib/esm/index.js"
|
|
@@ -25,13 +26,13 @@
|
|
|
25
26
|
"tslib": "2.7.0"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
|
-
"@tsed/ajv": "8.0.
|
|
29
|
-
"@tsed/barrels": "8.0.
|
|
30
|
-
"@tsed/core": "8.0.
|
|
31
|
-
"@tsed/di": "8.0.
|
|
32
|
-
"@tsed/json-mapper": "8.0.
|
|
33
|
-
"@tsed/schema": "8.0.
|
|
34
|
-
"@tsed/typescript": "8.0.
|
|
29
|
+
"@tsed/ajv": "8.0.2",
|
|
30
|
+
"@tsed/barrels": "8.0.2",
|
|
31
|
+
"@tsed/core": "8.0.2",
|
|
32
|
+
"@tsed/di": "8.0.2",
|
|
33
|
+
"@tsed/json-mapper": "8.0.2",
|
|
34
|
+
"@tsed/schema": "8.0.2",
|
|
35
|
+
"@tsed/typescript": "8.0.2",
|
|
35
36
|
"eslint": "9.12.0",
|
|
36
37
|
"knex": "^3.1.0",
|
|
37
38
|
"objection": "^2.2.18",
|
|
@@ -40,11 +41,11 @@
|
|
|
40
41
|
"vitest": "2.1.2"
|
|
41
42
|
},
|
|
42
43
|
"peerDependencies": {
|
|
43
|
-
"@tsed/ajv": "8.0.
|
|
44
|
-
"@tsed/core": "8.0.
|
|
45
|
-
"@tsed/di": "8.0.
|
|
46
|
-
"@tsed/json-mapper": "8.0.
|
|
47
|
-
"@tsed/schema": "8.0.
|
|
44
|
+
"@tsed/ajv": "8.0.2",
|
|
45
|
+
"@tsed/core": "8.0.2",
|
|
46
|
+
"@tsed/di": "8.0.2",
|
|
47
|
+
"@tsed/json-mapper": "8.0.2",
|
|
48
|
+
"@tsed/schema": "8.0.2",
|
|
48
49
|
"knex": ">=0.94.0",
|
|
49
50
|
"objection": ">=2.0.0"
|
|
50
51
|
},
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {Property} from "@tsed/schema";
|
|
2
|
+
|
|
3
|
+
import {createTableStub} from "../../test/helpers/knex/table.js";
|
|
4
|
+
import {Entity} from "../decorators/entity.js";
|
|
5
|
+
import {createColumns} from "../utils/createColumns.js";
|
|
6
|
+
|
|
7
|
+
describe("createBooleanColumn", () => {
|
|
8
|
+
it("should create table from a given class", () => {
|
|
9
|
+
@Entity("users")
|
|
10
|
+
class User {
|
|
11
|
+
@Property()
|
|
12
|
+
activated: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const table = createTableStub();
|
|
16
|
+
createColumns(table, User);
|
|
17
|
+
|
|
18
|
+
expect(table.boolean).toHaveBeenCalledWith("activated");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type {Knex} from "knex";
|
|
2
|
+
|
|
3
|
+
import {ColumnTypesContainer} from "../services/ColumnTypesContainer.js";
|
|
4
|
+
import {ColumnCtx} from "../utils/getColumnCtx.js";
|
|
5
|
+
/**
|
|
6
|
+
* @ignore
|
|
7
|
+
*/
|
|
8
|
+
export function createBooleanColumn(table: Knex.TableBuilder, {entity}: ColumnCtx) {
|
|
9
|
+
table.boolean(entity.propertyName);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
ColumnTypesContainer.set("boolean", createBooleanColumn);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {createTableStub} from "../../test/helpers/knex/table.js";
|
|
2
|
+
import {Entity} from "../decorators/entity.js";
|
|
3
|
+
import {IdColumn} from "../decorators/idColumn.js";
|
|
4
|
+
import {createColumns} from "../utils/createColumns.js";
|
|
5
|
+
|
|
6
|
+
describe("createIdColumn", () => {
|
|
7
|
+
it("should create table from a given class (bigIncrements)", () => {
|
|
8
|
+
@Entity("users")
|
|
9
|
+
class User {
|
|
10
|
+
@IdColumn("bigIncrements")
|
|
11
|
+
id: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const table = createTableStub();
|
|
15
|
+
createColumns(table, User);
|
|
16
|
+
|
|
17
|
+
expect(table.bigIncrements).toHaveBeenCalledWith("id");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should create table from a given class (increments)", () => {
|
|
21
|
+
@Entity("users")
|
|
22
|
+
class User {
|
|
23
|
+
@IdColumn("increments")
|
|
24
|
+
id: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const table = createTableStub();
|
|
28
|
+
createColumns(table, User);
|
|
29
|
+
|
|
30
|
+
expect(table.increments).toHaveBeenCalledWith("id");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should create table from a given class (uuid)", () => {
|
|
34
|
+
@Entity("users")
|
|
35
|
+
class User {
|
|
36
|
+
@IdColumn("uuid")
|
|
37
|
+
id: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const table = createTableStub();
|
|
41
|
+
createColumns(table, User);
|
|
42
|
+
|
|
43
|
+
expect(table.uuid).toHaveBeenCalledWith("id");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {randomUUID} from "crypto";
|
|
2
|
+
import type {Knex} from "knex";
|
|
3
|
+
|
|
4
|
+
import {ColumnTypesContainer} from "../services/ColumnTypesContainer.js";
|
|
5
|
+
import {ColumnCtx} from "../utils/getColumnCtx.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @ignore
|
|
9
|
+
*/
|
|
10
|
+
export function createIdColumn(table: Knex.TableBuilder, {entity, options}: ColumnCtx) {
|
|
11
|
+
switch (options.type) {
|
|
12
|
+
case "bigIncrements":
|
|
13
|
+
table.bigIncrements(entity.propertyName).primary();
|
|
14
|
+
break;
|
|
15
|
+
case "increments":
|
|
16
|
+
table.increments(entity.propertyName).primary();
|
|
17
|
+
break;
|
|
18
|
+
case "uuid":
|
|
19
|
+
table.uuid(entity.propertyName).primary().defaultTo(randomUUID());
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
ColumnTypesContainer.set("idColumn", createIdColumn);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {Property} from "@tsed/schema";
|
|
2
|
+
|
|
3
|
+
import {createTableStub} from "../../test/helpers/knex/table.js";
|
|
4
|
+
import {Decimal} from "../decorators/decimal.js";
|
|
5
|
+
import {Entity} from "../decorators/entity.js";
|
|
6
|
+
import {createColumns} from "../utils/createColumns.js";
|
|
7
|
+
|
|
8
|
+
describe("createNumberColumn", () => {
|
|
9
|
+
it("should create table from a given class (default)", async () => {
|
|
10
|
+
@Entity("users")
|
|
11
|
+
class User {
|
|
12
|
+
@Property()
|
|
13
|
+
prop: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const table = createTableStub();
|
|
17
|
+
await createColumns(table, User);
|
|
18
|
+
|
|
19
|
+
expect(table.decimal).toHaveBeenCalledWith("prop", undefined, undefined);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should create table from a given class (decimal)", async () => {
|
|
23
|
+
@Entity("users")
|
|
24
|
+
class User {
|
|
25
|
+
@Decimal({scale: 1, precision: 12})
|
|
26
|
+
prop: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const table = createTableStub();
|
|
30
|
+
await createColumns(table, User);
|
|
31
|
+
|
|
32
|
+
expect(table.decimal).toHaveBeenCalledWith("prop", 12, 1);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type {Knex} from "knex";
|
|
2
|
+
|
|
3
|
+
import {ColumnTypesContainer} from "../services/ColumnTypesContainer.js";
|
|
4
|
+
import {ColumnCtx} from "../utils/getColumnCtx.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @ignore
|
|
8
|
+
*/
|
|
9
|
+
export function createNumberColumn(table: Knex.TableBuilder, {entity, options}: ColumnCtx) {
|
|
10
|
+
table.decimal(entity.propertyName, options.precision, options.scale);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
ColumnTypesContainer.set("number", createNumberColumn);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {MaxLength, Property} from "@tsed/schema";
|
|
2
|
+
|
|
3
|
+
import {createTableStub} from "../../test/helpers/knex/table.js";
|
|
4
|
+
import {Entity} from "../decorators/entity.js";
|
|
5
|
+
import {createColumns} from "../utils/createColumns.js";
|
|
6
|
+
|
|
7
|
+
describe("createNumberColumn", () => {
|
|
8
|
+
it("should create table from a given class (with maxLength)", async () => {
|
|
9
|
+
@Entity("users")
|
|
10
|
+
class User {
|
|
11
|
+
@Property()
|
|
12
|
+
@MaxLength(200)
|
|
13
|
+
prop: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const table = createTableStub();
|
|
17
|
+
await createColumns(table, User);
|
|
18
|
+
|
|
19
|
+
expect(table.string).toHaveBeenCalledWith("prop", 200);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should create table from a given class (without maxLength)", async () => {
|
|
23
|
+
@Entity("users")
|
|
24
|
+
class User {
|
|
25
|
+
@Property()
|
|
26
|
+
prop: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const table = createTableStub();
|
|
30
|
+
await createColumns(table, User);
|
|
31
|
+
|
|
32
|
+
expect(table.string).toHaveBeenCalledWith("prop", undefined);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type {Knex} from "knex";
|
|
2
|
+
|
|
3
|
+
import {ColumnTypesContainer} from "../services/ColumnTypesContainer.js";
|
|
4
|
+
import {ColumnCtx} from "../utils/getColumnCtx.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @ignore
|
|
8
|
+
*/
|
|
9
|
+
export function createStringColumn(table: Knex.TableBuilder, {entity, schema}: ColumnCtx) {
|
|
10
|
+
table.string(entity.propertyName, schema.maxLength);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
ColumnTypesContainer.set("string", createStringColumn);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {Model} from "objection";
|
|
2
|
+
|
|
3
|
+
import {BelongsToOne} from "./belongsToOne.js";
|
|
4
|
+
import {Entity} from "./entity.js";
|
|
5
|
+
import {IdColumn} from "./idColumn.js";
|
|
6
|
+
|
|
7
|
+
describe("@BelongsToOne", () => {
|
|
8
|
+
it("should set metadata", () => {
|
|
9
|
+
@Entity("user")
|
|
10
|
+
class User extends Model {
|
|
11
|
+
@IdColumn()
|
|
12
|
+
id!: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Entity("movie")
|
|
16
|
+
class Movie extends Model {
|
|
17
|
+
@IdColumn()
|
|
18
|
+
id!: string;
|
|
19
|
+
userId!: string;
|
|
20
|
+
@BelongsToOne()
|
|
21
|
+
user?: User;
|
|
22
|
+
}
|
|
23
|
+
expect(Movie.relationMappings).toEqual({
|
|
24
|
+
user: {
|
|
25
|
+
relation: Model.BelongsToOneRelation,
|
|
26
|
+
modelClass: User,
|
|
27
|
+
join: {
|
|
28
|
+
from: "movie.userId",
|
|
29
|
+
to: "user.id"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
it("should set custom relationship path", () => {
|
|
35
|
+
@Entity("user")
|
|
36
|
+
class User extends Model {
|
|
37
|
+
@IdColumn()
|
|
38
|
+
id!: string;
|
|
39
|
+
userId!: string;
|
|
40
|
+
}
|
|
41
|
+
@Entity("movie")
|
|
42
|
+
class Movie extends Model {
|
|
43
|
+
@IdColumn()
|
|
44
|
+
id!: string;
|
|
45
|
+
ownerId!: string;
|
|
46
|
+
@BelongsToOne({from: "ownerId", to: "userId"})
|
|
47
|
+
user?: User;
|
|
48
|
+
}
|
|
49
|
+
expect(Movie.relationMappings).toEqual({
|
|
50
|
+
user: {
|
|
51
|
+
relation: Model.BelongsToOneRelation,
|
|
52
|
+
modelClass: User,
|
|
53
|
+
join: {
|
|
54
|
+
from: "movie.ownerId",
|
|
55
|
+
to: "user.userId"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {Model} from "objection";
|
|
2
|
+
|
|
3
|
+
import {RelationshipOptsWithoutThrough} from "../domain/RelationshipOpts.js";
|
|
4
|
+
import {RelatesTo} from "./relatesTo.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param opts
|
|
9
|
+
* @decorator
|
|
10
|
+
* @objection
|
|
11
|
+
*/
|
|
12
|
+
export function BelongsToOne(opts?: RelationshipOptsWithoutThrough): PropertyDecorator {
|
|
13
|
+
return RelatesTo(Model.BelongsToOneRelation, opts);
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {StoreSet} from "@tsed/core";
|
|
2
|
+
|
|
3
|
+
import type {ColumnOpts} from "../domain/ColumnOpts.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param options
|
|
8
|
+
* @decorator
|
|
9
|
+
* @objection
|
|
10
|
+
*/
|
|
11
|
+
export function ColumnOptions(options: Partial<ColumnOpts>): PropertyDecorator {
|
|
12
|
+
return StoreSet("objection", options) as any;
|
|
13
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {Store} from "@tsed/core";
|
|
2
|
+
import {getJsonSchema} from "@tsed/schema";
|
|
3
|
+
|
|
4
|
+
import {Decimal} from "./decimal.js";
|
|
5
|
+
|
|
6
|
+
describe("@Decimal", () => {
|
|
7
|
+
it("should set metadata", () => {
|
|
8
|
+
class MyModel {
|
|
9
|
+
@Decimal({scale: 1, precision: 12})
|
|
10
|
+
prop: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
expect(getJsonSchema(MyModel)).toEqual({
|
|
14
|
+
properties: {
|
|
15
|
+
prop: {
|
|
16
|
+
type: "number"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
type: "object"
|
|
20
|
+
});
|
|
21
|
+
expect(Store.from(MyModel, "prop").get("objection")).toEqual({
|
|
22
|
+
columnType: "number",
|
|
23
|
+
options: {
|
|
24
|
+
precision: 12,
|
|
25
|
+
scale: 1,
|
|
26
|
+
type: "decimal"
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should set metadata (without options)", () => {
|
|
32
|
+
class MyModel {
|
|
33
|
+
@Decimal()
|
|
34
|
+
prop: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
expect(getJsonSchema(MyModel)).toEqual({
|
|
38
|
+
properties: {
|
|
39
|
+
prop: {
|
|
40
|
+
type: "number"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
type: "object"
|
|
44
|
+
});
|
|
45
|
+
expect(Store.from(MyModel, "prop").get("objection")).toEqual({
|
|
46
|
+
columnType: "number",
|
|
47
|
+
options: {
|
|
48
|
+
type: "decimal"
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {StoreSet, useDecorators} from "@tsed/core";
|
|
2
|
+
import {Property} from "@tsed/schema";
|
|
3
|
+
|
|
4
|
+
import {ColumnOptions} from "./columnOptions.js";
|
|
5
|
+
|
|
6
|
+
export interface DecimalOptions {
|
|
7
|
+
precision?: number | null;
|
|
8
|
+
scale?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param options
|
|
14
|
+
* @decorator
|
|
15
|
+
* @objection
|
|
16
|
+
*/
|
|
17
|
+
export function Decimal(options: DecimalOptions = {}) {
|
|
18
|
+
return useDecorators(
|
|
19
|
+
Property(),
|
|
20
|
+
ColumnOptions({
|
|
21
|
+
columnType: "number",
|
|
22
|
+
options: {
|
|
23
|
+
type: "decimal",
|
|
24
|
+
...options
|
|
25
|
+
}
|
|
26
|
+
}),
|
|
27
|
+
StoreSet("objection", {
|
|
28
|
+
columnType: "number",
|
|
29
|
+
options: {
|
|
30
|
+
type: "decimal",
|
|
31
|
+
...options
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {Entity} from "./entity.js";
|
|
2
|
+
|
|
3
|
+
describe("@Entity", () => {
|
|
4
|
+
it("should set metadata", () => {
|
|
5
|
+
@Entity("models")
|
|
6
|
+
class MyModel {}
|
|
7
|
+
|
|
8
|
+
expect((MyModel as any).jsonSchema).toEqual({
|
|
9
|
+
type: "object"
|
|
10
|
+
});
|
|
11
|
+
expect((MyModel as any).tableName).toEqual("models");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should throw metadata when the tableName is empty", () => {
|
|
15
|
+
let actualError: any;
|
|
16
|
+
try {
|
|
17
|
+
@Entity("")
|
|
18
|
+
class MyModel {}
|
|
19
|
+
} catch (er) {
|
|
20
|
+
actualError = er;
|
|
21
|
+
}
|
|
22
|
+
expect(actualError.message).toEqual("Please provide a table name");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {Type} from "@tsed/core";
|
|
2
|
+
import {getJsonSchema} from "@tsed/schema";
|
|
3
|
+
|
|
4
|
+
import {defineStaticGetter} from "../utils/defineStaticGetter.js";
|
|
5
|
+
import {getJsonEntityRelationships} from "../utils/getJsonEntityRelationships.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param tableName
|
|
10
|
+
* @decorator
|
|
11
|
+
* @objection
|
|
12
|
+
*/
|
|
13
|
+
export function Entity(tableName: string): ClassDecorator {
|
|
14
|
+
if (!tableName) {
|
|
15
|
+
throw new Error("Please provide a table name");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (target: any) => {
|
|
19
|
+
const originalRelationMappings = target["relationMappings"];
|
|
20
|
+
defineStaticGetter(target, "tableName", () => tableName);
|
|
21
|
+
defineStaticGetter(target, "jsonSchema", () => getJsonSchema(target));
|
|
22
|
+
defineStaticGetter(target, "relationMappings", () => originalRelationMappings || getJsonEntityRelationships(target));
|
|
23
|
+
return target;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface EntityMethods<T> extends Type<T> {
|
|
28
|
+
readonly tableName: string;
|
|
29
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {Model} from "objection";
|
|
2
|
+
|
|
3
|
+
import {Entity} from "./entity.js";
|
|
4
|
+
import {HasMany} from "./hasMany.js";
|
|
5
|
+
import {IdColumn} from "./idColumn.js";
|
|
6
|
+
|
|
7
|
+
describe("@HasMany", () => {
|
|
8
|
+
it("should set metadata", () => {
|
|
9
|
+
@Entity("pet")
|
|
10
|
+
class Pet extends Model {
|
|
11
|
+
@IdColumn()
|
|
12
|
+
id!: string;
|
|
13
|
+
userId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Entity("user")
|
|
17
|
+
class User extends Model {
|
|
18
|
+
@IdColumn()
|
|
19
|
+
id!: string;
|
|
20
|
+
@HasMany(Pet)
|
|
21
|
+
pets?: Pet[];
|
|
22
|
+
}
|
|
23
|
+
expect(User.relationMappings).toEqual({
|
|
24
|
+
pets: {
|
|
25
|
+
relation: Model.HasManyRelation,
|
|
26
|
+
modelClass: Pet,
|
|
27
|
+
join: {
|
|
28
|
+
from: "user.id",
|
|
29
|
+
to: "pet.userId"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
it("should set custom relationship path", () => {
|
|
35
|
+
@Entity("pet")
|
|
36
|
+
class Pet extends Model {
|
|
37
|
+
@IdColumn()
|
|
38
|
+
id!: string;
|
|
39
|
+
ownerId?: string;
|
|
40
|
+
}
|
|
41
|
+
@Entity("user")
|
|
42
|
+
class User extends Model {
|
|
43
|
+
@IdColumn()
|
|
44
|
+
id!: string;
|
|
45
|
+
userId!: string;
|
|
46
|
+
@HasMany(Pet, {from: "userId", to: "ownerId"})
|
|
47
|
+
pets?: Pet[];
|
|
48
|
+
}
|
|
49
|
+
expect(User.relationMappings).toEqual({
|
|
50
|
+
pets: {
|
|
51
|
+
relation: Model.HasManyRelation,
|
|
52
|
+
modelClass: Pet,
|
|
53
|
+
join: {
|
|
54
|
+
from: "user.userId",
|
|
55
|
+
to: "pet.ownerId"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {Model, ModelClassSpecifier} from "objection";
|
|
2
|
+
|
|
3
|
+
import {RelationshipOptsWithThrough} from "../domain/RelationshipOpts.js";
|
|
4
|
+
import {RelatesTo} from "./relatesTo.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param opts
|
|
9
|
+
* @decorator
|
|
10
|
+
* @objection
|
|
11
|
+
*/
|
|
12
|
+
export function HasMany(type: ModelClassSpecifier, opts?: RelationshipOptsWithThrough): PropertyDecorator {
|
|
13
|
+
return RelatesTo(Model.HasManyRelation, {...opts, type});
|
|
14
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {Model} from "objection";
|
|
2
|
+
|
|
3
|
+
import {Entity} from "./entity.js";
|
|
4
|
+
import {HasOne} from "./hasOne.js";
|
|
5
|
+
import {IdColumn} from "./idColumn.js";
|
|
6
|
+
|
|
7
|
+
describe("@HasOne", () => {
|
|
8
|
+
it("should set metadata", () => {
|
|
9
|
+
@Entity("pet")
|
|
10
|
+
class Pet extends Model {
|
|
11
|
+
@IdColumn()
|
|
12
|
+
id!: string;
|
|
13
|
+
userId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Entity("user")
|
|
17
|
+
class User extends Model {
|
|
18
|
+
@IdColumn()
|
|
19
|
+
id!: string;
|
|
20
|
+
@HasOne()
|
|
21
|
+
pet?: Pet;
|
|
22
|
+
}
|
|
23
|
+
expect(User.relationMappings).toEqual({
|
|
24
|
+
pet: {
|
|
25
|
+
relation: Model.HasOneRelation,
|
|
26
|
+
modelClass: Pet,
|
|
27
|
+
join: {
|
|
28
|
+
from: "user.id",
|
|
29
|
+
to: "pet.userId"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
it("should set custom relationship path", () => {
|
|
35
|
+
@Entity("pet")
|
|
36
|
+
class Pet extends Model {
|
|
37
|
+
@IdColumn()
|
|
38
|
+
id!: string;
|
|
39
|
+
ownerId?: string;
|
|
40
|
+
}
|
|
41
|
+
@Entity("user")
|
|
42
|
+
class User extends Model {
|
|
43
|
+
@IdColumn()
|
|
44
|
+
id!: string;
|
|
45
|
+
userId!: string;
|
|
46
|
+
@HasOne({from: "userId", to: "ownerId"})
|
|
47
|
+
pet?: Pet;
|
|
48
|
+
}
|
|
49
|
+
expect(User.relationMappings).toEqual({
|
|
50
|
+
pet: {
|
|
51
|
+
relation: Model.HasOneRelation,
|
|
52
|
+
modelClass: Pet,
|
|
53
|
+
join: {
|
|
54
|
+
from: "user.userId",
|
|
55
|
+
to: "pet.ownerId"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {Model} from "objection";
|
|
2
|
+
|
|
3
|
+
import {RelationshipOptsWithoutThrough} from "../domain/RelationshipOpts.js";
|
|
4
|
+
import {RelatesTo} from "./relatesTo.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param opts
|
|
9
|
+
* @decorator
|
|
10
|
+
* @objection
|
|
11
|
+
*/
|
|
12
|
+
export function HasOne(opts?: RelationshipOptsWithoutThrough): PropertyDecorator {
|
|
13
|
+
return RelatesTo(Model.HasOneRelation, opts);
|
|
14
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {Model} from "objection";
|
|
2
|
+
|
|
3
|
+
import {Entity} from "./entity.js";
|
|
4
|
+
import {HasOneThroughRelation} from "./hasOneThroughRelation.js";
|
|
5
|
+
import {IdColumn} from "./idColumn.js";
|
|
6
|
+
|
|
7
|
+
describe("@HasOneThroughRelation", () => {
|
|
8
|
+
it("should set metadata", () => {
|
|
9
|
+
@Entity("pet")
|
|
10
|
+
class Pet extends Model {
|
|
11
|
+
@IdColumn()
|
|
12
|
+
id!: string;
|
|
13
|
+
userId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Entity("user")
|
|
17
|
+
class User extends Model {
|
|
18
|
+
@IdColumn()
|
|
19
|
+
id!: string;
|
|
20
|
+
@HasOneThroughRelation()
|
|
21
|
+
pet?: Pet;
|
|
22
|
+
}
|
|
23
|
+
expect(User.relationMappings).toEqual({
|
|
24
|
+
pet: {
|
|
25
|
+
relation: Model.HasOneThroughRelation,
|
|
26
|
+
modelClass: Pet,
|
|
27
|
+
join: {
|
|
28
|
+
from: "user.id",
|
|
29
|
+
through: {
|
|
30
|
+
from: "user_pet.userId",
|
|
31
|
+
to: "user_pet.petId"
|
|
32
|
+
},
|
|
33
|
+
to: "pet.id"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
it("should set custom relationship path", () => {
|
|
39
|
+
@Entity("pet")
|
|
40
|
+
class Pet extends Model {
|
|
41
|
+
@IdColumn()
|
|
42
|
+
id!: string;
|
|
43
|
+
petId?: string;
|
|
44
|
+
}
|
|
45
|
+
@Entity("user")
|
|
46
|
+
class User extends Model {
|
|
47
|
+
@IdColumn()
|
|
48
|
+
id!: string;
|
|
49
|
+
userId!: string;
|
|
50
|
+
@HasOneThroughRelation({
|
|
51
|
+
from: "userId",
|
|
52
|
+
through: {from: "user_pet.ownerId", to: "user_pet.petId"},
|
|
53
|
+
to: "petId"
|
|
54
|
+
})
|
|
55
|
+
pet?: Pet;
|
|
56
|
+
}
|
|
57
|
+
expect(User.relationMappings).toEqual({
|
|
58
|
+
pet: {
|
|
59
|
+
relation: Model.HasOneThroughRelation,
|
|
60
|
+
modelClass: Pet,
|
|
61
|
+
join: {
|
|
62
|
+
from: "user.userId",
|
|
63
|
+
through: {
|
|
64
|
+
from: "user_pet.ownerId",
|
|
65
|
+
to: "user_pet.petId"
|
|
66
|
+
},
|
|
67
|
+
to: "pet.petId"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {Model} from "objection";
|
|
2
|
+
|
|
3
|
+
import {RelationshipOptsWithoutThrough, RelationshipOptsWithThrough} from "../domain/RelationshipOpts.js";
|
|
4
|
+
import {RelatesTo} from "./relatesTo.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param opts
|
|
9
|
+
* @decorator
|
|
10
|
+
* @objection
|
|
11
|
+
*/
|
|
12
|
+
export function HasOneThroughRelation(opts?: RelationshipOptsWithThrough): PropertyDecorator {
|
|
13
|
+
return RelatesTo(Model.HasOneThroughRelation, opts);
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {Store} from "@tsed/core";
|
|
2
|
+
import {getJsonSchema} from "@tsed/schema";
|
|
3
|
+
import {Model} from "objection";
|
|
4
|
+
|
|
5
|
+
import {IdColumn} from "./idColumn.js";
|
|
6
|
+
|
|
7
|
+
describe("@IdColumn", () => {
|
|
8
|
+
it("should set metadata", () => {
|
|
9
|
+
class MyModel extends Model {
|
|
10
|
+
@IdColumn()
|
|
11
|
+
id: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
expect(getJsonSchema(MyModel)).toEqual({
|
|
15
|
+
properties: {
|
|
16
|
+
id: {
|
|
17
|
+
type: "number"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
type: "object"
|
|
21
|
+
});
|
|
22
|
+
expect(Store.from(MyModel, "id").get("objection")).toEqual({
|
|
23
|
+
columnType: "idColumn",
|
|
24
|
+
options: {
|
|
25
|
+
type: "increments"
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(MyModel.idColumn).toEqual("id");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {useDecorators} from "@tsed/core";
|
|
2
|
+
import {Property} from "@tsed/schema";
|
|
3
|
+
|
|
4
|
+
import {defineStaticGetter} from "../utils/defineStaticGetter.js";
|
|
5
|
+
import {ColumnOptions} from "./columnOptions.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param type
|
|
10
|
+
* @decorator
|
|
11
|
+
* @objection
|
|
12
|
+
*/
|
|
13
|
+
export function IdColumn(type: "uuid" | "increments" | "bigIncrements" = "increments"): PropertyDecorator {
|
|
14
|
+
return useDecorators(
|
|
15
|
+
Property(),
|
|
16
|
+
ColumnOptions({
|
|
17
|
+
columnType: "idColumn",
|
|
18
|
+
options: {
|
|
19
|
+
type
|
|
20
|
+
}
|
|
21
|
+
}),
|
|
22
|
+
(target: any, propertyKey: string) => {
|
|
23
|
+
defineStaticGetter(target, "idColumn", () => propertyKey);
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {Model} from "objection";
|
|
2
|
+
|
|
3
|
+
import {Entity} from "./entity.js";
|
|
4
|
+
import {IdColumn} from "./idColumn.js";
|
|
5
|
+
import {ManyToMany} from "./manyToMany.js";
|
|
6
|
+
|
|
7
|
+
describe("@ManyToMany", () => {
|
|
8
|
+
it("should set metadata", () => {
|
|
9
|
+
@Entity("pet")
|
|
10
|
+
class Pet extends Model {
|
|
11
|
+
@IdColumn()
|
|
12
|
+
id!: string;
|
|
13
|
+
userId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Entity("user")
|
|
17
|
+
class User extends Model {
|
|
18
|
+
@IdColumn()
|
|
19
|
+
id!: string;
|
|
20
|
+
@ManyToMany(Pet)
|
|
21
|
+
pets?: Pet[];
|
|
22
|
+
}
|
|
23
|
+
expect(User.relationMappings).toEqual({
|
|
24
|
+
pets: {
|
|
25
|
+
relation: Model.ManyToManyRelation,
|
|
26
|
+
modelClass: Pet,
|
|
27
|
+
join: {
|
|
28
|
+
from: "user.id",
|
|
29
|
+
through: {
|
|
30
|
+
from: "user_pet.userId",
|
|
31
|
+
to: "user_pet.petId"
|
|
32
|
+
},
|
|
33
|
+
to: "pet.id"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
it("should set custom relationship path", () => {
|
|
39
|
+
@Entity("pet")
|
|
40
|
+
class Pet extends Model {
|
|
41
|
+
@IdColumn()
|
|
42
|
+
id!: string;
|
|
43
|
+
petId?: string;
|
|
44
|
+
}
|
|
45
|
+
@Entity("user")
|
|
46
|
+
class User extends Model {
|
|
47
|
+
@IdColumn()
|
|
48
|
+
id!: string;
|
|
49
|
+
userId!: string;
|
|
50
|
+
@ManyToMany(Pet, {
|
|
51
|
+
from: "userId",
|
|
52
|
+
through: {from: "user_pet.ownerId", to: "user_pet.petId"},
|
|
53
|
+
to: "petId"
|
|
54
|
+
})
|
|
55
|
+
pets?: Pet[];
|
|
56
|
+
}
|
|
57
|
+
expect(User.relationMappings).toEqual({
|
|
58
|
+
pets: {
|
|
59
|
+
relation: Model.ManyToManyRelation,
|
|
60
|
+
modelClass: Pet,
|
|
61
|
+
join: {
|
|
62
|
+
from: "user.userId",
|
|
63
|
+
through: {
|
|
64
|
+
from: "user_pet.ownerId",
|
|
65
|
+
to: "user_pet.petId"
|
|
66
|
+
},
|
|
67
|
+
to: "pet.petId"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {Model, ModelClassSpecifier} from "objection";
|
|
2
|
+
|
|
3
|
+
import {RelationshipOptsWithThrough} from "../domain/RelationshipOpts.js";
|
|
4
|
+
import {RelatesTo} from "./relatesTo.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param opts
|
|
9
|
+
* @decorator
|
|
10
|
+
* @objection
|
|
11
|
+
*/
|
|
12
|
+
export function ManyToMany(type: ModelClassSpecifier, opts?: RelationshipOptsWithThrough): PropertyDecorator {
|
|
13
|
+
return RelatesTo(Model.ManyToManyRelation, {...opts, type});
|
|
14
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {Model} from "objection";
|
|
2
|
+
|
|
3
|
+
import {Entity} from "./entity.js";
|
|
4
|
+
import {IdColumn} from "./idColumn.js";
|
|
5
|
+
import {RelatesTo} from "./relatesTo.js";
|
|
6
|
+
|
|
7
|
+
describe("@RelatesTo", () => {
|
|
8
|
+
it("should set metadata", () => {
|
|
9
|
+
@Entity("user")
|
|
10
|
+
class User extends Model {
|
|
11
|
+
@IdColumn()
|
|
12
|
+
id!: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Entity("movie")
|
|
16
|
+
class Movie extends Model {
|
|
17
|
+
@IdColumn()
|
|
18
|
+
id!: string;
|
|
19
|
+
userId!: string;
|
|
20
|
+
@RelatesTo(Model.BelongsToOneRelation)
|
|
21
|
+
user?: User;
|
|
22
|
+
}
|
|
23
|
+
expect(Movie.relationMappings).toEqual({
|
|
24
|
+
user: {
|
|
25
|
+
relation: Model.BelongsToOneRelation,
|
|
26
|
+
modelClass: User,
|
|
27
|
+
join: {
|
|
28
|
+
from: "movie.userId",
|
|
29
|
+
to: "user.id"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {StoreFn, useDecorators} from "@tsed/core";
|
|
2
|
+
import {CollectionOf, Property} from "@tsed/schema";
|
|
3
|
+
import {RelationType} from "objection";
|
|
4
|
+
|
|
5
|
+
import {isModelClassFactory, RelationshipOpts} from "../domain/RelationshipOpts.js";
|
|
6
|
+
import {createRelationshipMapping} from "../utils/createRelationshipMapping.js";
|
|
7
|
+
import {OBJECTION_RELATIONSHIP_KEY} from "../utils/getJsonEntityRelationships.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @param relation
|
|
12
|
+
* @param opts
|
|
13
|
+
* @decorator
|
|
14
|
+
* @objection
|
|
15
|
+
*/
|
|
16
|
+
export function RelatesTo(relation: RelationType, opts?: RelationshipOpts): PropertyDecorator {
|
|
17
|
+
return useDecorators(
|
|
18
|
+
opts?.type ? CollectionOf(opts.type) : Property(),
|
|
19
|
+
StoreFn((store, params) => {
|
|
20
|
+
if (opts && isModelClassFactory(opts.type)) {
|
|
21
|
+
opts.type = opts.type();
|
|
22
|
+
}
|
|
23
|
+
store.set(OBJECTION_RELATIONSHIP_KEY, createRelationshipMapping(params, relation, opts));
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {isFunction, isString} from "@tsed/core";
|
|
2
|
+
import {ModelClassFactory, ModelClassSpecifier, RelationJoin, RelationMapping} from "objection";
|
|
3
|
+
|
|
4
|
+
export type RelationshipOptsWithThrough = Partial<RelationJoin> & Omit<RelationMapping<any>, "relation" | "join" | "modelClass">;
|
|
5
|
+
|
|
6
|
+
export type RelationshipOptsWithoutThrough = Omit<RelationshipOptsWithThrough, "through">;
|
|
7
|
+
|
|
8
|
+
export type RelationshipOpts = (RelationshipOptsWithThrough | RelationshipOptsWithoutThrough) & {type?: ModelClassSpecifier};
|
|
9
|
+
|
|
10
|
+
export const isRelationshipOptsWithThrough = (opts?: RelationshipOpts): opts is RelationshipOptsWithThrough =>
|
|
11
|
+
opts !== undefined && (<RelationshipOptsWithThrough>opts).through !== undefined;
|
|
12
|
+
|
|
13
|
+
export const isModelClassFactory = (type?: ModelClassSpecifier): type is ModelClassFactory =>
|
|
14
|
+
type !== undefined && !isString(type) && isFunction(type) && !("prototype" in type);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Automatically generated by @tsed/barrels.
|
|
3
|
+
*/
|
|
4
|
+
export * from "./components/createBooleanColumn.js";
|
|
5
|
+
export * from "./components/createIdColumn.js";
|
|
6
|
+
export * from "./components/createNumberColumn.js";
|
|
7
|
+
export * from "./components/createStringColumn.js";
|
|
8
|
+
export * from "./decorators/belongsToOne.js";
|
|
9
|
+
export * from "./decorators/columnOptions.js";
|
|
10
|
+
export * from "./decorators/decimal.js";
|
|
11
|
+
export * from "./decorators/entity.js";
|
|
12
|
+
export * from "./decorators/hasMany.js";
|
|
13
|
+
export * from "./decorators/hasOne.js";
|
|
14
|
+
export * from "./decorators/hasOneThroughRelation.js";
|
|
15
|
+
export * from "./decorators/idColumn.js";
|
|
16
|
+
export * from "./decorators/manyToMany.js";
|
|
17
|
+
export * from "./decorators/relatesTo.js";
|
|
18
|
+
export * from "./domain/ColumnOpts.js";
|
|
19
|
+
export * from "./domain/interfaces.js";
|
|
20
|
+
export * from "./domain/RelationshipOpts.js";
|
|
21
|
+
export * from "./services/ColumnTypesContainer.js";
|
|
22
|
+
export * from "./services/ObjectionConnection.js";
|
|
23
|
+
export * from "./utils/connect.js";
|
|
24
|
+
export * from "./utils/createColumns.js";
|
|
25
|
+
export * from "./utils/createJoinKeys.js";
|
|
26
|
+
export * from "./utils/createRelationshipMapping.js";
|
|
27
|
+
export * from "./utils/defineStaticGetter.js";
|
|
28
|
+
export * from "./utils/getColumnCtx.js";
|
|
29
|
+
export * from "./utils/getJsonEntityRelationships.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {Configuration, registerProvider} from "@tsed/di";
|
|
2
|
+
import type {Knex} from "knex";
|
|
3
|
+
|
|
4
|
+
import {createConnection} from "../utils/connect.js";
|
|
5
|
+
|
|
6
|
+
export const OBJECTION_CONNECTION = Symbol.for("DEFAULT_CONNECTION");
|
|
7
|
+
export type OBJECTION_CONNECTION = Knex<any, unknown[]>;
|
|
8
|
+
|
|
9
|
+
registerProvider({
|
|
10
|
+
token: OBJECTION_CONNECTION,
|
|
11
|
+
deps: [Configuration],
|
|
12
|
+
useAsyncFactory(configuration: Configuration) {
|
|
13
|
+
const connectionOptions = configuration.get<Knex.Config>("knex")!;
|
|
14
|
+
|
|
15
|
+
if (connectionOptions) {
|
|
16
|
+
try {
|
|
17
|
+
return createConnection(connectionOptions);
|
|
18
|
+
} catch (er) {
|
|
19
|
+
// istanbul ignore next
|
|
20
|
+
console.error(er);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Promise.resolve({});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {catchError} from "@tsed/core";
|
|
2
|
+
import {MaxLength, Property} from "@tsed/schema";
|
|
3
|
+
|
|
4
|
+
import {createTableStub} from "../../test/helpers/knex/table.js";
|
|
5
|
+
import {Decimal} from "../decorators/decimal.js";
|
|
6
|
+
import {Entity} from "../decorators/entity.js";
|
|
7
|
+
import {IdColumn} from "../decorators/idColumn.js";
|
|
8
|
+
import {createColumns} from "./createColumns.js";
|
|
9
|
+
|
|
10
|
+
describe("createTable", () => {
|
|
11
|
+
it("should create table from a given class (columns doesn't exists)", () => {
|
|
12
|
+
@Entity("users")
|
|
13
|
+
class User {
|
|
14
|
+
@IdColumn()
|
|
15
|
+
id: number;
|
|
16
|
+
|
|
17
|
+
@Property()
|
|
18
|
+
@MaxLength(200)
|
|
19
|
+
name: string;
|
|
20
|
+
|
|
21
|
+
@Property()
|
|
22
|
+
age: number;
|
|
23
|
+
|
|
24
|
+
@Decimal({scale: 1, precision: 12})
|
|
25
|
+
score: number;
|
|
26
|
+
|
|
27
|
+
@Property()
|
|
28
|
+
active: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const table = createTableStub();
|
|
32
|
+
|
|
33
|
+
createColumns(table, User);
|
|
34
|
+
|
|
35
|
+
expect(table.increments).toHaveBeenCalledWith("id");
|
|
36
|
+
expect(table.primary).toHaveBeenCalledWith();
|
|
37
|
+
expect(table.string).toHaveBeenCalledWith("name", 200);
|
|
38
|
+
expect(table.decimal).toHaveBeenCalledWith("age", undefined, undefined);
|
|
39
|
+
expect(table.decimal).toHaveBeenCalledWith("score", 12, 1);
|
|
40
|
+
expect(table.boolean).toHaveBeenCalledWith("active");
|
|
41
|
+
});
|
|
42
|
+
it("should create table from a given class (columns exists)", () => {
|
|
43
|
+
@Entity("users")
|
|
44
|
+
class User {
|
|
45
|
+
@IdColumn()
|
|
46
|
+
id: number;
|
|
47
|
+
|
|
48
|
+
@Property()
|
|
49
|
+
@MaxLength(200)
|
|
50
|
+
name: string;
|
|
51
|
+
|
|
52
|
+
@Property()
|
|
53
|
+
age: number;
|
|
54
|
+
|
|
55
|
+
@Decimal({scale: 1, precision: 12})
|
|
56
|
+
score: number;
|
|
57
|
+
|
|
58
|
+
@Property()
|
|
59
|
+
active: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const table = createTableStub();
|
|
63
|
+
|
|
64
|
+
createColumns(table, User);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should throw error when type isn't supported", () => {
|
|
68
|
+
@Entity("users")
|
|
69
|
+
class User {
|
|
70
|
+
@Property()
|
|
71
|
+
date: any;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const table = createTableStub();
|
|
75
|
+
const error = catchError(() => createColumns(table, User));
|
|
76
|
+
|
|
77
|
+
expect(error).toBe("Column type object isn't supported. Add function to ColumnTypesContainer to map the column with Knex");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import "../components/createBooleanColumn.js";
|
|
2
|
+
import "../components/createIdColumn.js";
|
|
3
|
+
import "../components/createNumberColumn.js";
|
|
4
|
+
import "../components/createStringColumn.js";
|
|
5
|
+
|
|
6
|
+
import {getProperties, JsonEntityStore} from "@tsed/schema";
|
|
7
|
+
import {Knex} from "knex";
|
|
8
|
+
|
|
9
|
+
import {ColumnTypesContainer} from "../services/ColumnTypesContainer.js";
|
|
10
|
+
import {getColumnCtx} from "./getColumnCtx.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @ignore
|
|
14
|
+
*/
|
|
15
|
+
export function createColumn(table: Knex.TableBuilder, entity: JsonEntityStore) {
|
|
16
|
+
const ctx = getColumnCtx(entity);
|
|
17
|
+
|
|
18
|
+
if (!ColumnTypesContainer.has(ctx.columnType)) {
|
|
19
|
+
throw `Column type ${ctx.columnType} isn't supported. Add function to ColumnTypesContainer to map the column with Knex`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
ColumnTypesContainer.get(ctx.columnType)!(table, ctx);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @ignore
|
|
27
|
+
*/
|
|
28
|
+
export function getColumns(model: any) {
|
|
29
|
+
return [...getProperties(model, {withIgnoredProps: true, objection: true, groups: false}).values()];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @ignore
|
|
34
|
+
*/
|
|
35
|
+
export function createColumns(table: Knex.TableBuilder, model: any) {
|
|
36
|
+
getColumns(model).map((entity) => {
|
|
37
|
+
createColumn(table, entity);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {Metadata} from "@tsed/core";
|
|
2
|
+
import {Model, RelationType} from "objection";
|
|
3
|
+
|
|
4
|
+
import {isRelationshipOptsWithThrough, RelationshipOpts} from "../domain/RelationshipOpts.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @ignore
|
|
8
|
+
*/
|
|
9
|
+
export function createJoinKeys(targetClass: any, targetSuper: any, propertyKey: string, relation: RelationType, opts?: RelationshipOpts) {
|
|
10
|
+
const FOREIGN_MODEL = opts?.type || Metadata.getType(targetSuper, propertyKey);
|
|
11
|
+
const SOURCE_MODEL_KEY = `${targetClass.tableName}.${opts?.from || targetClass.idColumn}`;
|
|
12
|
+
const FOREIGN_MODEL_KEY = `${FOREIGN_MODEL.tableName}.${opts?.to || FOREIGN_MODEL.idColumn}`;
|
|
13
|
+
|
|
14
|
+
switch (relation) {
|
|
15
|
+
case Model.HasManyRelation:
|
|
16
|
+
case Model.HasOneRelation:
|
|
17
|
+
return {
|
|
18
|
+
from: SOURCE_MODEL_KEY,
|
|
19
|
+
to: `${FOREIGN_MODEL.tableName}.${opts?.to || targetClass.tableName + "Id"}`
|
|
20
|
+
};
|
|
21
|
+
case Model.ManyToManyRelation:
|
|
22
|
+
case Model.HasOneThroughRelation:
|
|
23
|
+
return {
|
|
24
|
+
from: SOURCE_MODEL_KEY,
|
|
25
|
+
through: (isRelationshipOptsWithThrough(opts) && opts.through) || {
|
|
26
|
+
from: `${targetClass.tableName}_${FOREIGN_MODEL.tableName}.${targetClass.tableName}Id`,
|
|
27
|
+
to: `${targetClass.tableName}_${FOREIGN_MODEL.tableName}.${FOREIGN_MODEL.tableName}Id`
|
|
28
|
+
},
|
|
29
|
+
to: FOREIGN_MODEL_KEY
|
|
30
|
+
};
|
|
31
|
+
case Model.BelongsToOneRelation:
|
|
32
|
+
default:
|
|
33
|
+
return {
|
|
34
|
+
from: `${targetClass.tableName}.${opts?.from || propertyKey + "Id"}`,
|
|
35
|
+
to: FOREIGN_MODEL_KEY
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {DecoratorParameters, getClass, Metadata} from "@tsed/core";
|
|
2
|
+
import {RelationType} from "objection";
|
|
3
|
+
|
|
4
|
+
import {RelationshipOpts} from "../domain/RelationshipOpts.js";
|
|
5
|
+
import {createJoinKeys} from "./createJoinKeys.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @ignore
|
|
9
|
+
*/
|
|
10
|
+
export function createRelationshipMapping([target, propertyKey]: DecoratorParameters, relation: RelationType, opts?: RelationshipOpts) {
|
|
11
|
+
return (targetClass: any) => ({
|
|
12
|
+
[propertyKey]: {
|
|
13
|
+
relation,
|
|
14
|
+
modelClass: opts?.type || Metadata.getType(target, propertyKey),
|
|
15
|
+
join: createJoinKeys(targetClass, target, String(propertyKey), relation, opts),
|
|
16
|
+
modify: opts?.modify,
|
|
17
|
+
filter: opts?.filter,
|
|
18
|
+
beforeInsert: opts?.beforeInsert
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {JsonEntityStore} from "@tsed/schema";
|
|
2
|
+
|
|
3
|
+
import {ColumnOpts} from "../domain/ColumnOpts.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @ignore
|
|
7
|
+
*/
|
|
8
|
+
export interface ColumnCtx extends ColumnOpts {
|
|
9
|
+
entity: JsonEntityStore;
|
|
10
|
+
schema: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @ignore
|
|
15
|
+
*/
|
|
16
|
+
export function getColumnCtx(entity: JsonEntityStore): ColumnCtx {
|
|
17
|
+
const schema = entity.schema.toJSON();
|
|
18
|
+
const {columnType = schema.type, options = {}} = entity.store.get<Partial<ColumnOpts>>("objection", {});
|
|
19
|
+
|
|
20
|
+
return {entity, columnType, options, schema};
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {getColumns} from "./createColumns.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @ignore
|
|
5
|
+
*/
|
|
6
|
+
export const OBJECTION_RELATIONSHIP_KEY = "objectionRelationship";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @ignore
|
|
10
|
+
*/
|
|
11
|
+
export function getJsonEntityRelationships(target: any) {
|
|
12
|
+
return getColumns(target)
|
|
13
|
+
.filter((col) => col.store.get(OBJECTION_RELATIONSHIP_KEY))
|
|
14
|
+
.map((col) => col.store.get(OBJECTION_RELATIONSHIP_KEY)(target))
|
|
15
|
+
.reduce((dict, col) => ({...dict, ...col}), {});
|
|
16
|
+
}
|