@modular-rest/server 1.18.0 → 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.
@@ -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 new instance of CollectionDefinition
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 new instance of CollectionDefinition
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
- return new CollectionDefinition(options);
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
- * urlPath: '/assets'
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
- // Create db connection
43
- const fullDbName = (mongoOption.dbPrefix || '') + dbName;
44
- const connectionString = mongoOption.mongoBaseAddress;
45
- console.info(`- Connecting to database: ${fullDbName}`);
46
- const connection = mongoose_1.default.createConnection(connectionString, {
47
- useUnifiedTopology: true,
48
- useNewUrlParser: true,
49
- dbName: fullDbName,
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
- // create model from schema
62
- // and store in on global collection object
63
- const model = connection.model(collection, schema);
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.on('connected', () => {
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
  }
@@ -18,7 +18,7 @@ const authSchema = new mongoose_1.Schema({
18
18
  authSchema.index({ email: 1 }, { unique: true });
19
19
  authSchema.pre(['save', 'updateOne'], function (next) {
20
20
  // Encode the password before saving
21
- if (this.isModified && this.isModified('password')) {
21
+ if (this.isModified && this.isModified('password') && this.password) {
22
22
  this.password = Buffer.from(this.password).toString('base64');
23
23
  }
24
24
  next();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modular-rest/server",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "TypeScript version of a nodejs module based on KOAJS for developing Rest-APIs in a modular solution.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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 new instance of CollectionDefinition
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(options: CollectionDefinitionOptions) {
57
- return new CollectionDefinition(options);
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
- * urlPath: '/assets'
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
- // Create db connection
62
- const fullDbName = (mongoOption.dbPrefix || '') + dbName;
63
- const connectionString = mongoOption.mongoBaseAddress;
62
+ // Check if connection already exists in registry
63
+ let connection = modelRegistry.getConnection(dbName);
64
64
 
65
- console.info(`- Connecting to database: ${fullDbName}`);
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
- const connection = mongoose.createConnection(connectionString, {
68
- useUnifiedTopology: true,
69
- useNewUrlParser: true,
70
- dbName: fullDbName,
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
- // create model from schema
86
- // and store in on global collection object
87
- const model = connection.model(collection, schema);
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.on('connected', () => {
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
  );
@@ -24,7 +24,7 @@ const authSchema = new Schema(
24
24
  authSchema.index({ email: 1 }, { unique: true });
25
25
  authSchema.pre(['save', 'updateOne'], function (this: AuthDocument, next) {
26
26
  // Encode the password before saving
27
- if (this.isModified && this.isModified('password')) {
27
+ if (this.isModified && this.isModified('password') && this.password) {
28
28
  this.password = Buffer.from(this.password).toString('base64');
29
29
  }
30
30
  next();