@tsed/mongoose 8.0.1 → 8.0.3

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.
Files changed (78) hide show
  1. package/package.json +15 -14
  2. package/readme.md +5 -5
  3. package/src/MongooseModule.ts +32 -0
  4. package/src/constants/constants.ts +20 -0
  5. package/src/decorators/auto.spec.ts +30 -0
  6. package/src/decorators/auto.ts +24 -0
  7. package/src/decorators/discriminatorKey.ts +1 -0
  8. package/src/decorators/dynamicRef.spec.ts +46 -0
  9. package/src/decorators/dynamicRef.ts +76 -0
  10. package/src/decorators/excludeIndexes.spec.ts +30 -0
  11. package/src/decorators/excludeIndexes.ts +24 -0
  12. package/src/decorators/expires.spec.ts +18 -0
  13. package/src/decorators/expires.ts +24 -0
  14. package/src/decorators/immutable.spec.ts +30 -0
  15. package/src/decorators/immutable.ts +26 -0
  16. package/src/decorators/indexed.spec.ts +19 -0
  17. package/src/decorators/indexed.ts +24 -0
  18. package/src/decorators/lowercase.spec.ts +30 -0
  19. package/src/decorators/lowercase.ts +24 -0
  20. package/src/decorators/model.ts +69 -0
  21. package/src/decorators/mongooseIndex.spec.ts +26 -0
  22. package/src/decorators/mongooseIndex.ts +35 -0
  23. package/src/decorators/mongooseIndexes.spec.ts +35 -0
  24. package/src/decorators/mongooseIndexes.ts +35 -0
  25. package/src/decorators/mongoosePlugin.spec.ts +25 -0
  26. package/src/decorators/mongoosePlugin.ts +20 -0
  27. package/src/decorators/numberDecimal.spec.ts +266 -0
  28. package/src/decorators/numberDecimal.ts +84 -0
  29. package/src/decorators/objectID.spec.ts +24 -0
  30. package/src/decorators/objectID.ts +39 -0
  31. package/src/decorators/postHook.spec.ts +44 -0
  32. package/src/decorators/postHook.ts +72 -0
  33. package/src/decorators/preHook.spec.ts +75 -0
  34. package/src/decorators/preHook.ts +70 -0
  35. package/src/decorators/ref.spec.ts +361 -0
  36. package/src/decorators/ref.ts +111 -0
  37. package/src/decorators/schema.ts +79 -0
  38. package/src/decorators/schemaIgnore.spec.ts +18 -0
  39. package/src/decorators/schemaIgnore.ts +24 -0
  40. package/src/decorators/select.spec.ts +18 -0
  41. package/src/decorators/select.ts +24 -0
  42. package/src/decorators/sparse.spec.ts +30 -0
  43. package/src/decorators/sparse.ts +26 -0
  44. package/src/decorators/text.spec.ts +30 -0
  45. package/src/decorators/text.ts +25 -0
  46. package/src/decorators/trim.spec.ts +18 -0
  47. package/src/decorators/trim.ts +23 -0
  48. package/src/decorators/unique.spec.ts +18 -0
  49. package/src/decorators/unique.ts +23 -0
  50. package/src/decorators/uppercase.spec.ts +30 -0
  51. package/src/decorators/uppercase.ts +24 -0
  52. package/src/decorators/versionKey.spec.ts +19 -0
  53. package/src/decorators/versionKey.ts +9 -0
  54. package/src/decorators/virtualRef.spec.ts +383 -0
  55. package/src/decorators/virtualRef.ts +73 -0
  56. package/src/index.ts +47 -0
  57. package/src/interfaces/MongooseConnectionOptions.ts +10 -0
  58. package/src/interfaces/MongooseDocument.ts +3 -0
  59. package/src/interfaces/MongooseModel.ts +10 -0
  60. package/src/interfaces/MongooseModelOptions.ts +8 -0
  61. package/src/interfaces/MongooseSchemaOptions.ts +51 -0
  62. package/src/interfaces/MongooseSchemaTypes.ts +5 -0
  63. package/src/interfaces/MongooseVirtualRefOptions.ts +10 -0
  64. package/src/interfaces/interfaces.ts +9 -0
  65. package/src/registries/MongooseModels.ts +3 -0
  66. package/src/services/MongooseConnection.spec.ts +70 -0
  67. package/src/services/MongooseConnections.ts +58 -0
  68. package/src/services/MongooseService.spec.ts +73 -0
  69. package/src/services/MongooseService.ts +77 -0
  70. package/src/utils/buildMongooseSchema.spec.ts +67 -0
  71. package/src/utils/createModel.spec.ts +49 -0
  72. package/src/utils/createModel.ts +54 -0
  73. package/src/utils/createSchema.spec.ts +771 -0
  74. package/src/utils/createSchema.ts +198 -0
  75. package/src/utils/resolveRefType.spec.ts +30 -0
  76. package/src/utils/resolveRefType.ts +18 -0
  77. package/src/utils/schemaOptions.spec.ts +68 -0
  78. 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
+ }