@abtnode/core 1.6.14 → 1.6.18

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,67 +1,25 @@
1
1
  /* eslint-disable no-await-in-loop */
2
- const childProcess = require('child_process');
3
2
  const fs = require('fs-extra');
4
3
  const path = require('path');
5
4
  const semver = require('semver');
5
+ const runScript = require('@abtnode/util/lib/run-script');
6
6
 
7
7
  const { getMigrationScripts: getScripts } = require('../migrations');
8
8
  const { getSafeEnv } = require('../util');
9
9
  const { name } = require('../../package.json');
10
10
  const logger = require('@abtnode/logger')(`${name}:blocklet:migration`); // eslint-disable-line
11
11
 
12
- const _runScript = ({ appDir, env, migrationScript, progress = false }) => {
13
- const safeEnv = getSafeEnv(env);
14
-
15
- const child = childProcess.exec(`node ${migrationScript}`, {
16
- cwd: appDir,
17
- env: safeEnv,
18
- stdio: 'inherit',
19
- });
20
- let hasUnhandledRejection = false;
21
-
22
- if (progress) {
23
- child.stdout.pipe(process.stdout);
24
- child.stderr.pipe(process.stderr);
25
- }
26
-
27
- return new Promise((resolve, reject) => {
28
- const errorMessages = [];
29
-
30
- child.stderr.on('data', (err) => {
31
- // Check if has unhandledRejection in childProcess
32
- // https://stackoverflow.com/questions/32784649/gracefully-handle-errors-in-child-processes-in-nodejs
33
- if (err.includes('UnhandledPromiseRejectionWarning')) {
34
- hasUnhandledRejection = true;
35
- }
36
- errorMessages.push(err);
37
- });
38
-
39
- child.on('exit', (code) => {
40
- if (errorMessages.length > 0) {
41
- if (code !== 0 || hasUnhandledRejection) {
42
- return reject(new Error(errorMessages.join('\r\n')));
43
- }
44
-
45
- if (!progress) {
46
- errorMessages.forEach((message) => process.stderr.write(message));
47
- }
48
- }
49
-
50
- return resolve();
51
- });
52
- });
53
- };
54
-
55
12
  async function runScripts({
13
+ blocklet,
14
+ appDir,
15
+ env,
16
+ oldVersion,
56
17
  dbDir,
57
18
  backupDir,
58
19
  scriptsDir,
59
20
  printInfo,
60
21
  printSuccess,
61
22
  printError,
62
- appDir,
63
- env,
64
- oldVersion,
65
23
  }) {
66
24
  let scripts = [];
67
25
  try {
@@ -90,7 +48,11 @@ async function runScripts({
90
48
  const { script: scriptPath } = pendingScripts[i];
91
49
  try {
92
50
  printInfo(`Migration script started: ${scriptPath}`);
93
- await _runScript({ appDir, env, migrationScript: path.join(scriptsDir, scriptPath) });
51
+ await runScript(`node ${path.join(scriptsDir, scriptPath)}`, [blocklet.env.appId, 'migration'].join(':'), {
52
+ cwd: appDir,
53
+ env: getSafeEnv(env),
54
+ silent: false,
55
+ });
94
56
  printInfo(`Migration script executed: ${scriptPath}`);
95
57
  } catch (migrationErr) {
96
58
  printError(`Failed to execute migration script: ${scriptPath}, error: ${migrationErr.message}`);
@@ -123,6 +85,7 @@ async function doRestore({ dbDir, backupDir, printInfo, printSuccess }) {
123
85
  }
124
86
 
125
87
  module.exports = async ({
88
+ blocklet,
126
89
  appDir,
127
90
  env,
128
91
  oldVersion,
@@ -142,15 +105,16 @@ module.exports = async ({
142
105
  fs.ensureDirSync(backupDir);
143
106
 
144
107
  await runScripts({
108
+ blocklet,
109
+ appDir,
110
+ env,
111
+ oldVersion,
112
+ newVersion,
145
113
  dbDir,
146
114
  backupDir,
147
115
  scriptsDir,
148
116
  printError,
149
117
  printInfo,
150
118
  printSuccess,
151
- appDir,
152
- env,
153
- oldVersion,
154
- newVersion,
155
119
  });
156
120
  };
package/lib/event.js CHANGED
@@ -182,7 +182,7 @@ module.exports = ({
182
182
  .catch((error) => logger.error('refresh blocklets failed on initialize the registry', { error }));
183
183
 
184
184
  // We need update router on some fields change
185
- const fields = ['enableWelcomePage', 'webWalletUrl'];
185
+ const fields = ['enableWelcomePage', 'webWalletUrl', 'registerUrl'];
186
186
  const shouldUpdateRouter = fields.some((x) => nodeInfo[x] !== oldInfo[x]);
187
187
  if (shouldUpdateRouter) {
188
188
  handleRouting(nodeInfo).catch((err) => {
package/lib/index.js CHANGED
@@ -50,6 +50,7 @@ const { toStatus, fromStatus, ensureDataDirs, getQueueConcurrencyByMem } = requi
50
50
  * version
51
51
  * runtimeConfig: {}
52
52
  * autoUpgrade
53
+ * registerUrl
53
54
  * webWalletUrl
54
55
  */
55
56
  function ABTNode(options) {
@@ -196,16 +197,17 @@ function ABTNode(options) {
196
197
  // Blocklet manager
197
198
  installBlocklet: blockletManager.install.bind(blockletManager),
198
199
  installBlockletFromVc: blockletManager.installBlockletFromVc.bind(blockletManager),
200
+ installComponent: blockletManager.installComponent.bind(blockletManager),
199
201
  startBlocklet: blockletManager.start.bind(blockletManager),
200
202
  stopBlocklet: blockletManager.stop.bind(blockletManager),
201
203
  reloadBlocklet: blockletManager.reload.bind(blockletManager),
202
204
  restartBlocklet: blockletManager.restart.bind(blockletManager),
203
205
  deleteBlocklet: blockletManager.delete.bind(blockletManager),
206
+ deleteComponent: blockletManager.deleteComponent.bind(blockletManager),
204
207
  cancelDownloadBlocklet: blockletManager.cancelDownload.bind(blockletManager),
205
208
  upgradeBlocklet: blockletManager.upgrade.bind(blockletManager),
206
209
  configBlocklet: blockletManager.config.bind(blockletManager),
207
210
  devBlocklet: blockletManager.dev.bind(blockletManager),
208
- getBlockletStatus: blockletManager.getStatus.bind(blockletManager),
209
211
  checkChildBlockletsForUpdates: blockletManager.checkChildrenForUpdates.bind(blockletManager),
210
212
  updateChildBlocklets: blockletManager.updateChildren.bind(blockletManager),
211
213
  getLatestBlockletVersion: blockletManager.getLatestBlockletVersion.bind(blockletManager),
@@ -400,16 +402,6 @@ function ABTNode(options) {
400
402
  } else {
401
403
  // We should only respond to pm2 events when node is alive
402
404
  events.on('node.started', async () => {
403
- const info = await states.node.read();
404
- certManager
405
- .issue({ domain: `${info.did.toLowerCase()}.${info.didDomain}` })
406
- .then(() => {
407
- logger.info('add issue daemon certificate job');
408
- })
409
- .catch((error) => {
410
- logger.error('issue daemon certificate job failed', { error });
411
- });
412
-
413
405
  pm2Events.resume();
414
406
  initCron();
415
407
  });
@@ -0,0 +1,48 @@
1
+ /* eslint-disable no-continue */
2
+ /* eslint-disable no-await-in-loop */
3
+ /* eslint-disable no-underscore-dangle */
4
+
5
+ module.exports = async ({ states, printInfo }) => {
6
+ printInfo('Try to update blocklet to 1.6.17...');
7
+ const blockletState = states.blocklet;
8
+
9
+ const blocklets = await blockletState.getBlocklets();
10
+ for (const blocklet of blocklets) {
11
+ if (!blocklet) {
12
+ continue;
13
+ }
14
+
15
+ if (!blocklet.children || !blocklet.children.length) {
16
+ continue;
17
+ }
18
+
19
+ const meta = blocklet.meta || {};
20
+ const { did } = meta;
21
+ if (!did) {
22
+ continue;
23
+ }
24
+
25
+ const children = (blocklet.children || []).map((child) => {
26
+ if (child.mountPoint) {
27
+ return child;
28
+ }
29
+
30
+ const config = (meta.children || []).find((x) => x.name === child.meta.name);
31
+
32
+ if (
33
+ config &&
34
+ config.mountPoints &&
35
+ config.mountPoints[0] &&
36
+ config.mountPoints[0].root &&
37
+ config.mountPoints[0].root.prefix
38
+ ) {
39
+ child.mountPoint = config.mountPoints[0].root.prefix;
40
+ }
41
+ printInfo(`Set mountPoint: ${child.mountPoint} to child ${child.meta.name} in ${blocklet.meta.name}`);
42
+
43
+ return child;
44
+ });
45
+
46
+ await blockletState.update(blocklet._id, { $set: { children } });
47
+ }
48
+ };
@@ -55,6 +55,7 @@ const { getFromCache: getAccessibleExternalNodeIp } = require('../util/get-acces
55
55
 
56
56
  const Router = require('./index');
57
57
  const states = require('../states');
58
+ const { getBlockletDomainGroupName, getDidFromDomainGroupName } = require('../util/router');
58
59
 
59
60
  /**
60
61
  * replace 888-888-888-888 with accessible ip for domain
@@ -243,6 +244,9 @@ const ensureLatestNodeInfo = async (sites = [], { withDefaultCors = true } = {})
243
244
  site.domain = DOMAIN_FOR_IP_SITE_REGEXP;
244
245
 
245
246
  if (withDefaultCors) {
247
+ // Allow CORS from "Install on ABT Node"
248
+ addCorsToSite(site, info.registerUrl);
249
+
246
250
  // Allow CORS from "Web Wallet"
247
251
  addCorsToSite(site, info.webWalletUrl);
248
252
  }
@@ -313,7 +317,7 @@ const ensureWellknownRule = async (sites) => {
313
317
  rules.forEach((rule) => {
314
318
  if (
315
319
  rule.to.type !== ROUTING_RULE_TYPES.BLOCKLET || // is not blocklet
316
- rule.to.did !== rule.to.realDid // is a component endpoint
320
+ (rule.to.did !== rule.to.realDid && rule.from.pathPrefix !== '/') // is a component endpoint in sub path
317
321
  ) {
318
322
  return;
319
323
  }
@@ -353,6 +357,18 @@ const ensureCorsForWebWallet = async (sites) => {
353
357
  return sites;
354
358
  };
355
359
 
360
+ const filterSitesForRemovedBlocklets = async (sites = []) => {
361
+ const blocklets = await states.blocklet.getBlocklets();
362
+ return sites.filter((site) => {
363
+ if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
364
+ return true;
365
+ }
366
+
367
+ const did = getDidFromDomainGroupName(site.domain);
368
+ return blocklets.some((x) => x.meta.did === did);
369
+ });
370
+ };
371
+
356
372
  const ensureLatestInfo = async (sites = [], { withDefaultCors = true } = {}) => {
357
373
  let result = await ensureLatestNodeInfo(sites, { withDefaultCors });
358
374
  result = await ensureWellknownRule(result);
@@ -421,7 +437,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
421
437
 
422
438
  const https = get(info, 'routing.https', true);
423
439
  const dashboardDomain = get(info, 'routing.dashboardDomain', '');
424
- const certDownloadAddress = get(info, 'routing.dashboardCertDownloadAddress', '');
440
+ const certDownloadAddress =
441
+ process.env.ABT_NODE_DASHBOARD_CERT_DOWN_URL || get(info, 'routing.dashboardCertDownloadAddress', '');
425
442
  if (!https || !dashboardDomain || !certDownloadAddress) {
426
443
  return;
427
444
  }
@@ -495,9 +512,9 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
495
512
  return { status: 'existed' };
496
513
  }
497
514
 
498
- logger.debug('downloading certificate', { url: downloadUrl, dashboardDomain });
515
+ logger.debug('downloading dashboard ip-domain certificate', { url: downloadUrl, dashboardDomain });
499
516
  await updateDashboardCertificate({ checkExpire: false });
500
- logger.debug('downloading certificate', { url: downloadUrl, dashboardDomain });
517
+ logger.debug('downloaded dashboard ip-domain certificate', { url: downloadUrl, dashboardDomain });
501
518
  return { status: 'downloaded' };
502
519
  };
503
520
 
@@ -643,7 +660,9 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
643
660
 
644
661
  const dashboardDomain = get(info, 'routing.dashboardDomain', '');
645
662
  const didDomain = `${info.did.toLowerCase()}.${info.didDomain}`;
646
- const dashboardAliasDomains = [dashboardDomain, didDomain]
663
+ let dashboardAliasDomains = [dashboardDomain, didDomain];
664
+
665
+ dashboardAliasDomains = dashboardAliasDomains
647
666
  .filter((item) => item && !isExistsInAlias(item))
648
667
  .map((item) => ({ value: item, isProtected: true }));
649
668
 
@@ -713,7 +732,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
713
732
  return `/${str}`.replace(/^\/+/, '/');
714
733
  };
715
734
 
716
- const domainGroup = `${blocklet.meta.did}${BLOCKLET_SITE_GROUP_SUFFIX}`;
735
+ const domainGroup = getBlockletDomainGroupName(blocklet.meta.did);
717
736
 
718
737
  const pathPrefix = getPrefix(webInterface.prefix);
719
738
  const rule = {
@@ -730,17 +749,15 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
730
749
  const existSite = await states.site.findOne({ domain: domainGroup });
731
750
  if (!existSite) {
732
751
  const ipEchoDnsDomain = getIpDnsDomainForBlocklet(blocklet, webInterface, nodeInfo.did);
733
- const appIdEnv = blocklet.environments.find((e) => e.key === 'BLOCKLET_APP_ID');
734
752
  const domainAliases = [{ value: ipEchoDnsDomain, isProtected: true }];
735
753
 
736
754
  const didDomain = getDidDomainForBlocklet({
737
- appId: appIdEnv.value,
755
+ name: blocklet.meta.name,
756
+ daemonDid: nodeInfo.did,
738
757
  didDomain: nodeInfo.didDomain,
739
758
  });
740
759
 
741
- if (blocklet.mode !== 'development') {
742
- domainAliases.push({ value: didDomain, isProtected: true });
743
- }
760
+ domainAliases.push({ value: didDomain, isProtected: true });
744
761
 
745
762
  await routerManager.addRoutingSite(
746
763
  {
@@ -755,13 +772,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
755
772
  context
756
773
  );
757
774
  logger.info('add routing site', { site: domainGroup });
758
- if (
759
- process.env.NODE_ENV !== 'development' &&
760
- process.env.NODE_ENV !== 'test' &&
761
- blocklet.mode !== 'development'
762
- ) {
763
- await certManager.issue({ domain: didDomain });
764
- }
765
775
 
766
776
  return true;
767
777
  }
@@ -1080,7 +1090,10 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1080
1090
  const nodeInfo = await nodeState.read();
1081
1091
 
1082
1092
  const ruleChanged = await routerManager.deleteRoutingRulesItemByDid({ did: blocklet.meta.did }, context);
1083
- const siteChanged = await _removeBlockletSites(blocklet, nodeInfo, context);
1093
+ let siteChanged;
1094
+ if (!context.keepRouting) {
1095
+ siteChanged = await _removeBlockletSites(blocklet, nodeInfo, context);
1096
+ }
1084
1097
 
1085
1098
  return ruleChanged || siteChanged;
1086
1099
  };
@@ -1140,6 +1153,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1140
1153
  try {
1141
1154
  const info = await nodeState.read();
1142
1155
  let { sites } = await readRoutingSites();
1156
+ sites = await filterSitesForRemovedBlocklets(sites);
1143
1157
  sites = await ensureLatestInfo(sites);
1144
1158
  sites = await ensureAuthService(sites, blockletManager);
1145
1159
  sites = await ensureServiceRule(sites);
@@ -27,6 +27,7 @@ const {
27
27
  BLOCKLET_BUNDLE_FOLDER,
28
28
  BLOCKLET_DYNAMIC_PATH_PREFIX,
29
29
  BLOCKLET_INTERFACE_TYPE_WEB,
30
+ BlockletGroup,
30
31
  } = require('@blocklet/meta/lib/constants');
31
32
 
32
33
  const {
@@ -37,6 +38,7 @@ const {
37
38
  validateUpdateSite,
38
39
  } = require('../validators/router');
39
40
  const { getProviderFromNodeInfo, findInterfaceByName, isCLI, findInterfacePortByName } = require('../util');
41
+ const { findWebInterface } = require('../util/blocklet');
40
42
  const { attachInterfaceUrls, ensureLatestInfo } = require('./helper');
41
43
  const Router = require('./index');
42
44
  const states = require('../states');
@@ -186,19 +188,37 @@ class RouterManager extends EventEmitter {
186
188
  return dbSite;
187
189
  }
188
190
 
189
- async addDomainAlias({ id, domainAlias }, context = {}) {
191
+ async addDomainAlias({ id, domainAlias, force }, context = {}) {
190
192
  await validateAddDomainAlias(domainAlias, context);
191
193
  const dbSite = await states.site.findOne({ _id: id });
192
194
  if (!dbSite) {
193
195
  throw new Error(`site ${id} does not exist`);
194
196
  }
195
197
 
196
- const siteCount = await states.site.count({
197
- $or: [{ domain: domainAlias }, { domainAliases: domainAlias }, { 'domainAliases.value': domainAlias }],
198
+ // check domain exists in site domain
199
+ const mainDomainSiteCount = await states.site.count({
200
+ domain: domainAlias,
198
201
  });
199
202
 
200
- if (siteCount > 0) {
201
- throw new Error(`domain alias ${domainAlias} already exists`);
203
+ if (mainDomainSiteCount > 0) {
204
+ if (!force) {
205
+ throw new Error(`${domainAlias} already exists`);
206
+ } else {
207
+ throw new Error(`${domainAlias} cannot be forced-added`);
208
+ }
209
+ }
210
+
211
+ // check domain exists in site alias domain
212
+ const aliasDomainSite = await states.site.findOne({
213
+ $or: [{ domainAliases: domainAlias }, { 'domainAliases.value': domainAlias }],
214
+ });
215
+
216
+ if (aliasDomainSite) {
217
+ if (!force) {
218
+ throw new Error(`${domainAlias} already exists`);
219
+ } else {
220
+ await this.deleteDomainAlias({ id: aliasDomainSite.id, domainAlias });
221
+ }
202
222
  }
203
223
 
204
224
  const updateResult = await states.site.update(
@@ -661,49 +681,51 @@ class RouterManager extends EventEmitter {
661
681
 
662
682
  // get child rules
663
683
  const blocklet = await states.blocklet.getBlocklet(rule.to.did);
664
- for (const childMeta of blocklet.meta.children || []) {
665
- for (const mountPoint of childMeta.mountPoints) {
666
- const child = blocklet.children.find((b) => b.meta.name === childMeta.name);
667
-
668
- if (!child) {
669
- logger.error(`Child blocklet ${childMeta.name} does not exist`);
670
- // eslint-disable-next-line no-continue
671
- continue;
672
- }
684
+ for (const child of blocklet.children || []) {
685
+ const { mountPoint } = child;
686
+ if (!mountPoint) {
687
+ logger.error(`mountPoint of child ${child.meta.name} does not exist`);
688
+ // eslint-disable-next-line no-continue
689
+ continue;
690
+ }
673
691
 
674
- if (mountPoint.root.interfaceName === rule.to.interfaceName) {
675
- const pathPrefix = path.join(rule.from.pathPrefix, mountPoint.root.prefix);
676
- const isRootPath = pathPrefix === rule.from.pathPrefix;
677
- if (isRootPath) {
678
- occupied = true;
679
- }
692
+ const childWebInterface = findWebInterface(child);
693
+ if (!childWebInterface) {
694
+ logger.error(`web interface of child ${child.meta.name} does not exist`);
695
+ // eslint-disable-next-line no-continue
696
+ continue;
697
+ }
680
698
 
681
- // if is root path, child rule become root rule
682
- const childRule = {
683
- id: isRootPath ? rule.id : uuid.v4(),
684
- groupId: rule.id,
685
- from: {
686
- pathPrefix: normalizePathPrefix(pathPrefix),
687
- groupPathPrefix: rule.from.pathPrefix,
688
- },
689
- to: {
690
- type: ROUTING_RULE_TYPES.BLOCKLET,
691
- port: findInterfacePortByName(child, mountPoint.child.interfaceName),
692
- did: rule.to.did, // root blocklet did
693
- interfaceName: rule.to.interfaceName, // root blocklet interface
694
- realDid: child.meta.did, // child blocklet did
695
- realInterfaceName: mountPoint.child.interfaceName,
696
- },
697
- isProtected: isRootPath ? rule.isProtected : true,
698
- };
699
-
700
- rules.push(childRule);
701
- }
699
+ const pathPrefix = path.join(rule.from.pathPrefix, mountPoint);
700
+ const isRootPath = pathPrefix === rule.from.pathPrefix;
701
+ if (isRootPath) {
702
+ occupied = true;
702
703
  }
704
+
705
+ // if is root path, child rule become root rule
706
+ const childRule = {
707
+ id: isRootPath ? rule.id : uuid.v4(),
708
+ groupId: rule.id,
709
+ from: {
710
+ pathPrefix: normalizePathPrefix(pathPrefix),
711
+ groupPathPrefix: rule.from.pathPrefix,
712
+ },
713
+ to: {
714
+ type: ROUTING_RULE_TYPES.BLOCKLET,
715
+ port: findInterfacePortByName(child, childWebInterface.name),
716
+ did: rule.to.did, // root blocklet did
717
+ interfaceName: rule.to.interfaceName, // root blocklet interface
718
+ realDid: child.meta.did, // child blocklet did
719
+ realInterfaceName: childWebInterface.name,
720
+ },
721
+ isProtected: isRootPath ? rule.isProtected : true,
722
+ };
723
+
724
+ rules.push(childRule);
703
725
  }
704
726
 
705
727
  // get root rule
706
- if (!occupied) {
728
+ if (!occupied && blocklet.meta.group !== BlockletGroup.gateway) {
707
729
  rules.push(rule);
708
730
  }
709
731
 
@@ -3,6 +3,7 @@
3
3
  /* eslint-disable consistent-return */
4
4
  const logger = require('@abtnode/logger')('state-blocklet-extras');
5
5
  const camelCase = require('lodash/camelCase');
6
+ const get = require('lodash/get');
6
7
 
7
8
  const BaseState = require('./base');
8
9
 
@@ -36,16 +37,28 @@ class BlockletExtrasState extends BaseState {
36
37
  },
37
38
  },
38
39
  ];
40
+
41
+ this.childExtras = this.extras.filter((x) => ['configs'].includes(x.name));
42
+
39
43
  this.generateExtraFns();
40
44
  }
41
45
 
46
+ delete(did) {
47
+ return this.remove({ did });
48
+ }
49
+
42
50
  generateExtraFns() {
43
- const methods = ['get', 'set', 'del'];
51
+ const methods = ['get', 'set', 'del', 'list'];
44
52
  methods.forEach((method) => {
45
53
  this.extras.forEach((extra) => {
46
54
  const fn = camelCase(`${method} ${extra.name}`); // getConfigs, getRules
47
55
  this[fn] = this.generateExtraFn(method, extra);
56
+ });
57
+ });
48
58
 
59
+ const childMethods = ['get', 'set', 'del'];
60
+ childMethods.forEach((method) => {
61
+ this.childExtras.forEach((extra) => {
49
62
  const childFn = camelCase(`${method} child ${extra.name}`); // getChildConfigs, getChildRules
50
63
  this[childFn] = this.generateExtraChildFn(method, extra);
51
64
  });
@@ -66,14 +79,22 @@ class BlockletExtrasState extends BaseState {
66
79
  if (method === 'del') {
67
80
  return this.generateDelFn(extra);
68
81
  }
82
+
83
+ if (method === 'list') {
84
+ return this.generateListFn(extra);
85
+ }
69
86
  }
70
87
 
71
88
  generateGetFn(extra) {
72
- return async (did) => {
89
+ return async (did, path, defaultValue) => {
73
90
  const { dek } = this.options;
74
91
  const { name, afterGet = noop('data') } = extra;
75
92
  const item = await this.asyncDB.findOne({ did });
76
- return afterGet({ data: item ? item[name] : item, did, dek });
93
+ const data = afterGet({ data: item ? item[name] : item, did, dek });
94
+ if (!path) {
95
+ return data;
96
+ }
97
+ return get(data, path, defaultValue);
77
98
  };
78
99
  }
79
100
 
@@ -118,6 +139,21 @@ class BlockletExtrasState extends BaseState {
118
139
  };
119
140
  }
120
141
 
142
+ generateListFn(extra) {
143
+ return async () => {
144
+ const { dek } = this.options;
145
+ const { name, afterGet = noop('data') } = extra;
146
+ const docs = await this.asyncDB.find({});
147
+ const list = docs
148
+ .filter((x) => x[name])
149
+ .map((x) => ({
150
+ did: x.did,
151
+ [name]: afterGet({ data: x[name], did: x.did, dek }),
152
+ }));
153
+ return list;
154
+ };
155
+ }
156
+
121
157
  // generate extra child functions
122
158
 
123
159
  generateExtraChildFn(method, extra) {