@radatek/microserver 2.1.0 → 2.2.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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MicroServer
3
- * @version 2.1.0
3
+ * @version 2.2.0
4
4
  * @package @radatek/microserver
5
5
  * @copyright Darius Kisonas 2022
6
6
  * @license MIT
@@ -21,6 +21,9 @@ const defaultExpire = 24 * 60 * 60;
21
21
  const defaultMaxBodySize = 5 * 1024 * 1024;
22
22
  const defaultMethods = 'HEAD,GET,POST,PUT,PATCH,DELETE';
23
23
  function NOOP(...args) { }
24
+ function isFunction(fn) {
25
+ return typeof fn === 'function' && !fn.prototype?.constructor;
26
+ }
24
27
  export class Warning extends Error {
25
28
  constructor(text) {
26
29
  super(text);
@@ -71,8 +74,9 @@ export class ServerRequest extends http.IncomingMessage {
71
74
  _init(router) {
72
75
  Object.assign(this, {
73
76
  router,
77
+ auth: router.auth,
74
78
  protocol: 'encrypted' in this.socket && this.socket.encrypted ? 'https' : 'http',
75
- get: {},
79
+ query: {},
76
80
  params: {},
77
81
  paramsList: [],
78
82
  path: '/',
@@ -692,7 +696,7 @@ export class WebSocket extends EventEmitter {
692
696
  headers = [
693
697
  `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}`,
694
698
  'Connection: close',
695
- 'Content-Type: text/html',
699
+ 'Content-Type: ' + message.startsWith('<') ? 'text/html' : 'text/plain',
696
700
  `Content-Length: ${Buffer.byteLength(message)}`,
697
701
  '',
698
702
  message
@@ -903,6 +907,48 @@ export class Controller {
903
907
  return routes;
904
908
  }
905
909
  }
910
+ class Waiter {
911
+ constructor() {
912
+ this._waiters = {};
913
+ this._id = 0;
914
+ this._busy = 0;
915
+ }
916
+ get busy() {
917
+ return this._busy > 0;
918
+ }
919
+ startJob() {
920
+ this._busy++;
921
+ }
922
+ endJob(id) {
923
+ this._busy--;
924
+ if (!this._busy)
925
+ this.resolve(id || 'ready');
926
+ }
927
+ get nextId() {
928
+ return (++this._id).toString();
929
+ }
930
+ async wait(id) {
931
+ return new Promise(resolve => (this._waiters[id] = this._waiters[id] || []).push(resolve));
932
+ }
933
+ resolve(id) {
934
+ const resolvers = this._waiters[id];
935
+ if (resolvers)
936
+ for (const resolve of resolvers)
937
+ resolve(undefined);
938
+ delete this._waiters[id];
939
+ }
940
+ }
941
+ class EmitterWaiter extends Waiter {
942
+ constructor(emitter) {
943
+ super();
944
+ this._emitter = emitter;
945
+ }
946
+ resolve(id) {
947
+ if (id === 'ready')
948
+ this._emitter.emit(id);
949
+ super.resolve(id);
950
+ }
951
+ }
906
952
  /** Router */
907
953
  export class Router extends EventEmitter {
908
954
  /** @param {MicroServer} server */
@@ -912,8 +958,95 @@ export class Router extends EventEmitter {
912
958
  this._stack = [];
913
959
  this._stackAfter = [];
914
960
  this._tree = {};
961
+ this._waiter = new Waiter();
915
962
  this.server = server;
916
963
  }
964
+ /** bind middleware or create one from string like: 'redirect:302,https://redirect.to', 'error:422', 'param:name=value', 'acl:users/get', 'model:User', 'group:Users', 'user:admin' */
965
+ bind(fn) {
966
+ if (typeof fn === 'string') {
967
+ let name = fn;
968
+ let idx = name.indexOf(':');
969
+ if (idx < 0 && name.includes('=')) {
970
+ name = 'param:' + name;
971
+ idx = 5;
972
+ }
973
+ if (idx >= 0) {
974
+ const v = name.slice(idx + 1);
975
+ const type = name.slice(0, idx);
976
+ // predefined middlewares
977
+ switch (type) {
978
+ // redirect:302,https://redirect.to
979
+ case 'redirect': {
980
+ let redirect = v.split(','), code = parseInt(v[0]);
981
+ if (!code || code < 301 || code > 399)
982
+ code = 302;
983
+ return (req, res) => res.redirect(code, redirect[1] || v);
984
+ }
985
+ // error:422
986
+ case 'error':
987
+ return (req, res) => res.error(parseInt(v) || 422);
988
+ // param:name=value
989
+ case 'param': {
990
+ idx = v.indexOf('=');
991
+ if (idx > 0) {
992
+ const prm = v.slice(0, idx), val = v.slice(idx + 1);
993
+ return (req, res, next) => { req.params[prm] = val; return next(); };
994
+ }
995
+ break;
996
+ }
997
+ case 'model': {
998
+ const model = v;
999
+ return (req, res) => {
1000
+ res.isJson = true;
1001
+ req.params.model = model;
1002
+ req.model = Model.models[model];
1003
+ if (!req.model) {
1004
+ console.error(`Data model ${model} not defined for request ${req.path}`);
1005
+ return res.error(422);
1006
+ }
1007
+ return req.model.handler(req, res);
1008
+ };
1009
+ }
1010
+ // user:userid
1011
+ // group:user_groupid
1012
+ // acl:validacl
1013
+ case 'user':
1014
+ case 'group':
1015
+ case 'acl':
1016
+ return (req, res, next) => {
1017
+ if (type === 'user' && v === req.user?.id)
1018
+ return next();
1019
+ if (type === 'acl') {
1020
+ req.params.acl = v;
1021
+ if (req.auth?.acl(v))
1022
+ return next();
1023
+ }
1024
+ if (type === 'group') {
1025
+ req.params.group = v;
1026
+ if (req.user?.group === v)
1027
+ return next();
1028
+ }
1029
+ const accept = req.headers.accept || '';
1030
+ if (!res.isJson && req.auth?.options.redirect && req.method === 'GET' && !accept.includes('json') && (accept.includes('html') || accept.includes('*/*'))) {
1031
+ if (req.auth.options.redirect && req.url !== req.auth.options.redirect)
1032
+ return res.redirect(302, req.auth.options.redirect);
1033
+ else if (req.auth.options.mode !== 'cookie') {
1034
+ res.setHeader('WWW-Authenticate', `Basic realm="${req.auth.options.realm}"`);
1035
+ return res.error(401);
1036
+ }
1037
+ }
1038
+ return res.error('Permission denied');
1039
+ };
1040
+ }
1041
+ }
1042
+ throw new Error('Invalid option: ' + name);
1043
+ }
1044
+ if (fn && typeof fn === 'object' && 'handler' in fn && typeof fn.handler === 'function')
1045
+ return fn.handler.bind(fn);
1046
+ if (typeof fn !== 'function')
1047
+ throw new Error('Invalid middleware: ' + String.toString.call(fn));
1048
+ return fn.bind(this);
1049
+ }
917
1050
  /** Handler */
918
1051
  handler(req, res, next, method) {
919
1052
  const nextAfter = next;
@@ -1013,7 +1146,7 @@ export class Router extends EventEmitter {
1013
1146
  url,
1014
1147
  middlewares
1015
1148
  });
1016
- middlewares = middlewares.map(i => this.server.bind(i));
1149
+ middlewares = middlewares.map(m => this.bind(m));
1017
1150
  let item = this._tree[method];
1018
1151
  if (!item)
1019
1152
  item = this._tree[method] = { tree: {} };
@@ -1049,7 +1182,6 @@ export class Router extends EventEmitter {
1049
1182
  if (!item[key])
1050
1183
  item[key] = [];
1051
1184
  item[key].push(...middlewares);
1052
- return this;
1053
1185
  }
1054
1186
  /** Clear routes and middlewares */
1055
1187
  clear() {
@@ -1063,71 +1195,79 @@ export class Router extends EventEmitter {
1063
1195
  *
1064
1196
  * @signature add(plugin: Plugin)
1065
1197
  * @param {Plugin} plugin plugin module instance
1066
- * @return {Router} current router
1198
+ * @return {Promise<>}
1067
1199
  *
1068
1200
  * @signature add(pluginid: string, ...args: any)
1069
1201
  * @param {string} pluginid pluginid module
1070
1202
  * @param {...any} args arguments passed to constructor
1071
- * @return {Router} current router
1203
+ * @return {Promise<>}
1072
1204
  *
1073
1205
  * @signature add(pluginClass: typeof Plugin, ...args: any)
1074
1206
  * @param {typeof Plugin} pluginClass plugin class
1075
1207
  * @param {...any} args arguments passed to constructor
1076
- * @return {Router} current router
1208
+ * @return {Promise<>}
1077
1209
  *
1078
1210
  * @signature add(middleware: Middleware)
1079
1211
  * @param {Middleware} middleware
1080
- * @return {Router} current router
1212
+ * @return {Promise<>}
1081
1213
  *
1082
1214
  * @signature add(methodUrl: string, ...middlewares: any)
1083
1215
  * @param {string} methodUrl 'METHOD /url' or '/url'
1084
1216
  * @param {...any} middlewares
1085
- * @return {Router} current router
1217
+ * @return {Promise<>}
1086
1218
  *
1087
1219
  * @signature add(methodUrl: string, controllerClass: typeof Controller)
1088
1220
  * @param {string} methodUrl 'METHOD /url' or '/url'
1089
1221
  * @param {typeof Controller} controllerClass
1090
- * @return {Router} current router
1222
+ * @return {Promise<>}
1091
1223
  *
1092
1224
  * @signature add(methodUrl: string, routes: Array<Array<any>>)
1093
1225
  * @param {string} methodUrl 'METHOD /url' or '/url'
1094
1226
  * @param {Array<Array<any>>} routes list with subroutes: ['METHOD /suburl', ...middlewares]
1095
- * @return {Router} current router
1227
+ * @return {Promise<>}
1096
1228
  *
1097
1229
  * @signature add(methodUrl: string, routes: Array<Array<any>>)
1098
1230
  * @param {string} methodUrl 'METHOD /url' or '/url'
1099
1231
  * @param {Array<Array<any>>} routes list with subroutes: ['METHOD /suburl', ...middlewares]
1100
- * @return {Router} current router
1232
+ * @return {Promise<>}
1101
1233
  *
1102
1234
  * @signature add(routes: { [key: string]: Array<any> })
1103
1235
  * @param { {[key: string]: Array<any>} } routes list with subroutes: 'METHOD /suburl': [...middlewares]
1104
- * @return {Router} current router
1236
+ * @return {Promise<>}
1105
1237
  *
1106
1238
  * @signature add(methodUrl: string, routes: { [key: string]: Array<any> })
1107
1239
  * @param {string} methodUrl 'METHOD /url' or '/url'
1108
1240
  * @param { {[key: string]: Array<any>} } routes list with subroutes: 'METHOD /suburl': [...middlewares]
1109
- * @return {Router} current router
1241
+ * @return {Promise<>}
1110
1242
  */
1111
- use(...args) {
1243
+ async use(...args) {
1112
1244
  if (!args[0])
1113
- return this;
1245
+ return;
1246
+ this.server._waiter.startJob();
1247
+ for (let i = 0; i < args.length; i++)
1248
+ args[i] = await args[i];
1114
1249
  // use(plugin)
1115
- if (args[0] instanceof Plugin)
1116
- return this._plugin(args[0]);
1250
+ if (args[0] instanceof Plugin) {
1251
+ await this._plugin(args[0]);
1252
+ return this.server._waiter.endJob();
1253
+ }
1117
1254
  // use(pluginid, ...args)
1118
1255
  if (typeof args[0] === 'string' && MicroServer.plugins[args[0]]) {
1119
1256
  const constructor = MicroServer.plugins[args[0]];
1120
1257
  const plugin = new constructor(this, ...args.slice(1));
1121
- return this._plugin(plugin);
1258
+ await this._plugin(plugin);
1259
+ return this.server._waiter.endJob();
1122
1260
  }
1123
1261
  // use(PluginClass, ...args)
1124
- if (args[0].prototype instanceof Plugin) {
1262
+ if (typeof args[0] === 'function' && args[0].prototype instanceof Plugin) {
1125
1263
  const plugin = new args[0](this, ...args.slice(1));
1126
- return this._plugin(plugin);
1264
+ await this._plugin(plugin);
1265
+ return this.server._waiter.endJob();
1127
1266
  }
1128
1267
  // use(middleware)
1129
- if (typeof args[0] === 'function') {
1130
- return this._middleware(args[0]);
1268
+ if (isFunction(args[0])) {
1269
+ this._middleware(args[0]);
1270
+ return this.server._waiter.endJob();
1131
1271
  }
1132
1272
  let method = '*', url = '/';
1133
1273
  if (typeof args[0] === 'string') {
@@ -1142,7 +1282,7 @@ export class Router extends EventEmitter {
1142
1282
  }
1143
1283
  // use('/url', ControllerClass)
1144
1284
  if (typeof args[0] === 'function' && args[0].prototype instanceof Controller) {
1145
- const routes = args[0].routes();
1285
+ const routes = await args[0].routes();
1146
1286
  if (routes)
1147
1287
  args[0] = routes;
1148
1288
  }
@@ -1150,17 +1290,17 @@ export class Router extends EventEmitter {
1150
1290
  if (Array.isArray(args[0])) {
1151
1291
  if (method !== '*')
1152
1292
  throw new Error('Invalid router usage');
1153
- args[0].forEach(item => {
1293
+ for (const item of args[0]) {
1154
1294
  if (Array.isArray(item)) {
1155
1295
  // [methodUrl, ...middlewares]
1156
1296
  if (typeof item[0] !== 'string' || !item[0].match(/^(\w+ )?\//))
1157
1297
  throw new Error('Url expected');
1158
- return this.use(item[0].replace(/\//, (url === '/' ? '' : url) + '/'), ...item.slice(1));
1298
+ await this.use(item[0].replace(/\//, (url === '/' ? '' : url) + '/'), ...item.slice(1));
1159
1299
  }
1160
1300
  else
1161
1301
  throw new Error('Invalid param');
1162
- });
1163
- return this;
1302
+ }
1303
+ return this.server._waiter.endJob();
1164
1304
  }
1165
1305
  // use('/url', {'METHOD /url': [...middlewares], ... } ])
1166
1306
  if (typeof args[0] === 'object' && args[0].constructor === Object) {
@@ -1169,42 +1309,47 @@ export class Router extends EventEmitter {
1169
1309
  for (const [subUrl, subArgs] of Object.entries(args[0])) {
1170
1310
  if (!subUrl.match(/^(\w+ )?\//))
1171
1311
  throw new Error('Url expected');
1172
- this.use(subUrl.replace(/\//, (url === '/' ? '' : url) + '/'), ...(Array.isArray(subArgs) ? subArgs : [subArgs]));
1312
+ await this.use(subUrl.replace(/\//, (url === '/' ? '' : url) + '/'), ...(Array.isArray(subArgs) ? subArgs : [subArgs]));
1173
1313
  }
1174
- return this;
1314
+ return this.server._waiter.endJob();
1175
1315
  }
1176
1316
  // use('/url', ...middleware)
1177
- return this._add(method, url, 'next', args.filter((o) => o));
1317
+ this._add(method, url, 'next', args.filter((o) => o));
1318
+ return this.server._waiter.endJob();
1178
1319
  }
1179
1320
  _middleware(middleware) {
1180
1321
  if (!middleware)
1181
- return this;
1322
+ return;
1182
1323
  const priority = (middleware?.priority || 0) - 1;
1183
1324
  const stack = priority < -1 ? this._stackAfter : this._stack;
1184
1325
  const idx = stack.findIndex(f => 'priority' in f
1185
1326
  && priority >= (f.priority || 0));
1186
1327
  stack.splice(idx < 0 ? stack.length : idx, 0, middleware);
1187
- return this;
1188
1328
  }
1189
- _plugin(plugin) {
1329
+ async _plugin(plugin) {
1330
+ let added;
1190
1331
  if (plugin.name) {
1191
1332
  if (this.plugins[plugin.name])
1192
1333
  throw new Error(`Plugin ${plugin.name} already added`);
1193
1334
  this.plugins[plugin.name] = plugin;
1335
+ added = plugin.name;
1194
1336
  }
1337
+ await plugin.initialise?.();
1195
1338
  if (plugin.handler) {
1196
1339
  const middleware = plugin.handler.bind(plugin);
1197
1340
  middleware.plugin = plugin;
1198
1341
  middleware.priority = plugin.priority;
1199
- return this._middleware(middleware);
1200
- }
1201
- if (plugin.routes) {
1202
- if (typeof plugin.routes === 'function')
1203
- this.use(plugin.routes());
1204
- else
1205
- this.use(plugin.routes);
1342
+ this._middleware(middleware);
1206
1343
  }
1207
- return this;
1344
+ if (plugin.routes)
1345
+ await this.use(isFunction(plugin.routes) ? await plugin.routes() : plugin.routes);
1346
+ if (added)
1347
+ this._waiter.resolve(added);
1348
+ }
1349
+ async waitPlugin(id) {
1350
+ if (!this.plugins[id])
1351
+ await this._waiter.wait(id);
1352
+ return this.plugins[id];
1208
1353
  }
1209
1354
  /** Add hook */
1210
1355
  hook(url, ...mid) {
@@ -1212,7 +1357,7 @@ export class Router extends EventEmitter {
1212
1357
  let method = '*';
1213
1358
  if (m)
1214
1359
  [method, url] = [m[1], m[2]];
1215
- return this._add(method, url, 'hook', mid);
1360
+ this._add(method, url, 'hook', mid);
1216
1361
  }
1217
1362
  /** Check if middleware allready added */
1218
1363
  has(mid) {
@@ -1220,10 +1365,9 @@ export class Router extends EventEmitter {
1220
1365
  }
1221
1366
  }
1222
1367
  export class MicroServer extends EventEmitter {
1223
- get plugins() { return this.router.plugins; }
1224
1368
  constructor(config) {
1225
1369
  super();
1226
- this._ready = false;
1370
+ this._waiter = new EmitterWaiter(this);
1227
1371
  this._methods = {};
1228
1372
  let promise = Promise.resolve();
1229
1373
  this._init = (f, ...args) => {
@@ -1255,7 +1399,7 @@ export class MicroServer extends EventEmitter {
1255
1399
  }
1256
1400
  /** Add one time listener or call immediatelly for 'ready' */
1257
1401
  once(name, cb) {
1258
- if (name === 'ready' && this._ready)
1402
+ if (name === 'ready' && this.isReady())
1259
1403
  cb();
1260
1404
  else
1261
1405
  super.once(name, cb);
@@ -1263,11 +1407,22 @@ export class MicroServer extends EventEmitter {
1263
1407
  }
1264
1408
  /** Add listener and call immediatelly for 'ready' */
1265
1409
  on(name, cb) {
1266
- if (name === 'ready' && this._ready)
1410
+ if (name === 'ready' && this.isReady())
1267
1411
  cb();
1268
1412
  super.on(name, cb);
1269
1413
  return this;
1270
1414
  }
1415
+ isReady() {
1416
+ return !this._waiter.busy;
1417
+ }
1418
+ async waitReady() {
1419
+ if (this.isReady())
1420
+ return;
1421
+ return this._waiter.wait("ready");
1422
+ }
1423
+ async waitPlugin(id) {
1424
+ await this.router.waitPlugin(id);
1425
+ }
1271
1426
  /** Listen server, should be used only if config.listen is not set */
1272
1427
  listen(config) {
1273
1428
  const listen = (config?.listen || this.config.listen || 0) + '';
@@ -1293,164 +1448,62 @@ export class MicroServer extends EventEmitter {
1293
1448
  });
1294
1449
  }
1295
1450
  }
1296
- return new Promise((resolve) => {
1297
- let readyCount = 0;
1298
- this._ready = false;
1299
- const ready = (srv) => {
1300
- if (srv)
1301
- readyCount++;
1302
- if (readyCount >= this.servers.size) {
1303
- if (!this._ready) {
1304
- this._ready = true;
1305
- if (this.servers.size === 0)
1306
- this.close();
1307
- else
1308
- this.emit('ready');
1309
- resolve();
1310
- }
1311
- }
1312
- };
1313
- const reg = /^((?<proto>\w+):\/\/)?(?<host>(\[[^\]]+\]|[a-z][^:,]+|\d+\.\d+\.\d+\.\d+))?:?(?<port>\d+)?/;
1314
- listen.split(',').forEach(listen => {
1315
- let { proto, host, port } = reg.exec(listen)?.groups || {};
1316
- let srv;
1317
- switch (proto) {
1318
- case 'tcp':
1319
- if (!config?.handler)
1320
- throw new Error('Handler is required for tcp');
1321
- srv = net.createServer(handler);
1322
- break;
1323
- case 'tls':
1324
- if (!config?.handler)
1325
- throw new Error('Handler is required for tls');
1326
- srv = tls.createServer(tlsOptions(), handler);
1327
- tlsOptionsReload(srv);
1328
- break;
1329
- case 'https':
1330
- port = port || '443';
1331
- srv = https.createServer(tlsOptions(), handler);
1332
- tlsOptionsReload(srv);
1333
- break;
1334
- default:
1335
- port = port || '80';
1336
- srv = http.createServer(handler);
1337
- break;
1338
- }
1339
- this.servers.add(srv);
1340
- if (port === '0') // skip listening
1341
- ready(srv);
1342
- else {
1343
- srv.listen(parseInt(port), host?.replace(/[\[\]]/g, '') || '0.0.0.0', () => {
1344
- const addr = srv.address();
1345
- this.emit('listen', addr.port, addr.address, srv);
1346
- ready(srv);
1347
- });
1348
- }
1349
- srv.on('error', err => {
1350
- this.servers.delete(srv);
1351
- srv.close();
1352
- ready();
1353
- this.emit('error', err);
1354
- });
1355
- srv.on('connection', s => {
1356
- this.sockets.add(s);
1357
- s.once('close', () => this.sockets.delete(s));
1451
+ const reg = /^((?<proto>\w+):\/\/)?(?<host>(\[[^\]]+\]|[a-z][^:,]+|\d+\.\d+\.\d+\.\d+))?:?(?<port>\d+)?/;
1452
+ listen.split(',').forEach(listen => {
1453
+ this._waiter.startJob();
1454
+ let { proto, host, port } = reg.exec(listen)?.groups || {};
1455
+ let srv;
1456
+ switch (proto) {
1457
+ case 'tcp':
1458
+ if (!config?.handler)
1459
+ throw new Error('Handler is required for tcp');
1460
+ srv = net.createServer(handler);
1461
+ break;
1462
+ case 'tls':
1463
+ if (!config?.handler)
1464
+ throw new Error('Handler is required for tls');
1465
+ srv = tls.createServer(tlsOptions(), handler);
1466
+ tlsOptionsReload(srv);
1467
+ break;
1468
+ case 'https':
1469
+ port = port || '443';
1470
+ srv = https.createServer(tlsOptions(), handler);
1471
+ tlsOptionsReload(srv);
1472
+ break;
1473
+ default:
1474
+ port = port || '80';
1475
+ srv = http.createServer(handler);
1476
+ break;
1477
+ }
1478
+ this.servers.add(srv);
1479
+ if (port === '0') // skip listening
1480
+ this._waiter.endJob();
1481
+ else {
1482
+ srv.listen(parseInt(port), host?.replace(/[\[\]]/g, '') || '0.0.0.0', () => {
1483
+ const addr = srv.address();
1484
+ this.emit('listen', addr.port, addr.address, srv);
1485
+ srv._ready = true;
1486
+ this._waiter.endJob();
1358
1487
  });
1359
- srv.on('upgrade', this.handlerUpgrade.bind(this));
1360
- ready();
1488
+ }
1489
+ srv.on('error', err => {
1490
+ srv.close();
1491
+ this.servers.delete(srv);
1492
+ if (!srv._ready)
1493
+ this._waiter.endJob();
1494
+ this.emit('error', err);
1495
+ });
1496
+ srv.on('connection', s => {
1497
+ this.sockets.add(s);
1498
+ s.once('close', () => this.sockets.delete(s));
1361
1499
  });
1500
+ srv.on('upgrade', this.handlerUpgrade.bind(this));
1362
1501
  });
1363
- }
1364
- /** bind middleware or create one from string like: 'redirect:302,https://redirect.to', 'error:422', 'param:name=value', 'acl:users/get', 'model:User', 'group:Users', 'user:admin' */
1365
- bind(fn) {
1366
- if (typeof fn === 'string') {
1367
- let name = fn;
1368
- let idx = name.indexOf(':');
1369
- if (idx < 0 && name.includes('=')) {
1370
- name = 'param:' + name;
1371
- idx = 5;
1372
- }
1373
- if (idx >= 0) {
1374
- const v = name.slice(idx + 1);
1375
- const type = name.slice(0, idx);
1376
- // predefined middlewares
1377
- switch (type) {
1378
- // redirect:302,https://redirect.to
1379
- case 'redirect': {
1380
- let redirect = v.split(','), code = parseInt(v[0]);
1381
- if (!code || code < 301 || code > 399)
1382
- code = 302;
1383
- return (req, res) => res.redirect(code, redirect[1] || v);
1384
- }
1385
- // error:422
1386
- case 'error':
1387
- return (req, res) => res.error(parseInt(v) || 422);
1388
- // param:name=value
1389
- case 'param': {
1390
- idx = v.indexOf('=');
1391
- if (idx > 0) {
1392
- const prm = v.slice(0, idx), val = v.slice(idx + 1);
1393
- return (req, res, next) => { req.params[prm] = val; return next(); };
1394
- }
1395
- break;
1396
- }
1397
- case 'model': {
1398
- const model = v;
1399
- return (req, res) => {
1400
- res.isJson = true;
1401
- req.params.model = model;
1402
- req.model = Model.models[model];
1403
- if (!req.model) {
1404
- console.error(`Data model ${model} not defined for request ${req.path}`);
1405
- return res.error(422);
1406
- }
1407
- return req.model.handler(req, res);
1408
- };
1409
- }
1410
- // user:userid
1411
- // group:user_groupid
1412
- // acl:validacl
1413
- case 'user':
1414
- case 'group':
1415
- case 'acl':
1416
- return (req, res, next) => {
1417
- if (type === 'user' && v === req.user?.id)
1418
- return next();
1419
- if (type === 'acl') {
1420
- req.params.acl = v;
1421
- if (req.auth?.acl(v))
1422
- return next();
1423
- }
1424
- if (type === 'group') {
1425
- req.params.group = v;
1426
- if (req.user?.group === v)
1427
- return next();
1428
- }
1429
- const accept = req.headers.accept || '';
1430
- if (!res.isJson && req.auth?.options.redirect && req.method === 'GET' && !accept.includes('json') && (accept.includes('html') || accept.includes('*/*'))) {
1431
- if (req.auth.options.redirect && req.url !== req.auth.options.redirect)
1432
- return res.redirect(302, req.auth.options.redirect);
1433
- else if (req.auth.options.mode !== 'cookie') {
1434
- res.setHeader('WWW-Authenticate', `Basic realm="${req.auth.options.realm}"`);
1435
- return res.error(401);
1436
- }
1437
- }
1438
- return res.error('Permission denied');
1439
- };
1440
- }
1441
- }
1442
- throw new Error('Invalid option: ' + name);
1443
- }
1444
- if (fn && typeof fn === 'object' && 'handler' in fn && typeof fn.handler === 'function')
1445
- return fn.handler.bind(fn);
1446
- if (typeof fn !== 'function')
1447
- throw new Error('Invalid middleware: ' + String.toString.call(fn));
1448
- return fn.bind(this);
1502
+ return this._waiter.wait('ready');
1449
1503
  }
1450
1504
  /** Add middleware, routes, etc.. see {router.use} */
1451
1505
  use(...args) {
1452
- this.router.use(...args);
1453
- return this;
1506
+ return this.router.use(...args);
1454
1507
  }
1455
1508
  /** Default server handler */
1456
1509
  handler(req, res) {
@@ -1592,8 +1645,8 @@ export class MicroServer extends EventEmitter {
1592
1645
  }
1593
1646
  this.sockets.clear();
1594
1647
  }).then(() => {
1595
- this._ready = false;
1596
1648
  this.emit('close');
1649
+ this._waiter.resolve("close");
1597
1650
  });
1598
1651
  }
1599
1652
  /** Add route, alias to `server.router.use(url, ...args)` */