@modular-rest/server 1.19.0 → 1.20.1

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 (35) hide show
  1. package/.nvmrc +1 -1
  2. package/dist/application.js +5 -4
  3. package/dist/class/combinator.js +7 -3
  4. package/dist/class/directory.d.ts +2 -4
  5. package/dist/class/directory.js +42 -64
  6. package/dist/helper/data_insertion.js +93 -26
  7. package/dist/services/data_provider/model_registry.d.ts +5 -0
  8. package/dist/services/data_provider/model_registry.js +25 -0
  9. package/dist/services/data_provider/service.js +8 -0
  10. package/dist/services/file/service.d.ts +47 -78
  11. package/dist/services/file/service.js +124 -155
  12. package/dist/services/functions/service.js +4 -4
  13. package/dist/services/jwt/router.js +2 -1
  14. package/dist/services/user_manager/router.js +1 -1
  15. package/dist/services/user_manager/service.js +48 -17
  16. package/jest.config.ts +18 -0
  17. package/package.json +11 -2
  18. package/src/application.ts +5 -4
  19. package/src/class/combinator.ts +10 -3
  20. package/src/class/directory.ts +40 -58
  21. package/src/helper/data_insertion.ts +101 -27
  22. package/src/services/data_provider/model_registry.ts +28 -0
  23. package/src/services/data_provider/service.ts +6 -0
  24. package/src/services/file/service.ts +146 -178
  25. package/src/services/functions/service.ts +4 -4
  26. package/src/services/jwt/router.ts +2 -1
  27. package/src/services/user_manager/router.ts +1 -1
  28. package/src/services/user_manager/service.ts +49 -20
  29. package/tests/helpers/test-app.ts +182 -0
  30. package/tests/router/data-provider.router.int.test.ts +192 -0
  31. package/tests/router/file.router.int.test.ts +104 -0
  32. package/tests/router/functions.router.int.test.ts +91 -0
  33. package/tests/router/jwt.router.int.test.ts +69 -0
  34. package/tests/router/user-manager.router.int.test.ts +85 -0
  35. 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,26 @@ 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
+ // Ensure destination directory exists
167
+ const destDir = path_1.default.dirname(storedFile.fullPath);
168
+ try {
169
+ if (!fs_1.default.existsSync(destDir)) {
170
+ fs_1.default.mkdirSync(destDir, { recursive: true });
171
+ }
172
+ }
173
+ catch (err) {
174
+ return reject(err);
175
+ }
176
+ fs_1.default.copyFile(filePath, storedFile.fullPath, (err) => {
174
177
  if (err) {
175
178
  reject(err);
176
179
  }
@@ -179,7 +182,14 @@ class FileService {
179
182
  }
180
183
  // remove temp file
181
184
  if (removeFileAfterStore) {
182
- fs_1.default.unlinkSync(file.path);
185
+ try {
186
+ if (fs_1.default.existsSync(filePath)) {
187
+ fs_1.default.unlinkSync(filePath);
188
+ }
189
+ }
190
+ catch (e) {
191
+ console.warn('Failed to remove temp file:', e);
192
+ }
183
193
  }
184
194
  });
185
195
  })
@@ -191,161 +201,119 @@ class FileService {
191
201
  }
192
202
  const data = {
193
203
  owner: ownerId,
204
+ tag,
205
+ originalName: fileName,
194
206
  fileName: storedFile.fileName,
195
- originalName: file.name,
196
207
  format: storedFile.fileFormat,
197
- tag,
198
208
  size: file.size,
199
209
  };
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
- });
210
+ return CollectionModel.create(data);
209
211
  })
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
- });
212
+ .then(async (doc) => {
213
+ trigger_operator_1.default.call('insert-one', 'cms', 'file', { queryResult: doc });
214
+ return doc;
238
215
  });
239
216
  }
240
217
  /**
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
218
+ * Deletes a file from disc and database
219
+ * @param {string} fileId - ID of the file to delete
220
+ * @returns {Promise<boolean>} True if deletion was successful
221
+ * @throws {Error} If file is not found or deletion fails
246
222
  * @example
247
223
  * ```typescript
248
224
  * import { fileService } from '@modular-rest/server';
249
225
  *
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
- * }
226
+ * await fileService.removeFile('file123');
256
227
  * ```
257
228
  */
258
- removeFile(fileId) {
229
+ async removeFile(fileId) {
259
230
  if (!FileService.instance.directory) {
260
231
  throw new Error('Upload directory has not been set');
261
232
  }
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'));
233
+ const CollectionModel = DataProvider.getCollection('cms', 'file');
234
+ if (!CollectionModel) {
235
+ throw new Error('Collection model not found');
236
+ }
237
+ const doc = await CollectionModel.findById(fileId);
238
+ if (!doc) {
239
+ throw new Error('File not found');
240
+ }
241
+ const filePath = path_1.default.join(FileService.instance.directory, doc.format, doc.tag, doc.fileName);
242
+ if (fs_1.default.existsSync(filePath)) {
243
+ try {
244
+ await FileService.instance.removeFromDisc(filePath);
270
245
  }
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();
246
+ catch (err) {
247
+ // If the file is not found on disc, we can still proceed with deleting metadata
248
+ if (err.code !== 'ENOENT') {
280
249
  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);
250
+ }
251
+ }
252
+ }
253
+ await CollectionModel.findByIdAndDelete(fileId);
254
+ trigger_operator_1.default.call('remove-one', 'cms', 'file', { queryResult: doc });
255
+ return true;
256
+ }
257
+ /**
258
+ * Deletes a file from physical storage
259
+ * @param {string} path - Physical path to the file
260
+ * @returns {Promise<boolean>} True if deletion was successful
261
+ * @hidden
262
+ */
263
+ removeFromDisc(path) {
264
+ return new Promise((done, reject) => {
265
+ fs_1.default.unlink(path, err => {
266
+ if (err) {
267
+ reject(err);
268
+ }
269
+ else {
270
+ done(true);
271
+ }
272
+ });
291
273
  });
292
274
  }
293
275
  /**
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
- * ```
276
+ * Retrieves a file document from database
277
+ * @param {string} fileId - ID of the file
278
+ * @returns {Promise<IFile>} The file document
279
+ * @throws {Error} If file is not found
280
+ * @hidden
306
281
  */
307
- getFile(fileId) {
282
+ async getFile(fileId) {
308
283
  const CollectionModel = DataProvider.getCollection('cms', 'file');
309
284
  if (!CollectionModel) {
310
285
  throw new Error('Collection model not found');
311
286
  }
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
- });
287
+ const doc = await CollectionModel.findById(fileId);
288
+ if (!doc) {
289
+ throw new Error('File not found');
290
+ }
291
+ return doc;
320
292
  }
321
293
  /**
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
294
+ * Gets the public URL for a file
295
+ * @param {string} fileId - ID of the file
296
+ * @returns {Promise<string>} The public URL
327
297
  * @example
328
298
  * ```typescript
329
299
  * import { fileService } from '@modular-rest/server';
330
300
  *
331
- * const link = await fileService.getFileLink('file123');
332
- * // Returns: '/uploads/jpeg/profile/1234567890.jpeg'
301
+ * const url = await fileService.getFileLink('file123');
302
+ * // Returns: '/assets/jpeg/profile/1234567890.jpeg'
333
303
  * ```
334
304
  */
335
305
  async getFileLink(fileId) {
336
306
  const fileDoc = await FileService.instance.getFile(fileId);
337
307
  if (!FileService.instance.urlPath) {
338
- throw new Error('Upload directory URL path is not defined. Please configure uploadDirectoryConfig with a urlPath property.');
308
+ throw new Error('Upload directory config has not been set');
339
309
  }
340
310
  const link = `${FileService.instance.urlPath}/${fileDoc.format}/${fileDoc.tag}/${fileDoc.fileName}`;
341
311
  return link;
342
312
  }
343
313
  /**
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
314
+ * Gets the physical path for a file
315
+ * @param {string} fileId - ID of the file
316
+ * @returns {Promise<string>} The physical path
349
317
  * @example
350
318
  * ```typescript
351
319
  * import { fileService } from '@modular-rest/server';
@@ -367,3 +335,4 @@ class FileService {
367
335
  * @constant {FileService}
368
336
  */
369
337
  exports.main = new FileService();
338
+ 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.19.0",
3
+ "version": "1.20.1",
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