@abtnode/core 1.6.6 → 1.6.10

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.
@@ -21,15 +21,13 @@ const {
21
21
  NAME_FOR_WELLKNOWN_SITE,
22
22
  DEFAULT_HTTP_PORT,
23
23
  DEFAULT_HTTPS_PORT,
24
- DAY_IN_MS,
25
24
  NODE_MODES,
26
25
  ROUTING_RULE_TYPES,
27
26
  CERTIFICATE_EXPIRES_OFFSET,
28
- CERTIFICATE_EXPIRES_WARNING_OFFSET,
29
- DEFAULT_DAEMON_PORT,
30
27
  DEFAULT_SERVICE_PATH,
31
28
  SLOT_FOR_IP_DNS_SITE,
32
29
  BLOCKLET_SITE_GROUP_SUFFIX,
30
+ WELLKNOWN_ACME_CHALLENGE_PREFIX,
33
31
  } = require('@abtnode/constant');
34
32
  const {
35
33
  BLOCKLET_DYNAMIC_PATH_PREFIX,
@@ -50,7 +48,7 @@ const {
50
48
  getWellknownSitePort,
51
49
  } = require('../util');
52
50
  const { getServicesFromBlockletInterface } = require('../util/service');
53
- const getIpDnsDomainForBlocklet = require('../util/get-ip-dns-domain-for-blocklet');
51
+ const { getIpDnsDomainForBlocklet, getDidDomainForBlocklet } = require('../util/get-domain-for-blocklet');
54
52
  const { getFromCache: getAccessibleExternalNodeIp } = require('../util/get-accessible-external-node-ip');
55
53
 
56
54
  const Router = require('./index');
@@ -403,11 +401,10 @@ const decompressCertificates = async (source, dest) => {
403
401
  return dest;
404
402
  };
405
403
 
406
- module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager }) {
404
+ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager, certManager }) {
407
405
  const nodeState = states.node;
408
406
  const blockletState = states.blocklet;
409
407
  const siteState = states.site;
410
- const httpsCertState = states.certificate;
411
408
  const notification = states.notification;
412
409
 
413
410
  // site level duplication detection
@@ -468,58 +465,105 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
468
465
  const certificate = fs.readFileSync(certificateFilePath).toString();
469
466
  const privateKey = fs.readFileSync(privateKeyFilePath).toString();
470
467
 
471
- await routerManager.updateNginxHttpsCert({ domain: dashboardDomain, privateKey, certificate, isProtected: true });
468
+ await certManager.upsertByDomain({
469
+ domain: dashboardDomain,
470
+ privateKey,
471
+ certificate,
472
+ isProtected: true,
473
+ });
472
474
  logger.info('dashboard certificate updated');
473
475
  } catch (error) {
474
- logger.error('Update dashboard certificate failed', { error });
476
+ logger.error('update dashboard certificate failed', { error });
477
+ throw error;
475
478
  } finally {
476
479
  fs.removeSync(destFolder);
477
480
  }
478
481
  };
479
482
 
483
+ const ensureDashboardCertificate = async () => {
484
+ const info = await nodeState.read();
485
+ const downloadUrl = get(info, 'routing.dashboardCertDownloadAddress', '');
486
+ const dashboardDomain = get(info, 'routing.dashboardDomain', '');
487
+ if (!dashboardDomain || !downloadUrl) {
488
+ throw new Error('dashboardCertDownloadAddress and dashboardDomain are not found in the routing configs');
489
+ }
490
+
491
+ const cert = await certManager.getByDomain(dashboardDomain);
492
+ if (cert) {
493
+ return { status: 'existed' };
494
+ }
495
+
496
+ logger.debug('downloading certificate', { url: downloadUrl, dashboardDomain });
497
+ await updateDashboardCertificate({ checkExpire: false });
498
+ logger.debug('downloading certificate', { url: downloadUrl, dashboardDomain });
499
+ return { status: 'downloaded' };
500
+ };
501
+
480
502
  const addWellknownSite = async (sites, context) => {
481
503
  const site = (sites || []).find((x) => x.name === NAME_FOR_WELLKNOWN_SITE);
482
504
 
483
505
  try {
484
- const pathPrefix = joinUrl(WELLKNOWN_PATH_PREFIX, '/did.json');
506
+ const info = await nodeState.read();
507
+ const proxyTarget = {
508
+ port: info.port,
509
+ type: ROUTING_RULE_TYPES.GENERAL_PROXY,
510
+ interfaceName: BLOCKLET_INTERFACE_WELLKNOWN,
511
+ };
512
+
485
513
  const didResolverWellknownRule = {
486
- from: { pathPrefix },
487
- to: {
488
- port: DEFAULT_DAEMON_PORT,
489
- type: ROUTING_RULE_TYPES.GENERAL_PROXY,
490
- interfaceName: BLOCKLET_INTERFACE_WELLKNOWN,
491
- },
514
+ from: { pathPrefix: joinUrl(WELLKNOWN_PATH_PREFIX, '/did.json') },
515
+ to: proxyTarget,
516
+ };
517
+
518
+ const acmeChallengeWellknownRule = {
519
+ from: { pathPrefix: WELLKNOWN_ACME_CHALLENGE_PREFIX },
520
+ to: proxyTarget,
492
521
  };
493
522
 
494
523
  if (site) {
495
- if (site.rules.find((r) => r.from.pathPrefix === normalizePathPrefix(pathPrefix))) {
496
- return false;
524
+ let changed = false;
525
+ const exists = (prefix) => !!site.rules.find((r) => r.from.pathPrefix === normalizePathPrefix(prefix));
526
+
527
+ if (!exists(didResolverWellknownRule.from.pathPrefix)) {
528
+ await routerManager.addRoutingRule(
529
+ {
530
+ id: site.id,
531
+ rule: didResolverWellknownRule,
532
+ },
533
+ context
534
+ );
535
+ changed = true;
497
536
  }
498
537
 
499
- await routerManager.addRoutingRule(
500
- {
501
- id: site.id,
502
- rule: didResolverWellknownRule,
503
- },
504
- context
505
- );
506
- } else {
507
- await routerManager.addRoutingSite(
508
- {
509
- site: {
510
- domain: DOMAIN_FOR_INTERNAL_SITE,
511
- port: await getWellknownSitePort(),
512
- name: NAME_FOR_WELLKNOWN_SITE,
513
- rules: [didResolverWellknownRule],
514
- isProtected: true,
538
+ if (!exists(acmeChallengeWellknownRule.from.pathPrefix)) {
539
+ await routerManager.addRoutingRule(
540
+ {
541
+ id: site.id,
542
+ rule: acmeChallengeWellknownRule,
515
543
  },
516
- skipCheckDynamicBlacklist: true,
517
- skipValidation: true,
518
- },
519
- context
520
- );
544
+ context
545
+ );
546
+ changed = true;
547
+ }
548
+
549
+ return changed;
521
550
  }
522
551
 
552
+ await routerManager.addRoutingSite(
553
+ {
554
+ site: {
555
+ domain: DOMAIN_FOR_INTERNAL_SITE,
556
+ port: await getWellknownSitePort(),
557
+ name: NAME_FOR_WELLKNOWN_SITE,
558
+ rules: [didResolverWellknownRule, acmeChallengeWellknownRule],
559
+ isProtected: true,
560
+ },
561
+ skipCheckDynamicBlacklist: true,
562
+ skipValidation: true,
563
+ },
564
+ context
565
+ );
566
+
523
567
  return true;
524
568
  } catch (err) {
525
569
  console.error('add well-known site failed:', err);
@@ -533,7 +577,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
533
577
  *
534
578
  * @returns {boolean} if routing changed
535
579
  */
536
- const ensureDashboardRouting = async (context = {}, output) => {
580
+ const ensureDashboardRouting = async (context = {}) => {
537
581
  const info = await nodeState.read();
538
582
 
539
583
  const provider = getProviderFromNodeInfo(info);
@@ -579,11 +623,16 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
579
623
  });
580
624
 
581
625
  const dashboardDomain = get(info, 'routing.dashboardDomain', '');
582
- if (dashboardDomain && !isExistsInAlias(dashboardDomain)) {
626
+ const didDomain = `${info.did.toLowerCase()}.${info.didDomain}`;
627
+ const dashboardAliasDomains = [dashboardDomain, didDomain]
628
+ .filter((item) => item && !isExistsInAlias(item))
629
+ .map((item) => ({ value: item, isProtected: true }));
630
+
631
+ if (dashboardAliasDomains.length > 0) {
583
632
  try {
584
633
  const result = await siteState.update(
585
634
  { _id: dashboardSite.id },
586
- { $push: { domainAliases: { value: dashboardDomain, isProtected: true } } }
635
+ { $push: { domainAliases: { $each: dashboardAliasDomains } } }
587
636
  );
588
637
 
589
638
  updatedResult.push(result);
@@ -618,18 +667,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
618
667
  updatedResult.push(wellknownRes);
619
668
  }
620
669
 
621
- // Download dashboard certificates if not exists
622
- const certDownloadAddress = get(info, 'routing.dashboardCertDownloadAddress', '');
623
- if (dashboardDomain && certDownloadAddress) {
624
- const cert = await routerManager.findCertificateByDomain(dashboardDomain);
625
- if (!cert) {
626
- await updateDashboardCertificate({ checkExpire: false });
627
- if (typeof output === 'function') {
628
- output('Dashboard HTTPS certificate was downloaded successfully!');
629
- }
630
- }
631
- }
632
-
633
670
  if (updatedResult.length) {
634
671
  const hash = await takeRoutingSnapshot({ message: 'ensure dashboard routing rules', dryRun: false }, context);
635
672
  logger.info('take routing snapshot on ensure dashboard routing rules', { updatedResult, hash });
@@ -658,7 +695,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
658
695
  };
659
696
 
660
697
  const domainGroup = `${blocklet.meta.did}${BLOCKLET_SITE_GROUP_SUFFIX}`;
661
- const domain = getIpDnsDomainForBlocklet(blocklet, webInterface);
698
+
662
699
  const pathPrefix = getPrefix(webInterface.prefix);
663
700
  const rule = {
664
701
  from: { pathPrefix },
@@ -673,11 +710,24 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
673
710
 
674
711
  const existSite = await states.site.findOne({ domain: domainGroup });
675
712
  if (!existSite) {
713
+ const ipEchoDnsDomain = getIpDnsDomainForBlocklet(blocklet, webInterface, nodeInfo.did);
714
+ const appIdEnv = blocklet.environments.find((e) => e.key === 'BLOCKLET_APP_ID');
715
+ const domainAliases = [{ value: ipEchoDnsDomain, isProtected: true }];
716
+
717
+ const didDomain = getDidDomainForBlocklet({
718
+ appId: appIdEnv.value,
719
+ didDomain: nodeInfo.didDomain,
720
+ });
721
+
722
+ if (blocklet.mode !== 'development') {
723
+ domainAliases.push({ value: didDomain, isProtected: true });
724
+ }
725
+
676
726
  await routerManager.addRoutingSite(
677
727
  {
678
728
  site: {
679
729
  domain: domainGroup,
680
- domainAliases: [{ value: domain, isProtected: true }],
730
+ domainAliases,
681
731
  isProtected: true,
682
732
  rules: [rule],
683
733
  },
@@ -685,7 +735,14 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
685
735
  },
686
736
  context
687
737
  );
688
- logger.info('add routing site', { site: domain });
738
+ logger.info('add routing site', { site: domainGroup });
739
+ if (
740
+ process.env.NODE_ENV !== 'development' &&
741
+ process.env.NODE_ENV !== 'test' &&
742
+ blocklet.mode !== 'development'
743
+ ) {
744
+ await certManager.issue({ domain: didDomain });
745
+ }
689
746
 
690
747
  return true;
691
748
  }
@@ -700,13 +757,13 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
700
757
  },
701
758
  skipProtectedRuleChecking: true,
702
759
  });
703
- logger.info('update routing rule for site', { site: domain });
760
+ logger.info('update routing rule for site', { site: domainGroup });
704
761
  } else {
705
762
  await routerManager.addRoutingRule({
706
763
  id: existSite.id,
707
764
  rule,
708
765
  });
709
- logger.info('add routing rule for site', { site: domain });
766
+ logger.info('add routing rule for site', { site: domainGroup });
710
767
  }
711
768
 
712
769
  return true;
@@ -1068,9 +1125,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1068
1125
  sites = await ensureAuthService(sites, blockletManager);
1069
1126
  sites = await ensureServiceRule(sites);
1070
1127
 
1071
- const certificates = httpsEnabled
1072
- ? await httpsCertState.find({ type: providerName }, { domain: 1, certificate: 1, privateKey: 1 })
1073
- : [];
1128
+ const certificates = httpsEnabled ? await certManager.getAllNormal() : [];
1074
1129
 
1075
1130
  return {
1076
1131
  sites,
@@ -1093,9 +1148,9 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1093
1148
  },
1094
1149
  });
1095
1150
 
1096
- routerManager.on('router.certificate.add', () => routers[providerName].restart());
1097
- routerManager.on('router.certificate.updated', () => routers[providerName].restart());
1098
- routerManager.on('router.certificate.remove', () => routers[providerName].restart());
1151
+ certManager.on('cert.added', () => routers[providerName].restart());
1152
+ certManager.on('cert.removed', () => routers[providerName].restart());
1153
+ certManager.on('cert.issued', () => routers[providerName].restart());
1099
1154
 
1100
1155
  await routers[providerName].start();
1101
1156
  }
@@ -1257,18 +1312,21 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1257
1312
  };
1258
1313
 
1259
1314
  const getCertificates = async () => {
1260
- const certificates = await httpsCertState.find();
1315
+ const certificates = await certManager.getAll();
1261
1316
  const sites = await getSitesFromSnapshot();
1317
+
1318
+ const isMatch = (cert, domain) =>
1319
+ domain !== DOMAIN_FOR_DEFAULT_SITE && domain && routerManager.isCertMatchedDomain(cert, domain);
1320
+
1262
1321
  return certificates.map((cert) => {
1263
1322
  cert.matchedSites = [];
1264
1323
  sites.forEach((site) => {
1265
- if (
1266
- site.domain !== DOMAIN_FOR_DEFAULT_SITE &&
1267
- site.domain &&
1268
- routerManager.isCertMatchedDomain(cert, site.domain)
1269
- ) {
1270
- cert.matchedSites.push({ id: site.id, domain: site.domain });
1271
- }
1324
+ const domains = [site.domain, ...(site.domainAliases || []).map((x) => x.value)];
1325
+ domains.forEach((domain) => {
1326
+ if (isMatch(cert, domain)) {
1327
+ cert.matchedSites.push({ id: site.id, domain });
1328
+ }
1329
+ });
1272
1330
  });
1273
1331
 
1274
1332
  return cert;
@@ -1281,42 +1339,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1281
1339
  return { domain, isHttps: !!matchedCert, matchedCert };
1282
1340
  };
1283
1341
 
1284
- const checkCertificatesExpiration = async () => {
1285
- const now = Date.now();
1286
-
1287
- const certificates = await getCertificates();
1288
- for (let i = 0; i < certificates.length; i++) {
1289
- const cert = certificates[i];
1290
- const alreadyExpired = now >= cert.validTo;
1291
- const aboutToExpire = cert.validTo - now > 0 && cert.validTo - now < CERTIFICATE_EXPIRES_WARNING_OFFSET;
1292
-
1293
- if (alreadyExpired) {
1294
- logger.info('send certificate expire notification', { domain: cert.domain });
1295
- notification.create({
1296
- title: 'SSL Certificate Expired',
1297
- description: `Your SSL certificate for domain ${cert.domain} has expired, please update it in Blocklet Server`,
1298
- severity: 'error',
1299
- entityType: 'certificate',
1300
- entityId: cert._id, // eslint-disable-line no-underscore-dangle
1301
- });
1302
- } else if (aboutToExpire) {
1303
- logger.info('send certificate about-expire notification', { domain: cert.domain });
1304
- const expireInDays = Math.ceil((cert.validTo - now) / DAY_IN_MS);
1305
- notification.create({
1306
- title: 'SSL Certificate Expire Warning',
1307
- description: `Your SSL certificate for domain ${
1308
- cert.domain
1309
- } will expire in ${expireInDays} days (on ${new Date(
1310
- cert.validTo
1311
- ).toLocaleString()}), please remember to update it in Blocklet Server`,
1312
- severity: 'warning',
1313
- entityType: 'certificate',
1314
- entityId: cert._id, // eslint-disable-line no-underscore-dangle
1315
- });
1316
- }
1317
- }
1318
- };
1319
-
1320
1342
  return {
1321
1343
  ensureDashboardRouting,
1322
1344
  ensureBlockletRouting,
@@ -1328,9 +1350,10 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1328
1350
  takeRoutingSnapshot,
1329
1351
  getRoutingSites,
1330
1352
  getSnapshotSites,
1331
- getCertificates,
1332
1353
  getSitesFromSnapshot,
1354
+ getCertificates,
1333
1355
  checkDomain,
1356
+ ensureDashboardCertificate,
1334
1357
 
1335
1358
  getRoutingCrons: () => [
1336
1359
  {
@@ -1339,12 +1362,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1339
1362
  fn: () => updateDashboardCertificate({ checkExpire: true }),
1340
1363
  options: { runOnInit: true },
1341
1364
  },
1342
- {
1343
- name: 'check-certificate-expiration',
1344
- time: '0 0 9 * * *', // check on 09:00 every day
1345
- fn: checkCertificatesExpiration,
1346
- options: { runOnInit: false },
1347
- },
1348
1365
  {
1349
1366
  name: 'rotate-log-files',
1350
1367
  time: '5 0 0 * * *', // rotate at 05:00 every day
@@ -70,8 +70,9 @@ const normalizeRedirectUrl = (url) => {
70
70
  };
71
71
 
72
72
  class RouterManager extends EventEmitter {
73
- constructor() {
73
+ constructor({ certManager }) {
74
74
  super();
75
+ this.certManager = certManager;
75
76
 
76
77
  // HACK: do not emit any events from CLI
77
78
  if (isCLI()) {
@@ -366,7 +367,7 @@ class RouterManager extends EventEmitter {
366
367
  domain,
367
368
  });
368
369
  logger.info('add certificate result', { domain: newCert.domain });
369
- this.emit('router.certificate.add', { type: 'nginx', data: newCert });
370
+ this.emit('cert.added', { type: 'nginx', data: newCert });
370
371
  }
371
372
 
372
373
  // eslint-disable-next-line no-unused-vars
@@ -379,7 +380,7 @@ class RouterManager extends EventEmitter {
379
380
  const removeResult = await states.certificate.remove({ _id: id });
380
381
 
381
382
  logger.info('delete certificate', { removeResult, domain: tmpCert.domain });
382
- this.emit('router.certificate.remove', { type: 'nginx', data: { domain: tmpCert.domain } });
383
+ this.emit('cert.removed', { type: 'nginx', data: { domain: tmpCert.domain } });
383
384
  return {};
384
385
  }
385
386
 
@@ -389,7 +390,7 @@ class RouterManager extends EventEmitter {
389
390
  this.fixCertificate(entity);
390
391
  this.validateCertificate(entity, entity.domain);
391
392
  const dbEntity = await states.certificate.upsert(entity);
392
- this.emit('router.certificate.updated', { type: 'nginx', data: dbEntity });
393
+ this.emit('cert.issued', { type: 'nginx', data: dbEntity });
393
394
  }
394
395
 
395
396
  findCertificateByDomain(domain) {
@@ -451,16 +452,16 @@ class RouterManager extends EventEmitter {
451
452
  }
452
453
 
453
454
  async getMatchedCert(domain) {
454
- const certs = await states.certificate.find();
455
+ const certs = await this.certManager.getAll();
455
456
  const matchedCert = certs.find((cert) => this.isCertMatchedDomain(cert, domain));
456
457
 
457
458
  if (matchedCert) {
458
459
  return {
459
460
  id: matchedCert.id,
460
461
  domain: matchedCert.domain,
461
- issuer: matchedCert.issuer,
462
- validFrom: matchedCert.validFrom,
463
- validTo: matchedCert.validTo,
462
+ issuer: matchedCert.meta.issuer,
463
+ validFrom: matchedCert.meta.validFrom,
464
+ validTo: matchedCert.meta.validTo,
464
465
  };
465
466
  }
466
467
 
@@ -1,234 +1,17 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const util = require('util');
4
- const { EventEmitter } = require('events');
5
- const cloneDeep = require('lodash/cloneDeep');
6
- const DataStore =
7
- process.env.NODE_ENV === 'test' ? require('@nedb/core') : require('@nedb/multi')(Number(process.env.NEDB_MULTI_PORT));
1
+ const DB = require('@abtnode/db');
8
2
  const logger = require('@abtnode/logger')('@abtnode/core:states');
9
3
 
10
4
  const { isCLI } = require('../util');
11
5
 
12
- class BaseState extends EventEmitter {
6
+ class BaseState extends DB {
13
7
  constructor(baseDir, options) {
14
- super();
8
+ super(baseDir, options);
15
9
 
16
10
  // HACK: do not emit any events from CLI
17
11
  if (isCLI() && process.env.NODE_ENV !== 'test') {
18
12
  this.emit = (name) => logger.debug('stopped state db event in CLI', name);
19
13
  }
20
-
21
- const dbOptions = options.db || {};
22
- this.filename = path.join(baseDir, options.filename);
23
- this.options = Object.freeze(cloneDeep(options));
24
- this.db = new DataStore({
25
- filename: this.filename,
26
- timestampData: true,
27
- ...dbOptions,
28
- });
29
-
30
- logger.info('initialized', { filename: this.filename });
31
-
32
- this.ready = false;
33
- this.readyCallbacks = [];
34
- this.db.loadDatabase((err) => {
35
- if (err) {
36
- logger.error(`failed to load disk database ${this.filename}`, { error: err });
37
- console.error(err);
38
- } else {
39
- this.ready = true;
40
- if (this.readyCallbacks.length) {
41
- this.readyCallbacks.forEach((x) => x());
42
- }
43
- }
44
- });
45
-
46
- this.asyncDB = new Proxy(this.db, {
47
- get(target, property) {
48
- if (typeof target[property] === 'function') {
49
- return util
50
- .promisify((...args) => {
51
- const cb = args[args.length - 1];
52
- const rest = args.slice(0, args.length - 1);
53
-
54
- target[property](...rest, (err, ...result) => {
55
- if (err) {
56
- return cb(err);
57
- }
58
-
59
- if (result.length === 1) {
60
- return cb(null, result[0]);
61
- }
62
-
63
- return cb(null, result);
64
- });
65
- })
66
- .bind(target);
67
- }
68
-
69
- return target[property];
70
- },
71
- });
72
- }
73
-
74
- onReady(cb) {
75
- if (this.ready) {
76
- cb();
77
- } else {
78
- this.readyCallbacks.push(cb);
79
- }
80
- }
81
-
82
- createNotification(payload) {
83
- if (this.notification && typeof this.notification.create === 'function') {
84
- this.notification.create(payload);
85
- }
86
- }
87
-
88
- paginate(conditions, sort, paging) {
89
- const { pageSize: size = 20, page = 1 } = paging || {};
90
- const pageSize = Math.min(100, size);
91
-
92
- return new Promise((resolve, reject) => {
93
- this.db
94
- .find(conditions)
95
- .sort(sort)
96
- .exec((err, docs) => {
97
- if (err) {
98
- return reject(err);
99
- }
100
-
101
- const pageCount = Math.ceil(docs.length / pageSize);
102
- const total = docs.length;
103
- const skip = (page - 1) * pageSize;
104
- const list = docs.slice(skip, skip + pageSize);
105
-
106
- list.forEach((doc) => {
107
- // eslint-disable-next-line no-underscore-dangle
108
- doc.id = doc._id;
109
- });
110
-
111
- return resolve({
112
- list,
113
- paging: {
114
- total,
115
- pageSize,
116
- pageCount,
117
- page,
118
- },
119
- });
120
- });
121
- });
122
- }
123
-
124
- async updateById(id, updates, options = {}) {
125
- const [, doc] = await this.asyncDB.update({ _id: id }, updates, {
126
- multi: false,
127
- upsert: false,
128
- returnUpdatedDocs: true,
129
- ...options,
130
- });
131
-
132
- return doc;
133
- }
134
-
135
- update(...args) {
136
- if (args.length === 0) {
137
- throw new Error('param is required by update method');
138
- }
139
-
140
- if (typeof args[0] === 'string') {
141
- return this.updateById(...args);
142
- }
143
-
144
- return this.asyncDB.update(args[0], args[1], { returnUpdatedDocs: true, ...(args[2] || {}) });
145
- }
146
-
147
- count(conditions = {}) {
148
- return new Promise((resolve, reject) => {
149
- this.db.count(conditions, (err, num) => {
150
- if (err) {
151
- return reject(err);
152
- }
153
-
154
- return resolve(num);
155
- });
156
- });
157
- }
158
-
159
- remove(conditions = {}, options = { multi: false }) {
160
- return new Promise((resolve, reject) => {
161
- this.db.remove(conditions, options, (err, num) => {
162
- if (err) {
163
- return reject(err);
164
- }
165
-
166
- return resolve(num);
167
- });
168
- });
169
- }
170
-
171
- reset() {
172
- fs.unlinkSync(this.filename);
173
- }
174
-
175
- find(...args) {
176
- if (args.length === 0) {
177
- return this.asyncDB.find({});
178
- }
179
-
180
- return this.asyncDB.find(...args);
181
- }
182
-
183
- findOne(...args) {
184
- if (args.length === 0) {
185
- return this.asyncDB.findOne({});
186
- }
187
-
188
- return this.asyncDB.findOne(...args);
189
- }
190
-
191
- insert(...args) {
192
- return this.asyncDB.insert(...args);
193
- }
194
-
195
- compactDatafile() {
196
- this.db.persistence.compactDatafile((err) => {
197
- if (err) {
198
- console.error(`failed to compact datafile: ${this.filename}`, err);
199
- }
200
- });
201
14
  }
202
15
  }
203
16
 
204
- /**
205
- * Rename _id field name to id, this method has side effects
206
- * @param {object} entities
207
- */
208
- const renameIdFiledName = (entities, from = '_id', to = 'id') => {
209
- /* eslint-disable no-underscore-dangle, no-param-reassign */
210
-
211
- if (!entities) {
212
- return entities;
213
- }
214
-
215
- const mapEntity = (entity) => {
216
- if (entity[from]) {
217
- entity[to] = entity[from];
218
- delete entity[from];
219
- }
220
- };
221
-
222
- if (!Array.isArray(entities)) {
223
- mapEntity(entities);
224
- return entities;
225
- }
226
-
227
- entities.forEach(mapEntity);
228
-
229
- return entities;
230
- };
231
-
232
- BaseState.renameIdFiledName = renameIdFiledName;
233
-
234
17
  module.exports = BaseState;