5htp-core 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.0",
4
+ "version": "0.3.1",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -97,6 +97,6 @@
97
97
  "babel-plugin-glob-import": "^0.0.7"
98
98
  },
99
99
  "peerDependencies": {
100
- "5htp": "0.3.0"
100
+ "5htp": "0.3.1"
101
101
  }
102
102
  }
@@ -6,11 +6,11 @@
6
6
  import React from 'react';
7
7
 
8
8
  // Core
9
- import Dropdown, { TDropdownControl, Props as DropdownProps } from '@client/components/dropdown';
9
+ import { Props as DropdownProps } from '@client/components/dropdown';
10
10
  import Input from '@client/components/inputv3';
11
11
 
12
12
  // Specific
13
- import ChoiceSelector, {
13
+ import {
14
14
  Props as SelectorProps,
15
15
  Choice,
16
16
  } from './ChoiceSelector';
@@ -24,6 +24,8 @@ export type Props = DropdownProps & SelectorProps & {
24
24
  errors?: string[],
25
25
  }
26
26
 
27
+ export type { Choice } from './ChoiceSelector';
28
+
27
29
  const ChoiceElement = ({ choice, currentList, onChange, multiple, includeCurrent }: {
28
30
  choice: Choice,
29
31
  currentList: Choice[],
@@ -40,7 +40,7 @@ import * as appRoutes from '@/client/pages/**/*.tsx';
40
40
  - CONFIG
41
41
  ----------------------------------*/
42
42
 
43
- const debug = true;
43
+ const debug = false;
44
44
  const LogPrefix = '[router]'
45
45
 
46
46
  /*----------------------------------
@@ -61,6 +61,8 @@ export type Response = ClientResponse<ClientRouter> | ServerResponse<ServerRoute
61
61
  - TYPES: ROUTES LOADING
62
62
  ----------------------------------*/
63
63
 
64
+ // WARN: To be updated with the mplemenations list of Router.page
65
+ // (both server and client side)
64
66
  export type TRegisterPageArgs<TProvidedData extends TFetcherList = {}, TRouter extends Router = Router> = ([
65
67
  path: string,
66
68
  controller: TDataProvider<TProvidedData> | null,
@@ -219,6 +221,25 @@ export default class ClientRouter<
219
221
  return currentRoute;
220
222
  }
221
223
 
224
+ public page(
225
+ path: string,
226
+ controller: TDataProvider<{}> | null,
227
+ renderer: TFrontRenderer<{}>
228
+ ): TRoute;
229
+
230
+ public page(
231
+ path: string,
232
+ options: Partial<TRoute["options"]>,
233
+ renderer: TFrontRenderer<{}>
234
+ ): TRoute;
235
+
236
+ public page(
237
+ path: string,
238
+ options: Partial<TRoute["options"]>,
239
+ controller: TDataProvider<{}> | null,
240
+ renderer: TFrontRenderer<{}>
241
+ ): TRoute;
242
+
222
243
  public page(...args: TRegisterPageArgs): TRoute {
223
244
 
224
245
  const { path, options, controller, renderer, layout } = getRegisterPageArgs(...args);
@@ -86,7 +86,7 @@ export default class ClientPageResponse<
86
86
  route: this.route,
87
87
  api: this.request.api,
88
88
 
89
- ...this.request.router.config.context()
89
+ ...this.request.router.config.context( this.request.router )
90
90
  }
91
91
 
92
92
  context.context = context;
@@ -16,6 +16,26 @@ type TDetailsErreur = {
16
16
  urlRequete?: string,
17
17
  }
18
18
 
19
+ /*----------------------------------
20
+ - TYPES: BUG REPORT
21
+ ----------------------------------*/
22
+
23
+ export type ServerBug = {
24
+ // Context
25
+ hash: string,
26
+ date: Date, // Timestamp
27
+ channelType?: string,
28
+ channelId?: string,
29
+
30
+ user: string | null | undefined,
31
+ ip: string | null | undefined,
32
+
33
+ // Error
34
+ error: Error,
35
+ stacktrace: string,
36
+ logs: string,
37
+ }
38
+
19
39
  /*----------------------------------
20
40
  - ERREURS
21
41
  ----------------------------------*/
@@ -10,6 +10,8 @@ import {
10
10
  isURL
11
11
  } from 'validator';
12
12
 
13
+ import normalizeUrl, { Options as NormalizeUrlOptions } from 'normalize-url';
14
+
13
15
  // Core
14
16
  import { InputError } from '@common/errors';
15
17
  import FileToUpload from '@client/components/inputv3/file/FileToUpload';
@@ -157,16 +159,25 @@ export default class SchemaValidators {
157
159
 
158
160
  }, opts)
159
161
 
160
- public url = (opts: TValidator<string> & {} = {}) =>
162
+ public url = (opts: TValidator<string> & {
163
+ normalize?: NormalizeUrlOptions
164
+ } = {}) =>
161
165
  new Validator<string>('url', (inputVal, input, output, corriger?) => {
162
166
 
163
167
  let val = this.string(opts).validate(inputVal, input, output, corriger);
164
168
 
169
+ // Check if URL
165
170
  if (!isURL(val, {
166
171
  // https://www.npmjs.com/package/validator
167
172
  }))
168
173
  throw new InputError(`Please provide a valid URL.`);
169
174
 
175
+ // Normalize
176
+ if (opts.normalize !== undefined)
177
+ val = normalizeUrl(val, opts.normalize);
178
+
179
+ console.log("@@@@@@@@@@@@@NORMALISZE URL", opts.normalize, val);
180
+
170
181
  return val;
171
182
  }, opts)
172
183
 
@@ -41,15 +41,35 @@ export type Hooks = {
41
41
 
42
42
  }
43
43
 
44
+ export type Services = {
45
+
46
+ }
47
+
44
48
  /*----------------------------------
45
49
  - SERVICE
46
50
  ----------------------------------*/
47
- export default class CommandsManager extends Service<Config, Hooks, Application> {
51
+ export default class CommandsManager extends Service<Config, Hooks, Application, Services> {
48
52
 
49
53
  public priority = 2 as 2;
50
54
 
51
55
  public commandsIndex: CommandsList = {}
52
56
 
57
+ /*----------------------------------
58
+ - LIFECYCLE
59
+ ----------------------------------*/
60
+
61
+ protected async start() {
62
+
63
+ }
64
+
65
+ protected async ready() {
66
+
67
+ }
68
+
69
+ protected async shutdown() {
70
+
71
+ }
72
+
53
73
  /*----------------------------------
54
74
  - DEFINITIONS
55
75
  ----------------------------------*/
@@ -35,6 +35,7 @@ export type TEnvConfig = {
35
35
  type AppIdentityConfig = {
36
36
 
37
37
  name: string,
38
+ identifier: string,
38
39
  description: string,
39
40
  author: {
40
41
  name: string,
@@ -8,19 +8,22 @@ import './patch';
8
8
  import path from 'path';
9
9
 
10
10
  // Core
11
- import Services from '../service/container';
11
+ import type { StartedServicesIndex } from '../service';
12
+ import Services, { ServicesContainer } from '../service/container';
12
13
  import ConfigParser, { TEnvConfig } from './config';
13
14
 
14
15
  /*----------------------------------
15
16
  - CLASS
16
17
  ----------------------------------*/
17
- export class ApplicationContainer {
18
+ export class ApplicationContainer<
19
+ TServicesIndex extends StartedServicesIndex = StartedServicesIndex
20
+ > {
18
21
 
19
22
  /*----------------------------------
20
23
  - INIT
21
24
  ----------------------------------*/
22
25
 
23
- public Services = Services;
26
+ public Services = Services as ServicesContainer<TServicesIndex>;
24
27
  public Environment: TEnvConfig;
25
28
  public Identity: Config.Identity;
26
29
 
@@ -7,6 +7,7 @@ import AppContainer from './container';
7
7
  import ApplicationService, { AnyService } from './service';
8
8
  import CommandsManager from './commands';
9
9
  import ServicesContainer, { TRegisteredService, TServiceMetas } from './service/container';
10
+ import type { ServerBug } from '../services/console';
10
11
 
11
12
  // Built-in
12
13
  import type { default as Router, Request as ServerRequest } from '@server/services/router';
@@ -48,7 +49,7 @@ export const Service = ServicesContainer;
48
49
  /*----------------------------------
49
50
  - FUNCTIONS
50
51
  ----------------------------------*/
51
- export class Application extends ApplicationService<Config, Hooks, /* TODO: this ? */Application> {
52
+ export class Application extends ApplicationService<Config, Hooks, /* TODO: this ? */Application, {}> {
52
53
 
53
54
  /*----------------------------------
54
55
  - PROPERTIES
@@ -72,8 +73,8 @@ export class Application extends ApplicationService<Config, Hooks, /* TODO: this
72
73
  public debug: boolean = false;
73
74
  public launched: boolean = false;
74
75
 
75
- // All service instances by service id
76
- public allServices: {[serviceId: string]: AnyService} = {}
76
+ // Mandatory services
77
+ public Console = this.use('Core/Console');
77
78
 
78
79
  /*----------------------------------
79
80
  - INIT
@@ -81,10 +82,14 @@ export class Application extends ApplicationService<Config, Hooks, /* TODO: this
81
82
 
82
83
  public constructor() {
83
84
 
85
+ const self = 'self' as unknown as Application;
86
+
84
87
  // Application itself doesnt have configuration
85
88
  // Configuration must be handled by application services
86
- super({}, {}, {}, {});
89
+ super(self, {}, {}, self);
87
90
 
91
+ // We can't pass this in super so we assign here
92
+ this.parent = this;
88
93
  this.app = this;
89
94
 
90
95
  // Gestion crash
@@ -113,9 +118,9 @@ export class Application extends ApplicationService<Config, Hooks, /* TODO: this
113
118
  console.log(`5HTP Core`, process.env.npm_package_version);
114
119
 
115
120
  // Handle errors & crashs
116
- this.on('error', this.error.bind(this))
121
+ this.on('error', e => this.Console.createBugReport(e))
117
122
 
118
- this.debug && console.info(`[boot] Start services`);
123
+ console.info(`[boot] Start services`);
119
124
  await this.startServices();
120
125
 
121
126
  this.debug && console.info(`[boot] App ready`);
@@ -132,8 +137,10 @@ export class Application extends ApplicationService<Config, Hooks, /* TODO: this
132
137
  }
133
138
 
134
139
  // Default error handler
135
- public async error( e: Error ) {
136
- console.error( e );
140
+ public async reportBug( bug: ServerBug ) {
141
+
142
+ console.error( bug.error );
143
+
137
144
  }
138
145
 
139
146
  public async shutdown() {
@@ -150,30 +157,38 @@ export class Application extends ApplicationService<Config, Hooks, /* TODO: this
150
157
 
151
158
  // Don't check services prop as it will trigger an error (it's a proxy)
152
159
  // TODO: exclude all properties coming from the Service class itself
153
- if (propName === 'services' || typeof this[ propName ] !== 'object')
160
+ if (propName === 'services' || this[ propName ] === undefined)
154
161
  continue;
155
162
 
156
163
  // Check if this property is a service registration
157
- const registered = this[ propName ] as TRegisteredService;
158
- if (registered?.type !== 'service')
164
+ const service = this[ propName ] as TRegisteredService;
165
+ const isService = (
166
+ service instanceof ApplicationService
167
+ ||
168
+ (
169
+ typeof service === 'function'
170
+ &&
171
+ service.serviceInstance instanceof ApplicationService
172
+ )
173
+ )
174
+ if (!isService)
159
175
  continue;
160
176
 
161
177
  // Instanciate the service
162
- const service = this.registerService( propName, registered );
163
- this[ propName ] = service;
178
+ this.bindService( propName, service );
164
179
 
165
180
  // Register commands
166
181
  if (service.commands)
167
182
  this.commandsManager.fromList( service.commands );
168
183
 
169
184
  // Start service
170
- await this.startService( service );
185
+ await this.startService( propName, service );
171
186
  }
172
187
 
173
188
  // Check if any setup service has not been used
174
189
  const unused: string[] = []
175
190
  for (const serviceNS in ServicesContainer.registered)
176
- if (this.allServices[ serviceNS ] === undefined)
191
+ if (ServicesContainer.allServices[ serviceNS ] === undefined)
177
192
  unused.push(serviceNS);
178
193
 
179
194
  if (unused.length !== 0)
@@ -4,7 +4,7 @@
4
4
 
5
5
  // Specific
6
6
  import type {
7
- AnyService,
7
+ AnyService, StartedServicesIndex,
8
8
  // Hooks
9
9
  THookCallback, THooksIndex
10
10
  } from ".";
@@ -46,13 +46,18 @@ const LogPrefix = '[service]';
46
46
  /*----------------------------------
47
47
  - CLASS
48
48
  ----------------------------------*/
49
- export class ServicesContainer {
49
+ export class ServicesContainer<
50
+ TServicesIndex extends StartedServicesIndex = StartedServicesIndex
51
+ > {
50
52
 
51
53
  public registered: TRegisteredServicesIndex = {}
52
54
 
53
- public setup(
54
- serviceId: string,
55
- serviceConfig: {}
55
+ // All service instances by service id
56
+ public allServices: {[serviceId: string]: AnyService} = {}
57
+
58
+ public setup<TServiceId extends keyof TServicesIndex>(
59
+ serviceId: keyof TServicesIndex,
60
+ serviceConfig: TServicesIndex[TServiceId]["config"]
56
61
  ): TRegisteredService {
57
62
 
58
63
  // Check if the service is installed & has been indexed
@@ -86,25 +91,6 @@ export class ServicesContainer {
86
91
  return service;
87
92
  }
88
93
 
89
- public use(
90
- serviceId: string,
91
- // TODO: Only subservices types supported by the parent service
92
- subServices: TRegisteredServicesIndex = {}
93
- ): TRegisteredService {
94
-
95
- // Check of the service has been configurated
96
- const registered = this.registered[ serviceId ];
97
- if (registered === undefined)
98
- throw new Error(`Unable to use service "${serviceId}": This one hasn't been setup.`);
99
-
100
- // Bind subservices
101
- registered.subServices = subServices;
102
-
103
- // Return the service metas
104
- // The parent service will take care of instanciating & starting it
105
- return registered;
106
- }
107
-
108
94
  public callableInstance = <TInstance extends object, TCallableName extends keyof TInstance>(
109
95
  instance: TInstance,
110
96
  funcName: TCallableName
@@ -129,6 +115,9 @@ export class ServicesContainer {
129
115
  ? instance[ method ].bind( instance )
130
116
  : instance[ method ];
131
117
 
118
+ // Allow us to recognize a callable as a service
119
+ callable.serviceInstance = instance;
120
+
132
121
  return callable as TInstance[TCallableName] & TInstance;
133
122
  }
134
123
  }
@@ -6,12 +6,13 @@
6
6
  import type { Application } from "..";
7
7
  import type { Command } from "../commands";
8
8
  import type { TServiceMetas, TRegisteredServicesIndex, TRegisteredService } from './container';
9
+ import ServicesContainer from './container';
9
10
 
10
11
  /*----------------------------------
11
12
  - TYPES: OPTIONS
12
13
  ----------------------------------*/
13
14
 
14
- export type AnyService = Service<{}, {}, Application, TStartedServicesIndex>
15
+ export type AnyService = Service<{}, {}, Application, {}>
15
16
 
16
17
  type TServiceConfig = {
17
18
  debug?: boolean
@@ -52,7 +53,7 @@ export default abstract class Service<
52
53
  TConfig extends TServiceConfig,
53
54
  THooks extends THooksList,
54
55
  TApplication extends Application,
55
- TServicesIndex extends StartedServicesIndex = StartedServicesIndex
56
+ TServicesIndex extends StartedServicesIndex
56
57
  > {
57
58
 
58
59
  public started?: Promise<void>;
@@ -62,26 +63,34 @@ export default abstract class Service<
62
63
  public metas!: TServiceMetas;
63
64
  public bindings: string[] = []
64
65
 
65
- public static createInstance?: (
66
- parent: AnyService,
67
- config: TServiceConfig,
68
- services: TRegisteredServicesIndex,
69
- app: Application
70
- ) => Service<TServiceConfig, THooksList, Application, StartedServicesIndex>;
66
+ public app: TApplication;
71
67
 
72
68
  public constructor(
73
- public parent: AnyService,
69
+ public parent: AnyService | 'self',
74
70
  public config: TConfig,
75
- services: TRegisteredServicesIndex,
76
- public app: TApplication
71
+ // Make this argument appear as instanciated sercices index
72
+ // But actually, Setup.use returns a registered service, not yet launched
73
+ services: TServicesIndex,
74
+ app: TApplication | 'self'
77
75
  ) {
78
76
 
77
+ if (this.parent === 'self')
78
+ this.parent = this;
79
+
80
+ this.app = app === 'self'
81
+ ? this as unknown as TApplication
82
+ : app
83
+
79
84
  // Instanciate subservices
80
85
  for (const localName in services)
81
- this.registerService( localName, services[localName] );
86
+ this.bindService( localName, services[localName] as unknown as TRegisteredService );
82
87
 
83
88
  }
84
89
 
90
+ public getServiceInstance() {
91
+ return this;
92
+ }
93
+
85
94
  /*----------------------------------
86
95
  - LIFECYCLE
87
96
  ----------------------------------*/
@@ -90,7 +99,7 @@ export default abstract class Service<
90
99
 
91
100
  // Instanciate subservices
92
101
  for (const localName in this.services)
93
- await this.startService( this.services[localName] );
102
+ await this.startService( localName, this.services[localName] );
94
103
 
95
104
  // Start service
96
105
  if (this.start)
@@ -112,66 +121,103 @@ export default abstract class Service<
112
121
  - SUBSERVICES
113
122
  ----------------------------------*/
114
123
 
115
- public services: TRegisteredServicesIndex = {} as TRegisteredServicesIndex/*new Proxy({}, {
124
+ public registered: TRegisteredServicesIndex = {} as TRegisteredServicesIndex;
125
+
126
+ public services: TServicesIndex = {} as TServicesIndex;
127
+
128
+ /*new Proxy({}, {
116
129
  get: (target, prop, recever) => {
117
130
  if (!( prop in target )) {
118
131
 
119
- throw new Error(`You made reference to the "${prop}" service, but this one hasn't been loaded yet. Loaded services: ` + Object.keys(this.services).join(', '));
132
+ throw new Error(`You made reference to the "${prop}" service, but this one hasn't been loaded or started yet.
133
+ Registered services: ${Object.keys(this.services).join(', ')} ;
134
+ Loaded services: ${Object.keys(this.services).join(', ')}
135
+ `);
120
136
  }
121
137
  }
122
138
  }) as TRegisteredServicesIndex*/
123
139
 
124
- protected registerService( localName: string, registered: TRegisteredService ): AnyService {
140
+ // this.use immediatly instanciate the subservice for few reasons:
141
+ // - The subservice instance can be accesses from another service in the constructor, no matter the order of loading of the services
142
+ // - Consistency: the subserviuce proprties shouldn't be assogned to two different values according depending on the app lifecycle
143
+ public use<TServiceId extends keyof TServicesIndex>(
144
+ serviceId: TServiceId,
145
+ // TODO: Only subservices types supported by the parent service
146
+ subServices: TServicesIndex[TServiceId]["services"] = {}
147
+ ) {
125
148
 
126
- // Service already instabciates on the app scope
127
- let service = this.app.allServices[ registered.metas.id ];
149
+ // Check of the service has been configurated
150
+ const registered = ServicesContainer.registered[ serviceId ];
151
+ if (registered === undefined)
152
+ throw new Error(`Unable to use service "${serviceId}": This one hasn't been setup.`);
128
153
 
129
- // Service not yet instanciated
130
- if (service === undefined) {
154
+ // Bind subservices
155
+ registered.subServices = subServices;
131
156
 
132
- // Instanciate
133
- console.log(`[app] Load service`, registered.metas.id);
134
- const ServiceClass = registered.metas.class().default;
135
- // Create class instance
136
- service = ServiceClass.createInstance !== undefined
137
- ? ServiceClass.createInstance(this, registered.config, registered.subServices, this.app)
138
- : new ServiceClass(this, registered.config, registered.subServices, this.app);
157
+ // Check if not already instanciated
158
+ const existing = ServicesContainer.allServices[ serviceId ];
159
+ if (existing !== undefined) {
160
+ console.info("Service", serviceId, "already instanciated through another service.");
161
+ return existing;
162
+ }
139
163
 
140
- service.metas = registered.metas;
164
+ // Instanciate
165
+ console.log(`[app] Load service`, registered.metas.id);
166
+ const ServiceClass = registered.metas.class().default;
141
167
 
142
- } else {
143
- console.warn(`[app] Service`, registered.metas.id, 'already instanciated in this app.');
144
- }
168
+ // Create class instance
169
+ const service = new ServiceClass(this, registered.config, registered.subServices, this.app || this)
170
+ .getServiceInstance()
145
171
 
146
- // Bind to app
147
- this.services[ localName ] = service;
148
- service.bindings.push(this.constructor.name + '.' + localName);
149
- this.app.allServices[ registered.metas.id ] = service;
172
+ // Bind his own metas
173
+ service.metas = registered.metas;
174
+ ServicesContainer.allServices[ registered.metas.id ] = service;
150
175
 
151
176
  return service;
152
-
153
177
  }
154
178
 
155
- protected async startService( service: AnyService ) {
179
+ protected bindService( localName: string, service: AnyService ) {
156
180
 
157
- // Service already started
158
- if (service.started)
159
- return;
160
-
161
- // Start servuce & eventually his subservices
162
- console.log(`[app] Start service`, service.metas.id);
163
- service.status = 'starting';
164
- service.started = service.launch();
165
- await service.started.catch(e => {
166
- console.error("Catched error while starting service " + service.metas.id, e);
167
- if (this.app.env.profile === 'prod')
168
- process.exit();
169
- else
170
- throw e;
171
- })
181
+ const serviceScope = this.constructor.name + '.' + localName;
182
+
183
+ // Fix the parent service (the app could be provided as parent because the dev called this.use() in the app class definition)
184
+ service.parent = this;
185
+
186
+ // Bind subservice to service
187
+ //console.log(`Binding service ${serviceScope}`);
188
+ service.bindings.push(serviceScope);
189
+
190
+ // Since now we have the localname, we can bind the service in this.service too
191
+ this.services[localName] = service;
172
192
 
173
- console.log(`[app] Service`, service.metas.id, 'started (bound to:', service.bindings.join(', '),')');
174
- service.status = 'running';
193
+ // For serices that have been passed through this.use
194
+ if ((localName in this) && this[localName] === undefined)
195
+ this[ localName ] = service;
196
+
197
+ }
198
+
199
+ protected async startService( localName: string, service: AnyService ) {
200
+ // Service already started
201
+ if (!service.started) {
202
+
203
+ const serviceScope = this.constructor.name + '.' + localName;
204
+
205
+ // Start servuce & eventually his subservices
206
+ console.log(`[app] Start service`, serviceScope);
207
+ service.status = 'starting';
208
+ service.started = service.launch();
209
+ await service.started.catch(e => {
210
+ console.error("Catched error while starting service " + serviceScope, e);
211
+ if (this.app.env.profile === 'prod')
212
+ process.exit();
213
+ else
214
+ throw e;
215
+ })
216
+
217
+ // Bind to app
218
+ console.log(`[app] Service`, serviceScope, 'started (bound to:', service.bindings.join(', '),')');
219
+ service.status = 'running';
220
+ }
175
221
  }
176
222
 
177
223
  /*----------------------------------
@@ -5,6 +5,8 @@
5
5
  "baseUrl": "..",
6
6
  "paths": {
7
7
 
8
+ "@/server/models": ["./server/.generated/models"],
9
+
8
10
  "@client/*": ["../node_modules/5htp-core/src/client/*"],
9
11
  "@common/*": ["../node_modules/5htp-core/src/common/*"],
10
12
  "@server/*": ["../node_modules/5htp-core/src/server/*"],
@@ -5,7 +5,8 @@ import './app/container';
5
5
  import '@/server/config/*.ts';
6
6
 
7
7
  // Load Application
8
- import application from './app/instance';
8
+ import Application from '@/server';
9
+ const application = new Application;
9
10
 
10
11
  // Start application
11
12
  application.start();
@@ -65,10 +65,14 @@ export type Hooks = {
65
65
 
66
66
  }
67
67
 
68
+ export type Services = {
69
+ disks: DisksManager,
70
+ }
71
+
68
72
  /*----------------------------------
69
73
  - SERVICE
70
74
  ----------------------------------*/
71
- export default class Cache extends Service<Config, Hooks, Application> {
75
+ export default class Cache extends Service<Config, Hooks, Application, Services> {
72
76
 
73
77
  public commands = registerCommands(this);
74
78
 
@@ -79,9 +83,7 @@ export default class Cache extends Service<Config, Hooks, Application> {
79
83
  public constructor(
80
84
  parent: AnyService,
81
85
  config: Config,
82
- services: {
83
- disks: TRegisteredService< DisksManager >,
84
- },
86
+ services: Services,
85
87
  app: Application,
86
88
  ) {
87
89
 
@@ -11,6 +11,7 @@ import highlight from 'cli-highlight';
11
11
  import type { Application } from '@server/app';
12
12
  import Service from '@server/app/service';
13
13
  import context from '@server/context';
14
+ import type { ServerBug } from '@common/errors';
14
15
  import type ServerRequest from '@server/services/router/request';
15
16
  import { SqlError } from '@server/services/database/debug';
16
17
 
@@ -38,6 +39,10 @@ export type Hooks = {
38
39
 
39
40
  }
40
41
 
42
+ export type Services = {
43
+
44
+ }
45
+
41
46
  /*----------------------------------
42
47
  - TYPES
43
48
  ----------------------------------*/
@@ -82,25 +87,6 @@ export type TDbQueryLog = ChannelInfos & {
82
87
 
83
88
  export type TLog = ILogObject & ChannelInfos
84
89
 
85
- /*----------------------------------
86
- - TYPES: BUG REPORT
87
- ----------------------------------*/
88
-
89
- export type ServerBug = {
90
- // Context
91
- hash: string,
92
- date: Date, // Timestamp
93
- channelType?: string,
94
- channelId?: string,
95
-
96
- user: string | null | undefined,
97
- ip: string | null | undefined,
98
-
99
- // Error
100
- error: Error,
101
- stacktrace: string,
102
- logs: string,
103
- }
104
90
 
105
91
  /*----------------------------------
106
92
  - CONST
@@ -129,7 +115,7 @@ const logFields = [
129
115
  /*----------------------------------
130
116
  - LOGGER
131
117
  ----------------------------------*/
132
- export default class Console extends Service<Config, Hooks, Application> {
118
+ export default class Console extends Service<Config, Hooks, Application, Services> {
133
119
 
134
120
  // Services
135
121
  public logger!: Logger;
@@ -184,9 +170,6 @@ export default class Console extends Service<Config, Hooks, Application> {
184
170
  }, envConfig.level);*/
185
171
 
186
172
  setInterval(() => this.clean(), 10000);
187
-
188
- // Send email report
189
- this.app.on('error', this.createBugReport.bind(this));
190
173
  }
191
174
 
192
175
  public async ready() {
@@ -259,7 +242,7 @@ export default class Console extends Service<Config, Hooks, Application> {
259
242
  logs: logsHtml
260
243
  }
261
244
 
262
- await this.runHook('bugReport', bugReport);
245
+ await this.app.reportBug( bugReport );
263
246
  }
264
247
 
265
248
  public getChannel() {
@@ -28,11 +28,15 @@ export type Hooks = {
28
28
 
29
29
  }
30
30
 
31
+ export type Services = {
32
+
33
+ }
34
+
31
35
  /*----------------------------------
32
36
  - CLASSE
33
37
  ----------------------------------*/
34
38
 
35
- export default class CronManager extends Service<Config, Hooks, Application> {
39
+ export default class CronManager extends Service<Config, Hooks, Application, Services> {
36
40
 
37
41
  public static taches: { [nom: string]: CronTask } = {}
38
42
  public static timer: NodeJS.Timeout;
@@ -4,7 +4,7 @@
4
4
 
5
5
  // Npm
6
6
  import mysql from 'mysql2/promise';
7
- import type { ResultSetHeader } from 'mysql2';
7
+ import type { OkPacket } from 'mysql2';
8
8
  import dottie from 'dottie';
9
9
  const safeStringify = require('fast-safe-stringify'); // remplace les références circulairs par un [Circular]
10
10
 
@@ -29,6 +29,10 @@ export type Hooks = {
29
29
 
30
30
  }
31
31
 
32
+ export type Services = {
33
+
34
+ }
35
+
32
36
  /*----------------------------------
33
37
  - DEFINITIONS TYPES
34
38
  ----------------------------------*/
@@ -42,6 +46,7 @@ export type TSelectQueryOptions = TQueryOptions & {
42
46
  export type TUpdateQueryOptions<TData extends TObjetDonnees = TObjetDonnees> = TQueryOptions;
43
47
 
44
48
  export type TInsertQueryOptions<TData extends TObjetDonnees = TObjetDonnees> = TQueryOptions & {
49
+ bulk?: boolean,
45
50
  upsert?: TColsToUpsert<TData>, // When "*", we use table.upsertableColumns
46
51
  upsertMode?: 'increment'
47
52
  try?: boolean
@@ -66,13 +71,27 @@ type TColsToUpsert<TData extends TObjetDonnees> = (
66
71
 
67
72
  const LogPrefix = '[database]'
68
73
 
74
+ const emptyOkPacket = {
75
+ constructor: {
76
+ name: 'OkPacket'
77
+ },
78
+ fieldCount: 0,
79
+ affectedRows: 0,
80
+ changedRows: 0,
81
+ insertId: 0,
82
+ serverStatus: 0,
83
+ warningCount: 0,
84
+ message: '',
85
+ procotol41: true,
86
+ } as OkPacket
87
+
69
88
  /*----------------------------------
70
89
  - CORE
71
90
  ----------------------------------*/
72
91
 
73
92
  // TODO: build callable instance sithut instanciating the service
74
93
 
75
- export default class SQL extends Service<Config, Hooks, Application> {
94
+ export default class SQL extends Service<Config, Hooks, Application, Services> {
76
95
 
77
96
  public database: Database;
78
97
 
@@ -88,16 +107,8 @@ export default class SQL extends Service<Config, Hooks, Application> {
88
107
  this.database = new Database(this, config);
89
108
  }
90
109
 
91
- public static createInstance(
92
- parent: AnyService,
93
- config: Config,
94
- services: TRegisteredServicesIndex,
95
- app: Application
96
- ) {
97
- return Services.callableInstance(
98
- new SQL(parent, config, services, app),
99
- 'sql'
100
- )
110
+ public getServiceInstance() {
111
+ return Services.callableInstance(this, 'sql')
101
112
  }
102
113
 
103
114
  /*----------------------------------
@@ -321,7 +332,7 @@ export default class SQL extends Service<Config, Hooks, Application> {
321
332
  data: TData,
322
333
  where?: (keyof TData)[] | TObjetDonnees,
323
334
  opts?: TUpdateQueryOptions<TData>
324
- ]): Promise<ResultSetHeader> {
335
+ ]): Promise<OkPacket> {
325
336
 
326
337
  let [tableName, data, where, opts] = args;
327
338
 
@@ -389,7 +400,7 @@ export default class SQL extends Service<Config, Hooks, Application> {
389
400
  path: string,
390
401
  data: TData | TData[],
391
402
  opts: TInsertQueryOptions<TData> = {}
392
- ): Promise<ResultSetHeader> {
403
+ ): Promise<OkPacket> {
393
404
 
394
405
  const table = this.database.getTable(path);
395
406
 
@@ -398,39 +409,54 @@ export default class SQL extends Service<Config, Hooks, Application> {
398
409
  data = [data];
399
410
  else if (data.length === 0) {
400
411
  console.warn(LogPrefix, `Insert nothing in ${path}. Cancelled.`);
401
- return {
402
- fieldCount: 0,
403
- affectedRows: 0,
404
- changedRows: 0,
405
- insertId: 0,
406
- serverStatus: 0,
407
- warningStatus: 0,
408
- info: undefined
409
- };
412
+ return emptyOkPacket;
410
413
  }
411
414
 
412
- let querySuffix: string = '';
413
-
414
415
  // Upsert
416
+ let upsertStatement: string = '';
415
417
  if (opts.upsert !== undefined)
416
- querySuffix += ' ' + this.buildUpsertStatement<TData>(table, opts as With<TInsertQueryOptions<TData>, 'upsert'>);
418
+ upsertStatement = ' ' + this.buildUpsertStatement<TData>(table, opts as With<TInsertQueryOptions<TData>, 'upsert'>);
417
419
 
418
420
  // Create basic insert query
419
- const query = this.buildInsertStatement(table, data, opts) + querySuffix;
420
-
421
- const queryResult = await this.database.query<mysql.OkPacket>(query + ';', opts);
422
-
423
- return {
424
- fieldCount: queryResult.fieldCount,
425
- affectedRows: queryResult.affectedRows,
426
- changedRows: queryResult.changedRows,
427
- insertId: queryResult.insertId,
428
- serverStatus: queryResult.serverStatus,
429
- warningCount: queryResult.warningCount,
430
- message: queryResult.message,
431
- procotol41: queryResult.procotol41,
432
- };
433
-
421
+ if (opts.bulk === false) {
422
+
423
+ const okPacket = { ...emptyOkPacket }
424
+
425
+ for (const row of data) {
426
+
427
+ const query = this.buildInsertStatement(table, [row], opts) + upsertStatement;
428
+
429
+ const queryResult = await this.database.query<mysql.OkPacket>(query + ';', opts)
430
+
431
+ okPacket.fieldCount += queryResult.fieldCount;
432
+ okPacket.affectedRows += queryResult.affectedRows;
433
+ okPacket.changedRows += queryResult.changedRows;
434
+ okPacket.insertId = queryResult.insertId;
435
+ okPacket.serverStatus += queryResult.serverStatus;
436
+ okPacket.warningCount += queryResult.warningCount;
437
+ }
438
+
439
+ return okPacket;
440
+
441
+ } else {
442
+ const query = this.buildInsertStatement(table, data, opts) + upsertStatement;
443
+
444
+ const queryResult = await this.database.query<mysql.OkPacket>(query + ';', opts);
445
+
446
+ return {
447
+ constructor: {
448
+ name: 'OkPacket'
449
+ },
450
+ fieldCount: queryResult.fieldCount,
451
+ affectedRows: queryResult.affectedRows,
452
+ changedRows: queryResult.changedRows,
453
+ insertId: queryResult.insertId,
454
+ serverStatus: queryResult.serverStatus,
455
+ warningCount: queryResult.warningCount,
456
+ message: queryResult.message,
457
+ procotol41: queryResult.procotol41,
458
+ };
459
+ }
434
460
  // OLD: return [data, queryResult?.insertId, queryResult];
435
461
  }
436
462
 
@@ -14,6 +14,10 @@ export type THooks = {
14
14
 
15
15
  }
16
16
 
17
+ export type Services = {
18
+
19
+ }
20
+
17
21
  /*----------------------------------
18
22
  - TYPE
19
23
  ----------------------------------*/
@@ -52,7 +56,7 @@ export type TReadFileOptions = {
52
56
  export default abstract class FsDriver<
53
57
  Config extends TDrivercnfig = TDrivercnfig,
54
58
  TBucketName = keyof Config["buckets"]
55
- > extends Service<Config, {}, Application> {
59
+ > extends Service<Config, {}, Application, Services> {
56
60
 
57
61
  public abstract mount(): Promise<void>;
58
62
 
@@ -53,7 +53,7 @@ export default class S3Driver<
53
53
  public constructor(
54
54
  public parent: AnyService,
55
55
  public config: TConfig,
56
- public services: TRegisteredServicesIndex,
56
+ public services: {},
57
57
  public app: Application
58
58
  ) {
59
59
 
@@ -24,25 +24,27 @@ export type Hooks = {
24
24
 
25
25
  }
26
26
 
27
- type TMountpointList = { [name: string]: Driver }
27
+ export type Services = {
28
+
29
+ }
28
30
 
29
31
  /*----------------------------------
30
32
  - SERVICE
31
33
  ----------------------------------*/
32
34
  export default class DisksManager<
33
- MountpointList extends TMountpointList = {},
35
+ MountpointList extends Services = {},
34
36
  TConfig extends Config = Config,
35
37
  TApplication extends Application = Application
36
- > extends Service<TConfig, Hooks, TApplication> {
38
+ > extends Service<TConfig, Hooks, TApplication, MountpointList> {
37
39
 
38
- public default: Driver;
40
+ public default!: Driver;
39
41
 
40
- public mounted: TMountpointList = this.services;
42
+ public mounted: MountpointList = this.services;
41
43
 
42
44
  public constructor(
43
45
  parent: AnyService,
44
46
  config: TConfig,
45
- drivers: TRegisteredServicesIndex< Driver >,
47
+ drivers: MountpointList,
46
48
  app: TApplication,
47
49
  ) {
48
50
 
@@ -51,6 +53,14 @@ export default class DisksManager<
51
53
  if (Object.keys( drivers ).length === 0)
52
54
  throw new Error("At least one disk driver should be mounted.");
53
55
 
56
+ console.log('start disks service', Object.keys( drivers ), Object.keys( this.mounted ), Object.keys( this.services ));
57
+
58
+ const defaultDisk = drivers[ this.config.default ];
59
+ if (defaultDisk === undefined)
60
+ console.log(`Default disk "${this.config.default as string}" not mounted.`);
61
+
62
+ this.default = defaultDisk;
63
+
54
64
  }
55
65
 
56
66
  /*----------------------------------
@@ -58,12 +68,6 @@ export default class DisksManager<
58
68
  ----------------------------------*/
59
69
 
60
70
  public async start() {
61
-
62
- const defaultDisk = this.mounted[ this.config.default ];
63
- if (defaultDisk === undefined)
64
- console.log(`Default disk "${this.config.default as string}" not mounted.`);
65
-
66
- this.default = defaultDisk;
67
71
 
68
72
  }
69
73
 
@@ -41,6 +41,10 @@ export type Hooks = {
41
41
 
42
42
  }
43
43
 
44
+ export type Services = {
45
+
46
+ }
47
+
44
48
  /*----------------------------------
45
49
  - TYPES: EMAILS
46
50
  ----------------------------------*/
@@ -105,7 +109,7 @@ type TOptions = {
105
109
  /*----------------------------------
106
110
  - FONCTIONS
107
111
  ----------------------------------*/
108
- export default class Email extends Service<Config, Hooks, Application> {
112
+ export default class Email extends Service<Config, Hooks, Application, Services> {
109
113
 
110
114
  private transporters = this.config.transporters;
111
115
 
@@ -65,9 +65,6 @@ export default class FetchService extends Service<Config, Hooks, Application, Se
65
65
  ) {
66
66
 
67
67
  super(parent, config, services, app);
68
-
69
- if (this.services.disks)
70
- this.disk = this.services.disks.get( config.disk );
71
68
 
72
69
  }
73
70
 
@@ -77,7 +74,8 @@ export default class FetchService extends Service<Config, Hooks, Application, Se
77
74
 
78
75
  public async start() {
79
76
 
80
-
77
+ if (this.services.disks)
78
+ this.disk = this.services.disks.get( this.config.disk );
81
79
 
82
80
 
83
81
  }
@@ -114,7 +112,7 @@ export default class FetchService extends Service<Config, Hooks, Application, Se
114
112
  { width, height, fit, quality }: TImageConfig,
115
113
  saveToBucket: string,
116
114
  saveToPath?: string,
117
- disk?: FsDriver
115
+ disk?: string
118
116
  ): Promise<Buffer | null> {
119
117
 
120
118
  // Define target disk
@@ -96,7 +96,9 @@ export type Config<
96
96
 
97
97
  export type Services = {
98
98
  disks?: DisksManager
99
- } & TRegisteredServicesIndex< RouterService<ServerRouter> >
99
+ } & {
100
+ [routerServiceId: string]: RouterService
101
+ }
100
102
 
101
103
  // Set it as a function, so when we instanciate the services, we can callthis.router to pass the router instance in roiuter services
102
104
  type TRouterServicesList = {
@@ -132,7 +134,7 @@ export default class ServerRouter<
132
134
  public constructor(
133
135
  parent: AnyService,
134
136
  config: TConfig,
135
- services: TRegisteredServicesIndex<RouterService>,
137
+ services: Services,
136
138
  app: TApplication,
137
139
  ) {
138
140
 
@@ -156,8 +158,6 @@ export default class ServerRouter<
156
158
  if (routerService instanceof RouterService)
157
159
  this.routerServices[ serviceName ] = routerService;
158
160
  }
159
-
160
- console.log("this.routerServices", Object.keys( this.routerServices ));
161
161
  }
162
162
 
163
163
  public async ready() {
@@ -11,16 +11,22 @@ import type { default as Router } from '.';
11
11
  import type ServerRequest from './request';
12
12
  import type RequestService from './request/service';
13
13
 
14
+ export type Services = {
15
+
16
+ }
17
+
14
18
  /*----------------------------------
15
19
  - SERVICE
16
20
  ----------------------------------*/
17
21
  export default abstract class RouterService<
18
22
  TConfig extends {} = {}
19
- > extends Service<TConfig, {}, Application> {
23
+ > extends Service<TConfig, {}, Application, Services> {
20
24
 
21
25
  public constructor(
22
26
  // Parent is always a router in RouterService
23
- public router: Router,
27
+ // Warning: for now, it's possible that router is actually the app
28
+ // It's fixed with a not very clean way in Service.bindService
29
+ router: Router,
24
30
  config: TConfig,
25
31
  services: TRegisteredServicesIndex,
26
32
  app: Application
@@ -30,6 +36,6 @@ export default abstract class RouterService<
30
36
 
31
37
  }
32
38
 
33
- public abstract requestService( request: ServerRequest<TRouter> ): RequestService | null;
39
+ public abstract requestService( request: ServerRequest<Router> ): RequestService | null;
34
40
 
35
41
  }
@@ -10,11 +10,6 @@ import type { Application } from '@server/app';
10
10
  import Service from '@server/app/service';
11
11
  import { Forbidden } from '@common/errors';
12
12
 
13
- /*----------------------------------
14
- - CONFIG
15
- ----------------------------------*/
16
-
17
-
18
13
  /*----------------------------------
19
14
  - SERVICE CONFIG
20
15
  ----------------------------------*/
@@ -33,6 +28,14 @@ export type Hooks = {
33
28
 
34
29
  }
35
30
 
31
+ export type Services = {
32
+
33
+ }
34
+
35
+ /*----------------------------------
36
+ - TYPES
37
+ ----------------------------------*/
38
+
36
39
  type TEncryptOptions = {
37
40
  encoding: Encoding
38
41
  }
@@ -44,7 +47,7 @@ type TDecryptOptions = {
44
47
  /*----------------------------------
45
48
  - SERVICE
46
49
  ----------------------------------*/
47
- export default class AES<TConfig extends Config = Config> extends Service<TConfig, Hooks, Application> {
50
+ export default class AES<TConfig extends Config = Config> extends Service<TConfig, Hooks, Application, Services> {
48
51
 
49
52
  /*----------------------------------
50
53
  - LIFECYCLE
@@ -32,36 +32,39 @@ export type Hooks = {
32
32
 
33
33
  }
34
34
 
35
+ export type Services<TUser extends {}> = {
36
+ users: UsersManagementService<TUser, Application>,
37
+ router: Router
38
+ }
39
+
35
40
  /*----------------------------------
36
41
  - MANAGER
37
42
  ----------------------------------*/
38
43
  export default class WebSocketCommander<
39
44
  TUser extends {},
40
- TConfig extends Config<TUser>= Config<TUser>
41
- > extends Service<TConfig, Hooks, Application> {
45
+ TConfig extends Config<TUser>= Config<TUser>,
46
+ TServices extends Services<TUser> = Services<TUser>
47
+ > extends Service<TConfig, Hooks, Application, TServices> {
42
48
 
43
49
  // Services
44
50
  public ws!: WebSocketServer;
45
- public users: UsersManagementService<TUser>;
46
- public router: Router;
51
+ public users!: TServices["users"];
52
+ public router!: Router;
47
53
 
48
54
  // Context
49
55
  public scopes: {[path: string]: SocketScope<TUser>} = {}
50
56
 
51
- public constructor(
57
+ public constructor(
52
58
  parent: AnyService,
53
59
  config: TConfig,
54
- services: {
55
- users: TRegisteredService< UsersManagementService<TUser> >,
56
- router: Router
57
- },
58
- app: Application,
60
+ services: Services,
61
+ app: TApplication,
59
62
  ) {
60
-
61
63
  super(parent, config, services, app);
62
-
64
+
63
65
  this.users = this.services.users;
64
66
  this.router = this.services.router;
67
+
65
68
  }
66
69
 
67
70
  /*----------------------------------
@@ -71,10 +74,6 @@ export default class WebSocketCommander<
71
74
  public loading: Promise<void> | undefined = undefined;
72
75
  protected async start() {
73
76
 
74
- this.app.on('cleanup', async () => {
75
- this.closeAll();
76
- });
77
-
78
77
  this.users.on('disconnect', async (userId: string) => {
79
78
  this.disconnect(userId, 'Logout');
80
79
  });
@@ -126,7 +125,7 @@ export default class WebSocketCommander<
126
125
  }
127
126
 
128
127
  public async shutdown() {
129
-
128
+ this.closeAll();
130
129
  }
131
130
 
132
131
  /*----------------------------------
@@ -58,6 +58,10 @@ export type THooks = {
58
58
 
59
59
  }
60
60
 
61
+ export type TServices = {
62
+
63
+ }
64
+
61
65
  /*----------------------------------
62
66
  - SERVICE
63
67
  ----------------------------------*/
@@ -66,7 +70,7 @@ export default abstract class UsersManagementService<
66
70
  TApplication extends Application,
67
71
  TJwtSession extends {} = {},
68
72
  TRequest extends ServerRequest<Router> = ServerRequest<Router>,
69
- > extends Service<TConfig, THooks, TApplication> {
73
+ > extends Service<TConfig, THooks, TApplication, TServices> {
70
74
 
71
75
  /*----------------------------------
72
76
  - LIFECYCLE
@@ -42,7 +42,7 @@ export default class AuthenticationRouterService<
42
42
  protected async start() {
43
43
 
44
44
  // Decode current user
45
- this.router.on('request', async (request: TRequest) => {
45
+ this.parent.on('request', async (request: TRequest) => {
46
46
 
47
47
  // TODO: Typings. (context.user ?)
48
48
  const decoded = await this.services.users.decode( request.req, true);
@@ -51,7 +51,7 @@ export default class AuthenticationRouterService<
51
51
  })
52
52
 
53
53
  // Check route permissions
54
- this.router.on('resolved', async (route: TAnyRoute, request: TRequest) => {
54
+ this.parent.on('resolved', async (route: TAnyRoute, request: TRequest) => {
55
55
 
56
56
  if (route.options.auth !== undefined)
57
57
  // TODO: How to pas the router type to router config ? Circular rfeerence ?
@@ -1,3 +0,0 @@
1
- import Application from '@/server';
2
-
3
- export default new Application