@radatek/microserver 2.3.0 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MicroServer
3
- * @version 2.3.0
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
- get busy(): boolean;
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: string): Promise<void>;
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: AuthOptions;
549
- /** Get user function */
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?: ModelCollections;
737
+ static collections: ModelCollections;
713
738
  static models: Record<string, Model<any>>;
714
- static schema(schema: ModelSchema): ModelSchema;
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>;
@@ -739,9 +765,9 @@ export declare class Model<TSchema extends ModelSchema> {
739
765
  /** Find many documents */
740
766
  findMany(query: Query, options?: ModelContextOptions): Promise<ModelDocument<TSchema>[]>;
741
767
  /** Insert a new document */
742
- insert(data: Record<string, any>, options?: ModelContextOptions): Promise<void>;
768
+ insert(data: Record<string, any>, options?: ModelContextOptions): Promise<ModelDocument<TSchema>>;
743
769
  /** Update one matching document */
744
- update(query: Record<string, any>, options?: ModelContextOptions): Promise<void>;
770
+ update(query: Record<string, any>, options?: ModelContextOptions): Promise<ModelDocument<TSchema>>;
745
771
  /** Delete one matching document */
746
772
  delete(query: Query, options?: ModelContextOptions): Promise<void>;
747
773
  /** Microserver middleware */
@@ -750,10 +776,6 @@ export declare class Model<TSchema extends ModelSchema> {
750
776
  export declare interface MicroCollectionOptions<T extends ModelSchema = any> {
751
777
  /** Collection name */
752
778
  name?: string;
753
- /** Collection persistent store */
754
- store?: FileStore;
755
- /** Custom data loader */
756
- load?: (col: MicroCollection) => Promise<object>;
757
779
  /** Custom data saver */
758
780
  save?: (id: string, doc: ModelDocument<T> | undefined, col: MicroCollection) => Promise<ModelDocument<T>>;
759
781
  /** Preloaded data object */
@@ -773,8 +795,8 @@ export declare interface FindOptions {
773
795
  query?: Query;
774
796
  /** is upsert */
775
797
  upsert?: boolean;
776
- /** is new */
777
- new?: boolean;
798
+ /** is upsert */
799
+ delete?: boolean;
778
800
  /** update object */
779
801
  update?: Query;
780
802
  /** maximum number of hits */
@@ -782,7 +804,7 @@ export declare interface FindOptions {
782
804
  }
783
805
  /** Collection factory */
784
806
  export declare class MicroCollectionStore {
785
- constructor(dataPath?: string);
807
+ constructor(dataPath?: string, storeTimeDelay?: number);
786
808
  /** Get collection */
787
809
  collection(name: string): Promise<MicroCollection>;
788
810
  }
@@ -802,20 +824,20 @@ export declare class MicroCollection<TSchema extends ModelSchema = any> {
802
824
  /** Find all matching documents */
803
825
  find(query: Query): Cursor<TSchema>;
804
826
  /** Find and modify one matching document */
805
- findAndModify(options: FindOptions): Promise<number>;
827
+ findAndModify(options: FindOptions): Promise<ModelDocument<TSchema> | undefined>;
806
828
  /** Insert one document */
807
829
  insertOne(doc: ModelDocument<TSchema>): Promise<ModelDocument<TSchema>>;
808
830
  /** Insert multiple documents */
809
831
  insertMany(docs: ModelDocument<TSchema>[]): Promise<ModelDocument<TSchema>[]>;
810
832
  /** Delete one matching document */
811
- deleteOne(query: Query): Promise<void>;
833
+ deleteOne(query: Query): Promise<number>;
812
834
  /** Delete all matching documents */
813
835
  deleteMany(query: Query): Promise<number>;
814
- updateOne(query: Query, doc: ModelDocument<TSchema>, options?: FindOptions): Promise<{
836
+ updateOne(query: Query, update: Record<string, any>, options?: FindOptions): Promise<{
815
837
  upsertedId: any;
816
838
  modifiedCount: number;
817
839
  }>;
818
- updateMany(query: Query, update: any, options?: FindOptions): Promise<{
840
+ updateMany(query: Query, update: Record<string, any>, options?: FindOptions): Promise<{
819
841
  upsertedId: any;
820
842
  modifiedCount: number;
821
843
  }>;
package/microserver.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MicroServer
3
- * @version 2.3.0
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 Waiter {
927
+ class WaiterJob {
926
928
  constructor() {
927
- this._waiters = {};
928
- this._id = 0;
929
+ this._waiters = [];
929
930
  this._busy = 0;
930
931
  }
931
- get busy() {
932
- return this._busy > 0;
933
- }
934
- startJob() {
932
+ start() {
935
933
  this._busy++;
936
934
  }
937
- endJob(id) {
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
- this.resolve(id || 'ready');
942
+ return;
943
+ return new Promise(resolve => this._waiters.push(resolve));
941
944
  }
942
- get nextId() {
943
- return (++this._id).toString();
945
+ }
946
+ class Waiter {
947
+ constructor() {
948
+ this._id = 0;
949
+ this._waiters = {};
944
950
  }
945
- async wait(id) {
946
- return new Promise(resolve => (this._waiters[id] = this._waiters[id] || []).push(resolve));
951
+ isBusy(id) {
952
+ const waiter = this._waiters[id || 'ready'];
953
+ return !!waiter?._busy;
947
954
  }
948
- resolve(id) {
949
- const resolvers = this._waiters[id];
950
- if (resolvers)
951
- for (const resolve of resolvers)
952
- resolve(undefined);
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
- class EmitterWaiter extends Waiter {
957
- constructor(emitter) {
958
- super();
959
- this._emitter = emitter;
961
+ endJob(id) {
962
+ const waiter = this._waiters[id || 'ready'];
963
+ if (waiter)
964
+ waiter.end();
965
+ }
966
+ get nextId() {
967
+ return (++this._id).toString();
960
968
  }
961
- resolve(id) {
962
- if (id === 'ready')
963
- this._emitter.emit(id);
964
- super.resolve(id);
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._waiter.resolve(added);
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 EmitterWaiter(this);
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._init(() => {
1411
- this.listen({
1412
- tls: config.tls,
1413
- listen: config.listen || 8080
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.busy;
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("ready");
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('ready');
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
- let token = options?.token || defaultToken;
2011
- if (!token || token.length !== 32)
2012
- token = defaultToken;
2013
- if (token.length !== 32)
2014
- token = crypto.createHash('sha256').update(token).digest();
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, options?.salt);
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 (this.options.token === defaultToken)
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 || 1000;
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.mtime.getTime(),
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 schema(schema) {
2578
- return schema;
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 && this.collections)
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 })
@@ -2916,6 +2938,9 @@ export class Model {
2916
2938
  }
2917
2939
  }
2918
2940
  const res = await this.collection.findAndModify({ query: this.getFilter(query, { required: true, validate: false, default: false }), update: query, upsert: options?.insert });
2941
+ if (!res)
2942
+ throw new NotFound('Document not found');
2943
+ return res;
2919
2944
  }
2920
2945
  /** Delete one matching document */
2921
2946
  async delete(query, options) {
@@ -2960,13 +2985,16 @@ export class Model {
2960
2985
  }
2961
2986
  }
2962
2987
  }
2988
+ Model.collections = new ModelCollectionsInternal();
2963
2989
  Model.models = {};
2964
2990
  /** Collection factory */
2965
2991
  export class MicroCollectionStore {
2966
- constructor(dataPath) {
2992
+ constructor(dataPath, storeTimeDelay) {
2967
2993
  this._collections = new Map();
2968
2994
  if (dataPath)
2969
- 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;
2970
2998
  }
2971
2999
  /** Get collection */
2972
3000
  async collection(name) {
@@ -3035,54 +3063,20 @@ export class MicroCollection {
3035
3063
  }
3036
3064
  /** Find and modify one matching document */
3037
3065
  async findAndModify(options) {
3038
- if (!options.query)
3039
- return 0;
3040
- const id = ((options.upsert || options.new) && !options.query._id) ? newObjectId() : options.query._id;
3041
- if (!id) {
3042
- let count = 0;
3043
- let promise = Promise.resolve();
3044
- this.find(options.query).forEach((doc) => {
3045
- if (this.queryDocument(options.query, doc)) {
3046
- Object.assign(doc, options.update);
3047
- if (this._save) {
3048
- promise = promise.then(async () => {
3049
- if (!doc._id)
3050
- throw new Error('Internal error: missing _id in document');
3051
- this.data[doc._id] = await this._save?.(doc._id, doc, this) || this.data[doc._id];
3052
- });
3053
- }
3054
- count++;
3055
- if (options.limit && count >= options.limit)
3056
- return false;
3057
- }
3058
- });
3059
- await promise;
3060
- return count;
3061
- }
3062
- let doc = this.queryDocument(options.query, this.data[id]);
3063
- if (!doc) {
3064
- if (!options.upsert && !options.new)
3065
- throw new InvalidData(`Document not found`);
3066
- doc = { _id: id };
3067
- this.data[id] = doc;
3068
- }
3069
- else {
3070
- if (options.new)
3071
- throw new InvalidData(`Document dupplicate`);
3072
- }
3073
- if (options.update) {
3074
- for (const n in options.update) {
3066
+ const res = await this.findOne(options.query || {});
3067
+ if (res?._id) {
3068
+ await this.updateOne({ _id: res._id }, options.update || {}, options);
3069
+ return this.data[res._id];
3070
+ }
3071
+ if (options.upsert) {
3072
+ const doc = { ...options.query };
3073
+ for (const n in options.update)
3075
3074
  if (!n.startsWith('$'))
3076
3075
  doc[n] = options.update[n];
3077
- }
3078
- if (options.update.$unset) {
3079
- for (const n in options.update.$unset)
3080
- delete doc[n];
3081
- }
3076
+ if (options.update?.$set)
3077
+ Object.assign(doc, options.update.$set);
3078
+ return this.insertOne(doc);
3082
3079
  }
3083
- if (this._save)
3084
- this.data[id] = await this._save(id, doc, this) || doc;
3085
- return 1;
3086
3080
  }
3087
3081
  /** Insert one document */
3088
3082
  async insertOne(doc) {
@@ -3108,49 +3102,72 @@ export class MicroCollection {
3108
3102
  }
3109
3103
  /** Delete one matching document */
3110
3104
  async deleteOne(query) {
3111
- const id = query._id;
3112
- if (!id)
3113
- return;
3114
- delete this.data[id];
3105
+ return (await this.updateMany(query, {}, { delete: true, limit: 1 })).modifiedCount;
3115
3106
  }
3116
3107
  /** Delete all matching documents */
3117
3108
  async deleteMany(query) {
3118
- let count = 0;
3109
+ return (await this.updateMany(query, {}, { delete: true })).modifiedCount;
3110
+ }
3111
+ async updateOne(query, update, options) {
3112
+ const res = await this.updateMany(query, update, { ...options, limit: 1 });
3113
+ return res;
3114
+ }
3115
+ async updateMany(query, update, options) {
3116
+ let res = { upsertedId: undefined, modifiedCount: 0 };
3117
+ if (!query)
3118
+ return res;
3119
3119
  let promise = Promise.resolve();
3120
3120
  this.find(query).forEach((doc) => {
3121
3121
  if (this.queryDocument(query, doc)) {
3122
- count++;
3123
- if (!doc._id)
3124
- return;
3125
- delete this.data[doc._id];
3126
- if (this._save)
3127
- promise = promise.then(async () => { doc._id && this._save?.(doc._id, undefined, this); });
3122
+ if (options?.delete) {
3123
+ if (!doc._id)
3124
+ return;
3125
+ res.modifiedCount++;
3126
+ if (this._save)
3127
+ promise = promise.then(async () => { doc._id && this._save?.(doc._id, undefined, this); });
3128
+ delete this.data[doc._id];
3129
+ }
3130
+ else {
3131
+ Object.assign(doc, update);
3132
+ res.modifiedCount++;
3133
+ if (this._save) {
3134
+ promise = promise.then(async () => {
3135
+ if (!doc._id)
3136
+ throw new Error('Internal error: missing _id in document');
3137
+ this.data[doc._id] = await this._save?.(doc._id, doc, this) || this.data[doc._id];
3138
+ });
3139
+ }
3140
+ }
3141
+ if (options?.limit && res.modifiedCount >= options?.limit)
3142
+ return false;
3128
3143
  }
3129
3144
  });
3130
3145
  await promise;
3131
- return count;
3132
- }
3133
- async updateOne(query, doc, options) {
3134
- const count = await this.findAndModify({
3135
- query,
3136
- update: doc,
3137
- upsert: options?.upsert,
3138
- limit: 1
3139
- });
3140
- return {
3141
- upsertedId: undefined,
3142
- modifiedCount: count
3143
- };
3144
- }
3145
- async updateMany(query, update, options) {
3146
- const count = await this.findAndModify({
3147
- ...options,
3148
- query,
3149
- update
3150
- });
3151
- return {
3152
- upsertedId: undefined,
3153
- modifiedCount: count
3154
- };
3146
+ if (res.modifiedCount || !options?.upsert || options?.delete)
3147
+ return res;
3148
+ if (!query._id)
3149
+ query._id = res.upsertedId = newObjectId();
3150
+ let doc = this.queryDocument(options.query, this.data[query._id]);
3151
+ if (doc)
3152
+ throw new InvalidData(`Document dupplicate`);
3153
+ doc = { _id: query._id, ...query };
3154
+ this.data[query._id] = doc;
3155
+ if (update) {
3156
+ for (const n in update) {
3157
+ if (!n.startsWith('$'))
3158
+ doc[n] = update[n];
3159
+ }
3160
+ if (update.$set) {
3161
+ for (const n in update.$set)
3162
+ doc[n] = update.$set[n];
3163
+ }
3164
+ if (update.$unset) {
3165
+ for (const n in update.$unset)
3166
+ delete doc[n];
3167
+ }
3168
+ }
3169
+ if (this._save)
3170
+ this.data[query._id] = await this._save(query._id, doc, this) || doc;
3171
+ return res;
3155
3172
  }
3156
3173
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radatek/microserver",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "HTTP MicroServer",
5
5
  "author": "Darius Kisonas",
6
6
  "license": "MIT",