@tsed/mongoose 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 +15 -14
- package/src/MongooseModule.ts +32 -0
- package/src/constants/constants.ts +20 -0
- package/src/decorators/auto.spec.ts +30 -0
- package/src/decorators/auto.ts +24 -0
- package/src/decorators/discriminatorKey.ts +1 -0
- package/src/decorators/dynamicRef.spec.ts +46 -0
- package/src/decorators/dynamicRef.ts +76 -0
- package/src/decorators/excludeIndexes.spec.ts +30 -0
- package/src/decorators/excludeIndexes.ts +24 -0
- package/src/decorators/expires.spec.ts +18 -0
- package/src/decorators/expires.ts +24 -0
- package/src/decorators/immutable.spec.ts +30 -0
- package/src/decorators/immutable.ts +26 -0
- package/src/decorators/indexed.spec.ts +19 -0
- package/src/decorators/indexed.ts +24 -0
- package/src/decorators/lowercase.spec.ts +30 -0
- package/src/decorators/lowercase.ts +24 -0
- package/src/decorators/model.ts +69 -0
- package/src/decorators/mongooseIndex.spec.ts +26 -0
- package/src/decorators/mongooseIndex.ts +35 -0
- package/src/decorators/mongooseIndexes.spec.ts +35 -0
- package/src/decorators/mongooseIndexes.ts +35 -0
- package/src/decorators/mongoosePlugin.spec.ts +25 -0
- package/src/decorators/mongoosePlugin.ts +20 -0
- package/src/decorators/numberDecimal.spec.ts +266 -0
- package/src/decorators/numberDecimal.ts +84 -0
- package/src/decorators/objectID.spec.ts +24 -0
- package/src/decorators/objectID.ts +39 -0
- package/src/decorators/postHook.spec.ts +44 -0
- package/src/decorators/postHook.ts +72 -0
- package/src/decorators/preHook.spec.ts +75 -0
- package/src/decorators/preHook.ts +70 -0
- package/src/decorators/ref.spec.ts +361 -0
- package/src/decorators/ref.ts +111 -0
- package/src/decorators/schema.ts +79 -0
- package/src/decorators/schemaIgnore.spec.ts +18 -0
- package/src/decorators/schemaIgnore.ts +24 -0
- package/src/decorators/select.spec.ts +18 -0
- package/src/decorators/select.ts +24 -0
- package/src/decorators/sparse.spec.ts +30 -0
- package/src/decorators/sparse.ts +26 -0
- package/src/decorators/text.spec.ts +30 -0
- package/src/decorators/text.ts +25 -0
- package/src/decorators/trim.spec.ts +18 -0
- package/src/decorators/trim.ts +23 -0
- package/src/decorators/unique.spec.ts +18 -0
- package/src/decorators/unique.ts +23 -0
- package/src/decorators/uppercase.spec.ts +30 -0
- package/src/decorators/uppercase.ts +24 -0
- package/src/decorators/versionKey.spec.ts +19 -0
- package/src/decorators/versionKey.ts +9 -0
- package/src/decorators/virtualRef.spec.ts +383 -0
- package/src/decorators/virtualRef.ts +73 -0
- package/src/index.ts +47 -0
- package/src/interfaces/MongooseConnectionOptions.ts +10 -0
- package/src/interfaces/MongooseDocument.ts +3 -0
- package/src/interfaces/MongooseModel.ts +10 -0
- package/src/interfaces/MongooseModelOptions.ts +8 -0
- package/src/interfaces/MongooseSchemaOptions.ts +51 -0
- package/src/interfaces/MongooseSchemaTypes.ts +5 -0
- package/src/interfaces/MongooseVirtualRefOptions.ts +10 -0
- package/src/interfaces/interfaces.ts +9 -0
- package/src/registries/MongooseModels.ts +3 -0
- package/src/services/MongooseConnection.spec.ts +70 -0
- package/src/services/MongooseConnections.ts +58 -0
- package/src/services/MongooseService.spec.ts +73 -0
- package/src/services/MongooseService.ts +77 -0
- package/src/utils/buildMongooseSchema.spec.ts +67 -0
- package/src/utils/createModel.spec.ts +49 -0
- package/src/utils/createModel.ts +54 -0
- package/src/utils/createSchema.spec.ts +771 -0
- package/src/utils/createSchema.ts +198 -0
- package/src/utils/resolveRefType.spec.ts +30 -0
- package/src/utils/resolveRefType.ts +18 -0
- package/src/utils/schemaOptions.spec.ts +68 -0
- package/src/utils/schemaOptions.ts +74 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {isArray} from "@tsed/core";
|
|
2
|
+
import {Configuration, registerProvider} from "@tsed/di";
|
|
3
|
+
|
|
4
|
+
import {MongooseConnectionOptions} from "../interfaces/MongooseConnectionOptions.js";
|
|
5
|
+
import {MongooseService} from "../services/MongooseService.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @ignore
|
|
9
|
+
*/
|
|
10
|
+
// tslint:disable-next-line:variable-name
|
|
11
|
+
export const MONGOOSE_CONNECTIONS = Symbol.for("MONGOOSE_CONNECTIONS");
|
|
12
|
+
/**
|
|
13
|
+
* @ignore
|
|
14
|
+
*/
|
|
15
|
+
export type MONGOOSE_CONNECTIONS = MongooseService;
|
|
16
|
+
|
|
17
|
+
function mapOptions(options: Omit<MongooseConnectionOptions, "id"> | MongooseConnectionOptions[]): MongooseConnectionOptions[] {
|
|
18
|
+
if (!options) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!isArray(options)) {
|
|
23
|
+
const {url, connectionOptions} = options || {};
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
id: "default",
|
|
28
|
+
url,
|
|
29
|
+
connectionOptions
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (options as MongooseConnectionOptions[]).map((settings) => {
|
|
35
|
+
return {
|
|
36
|
+
...settings,
|
|
37
|
+
connectionOptions: settings.connectionOptions
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
registerProvider({
|
|
43
|
+
token: MONGOOSE_CONNECTIONS,
|
|
44
|
+
injectable: false,
|
|
45
|
+
deps: [Configuration, MongooseService],
|
|
46
|
+
async useAsyncFactory(configuration: Configuration, mongooseService: MongooseService) {
|
|
47
|
+
const settings = mapOptions(configuration.get<MongooseConnectionOptions | MongooseConnectionOptions[]>("mongoose"));
|
|
48
|
+
let isDefault = true;
|
|
49
|
+
|
|
50
|
+
for (const current of settings) {
|
|
51
|
+
await mongooseService.connect(current.id, current.url, current.connectionOptions || {}, isDefault);
|
|
52
|
+
|
|
53
|
+
isDefault = false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return mongooseService;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {PlatformTest} from "@tsed/platform-http/testing";
|
|
2
|
+
import Mongoose from "mongoose";
|
|
3
|
+
|
|
4
|
+
import {MongooseService} from "../../src/index.js";
|
|
5
|
+
|
|
6
|
+
describe("Mongoose", () => {
|
|
7
|
+
describe("MongooseService", () => {
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
await PlatformTest.create({
|
|
10
|
+
logger: {
|
|
11
|
+
level: "off"
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
await PlatformTest.reset();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should call mongoose.connect", async () => {
|
|
21
|
+
const mongooseService = PlatformTest.get<MongooseService>(MongooseService);
|
|
22
|
+
// GIVEN
|
|
23
|
+
const instance = {
|
|
24
|
+
readyState: 1,
|
|
25
|
+
close: vi.fn()
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
(Mongoose.createConnection as any) = vi.fn().mockResolvedValue(instance);
|
|
29
|
+
|
|
30
|
+
// WHEN
|
|
31
|
+
await mongooseService.connect("key", "mongodb://test", {options: "options"} as any);
|
|
32
|
+
|
|
33
|
+
const result = await mongooseService.connect("key", "mongodb://test", {options: "options"} as any);
|
|
34
|
+
|
|
35
|
+
// THEN
|
|
36
|
+
expect(result).toBe(instance);
|
|
37
|
+
expect(Mongoose.createConnection).toHaveBeenNthCalledWith(1, "mongodb://test", {options: "options"});
|
|
38
|
+
expect(mongooseService.get()).toBeUndefined();
|
|
39
|
+
expect(mongooseService.has()).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should close connection (1)", async () => {
|
|
43
|
+
const mongooseService = PlatformTest.get<MongooseService>(MongooseService);
|
|
44
|
+
// GIVEN
|
|
45
|
+
const instance = {
|
|
46
|
+
close: vi.fn()
|
|
47
|
+
};
|
|
48
|
+
(Mongoose.createConnection as any) = vi.fn().mockResolvedValue(instance);
|
|
49
|
+
|
|
50
|
+
// WHEN
|
|
51
|
+
await mongooseService.connect("key1", "mongodb://test", {options: "options"} as any);
|
|
52
|
+
await mongooseService.closeConnections();
|
|
53
|
+
|
|
54
|
+
// THEN
|
|
55
|
+
expect(instance.close).toHaveBeenCalledWith();
|
|
56
|
+
});
|
|
57
|
+
it("should close connection (2)", async () => {
|
|
58
|
+
const mongooseService = PlatformTest.get<MongooseService>(MongooseService);
|
|
59
|
+
// GIVEN
|
|
60
|
+
const instance = {
|
|
61
|
+
close: vi.fn()
|
|
62
|
+
};
|
|
63
|
+
(Mongoose.createConnection as any) = vi.fn().mockResolvedValue(instance);
|
|
64
|
+
|
|
65
|
+
// WHEN
|
|
66
|
+
await mongooseService.connect("key2", "mongodb://test", {options: "options"} as any);
|
|
67
|
+
await mongooseService.closeConnections();
|
|
68
|
+
|
|
69
|
+
// THEN
|
|
70
|
+
expect(instance.close).toHaveBeenCalledWith();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {Inject, Injectable} from "@tsed/di";
|
|
2
|
+
import {Logger} from "@tsed/logger";
|
|
3
|
+
import Mongoose from "mongoose";
|
|
4
|
+
import {ConnectOptions} from "mongoose";
|
|
5
|
+
|
|
6
|
+
// istanbul ignore next
|
|
7
|
+
function asPromise(c: any) {
|
|
8
|
+
return c && c.asPromise ? c.asPromise() : c;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class MongooseService {
|
|
13
|
+
readonly connections: Map<string, Mongoose.Connection> = new Map();
|
|
14
|
+
private defaultConnection: string = "default";
|
|
15
|
+
|
|
16
|
+
@Inject()
|
|
17
|
+
logger: Logger;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @returns {Promise<"mongoose".Connection>}
|
|
22
|
+
*/
|
|
23
|
+
async connect(id: string, url: string, connectionOptions: ConnectOptions, isDefault = false): Promise<any> {
|
|
24
|
+
if (this.has(id)) {
|
|
25
|
+
return this.get(id)!;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.logger.info(`Connect to mongo database: ${id}`);
|
|
29
|
+
this.logger.debug(`Url: ${url}`);
|
|
30
|
+
this.logger.debug(`options: ${JSON.stringify(connectionOptions)}`);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const connection = await asPromise(Mongoose.createConnection(url, connectionOptions));
|
|
34
|
+
this.connections.set(id, connection);
|
|
35
|
+
|
|
36
|
+
if (id === "default" || isDefault) {
|
|
37
|
+
this.defaultConnection = id;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return connection;
|
|
41
|
+
} catch (er) {
|
|
42
|
+
/* istanbul ignore next */
|
|
43
|
+
this.logger.error({
|
|
44
|
+
event: "MONGO_CONNECTION_ERROR",
|
|
45
|
+
error_name: er.name,
|
|
46
|
+
message: er.message,
|
|
47
|
+
stack: er.stack
|
|
48
|
+
});
|
|
49
|
+
/* istanbul ignore next */
|
|
50
|
+
process.exit();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
* @returns {"mongoose".Connection}
|
|
57
|
+
*/
|
|
58
|
+
get(id?: string): Mongoose.Connection | undefined {
|
|
59
|
+
return this.connections.get(id || this.defaultConnection);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
*
|
|
64
|
+
* @param {string} id
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
has(id?: string): boolean {
|
|
68
|
+
return this.connections.has(id || this.defaultConnection);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async closeConnections() {
|
|
72
|
+
for (const [id, connection] of this.connections.entries()) {
|
|
73
|
+
await connection.close();
|
|
74
|
+
this.connections.delete(id);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {JsonEntityStore, Property} from "@tsed/schema";
|
|
2
|
+
import {Types} from "mongoose";
|
|
3
|
+
|
|
4
|
+
import {buildMongooseSchema} from "../../src/utils/createSchema.js";
|
|
5
|
+
import {MONGOOSE_SCHEMA} from "../constants/constants.js";
|
|
6
|
+
|
|
7
|
+
describe("buildMongooseSchema", () => {
|
|
8
|
+
describe("when mongoose schema hasn't ref", () => {
|
|
9
|
+
it("should return schema", () => {
|
|
10
|
+
class Test {
|
|
11
|
+
@Property()
|
|
12
|
+
_id: string;
|
|
13
|
+
|
|
14
|
+
@Property()
|
|
15
|
+
test: String;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// WHEN
|
|
19
|
+
const result = buildMongooseSchema(Test);
|
|
20
|
+
|
|
21
|
+
// THEN
|
|
22
|
+
expect(result.schema).toEqual({
|
|
23
|
+
_id: {
|
|
24
|
+
required: false,
|
|
25
|
+
type: String
|
|
26
|
+
},
|
|
27
|
+
test: {
|
|
28
|
+
required: false,
|
|
29
|
+
type: String
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(result.virtuals.size).toBe(0);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("when mongoose schema has virtual ref", () => {
|
|
38
|
+
it("should return schema", () => {
|
|
39
|
+
class Test {
|
|
40
|
+
test: String;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const propertyMetadata = JsonEntityStore.get(Test, "test");
|
|
44
|
+
propertyMetadata.type = String;
|
|
45
|
+
propertyMetadata.store.set(MONGOOSE_SCHEMA, {
|
|
46
|
+
ref: "ref",
|
|
47
|
+
justOne: true,
|
|
48
|
+
localField: "localField",
|
|
49
|
+
foreignField: "foreignField"
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// WHEN
|
|
53
|
+
const result = buildMongooseSchema(Test);
|
|
54
|
+
|
|
55
|
+
// THEN
|
|
56
|
+
expect(result.schema).toEqual({});
|
|
57
|
+
|
|
58
|
+
expect(result.virtuals.size).toBe(1);
|
|
59
|
+
expect(result.virtuals.get("test")).toEqual({
|
|
60
|
+
foreignField: "foreignField",
|
|
61
|
+
justOne: true,
|
|
62
|
+
localField: "localField",
|
|
63
|
+
ref: "ref"
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
|
|
3
|
+
import {createModel} from "../../src/index.js";
|
|
4
|
+
|
|
5
|
+
describe("createModel()", () => {
|
|
6
|
+
let schema: any;
|
|
7
|
+
mongoose.model = vi.fn();
|
|
8
|
+
describe("when the model name is given", () => {
|
|
9
|
+
class Test {}
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
schema = {};
|
|
13
|
+
createModel(Test, schema, "name", "collection", true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should call mongoose.model", () => {
|
|
17
|
+
expect(mongoose.model).toHaveBeenCalledWith("name", schema, "collection", {overwriteModels: true});
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("when the model name is not given", () => {
|
|
22
|
+
class Test {}
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
schema = {};
|
|
26
|
+
|
|
27
|
+
createModel(Test, schema);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should call mongoose.model", () => {
|
|
31
|
+
expect(mongoose.model).toHaveBeenCalledWith("Test", schema, undefined, undefined);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("when the model is derived and parent has no discriminator key", () => {
|
|
36
|
+
class TestParent {}
|
|
37
|
+
|
|
38
|
+
class TestChild extends TestParent {}
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
schema = {};
|
|
42
|
+
createModel(TestChild, schema);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should call mongoose.model", () => {
|
|
46
|
+
expect(mongoose.model).toHaveBeenCalledWith("TestChild", schema, undefined, undefined);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {nameOf, Store, Type} from "@tsed/core";
|
|
2
|
+
import {JsonEntityStore} from "@tsed/schema";
|
|
3
|
+
import mongoose, {Connection} from "mongoose";
|
|
4
|
+
|
|
5
|
+
import {MONGOOSE_MODEL, MONGOOSE_MODEL_NAME} from "../constants/constants.js";
|
|
6
|
+
import {MongooseModels} from "../registries/MongooseModels.js";
|
|
7
|
+
import {getSchemaToken} from "./createSchema.js";
|
|
8
|
+
|
|
9
|
+
export function getModelToken(target: Type<any>, options: any) {
|
|
10
|
+
const {collectionName, token} = getSchemaToken(target, options);
|
|
11
|
+
|
|
12
|
+
Store.from(target).set(MONGOOSE_MODEL_NAME, collectionName);
|
|
13
|
+
MongooseModels.set(collectionName, target);
|
|
14
|
+
|
|
15
|
+
return {token, collectionName};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create an instance of mongoose.model from a class.
|
|
20
|
+
*
|
|
21
|
+
* @param {Type<any>} target Class attached to the schema and model.
|
|
22
|
+
* @param {"mongoose".Schema} schema Schema that will be attached to the model.
|
|
23
|
+
* @param name model name
|
|
24
|
+
* @param collection (optional, induced from model name)
|
|
25
|
+
* @param overwriteModels
|
|
26
|
+
* @param connection
|
|
27
|
+
* @returns {Model<T extends Document>}
|
|
28
|
+
*/
|
|
29
|
+
export function createModel<T>(
|
|
30
|
+
target: any,
|
|
31
|
+
schema: mongoose.Schema,
|
|
32
|
+
name: string = nameOf(target),
|
|
33
|
+
collection?: string,
|
|
34
|
+
overwriteModels?: boolean,
|
|
35
|
+
connection?: Connection
|
|
36
|
+
) {
|
|
37
|
+
const entity = JsonEntityStore.from(target);
|
|
38
|
+
|
|
39
|
+
if (entity.isDiscriminatorChild) {
|
|
40
|
+
const discriminatorName = entity.discriminatorAncestor!.schema.discriminator().getDefaultValue(target)!;
|
|
41
|
+
const ancestorModel = entity.discriminatorAncestor!.get(MONGOOSE_MODEL);
|
|
42
|
+
|
|
43
|
+
// check if discriminator is already registered on model before creating it
|
|
44
|
+
return ancestorModel.discriminators?.[discriminatorName] || ancestorModel.discriminator(discriminatorName, schema);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const opts = overwriteModels ? {overwriteModels} : undefined;
|
|
48
|
+
const c = connection || mongoose;
|
|
49
|
+
|
|
50
|
+
const model = c.model(name, schema, collection, opts);
|
|
51
|
+
Store.from(target).set(MONGOOSE_MODEL, model);
|
|
52
|
+
|
|
53
|
+
return model;
|
|
54
|
+
}
|