5htp-core 0.3.7 → 0.3.8

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/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.3.7",
4
+ "version": "0.3.8",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -80,7 +80,9 @@
80
80
  "validator": "^13.7.0",
81
81
  "ws": "^8.2.2",
82
82
  "yaml": "^1.10.2",
83
- "yargs-parser": "^21.1.1"
83
+ "yargs-parser": "^21.1.1",
84
+ "youch": "^3.3.3",
85
+ "youch-terminal": "^2.2.3"
84
86
  },
85
87
  "devDependencies": {
86
88
  "@types/cookie": "^0.4.1",
@@ -15,7 +15,7 @@ import type {
15
15
  TRouteHttpMethod
16
16
  } from '@server/services/router';
17
17
 
18
- import type { TUserRole } from '@server/services/users';
18
+ import type { TUserRole } from '@server/services/auth';
19
19
 
20
20
  import type { TAppArrowFunction } from '@common/app';
21
21
 
@@ -145,10 +145,9 @@ export default class SchemaValidators {
145
145
  /*----------------------------------
146
146
  - CHAINES
147
147
  ----------------------------------*/
148
- public string = ({ min, max, include, ...opts }: TValidator<string> & {
148
+ public string = ({ min, max, ...opts }: TValidator<string> & {
149
149
  min?: number,
150
- max?: number,
151
- include?: string
150
+ max?: number
152
151
  } = {}) => new Validator<string>('string', (val, input, output, corriger?: boolean) => {
153
152
 
154
153
  // Check type
@@ -5,6 +5,8 @@
5
5
  // Node
6
6
  import { serialize } from 'v8';
7
7
  import { formatWithOptions } from 'util';
8
+ import Youch from 'youch';
9
+ import forTerminal from 'youch-terminal';
8
10
 
9
11
  // Npm
10
12
  import { v4 as uuid } from 'uuid';
@@ -13,8 +15,7 @@ import { format as formatSql } from 'sql-formatter';
13
15
  import highlight from 'cli-highlight';
14
16
 
15
17
  // Core libs
16
- import type { Application } from '@server/app';
17
- import Service from '@server/app/service';
18
+ import type ApplicationContainer from '..';
18
19
  import context from '@server/context';
19
20
  import type { ServerBug } from '@common/errors';
20
21
  import type ServerRequest from '@server/services/router/request';
@@ -117,7 +118,7 @@ const logLevels = {
117
118
  /*----------------------------------
118
119
  - LOGGER
119
120
  ----------------------------------*/
120
- export default class Console extends Service<Config, Hooks, Application, Services> {
121
+ export default class Console {
121
122
 
122
123
  // Services
123
124
  public logger!: Logger<ILogObj>;
@@ -134,17 +135,27 @@ export default class Console extends Service<Config, Hooks, Application, Service
134
135
  /*----------------------------------
135
136
  - LIFECYCLE
136
137
  ----------------------------------*/
138
+ /*
139
+ WARN: This service should depend on the less services as possible, and be usable ASAP.
140
+ So bug reports can be sent at any state of the app, includoing thre most early
141
+ */
142
+ public constructor(
143
+ private container: typeof ApplicationContainer,
144
+ private config: Config,
145
+ ) {
137
146
 
138
- protected async start() {
147
+ console.log("Setting up Console shell module.");
139
148
 
140
149
  const origLog = console.log
141
150
 
142
- const envConfig = this.config[ this.app.env.profile === 'prod' ? 'prod' : 'dev' ];
151
+ const Env = container.Environment;
152
+
153
+ const envConfig = this.config[ Env.profile === 'prod' ? 'prod' : 'dev' ];
143
154
  const minLogLevel = logLevels[ envConfig.level ];
144
155
 
145
156
  this.logger = new Logger({
146
157
  // Use to improve performance in production
147
- hideLogPositionForProduction: this.app.env.profile === 'prod',
158
+ hideLogPositionForProduction: Env.profile === 'prod',
148
159
  type: 'pretty',
149
160
  prettyInspectOptions: {
150
161
  depth: 2
@@ -201,13 +212,10 @@ export default class Console extends Service<Config, Hooks, Application, Service
201
212
  setInterval(() => this.clean(), 10000);
202
213
  }
203
214
 
204
- public async ready() {
205
-
206
- }
207
-
208
- public async shutdown() {
209
-
210
- }
215
+ // Avoid to use lifecycle functions
216
+ protected async start() {}
217
+ public async ready() {}
218
+ public async shutdown() {}
211
219
 
212
220
  /*----------------------------------
213
221
  - LOGS FORMATTING
@@ -218,7 +226,7 @@ export default class Console extends Service<Config, Hooks, Application, Service
218
226
  if (filepath === undefined)
219
227
  return undefined;
220
228
 
221
- const projectRoot = this.app.container.path.root;
229
+ const projectRoot = this.container.path.root;
222
230
  if (filepath.startsWith( projectRoot ))
223
231
  filepath = filepath.substring( projectRoot.length )
224
232
 
@@ -257,6 +265,34 @@ export default class Console extends Service<Config, Hooks, Application, Service
257
265
 
258
266
  public async createBugReport( error: Error, request?: ServerRequest ) {
259
267
 
268
+ // Print error
269
+ this.logger.error(LogPrefix, `Sending bug report for the following error:`, error);
270
+ /*const youchRes = new Youch(error, {});
271
+ const jsonResponse = await youchRes.toJSON()
272
+ console.log( forTerminal(jsonResponse, {
273
+ // Defaults to false
274
+ displayShortPath: false,
275
+
276
+ // Defaults to single whitspace
277
+ prefix: ' ',
278
+
279
+ // Defaults to false
280
+ hideErrorTitle: false,
281
+
282
+ // Defaults to false
283
+ hideMessage: false,
284
+
285
+ // Defaults to false
286
+ displayMainFrameOnly: false,
287
+
288
+ // Defaults to 3
289
+ framesMaxLimit: 3,
290
+ }) );*/
291
+
292
+ const application = this.container.application;
293
+ if (application === undefined)
294
+ return console.error(LogPrefix, "Can't send bug report because the application is not instanciated");
295
+
260
296
  // Print the error so it's accessible via logs
261
297
  if (error instanceof SqlError) {
262
298
  let printedQuery: string;
@@ -267,7 +303,7 @@ export default class Console extends Service<Config, Hooks, Application, Service
267
303
  }
268
304
  console.error(`Error caused by this query:`, printedQuery);
269
305
  }
270
- console.error(LogPrefix, `Sending bug report for the following error:`, error);
306
+
271
307
  if (error.dataForDebugging !== undefined)
272
308
  console.error(LogPrefix, `More data about the error:`, error.dataForDebugging);
273
309
 
@@ -306,7 +342,7 @@ export default class Console extends Service<Config, Hooks, Application, Service
306
342
  logs: logsHtml
307
343
  }
308
344
 
309
- await this.app.reportBug( bugReport );
345
+ await application.reportBug( bugReport );
310
346
  }
311
347
 
312
348
  public getChannel() {
@@ -8,9 +8,11 @@ import './patch';
8
8
  import path from 'path';
9
9
 
10
10
  // Core
11
+ import type Application from '..';
11
12
  import type { StartedServicesIndex } from '../service';
12
13
  import Services, { ServicesContainer } from '../service/container';
13
14
  import ConfigParser, { TEnvConfig } from './config';
15
+ import Console from './console';
14
16
 
15
17
  /*----------------------------------
16
18
  - CLASS
@@ -26,6 +28,9 @@ export class ApplicationContainer<
26
28
  public Services = Services as ServicesContainer<TServicesIndex>;
27
29
  public Environment: TEnvConfig;
28
30
  public Identity: Config.Identity;
31
+ public Console: Console;
32
+
33
+ public application?: Application;
29
34
 
30
35
  public constructor() {
31
36
 
@@ -33,6 +38,16 @@ export class ApplicationContainer<
33
38
  const configParser = new ConfigParser( this.path.root );
34
39
  this.Environment = configParser.env();
35
40
  this.Identity = configParser.identity();
41
+ this.Console = new Console(this, {
42
+ debug: false,
43
+ bufferLimit: 10000,
44
+ dev: {
45
+ level: 'log'
46
+ },
47
+ prod: {
48
+ level: 'log'
49
+ }
50
+ });
36
51
  }
37
52
 
38
53
  // Context
@@ -51,6 +66,45 @@ export class ApplicationContainer<
51
66
  },
52
67
  }
53
68
 
69
+ public start( ApplicationClass: typeof Application ) {
70
+
71
+ // Instanciate Application
72
+ try {
73
+ this.application = new ApplicationClass;
74
+ } catch (error) {
75
+ this.handleBug(error, "Failed to instanciate the Application Class");
76
+ process.exit(1);
77
+ }
78
+
79
+ // Start application
80
+ try {
81
+ this.application.start();
82
+ } catch (error) {
83
+ this.handleBug(error, "Failed to start the Application");
84
+ process.exit(1);
85
+ }
86
+ }
87
+
88
+ public async handleBug( rejection: Error, message: string ) {
89
+ if (this.Console) {
90
+ try {
91
+
92
+ this.Console.createBugReport(rejection);
93
+
94
+ } catch (consoleError) {
95
+ console.error(
96
+ message, rejection,
97
+ "Failed to transmiss the previous error to console:", consoleError
98
+ );
99
+ process.exit(1);
100
+ }
101
+ } else {
102
+ console.error(message, rejection);
103
+ process.exit(1);
104
+ }
105
+ }
106
+
107
+
54
108
  /*----------------------------------
55
109
  - HMR
56
110
  - TODO: move in dev server
@@ -11,7 +11,7 @@ import ServicesContainer, {
11
11
  TRegisteredService,
12
12
  TServiceMetas
13
13
  } from './service/container';
14
- import type { ServerBug } from '../services/console';
14
+ import type { ServerBug } from './container/console';
15
15
 
16
16
  // Built-in
17
17
  import type { default as Router, Request as ServerRequest } from '@server/services/router';
@@ -82,9 +82,6 @@ export class Application<
82
82
  public debug: boolean = false;
83
83
  public launched: boolean = false;
84
84
 
85
- // Mandatory services
86
- public Console = this.use('Core/Console');
87
-
88
85
  /*----------------------------------
89
86
  - INIT
90
87
  ----------------------------------*/
@@ -96,16 +93,20 @@ export class Application<
96
93
  // Application itself doesnt have configuration
97
94
  // Configuration must be handled by application services
98
95
  super(self, {}, {}, self);
99
-
100
- // We can't pass this in super so we assign here
101
- this.parent = this;
102
- this.app = this;
103
-
104
- // Gestion crash
96
+
97
+ // Handle unhandled crash
98
+ this.on('error', e => this.container.handleBug(e, "An error occured in the application"));
99
+
105
100
  process.on('unhandledRejection', (error: any, promise: any) => {
101
+ console.log("unhandledRejection");
106
102
  // We don't log the error here because it's the role of the app to decidehiw to log errors
107
103
  this.runHook('error', error);
108
104
  });
105
+
106
+ // We can't pass this in super so we assign here
107
+ this.parent = this;
108
+ this.app = this;
109
+
109
110
  }
110
111
 
111
112
  /*----------------------------------
@@ -122,16 +123,12 @@ export class Application<
122
123
  - LAUNCH
123
124
  ----------------------------------*/
124
125
 
125
- protected async start() {
126
+ public async start() {
126
127
 
127
128
  console.log("Build date", BUILD_DATE);
128
129
  console.log("Core version", CORE_VERSION);
129
- console.log(`5HTP Core`, process.env.npm_package_version);
130
130
  const startTime = Date.now();
131
131
 
132
- // Handle errors & crashs
133
- this.on('error', e => this.Console.createBugReport(e))
134
-
135
132
  console.info(`[boot] Start services`);
136
133
  await this.startServices();
137
134
  this.debug && console.info(`[boot] Services are ready`);
@@ -147,13 +144,6 @@ export class Application<
147
144
  public async ready() {
148
145
 
149
146
 
150
- }
151
-
152
- // Default error handler
153
- public async reportBug( bug: ServerBug ) {
154
-
155
- console.error( bug.error );
156
-
157
147
  }
158
148
 
159
149
  public async shutdown() {
@@ -208,6 +198,16 @@ export class Application<
208
198
  unused.join(', '));
209
199
  }
210
200
 
201
+ /*----------------------------------
202
+ - ERROR HANDLING
203
+ ----------------------------------*/
204
+ // Default error handler
205
+ public async reportBug( bug: ServerBug ) {
206
+
207
+ console.error( bug.error );
208
+
209
+ }
210
+
211
211
  }
212
212
 
213
213
  export default Application
@@ -144,6 +144,7 @@ export default abstract class Service<
144
144
  // this.use immediatly instanciate the subservice for few reasons:
145
145
  // - The subservice instance can be accesses from another service in the constructor, no matter the order of loading of the services
146
146
  // - Consistency: the subserviuce proprties shouldn't be assogned to two different values according depending on the app lifecycle
147
+ // Don't throw errors here since process.on('unhandledRejection') has not been configurated at this step (constructor ran after properties initialization)
147
148
  public use<
148
149
  TInstalledServices extends this["app"]["servicesContainer"]["allServices"],
149
150
  TServiceId extends keyof TInstalledServices,
@@ -172,8 +173,9 @@ export default abstract class Service<
172
173
  if (registered === undefined) {
173
174
  if (serviceUseOptions.optional)
174
175
  return undefined;
175
- else
176
+ else {
176
177
  throw new Error(`Unable to use service "${serviceId}": This one hasn't been setup.`);
178
+ }
177
179
  }
178
180
 
179
181
  // Bind subservices
@@ -193,20 +195,43 @@ export default abstract class Service<
193
195
 
194
196
  // Instanciate
195
197
  console.log(`[app] Load service`, registered.metas.id);
196
- const ServiceClass = registered.metas.class().default;
198
+ let ServiceClass;
199
+ try {
200
+ ServiceClass = registered.metas.class().default;
201
+ } catch (error) {
202
+ console.error("Failed to load the class of the", registered.metas.id, "service:", error);
203
+ throw error;
204
+ }
197
205
 
198
206
  // Create class instance
199
- const service = new ServiceClass(
200
- this,
201
- registered.config,
202
- registered.subServices,
203
- this.app || this
204
- )
205
- const serviceInstance = service.getServiceInstance();
207
+ this.config.debug && console.log(`[app] Instanciate service`, registered.metas.id);
208
+ let service;
209
+ try {
210
+ service = new ServiceClass(
211
+ this,
212
+ registered.config,
213
+ registered.subServices,
214
+ this.app || this
215
+ )
216
+ } catch (error) {
217
+ console.error("Failed to instanciate class of the", registered.metas.id, "service");
218
+ throw error;
219
+ }
220
+
221
+ // Hande custom instance getter (ex: SQL callable class)
222
+ this.config.debug && console.log(`[app] Get service instance for`, registered.metas.id);
223
+ let serviceInstance;
224
+ try {
225
+ serviceInstance = service.getServiceInstance();
226
+ } catch (error) {
227
+ console.error("Failed to get service instance for the ", registered.metas.id, "service");
228
+ throw error;
229
+ }
206
230
 
207
231
  // Bind his own metas
208
232
  service.metas = registered.metas;
209
233
  ServicesContainer.allServices[ registered.metas.id ] = serviceInstance;
234
+ this.config.debug && console.log(`[app] Service`, registered.metas.id, 'Loaded');
210
235
 
211
236
  return serviceInstance;
212
237
  }
@@ -250,7 +275,7 @@ export default abstract class Service<
250
275
  await service.started.catch(e => {
251
276
  console.error("Catched error while starting service " + serviceScope, e);
252
277
  if (this.app.env.profile === 'prod')
253
- process.exit();
278
+ process.exit(1);
254
279
  else
255
280
  throw e;
256
281
  })
@@ -1,4 +1,4 @@
1
1
  import { AsyncLocalStorage } from 'async_hooks';
2
- import type { ChannelInfos } from '@server/services/console';
2
+ import type { ChannelInfos } from '@server/app/container/console';
3
3
 
4
4
  export default new AsyncLocalStorage<ChannelInfos>();
@@ -1,12 +1,4 @@
1
- // Load Application container
2
- import './app/container';
3
-
4
- // Load services setup
1
+ import AppContainer from './app/container';
5
2
  import '@/server/config/*.ts';
6
-
7
- // Load Application
8
3
  import Application from '@/server';
9
- const application = new Application;
10
-
11
- // Start application
12
- application.start();
4
+ AppContainer.start( Application );
@@ -65,7 +65,7 @@ export type TServices = {
65
65
  /*----------------------------------
66
66
  - SERVICE
67
67
  ----------------------------------*/
68
- export default abstract class UsersManagementService<
68
+ export default abstract class AuthService<
69
69
  TUser extends {},
70
70
  TApplication extends Application,
71
71
  TJwtSession extends {} = {},
@@ -233,9 +233,19 @@ export default class SQL extends Service<Config, Hooks, Application, Services> {
233
233
  // SQL query
234
234
  } else if (typeof value === 'function' && value.string !== undefined)
235
235
  value = ' ' + value.string;
236
- else
237
- value = ' ' + mysql.escape(value);
236
+ // Escape value
237
+ else {
238
238
 
239
+ const lastKeyword = stringBefore.trim().split(' ').pop();
240
+
241
+ // Escape table name
242
+ if (lastKeyword === 'FROM')
243
+ value = '`' + value + '`';
244
+ else
245
+ value = mysql.escape(value);
246
+
247
+ value = ' ' + value;
248
+ }
239
249
  stringBefore += value;
240
250
 
241
251
  }
@@ -83,7 +83,6 @@ export default class FetchService extends Service<Config, Hooks, Application, Se
83
83
  if (this.services.disks)
84
84
  this.disk = this.services.disks.get( this.config.disk );
85
85
 
86
-
87
86
  }
88
87
 
89
88
  public async ready() {
@@ -116,8 +115,10 @@ export default class FetchService extends Service<Config, Hooks, Application, Se
116
115
  ) {
117
116
 
118
117
  // Parse url if router service is provided
119
- if (this.services.router !== undefined)
120
- url = this.services.router.url(url);
118
+ if (this.services.router === undefined)
119
+ throw new Error(`Please bind the Router service to the Fetch service in order to contact APIs.`);
120
+
121
+ url = this.services.router.url(url);
121
122
 
122
123
  // Send request
123
124
  const res = await got(url, {
@@ -14,7 +14,7 @@ import Service, { AnyService, TRegisteredService } from '@server/app/service';
14
14
  import SocketScope, { WebSocket } from './scope';
15
15
  import type Router from '@server/services/router';
16
16
  export type { WebSocket, default as SocketScope } from './scope';
17
- import type UsersManagementService from '../users';
17
+ import type UsersManagementService from '../auth';
18
18
 
19
19
  /*----------------------------------
20
20
  - TYPES
@@ -1,6 +0,0 @@
1
- {
2
- "id": "Core/Console",
3
- "name": "Console",
4
- "parent": "app",
5
- "dependences": []
6
- }
File without changes