@avleon/core 0.0.20 → 0.0.25

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 (75) hide show
  1. package/README.md +559 -2
  2. package/dist/application.d.ts +47 -0
  3. package/dist/application.js +50 -0
  4. package/dist/cache.d.ts +12 -0
  5. package/dist/cache.js +78 -0
  6. package/dist/collection.js +3 -2
  7. package/dist/container.d.ts +1 -0
  8. package/dist/container.js +2 -1
  9. package/dist/environment-variables.js +1 -1
  10. package/dist/file-storage.js +0 -128
  11. package/dist/helpers.d.ts +2 -0
  12. package/dist/helpers.js +41 -4
  13. package/dist/icore.d.ts +93 -42
  14. package/dist/icore.js +319 -251
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.js +4 -2
  17. package/dist/middleware.js +0 -8
  18. package/dist/multipart.js +4 -1
  19. package/dist/openapi.d.ts +37 -37
  20. package/dist/params.js +2 -2
  21. package/dist/response.js +6 -2
  22. package/dist/route-methods.js +1 -1
  23. package/dist/swagger-schema.js +4 -1
  24. package/dist/testing.js +6 -3
  25. package/dist/utils/index.d.ts +2 -0
  26. package/dist/utils/index.js +18 -0
  27. package/dist/utils/optional-require.d.ts +8 -0
  28. package/dist/utils/optional-require.js +70 -0
  29. package/dist/validation.d.ts +4 -1
  30. package/dist/validation.js +10 -5
  31. package/package.json +26 -9
  32. package/src/application.ts +125 -0
  33. package/src/authentication.ts +16 -0
  34. package/src/cache.ts +91 -0
  35. package/src/collection.ts +254 -0
  36. package/src/config.ts +42 -0
  37. package/src/constants.ts +1 -0
  38. package/src/container.ts +54 -0
  39. package/src/controller.ts +127 -0
  40. package/src/decorators.ts +27 -0
  41. package/src/environment-variables.ts +46 -0
  42. package/src/exceptions/http-exceptions.ts +86 -0
  43. package/src/exceptions/index.ts +1 -0
  44. package/src/exceptions/system-exception.ts +34 -0
  45. package/src/file-storage.ts +206 -0
  46. package/src/helpers.ts +328 -0
  47. package/src/icore.ts +1140 -0
  48. package/src/index.ts +30 -0
  49. package/src/logger.ts +72 -0
  50. package/src/map-types.ts +159 -0
  51. package/src/middleware.ts +98 -0
  52. package/src/multipart.ts +116 -0
  53. package/src/openapi.ts +372 -0
  54. package/src/params.ts +111 -0
  55. package/src/queue.ts +126 -0
  56. package/src/response.ts +117 -0
  57. package/src/results.ts +30 -0
  58. package/src/route-methods.ts +186 -0
  59. package/src/swagger-schema.ts +213 -0
  60. package/src/testing.ts +220 -0
  61. package/src/types/app-builder.interface.ts +19 -0
  62. package/src/types/application.interface.ts +9 -0
  63. package/src/utils/hash.ts +5 -0
  64. package/src/utils/index.ts +2 -0
  65. package/src/utils/optional-require.ts +50 -0
  66. package/src/validation.ts +156 -0
  67. package/src/validator-extend.ts +25 -0
  68. package/dist/classToOpenapi.d.ts +0 -0
  69. package/dist/classToOpenapi.js +0 -1
  70. package/dist/render.d.ts +0 -1
  71. package/dist/render.js +0 -8
  72. package/jest.config.ts +0 -9
  73. package/tsconfig.json +0 -25
  74. /package/dist/{security.d.ts → utils/hash.d.ts} +0 -0
  75. /package/dist/{security.js → utils/hash.js} +0 -0
package/dist/icore.js CHANGED
@@ -32,29 +32,11 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __rest = (this && this.__rest) || function (s, e) {
36
- var t = {};
37
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
38
- t[p] = s[p];
39
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
40
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
41
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
42
- t[p[i]] = s[p[i]];
43
- }
44
- return t;
45
- };
46
- var __asyncValues = (this && this.__asyncValues) || function (o) {
47
- if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
48
- var m = o[Symbol.asyncIterator], i;
49
- return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
50
- function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
51
- function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
52
- };
53
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
54
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
55
37
  };
56
38
  Object.defineProperty(exports, "__esModule", { value: true });
57
- exports.Builder = exports.TestBuilder = exports.AvleonApplication = void 0;
39
+ exports.Avleon = exports.AvleonTest = exports.AvleonApplication = void 0;
58
40
  /**
59
41
  * @copyright 2024
60
42
  * @author Tareq Hossain
@@ -68,13 +50,14 @@ const path_1 = __importDefault(require("path"));
68
50
  const container_1 = __importStar(require("./container"));
69
51
  const helpers_1 = require("./helpers");
70
52
  const system_exception_1 = require("./exceptions/system-exception");
71
- const fs_1 = require("fs");
72
53
  const exceptions_1 = require("./exceptions");
73
54
  const swagger_1 = __importDefault(require("@fastify/swagger"));
74
55
  const config_1 = require("./config");
75
56
  const environment_variables_1 = require("./environment-variables");
76
57
  const cors_1 = __importDefault(require("@fastify/cors"));
77
58
  const multipart_1 = __importDefault(require("@fastify/multipart"));
59
+ const validation_1 = require("./validation");
60
+ const utils_1 = require("./utils");
78
61
  const isTsNode = process.env.TS_NODE_DEV ||
79
62
  process.env.TS_NODE_PROJECT ||
80
63
  process[Symbol.for("ts-node.register.instance")];
@@ -93,12 +76,21 @@ class AvleonApplication {
93
76
  this.controllers = [];
94
77
  this.authorizeMiddleware = undefined;
95
78
  this.dataSource = undefined;
79
+ this.isMapFeatures = false;
80
+ this.registerControllerAuto = false;
81
+ this.registerControllerPath = './src';
96
82
  this.metaCache = new Map();
97
83
  this.app = (0, fastify_1.default)();
98
84
  this.appConfig = new config_1.AppConfig();
99
- // this.app.setValidatorCompiler(() => () => true);
100
85
  }
101
86
  isTest() { }
87
+ static getApp() {
88
+ let isTestEnv = process.env.NODE_ENV == "test";
89
+ if (!AvleonApplication.instance) {
90
+ AvleonApplication.instance = new AvleonApplication();
91
+ }
92
+ return AvleonApplication.instance;
93
+ }
102
94
  static getInternalApp(buildOptions) {
103
95
  let isTestEnv = process.env.NODE_ENV == "test";
104
96
  if (!AvleonApplication.instance) {
@@ -110,7 +102,7 @@ class AvleonApplication {
110
102
  buildOptions.dataSourceOptions;
111
103
  const typeorm = require("typeorm");
112
104
  const datasource = new typeorm.DataSource(buildOptions.dataSourceOptions);
113
- typedi_1.default.set(isTestEnv ? "itestdatasource" : "idatasource", datasource);
105
+ typedi_1.default.set("idatasource", datasource);
114
106
  AvleonApplication.instance.dataSource = datasource;
115
107
  }
116
108
  return AvleonApplication.instance;
@@ -120,13 +112,20 @@ class AvleonApplication {
120
112
  return env.get("NODE_ENV") == "development";
121
113
  }
122
114
  async initSwagger(options) {
123
- const { routePrefix, logo, ui, theme, configuration } = options, restOptions = __rest(options, ["routePrefix", "logo", "ui", "theme", "configuration"]);
115
+ const { routePrefix, logo, ui, theme, configuration, ...restOptions } = options;
124
116
  this.app.register(swagger_1.default, {
125
- openapi: Object.assign({ openapi: "3.0.0" }, restOptions),
117
+ openapi: {
118
+ openapi: "3.0.0",
119
+ ...restOptions,
120
+ },
126
121
  });
127
122
  const rPrefix = routePrefix ? routePrefix : "/docs";
128
123
  if (options.ui && options.ui == "scalar") {
129
- await this.app.register(require("@scalar/fastify-api-reference"), {
124
+ const scalarPlugin = (0, utils_1.optionalRequire)("@scalar/fastify-api-reference", {
125
+ failOnMissing: true,
126
+ customMessage: 'Install "@scalar/fastify-api-reference" to enable API docs.\n\n npm install @scalar/fastify-api-reference',
127
+ });
128
+ await this.app.register(scalarPlugin, {
130
129
  routePrefix: rPrefix,
131
130
  configuration: configuration
132
131
  ? configuration
@@ -141,29 +140,38 @@ class AvleonApplication {
141
140
  });
142
141
  }
143
142
  else {
144
- await this.app.register(require("@fastify/swagger-ui"), {
143
+ const fastifySwaggerUi = (0, utils_1.optionalRequire)("@fastify/swagger-ui", {
144
+ failOnMissing: true,
145
+ customMessage: 'Install "@fastify/swagger-ui" to enable API docs.\n\n npm install @fastify/swagger-ui',
146
+ });
147
+ await this.app.register(fastifySwaggerUi, {
145
148
  logo: logo ? logo : null,
146
149
  theme: theme ? theme : {},
147
150
  routePrefix: rPrefix,
148
- configuration: {
149
- metaData: {
150
- title: "Avleon Api",
151
- ogTitle: "Avleon",
152
- },
153
- theme: "kepler",
154
- favicon: "/static/favicon.png",
155
- },
156
151
  });
157
152
  }
158
153
  }
159
154
  useCors(corsOptions = {}) {
160
155
  this.app.register(cors_1.default, corsOptions);
161
156
  }
162
- useOpenApi(ConfigClass, modifyConfig) {
163
- const openApiConfig = this.appConfig.get(ConfigClass);
157
+ useOpenApi(configOrClass, modifyConfig) {
158
+ let openApiConfig;
159
+ const isClass = (input) => {
160
+ var _a;
161
+ return (typeof input === 'function' &&
162
+ typeof input.prototype === 'object' &&
163
+ ((_a = input.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === input);
164
+ };
165
+ if (isClass(configOrClass)) {
166
+ // It's a class constructor
167
+ openApiConfig = this.appConfig.get(configOrClass);
168
+ }
169
+ else {
170
+ // It's a plain object
171
+ openApiConfig = configOrClass;
172
+ }
164
173
  if (modifyConfig) {
165
- const modifiedConfig = modifyConfig(openApiConfig);
166
- this.globalSwaggerOptions = modifiedConfig;
174
+ this.globalSwaggerOptions = modifyConfig(openApiConfig);
167
175
  }
168
176
  else {
169
177
  this.globalSwaggerOptions = openApiConfig;
@@ -171,16 +179,96 @@ class AvleonApplication {
171
179
  this.hasSwagger = true;
172
180
  }
173
181
  /**
174
- * @deprecated
175
- * Will remove in next major version
182
+ * Registers the fastify-multipart plugin with the Fastify instance.
183
+ * This enables handling of multipart/form-data requests, typically used for file uploads.
184
+ *
185
+ * @param {MultipartOptions} options - Options to configure the fastify-multipart plugin.
186
+ * @param {FastifyInstance} this.app - The Fastify instance to register the plugin with.
187
+ * @property {MultipartOptions} this.multipartOptions - Stores the provided multipart options.
188
+ * @see {@link https://github.com/fastify/fastify-multipart} for more details on available options.
176
189
  */
177
- async useSwagger(options) {
178
- this.hasSwagger = true;
179
- this.globalSwaggerOptions = options;
180
- }
181
190
  useMultipart(options) {
182
191
  this.multipartOptions = options;
183
- this.app.register(multipart_1.default, Object.assign(Object.assign({}, this.multipartOptions), { attachFieldsToBody: true }));
192
+ this.app.register(multipart_1.default, {
193
+ ...this.multipartOptions,
194
+ attachFieldsToBody: true,
195
+ });
196
+ }
197
+ /**
198
+ * Configures and initializes a TypeORM DataSource based on the provided configuration class.
199
+ * It retrieves the configuration from the application's configuration service and allows for optional modification.
200
+ * The initialized DataSource is then stored and registered within a dependency injection container.
201
+ *
202
+ * @template T - A generic type extending the `IConfig` interface, representing the configuration class.
203
+ * @template R - A generic type representing the return type of the configuration method of the `ConfigClass`.
204
+ * @param {Constructable<T>} ConfigClass - The constructor of the configuration class to be used for creating the DataSource.
205
+ * @param {(config: R) => R} [modifyConfig] - An optional function that takes the initial configuration and returns a modified configuration.
206
+ * @returns {void}
207
+ * @property {DataSourceOptions} this.dataSourceOptions - Stores the final DataSource options after potential modification.
208
+ * @property {DataSource} this.dataSource - Stores the initialized TypeORM DataSource instance.
209
+ * @see {@link https://typeorm.io/} for more information about TypeORM.
210
+ */
211
+ useDataSource(ConfigClass, modifyConfig) {
212
+ const dsConfig = this.appConfig.get(ConfigClass);
213
+ if (modifyConfig) {
214
+ const modifiedConfig = modifyConfig(dsConfig);
215
+ this.dataSourceOptions = modifiedConfig;
216
+ }
217
+ else {
218
+ this.dataSourceOptions = dsConfig;
219
+ }
220
+ const typeorm = require("typeorm");
221
+ const datasource = new typeorm.DataSource(dsConfig);
222
+ this.dataSource = datasource;
223
+ typedi_1.default.set("idatasource", datasource);
224
+ }
225
+ useCache(options) {
226
+ }
227
+ /**
228
+ * Registers an array of middleware classes to be executed before request handlers.
229
+ * It retrieves instances of the middleware classes from the dependency injection container
230
+ * and adds them as 'preHandler' hooks to the Fastify application.
231
+ *
232
+ * @template T - A generic type extending the `AppMiddleware` interface, representing the middleware class.
233
+ * @param {Constructor<T>[]} mclasses - An array of middleware class constructors to be registered.
234
+ * @returns {void}
235
+ * @property {Map<string, T>} this.middlewares - Stores the registered middleware instances, keyed by their class names.
236
+ * @see {@link https://www.fastify.io/docs/latest/Reference/Hooks/#prehandler} for more information about Fastify preHandler hooks.
237
+ */
238
+ useMiddlewares(mclasses) {
239
+ for (const mclass of mclasses) {
240
+ const cls = typedi_1.default.get(mclass);
241
+ this.middlewares.set(mclass.name, cls);
242
+ this.app.addHook("preHandler", cls.invoke);
243
+ }
244
+ }
245
+ /**
246
+ * Registers a middleware constructor to be used for authorization purposes.
247
+ * The specific implementation and usage of this middleware will depend on the application's authorization logic.
248
+ *
249
+ * @template T - A generic type representing the constructor of the authorization middleware.
250
+ * @param {Constructor<T>} middleware - The constructor of the middleware to be used for authorization.
251
+ * @returns {void}
252
+ * @property {any} this.authorizeMiddleware - Stores the constructor of the authorization middleware.
253
+ */
254
+ useAuthoriztion(middleware) {
255
+ this.authorizeMiddleware = middleware;
256
+ }
257
+ /**
258
+ * Registers the `@fastify/static` plugin to serve static files.
259
+ * It configures the root directory and URL prefix for serving static assets.
260
+ *
261
+ * @param {StaticFileOptions} [options={ path: undefined, prefix: undefined }] - Optional configuration for serving static files.
262
+ * @param {string} [options.path] - The absolute path to the static files directory. Defaults to 'process.cwd()/public'.
263
+ * @param {string} [options.prefix] - The URL prefix for serving static files. Defaults to '/static/'.
264
+ * @returns {void}
265
+ * @see {@link https://github.com/fastify/fastify-static} for more details on available options.
266
+ */
267
+ useStaticFiles(options = { path: undefined, prefix: undefined }) {
268
+ this.app.register(require("@fastify/static"), {
269
+ root: options.path ? options.path : path_1.default.join(process.cwd(), "public"),
270
+ prefix: options.prefix ? options.prefix : "/static/",
271
+ });
184
272
  }
185
273
  handleMiddlewares(mclasses) {
186
274
  for (const mclass of mclasses) {
@@ -202,7 +290,6 @@ class AvleonApplication {
202
290
  * @returns void
203
291
  */
204
292
  async buildController(controller) {
205
- var _a, e_1, _b, _c;
206
293
  const ctrl = typedi_1.default.get(controller);
207
294
  const controllerMeta = Reflect.getMetadata(container_1.CONTROLLER_META_KEY, ctrl.constructor);
208
295
  if (!controllerMeta)
@@ -212,89 +299,101 @@ class AvleonApplication {
212
299
  const tag = ctrl.constructor.name.replace("Controller", "");
213
300
  const swaggerControllerMeta = Reflect.getMetadata("controller:openapi", ctrl.constructor) || {};
214
301
  const authClsMeata = Reflect.getMetadata(container_1.AUTHORIZATION_META_KEY, ctrl.constructor) || { authorize: false, options: undefined };
215
- if (authClsMeata.authorize && this.authorizeMiddleware) {
216
- this.app.addHook("preHandler", (req, res) => {
217
- return this.authorizeMiddleware.authorize(req);
218
- });
219
- }
220
- try {
221
- for (var _d = true, methods_1 = __asyncValues(methods), methods_1_1; methods_1_1 = await methods_1.next(), _a = methods_1_1.done, !_a; _d = true) {
222
- _c = methods_1_1.value;
223
- _d = false;
224
- const method = _c;
225
- const methodMeta = Reflect.getMetadata(container_1.ROUTE_META_KEY, prototype, method);
226
- if (!methodMeta)
227
- continue;
228
- const methodmetaOptions = {
229
- method: methodMeta.method.toLowerCase(),
230
- path: (0, helpers_1.formatUrl)(controllerMeta.path + methodMeta.path),
231
- };
232
- const routeKey = `${methodmetaOptions.method}:${methodmetaOptions.path}`;
233
- if (!this.routeSet.has(routeKey)) {
234
- this.routeSet.add(routeKey);
302
+ for await (const method of methods) {
303
+ const methodMeta = Reflect.getMetadata(container_1.ROUTE_META_KEY, prototype, method);
304
+ if (!methodMeta)
305
+ continue;
306
+ const methodmetaOptions = {
307
+ method: methodMeta.method.toLowerCase(),
308
+ path: (0, helpers_1.formatUrl)(controllerMeta.path + methodMeta.path),
309
+ };
310
+ const routeKey = `${methodmetaOptions.method}:${methodmetaOptions.path}`;
311
+ if (!this.routeSet.has(routeKey)) {
312
+ this.routeSet.add(routeKey);
313
+ }
314
+ const classMiddlewares = this.executeMiddlewares(ctrl, method);
315
+ // handle openapi data
316
+ const swaggerMeta = Reflect.getMetadata("route:openapi", prototype, method) || {};
317
+ const authClsMethodMeata = Reflect.getMetadata(container_1.AUTHORIZATION_META_KEY, ctrl.constructor, method) || { authorize: false, options: undefined };
318
+ const allMeta = this._processMeta(prototype, method);
319
+ let bodySchema = null;
320
+ allMeta.body.forEach((r) => {
321
+ if (r.schema) {
322
+ bodySchema = { ...r.schema };
235
323
  }
236
- const classMiddlewares = this.executeMiddlewares(ctrl, method);
237
- // handle openapi data
238
- const swaggerMeta = Reflect.getMetadata("route:openapi", prototype, method) || {};
239
- const authClsMethodMeata = Reflect.getMetadata(container_1.AUTHORIZATION_META_KEY, ctrl.constructor, method) || { authorize: false, options: undefined };
240
- const allMeta = this._processMeta(prototype, method);
241
- let bodySchema = null;
242
- allMeta.body.forEach((r) => {
243
- if (r.schema) {
244
- bodySchema = Object.assign({}, r.schema);
324
+ });
325
+ const routePath = methodmetaOptions.path == "" ? "/" : methodmetaOptions.path;
326
+ let schema = { ...swaggerControllerMeta, ...swaggerMeta, tags: [tag] };
327
+ if (!swaggerMeta.body && bodySchema) {
328
+ schema = { ...schema, body: bodySchema };
329
+ }
330
+ this.app.route({
331
+ url: routePath,
332
+ method: methodmetaOptions.method.toUpperCase(),
333
+ schema: { ...schema },
334
+ handler: async (req, res) => {
335
+ let reqClone = req;
336
+ // class level authrization
337
+ if (authClsMeata.authorize && this.authorizeMiddleware) {
338
+ const cls = container_1.default.get(this.authorizeMiddleware);
339
+ await cls.authorize(reqClone, authClsMeata.options);
340
+ if (res.sent)
341
+ return;
245
342
  }
246
- });
247
- const routePath = methodmetaOptions.path == "" ? "/" : methodmetaOptions.path;
248
- let schema = Object.assign(Object.assign(Object.assign({}, swaggerControllerMeta), swaggerMeta), { tags: [tag] });
249
- if (!swaggerMeta.body && bodySchema) {
250
- schema = Object.assign(Object.assign({}, schema), { body: bodySchema });
251
- }
252
- this.app.route({
253
- url: routePath,
254
- method: methodmetaOptions.method.toUpperCase(),
255
- schema: Object.assign({}, schema),
256
- handler: async (req, res) => {
257
- let reqClone = req;
258
- if (authClsMethodMeata.authorize && this.authorizeMiddleware) {
259
- const cls = container_1.default.get(this.authorizeMiddleware);
260
- await cls.authorize(reqClone, authClsMethodMeata.options);
343
+ // method level authorization
344
+ if (authClsMethodMeata.authorize && this.authorizeMiddleware) {
345
+ const cls = container_1.default.get(this.authorizeMiddleware);
346
+ await cls.authorize(reqClone, authClsMethodMeata.options);
347
+ if (res.sent)
348
+ return;
349
+ }
350
+ if (classMiddlewares.length > 0) {
351
+ for (let m of classMiddlewares) {
352
+ const cls = typedi_1.default.get(m.constructor);
353
+ reqClone = (await cls.invoke(reqClone, res));
261
354
  if (res.sent)
262
355
  return;
263
356
  }
264
- if (classMiddlewares.length > 0) {
265
- for (let m of classMiddlewares) {
266
- const cls = typedi_1.default.get(m.constructor);
267
- reqClone = (await cls.invoke(reqClone, res));
268
- if (res.sent)
269
- return;
357
+ }
358
+ const args = await this._mapArgs(reqClone, allMeta);
359
+ for (let paramMeta of allMeta.params) {
360
+ if (paramMeta.required) {
361
+ (0, validation_1.validateOrThrow)({ [paramMeta.key]: args[paramMeta.index] }, { [paramMeta.key]: { type: paramMeta.dataType } }, { location: 'param' });
362
+ }
363
+ }
364
+ for (let queryMeta of allMeta.query) {
365
+ if (queryMeta.validatorClass) {
366
+ const err = await (0, helpers_1.validateObjectByInstance)(queryMeta.dataType, args[queryMeta.index]);
367
+ if (err) {
368
+ return await res.code(400).send({
369
+ code: 400,
370
+ error: "ValidationError",
371
+ errors: err,
372
+ message: err.message,
373
+ });
270
374
  }
271
375
  }
272
- const args = await this._mapArgs(reqClone, allMeta);
273
- for (let bodyMeta of allMeta.body) {
274
- if (bodyMeta.validatorClass) {
275
- const err = await (0, helpers_1.validateObjectByInstance)(bodyMeta.dataType, args[bodyMeta.index]);
276
- if (err) {
277
- return await res.code(400).send({
278
- code: 400,
279
- error: "ValidationError",
280
- errors: err,
281
- message: err.message,
282
- });
283
- }
376
+ if (queryMeta.required) {
377
+ (0, validation_1.validateOrThrow)({ [queryMeta.key]: args[queryMeta.index] }, { [queryMeta.key]: { type: queryMeta.dataType } }, { location: 'queryparam' });
378
+ }
379
+ }
380
+ for (let bodyMeta of allMeta.body) {
381
+ if (bodyMeta.validatorClass) {
382
+ const err = await (0, helpers_1.validateObjectByInstance)(bodyMeta.dataType, args[bodyMeta.index]);
383
+ if (err) {
384
+ return await res.code(400).send({
385
+ code: 400,
386
+ error: "ValidationError",
387
+ errors: err,
388
+ message: err.message,
389
+ });
284
390
  }
285
391
  }
286
- const result = await prototype[method].apply(ctrl, args);
287
- return result;
288
- },
289
- });
290
- }
291
- }
292
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
293
- finally {
294
- try {
295
- if (!_d && !_a && (_b = methods_1.return)) await _b.call(methods_1);
296
- }
297
- finally { if (e_1) throw e_1.error; }
392
+ }
393
+ const result = await prototype[method].apply(ctrl, args);
394
+ return result;
395
+ },
396
+ });
298
397
  }
299
398
  }
300
399
  /**
@@ -304,8 +403,7 @@ class AvleonApplication {
304
403
  * @returns
305
404
  */
306
405
  async _mapArgs(req, meta) {
307
- var _a, e_2, _b, _c, _d, e_3, _e, _f;
308
- var _g;
406
+ var _a;
309
407
  if (!req.hasOwnProperty("_argsCache")) {
310
408
  Object.defineProperty(req, "_argsCache", {
311
409
  value: new Map(),
@@ -319,29 +417,17 @@ class AvleonApplication {
319
417
  }
320
418
  const args = meta.params.map((p) => req.params[p.key] || null);
321
419
  meta.query.forEach((q) => (args[q.index] = q.key === "all" ? req.query : req.query[q.key]));
322
- meta.body.forEach((body) => (args[body.index] = Object.assign(Object.assign({}, req.body), req.formData)));
420
+ meta.body.forEach((body) => (args[body.index] = { ...req.body, ...req.formData }));
323
421
  meta.currentUser.forEach((user) => (args[user.index] = req.user));
324
422
  meta.headers.forEach((header) => (args[header.index] =
325
423
  header.key === "all" ? req.headers : req.headers[header.key]));
326
424
  if (meta.file) {
327
- try {
328
- for (var _h = true, _j = __asyncValues(meta.file), _k; _k = await _j.next(), _a = _k.done, !_a; _h = true) {
329
- _c = _k.value;
330
- _h = false;
331
- let f = _c;
332
- args[f.index] = await req.file();
333
- }
334
- }
335
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
336
- finally {
337
- try {
338
- if (!_h && !_a && (_b = _j.return)) await _b.call(_j);
339
- }
340
- finally { if (e_2) throw e_2.error; }
425
+ for await (let f of meta.file) {
426
+ args[f.index] = await req.file();
341
427
  }
342
428
  }
343
429
  if (meta.files &&
344
- ((_g = req.headers["content-type"]) === null || _g === void 0 ? void 0 : _g.startsWith("multipart/form-data")) === true) {
430
+ ((_a = req.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.startsWith("multipart/form-data")) === true) {
345
431
  const files = await req.saveRequestFiles();
346
432
  if (!files || files.length === 0) {
347
433
  throw new exceptions_1.BadRequestException({ error: "No files uploaded" });
@@ -355,27 +441,15 @@ class AvleonApplication {
355
441
  mimetype: file.mimetype,
356
442
  fields: file.fields,
357
443
  }));
358
- try {
359
- for (var _l = true, _m = __asyncValues(meta.files), _o; _o = await _m.next(), _d = _o.done, !_d; _l = true) {
360
- _f = _o.value;
361
- _l = false;
362
- let f = _f;
363
- const findex = fileInfo.findIndex((x) => x.fieldname == f.fieldName);
364
- if (f.fieldName != "all" && findex == -1) {
365
- throw new exceptions_1.BadRequestException(`${f.fieldName} doesn't exists in request files tree.`);
366
- }
367
- args[f.index] =
368
- f.fieldName == "all"
369
- ? fileInfo
370
- : fileInfo.filter((x) => x.fieldname == f.fieldName);
444
+ for await (let f of meta.files) {
445
+ const findex = fileInfo.findIndex((x) => x.fieldname == f.fieldName);
446
+ if (f.fieldName != "all" && findex == -1) {
447
+ throw new exceptions_1.BadRequestException(`${f.fieldName} doesn't exists in request files tree.`);
371
448
  }
372
- }
373
- catch (e_3_1) { e_3 = { error: e_3_1 }; }
374
- finally {
375
- try {
376
- if (!_l && !_d && (_e = _m.return)) await _e.call(_m);
377
- }
378
- finally { if (e_3) throw e_3.error; }
449
+ args[f.index] =
450
+ f.fieldName == "all"
451
+ ? fileInfo
452
+ : fileInfo.filter((x) => x.fieldname == f.fieldName);
379
453
  }
380
454
  }
381
455
  cache.set(cacheKey, args);
@@ -405,25 +479,59 @@ class AvleonApplication {
405
479
  this.metaCache.set(cacheKey, meta);
406
480
  return meta;
407
481
  }
408
- async autoControllers() {
409
- const controllers = [];
410
- const files = await promises_1.default.readdir(controllerDir);
482
+ _resolveControllerDir(dir) {
483
+ const isTsNode = process.env.TS_NODE_DEV ||
484
+ process.env.TS_NODE_PROJECT ||
485
+ process[Symbol.for("ts-node.register.instance")];
486
+ const controllerDir = path_1.default.join(process.cwd(), this.registerControllerPath);
487
+ return isTsNode ? controllerDir : controllerDir.replace('src', 'dist');
488
+ }
489
+ async autoControllers(controllersPath) {
490
+ const conDir = this._resolveControllerDir(controllersPath);
491
+ const files = await promises_1.default.readdir(conDir, { recursive: true });
411
492
  for (const file of files) {
493
+ const isTestFile = /\.(test|spec|e2e-spec)\.ts$/.test(file);
494
+ if (isTestFile)
495
+ continue;
412
496
  if (isTsNode ? file.endsWith(".ts") : file.endsWith(".js")) {
413
- const filePath = path_1.default.join(controllerDir, file);
497
+ const filePath = path_1.default.join(conDir, file);
414
498
  const module = await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)));
415
499
  for (const exported of Object.values(module)) {
416
500
  if (typeof exported === "function" && (0, container_1.isApiController)(exported)) {
417
- //controllers.push(exported);
418
- this.buildController(exported);
501
+ console.log('adding', exported.name);
502
+ if (!this.controllers.some(con => exported.name == con.name)) {
503
+ this.controllers.push(exported);
504
+ }
505
+ //this.buildController(exported);
419
506
  }
420
507
  }
421
508
  }
422
509
  }
423
510
  }
424
- mapControllers(controllers) {
425
- this.controllers = controllers;
511
+ useControllers(controllers) {
512
+ if (Array.isArray(controllers)) {
513
+ this.controllers = controllers;
514
+ controllers.forEach(controller => {
515
+ if (!this.controllers.includes(controller)) {
516
+ this.controllers.push(controller);
517
+ }
518
+ });
519
+ }
520
+ else {
521
+ this.registerControllerAuto = true;
522
+ if (controllers.path) {
523
+ this.registerControllerPath = controllers.path;
524
+ }
525
+ }
426
526
  }
527
+ // addFeature(feature:{controllers:Function[]}){
528
+ // feature.controllers.forEach(c=> this.controllers.push(c))
529
+ // }
530
+ // mapFeature(){
531
+ // if(!this.isMapFeatures){
532
+ // this.isMapFeatures = true;
533
+ // }
534
+ // }
427
535
  async _mapControllers() {
428
536
  if (this.controllers.length > 0) {
429
537
  for (let controller of this.controllers) {
@@ -436,28 +544,15 @@ class AvleonApplication {
436
544
  }
437
545
  }
438
546
  }
439
- mapControllersAuto() {
440
- const isExists = (0, fs_1.existsSync)(controllerDir);
441
- if (isExists) {
442
- this.autoControllers();
443
- }
444
- }
445
- async handleRoute(args) { }
547
+ // useControllersAuto(controllerPath?:string) {
548
+ // this.registerControllerAuto = true;
549
+ // //this.autoControllers();
550
+ // }
446
551
  async mapFn(fn) {
447
552
  const original = fn;
448
553
  fn = function () { };
449
554
  return fn;
450
555
  }
451
- useMiddlewares(mclasses) {
452
- for (const mclass of mclasses) {
453
- const cls = typedi_1.default.get(mclass);
454
- this.middlewares.set(mclass.name, cls);
455
- this.app.addHook("preHandler", cls.invoke);
456
- }
457
- }
458
- useAuthoriztion(middleware) {
459
- this.authorizeMiddleware = middleware;
460
- }
461
556
  _handleError(error) {
462
557
  if (error instanceof exceptions_1.BaseHttpException) {
463
558
  return {
@@ -501,7 +596,7 @@ class AvleonApplication {
501
596
  middlewares: [],
502
597
  schema: {},
503
598
  });
504
- this.mapFn(fn);
599
+ // this.mapFn(fn);
505
600
  const route = {
506
601
  useMiddleware: (middlewares) => {
507
602
  const midds = Array.isArray(middlewares) ? middlewares : [middlewares];
@@ -516,7 +611,7 @@ class AvleonApplication {
516
611
  }
517
612
  return route;
518
613
  },
519
- useSwagger: (options) => {
614
+ useOpenApi: (options) => {
520
615
  const r = this.rMap.get(routeKey);
521
616
  if (r) {
522
617
  r.schema = options;
@@ -538,18 +633,16 @@ class AvleonApplication {
538
633
  mapDelete(path = "", fn) {
539
634
  return this._routeHandler(path, "DELETE", fn);
540
635
  }
541
- useStaticFiles(options = { path: undefined, prefix: undefined }) {
542
- this.app.register(require("@fastify/static"), {
543
- root: options.path ? options.path : path_1.default.join(process.cwd(), "public"),
544
- prefix: options.prefix ? options.prefix : "/static/",
545
- });
636
+ _mapFeatures() {
637
+ const features = typedi_1.default.get('features');
638
+ console.log('Features', features);
546
639
  }
547
640
  async initializeDatabase() {
548
641
  if (this.dataSourceOptions && this.dataSource) {
549
642
  await this.dataSource.initialize();
550
643
  }
551
644
  }
552
- async run(port = 4000) {
645
+ async run(port = 4000, fn) {
553
646
  if (this.alreadyRun)
554
647
  throw new system_exception_1.SystemUseError("App already running");
555
648
  this.alreadyRun = true;
@@ -557,6 +650,12 @@ class AvleonApplication {
557
650
  await this.initSwagger(this.globalSwaggerOptions);
558
651
  }
559
652
  await this.initializeDatabase();
653
+ if (this.isMapFeatures) {
654
+ this._mapFeatures();
655
+ }
656
+ if (this.registerControllerAuto) {
657
+ await this.autoControllers();
658
+ }
560
659
  await this._mapControllers();
561
660
  this.rMap.forEach((value, key) => {
562
661
  const [m, r] = key.split(":");
@@ -573,7 +672,7 @@ class AvleonApplication {
573
672
  });
574
673
  this.app.setErrorHandler(async (error, req, res) => {
575
674
  const handledErr = this._handleError(error);
576
- if (error instanceof exceptions_1.ValidationErrorException) {
675
+ if (error instanceof exceptions_1.ValidationErrorException || error instanceof exceptions_1.BadRequestException) {
577
676
  return res.status(handledErr.code).send({
578
677
  code: handledErr.code,
579
678
  error: handledErr.error,
@@ -618,13 +717,17 @@ class AvleonApplication {
618
717
  // return this.app as any;
619
718
  //
620
719
  return {
621
- get: async (url, options) => this.app.inject(Object.assign({ method: "GET", url }, options)),
622
- post: async (url, options) => this.app.inject(Object.assign({ method: "POST", url }, options)),
623
- put: async (url, options) => this.app.inject(Object.assign({ method: "PUT", url }, options)),
624
- patch: async (url, options) => this.app.inject(Object.assign({ method: "PATCH", url }, options)),
625
- delete: async (url, options) => this.app.inject(Object.assign({ method: "DELETE", url }, options)),
626
- options: async (url, options) => this.app.inject(Object.assign({ method: "OPTIONS", url }, options)),
627
- getController: (controller) => {
720
+ get: async (url, options) => this.app.inject({ method: "GET", url, ...options }),
721
+ post: async (url, options) => this.app.inject({ method: "POST", url, ...options }),
722
+ put: async (url, options) => this.app.inject({ method: "PUT", url, ...options }),
723
+ patch: async (url, options) => this.app.inject({ method: "PATCH", url, ...options }),
724
+ delete: async (url, options) => this.app.inject({ method: "DELETE", url, ...options }),
725
+ options: async (url, options) => this.app.inject({ method: "OPTIONS", url, ...options }),
726
+ getController: (controller, deps = []) => {
727
+ const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
728
+ deps.forEach((dep, i) => {
729
+ typedi_1.default.set(paramTypes[i], dep);
730
+ });
628
731
  return typedi_1.default.get(controller);
629
732
  },
630
733
  };
@@ -637,77 +740,42 @@ class AvleonApplication {
637
740
  }
638
741
  exports.AvleonApplication = AvleonApplication;
639
742
  AvleonApplication.buildOptions = {};
640
- class TestBuilder {
743
+ class AvleonTest {
641
744
  constructor() {
642
745
  process.env.NODE_ENV = "test";
643
746
  }
644
- static createBuilder() {
645
- if (!TestBuilder.instance) {
646
- TestBuilder.instance = new TestBuilder();
647
- }
648
- return TestBuilder.instance;
649
- }
650
747
  addDatasource(options) {
651
748
  this.dataSourceOptions = options;
652
749
  }
653
- getController(controller) {
750
+ getController(controller, deps = []) {
751
+ const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
752
+ deps.forEach((dep, i) => {
753
+ typedi_1.default.set(paramTypes[i], dep);
754
+ });
654
755
  return typedi_1.default.get(controller);
655
756
  }
656
757
  getService(service) {
657
758
  return typedi_1.default.get(service);
658
759
  }
659
- getTestApplication(options) {
760
+ static createTestApplication(options) {
660
761
  const app = AvleonApplication.getInternalApp({
661
- dataSourceOptions: this.dataSourceOptions,
762
+ dataSourceOptions: options.dataSource ? options.dataSource : null,
662
763
  });
663
- app.mapControllers([...options.controllers]);
764
+ app.useControllers([...options.controllers]);
664
765
  return app.getTestApp();
665
766
  }
666
- build(app) {
767
+ static from(app) {
667
768
  return app.getTestApp();
668
769
  }
669
- fromApplication(app) {
670
- return app.getTestApp();
770
+ static clean() {
771
+ typedi_1.default.reset();
671
772
  }
672
773
  }
673
- exports.TestBuilder = TestBuilder;
674
- class Builder {
675
- constructor() {
676
- this.alreadyBuilt = false;
677
- this.database = false;
678
- this.testBuilder = false;
679
- this.appConfig = new config_1.AppConfig();
680
- }
681
- static createAppBuilder() {
682
- if (!Builder.instance) {
683
- Builder.instance = new Builder();
684
- }
685
- return Builder.instance;
686
- }
687
- async registerPlugin(plugin, options) {
688
- container_1.default.set(plugin, plugin.prototype);
689
- }
690
- addDataSource(ConfigClass, modifyConfig) {
691
- const openApiConfig = this.appConfig.get(ConfigClass);
692
- if (modifyConfig) {
693
- const modifiedConfig = modifyConfig(openApiConfig);
694
- this.dataSourceOptions = modifiedConfig;
695
- }
696
- else {
697
- this.dataSourceOptions = openApiConfig;
698
- }
699
- }
700
- build() {
701
- if (this.alreadyBuilt) {
702
- throw new Error("Already built");
703
- }
704
- this.alreadyBuilt = true;
705
- const app = AvleonApplication.getInternalApp({
706
- database: this.database,
707
- multipartOptions: this.multipartOptions,
708
- dataSourceOptions: this.dataSourceOptions,
709
- });
774
+ exports.AvleonTest = AvleonTest;
775
+ class Avleon {
776
+ static createApplication() {
777
+ const app = AvleonApplication.getApp();
710
778
  return app;
711
779
  }
712
780
  }
713
- exports.Builder = Builder;
781
+ exports.Avleon = Avleon;