5htp-core 0.2.9 → 0.3.0
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 +7 -5
- package/src/client/app/component.tsx +2 -2
- package/src/client/assets/css/text/titres.less +4 -0
- package/src/client/components/Dialog/card.tsx +1 -1
- package/src/client/components/Dialog/index.less +3 -3
- package/src/client/components/inputv3/date/index.tsx +1 -1
- package/src/client/components/inputv3/index.tsx +14 -4
- package/src/client/pages/_layout/index.tsx +3 -3
- package/src/client/pages/_messages/400.tsx +2 -2
- package/src/client/pages/_messages/401.tsx +2 -2
- package/src/client/pages/_messages/403.tsx +2 -2
- package/src/client/pages/_messages/404.tsx +2 -2
- package/src/client/pages/_messages/500.tsx +2 -2
- package/src/client/pages/useHeader.tsx +2 -1
- package/src/client/services/router/components/router.tsx +1 -1
- package/src/client/services/router/index.tsx +5 -1
- package/src/client/services/router/response/index.tsx +5 -2
- package/src/common/data/objets.ts +0 -25
- package/src/common/router/index.ts +8 -12
- package/src/common/router/layouts.ts +3 -1
- package/src/common/router/register.ts +5 -3
- package/src/common/router/response/index.ts +3 -3
- package/src/server/app/commands.ts +2 -11
- package/src/server/app/{config.ts → container/config.ts} +4 -1
- package/src/server/app/container/index.ts +84 -0
- package/src/server/app/index.ts +73 -136
- package/src/server/app/instance.ts +3 -0
- package/src/server/app/service/container.ts +136 -0
- package/src/server/app/service/index.ts +219 -0
- package/src/server/index.ts +9 -3
- package/src/server/services/cache/index.ts +42 -16
- package/src/server/services/cache/service.json +6 -0
- package/src/server/services/console/index.ts +35 -22
- package/src/server/services/console/service.json +6 -0
- package/src/server/services/cron/index.ts +11 -11
- package/src/server/services/cron/service.json +6 -0
- package/src/server/services/database/connection.ts +26 -34
- package/src/server/services/database/index.ts +41 -21
- package/src/server/services/database/metas.ts +12 -7
- package/src/server/services/database/repository.ts +0 -9
- package/src/server/services/database/service.json +6 -0
- package/src/server/services/database/stats.ts +3 -3
- package/src/server/services/disks/driver.ts +9 -7
- package/src/server/services/disks/drivers/local/index.ts +199 -0
- package/src/server/services/disks/drivers/local/service.json +6 -0
- package/src/server/services/disks/drivers/s3/index.ts +282 -0
- package/src/server/services/disks/drivers/s3/service.json +6 -0
- package/src/server/services/disks/index.ts +47 -16
- package/src/server/services/disks/service.json +6 -0
- package/src/server/services/email/index.ts +16 -4
- package/src/server/services/email/service.json +6 -0
- package/src/server/services/fetch/index.ts +52 -8
- package/src/server/services/fetch/service.json +6 -0
- package/src/server/services/router/http/index.ts +12 -9
- package/src/server/services/router/index.ts +95 -68
- package/src/server/services/router/request/api.ts +1 -1
- package/src/server/services/router/response/index.ts +13 -19
- package/src/server/services/router/service.json +6 -0
- package/src/server/services/router/service.ts +16 -29
- package/src/server/services/schema/{router.ts → router/index.ts} +19 -7
- package/src/server/services/schema/router/service.json +6 -0
- package/src/server/services/schema/service.json +6 -0
- package/src/server/services/security/encrypt/{aes.ts → aes/index.ts} +16 -3
- package/src/server/services/security/encrypt/aes/service.json +6 -0
- package/src/server/services/socket/index.ts +39 -13
- package/src/server/services/socket/service.json +6 -0
- package/src/server/services/users/index.ts +16 -4
- package/src/server/services/users/old.ts +1 -1
- package/src/server/services/users/router/index.ts +21 -14
- package/src/server/services/users/router/service.json +6 -0
- package/src/server/services/users/service.json +6 -0
- package/src/types/aliases.d.ts +5 -0
- package/tsconfig.common.json +2 -0
- package/src/server/app/service.ts +0 -109
- /package/src/server/{patch.ts → app/container/patch.ts} +0 -0
package/src/server/app/index.ts
CHANGED
|
@@ -2,26 +2,20 @@
|
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
|
-
import '../patch';
|
|
6
|
-
|
|
7
|
-
// Npm
|
|
8
|
-
import fs from 'fs-extra';
|
|
9
|
-
|
|
10
5
|
// Core
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
6
|
+
import AppContainer from './container';
|
|
7
|
+
import ApplicationService, { AnyService } from './service';
|
|
13
8
|
import CommandsManager from './commands';
|
|
9
|
+
import ServicesContainer, { TRegisteredService, TServiceMetas } from './service/container';
|
|
14
10
|
|
|
15
11
|
// Built-in
|
|
16
12
|
import type { default as Router, Request as ServerRequest } from '@server/services/router';
|
|
13
|
+
export { default as Services } from './service/container';
|
|
17
14
|
|
|
18
15
|
/*----------------------------------
|
|
19
16
|
- TYPES
|
|
20
17
|
----------------------------------*/
|
|
21
18
|
|
|
22
|
-
export { default as Service } from './service';
|
|
23
|
-
export type { TPriority } from './service';
|
|
24
|
-
|
|
25
19
|
type Config = {
|
|
26
20
|
|
|
27
21
|
}
|
|
@@ -49,80 +43,62 @@ declare global {
|
|
|
49
43
|
interface User { }
|
|
50
44
|
}
|
|
51
45
|
|
|
46
|
+
export const Service = ServicesContainer;
|
|
47
|
+
|
|
52
48
|
/*----------------------------------
|
|
53
49
|
- FUNCTIONS
|
|
54
50
|
----------------------------------*/
|
|
55
|
-
export
|
|
51
|
+
export class Application extends ApplicationService<Config, Hooks, /* TODO: this ? */Application> {
|
|
56
52
|
|
|
57
53
|
/*----------------------------------
|
|
58
54
|
- PROPERTIES
|
|
59
55
|
----------------------------------*/
|
|
60
56
|
|
|
61
57
|
public side = 'server' as 'server';
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
public: process.cwd() + '/public',
|
|
69
|
-
|
|
70
|
-
// TODO: move to disk
|
|
71
|
-
var: process.cwd() + '/var',
|
|
72
|
-
typings: process.cwd() + '/var/typings',
|
|
73
|
-
cache: process.cwd() + '/var/cache',
|
|
74
|
-
data: process.cwd() + '/var/data',
|
|
75
|
-
log: process.cwd() + '/var/log',
|
|
58
|
+
public metas: TServiceMetas = {
|
|
59
|
+
id: 'application',
|
|
60
|
+
name: 'Application',
|
|
61
|
+
parent: 'root',
|
|
62
|
+
dependences: [],
|
|
63
|
+
class: () => ({ 'default': Application })
|
|
76
64
|
}
|
|
77
65
|
|
|
78
|
-
|
|
66
|
+
// Shortcuts to ApplicationContainer
|
|
67
|
+
public container = AppContainer;
|
|
68
|
+
public env = AppContainer.Environment;
|
|
69
|
+
public identity = AppContainer.Identity;
|
|
79
70
|
|
|
80
71
|
// Status
|
|
72
|
+
public debug: boolean = false;
|
|
81
73
|
public launched: boolean = false;
|
|
82
|
-
public status = {
|
|
83
|
-
services: false
|
|
84
|
-
}
|
|
85
74
|
|
|
86
|
-
|
|
75
|
+
// All service instances by service id
|
|
76
|
+
public allServices: {[serviceId: string]: AnyService} = {}
|
|
87
77
|
|
|
88
78
|
/*----------------------------------
|
|
89
79
|
- INIT
|
|
90
80
|
----------------------------------*/
|
|
91
81
|
|
|
92
|
-
public env: TEnvConfig;
|
|
93
|
-
public identity: Config.Identity;
|
|
94
|
-
|
|
95
82
|
public constructor() {
|
|
96
83
|
|
|
97
|
-
|
|
84
|
+
// Application itself doesnt have configuration
|
|
85
|
+
// Configuration must be handled by application services
|
|
86
|
+
super({}, {}, {}, {});
|
|
87
|
+
|
|
88
|
+
this.app = this;
|
|
98
89
|
|
|
99
90
|
// Gestion crash
|
|
100
91
|
process.on('unhandledRejection', (error: any, promise: any) => {
|
|
101
92
|
// We don't log the error here because it's the role of the app to decidehiw to log errors
|
|
102
93
|
this.runHook('error', error);
|
|
103
94
|
});
|
|
104
|
-
|
|
105
|
-
// Load config files
|
|
106
|
-
const configParser = new ConfigParser( this.path.root );
|
|
107
|
-
this.env = configParser.env();
|
|
108
|
-
this.identity = configParser.identity();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/*----------------------------------
|
|
112
|
-
- REGISTER
|
|
113
|
-
----------------------------------*/
|
|
114
|
-
|
|
115
|
-
// Require a service at file scope
|
|
116
|
-
// Only use in files where a service is strictly required
|
|
117
|
-
public use<ServiceType extends Service<{}, {}, this>>( serviceName: string ): ServiceType | undefined {
|
|
118
|
-
return this[ serviceName ];
|
|
119
95
|
}
|
|
120
96
|
|
|
121
97
|
/*----------------------------------
|
|
122
98
|
- COMMANDS
|
|
123
99
|
----------------------------------*/
|
|
124
100
|
|
|
125
|
-
private commandsManager = new CommandsManager(this, { debug: true });
|
|
101
|
+
private commandsManager = new CommandsManager(this, { debug: true }, {}, this);
|
|
126
102
|
|
|
127
103
|
public command( ...args: Parameters<CommandsManager["command"]> ) {
|
|
128
104
|
return this.commandsManager.command(...args);
|
|
@@ -131,119 +107,80 @@ export default abstract class Application extends Service<Config, Hooks, /* TODO
|
|
|
131
107
|
/*----------------------------------
|
|
132
108
|
- LAUNCH
|
|
133
109
|
----------------------------------*/
|
|
134
|
-
|
|
135
|
-
public async register() {
|
|
136
|
-
|
|
137
|
-
}
|
|
138
110
|
|
|
139
|
-
|
|
111
|
+
protected async start() {
|
|
140
112
|
|
|
141
113
|
console.log(`5HTP Core`, process.env.npm_package_version);
|
|
142
114
|
|
|
143
|
-
|
|
144
|
-
|
|
115
|
+
// Handle errors & crashs
|
|
116
|
+
this.on('error', this.error.bind(this))
|
|
145
117
|
|
|
146
|
-
console.info(`[boot] Start services`);
|
|
147
|
-
await this.startServices()
|
|
118
|
+
this.debug && console.info(`[boot] Start services`);
|
|
119
|
+
await this.startServices();
|
|
148
120
|
|
|
149
|
-
console.info(`[boot] App ready`);
|
|
121
|
+
this.debug && console.info(`[boot] App ready`);
|
|
122
|
+
await this.ready();
|
|
150
123
|
await this.runHook('ready');
|
|
151
124
|
|
|
152
|
-
console.info(`[boot]
|
|
153
|
-
await this.boot();
|
|
154
|
-
|
|
155
|
-
console.info(`[boot] Application is ready.`);
|
|
125
|
+
this.debug && console.info(`[boot] Application is ready.`);
|
|
156
126
|
this.launched = true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public async ready() {
|
|
130
|
+
|
|
157
131
|
|
|
158
132
|
}
|
|
159
133
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
fs.ensureDir( this.path.cache ),
|
|
164
|
-
fs.ensureDir( this.path.log ),
|
|
165
|
-
fs.ensureDir( this.path.data ),
|
|
166
|
-
]);
|
|
134
|
+
// Default error handler
|
|
135
|
+
public async error( e: Error ) {
|
|
136
|
+
console.error( e );
|
|
167
137
|
}
|
|
168
138
|
|
|
169
|
-
public
|
|
170
|
-
|
|
171
|
-
this.servicesList.push(service);
|
|
139
|
+
public async shutdown() {
|
|
140
|
+
|
|
172
141
|
}
|
|
173
142
|
|
|
143
|
+
// TODO: move to servie class
|
|
144
|
+
// So we can do public myService = Services.use() inside service class ded
|
|
174
145
|
public async startServices() {
|
|
175
146
|
|
|
176
|
-
|
|
177
|
-
|
|
147
|
+
const propsNames = Object.getOwnPropertyNames(this);
|
|
148
|
+
|
|
149
|
+
for (const propName of propsNames) {
|
|
150
|
+
|
|
151
|
+
// Don't check services prop as it will trigger an error (it's a proxy)
|
|
152
|
+
// TODO: exclude all properties coming from the Service class itself
|
|
153
|
+
if (propName === 'services' || typeof this[ propName ] !== 'object')
|
|
154
|
+
continue;
|
|
178
155
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
service.status = 'starting';
|
|
156
|
+
// Check if this property is a service registration
|
|
157
|
+
const registered = this[ propName ] as TRegisteredService;
|
|
158
|
+
if (registered?.type !== 'service')
|
|
159
|
+
continue;
|
|
184
160
|
|
|
185
|
-
|
|
186
|
-
|
|
161
|
+
// Instanciate the service
|
|
162
|
+
const service = this.registerService( propName, registered );
|
|
163
|
+
this[ propName ] = service;
|
|
187
164
|
|
|
188
165
|
// Register commands
|
|
189
166
|
if (service.commands)
|
|
190
|
-
this.commandsManager.fromList(service.commands);
|
|
167
|
+
this.commandsManager.fromList( service.commands );
|
|
191
168
|
|
|
192
169
|
// Start service
|
|
193
|
-
|
|
194
|
-
service.started = service.start();
|
|
195
|
-
await service.started.catch(e => {
|
|
196
|
-
console.error("Catched error while starting service " + serviceClassName + '. Exiting process if mode production.', e);
|
|
197
|
-
if (this.env.profile === 'prod')
|
|
198
|
-
process.exit();
|
|
199
|
-
else
|
|
200
|
-
throw e;
|
|
201
|
-
})
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
service.status = 'running';
|
|
170
|
+
await this.startService( service );
|
|
205
171
|
}
|
|
206
172
|
|
|
207
|
-
|
|
173
|
+
// Check if any setup service has not been used
|
|
174
|
+
const unused: string[] = []
|
|
175
|
+
for (const serviceNS in ServicesContainer.registered)
|
|
176
|
+
if (this.allServices[ serviceNS ] === undefined)
|
|
177
|
+
unused.push(serviceNS);
|
|
178
|
+
|
|
179
|
+
if (unused.length !== 0)
|
|
180
|
+
console.warn(`${unused.length} services were setup, but are not used anywhere:`,
|
|
181
|
+
unused.join(', '));
|
|
208
182
|
}
|
|
209
183
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
// TODO: make it work
|
|
213
|
-
private activateHMR() {
|
|
214
|
-
|
|
215
|
-
if (!module.hot) return;
|
|
216
|
-
|
|
217
|
-
console.info(`Activating HMR ...`);
|
|
218
|
-
|
|
219
|
-
module.hot.accept();
|
|
220
|
-
module.hot.accept( this.path.root + '/.cache/commun/routes.ts' );
|
|
221
|
-
|
|
222
|
-
module.hot.addDisposeHandler((data) => {
|
|
223
|
-
|
|
224
|
-
console.info(`Cleaning application ...`);
|
|
225
|
-
|
|
226
|
-
// Services hooks
|
|
227
|
-
/*for (const id in this.services) {
|
|
228
|
-
const service = this.services[id]
|
|
229
|
-
if (service.cleanup) {
|
|
230
|
-
console.info(`Cleaning ${id} service ...`);
|
|
231
|
-
service.cleanup();
|
|
232
|
-
}
|
|
233
|
-
}*/
|
|
234
|
-
|
|
235
|
-
// Application specific hooks
|
|
236
|
-
this.runHook('cleanup');
|
|
237
|
-
|
|
238
|
-
/*
|
|
239
|
-
console.log("[nettoyage] Arrêt serveur socket ...");
|
|
240
|
-
if (socket !== undefined)
|
|
241
|
-
socket.serveur.close()
|
|
242
|
-
|
|
243
|
-
console.log("[nettoyage] Reset du cache requêtes JSQL ...");
|
|
244
|
-
QueryParser.clearCache();*/
|
|
245
|
-
|
|
246
|
-
});
|
|
247
|
-
}
|
|
184
|
+
}
|
|
248
185
|
|
|
249
|
-
|
|
186
|
+
export default Application
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Specific
|
|
6
|
+
import type {
|
|
7
|
+
AnyService,
|
|
8
|
+
// Hooks
|
|
9
|
+
THookCallback, THooksIndex
|
|
10
|
+
} from ".";
|
|
11
|
+
|
|
12
|
+
import ServicesIndex from '@/server/.generated/services';
|
|
13
|
+
|
|
14
|
+
/*----------------------------------
|
|
15
|
+
- TYPES: REGISTRATION
|
|
16
|
+
----------------------------------*/
|
|
17
|
+
|
|
18
|
+
// From service/service.json
|
|
19
|
+
export type TServiceMetas<TServiceClass extends AnyService = AnyService> = {
|
|
20
|
+
id: string,
|
|
21
|
+
name: string,
|
|
22
|
+
parent: string,
|
|
23
|
+
dependences: string[],
|
|
24
|
+
class: () => { default: ClassType<TServiceClass> }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type TRegisteredService<TServiceClass extends AnyService = AnyService> = {
|
|
28
|
+
type: 'service', // Used to recognize if an object is a registered service
|
|
29
|
+
config?: {},
|
|
30
|
+
metas: TServiceMetas<TServiceClass>,
|
|
31
|
+
hooks: THooksIndex<{}>,
|
|
32
|
+
on: (hookName: string, hookFunc: THookCallback<any>) => void,
|
|
33
|
+
subServices: TRegisteredServicesIndex
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type TRegisteredServicesIndex<TServiceClass extends AnyService = AnyService> = {
|
|
37
|
+
[serviceId: string]: TRegisteredService<TServiceClass>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/*----------------------------------
|
|
41
|
+
- CONFIG
|
|
42
|
+
----------------------------------*/
|
|
43
|
+
|
|
44
|
+
const LogPrefix = '[service]';
|
|
45
|
+
|
|
46
|
+
/*----------------------------------
|
|
47
|
+
- CLASS
|
|
48
|
+
----------------------------------*/
|
|
49
|
+
export class ServicesContainer {
|
|
50
|
+
|
|
51
|
+
public registered: TRegisteredServicesIndex = {}
|
|
52
|
+
|
|
53
|
+
public setup(
|
|
54
|
+
serviceId: string,
|
|
55
|
+
serviceConfig: {}
|
|
56
|
+
): TRegisteredService {
|
|
57
|
+
|
|
58
|
+
// Check if the service is installed & has been indexed
|
|
59
|
+
const installedService = ServicesIndex[ serviceId ];
|
|
60
|
+
if (installedService === undefined)
|
|
61
|
+
throw new Error(`Unable to setup service "${serviceId}": This one doesn't seems to be installed.`);
|
|
62
|
+
|
|
63
|
+
// Service already registered & setup
|
|
64
|
+
const registered = this.registered[ serviceId ];
|
|
65
|
+
if (registered !== undefined)
|
|
66
|
+
throw new Error(`Service ${serviceId} has been setup multiple times. One service can only be setup one time.`);
|
|
67
|
+
|
|
68
|
+
// Create & register metas
|
|
69
|
+
const service: TRegisteredService = {
|
|
70
|
+
type: 'service',
|
|
71
|
+
config: serviceConfig,
|
|
72
|
+
metas: installedService,
|
|
73
|
+
hooks: {},
|
|
74
|
+
on: (hookName: string, hookFunc: THookCallback<any>) => {
|
|
75
|
+
const callbacks = service.hooks[ hookName ];
|
|
76
|
+
if (callbacks)
|
|
77
|
+
callbacks.push( hookFunc );
|
|
78
|
+
else
|
|
79
|
+
service.hooks[ hookName ] = [hookFunc]
|
|
80
|
+
},
|
|
81
|
+
subServices: {}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
this.registered[ serviceId ] = service;
|
|
85
|
+
|
|
86
|
+
return service;
|
|
87
|
+
}
|
|
88
|
+
|
|
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
|
+
public callableInstance = <TInstance extends object, TCallableName extends keyof TInstance>(
|
|
109
|
+
instance: TInstance,
|
|
110
|
+
funcName: TCallableName
|
|
111
|
+
): TInstance[TCallableName] & TInstance => {
|
|
112
|
+
|
|
113
|
+
const callableFunc = instance[funcName];
|
|
114
|
+
if (typeof callableFunc !== 'function')
|
|
115
|
+
throw new Error(`instance[funcName] isn't callable.`);
|
|
116
|
+
|
|
117
|
+
const callable = callableFunc.bind(instance);
|
|
118
|
+
|
|
119
|
+
const methods = [
|
|
120
|
+
...Object.getOwnPropertyNames( Object.getPrototypeOf( instance )),
|
|
121
|
+
...Object.getOwnPropertyNames( instance ),
|
|
122
|
+
// service.launch() isn't included, maybe because parent abstract class
|
|
123
|
+
'launch'
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
for (const method of methods)
|
|
127
|
+
if (method !== 'constructor')
|
|
128
|
+
callable[ method ] = typeof instance[ method ] === 'function'
|
|
129
|
+
? instance[ method ].bind( instance )
|
|
130
|
+
: instance[ method ];
|
|
131
|
+
|
|
132
|
+
return callable as TInstance[TCallableName] & TInstance;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default new ServicesContainer
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Specific
|
|
6
|
+
import type { Application } from "..";
|
|
7
|
+
import type { Command } from "../commands";
|
|
8
|
+
import type { TServiceMetas, TRegisteredServicesIndex, TRegisteredService } from './container';
|
|
9
|
+
|
|
10
|
+
/*----------------------------------
|
|
11
|
+
- TYPES: OPTIONS
|
|
12
|
+
----------------------------------*/
|
|
13
|
+
|
|
14
|
+
export type AnyService = Service<{}, {}, Application, TStartedServicesIndex>
|
|
15
|
+
|
|
16
|
+
type TServiceConfig = {
|
|
17
|
+
debug?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type { TRegisteredServicesIndex, TRegisteredService } from './container';
|
|
21
|
+
|
|
22
|
+
/*----------------------------------
|
|
23
|
+
- TYPES: HOOKS
|
|
24
|
+
----------------------------------*/
|
|
25
|
+
|
|
26
|
+
export type THookCallback<THookArgs extends THookOptions> = (...args: THookArgs["args"]) => Promise<void>;
|
|
27
|
+
|
|
28
|
+
type THooksList = {
|
|
29
|
+
[hookName: string]: THookOptions
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type THookOptions = {
|
|
33
|
+
args: any[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type THooksIndex<THooks extends THooksList> = {[name in keyof THooks]?: THookCallback< THooks[name] >[]}
|
|
37
|
+
|
|
38
|
+
export type StartedServicesIndex = {
|
|
39
|
+
[serviceId: string]: AnyService
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/*----------------------------------
|
|
43
|
+
- CONFIG
|
|
44
|
+
----------------------------------*/
|
|
45
|
+
|
|
46
|
+
const LogPrefix = '[service]';
|
|
47
|
+
|
|
48
|
+
/*----------------------------------
|
|
49
|
+
- CLASS
|
|
50
|
+
----------------------------------*/
|
|
51
|
+
export default abstract class Service<
|
|
52
|
+
TConfig extends TServiceConfig,
|
|
53
|
+
THooks extends THooksList,
|
|
54
|
+
TApplication extends Application,
|
|
55
|
+
TServicesIndex extends StartedServicesIndex = StartedServicesIndex
|
|
56
|
+
> {
|
|
57
|
+
|
|
58
|
+
public started?: Promise<void>;
|
|
59
|
+
public status: 'stopped' | 'starting' | 'running' | 'paused' = 'stopped';
|
|
60
|
+
|
|
61
|
+
public commands?: Command[];
|
|
62
|
+
public metas!: TServiceMetas;
|
|
63
|
+
public bindings: string[] = []
|
|
64
|
+
|
|
65
|
+
public static createInstance?: (
|
|
66
|
+
parent: AnyService,
|
|
67
|
+
config: TServiceConfig,
|
|
68
|
+
services: TRegisteredServicesIndex,
|
|
69
|
+
app: Application
|
|
70
|
+
) => Service<TServiceConfig, THooksList, Application, StartedServicesIndex>;
|
|
71
|
+
|
|
72
|
+
public constructor(
|
|
73
|
+
public parent: AnyService,
|
|
74
|
+
public config: TConfig,
|
|
75
|
+
services: TRegisteredServicesIndex,
|
|
76
|
+
public app: TApplication
|
|
77
|
+
) {
|
|
78
|
+
|
|
79
|
+
// Instanciate subservices
|
|
80
|
+
for (const localName in services)
|
|
81
|
+
this.registerService( localName, services[localName] );
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/*----------------------------------
|
|
86
|
+
- LIFECYCLE
|
|
87
|
+
----------------------------------*/
|
|
88
|
+
|
|
89
|
+
public async launch() {
|
|
90
|
+
|
|
91
|
+
// Instanciate subservices
|
|
92
|
+
for (const localName in this.services)
|
|
93
|
+
await this.startService( this.services[localName] );
|
|
94
|
+
|
|
95
|
+
// Start service
|
|
96
|
+
if (this.start)
|
|
97
|
+
await this.start();
|
|
98
|
+
|
|
99
|
+
// Bind app events
|
|
100
|
+
this.app.on('ready', this.ready.bind(this))
|
|
101
|
+
this.app.on('shutdown', this.shutdown.bind(this))
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected abstract start(): Promise<void>;
|
|
106
|
+
|
|
107
|
+
protected abstract ready(): Promise<void>;
|
|
108
|
+
|
|
109
|
+
protected abstract shutdown(): Promise<void>;
|
|
110
|
+
|
|
111
|
+
/*----------------------------------
|
|
112
|
+
- SUBSERVICES
|
|
113
|
+
----------------------------------*/
|
|
114
|
+
|
|
115
|
+
public services: TRegisteredServicesIndex = {} as TRegisteredServicesIndex/*new Proxy({}, {
|
|
116
|
+
get: (target, prop, recever) => {
|
|
117
|
+
if (!( prop in target )) {
|
|
118
|
+
|
|
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(', '));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}) as TRegisteredServicesIndex*/
|
|
123
|
+
|
|
124
|
+
protected registerService( localName: string, registered: TRegisteredService ): AnyService {
|
|
125
|
+
|
|
126
|
+
// Service already instabciates on the app scope
|
|
127
|
+
let service = this.app.allServices[ registered.metas.id ];
|
|
128
|
+
|
|
129
|
+
// Service not yet instanciated
|
|
130
|
+
if (service === undefined) {
|
|
131
|
+
|
|
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);
|
|
139
|
+
|
|
140
|
+
service.metas = registered.metas;
|
|
141
|
+
|
|
142
|
+
} else {
|
|
143
|
+
console.warn(`[app] Service`, registered.metas.id, 'already instanciated in this app.');
|
|
144
|
+
}
|
|
145
|
+
|
|
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;
|
|
150
|
+
|
|
151
|
+
return service;
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
protected async startService( service: AnyService ) {
|
|
156
|
+
|
|
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
|
+
})
|
|
172
|
+
|
|
173
|
+
console.log(`[app] Service`, service.metas.id, 'started (bound to:', service.bindings.join(', '),')');
|
|
174
|
+
service.status = 'running';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/*----------------------------------
|
|
178
|
+
- HOOKS
|
|
179
|
+
----------------------------------*/
|
|
180
|
+
public hooks: THooksIndex<THooks> = {}
|
|
181
|
+
|
|
182
|
+
public on<THookName extends keyof THooksList>(
|
|
183
|
+
name: THookName,
|
|
184
|
+
callback: THookCallback<THooksList[THookName]>
|
|
185
|
+
) {
|
|
186
|
+
|
|
187
|
+
const callbacks = this.hooks[ name ];
|
|
188
|
+
if (callbacks)
|
|
189
|
+
callbacks.push( callback );
|
|
190
|
+
else
|
|
191
|
+
this.hooks[ name ] = [callback]
|
|
192
|
+
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public runHook<THookName extends keyof THooksList>(
|
|
197
|
+
name: THookName,
|
|
198
|
+
...args: THooksList[THookName]["args"]
|
|
199
|
+
) {
|
|
200
|
+
|
|
201
|
+
const callbacks = this.hooks[name];
|
|
202
|
+
if (!callbacks)
|
|
203
|
+
return;// console.info(LogPrefix, `No ${name} hook defined in the current service instance.`);
|
|
204
|
+
|
|
205
|
+
this.config.debug && console.info(`[hook] Run all ${name} hook (${callbacks.length}).`);
|
|
206
|
+
return Promise.all(
|
|
207
|
+
callbacks.map(
|
|
208
|
+
cb => cb(...args).catch(e => {
|
|
209
|
+
console.error(`[hook] Error while executing hook ${name}:`, e);
|
|
210
|
+
if (name !== 'error')
|
|
211
|
+
this.runHook('error', e);
|
|
212
|
+
})
|
|
213
|
+
)
|
|
214
|
+
).then(() => {
|
|
215
|
+
this.config.debug && console.info(`[hook] Hooks ${name} executed with success.`);
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
}
|
package/src/server/index.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
// Load Application container
|
|
2
|
+
import './app/container';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
// Load services setup
|
|
5
|
+
import '@/server/config/*.ts';
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
// Load Application
|
|
8
|
+
import application from './app/instance';
|
|
9
|
+
|
|
10
|
+
// Start application
|
|
11
|
+
application.start();
|