@modular-rest/server 1.18.1 → 1.19.0
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/dist/class/collection_definition.d.ts +28 -3
- package/dist/class/collection_definition.js +72 -2
- package/dist/index.d.ts +12 -1
- package/dist/index.js +13 -1
- package/dist/services/data_provider/model_registry.d.ts +67 -0
- package/dist/services/data_provider/model_registry.js +189 -0
- package/dist/services/data_provider/service.js +65 -14
- package/package.json +1 -1
- package/src/class/collection_definition.ts +94 -4
- package/src/index.ts +13 -1
- package/src/services/data_provider/model_registry.ts +215 -0
- package/src/services/data_provider/service.ts +68 -14
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Schema } from 'mongoose';
|
|
1
|
+
import { Schema, Model } from 'mongoose';
|
|
2
2
|
import { Permission } from './security';
|
|
3
3
|
import { DatabaseTrigger } from './database_trigger';
|
|
4
|
+
import { MongoOption } from '../services/data_provider/service';
|
|
4
5
|
/**
|
|
5
6
|
* Configuration options for creating a collection definition.
|
|
6
7
|
* This interface defines the structure for configuring MongoDB collections with their associated
|
|
@@ -24,6 +25,11 @@ interface CollectionDefinitionOptions {
|
|
|
24
25
|
* @see https://mongoosejs.com/docs/5.x/docs/guide.html
|
|
25
26
|
*/
|
|
26
27
|
schema: Schema<any>;
|
|
28
|
+
/**
|
|
29
|
+
* Optional MongoDB connection options. If not provided, will use config.mongo if available.
|
|
30
|
+
* This is used to pre-create the model before server startup.
|
|
31
|
+
*/
|
|
32
|
+
mongoOption?: MongoOption;
|
|
27
33
|
}
|
|
28
34
|
/**
|
|
29
35
|
* To have define any collection in your database you haveto use below method in your `db.[js|ts]` file and export an array of CollectionDefinition instances.
|
|
@@ -31,7 +37,7 @@ interface CollectionDefinitionOptions {
|
|
|
31
37
|
* @param {CollectionDefinitionOptions} options - The options for the collection
|
|
32
38
|
* @expandType CollectionDefinitionOptions
|
|
33
39
|
*
|
|
34
|
-
* @returns A
|
|
40
|
+
* @returns A CollectionDefinition instance with a model property that returns the mongoose model
|
|
35
41
|
*
|
|
36
42
|
* @public
|
|
37
43
|
*
|
|
@@ -48,9 +54,16 @@ interface CollectionDefinitionOptions {
|
|
|
48
54
|
* // trigger: DatabaseTrigger[]
|
|
49
55
|
* })
|
|
50
56
|
* ]
|
|
57
|
+
*
|
|
58
|
+
* // Access the model directly:
|
|
59
|
+
* const userCollection = defineCollection({...});
|
|
60
|
+
* const UserModel = userCollection.model;
|
|
61
|
+
* const users = await UserModel.find();
|
|
51
62
|
* ```
|
|
52
63
|
*/
|
|
53
|
-
export declare function defineCollection(options: CollectionDefinitionOptions): CollectionDefinition
|
|
64
|
+
export declare function defineCollection(options: CollectionDefinitionOptions): CollectionDefinition & {
|
|
65
|
+
model: Model<any>;
|
|
66
|
+
};
|
|
54
67
|
/**
|
|
55
68
|
* A class that represents a MongoDB collection configuration. Provides full support for schema validation, access control through permissions,
|
|
56
69
|
* and custom triggers for various database operations.
|
|
@@ -99,6 +112,8 @@ export declare class CollectionDefinition {
|
|
|
99
112
|
permissions: Permission[];
|
|
100
113
|
/** @readonly Optional database triggers */
|
|
101
114
|
triggers?: DatabaseTrigger[];
|
|
115
|
+
/** Optional mongoose model for this collection */
|
|
116
|
+
private _model;
|
|
102
117
|
/**
|
|
103
118
|
* Creates a new CollectionDefinition instance
|
|
104
119
|
*
|
|
@@ -108,5 +123,15 @@ export declare class CollectionDefinition {
|
|
|
108
123
|
* @beta
|
|
109
124
|
*/
|
|
110
125
|
constructor({ database, collection, schema, permissions, triggers, }: CollectionDefinitionOptions);
|
|
126
|
+
/**
|
|
127
|
+
* Get the mongoose model for this collection
|
|
128
|
+
* @returns {Model<any> | null} The mongoose model or null if not set
|
|
129
|
+
*/
|
|
130
|
+
getModel(): Model<any> | null;
|
|
131
|
+
/**
|
|
132
|
+
* Set the mongoose model for this collection
|
|
133
|
+
* @param {Model<any>} model - The mongoose model
|
|
134
|
+
*/
|
|
135
|
+
setModel(model: Model<any>): void;
|
|
111
136
|
}
|
|
112
137
|
export {};
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.CollectionDefinition = void 0;
|
|
4
7
|
exports.defineCollection = defineCollection;
|
|
8
|
+
const model_registry_1 = __importDefault(require("../services/data_provider/model_registry"));
|
|
9
|
+
const config_1 = require("../config");
|
|
5
10
|
/**
|
|
6
11
|
* To have define any collection in your database you haveto use below method in your `db.[js|ts]` file and export an array of CollectionDefinition instances.
|
|
7
12
|
*
|
|
8
13
|
* @param {CollectionDefinitionOptions} options - The options for the collection
|
|
9
14
|
* @expandType CollectionDefinitionOptions
|
|
10
15
|
*
|
|
11
|
-
* @returns A
|
|
16
|
+
* @returns A CollectionDefinition instance with a model property that returns the mongoose model
|
|
12
17
|
*
|
|
13
18
|
* @public
|
|
14
19
|
*
|
|
@@ -25,10 +30,59 @@ exports.defineCollection = defineCollection;
|
|
|
25
30
|
* // trigger: DatabaseTrigger[]
|
|
26
31
|
* })
|
|
27
32
|
* ]
|
|
33
|
+
*
|
|
34
|
+
* // Access the model directly:
|
|
35
|
+
* const userCollection = defineCollection({...});
|
|
36
|
+
* const UserModel = userCollection.model;
|
|
37
|
+
* const users = await UserModel.find();
|
|
28
38
|
* ```
|
|
29
39
|
*/
|
|
30
40
|
function defineCollection(options) {
|
|
31
|
-
|
|
41
|
+
const definition = new CollectionDefinition(options);
|
|
42
|
+
// Try to get mongoOption from options or config
|
|
43
|
+
let mongoOption = options.mongoOption;
|
|
44
|
+
if (!mongoOption && config_1.config.mongo) {
|
|
45
|
+
mongoOption = config_1.config.mongo;
|
|
46
|
+
}
|
|
47
|
+
// If mongoOption is available, register the collection and create the model
|
|
48
|
+
if (mongoOption) {
|
|
49
|
+
const model = model_registry_1.default.registerCollection(definition, mongoOption);
|
|
50
|
+
definition.setModel(model);
|
|
51
|
+
}
|
|
52
|
+
// Create a proxy object that includes both CollectionDefinition properties and model
|
|
53
|
+
const result = definition;
|
|
54
|
+
// Add model property getter
|
|
55
|
+
Object.defineProperty(result, 'model', {
|
|
56
|
+
get() {
|
|
57
|
+
// If model was already set, return it
|
|
58
|
+
const existingModel = definition.getModel();
|
|
59
|
+
if (existingModel) {
|
|
60
|
+
return existingModel;
|
|
61
|
+
}
|
|
62
|
+
// Otherwise, try to get from registry
|
|
63
|
+
const registryModel = model_registry_1.default.getModel(definition.database, definition.collection);
|
|
64
|
+
if (registryModel) {
|
|
65
|
+
definition.setModel(registryModel);
|
|
66
|
+
return registryModel;
|
|
67
|
+
}
|
|
68
|
+
// If still not found, try to get mongoOption from config and register now
|
|
69
|
+
let currentMongoOption = mongoOption;
|
|
70
|
+
if (!currentMongoOption && config_1.config.mongo) {
|
|
71
|
+
currentMongoOption = config_1.config.mongo;
|
|
72
|
+
}
|
|
73
|
+
if (currentMongoOption) {
|
|
74
|
+
const newModel = model_registry_1.default.registerCollection(definition, currentMongoOption);
|
|
75
|
+
definition.setModel(newModel);
|
|
76
|
+
return newModel;
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`Model for ${definition.database}.${definition.collection} is not available. ` +
|
|
79
|
+
`Ensure mongoOption is provided in defineCollection options, config.mongo is set, ` +
|
|
80
|
+
`or the collection is registered via addCollectionDefinitionByList before accessing the model.`);
|
|
81
|
+
},
|
|
82
|
+
enumerable: true,
|
|
83
|
+
configurable: true,
|
|
84
|
+
});
|
|
85
|
+
return result;
|
|
32
86
|
}
|
|
33
87
|
/**
|
|
34
88
|
* A class that represents a MongoDB collection configuration. Provides full support for schema validation, access control through permissions,
|
|
@@ -77,11 +131,27 @@ class CollectionDefinition {
|
|
|
77
131
|
* @beta
|
|
78
132
|
*/
|
|
79
133
|
constructor({ database, collection, schema, permissions, triggers, }) {
|
|
134
|
+
/** Optional mongoose model for this collection */
|
|
135
|
+
this._model = null;
|
|
80
136
|
this.database = database;
|
|
81
137
|
this.collection = collection;
|
|
82
138
|
this.schema = schema;
|
|
83
139
|
this.permissions = permissions;
|
|
84
140
|
this.triggers = triggers;
|
|
85
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Get the mongoose model for this collection
|
|
144
|
+
* @returns {Model<any> | null} The mongoose model or null if not set
|
|
145
|
+
*/
|
|
146
|
+
getModel() {
|
|
147
|
+
return this._model;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Set the mongoose model for this collection
|
|
151
|
+
* @param {Model<any>} model - The mongoose model
|
|
152
|
+
*/
|
|
153
|
+
setModel(model) {
|
|
154
|
+
this._model = model;
|
|
155
|
+
}
|
|
86
156
|
}
|
|
87
157
|
exports.CollectionDefinition = CollectionDefinition;
|
package/dist/index.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export { createRest };
|
|
|
40
40
|
* ],
|
|
41
41
|
* uploadDirectoryConfig: {
|
|
42
42
|
* directory: './uploads',
|
|
43
|
-
|
|
43
|
+
* urlPath: '/assets'
|
|
44
44
|
* }
|
|
45
45
|
* };
|
|
46
46
|
* ```
|
|
@@ -118,6 +118,17 @@ export { paginator };
|
|
|
118
118
|
* @description Database collection access utilities
|
|
119
119
|
*/
|
|
120
120
|
export { getCollection };
|
|
121
|
+
/**
|
|
122
|
+
* @description Model registry for managing mongoose models and connections
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* import { modelRegistry } from '@modular-rest/server';
|
|
126
|
+
*
|
|
127
|
+
* // Get a model directly from registry
|
|
128
|
+
* const userModel = modelRegistry.getModel('myapp', 'users');
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export { modelRegistry } from './services/data_provider/model_registry';
|
|
121
132
|
/**
|
|
122
133
|
* @description File handling utilities
|
|
123
134
|
* @example
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.userManager = exports.middleware = exports.fileService = exports.getCollection = exports.paginator = exports.reply = exports.validator = exports.TypeCasters = exports.defineFunction = exports.AccessTypes = exports.PermissionGroup = exports.PermissionTypes = exports.Permission = exports.AccessDefinition = exports.CmsTrigger = exports.DatabaseTrigger = exports.Schema = exports.schemas = exports.defineCollection = exports.CollectionDefinition = exports.createRest = void 0;
|
|
39
|
+
exports.userManager = exports.middleware = exports.fileService = exports.modelRegistry = exports.getCollection = exports.paginator = exports.reply = exports.validator = exports.TypeCasters = exports.defineFunction = exports.AccessTypes = exports.PermissionGroup = exports.PermissionTypes = exports.Permission = exports.AccessDefinition = exports.CmsTrigger = exports.DatabaseTrigger = exports.Schema = exports.schemas = exports.defineCollection = exports.CollectionDefinition = exports.createRest = void 0;
|
|
40
40
|
// Application
|
|
41
41
|
const application_1 = require("./application");
|
|
42
42
|
Object.defineProperty(exports, "createRest", { enumerable: true, get: function () { return application_1.createRest; } });
|
|
@@ -77,3 +77,15 @@ Object.defineProperty(exports, "PermissionGroup", { enumerable: true, get: funct
|
|
|
77
77
|
Object.defineProperty(exports, "AccessTypes", { enumerable: true, get: function () { return security_1.AccessTypes; } });
|
|
78
78
|
const middleware = __importStar(require("./middlewares"));
|
|
79
79
|
exports.middleware = middleware;
|
|
80
|
+
/**
|
|
81
|
+
* @description Model registry for managing mongoose models and connections
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* import { modelRegistry } from '@modular-rest/server';
|
|
85
|
+
*
|
|
86
|
+
* // Get a model directly from registry
|
|
87
|
+
* const userModel = modelRegistry.getModel('myapp', 'users');
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
var model_registry_1 = require("./services/data_provider/model_registry");
|
|
91
|
+
Object.defineProperty(exports, "modelRegistry", { enumerable: true, get: function () { return model_registry_1.modelRegistry; } });
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Connection, Model } from 'mongoose';
|
|
2
|
+
import { CollectionDefinition } from '../../class/collection_definition';
|
|
3
|
+
import { MongoOption } from './service';
|
|
4
|
+
/**
|
|
5
|
+
* ModelRegistry - Singleton class for managing mongoose models and connections
|
|
6
|
+
* Pre-creates all models before database connections are established
|
|
7
|
+
*/
|
|
8
|
+
declare class ModelRegistry {
|
|
9
|
+
private static instance;
|
|
10
|
+
private connections;
|
|
11
|
+
private models;
|
|
12
|
+
private collectionDefinitions;
|
|
13
|
+
private mongoOption;
|
|
14
|
+
private constructor();
|
|
15
|
+
/**
|
|
16
|
+
* Get the singleton instance of ModelRegistry
|
|
17
|
+
* @returns {ModelRegistry} The singleton instance
|
|
18
|
+
*/
|
|
19
|
+
static getInstance(): ModelRegistry;
|
|
20
|
+
/**
|
|
21
|
+
* Register a collection definition and create its model
|
|
22
|
+
* @param {CollectionDefinition} definition - The collection definition
|
|
23
|
+
* @param {MongoOption} mongoOption - MongoDB connection options
|
|
24
|
+
* @returns {Model<any>} The created mongoose model
|
|
25
|
+
*/
|
|
26
|
+
registerCollection(definition: CollectionDefinition, mongoOption: MongoOption): Model<any>;
|
|
27
|
+
/**
|
|
28
|
+
* Get a model by database and collection name
|
|
29
|
+
* @param {string} database - Database name
|
|
30
|
+
* @param {string} collection - Collection name
|
|
31
|
+
* @returns {Model<any> | null} The mongoose model or null if not found
|
|
32
|
+
*/
|
|
33
|
+
getModel(database: string, collection: string): Model<any> | null;
|
|
34
|
+
/**
|
|
35
|
+
* Get a connection for a database
|
|
36
|
+
* @param {string} database - Database name
|
|
37
|
+
* @returns {Connection | null} The mongoose connection or null if not found
|
|
38
|
+
*/
|
|
39
|
+
getConnection(database: string): Connection | null;
|
|
40
|
+
/**
|
|
41
|
+
* Initialize all connections and connect to databases
|
|
42
|
+
* This should be called during server startup
|
|
43
|
+
* @param {MongoOption} mongoOption - MongoDB connection options
|
|
44
|
+
* @returns {Promise<void>} A promise that resolves when all connections are established
|
|
45
|
+
*/
|
|
46
|
+
initializeConnections(mongoOption: MongoOption): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Get all registered collection definitions
|
|
49
|
+
* @returns {CollectionDefinition[]} Array of collection definitions
|
|
50
|
+
*/
|
|
51
|
+
getCollectionDefinitions(): CollectionDefinition[];
|
|
52
|
+
/**
|
|
53
|
+
* Get all models for a specific database
|
|
54
|
+
* @param {string} database - Database name
|
|
55
|
+
* @returns {Record<string, Model<any>> | null} Object mapping collection names to models, or null if database not found
|
|
56
|
+
*/
|
|
57
|
+
getModelsForDatabase(database: string): Record<string, Model<any>> | null;
|
|
58
|
+
/**
|
|
59
|
+
* Check if a model exists
|
|
60
|
+
* @param {string} database - Database name
|
|
61
|
+
* @param {string} collection - Collection name
|
|
62
|
+
* @returns {boolean} True if model exists, false otherwise
|
|
63
|
+
*/
|
|
64
|
+
hasModel(database: string, collection: string): boolean;
|
|
65
|
+
}
|
|
66
|
+
export declare const modelRegistry: ModelRegistry;
|
|
67
|
+
export default modelRegistry;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.modelRegistry = void 0;
|
|
7
|
+
const mongoose_1 = __importDefault(require("mongoose"));
|
|
8
|
+
/**
|
|
9
|
+
* ModelRegistry - Singleton class for managing mongoose models and connections
|
|
10
|
+
* Pre-creates all models before database connections are established
|
|
11
|
+
*/
|
|
12
|
+
class ModelRegistry {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.connections = {};
|
|
15
|
+
this.models = {};
|
|
16
|
+
this.collectionDefinitions = [];
|
|
17
|
+
this.mongoOption = null;
|
|
18
|
+
// Private constructor for singleton pattern
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get the singleton instance of ModelRegistry
|
|
22
|
+
* @returns {ModelRegistry} The singleton instance
|
|
23
|
+
*/
|
|
24
|
+
static getInstance() {
|
|
25
|
+
if (!ModelRegistry.instance) {
|
|
26
|
+
ModelRegistry.instance = new ModelRegistry();
|
|
27
|
+
}
|
|
28
|
+
return ModelRegistry.instance;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register a collection definition and create its model
|
|
32
|
+
* @param {CollectionDefinition} definition - The collection definition
|
|
33
|
+
* @param {MongoOption} mongoOption - MongoDB connection options
|
|
34
|
+
* @returns {Model<any>} The created mongoose model
|
|
35
|
+
*/
|
|
36
|
+
registerCollection(definition, mongoOption) {
|
|
37
|
+
// Store mongoOption if not already stored
|
|
38
|
+
if (!this.mongoOption) {
|
|
39
|
+
this.mongoOption = mongoOption;
|
|
40
|
+
}
|
|
41
|
+
const { database, collection, schema } = definition;
|
|
42
|
+
// Check if this definition is already registered
|
|
43
|
+
const isAlreadyRegistered = this.collectionDefinitions.some(def => def.database === database && def.collection === collection);
|
|
44
|
+
// Store collection definition if not already registered
|
|
45
|
+
if (!isAlreadyRegistered) {
|
|
46
|
+
this.collectionDefinitions.push(definition);
|
|
47
|
+
}
|
|
48
|
+
// Initialize models object for this database if needed
|
|
49
|
+
if (!this.models[database]) {
|
|
50
|
+
this.models[database] = {};
|
|
51
|
+
}
|
|
52
|
+
// Check if model already exists
|
|
53
|
+
if (this.models[database][collection]) {
|
|
54
|
+
return this.models[database][collection];
|
|
55
|
+
}
|
|
56
|
+
// Get or create connection for this database
|
|
57
|
+
let connection = this.connections[database];
|
|
58
|
+
if (!connection) {
|
|
59
|
+
const fullDbName = (mongoOption.dbPrefix || '') + database;
|
|
60
|
+
const connectionString = mongoOption.mongoBaseAddress;
|
|
61
|
+
// Create connection (but don't connect yet)
|
|
62
|
+
connection = mongoose_1.default.createConnection(connectionString, {
|
|
63
|
+
useUnifiedTopology: true,
|
|
64
|
+
useNewUrlParser: true,
|
|
65
|
+
dbName: fullDbName,
|
|
66
|
+
});
|
|
67
|
+
this.connections[database] = connection;
|
|
68
|
+
}
|
|
69
|
+
// Create model on the connection
|
|
70
|
+
// Mongoose allows creating models before connection is established
|
|
71
|
+
const model = connection.model(collection, schema);
|
|
72
|
+
this.models[database][collection] = model;
|
|
73
|
+
return model;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get a model by database and collection name
|
|
77
|
+
* @param {string} database - Database name
|
|
78
|
+
* @param {string} collection - Collection name
|
|
79
|
+
* @returns {Model<any> | null} The mongoose model or null if not found
|
|
80
|
+
*/
|
|
81
|
+
getModel(database, collection) {
|
|
82
|
+
if (!this.models[database] || !this.models[database][collection]) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return this.models[database][collection];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get a connection for a database
|
|
89
|
+
* @param {string} database - Database name
|
|
90
|
+
* @returns {Connection | null} The mongoose connection or null if not found
|
|
91
|
+
*/
|
|
92
|
+
getConnection(database) {
|
|
93
|
+
return this.connections[database] || null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Initialize all connections and connect to databases
|
|
97
|
+
* This should be called during server startup
|
|
98
|
+
* @param {MongoOption} mongoOption - MongoDB connection options
|
|
99
|
+
* @returns {Promise<void>} A promise that resolves when all connections are established
|
|
100
|
+
*/
|
|
101
|
+
async initializeConnections(mongoOption) {
|
|
102
|
+
this.mongoOption = mongoOption;
|
|
103
|
+
// Group collection definitions by database
|
|
104
|
+
const dbGroups = {};
|
|
105
|
+
this.collectionDefinitions.forEach(definition => {
|
|
106
|
+
if (!dbGroups[definition.database]) {
|
|
107
|
+
dbGroups[definition.database] = [];
|
|
108
|
+
}
|
|
109
|
+
dbGroups[definition.database].push(definition);
|
|
110
|
+
});
|
|
111
|
+
// Connect to each database
|
|
112
|
+
const connectionPromises = Object.entries(dbGroups).map(([dbName]) => {
|
|
113
|
+
return new Promise((done, reject) => {
|
|
114
|
+
const connection = this.connections[dbName];
|
|
115
|
+
if (!connection) {
|
|
116
|
+
// If connection doesn't exist, create it
|
|
117
|
+
const fullDbName = (mongoOption.dbPrefix || '') + dbName;
|
|
118
|
+
const connectionString = mongoOption.mongoBaseAddress;
|
|
119
|
+
const newConnection = mongoose_1.default.createConnection(connectionString, {
|
|
120
|
+
useUnifiedTopology: true,
|
|
121
|
+
useNewUrlParser: true,
|
|
122
|
+
dbName: fullDbName,
|
|
123
|
+
});
|
|
124
|
+
this.connections[dbName] = newConnection;
|
|
125
|
+
// Create models for this database if they don't exist
|
|
126
|
+
const definitions = dbGroups[dbName];
|
|
127
|
+
definitions.forEach(def => {
|
|
128
|
+
if (!this.models[dbName]) {
|
|
129
|
+
this.models[dbName] = {};
|
|
130
|
+
}
|
|
131
|
+
if (!this.models[dbName][def.collection]) {
|
|
132
|
+
this.models[dbName][def.collection] = newConnection.model(def.collection, def.schema);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
newConnection.on('connected', () => {
|
|
136
|
+
console.info(`- ${fullDbName} database has been connected`);
|
|
137
|
+
done();
|
|
138
|
+
});
|
|
139
|
+
newConnection.on('error', err => {
|
|
140
|
+
reject(err);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// Connection already exists, just wait for it to be ready
|
|
145
|
+
if (connection.readyState === 1) {
|
|
146
|
+
// Already connected
|
|
147
|
+
done();
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
connection.once('connected', () => {
|
|
151
|
+
done();
|
|
152
|
+
});
|
|
153
|
+
connection.once('error', err => {
|
|
154
|
+
reject(err);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
await Promise.all(connectionPromises);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get all registered collection definitions
|
|
164
|
+
* @returns {CollectionDefinition[]} Array of collection definitions
|
|
165
|
+
*/
|
|
166
|
+
getCollectionDefinitions() {
|
|
167
|
+
return [...this.collectionDefinitions];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get all models for a specific database
|
|
171
|
+
* @param {string} database - Database name
|
|
172
|
+
* @returns {Record<string, Model<any>> | null} Object mapping collection names to models, or null if database not found
|
|
173
|
+
*/
|
|
174
|
+
getModelsForDatabase(database) {
|
|
175
|
+
return this.models[database] || null;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Check if a model exists
|
|
179
|
+
* @param {string} database - Database name
|
|
180
|
+
* @param {string} collection - Collection name
|
|
181
|
+
* @returns {boolean} True if model exists, false otherwise
|
|
182
|
+
*/
|
|
183
|
+
hasModel(database, collection) {
|
|
184
|
+
return !!(this.models[database] && this.models[database][collection]);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Export singleton instance
|
|
188
|
+
exports.modelRegistry = ModelRegistry.getInstance();
|
|
189
|
+
exports.default = exports.modelRegistry;
|
|
@@ -16,6 +16,7 @@ const trigger_operator_1 = __importDefault(require("../../class/trigger_operator
|
|
|
16
16
|
exports.triggers = trigger_operator_1.default;
|
|
17
17
|
const typeCasters_1 = __importDefault(require("./typeCasters"));
|
|
18
18
|
exports.TypeCasters = typeCasters_1.default;
|
|
19
|
+
const model_registry_1 = __importDefault(require("./model_registry"));
|
|
19
20
|
/**
|
|
20
21
|
* Service name constant
|
|
21
22
|
* @constant {string}
|
|
@@ -39,15 +40,23 @@ const permissionDefinitions = {};
|
|
|
39
40
|
*/
|
|
40
41
|
function connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitionList = [], mongoOption) {
|
|
41
42
|
return new Promise((done, reject) => {
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
// Check if connection already exists in registry
|
|
44
|
+
let connection = model_registry_1.default.getConnection(dbName);
|
|
45
|
+
// Create db connection if it doesn't exist
|
|
46
|
+
if (!connection) {
|
|
47
|
+
const fullDbName = (mongoOption.dbPrefix || '') + dbName;
|
|
48
|
+
const connectionString = mongoOption.mongoBaseAddress;
|
|
49
|
+
console.info(`- Connecting to database: ${fullDbName}`);
|
|
50
|
+
connection = mongoose_1.default.createConnection(connectionString, {
|
|
51
|
+
useUnifiedTopology: true,
|
|
52
|
+
useNewUrlParser: true,
|
|
53
|
+
dbName: fullDbName,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const fullDbName = (mongoOption.dbPrefix || '') + dbName;
|
|
58
|
+
console.info(`- Using existing connection for database: ${fullDbName}`);
|
|
59
|
+
}
|
|
51
60
|
// Store connection
|
|
52
61
|
connections[dbName] = connection;
|
|
53
62
|
// add db models from schemas
|
|
@@ -58,10 +67,30 @@ function connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitio
|
|
|
58
67
|
collections[dbName] = {};
|
|
59
68
|
if (permissionDefinitions[dbName] == undefined)
|
|
60
69
|
permissionDefinitions[dbName] = {};
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
// Check if model already exists in registry (pre-created)
|
|
71
|
+
let model = model_registry_1.default.getModel(dbName, collection);
|
|
72
|
+
if (!model) {
|
|
73
|
+
// Model doesn't exist in registry, create it on the connection
|
|
74
|
+
// This can happen if defineCollection was called without mongoOption
|
|
75
|
+
model = connection.model(collection, schema);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Model exists in registry, verify it's using the same connection
|
|
79
|
+
// Mongoose models are bound to their connection, so we should use the registry model
|
|
80
|
+
// But we need to ensure the connection matches
|
|
81
|
+
const registryConnection = model_registry_1.default.getConnection(dbName);
|
|
82
|
+
if (registryConnection && registryConnection !== connection) {
|
|
83
|
+
// Connections don't match, but this shouldn't happen in normal flow
|
|
84
|
+
// Use the model from registry as it's already created
|
|
85
|
+
model = model_registry_1.default.getModel(dbName, collection);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Store model in global collections object
|
|
64
89
|
collections[dbName][collection] = model;
|
|
90
|
+
// Also update the CollectionDefinition with the model if it has a setModel method
|
|
91
|
+
if (collectionDefinition.setModel) {
|
|
92
|
+
collectionDefinition.setModel(model);
|
|
93
|
+
}
|
|
65
94
|
// define Access Definition from component permissions
|
|
66
95
|
// and store it on global access definition object
|
|
67
96
|
permissionDefinitions[dbName][collection] = new security_1.AccessDefinition({
|
|
@@ -83,10 +112,22 @@ function connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitio
|
|
|
83
112
|
});
|
|
84
113
|
}
|
|
85
114
|
});
|
|
86
|
-
connection
|
|
115
|
+
// If connection is already connected, resolve immediately
|
|
116
|
+
if (connection.readyState === 1) {
|
|
117
|
+
const fullDbName = (mongoOption.dbPrefix || '') + dbName;
|
|
87
118
|
console.info(`- ${fullDbName} database has been connected`);
|
|
88
119
|
done();
|
|
89
|
-
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
connection.on('connected', () => {
|
|
123
|
+
const fullDbName = (mongoOption.dbPrefix || '') + dbName;
|
|
124
|
+
console.info(`- ${fullDbName} database has been connected`);
|
|
125
|
+
done();
|
|
126
|
+
});
|
|
127
|
+
connection.on('error', err => {
|
|
128
|
+
reject(err);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
90
131
|
});
|
|
91
132
|
}
|
|
92
133
|
/**
|
|
@@ -113,6 +154,15 @@ function connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitio
|
|
|
113
154
|
* ```
|
|
114
155
|
*/
|
|
115
156
|
async function addCollectionDefinitionByList({ list, mongoOption, }) {
|
|
157
|
+
// First, ensure all collections are registered in the ModelRegistry
|
|
158
|
+
// This pre-creates models before connections are established
|
|
159
|
+
list.forEach(collectionDefinition => {
|
|
160
|
+
// Check if model already exists in registry
|
|
161
|
+
if (!model_registry_1.default.hasModel(collectionDefinition.database, collectionDefinition.collection)) {
|
|
162
|
+
// Register the collection and create its model
|
|
163
|
+
model_registry_1.default.registerCollection(collectionDefinition, mongoOption);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
116
166
|
// Group collection definitions by database
|
|
117
167
|
const dbGroups = {};
|
|
118
168
|
list.forEach(collectionDefinition => {
|
|
@@ -122,6 +172,7 @@ async function addCollectionDefinitionByList({ list, mongoOption, }) {
|
|
|
122
172
|
dbGroups[collectionDefinition.database].push(collectionDefinition);
|
|
123
173
|
});
|
|
124
174
|
// Connect to each database
|
|
175
|
+
// Models are already pre-created, so connections will use existing models
|
|
125
176
|
const connectionPromises = Object.entries(dbGroups).map(([dbName, collectionDefinitionList]) => connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitionList, mongoOption));
|
|
126
177
|
await Promise.all(connectionPromises);
|
|
127
178
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { Schema } from 'mongoose';
|
|
1
|
+
import { Schema, Model } from 'mongoose';
|
|
2
2
|
import { Permission } from './security';
|
|
3
3
|
import { DatabaseTrigger } from './database_trigger';
|
|
4
|
+
import modelRegistry from '../services/data_provider/model_registry';
|
|
5
|
+
import { MongoOption } from '../services/data_provider/service';
|
|
6
|
+
import { config } from '../config';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Configuration options for creating a collection definition.
|
|
@@ -26,6 +29,12 @@ interface CollectionDefinitionOptions {
|
|
|
26
29
|
* @see https://mongoosejs.com/docs/5.x/docs/guide.html
|
|
27
30
|
*/
|
|
28
31
|
schema: Schema<any>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Optional MongoDB connection options. If not provided, will use config.mongo if available.
|
|
35
|
+
* This is used to pre-create the model before server startup.
|
|
36
|
+
*/
|
|
37
|
+
mongoOption?: MongoOption;
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
/**
|
|
@@ -34,7 +43,7 @@ interface CollectionDefinitionOptions {
|
|
|
34
43
|
* @param {CollectionDefinitionOptions} options - The options for the collection
|
|
35
44
|
* @expandType CollectionDefinitionOptions
|
|
36
45
|
*
|
|
37
|
-
* @returns A
|
|
46
|
+
* @returns A CollectionDefinition instance with a model property that returns the mongoose model
|
|
38
47
|
*
|
|
39
48
|
* @public
|
|
40
49
|
*
|
|
@@ -51,10 +60,72 @@ interface CollectionDefinitionOptions {
|
|
|
51
60
|
* // trigger: DatabaseTrigger[]
|
|
52
61
|
* })
|
|
53
62
|
* ]
|
|
63
|
+
*
|
|
64
|
+
* // Access the model directly:
|
|
65
|
+
* const userCollection = defineCollection({...});
|
|
66
|
+
* const UserModel = userCollection.model;
|
|
67
|
+
* const users = await UserModel.find();
|
|
54
68
|
* ```
|
|
55
69
|
*/
|
|
56
|
-
export function defineCollection(
|
|
57
|
-
|
|
70
|
+
export function defineCollection(
|
|
71
|
+
options: CollectionDefinitionOptions
|
|
72
|
+
): CollectionDefinition & { model: Model<any> } {
|
|
73
|
+
const definition = new CollectionDefinition(options);
|
|
74
|
+
|
|
75
|
+
// Try to get mongoOption from options or config
|
|
76
|
+
let mongoOption: MongoOption | undefined = options.mongoOption;
|
|
77
|
+
if (!mongoOption && config.mongo) {
|
|
78
|
+
mongoOption = config.mongo as MongoOption;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// If mongoOption is available, register the collection and create the model
|
|
82
|
+
if (mongoOption) {
|
|
83
|
+
const model = modelRegistry.registerCollection(definition, mongoOption);
|
|
84
|
+
definition.setModel(model);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Create a proxy object that includes both CollectionDefinition properties and model
|
|
88
|
+
const result = definition as CollectionDefinition & { model: Model<any> };
|
|
89
|
+
|
|
90
|
+
// Add model property getter
|
|
91
|
+
Object.defineProperty(result, 'model', {
|
|
92
|
+
get() {
|
|
93
|
+
// If model was already set, return it
|
|
94
|
+
const existingModel = definition.getModel();
|
|
95
|
+
if (existingModel) {
|
|
96
|
+
return existingModel;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Otherwise, try to get from registry
|
|
100
|
+
const registryModel = modelRegistry.getModel(definition.database, definition.collection);
|
|
101
|
+
if (registryModel) {
|
|
102
|
+
definition.setModel(registryModel);
|
|
103
|
+
return registryModel;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// If still not found, try to get mongoOption from config and register now
|
|
107
|
+
let currentMongoOption: MongoOption | undefined = mongoOption;
|
|
108
|
+
if (!currentMongoOption && config.mongo) {
|
|
109
|
+
currentMongoOption = config.mongo as MongoOption;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (currentMongoOption) {
|
|
113
|
+
const newModel = modelRegistry.registerCollection(definition, currentMongoOption);
|
|
114
|
+
definition.setModel(newModel);
|
|
115
|
+
return newModel;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Model for ${definition.database}.${definition.collection} is not available. ` +
|
|
120
|
+
`Ensure mongoOption is provided in defineCollection options, config.mongo is set, ` +
|
|
121
|
+
`or the collection is registered via addCollectionDefinitionByList before accessing the model.`
|
|
122
|
+
);
|
|
123
|
+
},
|
|
124
|
+
enumerable: true,
|
|
125
|
+
configurable: true,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return result;
|
|
58
129
|
}
|
|
59
130
|
|
|
60
131
|
/**
|
|
@@ -110,6 +181,9 @@ export class CollectionDefinition {
|
|
|
110
181
|
/** @readonly Optional database triggers */
|
|
111
182
|
triggers?: DatabaseTrigger[];
|
|
112
183
|
|
|
184
|
+
/** Optional mongoose model for this collection */
|
|
185
|
+
private _model: Model<any> | null = null;
|
|
186
|
+
|
|
113
187
|
/**
|
|
114
188
|
* Creates a new CollectionDefinition instance
|
|
115
189
|
*
|
|
@@ -131,4 +205,20 @@ export class CollectionDefinition {
|
|
|
131
205
|
this.permissions = permissions;
|
|
132
206
|
this.triggers = triggers;
|
|
133
207
|
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get the mongoose model for this collection
|
|
211
|
+
* @returns {Model<any> | null} The mongoose model or null if not set
|
|
212
|
+
*/
|
|
213
|
+
getModel(): Model<any> | null {
|
|
214
|
+
return this._model;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Set the mongoose model for this collection
|
|
219
|
+
* @param {Model<any>} model - The mongoose model
|
|
220
|
+
*/
|
|
221
|
+
setModel(model: Model<any>): void {
|
|
222
|
+
this._model = model;
|
|
223
|
+
}
|
|
134
224
|
}
|
package/src/index.ts
CHANGED
|
@@ -56,7 +56,7 @@ export { createRest };
|
|
|
56
56
|
* ],
|
|
57
57
|
* uploadDirectoryConfig: {
|
|
58
58
|
* directory: './uploads',
|
|
59
|
-
|
|
59
|
+
* urlPath: '/assets'
|
|
60
60
|
* }
|
|
61
61
|
* };
|
|
62
62
|
* ```
|
|
@@ -147,6 +147,18 @@ export { paginator };
|
|
|
147
147
|
*/
|
|
148
148
|
export { getCollection };
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* @description Model registry for managing mongoose models and connections
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* import { modelRegistry } from '@modular-rest/server';
|
|
155
|
+
*
|
|
156
|
+
* // Get a model directly from registry
|
|
157
|
+
* const userModel = modelRegistry.getModel('myapp', 'users');
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export { modelRegistry } from './services/data_provider/model_registry';
|
|
161
|
+
|
|
150
162
|
/**
|
|
151
163
|
* @description File handling utilities
|
|
152
164
|
* @example
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import mongoose, { Connection, Model } from 'mongoose';
|
|
2
|
+
import { CollectionDefinition } from '../../class/collection_definition';
|
|
3
|
+
import { MongoOption } from './service';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ModelRegistry - Singleton class for managing mongoose models and connections
|
|
7
|
+
* Pre-creates all models before database connections are established
|
|
8
|
+
*/
|
|
9
|
+
class ModelRegistry {
|
|
10
|
+
private static instance: ModelRegistry;
|
|
11
|
+
private connections: Record<string, Connection> = {};
|
|
12
|
+
private models: Record<string, Record<string, Model<any>>> = {};
|
|
13
|
+
private collectionDefinitions: CollectionDefinition[] = [];
|
|
14
|
+
private mongoOption: MongoOption | null = null;
|
|
15
|
+
|
|
16
|
+
private constructor() {
|
|
17
|
+
// Private constructor for singleton pattern
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the singleton instance of ModelRegistry
|
|
22
|
+
* @returns {ModelRegistry} The singleton instance
|
|
23
|
+
*/
|
|
24
|
+
static getInstance(): ModelRegistry {
|
|
25
|
+
if (!ModelRegistry.instance) {
|
|
26
|
+
ModelRegistry.instance = new ModelRegistry();
|
|
27
|
+
}
|
|
28
|
+
return ModelRegistry.instance;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Register a collection definition and create its model
|
|
33
|
+
* @param {CollectionDefinition} definition - The collection definition
|
|
34
|
+
* @param {MongoOption} mongoOption - MongoDB connection options
|
|
35
|
+
* @returns {Model<any>} The created mongoose model
|
|
36
|
+
*/
|
|
37
|
+
registerCollection(definition: CollectionDefinition, mongoOption: MongoOption): Model<any> {
|
|
38
|
+
// Store mongoOption if not already stored
|
|
39
|
+
if (!this.mongoOption) {
|
|
40
|
+
this.mongoOption = mongoOption;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { database, collection, schema } = definition;
|
|
44
|
+
|
|
45
|
+
// Check if this definition is already registered
|
|
46
|
+
const isAlreadyRegistered = this.collectionDefinitions.some(
|
|
47
|
+
def => def.database === database && def.collection === collection
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Store collection definition if not already registered
|
|
51
|
+
if (!isAlreadyRegistered) {
|
|
52
|
+
this.collectionDefinitions.push(definition);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Initialize models object for this database if needed
|
|
56
|
+
if (!this.models[database]) {
|
|
57
|
+
this.models[database] = {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check if model already exists
|
|
61
|
+
if (this.models[database][collection]) {
|
|
62
|
+
return this.models[database][collection];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Get or create connection for this database
|
|
66
|
+
let connection = this.connections[database];
|
|
67
|
+
if (!connection) {
|
|
68
|
+
const fullDbName = (mongoOption.dbPrefix || '') + database;
|
|
69
|
+
const connectionString = mongoOption.mongoBaseAddress;
|
|
70
|
+
|
|
71
|
+
// Create connection (but don't connect yet)
|
|
72
|
+
connection = mongoose.createConnection(connectionString, {
|
|
73
|
+
useUnifiedTopology: true,
|
|
74
|
+
useNewUrlParser: true,
|
|
75
|
+
dbName: fullDbName,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.connections[database] = connection;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Create model on the connection
|
|
82
|
+
// Mongoose allows creating models before connection is established
|
|
83
|
+
const model = connection.model(collection, schema);
|
|
84
|
+
this.models[database][collection] = model;
|
|
85
|
+
|
|
86
|
+
return model;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get a model by database and collection name
|
|
91
|
+
* @param {string} database - Database name
|
|
92
|
+
* @param {string} collection - Collection name
|
|
93
|
+
* @returns {Model<any> | null} The mongoose model or null if not found
|
|
94
|
+
*/
|
|
95
|
+
getModel(database: string, collection: string): Model<any> | null {
|
|
96
|
+
if (!this.models[database] || !this.models[database][collection]) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return this.models[database][collection];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get a connection for a database
|
|
104
|
+
* @param {string} database - Database name
|
|
105
|
+
* @returns {Connection | null} The mongoose connection or null if not found
|
|
106
|
+
*/
|
|
107
|
+
getConnection(database: string): Connection | null {
|
|
108
|
+
return this.connections[database] || null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Initialize all connections and connect to databases
|
|
113
|
+
* This should be called during server startup
|
|
114
|
+
* @param {MongoOption} mongoOption - MongoDB connection options
|
|
115
|
+
* @returns {Promise<void>} A promise that resolves when all connections are established
|
|
116
|
+
*/
|
|
117
|
+
async initializeConnections(mongoOption: MongoOption): Promise<void> {
|
|
118
|
+
this.mongoOption = mongoOption;
|
|
119
|
+
|
|
120
|
+
// Group collection definitions by database
|
|
121
|
+
const dbGroups: Record<string, CollectionDefinition[]> = {};
|
|
122
|
+
this.collectionDefinitions.forEach(definition => {
|
|
123
|
+
if (!dbGroups[definition.database]) {
|
|
124
|
+
dbGroups[definition.database] = [];
|
|
125
|
+
}
|
|
126
|
+
dbGroups[definition.database].push(definition);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Connect to each database
|
|
130
|
+
const connectionPromises = Object.entries(dbGroups).map(([dbName]) => {
|
|
131
|
+
return new Promise<void>((done, reject) => {
|
|
132
|
+
const connection = this.connections[dbName];
|
|
133
|
+
if (!connection) {
|
|
134
|
+
// If connection doesn't exist, create it
|
|
135
|
+
const fullDbName = (mongoOption.dbPrefix || '') + dbName;
|
|
136
|
+
const connectionString = mongoOption.mongoBaseAddress;
|
|
137
|
+
|
|
138
|
+
const newConnection = mongoose.createConnection(connectionString, {
|
|
139
|
+
useUnifiedTopology: true,
|
|
140
|
+
useNewUrlParser: true,
|
|
141
|
+
dbName: fullDbName,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
this.connections[dbName] = newConnection;
|
|
145
|
+
|
|
146
|
+
// Create models for this database if they don't exist
|
|
147
|
+
const definitions = dbGroups[dbName];
|
|
148
|
+
definitions.forEach(def => {
|
|
149
|
+
if (!this.models[dbName]) {
|
|
150
|
+
this.models[dbName] = {};
|
|
151
|
+
}
|
|
152
|
+
if (!this.models[dbName][def.collection]) {
|
|
153
|
+
this.models[dbName][def.collection] = newConnection.model(def.collection, def.schema);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
newConnection.on('connected', () => {
|
|
158
|
+
console.info(`- ${fullDbName} database has been connected`);
|
|
159
|
+
done();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
newConnection.on('error', err => {
|
|
163
|
+
reject(err);
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
// Connection already exists, just wait for it to be ready
|
|
167
|
+
if (connection.readyState === 1) {
|
|
168
|
+
// Already connected
|
|
169
|
+
done();
|
|
170
|
+
} else {
|
|
171
|
+
connection.once('connected', () => {
|
|
172
|
+
done();
|
|
173
|
+
});
|
|
174
|
+
connection.once('error', err => {
|
|
175
|
+
reject(err);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await Promise.all(connectionPromises);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get all registered collection definitions
|
|
187
|
+
* @returns {CollectionDefinition[]} Array of collection definitions
|
|
188
|
+
*/
|
|
189
|
+
getCollectionDefinitions(): CollectionDefinition[] {
|
|
190
|
+
return [...this.collectionDefinitions];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get all models for a specific database
|
|
195
|
+
* @param {string} database - Database name
|
|
196
|
+
* @returns {Record<string, Model<any>> | null} Object mapping collection names to models, or null if database not found
|
|
197
|
+
*/
|
|
198
|
+
getModelsForDatabase(database: string): Record<string, Model<any>> | null {
|
|
199
|
+
return this.models[database] || null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Check if a model exists
|
|
204
|
+
* @param {string} database - Database name
|
|
205
|
+
* @param {string} collection - Collection name
|
|
206
|
+
* @returns {boolean} True if model exists, false otherwise
|
|
207
|
+
*/
|
|
208
|
+
hasModel(database: string, collection: string): boolean {
|
|
209
|
+
return !!(this.models[database] && this.models[database][collection]);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Export singleton instance
|
|
214
|
+
export const modelRegistry = ModelRegistry.getInstance();
|
|
215
|
+
export default modelRegistry;
|
|
@@ -5,6 +5,7 @@ import TypeCasters from './typeCasters';
|
|
|
5
5
|
import { config } from '../../config';
|
|
6
6
|
import { CollectionDefinition } from '../../class/collection_definition';
|
|
7
7
|
import { User } from '../../class/user';
|
|
8
|
+
import modelRegistry from './model_registry';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Service name constant
|
|
@@ -58,17 +59,25 @@ function connectToDatabaseByCollectionDefinitionList(
|
|
|
58
59
|
mongoOption: MongoOption
|
|
59
60
|
): Promise<void> {
|
|
60
61
|
return new Promise((done, reject) => {
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
const connectionString = mongoOption.mongoBaseAddress;
|
|
62
|
+
// Check if connection already exists in registry
|
|
63
|
+
let connection = modelRegistry.getConnection(dbName);
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
// Create db connection if it doesn't exist
|
|
66
|
+
if (!connection) {
|
|
67
|
+
const fullDbName = (mongoOption.dbPrefix || '') + dbName;
|
|
68
|
+
const connectionString = mongoOption.mongoBaseAddress;
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
console.info(`- Connecting to database: ${fullDbName}`);
|
|
71
|
+
|
|
72
|
+
connection = mongoose.createConnection(connectionString, {
|
|
73
|
+
useUnifiedTopology: true,
|
|
74
|
+
useNewUrlParser: true,
|
|
75
|
+
dbName: fullDbName,
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
const fullDbName = (mongoOption.dbPrefix || '') + dbName;
|
|
79
|
+
console.info(`- Using existing connection for database: ${fullDbName}`);
|
|
80
|
+
}
|
|
72
81
|
|
|
73
82
|
// Store connection
|
|
74
83
|
connections[dbName] = connection;
|
|
@@ -82,11 +91,33 @@ function connectToDatabaseByCollectionDefinitionList(
|
|
|
82
91
|
|
|
83
92
|
if (permissionDefinitions[dbName] == undefined) permissionDefinitions[dbName] = {};
|
|
84
93
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
// Check if model already exists in registry (pre-created)
|
|
95
|
+
let model = modelRegistry.getModel(dbName, collection);
|
|
96
|
+
|
|
97
|
+
if (!model) {
|
|
98
|
+
// Model doesn't exist in registry, create it on the connection
|
|
99
|
+
// This can happen if defineCollection was called without mongoOption
|
|
100
|
+
model = connection.model(collection, schema);
|
|
101
|
+
} else {
|
|
102
|
+
// Model exists in registry, verify it's using the same connection
|
|
103
|
+
// Mongoose models are bound to their connection, so we should use the registry model
|
|
104
|
+
// But we need to ensure the connection matches
|
|
105
|
+
const registryConnection = modelRegistry.getConnection(dbName);
|
|
106
|
+
if (registryConnection && registryConnection !== connection) {
|
|
107
|
+
// Connections don't match, but this shouldn't happen in normal flow
|
|
108
|
+
// Use the model from registry as it's already created
|
|
109
|
+
model = modelRegistry.getModel(dbName, collection)!;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Store model in global collections object
|
|
88
114
|
collections[dbName][collection] = model;
|
|
89
115
|
|
|
116
|
+
// Also update the CollectionDefinition with the model if it has a setModel method
|
|
117
|
+
if (collectionDefinition.setModel) {
|
|
118
|
+
collectionDefinition.setModel(model);
|
|
119
|
+
}
|
|
120
|
+
|
|
90
121
|
// define Access Definition from component permissions
|
|
91
122
|
// and store it on global access definition object
|
|
92
123
|
permissionDefinitions[dbName][collection] = new AccessDefinition({
|
|
@@ -111,10 +142,22 @@ function connectToDatabaseByCollectionDefinitionList(
|
|
|
111
142
|
}
|
|
112
143
|
});
|
|
113
144
|
|
|
114
|
-
connection
|
|
145
|
+
// If connection is already connected, resolve immediately
|
|
146
|
+
if (connection.readyState === 1) {
|
|
147
|
+
const fullDbName = (mongoOption.dbPrefix || '') + dbName;
|
|
115
148
|
console.info(`- ${fullDbName} database has been connected`);
|
|
116
149
|
done();
|
|
117
|
-
}
|
|
150
|
+
} else {
|
|
151
|
+
connection.on('connected', () => {
|
|
152
|
+
const fullDbName = (mongoOption.dbPrefix || '') + dbName;
|
|
153
|
+
console.info(`- ${fullDbName} database has been connected`);
|
|
154
|
+
done();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
connection.on('error', err => {
|
|
158
|
+
reject(err);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
118
161
|
});
|
|
119
162
|
}
|
|
120
163
|
|
|
@@ -145,6 +188,16 @@ export async function addCollectionDefinitionByList({
|
|
|
145
188
|
list,
|
|
146
189
|
mongoOption,
|
|
147
190
|
}: CollectionDefinitionListOption): Promise<void> {
|
|
191
|
+
// First, ensure all collections are registered in the ModelRegistry
|
|
192
|
+
// This pre-creates models before connections are established
|
|
193
|
+
list.forEach(collectionDefinition => {
|
|
194
|
+
// Check if model already exists in registry
|
|
195
|
+
if (!modelRegistry.hasModel(collectionDefinition.database, collectionDefinition.collection)) {
|
|
196
|
+
// Register the collection and create its model
|
|
197
|
+
modelRegistry.registerCollection(collectionDefinition, mongoOption);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
148
201
|
// Group collection definitions by database
|
|
149
202
|
const dbGroups: Record<string, CollectionDefinition[]> = {};
|
|
150
203
|
list.forEach(collectionDefinition => {
|
|
@@ -155,6 +208,7 @@ export async function addCollectionDefinitionByList({
|
|
|
155
208
|
});
|
|
156
209
|
|
|
157
210
|
// Connect to each database
|
|
211
|
+
// Models are already pre-created, so connections will use existing models
|
|
158
212
|
const connectionPromises = Object.entries(dbGroups).map(([dbName, collectionDefinitionList]) =>
|
|
159
213
|
connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitionList, mongoOption)
|
|
160
214
|
);
|