5htp-core 0.6.2 → 0.6.3-2

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 (38) hide show
  1. package/client/app/index.ts +2 -1
  2. package/client/assets/css/components/table.less +1 -0
  3. package/client/components/Input.tsx +0 -2
  4. package/client/components/Rte/Editor.tsx +2 -0
  5. package/client/components/Rte/index.tsx +0 -1
  6. package/client/services/router/index.tsx +1 -1
  7. package/client/services/router/request/api.ts +26 -52
  8. package/common/data/dates.ts +3 -0
  9. package/common/router/request/api.ts +0 -8
  10. package/package.json +1 -1
  11. package/server/app/container/config.ts +43 -4
  12. package/server/app/container/console/index.ts +66 -49
  13. package/server/app/index.ts +19 -8
  14. package/server/app/service/index.ts +55 -15
  15. package/server/services/auth/router/index.ts +8 -4
  16. package/server/services/database/connection.ts +33 -19
  17. package/server/services/disks/driver.ts +5 -1
  18. package/server/services/disks/drivers/s3/index.ts +2 -2
  19. package/server/services/disks/index.ts +10 -5
  20. package/server/services/email/index.ts +1 -1
  21. package/server/services/prisma/Facet.ts +39 -15
  22. package/server/services/prisma/index.ts +5 -7
  23. package/server/services/router/http/multipart.ts +5 -0
  24. package/server/services/router/index.ts +50 -35
  25. package/server/services/router/request/api.ts +0 -12
  26. package/server/services/router/request/validation/zod.ts +180 -0
  27. package/server/services/router/response/index.ts +19 -10
  28. package/server/services/router/response/page/document.tsx +5 -3
  29. package/server/services/router/service.ts +7 -4
  30. package/server/services/schema/request.ts +21 -34
  31. package/server/services/schema/router/index.ts +3 -3
  32. package/types/global/utils.d.ts +22 -4
  33. package/types/icons.d.ts +1 -1
  34. package/common/data/input/validate.ts +0 -54
  35. package/server/services/router/request/validation/index.ts +0 -23
  36. package/server/services/router/request/validation/schema.ts +0 -211
  37. package/server/services/router/request/validation/validator.ts +0 -117
  38. package/server/services/router/request/validation/validators.ts +0 -485
@@ -21,7 +21,8 @@ import type { AnyService } from './service';
21
21
  export { default as Service } from './service';
22
22
 
23
23
  // Resources
24
- import '@client/assets/css/core.less';
24
+ //import '@client/assets/css/core.less';
25
+ //import '@mantine/core/styles.css';
25
26
 
26
27
  /*----------------------------------
27
28
  - TYPES
@@ -1,6 +1,7 @@
1
1
  .table {
2
2
 
3
3
  overflow: auto;
4
+ display: block!important;
4
5
 
5
6
  &.scrollable {
6
7
  max-height: 90vh;
@@ -13,8 +13,6 @@ import {
13
13
 
14
14
  // Core libs
15
15
  import { InputBaseProps, useMantineInput } from './utils';
16
- import { default as Validator } from '../../server/services/router/request/validation/validator';
17
- import type { SchemaValidators } from '@server/services/router/request/validation/validators';
18
16
 
19
17
  /*----------------------------------
20
18
  - TYPES
@@ -81,6 +81,8 @@ import ToolbarPlugin from './ToolbarPlugin';
81
81
 
82
82
  export const EMPTY_STATE = '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}';
83
83
 
84
+ import './style.less';
85
+
84
86
  /*----------------------------------
85
87
  - TYPES
86
88
  ----------------------------------*/
@@ -11,7 +11,6 @@ import type { TToolbarDisplay } from './ToolbarPlugin';
11
11
 
12
12
  // Special componets
13
13
  import type TEditor from './Editor';
14
- import './style.less';
15
14
 
16
15
  /*----------------------------------
17
16
  - TYPES
@@ -17,7 +17,7 @@ import type { TBasicSSrData } from '@server/services/router/response';
17
17
  import BaseRouter, {
18
18
  defaultOptions, TRoute, TErrorRoute,
19
19
  TClientOrServerContextForPage, TRouteModule,
20
- matchRoute, buildUrl, TDomainsList
20
+ matchRoute, buildUrl
21
21
  } from '@common/router'
22
22
  import { getLayout } from '@common/router/layouts';
23
23
  import { getRegisterPageArgs, buildRegex } from '@common/router/register';
@@ -50,18 +50,9 @@ export default class ApiClient implements ApiClientService {
50
50
  throw new Error("api.fetch shouldn't be called here.");
51
51
  }
52
52
 
53
- public get = <TData extends unknown = unknown>(path: string, data?: TPostData, opts?: TApiFetchOptions) =>
54
- this.createFetcher<TData>('GET', path, data, opts);
55
-
56
53
  public post = <TData extends unknown = unknown>(path: string, data?: TPostData, opts?: TApiFetchOptions) =>
57
54
  this.createFetcher<TData>('POST', path, data, opts);
58
55
 
59
- public put = <TData extends unknown = unknown>(path: string, data?: TPostData, opts?: TApiFetchOptions) =>
60
- this.createFetcher<TData>('PUT', path, data, opts);
61
-
62
- public delete = <TData extends unknown = unknown>(path: string, data?: TPostData, opts?: TApiFetchOptions) =>
63
- this.createFetcher<TData>('DELETE', path, data, opts);
64
-
65
56
  public set( newData: TObjetDonnees ) {
66
57
 
67
58
  if (!('context' in this.router))
@@ -111,51 +102,34 @@ export default class ApiClient implements ApiClientService {
111
102
  ----------------------------------*/
112
103
  public createFetcher<TData extends unknown = unknown>(...args: TFetcherArgs): TFetcher<TData> {
113
104
  const [method, path, data, options] = args;
114
- return {
105
+
106
+ // Lazily create (and cache) the underlying promise so the fetcher behaves like a real promise instance.
107
+ let promise: Promise<TData> | undefined;
108
+
109
+ const fetcher = {
115
110
  method, path, data, options,
111
+ } as TFetcher<TData>;
112
+
113
+ const getPromise = () => {
114
+ if (!promise)
115
+ promise = this.fetchAsync<TData>(fetcher.method, fetcher.path, fetcher.data, fetcher.options);
116
116
 
117
- // For async calls: api.post(...).then((data) => ...)
118
- then: (callback: (data: any) => void) => this.fetchAsync<TData>(...args)
119
- .then(callback)
120
- .catch( e => {
121
- this.app.handleError(e);
122
-
123
- // Don't run what is next
124
- return {
125
- then: () => {},
126
- catch: () => {},
127
- finally: (callback: () => void) => {
128
- callback();
129
- },
130
- }
131
- }),
132
-
133
- // Default error behavior only if not handled before by the app
134
- catch: (callback: (data: any) => false | void) => this.fetchAsync<TData>(...args)
135
- .catch((e) => {
136
-
137
- const shouldThrow = callback(e);
138
- if (shouldThrow)
139
- this.app.handleError(e);
140
-
141
- // Don't run what is next
142
- return {
143
- then: () => {},
144
- catch: () => {},
145
- finally: (callback: () => void) => {
146
- callback();
147
- },
148
- }
149
-
150
- }),
151
-
152
- finally: (callback: () => void) => this.fetchAsync<TData>(...args)
153
- .finally(callback)
154
- .catch( e => this.app.handleError(e)),
155
-
156
- run: () => this.fetchAsync<TData>(...args)
157
- .catch( e => this.app.handleError(e)),
117
+ return promise;
158
118
  };
119
+
120
+ // For async calls: api.post(...).then((data) => ...)
121
+ fetcher.then = (onfulfilled?: any, onrejected?: any) =>
122
+ getPromise().then(onfulfilled, onrejected) as any;
123
+
124
+ fetcher.catch = (onrejected?: any) =>
125
+ getPromise().catch(onrejected) as any;
126
+
127
+ fetcher.finally = (onfinally?: any) =>
128
+ getPromise().finally(onfinally) as any;
129
+
130
+ fetcher.run = () => getPromise();
131
+
132
+ return fetcher;
159
133
  }
160
134
 
161
135
  public async fetchAsync<TData extends unknown = unknown>(...[
@@ -290,4 +264,4 @@ export default class ApiClient implements ApiClientService {
290
264
  });
291
265
  }
292
266
 
293
- }
267
+ }
@@ -134,6 +134,9 @@ const units: {[name: string]: TUnit} = {
134
134
 
135
135
  export function ago(date: Date | string, { min, max }: { min?: string, max?: string } = {}): string {
136
136
 
137
+ if (!date)
138
+ return '-';
139
+
137
140
  if (typeof date === 'string')
138
141
  date = new Date(date);
139
142
 
@@ -63,14 +63,6 @@ export default abstract class ApiClient {
63
63
  - TOP LEVEL
64
64
  ----------------------------------*/
65
65
 
66
- public abstract get<TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions): TFetcher<TData>;
67
-
68
- public abstract post<TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions): TFetcher<TData>;
69
-
70
- public abstract put<TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions): TFetcher<TData>;
71
-
72
- public abstract delete<TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions): TFetcher<TData>;
73
-
74
66
  public abstract set( newData: TObjetDonnees );
75
67
 
76
68
  public abstract reload( ids?: string | string[], params?: TObjetDonnees );
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "5htp-core",
3
3
  "description": "Convenient TypeScript framework designed for Performance and Productivity.",
4
- "version": "0.6.2",
4
+ "version": "0.6.3-2",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -12,7 +12,8 @@ import fs from 'fs-extra';
12
12
  import yaml from 'yaml';
13
13
 
14
14
  // Types
15
- import type { Config as TConsoleConfig } from './console';
15
+ import type { TDomainsList } from '@common/router';
16
+ import type { TLogProfile } from './console';
16
17
 
17
18
  /*----------------------------------
18
19
  - TYPES
@@ -29,12 +30,50 @@ declare global {
29
30
  }
30
31
  }
31
32
 
33
+ /*
34
+ name: server
35
+ profile: prod
36
+
37
+ router:
38
+ port: 80
39
+ domains:
40
+ current: 'https://recruiters.becrosspath.com'
41
+ recruiters: 'https://recruiters.becrosspath.com'
42
+ landing: 'https://becrosspath.com'
43
+ employers: 'https://employers.becrosspath.com'
44
+ candidates: 'https://candidates.becrosspath.com'
45
+ csm: 'https://csm.becrosspath.com'
46
+
47
+ database:
48
+ name: 'aws'
49
+ databases: [railway]
50
+ host: 'mysql-z7vp.railway.internal'
51
+ port: 3306
52
+ login: root
53
+ password: "GMnVsczoyYkyzwvVqDkMUOAIjVsumEev"
54
+
55
+ console:
56
+ enable: false
57
+ debug: false
58
+ bufferLimit: 10000
59
+ level: 'log'
60
+ */
61
+
32
62
  export type TEnvName = TEnvConfig["name"];
33
63
  export type TEnvConfig = {
34
64
  name: 'local' | 'server',
35
- profile: 'dev' | 'prod',
36
- version: string,
37
- console: TConsoleConfig
65
+ profile: 'dev' | 'testing' | 'prod',
66
+
67
+ router: {
68
+ port: number,
69
+ domains: TDomainsList
70
+ },
71
+ console: {
72
+ enable: boolean,
73
+ debug: boolean,
74
+ bufferLimit: number,
75
+ level: TLogProfile,
76
+ },
38
77
  }
39
78
 
40
79
  type AppIdentityConfig = {
@@ -27,7 +27,7 @@ import { SqlError } from '@server/services/database/debug';
27
27
  - SERVICE CONFIG
28
28
  ----------------------------------*/
29
29
 
30
- type TLogProfile = 'silly' | 'info' | 'warn' | 'error'
30
+ export type TLogProfile = 'silly' | 'info' | 'warn' | 'error'
31
31
 
32
32
  export type Config = {
33
33
  debug?: boolean,
@@ -197,9 +197,14 @@ export default class Console {
197
197
  logErrors: string[],
198
198
  settings: ISettings<ILogObj>
199
199
  ) => {
200
- const logErrorsStr = (logErrors.length > 0 && logArgs.length > 0 ? "\n" : "") + logErrors.join("\n");
201
- settings.prettyInspectOptions.colors = settings.stylePrettyLogs;
202
- origLog(logMetaMarkup + formatWithOptions(settings.prettyInspectOptions, ...logArgs) + logErrorsStr);
200
+ try {
201
+ const logErrorsStr = (logErrors.length > 0 && logArgs.length > 0 ? "\n" : "") + logErrors.join("\n");
202
+ settings.prettyInspectOptions = settings.prettyInspectOptions || {};
203
+ settings.prettyInspectOptions.colors = settings.stylePrettyLogs;
204
+ origLog(logMetaMarkup + formatWithOptions(settings.prettyInspectOptions, ...logArgs) + logErrorsStr);
205
+ } catch (error) {
206
+ origLog("Error formatting log", error);
207
+ }
203
208
  },
204
209
  }
205
210
  });
@@ -301,45 +306,10 @@ export default class Console {
301
306
  // On envoi l'email avant l'insertion dans bla bdd
302
307
  // Car cette denrière a plus de chances de provoquer une erreur
303
308
  //const logs = this.logs.filter(e => e.channel.channelId === channelId).slice(-100);
304
- const stacktraces: string[] = [];
305
- const context: object[] = [];
306
-
307
- let currentError: TCatchedError | undefined = error;
308
- let title: string | undefined;
309
- while (currentError !== undefined) {
310
-
311
- if (title === undefined)
312
- title = currentError.message;
313
-
314
- // Stacktrace
315
- this.logger.error(LogPrefix, `Sending bug report for the following error:`, currentError);
316
- stacktraces.push(currentError.stack || currentError.message);
317
-
318
- // Context
319
- if (('dataForDebugging' in currentError) && currentError.dataForDebugging !== undefined) {
320
- console.error(LogPrefix, `More data about the error:`, currentError.dataForDebugging);
321
- context.push(currentError.dataForDebugging || {});
322
- }
323
-
324
- // Print the error so it's accessible via logs
325
- if (currentError instanceof SqlError) {
326
- let printedQuery: string;
327
- try {
328
- printedQuery = this.printSql( currentError.query );
329
- } catch (error) {
330
- printedQuery = 'Failed to print query:' + (error || 'unknown error');
331
- }
332
- console.error(`Error caused by this query:`, printedQuery);
333
- }
334
-
335
- // Go deeper
336
- currentError = 'originalError' in currentError
337
- ? currentError.originalError
338
- : undefined
339
- }
309
+ const inspection = this.getDetailledError(error);
340
310
 
341
311
  // Genertae unique error hash
342
- const hash = md5( stacktraces.join('\n') );
312
+ const hash = md5( inspection.stacktraces[0] );
343
313
 
344
314
  // Don't send the same error twice in a row (avoid email spamming)
345
315
  const lastReport = this.reported[hash];
@@ -390,12 +360,56 @@ export default class Console {
390
360
  } : {}),
391
361
 
392
362
  // Error
393
- title,
394
- stacktraces,
395
- context
363
+ title: inspection.title,
364
+ stacktraces: inspection.stacktraces,
365
+ context: inspection.context
396
366
  }
397
367
 
398
368
  await application.runHook('bug', bugReport);
369
+
370
+ return bugReport;
371
+ }
372
+
373
+ public getDetailledError( error: TCatchedError ) {
374
+
375
+ const stacktraces: string[] = [];
376
+ const context: object[] = [];
377
+
378
+ let currentError: TCatchedError | undefined = error;
379
+ let title: string | undefined;
380
+ while (currentError !== undefined) {
381
+
382
+ if (title === undefined)
383
+ title = currentError.message;
384
+
385
+ // Stacktrace
386
+ this.logger.error(LogPrefix, `Sending bug report for the following error:`, currentError);
387
+ stacktraces.push(currentError.stack || currentError.message);
388
+
389
+ // Context
390
+ if (('dataForDebugging' in currentError) && currentError.dataForDebugging !== undefined) {
391
+ console.error(LogPrefix, `More data about the error:`, currentError.dataForDebugging);
392
+ context.push(currentError.dataForDebugging || {});
393
+ }
394
+
395
+ // Print the error so it's accessible via logs
396
+ if (currentError instanceof SqlError) {
397
+ let printedQuery: string;
398
+ try {
399
+ printedQuery = this.printSql( currentError.query );
400
+ } catch (error) {
401
+ printedQuery = 'Failed to print query:' + (error || 'unknown error');
402
+ }
403
+ console.error(`Error caused by this query:`, printedQuery);
404
+ }
405
+
406
+ // Go deeper
407
+ currentError = 'originalError' in currentError
408
+ ? currentError.originalError
409
+ : undefined
410
+ }
411
+
412
+ return { title, stacktraces, context };
399
413
  }
400
414
 
401
415
  public getChannel() {
@@ -416,11 +430,7 @@ export default class Console {
416
430
  <b>User</b>: ${report.user ? (report.user.name + ' (' + report.user.email + ')') : 'Unknown'}<br />
417
431
  <b>IP</b>: ${report.ip}<br />
418
432
 
419
- ${report.stacktraces.map((stacktrace, index) => `
420
- <hr />
421
- <b>Error ${index + 1}</b>:
422
- ${this.printHtml(stacktrace)}<br />
423
- `).join('')}
433
+ ${this.stacktracesToHTML(report.stacktraces)}
424
434
 
425
435
  ${report.context.map((context, index) => `
426
436
  <hr />
@@ -439,6 +449,13 @@ ${report.request ? `
439
449
  Logs: ${this.config.enable ? `<br/>` + this.logsToHTML(report.logs) : 'Logs collection is disabled'}<br />
440
450
  `
441
451
  }
452
+
453
+ public stacktracesToHTML( stacktraces: string[] ): string {
454
+ return stacktraces.map((stacktrace, index) => `
455
+ <hr />
456
+ <b>Stacktrace ${index + 1}</b>: ${this.printHtml(stacktrace)}<br />
457
+ `).join('');
458
+ }
442
459
 
443
460
  public logsToHTML( logs: TJsonLog[] ): string {
444
461
 
@@ -17,6 +17,7 @@ import ServicesContainer, {
17
17
  // Built-in
18
18
  import type { default as Router, Request as ServerRequest, TRoute } from '@server/services/router';
19
19
  import { Anomaly } from '@common/errors';
20
+ import { preprocessSchema } from '@server/services/router/request/validation/zod';
20
21
 
21
22
  export { default as Services } from './service/container';
22
23
  export type { TEnvConfig as Environment } from './container/config';
@@ -105,7 +106,10 @@ export abstract class Application<
105
106
  this.on('error', (e, request) => this.container.handleBug(e, "An error occured in the application", request));
106
107
 
107
108
  process.on('unhandledRejection', (error: any, promise: any) => {
108
- console.log("unhandledRejection");
109
+
110
+ // Log so we know it's coming from unhandledRejection
111
+ console.error("unhandledRejection", error);
112
+
109
113
  // We don't log the error here because it's the role of the app to decidehiw to log errors
110
114
  this.runHook('error', error);
111
115
  });
@@ -147,7 +151,9 @@ export abstract class Application<
147
151
  console.log('----------------------------------');
148
152
  console.log('- SERVICES');
149
153
  console.log('----------------------------------');
150
- await this.ready();
154
+ const startingServices = await this.ready();
155
+ await Promise.all(startingServices);
156
+ console.log('All services are ready');
151
157
  await this.runHook('ready');
152
158
 
153
159
  const startedTime = (Date.now() - startTime) / 1000;
@@ -176,12 +182,14 @@ export abstract class Application<
176
182
 
177
183
  public register( service: AnyService ) {
178
184
 
179
- service.ready();
185
+ return service.ready();
180
186
 
181
187
  }
182
188
 
183
189
  protected async ready() {
184
190
 
191
+ const startingServices: Promise<any>[] = [];
192
+
185
193
  // Print services
186
194
  const processService = async (propKey: string, service: AnyService, level: number = 0) => {
187
195
 
@@ -190,7 +198,8 @@ export abstract class Application<
190
198
 
191
199
  // Services start shouldn't block app boot
192
200
  // use await ServiceName.started to make services depends on each other
193
- this.starting = service.ready();
201
+ service.starting = service.ready();
202
+ startingServices.push(service.starting);
194
203
  service.status = 'running';
195
204
  console.log('-' + '-'.repeat(level * 1), propKey + ': ' + service.constructor.name);
196
205
 
@@ -200,16 +209,16 @@ export abstract class Application<
200
209
 
201
210
  console.log('Attached service', service.constructor.name, 'to route', route.path);
202
211
 
212
+ const preprocessedSchema = route.schema ? preprocessSchema(route.schema) : undefined;
213
+
203
214
  const origController = route.controller;
204
215
  route.controller = (context: RouterContext) => {
205
216
 
206
217
  // Filter data
207
- const data = route.schema
208
- ? route.schema.parse( context.request.data )
218
+ const data = preprocessedSchema
219
+ ? preprocessedSchema.parse( context.request.data )
209
220
  : {};
210
221
 
211
- console.log('-----data', data);
212
-
213
222
  // Run controller
214
223
  return origController.bind( service )(
215
224
  data,
@@ -252,6 +261,8 @@ export abstract class Application<
252
261
  // Services start shouldn't block app boot
253
262
  processService(serviceId, service);
254
263
  }
264
+
265
+ return startingServices;
255
266
  }
256
267
 
257
268
  }
@@ -2,6 +2,9 @@
2
2
  - DEPENDANCES
3
3
  ----------------------------------*/
4
4
 
5
+ // Npm
6
+ import zod from 'zod';
7
+
5
8
  // Specific
6
9
  import type { Application } from "..";
7
10
  import type { Command } from "../commands";
@@ -9,7 +12,8 @@ import type { TServiceMetas } from './container';
9
12
  import type { TControllerDefinition, TRoute } from '../../services/router';
10
13
  import { Anomaly } from "@common/errors";
11
14
 
12
- export { default as schema } from 'zod';
15
+ export { schema } from '../../services/router/request/validation/zod';
16
+ export type { z } from '../../services/router/request/validation/zod';
13
17
 
14
18
  /*----------------------------------
15
19
  - TYPES: OPTIONS
@@ -42,7 +46,7 @@ export type StartedServicesIndex = {
42
46
 
43
47
  export type TServiceArgs<TService extends AnyService> = [
44
48
  parent: AnyService | 'self',
45
- getConfig: null | undefined | ((instance: TService) => {}),
49
+ config: null | undefined | TService['config'],
46
50
  app: TService['app'] | 'self'
47
51
  ]
48
52
 
@@ -52,7 +56,30 @@ export type TServiceArgs<TService extends AnyService> = [
52
56
 
53
57
  const LogPrefix = '[service]';
54
58
 
55
- export function Route(options: Omit<TControllerDefinition, 'controller'> = {}) {
59
+ type TDecoratorArgs = (
60
+ [path: string] |
61
+ [path: string, schema: zod.ZodSchema] |
62
+ [path: string, schema: zod.ZodSchema, options?: Omit<TControllerDefinition, 'controller'|'schema'|'path'>] |
63
+ [options: Omit<TControllerDefinition, 'controller'>]
64
+ )
65
+
66
+ export function Route( ...args: TDecoratorArgs ) {
67
+
68
+ let path: string | undefined;
69
+ let schema: zod.ZodSchema | undefined;
70
+ let options: Omit<TControllerDefinition, 'controller'|'schema'|'path'> = {};
71
+
72
+ if (typeof args[0] === 'object') {
73
+ const { path: path_, schema: schema_, ...options_ } = args[0];
74
+ path = path_;
75
+ schema = schema_;
76
+ options = options_;
77
+ } else {
78
+ path = args[0];
79
+ schema = args[1];
80
+ options = args[2] || {};
81
+ }
82
+
56
83
  return function (
57
84
  target: any,
58
85
  propertyKey: string,
@@ -61,8 +88,8 @@ export function Route(options: Omit<TControllerDefinition, 'controller'> = {}) {
61
88
  // Store the original method
62
89
  const originalMethod = descriptor.value;
63
90
 
64
- if (options.path === undefined)
65
- options.path = target.constructor.name + '/' + propertyKey;
91
+ if (path === undefined)
92
+ path = target.constructor.name + '/' + propertyKey;
66
93
 
67
94
  // Ensure the class has a static property to collect routes
68
95
  if (!target.__routes) {
@@ -72,9 +99,9 @@ export function Route(options: Omit<TControllerDefinition, 'controller'> = {}) {
72
99
  // Create route object
73
100
  const route: TRoute = {
74
101
  method: 'POST',
75
- path: '/api/' + options.path,
102
+ path: '/api/' + path,
76
103
  controller: originalMethod,
77
- schema: options.schema,
104
+ schema: schema,
78
105
  options: {
79
106
  priority: options.priority || 0
80
107
  }
@@ -95,7 +122,7 @@ export default abstract class Service<
95
122
  TConfig extends {},
96
123
  THooks extends THooksList,
97
124
  TApplication extends Application,
98
- TParent extends AnyService | Application = Application
125
+ TParent extends AnyService
99
126
  > {
100
127
 
101
128
  public started?: Promise<void>;
@@ -109,18 +136,17 @@ export default abstract class Service<
109
136
  public app: TApplication;
110
137
  public config: TConfig = {} as TConfig;
111
138
 
112
- public constructor(...[parent, getConfig, app]: TServiceArgs<AnyService>) {
139
+ public constructor(...[parent, config, app]: TServiceArgs<AnyService>) {
113
140
 
114
141
  this.parent = parent;
115
142
  if (this.parent === 'self')
116
- this.parent = this;
143
+ this.parent = this as unknown as TParent;
117
144
 
118
145
  this.app = app === 'self'
119
146
  ? this as unknown as TApplication
120
147
  : app
121
148
 
122
- if (typeof getConfig === 'function')
123
- this.config = getConfig(this);
149
+ this.config = config || {};
124
150
 
125
151
  }
126
152
 
@@ -144,13 +170,16 @@ export default abstract class Service<
144
170
  public use<TService extends AnyService = AnyService>(
145
171
  serviceId: string,
146
172
  useOptions: { optional?: boolean } = {}
147
- ): TService {
173
+ ): TService | undefined {
148
174
 
149
175
  const registeredService = this.app.registered[serviceId];
150
- if (registeredService === undefined && useOptions.optional === false)
176
+ if (registeredService !== undefined)
177
+ return this.app[ registeredService.name ];
178
+
179
+ if (useOptions.optional === false)
151
180
  throw new Error(`Service ${registeredService} not registered.`);
152
181
 
153
- return this.app[ registeredService.name ];
182
+ return undefined;
154
183
  }
155
184
 
156
185
  /*----------------------------------
@@ -189,6 +218,17 @@ export default abstract class Service<
189
218
  )
190
219
  ).then(() => {
191
220
  //this.config.debug && console.info(`[hook] Hooks ${name} executed with success.`);
221
+ }).catch(e => {
222
+ if (name === 'error') {
223
+
224
+ // In error hook = avoid infinite loop
225
+ console.error("Error hook", e);
226
+
227
+ } else {
228
+
229
+ // Let the error hook handle it
230
+ throw e;
231
+ }
192
232
  })
193
233
  }
194
234