@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
@@ -47,11 +47,11 @@ const trigger_operator_1 = __importDefault(require("../../class/trigger_operator
47
47
  */
48
48
  exports.name = 'file';
49
49
  /**
50
- * File service for handling file storage and retrieval.
51
- *
52
- * This service provides functionality for storing, retrieving, and managing files.
53
- * It handles file storage on disk and maintains file metadata in the database.
54
- * 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.
55
55
  */
56
56
  class FileService {
57
57
  /**
@@ -95,56 +95,43 @@ class FileService {
95
95
  fs_1.default.mkdirSync(directoryOrConfig, { recursive: true });
96
96
  }
97
97
  this.directory = directoryOrConfig;
98
- this.urlPath = null; // No URL path available with legacy format
99
- return;
98
+ this.urlPath = '/assets'; // Default urlPath for legacy
100
99
  }
101
- // New format: Extract only necessary properties from StaticPathOptions
102
- const directory = directoryOrConfig.directory || '';
103
- const urlPath = directoryOrConfig.urlPath || '/assets';
104
- if (!directory) {
105
- throw new Error('directory is required in uploadDirectoryConfig');
106
- }
107
- if (!fs_1.default.existsSync(directory)) {
108
- fs_1.default.mkdirSync(directory, { recursive: true });
100
+ else {
101
+ const { directory, urlPath } = directoryOrConfig;
102
+ if (!fs_1.default.existsSync(directory)) {
103
+ fs_1.default.mkdirSync(directory, { recursive: true });
104
+ }
105
+ this.directory = directory;
106
+ this.urlPath = urlPath || '/assets';
109
107
  }
110
- // Store only the necessary properties (ignore koa-static options)
111
- this.directory = directory;
112
- this.urlPath = urlPath;
113
108
  }
114
109
  /**
115
- * @hidden
116
- *
117
- * Creates stored file details with unique filename
110
+ * Creates a unique filename and storage details
118
111
  * @param {string} fileType - MIME type of the file
119
- * @param {string} tag - Tag for file organization
120
- * @returns {StoredFileDetail} Storage details including filename and path
121
- * @throws {Error} If upload directory is not set
122
- *
123
- * @example
124
- * ```typescript
125
- * import { fileService } from '@modular-rest/server';
126
- *
127
- * const details = fileService.createStoredDetail('image/jpeg', 'profile');
128
- * // Returns: { fileName: '1234567890.jpeg', fullPath: '/uploads/jpeg/profile/1234567890.jpeg', fileFormat: 'jpeg' }
129
- * ```
112
+ * @param {string} tag - File tag
113
+ * @returns {StoredFileDetail} Storage details including unique filename and path
114
+ * @hidden
130
115
  */
131
116
  createStoredDetail(fileType, tag) {
132
117
  const typeParts = fileType.split('/');
133
118
  const fileFormat = typeParts[1] || typeParts[0] || 'unknown';
134
119
  const time = new Date().getTime();
135
120
  const fileName = `${time}.${fileFormat}`;
136
- if (!FileService.instance.directory) {
137
- throw new Error('Upload directory has not been set');
121
+ if (!this.directory) {
122
+ throw new Error('Upload directory not set');
138
123
  }
139
- const fullPath = path_1.default.join(FileService.instance.directory, fileFormat, tag, fileName);
140
- return { fileName, fullPath, fileFormat };
124
+ const fullPath = path_1.default.join(this.directory, fileFormat, tag, fileName);
125
+ return {
126
+ fileName,
127
+ fullPath,
128
+ fileFormat,
129
+ };
141
130
  }
142
131
  /**
143
- * @hidden
144
- *
145
- * Stores a file, removes the temporary file, and saves metadata to database
132
+ * Stores a file on disc and creates metadata in database
146
133
  * @param {StoreFileOptions} options - File storage options
147
- * @returns {Promise<IFile>} Promise resolving to stored file document
134
+ * @returns {Promise<IFile>} The created file document
148
135
  * @throws {Error} If upload directory is not set or storage fails
149
136
  * @example
150
137
  * ```typescript
@@ -167,10 +154,16 @@ class FileService {
167
154
  if (!FileService.instance.directory) {
168
155
  throw new Error('Upload directory has not been set');
169
156
  }
157
+ const fileType = file.mimetype || file.type || 'unknown/unknown';
158
+ const filePath = file.filepath || file.path;
159
+ const fileName = file.originalFilename || file.name || 'unknown';
160
+ if (!filePath) {
161
+ throw new Error('File path is missing');
162
+ }
170
163
  let storedFile;
171
164
  return new Promise(async (done, reject) => {
172
- storedFile = FileService.instance.createStoredDetail(file.type, tag);
173
- fs_1.default.copyFile(file.path, storedFile.fullPath, (err) => {
165
+ storedFile = FileService.instance.createStoredDetail(fileType, tag);
166
+ fs_1.default.copyFile(filePath, storedFile.fullPath, (err) => {
174
167
  if (err) {
175
168
  reject(err);
176
169
  }
@@ -179,7 +172,14 @@ class FileService {
179
172
  }
180
173
  // remove temp file
181
174
  if (removeFileAfterStore) {
182
- fs_1.default.unlinkSync(file.path);
175
+ try {
176
+ if (fs_1.default.existsSync(filePath)) {
177
+ fs_1.default.unlinkSync(filePath);
178
+ }
179
+ }
180
+ catch (e) {
181
+ console.warn('Failed to remove temp file:', e);
182
+ }
183
183
  }
184
184
  });
185
185
  })
@@ -191,161 +191,119 @@ class FileService {
191
191
  }
192
192
  const data = {
193
193
  owner: ownerId,
194
+ tag,
195
+ originalName: fileName,
194
196
  fileName: storedFile.fileName,
195
- originalName: file.name,
196
197
  format: storedFile.fileFormat,
197
- tag,
198
198
  size: file.size,
199
199
  };
200
- // Create new document
201
- const doc = new CollectionModel(data);
202
- return doc.save().then(savedDoc => {
203
- trigger_operator_1.default.call('insert-one', 'cms', 'file', {
204
- query: null,
205
- queryResult: savedDoc,
206
- });
207
- return savedDoc;
208
- });
200
+ return CollectionModel.create(data);
209
201
  })
210
- .catch(err => {
211
- // remove stored file
212
- fs_1.default.unlinkSync(storedFile.fullPath);
213
- throw err;
214
- });
215
- }
216
- /**
217
- * @hidden
218
- *
219
- * Removes a file from the disk
220
- * @param {string} path - File path to remove
221
- * @returns {Promise<void>} Promise resolving when file is removed
222
- * @throws {Error} If file removal fails
223
- * @example
224
- * ```typescript
225
- * import { fileService } from '@modular-rest/server';
226
- *
227
- * await fileService.removeFromDisc('/uploads/jpeg/profile/1234567890.jpeg');
228
- * ```
229
- */
230
- removeFromDisc(path) {
231
- return new Promise((done, reject) => {
232
- fs_1.default.unlink(path, (err) => {
233
- if (err)
234
- reject(err);
235
- else
236
- done();
237
- });
202
+ .then(async (doc) => {
203
+ trigger_operator_1.default.call('insert-one', 'cms', 'file', { queryResult: doc });
204
+ return doc;
238
205
  });
239
206
  }
240
207
  /**
241
- * Removes a file from both database and disk
242
- *
243
- * @param {string} fileId - File ID to remove
244
- * @returns {Promise<void>} Promise resolving when file is removed
245
- * @throws {Error} If file is not found or removal fails
208
+ * Deletes a file from disc and database
209
+ * @param {string} fileId - ID of the file to delete
210
+ * @returns {Promise<boolean>} True if deletion was successful
211
+ * @throws {Error} If file is not found or deletion fails
246
212
  * @example
247
213
  * ```typescript
248
214
  * import { fileService } from '@modular-rest/server';
249
215
  *
250
- * try {
251
- * await fileService.removeFile('file123');
252
- * console.log('File removed successfully');
253
- * } catch (error) {
254
- * console.error('Failed to remove file:', error);
255
- * }
216
+ * await fileService.removeFile('file123');
256
217
  * ```
257
218
  */
258
- removeFile(fileId) {
219
+ async removeFile(fileId) {
259
220
  if (!FileService.instance.directory) {
260
221
  throw new Error('Upload directory has not been set');
261
222
  }
262
- return new Promise(async (done, reject) => {
263
- const CollectionModel = DataProvider.getCollection('cms', 'file');
264
- if (!CollectionModel) {
265
- return reject(new Error('Collection model not found'));
266
- }
267
- const fileDoc = await CollectionModel.findOne({ _id: fileId }).exec();
268
- if (!fileDoc) {
269
- return reject(new Error('File not found'));
223
+ const CollectionModel = DataProvider.getCollection('cms', 'file');
224
+ if (!CollectionModel) {
225
+ throw new Error('Collection model not found');
226
+ }
227
+ const doc = await CollectionModel.findById(fileId);
228
+ if (!doc) {
229
+ throw new Error('File not found');
230
+ }
231
+ const filePath = path_1.default.join(FileService.instance.directory, doc.format, doc.tag, doc.fileName);
232
+ if (fs_1.default.existsSync(filePath)) {
233
+ try {
234
+ await FileService.instance.removeFromDisc(filePath);
270
235
  }
271
- await CollectionModel.deleteOne({ _id: fileId })
272
- .exec()
273
- .then(() => {
274
- // create file path
275
- const filePath = path_1.default.join(FileService.instance.directory, fileDoc.format, fileDoc.tag, fileDoc.fileName);
276
- // Remove file from disc
277
- return FileService.instance.removeFromDisc(filePath).catch(async (err) => {
278
- // Recreate fileDoc if removing file operation has error
279
- await new CollectionModel(fileDoc).save();
236
+ catch (err) {
237
+ // If the file is not found on disc, we can still proceed with deleting metadata
238
+ if (err.code !== 'ENOENT') {
280
239
  throw err;
281
- });
282
- })
283
- .then(() => {
284
- trigger_operator_1.default.call('remove-one', 'cms', 'file', {
285
- query: { _id: fileId },
286
- queryResult: null,
287
- });
288
- })
289
- .then(done)
290
- .catch(reject);
240
+ }
241
+ }
242
+ }
243
+ await CollectionModel.findByIdAndDelete(fileId);
244
+ trigger_operator_1.default.call('remove-one', 'cms', 'file', { queryResult: doc });
245
+ return true;
246
+ }
247
+ /**
248
+ * Deletes a file from physical storage
249
+ * @param {string} path - Physical path to the file
250
+ * @returns {Promise<boolean>} True if deletion was successful
251
+ * @hidden
252
+ */
253
+ removeFromDisc(path) {
254
+ return new Promise((done, reject) => {
255
+ fs_1.default.unlink(path, err => {
256
+ if (err) {
257
+ reject(err);
258
+ }
259
+ else {
260
+ done(true);
261
+ }
262
+ });
291
263
  });
292
264
  }
293
265
  /**
294
- * Retrieves a file document from the database
295
- *
296
- * @param {string} fileId - File ID to retrieve
297
- * @returns {Promise<IFile>} Promise resolving to file document
298
- * @throws {Error} If collection model is not found or file is not found
299
- * @example
300
- * ```typescript
301
- * import { fileService } from '@modular-rest/server';
302
- *
303
- * const fileDoc = await fileService.getFile('file123');
304
- * console.log('File details:', fileDoc);
305
- * ```
266
+ * Retrieves a file document from database
267
+ * @param {string} fileId - ID of the file
268
+ * @returns {Promise<IFile>} The file document
269
+ * @throws {Error} If file is not found
270
+ * @hidden
306
271
  */
307
- getFile(fileId) {
272
+ async getFile(fileId) {
308
273
  const CollectionModel = DataProvider.getCollection('cms', 'file');
309
274
  if (!CollectionModel) {
310
275
  throw new Error('Collection model not found');
311
276
  }
312
- return CollectionModel.findOne({ _id: fileId })
313
- .exec()
314
- .then(doc => {
315
- if (!doc) {
316
- throw new Error('File not found');
317
- }
318
- return doc;
319
- });
277
+ const doc = await CollectionModel.findById(fileId);
278
+ if (!doc) {
279
+ throw new Error('File not found');
280
+ }
281
+ return doc;
320
282
  }
321
283
  /**
322
- * Retrieves the public URL link for a file
323
- *
324
- * @param {string} fileId - File ID to get link for
325
- * @returns {Promise<string>} Promise resolving to file URL
326
- * @throws {Error} If URL path is not defined or file is not found
284
+ * Gets the public URL for a file
285
+ * @param {string} fileId - ID of the file
286
+ * @returns {Promise<string>} The public URL
327
287
  * @example
328
288
  * ```typescript
329
289
  * import { fileService } from '@modular-rest/server';
330
290
  *
331
- * const link = await fileService.getFileLink('file123');
332
- * // Returns: '/uploads/jpeg/profile/1234567890.jpeg'
291
+ * const url = await fileService.getFileLink('file123');
292
+ * // Returns: '/assets/jpeg/profile/1234567890.jpeg'
333
293
  * ```
334
294
  */
335
295
  async getFileLink(fileId) {
336
296
  const fileDoc = await FileService.instance.getFile(fileId);
337
297
  if (!FileService.instance.urlPath) {
338
- throw new Error('Upload directory URL path is not defined. Please configure uploadDirectoryConfig with a urlPath property.');
298
+ throw new Error('Upload directory config has not been set');
339
299
  }
340
300
  const link = `${FileService.instance.urlPath}/${fileDoc.format}/${fileDoc.tag}/${fileDoc.fileName}`;
341
301
  return link;
342
302
  }
343
303
  /**
344
- * Gets the full filesystem path for a file
345
- *
346
- * @param {string} fileId - File ID to get path for
347
- * @returns {Promise<string>} Promise resolving to full file path
348
- * @throws {Error} If upload directory is not set or file is not found
304
+ * Gets the physical path for a file
305
+ * @param {string} fileId - ID of the file
306
+ * @returns {Promise<string>} The physical path
349
307
  * @example
350
308
  * ```typescript
351
309
  * import { fileService } from '@modular-rest/server';
@@ -367,3 +325,4 @@ class FileService {
367
325
  * @constant {FileService}
368
326
  */
369
327
  exports.main = new FileService();
328
+ FileService.instance = exports.main;
@@ -61,17 +61,17 @@ const functions = [];
61
61
  */
62
62
  function defineFunction(options) {
63
63
  // Check if the function already exists
64
- const existingFunction = functions.find(f => f.name === exports.name);
64
+ const existingFunction = functions.find(f => f.name === options.name);
65
65
  if (existingFunction) {
66
- throw new Error(`Function with name ${exports.name} already exists`);
66
+ throw new Error(`Function with name ${options.name} already exists`);
67
67
  }
68
68
  // Check if the permission types provided
69
69
  if (!options.permissionTypes || !options.permissionTypes.length) {
70
- throw new Error(`Permission types not provided for function ${exports.name}`);
70
+ throw new Error(`Permission types not provided for function ${options.name}`);
71
71
  }
72
72
  // Check if the callback is a function
73
73
  if (typeof options.callback !== 'function') {
74
- throw new Error(`Callback is not a function for function ${exports.name}`);
74
+ throw new Error(`Callback is not a function for function ${options.name}`);
75
75
  }
76
76
  // Add the function to the list of functions
77
77
  return options;
@@ -41,6 +41,7 @@ const koa_router_1 = __importDefault(require("koa-router"));
41
41
  const validator_1 = require("../../class/validator");
42
42
  const reply_1 = require("../../class/reply");
43
43
  const service = __importStar(require("./service"));
44
+ const userManager = __importStar(require("../user_manager/service"));
44
45
  const name = 'verify';
45
46
  exports.name = name;
46
47
  const verify = new koa_router_1.default();
@@ -82,7 +83,7 @@ verify.post('/checkAccess', async (ctx) => {
82
83
  ctx.throw(412, err.message);
83
84
  });
84
85
  const userid = payload.id;
85
- await global.services.userManager.main
86
+ await userManager.main
86
87
  .getUserById(userid)
87
88
  .then((user) => {
88
89
  const key = user.hasPermission(body.permissionField);
@@ -41,6 +41,7 @@ const koa_router_1 = __importDefault(require("koa-router"));
41
41
  const validator_1 = require("../../class/validator");
42
42
  const reply_1 = require("../../class/reply");
43
43
  const service = __importStar(require("./service"));
44
+ const dataProvider = __importStar(require("../data_provider/service"));
44
45
  const name = 'user';
45
46
  exports.name = name;
46
47
  const userManager = new koa_router_1.default();
@@ -183,7 +184,6 @@ userManager.post('/getPermission', async (ctx) => {
183
184
  return;
184
185
  }
185
186
  const query = { _id: body.id };
186
- const dataProvider = global.services.dataProvider;
187
187
  const permission = await dataProvider
188
188
  .getCollection('cms', 'permission')
189
189
  .findOne(query)
@@ -431,11 +431,19 @@ class UserManager {
431
431
  if (!this.isCodeValid(id, code)) {
432
432
  throw new Error('Invalid verification code');
433
433
  }
434
+ const tempId = this.tempIds[id];
435
+ const idType = tempId ? tempId.type : 'email';
434
436
  const userModel = DataProvider.getCollection('cms', 'auth');
435
437
  if (!userModel) {
436
438
  throw new Error('User model not found');
437
439
  }
438
- const query = { email: id };
440
+ const query = {};
441
+ if (idType === 'phone') {
442
+ query.phone = id;
443
+ }
444
+ else {
445
+ query.email = id;
446
+ }
439
447
  // Get from database
440
448
  let gottenFromDB;
441
449
  try {
@@ -444,20 +452,30 @@ class UserManager {
444
452
  catch (error) {
445
453
  throw error;
446
454
  }
447
- if (!gottenFromDB) {
448
- throw new Error('User not found');
449
- }
450
455
  try {
451
- // Load user
452
- const user = await user_1.User.loadFromModel(gottenFromDB);
453
- // Update password
454
- user.password = password;
455
- // Save to database
456
- await user.save();
457
- // Get token payload
458
- const payload = user.getBrief();
459
- // Generate json web token
460
- const token = await JWT.main.sign(payload);
456
+ let token;
457
+ if (!gottenFromDB) {
458
+ // Registration flow: create new user
459
+ const registrationData = {
460
+ password,
461
+ type: 'user',
462
+ };
463
+ if (idType === 'phone') {
464
+ registrationData.phone = id;
465
+ }
466
+ else {
467
+ registrationData.email = id;
468
+ }
469
+ token = await this.registerUser(registrationData);
470
+ }
471
+ else {
472
+ // Password reset flow: update existing user
473
+ const user = await user_1.User.loadFromModel(gottenFromDB);
474
+ user.password = password;
475
+ await user.save();
476
+ const payload = user.getBrief();
477
+ token = await JWT.main.sign(payload);
478
+ }
461
479
  // Remove temporary ID
462
480
  delete this.tempIds[id];
463
481
  return token;
@@ -493,11 +511,19 @@ class UserManager {
493
511
  if (!this.isCodeValid(id, code)) {
494
512
  throw new Error('Invalid verification code');
495
513
  }
514
+ const tempId = this.tempIds[id];
515
+ const idType = tempId ? tempId.type : 'email';
496
516
  const userModel = DataProvider.getCollection('cms', 'auth');
497
517
  if (!userModel) {
498
518
  throw new Error('User model not found');
499
519
  }
500
- const query = { email: id };
520
+ const query = {};
521
+ if (idType === 'phone') {
522
+ query.phone = id;
523
+ }
524
+ else {
525
+ query.email = id;
526
+ }
501
527
  // Get from database
502
528
  let gottenFromDB;
503
529
  try {
@@ -557,8 +583,8 @@ class UserManager {
557
583
  return reject(new Error('User model not found'));
558
584
  }
559
585
  try {
560
- // Create user document
561
- const userDoc = await userModel.create({
586
+ // Create user document with timeout
587
+ const createPromise = userModel.create({
562
588
  ...detail,
563
589
  type: detail.type || 'user',
564
590
  permissionGroup: detail.permissionGroup || (0, permissionManager_1.getDefaultPermissionGroups)().title,
@@ -566,6 +592,11 @@ class UserManager {
566
592
  email: detail.email || undefined,
567
593
  password: detail.password || undefined,
568
594
  });
595
+ // Add timeout wrapper to prevent hanging
596
+ const userDoc = await Promise.race([
597
+ createPromise,
598
+ new Promise((_, reject) => setTimeout(() => reject(new Error('User creation timeout after 10s')), 10000)),
599
+ ]);
569
600
  // Load user from document
570
601
  const user = await user_1.User.loadFromModel(userDoc);
571
602
  // Get token payload
package/jest.config.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { Config } from 'jest';
2
+
3
+ const config: Config = {
4
+ preset: 'ts-jest',
5
+ testEnvironment: 'node',
6
+ roots: ['<rootDir>/tests', '<rootDir>/src'],
7
+ testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(spec|test).ts'],
8
+ moduleFileExtensions: ['ts', 'js', 'json'],
9
+ transform: {
10
+ '^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.json' }],
11
+ },
12
+ setupFilesAfterEnv: ['<rootDir>/tests/setup/jest.setup.ts'],
13
+ testTimeout: 60000,
14
+ maxWorkers: 1,
15
+ verbose: true,
16
+ };
17
+
18
+ export default config;
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@modular-rest/server",
3
- "version": "1.18.1",
3
+ "version": "1.20.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",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "dev": "tsc --watch",
10
- "test": "echo \"Error: no test specified\" && exit 1",
10
+ "test": "jest",
11
+ "test:watch": "jest --watch",
12
+ "test:router": "jest tests/router --runInBand",
13
+ "test:coverage": "jest --coverage --runInBand",
11
14
  "play-test": "tsc -p tsconfig.json && node dist/play-test.js",
12
15
  "docs": "typedoc --router kind src/index.ts"
13
16
  },
@@ -44,6 +47,7 @@
44
47
  },
45
48
  "devDependencies": {
46
49
  "@types/bson": "4.2.0",
50
+ "@types/jest": "30.0.0",
47
51
  "@types/jsonwebtoken": "^9.0.5",
48
52
  "@types/koa": "^2.14.0",
49
53
  "@types/koa-mount": "^4.0.5",
@@ -52,6 +56,11 @@
52
56
  "@types/koa__cors": "^5.0.0",
53
57
  "@types/mongoose": "^5.10.5",
54
58
  "@types/node": "^20.10.0",
59
+ "jest": "30.2.0",
60
+ "mongodb-memory-server": "11.0.1",
61
+ "supertest": "7.1.4",
62
+ "@types/supertest": "^6.0.2",
63
+ "ts-jest": "29.4.6",
55
64
  "typedoc": "^0.28.1",
56
65
  "typedoc-plugin-markdown": "^4.6.0",
57
66
  "typedoc-plugin-missing-exports": "^4.0.0",
@@ -203,17 +203,18 @@ export async function createRest(options: RestOptions): Promise<{ app: Koa; serv
203
203
  extension: '.js',
204
204
  },
205
205
  });
206
+ }
206
207
 
207
- // 5. Plug in additional defined functions
208
- if (config.functions) {
209
- Combination.addFunctionsByArray(config.functions);
210
- }
208
+ // 5. Plug in additional defined functions
209
+ if (config.functions) {
210
+ Combination.addFunctionsByArray(config.functions);
211
211
  }
212
212
 
213
213
  // 4. Setting up default services
214
214
  try {
215
215
  await require('./helper/presetup_services').setup(config);
216
216
  } catch (e) {
217
+ console.error(`[createRest] Error in setup:`, e);
217
218
  return Promise.reject(e);
218
219
  }
219
220