@radatek/microserver 2.3.1 → 2.3.2
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/microserver.d.ts +39 -13
- package/microserver.js +121 -96
- package/package.json +1 -1
package/microserver.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 2.3.
|
|
3
|
+
* @version 2.3.2
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -128,6 +128,7 @@ export interface WebSocketOptions {
|
|
|
128
128
|
autoPong?: boolean;
|
|
129
129
|
permessageDeflate?: boolean;
|
|
130
130
|
maxWindowBits?: number;
|
|
131
|
+
timeout?: number;
|
|
131
132
|
deflate?: boolean;
|
|
132
133
|
}
|
|
133
134
|
/** WebSocket class */
|
|
@@ -213,12 +214,11 @@ export interface Middleware {
|
|
|
213
214
|
plugin?: Plugin;
|
|
214
215
|
}
|
|
215
216
|
declare class Waiter {
|
|
216
|
-
|
|
217
|
-
startJob(): void;
|
|
217
|
+
isBusy(id?: string): boolean;
|
|
218
|
+
startJob(id?: string): void;
|
|
218
219
|
endJob(id?: string): void;
|
|
219
220
|
get nextId(): string;
|
|
220
|
-
wait(id
|
|
221
|
-
resolve(id: string): void;
|
|
221
|
+
wait(id?: string): Promise<void>;
|
|
222
222
|
}
|
|
223
223
|
/** Router */
|
|
224
224
|
export declare class Router extends EventEmitter {
|
|
@@ -373,7 +373,7 @@ export declare class MicroServer extends EventEmitter {
|
|
|
373
373
|
once(name: string, cb: Function): this;
|
|
374
374
|
/** Add listener and call immediatelly for 'ready' */
|
|
375
375
|
on(name: string, cb: Function): this;
|
|
376
|
-
isReady(): boolean;
|
|
376
|
+
get isReady(): boolean;
|
|
377
377
|
waitReady(): Promise<void>;
|
|
378
378
|
waitPlugin(id: string): Promise<void>;
|
|
379
379
|
/** Listen server, should be used only if config.listen is not set */
|
|
@@ -538,6 +538,33 @@ export interface AuthOptions {
|
|
|
538
538
|
/** Interal next cache cleanup time */
|
|
539
539
|
cacheCleanup?: number;
|
|
540
540
|
}
|
|
541
|
+
export interface AuthOptionsInternal extends AuthOptions {
|
|
542
|
+
/** Authentication token */
|
|
543
|
+
token: Buffer;
|
|
544
|
+
/** Users */
|
|
545
|
+
users: (usr: string, psw?: string) => Promise<UserInfo | undefined>;
|
|
546
|
+
/** Default ACL */
|
|
547
|
+
defaultAcl: {
|
|
548
|
+
[key: string]: boolean;
|
|
549
|
+
};
|
|
550
|
+
/** Expire time in seconds */
|
|
551
|
+
expire: number;
|
|
552
|
+
/** Authentication mode */
|
|
553
|
+
mode: 'cookie' | 'token';
|
|
554
|
+
/** Authentication realm for basic authentication */
|
|
555
|
+
realm: string;
|
|
556
|
+
/** Redirect URL */
|
|
557
|
+
redirect: string;
|
|
558
|
+
/** Authentication cache */
|
|
559
|
+
cache: {
|
|
560
|
+
[key: string]: {
|
|
561
|
+
data: UserInfo;
|
|
562
|
+
time: number;
|
|
563
|
+
};
|
|
564
|
+
};
|
|
565
|
+
/** Interal next cache cleanup time */
|
|
566
|
+
cacheCleanup: number;
|
|
567
|
+
}
|
|
541
568
|
/** Authentication class */
|
|
542
569
|
export declare class Auth {
|
|
543
570
|
/** Server request */
|
|
@@ -545,10 +572,8 @@ export declare class Auth {
|
|
|
545
572
|
/** Server response */
|
|
546
573
|
res: ServerResponse | undefined;
|
|
547
574
|
/** Authentication options */
|
|
548
|
-
options:
|
|
549
|
-
|
|
550
|
-
users: ((usr: string, psw?: string, salt?: string) => Promise<UserInfo | undefined>);
|
|
551
|
-
constructor(options?: AuthOptions);
|
|
575
|
+
options: AuthOptionsInternal;
|
|
576
|
+
constructor(options: AuthOptionsInternal, req?: ServerRequest, res?: ServerResponse);
|
|
552
577
|
/** Decode token */
|
|
553
578
|
decode(data: string): {
|
|
554
579
|
data: string;
|
|
@@ -709,9 +734,10 @@ export declare interface ModelCollections {
|
|
|
709
734
|
collection(name: string): Promise<MicroCollection>;
|
|
710
735
|
}
|
|
711
736
|
export declare class Model<TSchema extends ModelSchema> {
|
|
712
|
-
static collections
|
|
737
|
+
static collections: ModelCollections;
|
|
713
738
|
static models: Record<string, Model<any>>;
|
|
714
|
-
static
|
|
739
|
+
static set db(db: any);
|
|
740
|
+
static get db(): any;
|
|
715
741
|
/** Define model */
|
|
716
742
|
static define<T extends ModelSchema>(name: string, schema: T, options?: {
|
|
717
743
|
collection?: MicroCollection | Promise<MicroCollection>;
|
|
@@ -778,7 +804,7 @@ export declare interface FindOptions {
|
|
|
778
804
|
}
|
|
779
805
|
/** Collection factory */
|
|
780
806
|
export declare class MicroCollectionStore {
|
|
781
|
-
constructor(dataPath?: string);
|
|
807
|
+
constructor(dataPath?: string, storeTimeDelay?: number);
|
|
782
808
|
/** Get collection */
|
|
783
809
|
collection(name: string): Promise<MicroCollection>;
|
|
784
810
|
}
|
package/microserver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 2.3.
|
|
3
|
+
* @version 2.3.2
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -457,8 +457,10 @@ export class WebSocket extends EventEmitter {
|
|
|
457
457
|
maxPayload: 1024 * 1024,
|
|
458
458
|
permessageDeflate: false,
|
|
459
459
|
maxWindowBits: 15,
|
|
460
|
+
timeout: 120000,
|
|
460
461
|
...options
|
|
461
462
|
};
|
|
463
|
+
this._socket.setTimeout(this._options.timeout || 120000);
|
|
462
464
|
const key = req.headers['sec-websocket-key'];
|
|
463
465
|
const upgrade = req.headers.upgrade;
|
|
464
466
|
const version = +(req.headers['sec-websocket-version'] || 0);
|
|
@@ -922,46 +924,53 @@ export class Controller {
|
|
|
922
924
|
return routes;
|
|
923
925
|
}
|
|
924
926
|
}
|
|
925
|
-
class
|
|
927
|
+
class WaiterJob {
|
|
926
928
|
constructor() {
|
|
927
|
-
this._waiters =
|
|
928
|
-
this._id = 0;
|
|
929
|
+
this._waiters = [];
|
|
929
930
|
this._busy = 0;
|
|
930
931
|
}
|
|
931
|
-
|
|
932
|
-
return this._busy > 0;
|
|
933
|
-
}
|
|
934
|
-
startJob() {
|
|
932
|
+
start() {
|
|
935
933
|
this._busy++;
|
|
936
934
|
}
|
|
937
|
-
|
|
935
|
+
end() {
|
|
938
936
|
this._busy--;
|
|
937
|
+
for (const resolve of this._waiters.splice(0))
|
|
938
|
+
resolve();
|
|
939
|
+
}
|
|
940
|
+
async wait() {
|
|
939
941
|
if (!this._busy)
|
|
940
|
-
|
|
942
|
+
return;
|
|
943
|
+
return new Promise(resolve => this._waiters.push(resolve));
|
|
941
944
|
}
|
|
942
|
-
|
|
943
|
-
|
|
945
|
+
}
|
|
946
|
+
class Waiter {
|
|
947
|
+
constructor() {
|
|
948
|
+
this._id = 0;
|
|
949
|
+
this._waiters = {};
|
|
944
950
|
}
|
|
945
|
-
|
|
946
|
-
|
|
951
|
+
isBusy(id) {
|
|
952
|
+
const waiter = this._waiters[id || 'ready'];
|
|
953
|
+
return !!waiter?._busy;
|
|
947
954
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
if (
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
delete this._waiters[id];
|
|
955
|
+
startJob(id) {
|
|
956
|
+
let waiter = this._waiters[id || 'ready'];
|
|
957
|
+
if (!waiter)
|
|
958
|
+
waiter = this._waiters[id || 'ready'] = new WaiterJob();
|
|
959
|
+
waiter._busy++;
|
|
954
960
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
this._emitter = emitter;
|
|
961
|
+
endJob(id) {
|
|
962
|
+
const waiter = this._waiters[id || 'ready'];
|
|
963
|
+
if (waiter)
|
|
964
|
+
waiter.end();
|
|
960
965
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
966
|
+
get nextId() {
|
|
967
|
+
return (++this._id).toString();
|
|
968
|
+
}
|
|
969
|
+
async wait(id) {
|
|
970
|
+
const waiter = this._waiters[id || 'ready'];
|
|
971
|
+
if (!waiter)
|
|
972
|
+
return;
|
|
973
|
+
return waiter.wait();
|
|
965
974
|
}
|
|
966
975
|
}
|
|
967
976
|
/** Router */
|
|
@@ -1361,7 +1370,7 @@ export class Router extends EventEmitter {
|
|
|
1361
1370
|
if (plugin.routes)
|
|
1362
1371
|
await this.use(isFunction(plugin.routes) ? await plugin.routes() : plugin.routes);
|
|
1363
1372
|
if (added)
|
|
1364
|
-
this.
|
|
1373
|
+
this.emit(added);
|
|
1365
1374
|
}
|
|
1366
1375
|
async waitPlugin(id) {
|
|
1367
1376
|
if (!this.plugins[id])
|
|
@@ -1384,12 +1393,9 @@ export class Router extends EventEmitter {
|
|
|
1384
1393
|
export class MicroServer extends EventEmitter {
|
|
1385
1394
|
constructor(config) {
|
|
1386
1395
|
super();
|
|
1387
|
-
this._waiter = new
|
|
1396
|
+
this._waiter = new Waiter();
|
|
1388
1397
|
this._methods = {};
|
|
1389
1398
|
let promise = Promise.resolve();
|
|
1390
|
-
this._init = (f, ...args) => {
|
|
1391
|
-
promise = promise.then(() => f.apply(this, args)).catch(e => this.emit('error', e));
|
|
1392
|
-
};
|
|
1393
1399
|
this.config = {
|
|
1394
1400
|
maxBodySize: defaultMaxBodySize,
|
|
1395
1401
|
methods: defaultMethods,
|
|
@@ -1406,17 +1412,20 @@ export class MicroServer extends EventEmitter {
|
|
|
1406
1412
|
if (config[key])
|
|
1407
1413
|
this.router.use(MicroServer.plugins[key], config[key]);
|
|
1408
1414
|
}
|
|
1409
|
-
if (config.listen)
|
|
1410
|
-
this.
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
+
if (config.listen) {
|
|
1416
|
+
this._waiter.startJob();
|
|
1417
|
+
this.listen({
|
|
1418
|
+
tls: config.tls,
|
|
1419
|
+
listen: config.listen || 8080
|
|
1420
|
+
}).then(() => {
|
|
1421
|
+
this._waiter.endJob();
|
|
1415
1422
|
});
|
|
1423
|
+
}
|
|
1424
|
+
this._waiter.wait().then(() => this.emit('ready'));
|
|
1416
1425
|
}
|
|
1417
1426
|
/** Add one time listener or call immediatelly for 'ready' */
|
|
1418
1427
|
once(name, cb) {
|
|
1419
|
-
if (name === 'ready' && this.isReady
|
|
1428
|
+
if (name === 'ready' && this.isReady)
|
|
1420
1429
|
cb();
|
|
1421
1430
|
else
|
|
1422
1431
|
super.once(name, cb);
|
|
@@ -1424,18 +1433,18 @@ export class MicroServer extends EventEmitter {
|
|
|
1424
1433
|
}
|
|
1425
1434
|
/** Add listener and call immediatelly for 'ready' */
|
|
1426
1435
|
on(name, cb) {
|
|
1427
|
-
if (name === 'ready' && this.isReady
|
|
1436
|
+
if (name === 'ready' && this.isReady)
|
|
1428
1437
|
cb();
|
|
1429
1438
|
super.on(name, cb);
|
|
1430
1439
|
return this;
|
|
1431
1440
|
}
|
|
1432
|
-
isReady() {
|
|
1433
|
-
return !this._waiter.
|
|
1441
|
+
get isReady() {
|
|
1442
|
+
return !this._waiter.isBusy();
|
|
1434
1443
|
}
|
|
1435
1444
|
async waitReady() {
|
|
1436
|
-
if (this.isReady
|
|
1445
|
+
if (this.isReady)
|
|
1437
1446
|
return;
|
|
1438
|
-
return this._waiter.wait(
|
|
1447
|
+
return this._waiter.wait();
|
|
1439
1448
|
}
|
|
1440
1449
|
async waitPlugin(id) {
|
|
1441
1450
|
await this.router.waitPlugin(id);
|
|
@@ -1467,7 +1476,7 @@ export class MicroServer extends EventEmitter {
|
|
|
1467
1476
|
}
|
|
1468
1477
|
const reg = /^((?<proto>\w+):\/\/)?(?<host>(\[[^\]]+\]|[a-z][^:,]+|\d+\.\d+\.\d+\.\d+))?:?(?<port>\d+)?/;
|
|
1469
1478
|
listen.split(',').forEach(listen => {
|
|
1470
|
-
this._waiter.startJob();
|
|
1479
|
+
this._waiter.startJob('listen');
|
|
1471
1480
|
let { proto, host, port } = reg.exec(listen)?.groups || {};
|
|
1472
1481
|
let srv;
|
|
1473
1482
|
switch (proto) {
|
|
@@ -1494,20 +1503,20 @@ export class MicroServer extends EventEmitter {
|
|
|
1494
1503
|
}
|
|
1495
1504
|
this.servers.add(srv);
|
|
1496
1505
|
if (port === '0') // skip listening
|
|
1497
|
-
this._waiter.endJob();
|
|
1506
|
+
this._waiter.endJob('listen');
|
|
1498
1507
|
else {
|
|
1499
1508
|
srv.listen(parseInt(port), host?.replace(/[\[\]]/g, '') || '0.0.0.0', () => {
|
|
1500
1509
|
const addr = srv.address();
|
|
1501
1510
|
this.emit('listen', addr.port, addr.address, srv);
|
|
1502
1511
|
srv._ready = true;
|
|
1503
|
-
this._waiter.endJob();
|
|
1512
|
+
this._waiter.endJob('listen');
|
|
1504
1513
|
});
|
|
1505
1514
|
}
|
|
1506
1515
|
srv.on('error', err => {
|
|
1507
1516
|
srv.close();
|
|
1508
1517
|
this.servers.delete(srv);
|
|
1509
1518
|
if (!srv._ready)
|
|
1510
|
-
this._waiter.endJob();
|
|
1519
|
+
this._waiter.endJob('listen');
|
|
1511
1520
|
this.emit('error', err);
|
|
1512
1521
|
});
|
|
1513
1522
|
srv.on('connection', s => {
|
|
@@ -1516,7 +1525,7 @@ export class MicroServer extends EventEmitter {
|
|
|
1516
1525
|
});
|
|
1517
1526
|
srv.on('upgrade', this.handlerUpgrade.bind(this));
|
|
1518
1527
|
});
|
|
1519
|
-
return this._waiter.wait('
|
|
1528
|
+
return this._waiter.wait('listen');
|
|
1520
1529
|
}
|
|
1521
1530
|
/** Add middleware, routes, etc.. see {router.use} */
|
|
1522
1531
|
use(...args) {
|
|
@@ -1663,7 +1672,6 @@ export class MicroServer extends EventEmitter {
|
|
|
1663
1672
|
this.sockets.clear();
|
|
1664
1673
|
}).then(() => {
|
|
1665
1674
|
this.emit('close');
|
|
1666
|
-
this._waiter.resolve("close");
|
|
1667
1675
|
});
|
|
1668
1676
|
}
|
|
1669
1677
|
/** Add route, alias to `server.router.use(url, ...args)` */
|
|
@@ -2006,31 +2014,12 @@ ProxyPlugin.validHeaders = {
|
|
|
2006
2014
|
MicroServer.plugins.proxy = ProxyPlugin;
|
|
2007
2015
|
/** Authentication class */
|
|
2008
2016
|
export class Auth {
|
|
2009
|
-
constructor(options) {
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
if (
|
|
2014
|
-
|
|
2015
|
-
if (!(token instanceof Buffer))
|
|
2016
|
-
token = Buffer.from(token);
|
|
2017
|
-
this.options = {
|
|
2018
|
-
token,
|
|
2019
|
-
users: options?.users,
|
|
2020
|
-
mode: options?.mode || 'cookie',
|
|
2021
|
-
defaultAcl: options?.defaultAcl || { '*': false },
|
|
2022
|
-
expire: options?.expire || defaultExpire,
|
|
2023
|
-
cache: options?.cache || {}
|
|
2024
|
-
};
|
|
2025
|
-
if (typeof options?.users === 'function')
|
|
2026
|
-
this.users = options.users;
|
|
2027
|
-
else
|
|
2028
|
-
this.users = async (usrid, psw) => {
|
|
2029
|
-
const users = this.options.users;
|
|
2030
|
-
const usr = users?.[usrid];
|
|
2031
|
-
if (usr && (psw === undefined || this.checkPassword(usrid, psw, usr.password || '')))
|
|
2032
|
-
return usr;
|
|
2033
|
-
};
|
|
2017
|
+
constructor(options, req, res) {
|
|
2018
|
+
this.options = options;
|
|
2019
|
+
this.req = req;
|
|
2020
|
+
this.res = res;
|
|
2021
|
+
if (req)
|
|
2022
|
+
req.auth = this;
|
|
2034
2023
|
}
|
|
2035
2024
|
/** Decode token */
|
|
2036
2025
|
decode(data) {
|
|
@@ -2106,7 +2095,7 @@ export class Auth {
|
|
|
2106
2095
|
data = JSON.stringify(usr);
|
|
2107
2096
|
else if (typeof usr === 'string') {
|
|
2108
2097
|
if (psw !== undefined) {
|
|
2109
|
-
const userInfo = await this.users(usr, psw);
|
|
2098
|
+
const userInfo = await this.options.users(usr, psw);
|
|
2110
2099
|
data = userInfo?.id || userInfo?._id;
|
|
2111
2100
|
}
|
|
2112
2101
|
else
|
|
@@ -2123,7 +2112,7 @@ export class Auth {
|
|
|
2123
2112
|
if (typeof usr === 'object')
|
|
2124
2113
|
usrInfo = usr;
|
|
2125
2114
|
if (typeof usr === 'string')
|
|
2126
|
-
usrInfo = await this.users(usr, psw
|
|
2115
|
+
usrInfo = await this.options.users(usr, psw);
|
|
2127
2116
|
if (usrInfo?.id || usrInfo?._id) {
|
|
2128
2117
|
const expire = Math.min(34560000, options?.expire || this.options.expire || defaultExpire);
|
|
2129
2118
|
const expireTime = new Date().getTime() + expire * 1000;
|
|
@@ -2289,17 +2278,32 @@ class AuthPlugin extends Plugin {
|
|
|
2289
2278
|
...options,
|
|
2290
2279
|
cacheCleanup: new Date().getTime()
|
|
2291
2280
|
};
|
|
2292
|
-
if (
|
|
2281
|
+
if (options?.token === defaultToken)
|
|
2293
2282
|
console.warn('Default token in auth plugin');
|
|
2283
|
+
let token = options?.token || defaultToken;
|
|
2284
|
+
if (!token || token.length !== 32)
|
|
2285
|
+
token = defaultToken;
|
|
2286
|
+
if (token.length !== 32)
|
|
2287
|
+
token = crypto.createHash('sha256').update(token).digest();
|
|
2288
|
+
if (!(token instanceof Buffer))
|
|
2289
|
+
token = Buffer.from(token);
|
|
2290
|
+
this.options.token = token;
|
|
2291
|
+
if (typeof options?.users === 'function')
|
|
2292
|
+
this.options.users = (usr, psw) => options.users(usr, psw);
|
|
2293
|
+
else {
|
|
2294
|
+
this.options.users = async (usrid, psw) => {
|
|
2295
|
+
const users = this.options.users;
|
|
2296
|
+
const usr = users?.[usrid];
|
|
2297
|
+
if (usr && (psw === undefined || router.auth?.checkPassword(usrid, psw, usr.password || '')))
|
|
2298
|
+
return usr;
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2294
2301
|
router.auth = new Auth(this.options);
|
|
2295
2302
|
}
|
|
2296
2303
|
/** Authentication middleware */
|
|
2297
2304
|
async handler(req, res, next) {
|
|
2298
2305
|
const options = this.options, cache = options.cache;
|
|
2299
|
-
const auth = new Auth(options);
|
|
2300
|
-
req.auth = auth;
|
|
2301
|
-
auth.req = req;
|
|
2302
|
-
auth.res = res;
|
|
2306
|
+
const auth = new Auth(this.options, req, res);
|
|
2303
2307
|
const authorization = req.headers.authorization || '';
|
|
2304
2308
|
if (authorization.startsWith('Basic ')) {
|
|
2305
2309
|
let usr = cache?.[authorization];
|
|
@@ -2308,7 +2312,7 @@ class AuthPlugin extends Plugin {
|
|
|
2308
2312
|
else {
|
|
2309
2313
|
const usrpsw = Buffer.from(authorization.slice(6), 'base64').toString('utf-8'), pos = usrpsw.indexOf(':'), username = usrpsw.slice(0, pos), psw = usrpsw.slice(pos + 1);
|
|
2310
2314
|
if (username && psw)
|
|
2311
|
-
req.user = await auth.users(username, psw);
|
|
2315
|
+
req.user = await auth.options.users(username, psw);
|
|
2312
2316
|
if (!req.user)
|
|
2313
2317
|
return res.error(401);
|
|
2314
2318
|
if (cache) // 1 hour to expire in cache
|
|
@@ -2354,7 +2358,7 @@ class AuthPlugin extends Plugin {
|
|
|
2354
2358
|
catch (e) { }
|
|
2355
2359
|
}
|
|
2356
2360
|
else {
|
|
2357
|
-
req.user = await auth.users(usrData.data);
|
|
2361
|
+
req.user = await auth.options.users(usrData.data);
|
|
2358
2362
|
if (!req.user) {
|
|
2359
2363
|
req.auth.logout();
|
|
2360
2364
|
return new AccessDenied();
|
|
@@ -2383,7 +2387,7 @@ export class FileStore {
|
|
|
2383
2387
|
this._dir = options?.dir || 'data';
|
|
2384
2388
|
this._cacheTimeout = options?.cacheTimeout || 2000;
|
|
2385
2389
|
this._cacheItems = options?.cacheItems || 10;
|
|
2386
|
-
this._debounceTimeout = options?.debounceTimeout
|
|
2390
|
+
this._debounceTimeout = options?.debounceTimeout ?? 1000;
|
|
2387
2391
|
this._iter = 0;
|
|
2388
2392
|
}
|
|
2389
2393
|
/** cleanup cache */
|
|
@@ -2435,16 +2439,16 @@ export class FileStore {
|
|
|
2435
2439
|
if (item && new Date().getTime() - item.atime < this._cacheTimeout)
|
|
2436
2440
|
return item.data;
|
|
2437
2441
|
try {
|
|
2438
|
-
const stat = await fs.promises.lstat(path.join(this._dir, name));
|
|
2439
|
-
if (item?.mtime !== stat.mtime.getTime()) {
|
|
2440
|
-
let data = JSON.parse(await fs.promises.readFile(path.join(this._dir, name), 'utf8') || '{}');
|
|
2442
|
+
const stat = await fs.promises.lstat(path.join(this._dir, name)).catch(() => null);
|
|
2443
|
+
if (!stat || item?.mtime !== stat.mtime.getTime()) {
|
|
2444
|
+
let data = stat ? JSON.parse(await fs.promises.readFile(path.join(this._dir, name), 'utf8').catch(() => '') || '{}') : {};
|
|
2441
2445
|
this._iter++;
|
|
2442
2446
|
this.cleanup();
|
|
2443
2447
|
if (autosave)
|
|
2444
2448
|
data = this.observe(data, () => this.save(name, data));
|
|
2445
2449
|
this._cache[name] = {
|
|
2446
2450
|
atime: new Date().getTime(),
|
|
2447
|
-
mtime: stat
|
|
2451
|
+
mtime: stat?.mtime.getTime() || new Date().getTime(),
|
|
2448
2452
|
data: data
|
|
2449
2453
|
};
|
|
2450
2454
|
return data;
|
|
@@ -2573,14 +2577,32 @@ function newObjectId() {
|
|
|
2573
2577
|
break;
|
|
2574
2578
|
return (new Date().getTime() / 1000 | 0).toString(16) + globalObjectId.toString('hex');
|
|
2575
2579
|
}
|
|
2580
|
+
class ModelCollectionsInternal {
|
|
2581
|
+
constructor() {
|
|
2582
|
+
this._wait = new Promise(resolve => this._ready = resolve);
|
|
2583
|
+
}
|
|
2584
|
+
set db(db) {
|
|
2585
|
+
Promise.resolve(db).then(db => this._ready(this._db = db));
|
|
2586
|
+
}
|
|
2587
|
+
get db() {
|
|
2588
|
+
return this._db;
|
|
2589
|
+
}
|
|
2590
|
+
async collection(name) {
|
|
2591
|
+
const db = await this._wait;
|
|
2592
|
+
return db.collection(name);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2576
2595
|
export class Model {
|
|
2577
|
-
static
|
|
2578
|
-
|
|
2596
|
+
static set db(db) {
|
|
2597
|
+
this.collections.db = db;
|
|
2598
|
+
}
|
|
2599
|
+
static get db() {
|
|
2600
|
+
return this.collections.db;
|
|
2579
2601
|
}
|
|
2580
2602
|
/** Define model */
|
|
2581
2603
|
static define(name, schema, options) {
|
|
2582
2604
|
options = options || {};
|
|
2583
|
-
if (!options.collection
|
|
2605
|
+
if (!options.collection)
|
|
2584
2606
|
options.collection = this.collections.collection(name);
|
|
2585
2607
|
const inst = options?.class
|
|
2586
2608
|
? new options.class(schema, { name, ...options })
|
|
@@ -2963,13 +2985,16 @@ export class Model {
|
|
|
2963
2985
|
}
|
|
2964
2986
|
}
|
|
2965
2987
|
}
|
|
2988
|
+
Model.collections = new ModelCollectionsInternal();
|
|
2966
2989
|
Model.models = {};
|
|
2967
2990
|
/** Collection factory */
|
|
2968
2991
|
export class MicroCollectionStore {
|
|
2969
|
-
constructor(dataPath) {
|
|
2992
|
+
constructor(dataPath, storeTimeDelay) {
|
|
2970
2993
|
this._collections = new Map();
|
|
2971
2994
|
if (dataPath)
|
|
2972
|
-
this._store = new FileStore({ dir: dataPath.replace(/^\w+:\/\//, '') });
|
|
2995
|
+
this._store = new FileStore({ dir: dataPath.replace(/^\w+:\/\//, ''), debounceTimeout: storeTimeDelay ?? 1000 });
|
|
2996
|
+
if (!Model.db)
|
|
2997
|
+
Model.db = this;
|
|
2973
2998
|
}
|
|
2974
2999
|
/** Get collection */
|
|
2975
3000
|
async collection(name) {
|