@modular-rest/server 1.18.1 → 1.20.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.
Files changed (41) hide show
  1. package/.nvmrc +1 -1
  2. package/dist/application.js +5 -4
  3. package/dist/class/collection_definition.d.ts +28 -3
  4. package/dist/class/collection_definition.js +72 -2
  5. package/dist/class/combinator.js +7 -3
  6. package/dist/class/directory.d.ts +2 -4
  7. package/dist/class/directory.js +42 -64
  8. package/dist/helper/data_insertion.js +93 -26
  9. package/dist/index.d.ts +12 -1
  10. package/dist/index.js +13 -1
  11. package/dist/services/data_provider/model_registry.d.ts +72 -0
  12. package/dist/services/data_provider/model_registry.js +214 -0
  13. package/dist/services/data_provider/service.js +73 -14
  14. package/dist/services/file/service.d.ts +47 -78
  15. package/dist/services/file/service.js +114 -155
  16. package/dist/services/functions/service.js +4 -4
  17. package/dist/services/jwt/router.js +2 -1
  18. package/dist/services/user_manager/router.js +1 -1
  19. package/dist/services/user_manager/service.js +48 -17
  20. package/jest.config.ts +18 -0
  21. package/package.json +11 -2
  22. package/src/application.ts +5 -4
  23. package/src/class/collection_definition.ts +94 -4
  24. package/src/class/combinator.ts +10 -3
  25. package/src/class/directory.ts +40 -58
  26. package/src/helper/data_insertion.ts +101 -27
  27. package/src/index.ts +13 -1
  28. package/src/services/data_provider/model_registry.ts +243 -0
  29. package/src/services/data_provider/service.ts +74 -14
  30. package/src/services/file/service.ts +136 -178
  31. package/src/services/functions/service.ts +4 -4
  32. package/src/services/jwt/router.ts +2 -1
  33. package/src/services/user_manager/router.ts +1 -1
  34. package/src/services/user_manager/service.ts +49 -20
  35. package/tests/helpers/test-app.ts +182 -0
  36. package/tests/router/data-provider.router.int.test.ts +192 -0
  37. package/tests/router/file.router.int.test.ts +104 -0
  38. package/tests/router/functions.router.int.test.ts +91 -0
  39. package/tests/router/jwt.router.int.test.ts +69 -0
  40. package/tests/router/user-manager.router.int.test.ts +85 -0
  41. package/tests/setup/jest.setup.ts +5 -0
@@ -0,0 +1,214 @@
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
+ serverSelectionTimeoutMS: 10000, // 10 second timeout for server selection
67
+ socketTimeoutMS: 45000, // 45 second timeout for socket operations
68
+ });
69
+ this.connections[database] = connection;
70
+ }
71
+ // Create model on the connection
72
+ // Mongoose allows creating models before connection is established
73
+ const model = connection.model(collection, schema);
74
+ this.models[database][collection] = model;
75
+ return model;
76
+ }
77
+ /**
78
+ * Get a model by database and collection name
79
+ * @param {string} database - Database name
80
+ * @param {string} collection - Collection name
81
+ * @returns {Model<any> | null} The mongoose model or null if not found
82
+ */
83
+ getModel(database, collection) {
84
+ if (!this.models[database] || !this.models[database][collection]) {
85
+ return null;
86
+ }
87
+ return this.models[database][collection];
88
+ }
89
+ /**
90
+ * Get a connection for a database
91
+ * @param {string} database - Database name
92
+ * @returns {Connection | null} The mongoose connection or null if not found
93
+ */
94
+ getConnection(database) {
95
+ return this.connections[database] || null;
96
+ }
97
+ /**
98
+ * Initialize all connections and connect to databases
99
+ * This should be called during server startup
100
+ * @param {MongoOption} mongoOption - MongoDB connection options
101
+ * @returns {Promise<void>} A promise that resolves when all connections are established
102
+ */
103
+ async initializeConnections(mongoOption) {
104
+ this.mongoOption = mongoOption;
105
+ // Group collection definitions by database
106
+ const dbGroups = {};
107
+ this.collectionDefinitions.forEach(definition => {
108
+ if (!dbGroups[definition.database]) {
109
+ dbGroups[definition.database] = [];
110
+ }
111
+ dbGroups[definition.database].push(definition);
112
+ });
113
+ // Connect to each database
114
+ const connectionPromises = Object.entries(dbGroups).map(([dbName]) => {
115
+ return new Promise((done, reject) => {
116
+ const connection = this.connections[dbName];
117
+ if (!connection) {
118
+ // If connection doesn't exist, create it
119
+ const fullDbName = (mongoOption.dbPrefix || '') + dbName;
120
+ const connectionString = mongoOption.mongoBaseAddress;
121
+ const newConnection = mongoose_1.default.createConnection(connectionString, {
122
+ useUnifiedTopology: true,
123
+ useNewUrlParser: true,
124
+ dbName: fullDbName,
125
+ serverSelectionTimeoutMS: 10000, // 10 second timeout for server selection
126
+ socketTimeoutMS: 45000, // 45 second timeout for socket operations
127
+ });
128
+ this.connections[dbName] = newConnection;
129
+ // Create models for this database if they don't exist
130
+ const definitions = dbGroups[dbName];
131
+ definitions.forEach(def => {
132
+ if (!this.models[dbName]) {
133
+ this.models[dbName] = {};
134
+ }
135
+ if (!this.models[dbName][def.collection]) {
136
+ this.models[dbName][def.collection] = newConnection.model(def.collection, def.schema);
137
+ }
138
+ });
139
+ newConnection.on('connected', () => {
140
+ console.info(`- ${fullDbName} database has been connected`);
141
+ done();
142
+ });
143
+ newConnection.on('error', err => {
144
+ reject(err);
145
+ });
146
+ }
147
+ else {
148
+ // Connection already exists, just wait for it to be ready
149
+ if (connection.readyState === 1) {
150
+ // Already connected
151
+ done();
152
+ }
153
+ else {
154
+ connection.once('connected', () => {
155
+ done();
156
+ });
157
+ connection.once('error', err => {
158
+ reject(err);
159
+ });
160
+ }
161
+ }
162
+ });
163
+ });
164
+ await Promise.all(connectionPromises);
165
+ }
166
+ /**
167
+ * Get all registered collection definitions
168
+ * @returns {CollectionDefinition[]} Array of collection definitions
169
+ */
170
+ getCollectionDefinitions() {
171
+ return [...this.collectionDefinitions];
172
+ }
173
+ /**
174
+ * Get all models for a specific database
175
+ * @param {string} database - Database name
176
+ * @returns {Record<string, Model<any>> | null} Object mapping collection names to models, or null if database not found
177
+ */
178
+ getModelsForDatabase(database) {
179
+ return this.models[database] || null;
180
+ }
181
+ /**
182
+ * Check if a model exists
183
+ * @param {string} database - Database name
184
+ * @param {string} collection - Collection name
185
+ * @returns {boolean} True if model exists, false otherwise
186
+ */
187
+ hasModel(database, collection) {
188
+ return !!(this.models[database] && this.models[database][collection]);
189
+ }
190
+ /**
191
+ * Clear all internal state, including connections and models.
192
+ * Useful for testing when different database prefixes are used.
193
+ */
194
+ async clear() {
195
+ // Close all connections first
196
+ await Promise.all(Object.values(this.connections).map(async (connection) => {
197
+ if (connection.readyState !== 0) {
198
+ try {
199
+ await connection.close();
200
+ }
201
+ catch (err) {
202
+ // Ignore close errors
203
+ }
204
+ }
205
+ }));
206
+ this.connections = {};
207
+ this.models = {};
208
+ this.collectionDefinitions = [];
209
+ this.mongoOption = null;
210
+ }
211
+ }
212
+ // Export singleton instance
213
+ exports.modelRegistry = ModelRegistry.getInstance();
214
+ 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,25 @@ 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
+ serverSelectionTimeoutMS: 10000, // 10 second timeout for server selection
55
+ socketTimeoutMS: 45000, // 45 second timeout for socket operations
56
+ });
57
+ }
58
+ else {
59
+ const fullDbName = (mongoOption.dbPrefix || '') + dbName;
60
+ console.info(`- Using existing connection for database: ${fullDbName}`);
61
+ }
51
62
  // Store connection
52
63
  connections[dbName] = connection;
53
64
  // add db models from schemas
@@ -58,10 +69,30 @@ function connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitio
58
69
  collections[dbName] = {};
59
70
  if (permissionDefinitions[dbName] == undefined)
60
71
  permissionDefinitions[dbName] = {};
61
- // create model from schema
62
- // and store in on global collection object
63
- const model = connection.model(collection, schema);
72
+ // Check if model already exists in registry (pre-created)
73
+ let model = model_registry_1.default.getModel(dbName, collection);
74
+ if (!model) {
75
+ // Model doesn't exist in registry, create it on the connection
76
+ // This can happen if defineCollection was called without mongoOption
77
+ model = connection.model(collection, schema);
78
+ }
79
+ else {
80
+ // Model exists in registry, verify it's using the same connection
81
+ // Mongoose models are bound to their connection, so we should use the registry model
82
+ // But we need to ensure the connection matches
83
+ const registryConnection = model_registry_1.default.getConnection(dbName);
84
+ if (registryConnection && registryConnection !== connection) {
85
+ // Connections don't match, but this shouldn't happen in normal flow
86
+ // Use the model from registry as it's already created
87
+ model = model_registry_1.default.getModel(dbName, collection);
88
+ }
89
+ }
90
+ // Store model in global collections object
64
91
  collections[dbName][collection] = model;
92
+ // Also update the CollectionDefinition with the model if it has a setModel method
93
+ if (collectionDefinition.setModel) {
94
+ collectionDefinition.setModel(model);
95
+ }
65
96
  // define Access Definition from component permissions
66
97
  // and store it on global access definition object
67
98
  permissionDefinitions[dbName][collection] = new security_1.AccessDefinition({
@@ -83,10 +114,22 @@ function connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitio
83
114
  });
84
115
  }
85
116
  });
86
- connection.on('connected', () => {
117
+ // If connection is already connected, resolve immediately
118
+ if (connection.readyState === 1) {
119
+ const fullDbName = (mongoOption.dbPrefix || '') + dbName;
87
120
  console.info(`- ${fullDbName} database has been connected`);
88
121
  done();
89
- });
122
+ }
123
+ else {
124
+ connection.on('connected', () => {
125
+ const fullDbName = (mongoOption.dbPrefix || '') + dbName;
126
+ console.info(`- ${fullDbName} database has been connected`);
127
+ done();
128
+ });
129
+ connection.on('error', err => {
130
+ reject(err);
131
+ });
132
+ }
90
133
  });
91
134
  }
92
135
  /**
@@ -113,6 +156,15 @@ function connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitio
113
156
  * ```
114
157
  */
115
158
  async function addCollectionDefinitionByList({ list, mongoOption, }) {
159
+ // First, ensure all collections are registered in the ModelRegistry
160
+ // This pre-creates models before connections are established
161
+ list.forEach(collectionDefinition => {
162
+ // Check if model already exists in registry
163
+ if (!model_registry_1.default.hasModel(collectionDefinition.database, collectionDefinition.collection)) {
164
+ // Register the collection and create its model
165
+ model_registry_1.default.registerCollection(collectionDefinition, mongoOption);
166
+ }
167
+ });
116
168
  // Group collection definitions by database
117
169
  const dbGroups = {};
118
170
  list.forEach(collectionDefinition => {
@@ -122,6 +174,7 @@ async function addCollectionDefinitionByList({ list, mongoOption, }) {
122
174
  dbGroups[collectionDefinition.database].push(collectionDefinition);
123
175
  });
124
176
  // Connect to each database
177
+ // Models are already pre-created, so connections will use existing models
125
178
  const connectionPromises = Object.entries(dbGroups).map(([dbName, collectionDefinitionList]) => connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitionList, mongoOption));
126
179
  await Promise.all(connectionPromises);
127
180
  }
@@ -185,6 +238,12 @@ function checkAccess(db, collection, operationType, queryOrDoc, user) {
185
238
  return true;
186
239
  if (permission.accessType === 'user_access' && user.type === 'user')
187
240
  return true;
241
+ if (typeof user.hasPermission === 'function' && user.hasPermission(permission.accessType)) {
242
+ if (operationType === security_1.AccessTypes.read)
243
+ return permission.read;
244
+ if (operationType === security_1.AccessTypes.write)
245
+ return permission.write;
246
+ }
188
247
  return false;
189
248
  });
190
249
  }
@@ -21,9 +21,12 @@ interface StoredFileDetail {
21
21
  * File upload options interface
22
22
  * @interface StoreFileOptions
23
23
  * @property {Object} file - File details
24
- * @property {string} file.path - Temporary file path
25
- * @property {string} file.type - MIME type of the file
26
- * @property {string} file.name - Original filename
24
+ * @property {string} [file.path] - Temporary file path (legacy)
25
+ * @property {string} [file.filepath] - Temporary file path (koa-body v6+)
26
+ * @property {string} [file.type] - MIME type of the file (legacy)
27
+ * @property {string} [file.mimetype] - MIME type of the file (koa-body v6+)
28
+ * @property {string} [file.name] - Original filename (legacy)
29
+ * @property {string} [file.originalFilename] - Original filename (koa-body v6+)
27
30
  * @property {number} file.size - File size in bytes
28
31
  * @property {string} ownerId - ID of the file owner
29
32
  * @property {string} tag - Tag for file organization
@@ -31,9 +34,12 @@ interface StoredFileDetail {
31
34
  */
32
35
  interface StoreFileOptions {
33
36
  file: {
34
- path: string;
35
- type: string;
36
- name: string;
37
+ path?: string;
38
+ filepath?: string;
39
+ type?: string;
40
+ mimetype?: string;
41
+ name?: string;
42
+ originalFilename?: string;
37
43
  size: number;
38
44
  };
39
45
  ownerId: string;
@@ -41,11 +47,11 @@ interface StoreFileOptions {
41
47
  removeFileAfterStore?: boolean;
42
48
  }
43
49
  /**
44
- * File service for handling file storage and retrieval.
45
- *
46
- * This service provides functionality for storing, retrieving, and managing files.
47
- * It handles file storage on disk and maintains file metadata in the database.
48
- * Files are organized by format and tag in the upload directory.
50
+ * File service class for handling file operations
51
+ * @class FileService
52
+ * @description
53
+ * This class provides methods for managing file uploads, retrieval, and deletion.
54
+ * It handles physical file storage and database metadata management.
49
55
  */
50
56
  declare class FileService {
51
57
  /**
@@ -86,29 +92,17 @@ declare class FileService {
86
92
  */
87
93
  setUploadDirectory(directoryOrConfig: string | StaticPathOptions): void;
88
94
  /**
89
- * @hidden
90
- *
91
- * Creates stored file details with unique filename
95
+ * Creates a unique filename and storage details
92
96
  * @param {string} fileType - MIME type of the file
93
- * @param {string} tag - Tag for file organization
94
- * @returns {StoredFileDetail} Storage details including filename and path
95
- * @throws {Error} If upload directory is not set
96
- *
97
- * @example
98
- * ```typescript
99
- * import { fileService } from '@modular-rest/server';
100
- *
101
- * const details = fileService.createStoredDetail('image/jpeg', 'profile');
102
- * // Returns: { fileName: '1234567890.jpeg', fullPath: '/uploads/jpeg/profile/1234567890.jpeg', fileFormat: 'jpeg' }
103
- * ```
97
+ * @param {string} tag - File tag
98
+ * @returns {StoredFileDetail} Storage details including unique filename and path
99
+ * @hidden
104
100
  */
105
101
  createStoredDetail(fileType: string, tag: string): StoredFileDetail;
106
102
  /**
107
- * @hidden
108
- *
109
- * Stores a file, removes the temporary file, and saves metadata to database
103
+ * Stores a file on disc and creates metadata in database
110
104
  * @param {StoreFileOptions} options - File storage options
111
- * @returns {Promise<IFile>} Promise resolving to stored file document
105
+ * @returns {Promise<IFile>} The created file document
112
106
  * @throws {Error} If upload directory is not set or storage fails
113
107
  * @example
114
108
  * ```typescript
@@ -129,75 +123,50 @@ declare class FileService {
129
123
  */
130
124
  storeFile({ file, ownerId, tag, removeFileAfterStore }: StoreFileOptions): Promise<IFile>;
131
125
  /**
132
- * @hidden
133
- *
134
- * Removes a file from the disk
135
- * @param {string} path - File path to remove
136
- * @returns {Promise<void>} Promise resolving when file is removed
137
- * @throws {Error} If file removal fails
126
+ * Deletes a file from disc and database
127
+ * @param {string} fileId - ID of the file to delete
128
+ * @returns {Promise<boolean>} True if deletion was successful
129
+ * @throws {Error} If file is not found or deletion fails
138
130
  * @example
139
131
  * ```typescript
140
132
  * import { fileService } from '@modular-rest/server';
141
133
  *
142
- * await fileService.removeFromDisc('/uploads/jpeg/profile/1234567890.jpeg');
134
+ * await fileService.removeFile('file123');
143
135
  * ```
144
136
  */
145
- removeFromDisc(path: string): Promise<void>;
137
+ removeFile(fileId: string): Promise<boolean>;
146
138
  /**
147
- * Removes a file from both database and disk
148
- *
149
- * @param {string} fileId - File ID to remove
150
- * @returns {Promise<void>} Promise resolving when file is removed
151
- * @throws {Error} If file is not found or removal fails
152
- * @example
153
- * ```typescript
154
- * import { fileService } from '@modular-rest/server';
155
- *
156
- * try {
157
- * await fileService.removeFile('file123');
158
- * console.log('File removed successfully');
159
- * } catch (error) {
160
- * console.error('Failed to remove file:', error);
161
- * }
162
- * ```
139
+ * Deletes a file from physical storage
140
+ * @param {string} path - Physical path to the file
141
+ * @returns {Promise<boolean>} True if deletion was successful
142
+ * @hidden
163
143
  */
164
- removeFile(fileId: string): Promise<void>;
144
+ removeFromDisc(path: string): Promise<boolean>;
165
145
  /**
166
- * Retrieves a file document from the database
167
- *
168
- * @param {string} fileId - File ID to retrieve
169
- * @returns {Promise<IFile>} Promise resolving to file document
170
- * @throws {Error} If collection model is not found or file is not found
171
- * @example
172
- * ```typescript
173
- * import { fileService } from '@modular-rest/server';
174
- *
175
- * const fileDoc = await fileService.getFile('file123');
176
- * console.log('File details:', fileDoc);
177
- * ```
146
+ * Retrieves a file document from database
147
+ * @param {string} fileId - ID of the file
148
+ * @returns {Promise<IFile>} The file document
149
+ * @throws {Error} If file is not found
150
+ * @hidden
178
151
  */
179
152
  getFile(fileId: string): Promise<IFile>;
180
153
  /**
181
- * Retrieves the public URL link for a file
182
- *
183
- * @param {string} fileId - File ID to get link for
184
- * @returns {Promise<string>} Promise resolving to file URL
185
- * @throws {Error} If URL path is not defined or file is not found
154
+ * Gets the public URL for a file
155
+ * @param {string} fileId - ID of the file
156
+ * @returns {Promise<string>} The public URL
186
157
  * @example
187
158
  * ```typescript
188
159
  * import { fileService } from '@modular-rest/server';
189
160
  *
190
- * const link = await fileService.getFileLink('file123');
191
- * // Returns: '/uploads/jpeg/profile/1234567890.jpeg'
161
+ * const url = await fileService.getFileLink('file123');
162
+ * // Returns: '/assets/jpeg/profile/1234567890.jpeg'
192
163
  * ```
193
164
  */
194
165
  getFileLink(fileId: string): Promise<string>;
195
166
  /**
196
- * Gets the full filesystem path for a file
197
- *
198
- * @param {string} fileId - File ID to get path for
199
- * @returns {Promise<string>} Promise resolving to full file path
200
- * @throws {Error} If upload directory is not set or file is not found
167
+ * Gets the physical path for a file
168
+ * @param {string} fileId - ID of the file
169
+ * @returns {Promise<string>} The physical path
201
170
  * @example
202
171
  * ```typescript
203
172
  * import { fileService } from '@modular-rest/server';