miqro 7.0.1 → 7.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/build/esm/editor/auth.d.ts +6 -0
  2. package/build/esm/editor/auth.js +41 -0
  3. package/build/esm/editor/common/admin-interface.d.ts +36 -0
  4. package/build/esm/editor/common/admin-interface.js +44 -0
  5. package/build/esm/editor/common/constants.d.ts +4 -0
  6. package/build/esm/editor/common/constants.js +20 -0
  7. package/build/esm/editor/common/constants.server.d.ts +2 -0
  8. package/build/esm/editor/common/constants.server.js +4 -0
  9. package/build/esm/editor/common/editor-index.d.ts +2 -0
  10. package/build/esm/editor/common/editor-index.js +14 -0
  11. package/build/esm/editor/common/html-encode.d.ts +1 -0
  12. package/build/esm/editor/common/html-encode.js +14 -0
  13. package/build/esm/editor/common/log-socket.d.ts +15 -0
  14. package/build/esm/editor/common/log-socket.js +71 -0
  15. package/build/esm/editor/common/templates.d.ts +11 -0
  16. package/build/esm/editor/common/templates.js +477 -0
  17. package/build/esm/editor/components/api-preview.d.ts +11 -0
  18. package/build/esm/editor/components/api-preview.js +92 -0
  19. package/build/esm/editor/components/editor.d.ts +16 -0
  20. package/build/esm/editor/components/editor.js +367 -0
  21. package/build/esm/editor/components/file-browser.d.ts +37 -0
  22. package/build/esm/editor/components/file-browser.js +127 -0
  23. package/build/esm/editor/components/file-editor-toolbar.d.ts +22 -0
  24. package/build/esm/editor/components/file-editor-toolbar.js +95 -0
  25. package/build/esm/editor/components/file-editor.d.ts +32 -0
  26. package/build/esm/editor/components/file-editor.js +61 -0
  27. package/build/esm/editor/components/filter-query.d.ts +1 -0
  28. package/build/esm/editor/components/filter-query.js +23 -0
  29. package/build/esm/editor/components/highlight-text-area.d.ts +11 -0
  30. package/build/esm/editor/components/highlight-text-area.js +127 -0
  31. package/build/esm/editor/components/log-viewer.d.ts +6 -0
  32. package/build/esm/editor/components/log-viewer.js +71 -0
  33. package/build/esm/editor/components/new-file.d.ts +10 -0
  34. package/build/esm/editor/components/new-file.js +119 -0
  35. package/build/esm/editor/components/scroll-query.d.ts +7 -0
  36. package/build/esm/editor/components/scroll-query.js +22 -0
  37. package/build/esm/editor/components/start-page.d.ts +13 -0
  38. package/build/esm/editor/components/start-page.js +32 -0
  39. package/build/esm/editor/http/admin/editor/api/fs/delete.api.d.ts +3 -0
  40. package/build/esm/editor/http/admin/editor/api/fs/delete.api.js +30 -0
  41. package/build/esm/editor/http/admin/editor/api/fs/read.api.d.ts +5 -0
  42. package/build/esm/editor/http/admin/editor/api/fs/read.api.js +50 -0
  43. package/build/esm/editor/http/admin/editor/api/fs/rename.api.d.ts +4 -0
  44. package/build/esm/editor/http/admin/editor/api/fs/rename.api.js +40 -0
  45. package/build/esm/editor/http/admin/editor/api/fs/scan.api.d.ts +26 -0
  46. package/build/esm/editor/http/admin/editor/api/fs/scan.api.js +150 -0
  47. package/build/esm/editor/http/admin/editor/api/fs/write.api.d.ts +3 -0
  48. package/build/esm/editor/http/admin/editor/api/fs/write.api.js +39 -0
  49. package/build/esm/editor/http/admin/editor/api/server/reload.api.d.ts +10 -0
  50. package/build/esm/editor/http/admin/editor/api/server/reload.api.js +46 -0
  51. package/build/esm/editor/http/admin/editor/api/server/restart.api.d.ts +10 -0
  52. package/build/esm/editor/http/admin/editor/api/server/restart.api.js +46 -0
  53. package/build/esm/editor/http/admin/editor/editor.d.ts +1 -0
  54. package/build/esm/editor/http/admin/editor/editor.js +8 -0
  55. package/build/esm/editor/http/admin/editor/index.api.d.ts +3 -0
  56. package/build/esm/editor/http/admin/editor/index.api.js +22 -0
  57. package/build/esm/editor/server.d.ts +3 -0
  58. package/build/esm/editor/server.js +49 -0
  59. package/build/esm/editor/ws.d.ts +3 -0
  60. package/build/esm/editor/ws.js +11 -0
  61. package/build/esm/src/inflate/inflate-sea.js +8 -1
  62. package/build/esm/src/services/app.js +7 -2
  63. package/build/lib.cjs +14 -3
  64. package/editor/auth.ts +51 -0
  65. package/editor/common/admin-interface.ts +84 -0
  66. package/editor/common/constants.server.ts +5 -0
  67. package/editor/common/constants.ts +21 -0
  68. package/editor/common/editor-index.tsx +17 -0
  69. package/editor/common/html-encode.ts +14 -0
  70. package/editor/common/log-socket.tsx +87 -0
  71. package/editor/common/templates.ts +481 -0
  72. package/editor/components/api-preview.tsx +118 -0
  73. package/editor/components/editor.tsx +496 -0
  74. package/editor/components/file-browser.tsx +311 -0
  75. package/editor/components/file-editor-toolbar.tsx +194 -0
  76. package/editor/components/file-editor.tsx +125 -0
  77. package/editor/components/filter-query.tsx +26 -0
  78. package/editor/components/highlight-text-area.tsx +148 -0
  79. package/editor/components/log-viewer.tsx +113 -0
  80. package/editor/components/new-file.tsx +172 -0
  81. package/editor/components/scroll-query.tsx +25 -0
  82. package/editor/components/start-page.tsx +52 -0
  83. package/editor/http/admin/editor/api/fs/delete.api.tsx +32 -0
  84. package/editor/http/admin/editor/api/fs/read.api.tsx +55 -0
  85. package/editor/http/admin/editor/api/fs/rename.api.tsx +41 -0
  86. package/editor/http/admin/editor/api/fs/scan.api.tsx +181 -0
  87. package/editor/http/admin/editor/api/fs/write.api.tsx +41 -0
  88. package/editor/http/admin/editor/api/server/reload.api.ts +53 -0
  89. package/editor/http/admin/editor/api/server/restart.api.tsx +52 -0
  90. package/editor/http/admin/editor/editor.tsx +10 -0
  91. package/editor/http/admin/editor/index.api.tsx +42 -0
  92. package/editor/server.ts +57 -0
  93. package/editor/ws.ts +15 -0
  94. package/package.json +2 -2
  95. package/src/bin/compile.ts +35 -0
  96. package/src/bin/doc-md.ts +210 -0
  97. package/src/bin/generate-doc.ts +64 -0
  98. package/src/bin/test.ts +92 -0
  99. package/src/bin/types.ts +34 -0
  100. package/src/cluster.ts +27 -0
  101. package/src/common/arguments.ts +762 -0
  102. package/src/common/assets.ts +148 -0
  103. package/src/common/checksum.ts +58 -0
  104. package/src/common/constants.ts +18 -0
  105. package/src/common/content-type.ts +84 -0
  106. package/src/common/esbuild.ts +102 -0
  107. package/src/common/exit.ts +91 -0
  108. package/src/common/fs.ts +82 -0
  109. package/src/common/help.ts +60 -0
  110. package/src/common/jsx.ts +562 -0
  111. package/src/common/jwt.ts +85 -0
  112. package/src/common/paths.ts +107 -0
  113. package/src/common/watch.ts +88 -0
  114. package/src/inflate/inflate-sea.ts +244 -0
  115. package/src/inflate/inflate.ts +101 -0
  116. package/src/inflate/md.ts +25 -0
  117. package/src/inflate/setup-auth.ts +41 -0
  118. package/src/inflate/setup-cors.ts +41 -0
  119. package/src/inflate/setup-db.ts +117 -0
  120. package/src/inflate/setup-error.ts +44 -0
  121. package/src/inflate/setup-http.ts +704 -0
  122. package/src/inflate/setup-log.ts +45 -0
  123. package/src/inflate/setup-middleware.ts +47 -0
  124. package/src/inflate/setup-server-config.ts +48 -0
  125. package/src/inflate/setup-test.ts +23 -0
  126. package/src/inflate/setup-ws.ts +50 -0
  127. package/src/inflate/setup.doc.ts +92 -0
  128. package/src/inflate/utils/sea-utils.ts +14 -0
  129. package/src/lib.ts +34 -0
  130. package/src/main.ts +101 -0
  131. package/src/services/app.ts +703 -0
  132. package/src/services/editor.tsx +101 -0
  133. package/src/services/globals.ts.ignore +186 -0
  134. package/src/services/hot-reload.ts +51 -0
  135. package/src/services/migrations.ts +68 -0
  136. package/src/services/utils/admin-interface.ts +37 -0
  137. package/src/services/utils/cache.ts +88 -0
  138. package/src/services/utils/cluster-cache.ts +230 -0
  139. package/src/services/utils/cluster-ws.ts +202 -0
  140. package/src/services/utils/db-manager.ts +92 -0
  141. package/src/services/utils/get-route.ts +70 -0
  142. package/src/services/utils/jwt.ts +25 -0
  143. package/src/services/utils/log-transport.ts +81 -0
  144. package/src/services/utils/log.ts +92 -0
  145. package/src/services/utils/middleware.ts +10 -0
  146. package/src/services/utils/server-interface.ts +122 -0
  147. package/src/services/utils/websocketmanager.ts +157 -0
  148. package/src/types.ts +307 -0
@@ -0,0 +1,703 @@
1
+ import cluster from "node:cluster";
2
+ import { App, Router, Logger, LoggerHandler } from "@miqro/core";
3
+ import { migration } from "@miqro/query";
4
+ import { WebSocketManager } from "./utils/websocketmanager.js";
5
+ import { DBManager } from "./utils/db-manager.js";
6
+ import { inflateApp } from "../inflate/inflate.js";
7
+ import { InflateError } from "../common/jsx.js";
8
+ import { DBConfig, MigrateOptions, ServerInterface, ServerRequest, WSConfig } from "../types.js";
9
+ import { RouteFileMap } from "../inflate/setup-http.js";
10
+ import { ServerConfigMap, setupServerConfig } from "../inflate/setup-server-config.js";
11
+ import { BASEEDITOR_PATH, LOG_SOCKET_PATH } from "../../editor/common/constants.js";
12
+ import editorWSConfig from "../../editor/ws.js";
13
+ import editorServerConfig from "../../editor/server.js";
14
+
15
+ import { ClusterCache } from "./utils/cluster-cache.js";
16
+ import { createEditorRouter } from "./editor.js";
17
+ import { EDITOR_CONFIG_KEY, HOT_RELOAD_PATH } from "../common/constants.js";
18
+ import { watchAndServer } from "../common/watch.js";
19
+ import { LocalCache } from "./utils/cache.js";
20
+ // import { initGlobals } from "./globals.js";
21
+ import { EditorAdminInterface } from "../../editor/common/admin-interface.js";
22
+ import { LogProvider, LogProviderOptions } from "./utils/log.js";
23
+ import { initAssets } from "../common/assets.js";
24
+ import { setupExitHandlers } from "../common/exit.js";
25
+ import { inflateDBConfig, inflateDBMigrations, MigrationModule } from "../inflate/setup-db.js";
26
+ import { getServicePath } from "../common/paths.js";
27
+ import { LogConfigMap } from "../inflate/setup-log.js";
28
+ import { createServerInterface } from "./utils/server-interface.js";
29
+ import { getPORT, importMiqroJSON } from "../common/arguments.js";
30
+ import { createLogProviderOptions } from "./utils/log-transport.js";
31
+ import { createAdminInterface } from "./utils/admin-interface.js";
32
+ import { dirname, join, relative, resolve } from "node:path";
33
+ import { cwd } from "node:process";
34
+ import { ServerOptions } from "node:https";
35
+
36
+ export interface MiqroOptions {
37
+ name: string;
38
+ logger?: Logger;
39
+ logProviderOptions?: LogProviderOptions;
40
+ services: string[];
41
+ editor: boolean;
42
+ port: string;
43
+ browser?: string | boolean;
44
+ logFile?: string | boolean;
45
+ hotreload?: boolean;
46
+ watch?: boolean;
47
+ serverOptions?: ServerOptions<any, any>;
48
+ https?: boolean;
49
+ httpRedirect?: number;
50
+ }
51
+
52
+ export interface InflateOptions {
53
+ inflateDir?: string;
54
+ inflateSea?: boolean;
55
+ inflateParallel?: number;
56
+ }
57
+
58
+ export interface InflatedResult {
59
+ router: Router;
60
+ errors: InflateError[];
61
+ fileMap: RouteFileMap;
62
+ dbList: {
63
+ dbConfig: DBConfig;
64
+ service: string;
65
+ migrations: MigrationModule[];
66
+ }[];
67
+ wsConfigList: WSConfig[];
68
+ serverConfigMap: ServerConfigMap;
69
+ logConfigMap: LogConfigMap;
70
+ }
71
+
72
+ export const MiqroApplicationMessageType = "$$$MiqroApplicationMessageType$$$";
73
+
74
+ export interface MiqroClusterMessage {
75
+ type: typeof MiqroApplicationMessageType;
76
+ action: "reload" | "restart";
77
+ target: string;
78
+ fromPID: number;
79
+ }
80
+
81
+ export class Miqro {
82
+ public status: "stopped" | "starting" | "stopping" | "reloading" | "started" = "stopped";
83
+ public options: MiqroOptions;
84
+ public server?: App | null = null;
85
+ public httpsRedirectServer?: App | null = null;
86
+ public cache: ClusterCache;
87
+ public localCache: LocalCache;
88
+ public webSocketManager: WebSocketManager;
89
+ public dbManager: DBManager;
90
+
91
+ public serverInterface: ServerInterface;
92
+ public adminInterface: EditorAdminInterface;
93
+
94
+ public inflated: InflatedResult | null | undefined = null;
95
+ private listener: (msg: MiqroClusterMessage) => void;
96
+ public serverRequestHandler: (req: ServerRequest) => void;
97
+ public logger?: Logger;
98
+ public loggerProvider: LogProvider;
99
+ public static initAssetsPromise: Promise<any> | null = null;
100
+ public watcher?: { stopWatch: () => void; };
101
+
102
+ constructor(options?: Partial<MiqroOptions>) {
103
+ this.options = {
104
+ editor: false,
105
+ name: "server",
106
+ port: getPORT(),
107
+ services: [],
108
+ ...(options ? options : {})
109
+ };
110
+ this.loggerProvider = new LogProvider(createLogProviderOptions(this));
111
+ //if (!this.options.logger) {
112
+ const SERVER_IDENTIFIER = cluster.isPrimary ?
113
+ "" :
114
+ process.env["CLUSTER_NODE_NUMBER"] ?
115
+ `WORKER_${process.env["CLUSTER_NODE_NUMBER"]}`.toUpperCase() :
116
+ `WORKER`;
117
+ this.logger = this.options.logger ? this.options.logger : this.loggerProvider.getLogger(SERVER_IDENTIFIER);
118
+ //}
119
+ this.listener = async (data: any) => {
120
+ try {
121
+ const msg = (data as MiqroClusterMessage);
122
+ if (
123
+ msg &&
124
+ msg.action &&
125
+ msg.type === MiqroApplicationMessageType &&
126
+ msg.target === this.options.name,
127
+ msg.fromPID !== process.pid,
128
+ (msg.action === "reload" || msg.action === "restart")) {
129
+ this.logger?.debug("remote server message from [%s] [%s]", msg.fromPID, msg.action);
130
+ switch (msg.action) {
131
+ case "reload":
132
+ await this.reload(true);
133
+ break;
134
+ case "restart":
135
+ await this.restart(true);
136
+ break;
137
+ default:
138
+ throw new Error("unsopported message for ApplicaitonServer");
139
+ }
140
+ }
141
+ } catch (e) {
142
+ this.logger?.error(e);
143
+ }
144
+ };
145
+ this.cache = new ClusterCache(`MiqroApplicationCache[${this.options.name}]`, this.logger);
146
+ this.localCache = new LocalCache(`MiqroApplicationLocalCache[${this.options.name}]`, this.logger);
147
+ this.webSocketManager = new WebSocketManager({
148
+ logger: this.logger,
149
+ loggerProvider: this.loggerProvider,
150
+ name: `MiqroApplicationWebsocketManager[${this.options.name}]`,
151
+ avoidLogSocket: this.options.editor
152
+ });
153
+ this.listener = this.listener.bind(this);
154
+ //this.requestLoggerFactory = this.requestLoggerFactory.bind(this);
155
+ //this.loggerFactory = this.loggerFactory.bind(this);
156
+ this.dbManager = new DBManager({
157
+ loggerProvider: this.loggerProvider,
158
+ logger: this.logger
159
+ });
160
+
161
+ this.serverInterface = createServerInterface({
162
+ cache: this.cache,
163
+ dbManager: this.dbManager,
164
+ localCache: this.localCache,
165
+ webSocketManager: this.webSocketManager,
166
+ app: this,
167
+ logger: this.logger,
168
+ loggerProvider: this.loggerProvider,
169
+ port: this.options.port
170
+ });
171
+
172
+ this.serverRequestHandler = ServerRequestHandler(this.serverInterface);
173
+
174
+ this.adminInterface = createAdminInterface(this);
175
+
176
+ setupExitHandlers(this);
177
+ }
178
+
179
+ public static async import(inFile: string, options?: Partial<MiqroOptions>, inflate?: Partial<InflateOptions>): Promise<Miqro> {
180
+ const miqroJSON = importMiqroJSON(resolve(inFile));
181
+ const miqroJSONDir = dirname(resolve(inFile));
182
+ const app = new Miqro({
183
+ name: miqroJSON.name ? miqroJSON.name : undefined,
184
+ port: miqroJSON.port ? String(miqroJSON.port) : undefined,
185
+ services: miqroJSON.services ? miqroJSON.services.map(s => join(relative(cwd(), miqroJSONDir), s)) : undefined,
186
+ ...(options ? options : {}),
187
+ });
188
+ await app.inflate({
189
+ inflateDir: miqroJSON.inflateDir ? String(miqroJSON.inflateDir) : undefined,
190
+ inflateParallel: miqroJSON.inflateParallel ? miqroJSON.inflateParallel : undefined,
191
+ ...(inflate ? inflate : {}),
192
+ });
193
+ return app;
194
+ }
195
+
196
+ public connect() {
197
+ if (process.send) {
198
+ process.removeListener("message", this.listener);
199
+ process.on("message", this.listener);
200
+ }
201
+ this.cache.connect();
202
+ (this.adminInterface?.getCache() as ClusterCache)?.connect();
203
+ }
204
+
205
+ public disconnect() {
206
+ if (this.server !== null) {
207
+ throw new Error("already running! call stop() first");
208
+ }
209
+ if (process.send) {
210
+ process.removeListener("message", this.listener);
211
+ }
212
+ this.cache.disconnect();
213
+ (this.adminInterface?.getCache() as ClusterCache)?.disconnect();
214
+ this.webSocketManager.disconnectAll();
215
+ this.dbManager.closeAll();
216
+ }
217
+
218
+ public async inflateWith(inflated?: Partial<InflatedResult>) {
219
+ if (this.inflated !== null) {
220
+ throw new Error("already inflated! call deflate()");
221
+ }
222
+ // initGlobals();
223
+ // block others from inflating while inflateApp is running
224
+ this.inflated = undefined;
225
+ this.inflated = {
226
+ router: inflated && inflated.router ? inflated.router : new Router(),
227
+ errors: inflated && inflated.errors ? inflated.errors : [],
228
+ fileMap: inflated && inflated.fileMap ? inflated.fileMap : {},
229
+ logConfigMap: inflated && inflated.logConfigMap ? inflated.logConfigMap : {},
230
+ //migrations: inflated && inflated.migrations ? inflated.migrations : [],
231
+ dbList: inflated && inflated.dbList ? inflated.dbList : [],
232
+ wsConfigList: inflated && inflated.wsConfigList ? inflated.wsConfigList : [],
233
+ serverConfigMap: inflated && inflated.serverConfigMap ? inflated.serverConfigMap : {}
234
+ };
235
+ await Promise.all(this.inflated.dbList.map(db => this.dbManager.setupDB(db.dbConfig)));
236
+ }
237
+
238
+ public async loadServerConfig(options?: InflateOptions) {
239
+ const serverConfigMap: ServerConfigMap = {};
240
+ const errors: InflateError[] = [];
241
+ for (const service of this.options.services) {
242
+ const servicePath = getServicePath(service);
243
+ await setupServerConfig(this.logger, servicePath, service, serverConfigMap, options && options.inflateSea ? options.inflateDir : undefined, errors);
244
+ }
245
+ return {
246
+ serverConfigMap,
247
+ errors
248
+ }
249
+ }
250
+
251
+ public async loadDBConfig(options?: InflateOptions): Promise<{
252
+ dbList: {
253
+ dbConfig: DBConfig;
254
+ service: string;
255
+ migrations: MigrationModule[];
256
+ }[]; errors: InflateError[]
257
+ }> {
258
+ const errors: InflateError[] = [];
259
+ const dbList: {
260
+ service: string;
261
+ dbConfig: DBConfig;
262
+ migrations: MigrationModule[];
263
+ }[] = [];
264
+ const dbConfigListALL: DBConfig[] = [];
265
+ for (const service of this.options.services) {
266
+ const dbConfig = await inflateDBConfig(this.logger, service, dbConfigListALL, options?.inflateSea ? options?.inflateDir : undefined, errors);
267
+ if (dbConfig) {
268
+ const migrations = await inflateDBMigrations(this.logger, service, dbConfig.name, options?.inflateSea ? options?.inflateDir : undefined, errors);
269
+ dbList.push({
270
+ service,
271
+ dbConfig,
272
+ migrations: migrations ? migrations : []
273
+ });
274
+ }
275
+ }
276
+ return { dbList, errors };
277
+ }
278
+
279
+ public async migrate({ direction, service, name, dbName }: MigrateOptions = { direction: "up" }) {
280
+ // init assets only once for all ApplicationServer's
281
+ if (Miqro.initAssetsPromise === null) {
282
+ // init globals only once for all inflations
283
+ // initGlobals();
284
+ Miqro.initAssetsPromise = initAssets(this.logger);
285
+ }
286
+ await Miqro.initAssetsPromise;
287
+ const { dbList } = await this.loadDBConfig();
288
+
289
+ for (const serviceDB of dbList) {
290
+ if ((!service || serviceDB.service === service) && (!dbName || serviceDB.dbConfig.name === dbName)) {
291
+ const db = this.dbManager.getDB(serviceDB.dbConfig.name) ? this.dbManager.getDB(serviceDB.dbConfig.name) : await this.dbManager.setupDB(serviceDB.dbConfig);
292
+ if (db) {
293
+ this.logger.info("\t\t====== [init migration table] ======");
294
+ await migration.init(db, this.logger);
295
+ const migrations = direction === "down" ? serviceDB.migrations.reverse() : serviceDB.migrations;
296
+ if (!name) {
297
+ for (const m of migrations) {
298
+ if (direction === "down") {
299
+ await migration.down.module(db, m.name, m, this.logger);
300
+ } else {
301
+ await migration.up.module(db, m.name, m, this.logger);
302
+ }
303
+ }
304
+ } else {
305
+ for (const m of migrations) {
306
+ if (m.name === name) {
307
+ if (direction === "down") {
308
+ await migration.down.module(db, m.name, m, this.logger);
309
+ } else {
310
+ await migration.up.module(db, m.name, m, this.logger);
311
+ }
312
+ break;
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+ }
319
+ }
320
+
321
+ public async inflate(options?: InflateOptions): Promise<InflatedResult> {
322
+ if (this.inflated !== null) {
323
+ throw new Error("already inflated! call await deflate() first!");
324
+ }
325
+ try {
326
+ // block others from inflating while inflateApp is running
327
+ this.inflated = undefined;
328
+ // init assets only once for all ApplicationServer's
329
+ if (Miqro.initAssetsPromise === null) {
330
+ // init globals only once for all inflations
331
+ // initGlobals();
332
+ Miqro.initAssetsPromise = initAssets(this.logger);
333
+ }
334
+ await Miqro.initAssetsPromise;
335
+
336
+ // store the result
337
+ const router = new Router();
338
+ const wsConfigList: WSConfig[] = [];
339
+
340
+ /* setup db connection before inflate app router and */
341
+ const { dbList, errors: dbConfigErrors } = await this.loadDBConfig(options);
342
+ await Promise.all(dbList.map(db => this.dbManager.setupDB(db.dbConfig)));
343
+
344
+ /* setup server config before inflate app router and ws and bs for preload*/
345
+ const { errors: serviceConfigErrors, serverConfigMap } = await this.loadServerConfig(options);
346
+
347
+ // editor WSConfig must be put a head of the service WSConfig to avoid LOG_SOCKET replacement
348
+ if (this.options.editor) {
349
+ this.logger?.debug("setting up editor on %s", BASEEDITOR_PATH);
350
+ const editorRouter = await createEditorRouter(this.adminInterface);
351
+ router.use(editorRouter);
352
+ this.logger?.debug("setting up log websocket on [%s]", LOG_SOCKET_PATH);
353
+ wsConfigList.push(editorWSConfig);
354
+ serverConfigMap[EDITOR_CONFIG_KEY] = editorServerConfig;
355
+ }
356
+
357
+ if (this.options?.hotreload/* && !options?.inflateTests*/) {
358
+ this.logger?.debug("setting up websocket on [%s]", HOT_RELOAD_PATH);
359
+ wsConfigList.push({
360
+ path: HOT_RELOAD_PATH
361
+ });
362
+ }
363
+
364
+ await notifiyServerConfig(this.logger, this.serverInterface, this.adminInterface, serverConfigMap, "preload");
365
+
366
+ /* setup app (router, websocket servers files)*/
367
+ const [serviceRouter, errors, fileMap/*, migrations*/, serviceWSConfigList/*, serverConfigMap*/, serviceLogConfigMap] = await inflateApp({
368
+ logger: this.logger,
369
+ services: this.options.services,
370
+ serverInterface: this.serverInterface,
371
+ //dbManager: this.dbManager,
372
+ port: this.options.port,
373
+ inflateDir: options?.inflateDir,
374
+ inflateSea: options?.inflateSea ? true : false,
375
+ //inflateTests: options?.inflateTests ? true : false,
376
+ hotreload: this.options?.hotreload ? true : false,
377
+ inflateParallel: options?.inflateParallel
378
+ });
379
+
380
+ wsConfigList.push(...serviceWSConfigList);
381
+ router.use(serviceRouter);
382
+ this.inflated = {
383
+ router,
384
+ logConfigMap: serviceLogConfigMap,
385
+ errors: (errors ? errors : []).concat(dbConfigErrors).concat(serviceConfigErrors),
386
+ fileMap,
387
+ //migrations,
388
+ dbList,
389
+ wsConfigList,
390
+ serverConfigMap
391
+ };
392
+
393
+ return this.inflated;
394
+ } catch (e) {
395
+ this.inflated = null;
396
+ throw e;
397
+ }
398
+ }
399
+
400
+ public isInflated() {
401
+ return this.inflated ? true : false;
402
+ }
403
+
404
+ public isRunning() {
405
+ return this.server !== null;
406
+ }
407
+
408
+ public async deflate() {
409
+ if (this.isRunning()) {
410
+ throw new Error("cannot deflate app running. call app.stop() first.");
411
+ }
412
+ if (this.inflated !== null) {
413
+ if (this.inflated === undefined) {
414
+ throw new Error("cannot deflate an app that is currently inflating. wait for the result with await app.inflate() first.");
415
+ } else {
416
+ throw new Error("cannot deflate uninflated app. call await app.inflate() first.");
417
+ }
418
+ }
419
+ await this.dbManager.closeAll();
420
+ await this.dbManager.deleteAll();
421
+ this.inflated = null;
422
+ }
423
+
424
+ public async start() {
425
+ if (this.server !== null || this.status !== "stopped") {
426
+ throw new Error("cannot start app already running.");
427
+ }
428
+ if (!this.isInflated()) {
429
+ throw new Error("cannot start uninflated app. call await app.inflate() first.");
430
+ }
431
+ //this.logger?.debug("starting");
432
+ this.logger?.debug("\t\t==start==");
433
+
434
+ //this.disconnect();
435
+ this.status = "starting";
436
+ this.server = undefined;
437
+ this.httpsRedirectServer = undefined;
438
+ this.connect();
439
+ await this.dbManager.connectAll();
440
+ this.server = new App({
441
+ onUpgrade: (req: ServerRequest, socket, head) => {
442
+ try {
443
+ req.server = this.serverInterface;
444
+ return this.webSocketManager.onUpgrade(req, socket, head);
445
+ } catch (e) {
446
+ this.logger?.error(e);
447
+ }
448
+ },
449
+ loggerFactory: this.loggerProvider.requestLoggerFactory,
450
+ serverOptions: this.options?.serverOptions,
451
+ https: this.options?.https
452
+ });
453
+ if (this.options?.httpRedirect) {
454
+ this.httpsRedirectServer = new App();
455
+ this.httpsRedirectServer.use(async (req: ServerRequest, res) => {
456
+ const hostname = req.headers.host.split(":").length > 1 ? req.headers.host.split(":")[0] : req.headers.host;
457
+ return await res.redirect('https://' + hostname + ":" + this.options.port + req.url);
458
+ });
459
+ }
460
+
461
+ this.webSocketManager.replaceALLWS(this.inflated.wsConfigList);
462
+
463
+ reloadInflatedRouter(this);
464
+
465
+ await notifiyServerConfig(this.logger, this.serverInterface, this.adminInterface, this.inflated.serverConfigMap, "load");
466
+
467
+ if (this.logger && (cluster.isPrimary || process.env["CLUSTER_NODE_NUMBER"] === "0")) {
468
+ /*this.logger?.debug("\t\t==http routes==");
469
+ this.server.logPaths({
470
+ debug: this.logger.debug.bind(this.logger),
471
+ error: this.logger.error.bind(this.logger),
472
+ info: this.logger.debug.bind(this.logger),
473
+ log: this.logger.debug.bind(this.logger),
474
+ trace: this.logger.trace.bind(this.logger),
475
+ warn: this.logger.warn.bind(this.logger)
476
+ });*/
477
+ this.logger?.info("\t\t==http routes==");
478
+ this.server.logPaths(this.logger);
479
+ }
480
+
481
+ this.logger?.trace("calling listen on [%s]", this.options.port);
482
+
483
+ await this.server.listen(this.options.port);
484
+ if (this.options?.httpRedirect) {
485
+ await this.httpsRedirectServer.listen(this.options.httpRedirect);
486
+ }
487
+
488
+ if (this.options?.httpRedirect && this.logger && (cluster.isPrimary || process.env["CLUSTER_NODE_NUMBER"] === "0")) {
489
+ this.logger?.log("\t\t==listening on [http][%s] for [https][%s] redirection==", this.options?.httpRedirect, this.options.port);
490
+ } else if (this.options?.httpRedirect) {
491
+ this.logger?.debug("\t\t==listening on [http][%s] for [https][%s] redirection==", this.options?.httpRedirect, this.options.port);
492
+ }
493
+
494
+ if (this.logger && (cluster.isPrimary || process.env["CLUSTER_NODE_NUMBER"] === "0")) {
495
+ this.logger?.log("\t\t==listening on [%s][%s]==", this.options.https ? "https" : "http", this.options.port);
496
+ } else {
497
+ this.logger?.debug("\t\t==listening on [%s][%s]==", this.options.https ? "https" : "http", this.options.port);
498
+ }
499
+
500
+ await notifiyServerConfig(this.logger, this.serverInterface, this.adminInterface, this.inflated.serverConfigMap, "start");
501
+
502
+ if (this.options.watch && (cluster.isPrimary || process.env["CLUSTER_NODE_NUMBER"] === "0")) {
503
+ this.watcher = await watchAndServer(this);
504
+ }
505
+
506
+ this.logger?.debug("\t\t==start done==");
507
+
508
+ this.status = "started";
509
+ return this.inflated.errors && this.inflated.errors.length > 0 ? this.inflated.errors : null;
510
+ }
511
+
512
+ public async stop() {
513
+ if (!this.server || !this.inflated || this.status !== "started") {
514
+ throw new Error("cannot stop server not running");
515
+ }
516
+ this.status = "stopping";
517
+ if (this.watcher) {
518
+ this.watcher.stopWatch();
519
+ this.watcher = null;
520
+ }
521
+ const server = this.server;
522
+ const httpsRedirectServer = this.httpsRedirectServer;
523
+ this.server = null;
524
+ this.httpsRedirectServer = null;
525
+ this.disconnect();
526
+ this.logger?.debug("\t\t==stop==");
527
+ this.logger?.debug("clear running server routes");
528
+ server.clear();
529
+ this.webSocketManager.disconnectAll();
530
+ this.webSocketManager.deleteAllWS();
531
+ const pD = this.dbManager.deleteAll();
532
+ notifiyServerConfigSync(this, "unload");
533
+ //server.ws.disconnectAll();
534
+ this.logger?.debug("stopping");
535
+ if (httpsRedirectServer) {
536
+ await httpsRedirectServer.close();
537
+ }
538
+ const p = server.close();
539
+ await p;
540
+ await pD;
541
+ this.status = "stopped";
542
+ notifiyServerConfigSync(this, "stop");
543
+ this.logger?.debug("\t\t==stop done==");
544
+ }
545
+
546
+ public async restart(avoidSend?: boolean) {
547
+ if (!this.server || !this.inflated || this.status !== "started") {
548
+ throw new Error("cannot start server not running");
549
+ }
550
+ this.logger?.debug("\t\t==restart==");
551
+ if (process.send && !avoidSend) {
552
+ process.send({
553
+ type: MiqroApplicationMessageType,
554
+ target: this.options.name,
555
+ action: "restart",
556
+ fromPID: process.pid
557
+ } as MiqroClusterMessage);
558
+ }
559
+ await this.stop();
560
+ const ret = await this.start();
561
+ this.logger?.debug("\t\t==restart done==");
562
+ return ret;
563
+ }
564
+
565
+ public async reload(avoidSend?: boolean) {
566
+ if (!this.server || !this.inflated || this.status !== "started") {
567
+ throw new Error("cannot reload server not running");
568
+ }
569
+ this.status = "reloading";
570
+ /*this.logger?.log("====================");
571
+ this.logger?.log("=======reload=======");
572
+ this.logger?.log("====================");*/
573
+
574
+ if (!avoidSend) {
575
+ this.logger?.debug("\t\t==reload==");
576
+ } else {
577
+ this.logger?.debug("\t\t==reload==");
578
+ }
579
+ // block others from calling this
580
+ const oldServerConfigMap = this.inflated.serverConfigMap;
581
+ this.inflated = undefined;
582
+ if (process.send && !avoidSend) {
583
+ process.send({
584
+ type: MiqroApplicationMessageType,
585
+ target: this.options.name,
586
+ action: "reload",
587
+ fromPID: process.pid
588
+ } as MiqroClusterMessage);
589
+ }
590
+
591
+ this.logger?.debug("clear running server routes");
592
+ this.server.clear();
593
+ await this.webSocketManager.disconnectAllButLOGSocket();
594
+ await notifiyServerConfig(this.logger, this.serverInterface, this.adminInterface, oldServerConfigMap, "unload");
595
+
596
+ /* force deflation*/
597
+ await this.dbManager.closeAll();
598
+ await this.dbManager.deleteAll();
599
+ this.inflated = null;
600
+ /* re-inflation */
601
+ this.inflated = await this.inflate();
602
+ if (!this.inflated) {
603
+ throw new Error("inflation failed!");
604
+ }
605
+
606
+ reloadInflatedRouter(this);
607
+ await this.webSocketManager.disconnectAllButLOGSocket();
608
+ this.webSocketManager.replaceALLWSBuLOGSocket(this.inflated.wsConfigList);
609
+
610
+ await notifiyServerConfig(this.logger, this.serverInterface, this.adminInterface, this.inflated.serverConfigMap, "load");
611
+
612
+ if (this.logger && (cluster.isPrimary || process.env["CLUSTER_NODE_NUMBER"] === "0")) {
613
+ this.logger?.info("\t\t==http routes==");
614
+ this.server.logPaths(this.logger);
615
+ /*this.server.logPaths({
616
+ debug: this.logger.debug.bind(this.logger),
617
+ error: this.logger.error.bind(this.logger),
618
+ info: this.logger.debug.bind(this.logger),
619
+ log: this.logger.debug.bind(this.logger),
620
+ trace: this.logger.trace.bind(this.logger),
621
+ warn: this.logger.warn.bind(this.logger)
622
+ });*/
623
+ }
624
+
625
+ //this.logger?.log("=====================");
626
+ this.logger?.debug("\t\t==reload done==");
627
+ //this.logger?.log("=====================");
628
+
629
+ this.status = "started";
630
+ return this.inflated.errors && this.inflated.errors.length > 0 ? this.inflated.errors : null;
631
+
632
+ }
633
+ }
634
+
635
+ export function ServerRequestHandler(server: ServerInterface) {
636
+ return function ServerRequestHandler(req: ServerRequest) {
637
+ req.server = server;
638
+ }
639
+ }
640
+
641
+ function reloadInflatedRouter(app: Miqro) {
642
+ if (!app.server) {
643
+ throw new Error("not running");
644
+ }
645
+ if (!app.inflated) {
646
+ throw new Error("not inflated");
647
+ }
648
+ app.logger?.debug("clear running server routes");
649
+ app.server.clear();
650
+ app.logger?.debug("load running server with new routes");
651
+
652
+ app.server.use(app.serverRequestHandler);
653
+
654
+ app.server.use(LoggerHandler());
655
+ if (app.options.editor) {
656
+ app.server.use(async (req, res) => {
657
+ res.setHeader("x-uuid", req.uuid);
658
+ });
659
+ }
660
+
661
+ app.server.use(app.inflated.router);
662
+ }
663
+
664
+ async function notifiyServerConfig(logger: Logger, serverInterface: ServerInterface, adminInterface: EditorAdminInterface, serverConfigMap: ServerConfigMap, method: "load" | "start" | "unload" | "stop" | "preload") {
665
+ if (serverConfigMap) {
666
+ await Promise.allSettled(Object.keys(serverConfigMap).map(name => {
667
+ return new Promise<void>(async (resolve) => {
668
+ try {
669
+ const serverConfig = serverConfigMap[name];
670
+ if (serverConfig && serverConfig[method]) {
671
+ if (name === EDITOR_CONFIG_KEY) {
672
+ await (serverConfig[method] as any)(serverInterface, adminInterface);
673
+ } else {
674
+ await serverConfig[method](serverInterface);
675
+ }
676
+
677
+ }
678
+ resolve();
679
+ } catch (e) {
680
+ logger?.error(e);
681
+ resolve();
682
+ }
683
+ })
684
+ }));
685
+ }
686
+ }
687
+
688
+ export function notifiyServerConfigSync(app: Miqro, method: "unload" | "stop") {
689
+ if (app.inflated) {
690
+ Object.keys(app.inflated.serverConfigMap).map(name => {
691
+ try {
692
+ if (app.inflated) {
693
+ const serverConfig = app.inflated.serverConfigMap[name];
694
+ if (serverConfig && serverConfig[method]) {
695
+ serverConfig[method](app.serverInterface);
696
+ }
697
+ }
698
+ } catch (e) {
699
+ app.logger?.error(e);
700
+ }
701
+ });
702
+ }
703
+ }