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.
- package/client/app/index.ts +2 -1
- package/client/assets/css/components/table.less +1 -0
- package/client/components/Input.tsx +0 -2
- package/client/components/Rte/Editor.tsx +2 -0
- package/client/components/Rte/index.tsx +0 -1
- package/client/services/router/index.tsx +1 -1
- package/client/services/router/request/api.ts +26 -52
- package/common/data/dates.ts +3 -0
- package/common/router/request/api.ts +0 -8
- package/package.json +1 -1
- package/server/app/container/config.ts +43 -4
- package/server/app/container/console/index.ts +66 -49
- package/server/app/index.ts +19 -8
- package/server/app/service/index.ts +55 -15
- package/server/services/auth/router/index.ts +8 -4
- package/server/services/database/connection.ts +33 -19
- package/server/services/disks/driver.ts +5 -1
- package/server/services/disks/drivers/s3/index.ts +2 -2
- package/server/services/disks/index.ts +10 -5
- package/server/services/email/index.ts +1 -1
- package/server/services/prisma/Facet.ts +39 -15
- package/server/services/prisma/index.ts +5 -7
- package/server/services/router/http/multipart.ts +5 -0
- package/server/services/router/index.ts +50 -35
- package/server/services/router/request/api.ts +0 -12
- package/server/services/router/request/validation/zod.ts +180 -0
- package/server/services/router/response/index.ts +19 -10
- package/server/services/router/response/page/document.tsx +5 -3
- package/server/services/router/service.ts +7 -4
- package/server/services/schema/request.ts +21 -34
- package/server/services/schema/router/index.ts +3 -3
- package/types/global/utils.d.ts +22 -4
- package/types/icons.d.ts +1 -1
- package/common/data/input/validate.ts +0 -54
- package/server/services/router/request/validation/index.ts +0 -23
- package/server/services/router/request/validation/schema.ts +0 -211
- package/server/services/router/request/validation/validator.ts +0 -117
- package/server/services/router/request/validation/validators.ts +0 -485
package/client/app/index.ts
CHANGED
|
@@ -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
|
|
@@ -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
|
----------------------------------*/
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/common/data/dates.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
package/server/app/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 =
|
|
208
|
-
?
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
65
|
-
|
|
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/' +
|
|
102
|
+
path: '/api/' + path,
|
|
76
103
|
controller: originalMethod,
|
|
77
|
-
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|