@abtnode/core 1.7.17 → 1.7.20

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.
@@ -53,7 +53,7 @@ async function runScripts({
53
53
  const { script: scriptPath } = pendingScripts[i];
54
54
  try {
55
55
  printInfo(`Migration script started: ${scriptPath}`);
56
- await runScript(`node ${path.join(scriptsDir, scriptPath)}`, [blocklet.env.appId, 'migration'].join(':'), {
56
+ await runScript(`node ${path.join(scriptsDir, scriptPath)}`, [blocklet.env.processId, 'migration'].join(':'), {
57
57
  cwd: appDir,
58
58
  env: getSafeEnv(env),
59
59
  silent: false,
@@ -0,0 +1,41 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ /* eslint-disable no-continue */
3
+
4
+ module.exports = async ({ states, printInfo }) => {
5
+ printInfo('Try to delete realDid, realInterface and add componentId in db...');
6
+
7
+ const sites = await states.site.find({});
8
+
9
+ for (const site of sites) {
10
+ let changed = false;
11
+ for (const rule of site.rules || []) {
12
+ if (!rule.to) {
13
+ continue;
14
+ }
15
+
16
+ if (rule.to.type !== 'blocklet') {
17
+ continue;
18
+ }
19
+
20
+ if (rule.to.componentId) {
21
+ continue;
22
+ }
23
+
24
+ if (!rule.to.realDid || rule.to.did === rule.to.realDid) {
25
+ rule.to.componentId = rule.to.did;
26
+ } else {
27
+ rule.to.componentId = `${rule.to.did}/${rule.to.realDid}`;
28
+ }
29
+
30
+ delete rule.to.realDid;
31
+ delete rule.to.realInterfaceName;
32
+
33
+ changed = true;
34
+ }
35
+
36
+ if (changed) {
37
+ await states.site.update({ _id: site._id }, site);
38
+ printInfo(`site ${site.domain} has been updated`);
39
+ }
40
+ }
41
+ };
@@ -334,7 +334,7 @@ const ensureWellknownRule = async (sites) => {
334
334
  .sort((a, b) => (a.from.pathPrefix.length > b.from.pathPrefix.length ? 1 : -1));
335
335
  if (blockletRules.length) {
336
336
  // get pathPrefix for blocklet-service
337
- const rootBlockletRule = blockletRules.find((x) => x.to.did === x.to.realDid);
337
+ const rootBlockletRule = blockletRules.find((x) => x.to.did === x.to.componentId);
338
338
  const pathPrefix = joinUrl(rootBlockletRule?.from?.pathPrefix || '/', WELLKNOWN_SERVICE_PATH_PREFIX);
339
339
 
340
340
  // requests for /.well-known/service will stay in blocklet-service and never proxy back to blocklet
@@ -206,7 +206,7 @@ Router.formatSites = (sites = []) => {
206
206
  type: ROUTING_RULE_TYPES.DAEMON,
207
207
  port: daemonRule.to.port,
208
208
  did: rule.to.did,
209
- realDid: rule.to.realDid,
209
+ componentId: rule.to.componentId,
210
210
  },
211
211
  });
212
212
 
@@ -29,6 +29,7 @@ const {
29
29
  BLOCKLET_INTERFACE_TYPE_WEB,
30
30
  BlockletGroup,
31
31
  } = require('@blocklet/meta/lib/constants');
32
+ const { forEachChildSync } = require('@blocklet/meta/lib/util');
32
33
 
33
34
  const {
34
35
  validateAddSite,
@@ -112,7 +113,7 @@ class RouterManager extends EventEmitter {
112
113
  for (const rule of newSite.rules) {
113
114
  this.fixRootBlockletRule(rule);
114
115
  checkPathPrefixInBlackList(rule.from.pathPrefix, dynamicPathBlackList);
115
- rules.push(...(await this.getRules(rule)));
116
+ rules.push(...(await this.getRulesForMutation(rule)));
116
117
  }
117
118
  }
118
119
  newSite.rules = rules;
@@ -281,7 +282,7 @@ class RouterManager extends EventEmitter {
281
282
  await this.validateRouterConfig('addRoutingRule', { id, rule });
282
283
 
283
284
  // add child blocklet rules
284
- for (const x of await this.getRules(rule)) {
285
+ for (const x of await this.getRulesForMutation(rule)) {
285
286
  await states.site.addRuleToSite(id, x);
286
287
  }
287
288
 
@@ -323,7 +324,7 @@ class RouterManager extends EventEmitter {
323
324
  // update rules
324
325
  const newRules = [
325
326
  ...dbSite.rules.filter((x) => (x.groupId && x.groupId !== rule.id) || x.id !== rule.id), // 有些路由没有 rule.groupId
326
- ...(await this.getRules(rule)),
327
+ ...(await this.getRulesForMutation(rule)),
327
328
  ];
328
329
 
329
330
  const updateResult = await states.site.update({ _id: id }, { $set: { rules: newRules } });
@@ -604,8 +605,7 @@ class RouterManager extends EventEmitter {
604
605
  rule.from.pathPrefix = normalizePathPrefix(rule.from.pathPrefix);
605
606
  if (rule.to.type === ROUTING_RULE_TYPES.BLOCKLET) {
606
607
  rule.from.groupPathPrefix = rule.from.pathPrefix;
607
- rule.to.realDid = rule.to.did;
608
- rule.to.realInterfaceName = rule.to.interfaceName;
608
+ rule.to.componentId = rule.to.did;
609
609
  }
610
610
  if (rule.to.url) {
611
611
  rule.to.url = normalizeRedirectUrl(rule.to.url);
@@ -616,7 +616,7 @@ class RouterManager extends EventEmitter {
616
616
  * get all rules to be add or update to site from root rule
617
617
  * @param {*} rule
618
618
  */
619
- async getRules(rule) {
619
+ async getRulesForMutation(rule) {
620
620
  if (rule.to.type !== ROUTING_RULE_TYPES.BLOCKLET) {
621
621
  return [rule];
622
622
  }
@@ -626,23 +626,27 @@ class RouterManager extends EventEmitter {
626
626
 
627
627
  // get child rules
628
628
  const blocklet = await states.blocklet.getBlocklet(rule.to.did);
629
- for (const child of blocklet.children || []) {
630
- const { mountPoint } = child;
629
+ forEachChildSync(blocklet, (component, { id, ancestors }) => {
630
+ if (component.meta.group === BlockletGroup.gateway) {
631
+ return;
632
+ }
633
+
634
+ const { mountPoint } = component;
631
635
  if (!mountPoint) {
632
- logger.error(`mountPoint of child ${child.meta.name} does not exist`);
636
+ logger.error(`mountPoint of child ${component.meta.name} does not exist`);
633
637
  // eslint-disable-next-line no-continue
634
- continue;
638
+ return;
635
639
  }
636
640
 
637
- const childWebInterface = findWebInterface(child);
641
+ const childWebInterface = findWebInterface(component);
638
642
  if (!childWebInterface) {
639
- logger.error(`web interface of child ${child.meta.name} does not exist`);
643
+ logger.error(`web interface of child ${component.meta.name} does not exist`);
640
644
  // eslint-disable-next-line no-continue
641
- continue;
645
+ return;
642
646
  }
643
647
 
644
- const pathPrefix = path.join(rule.from.pathPrefix, mountPoint);
645
- const isRootPath = pathPrefix === rule.from.pathPrefix;
648
+ const pathPrefix = path.join(rule.from.pathPrefix, ...ancestors.map((x) => x.mountPoint || ''), mountPoint);
649
+ const isRootPath = normalizePathPrefix(pathPrefix) === normalizePathPrefix(rule.from.pathPrefix);
646
650
  if (isRootPath) {
647
651
  occupied = true;
648
652
  }
@@ -657,17 +661,16 @@ class RouterManager extends EventEmitter {
657
661
  },
658
662
  to: {
659
663
  type: ROUTING_RULE_TYPES.BLOCKLET,
660
- port: findInterfacePortByName(child, childWebInterface.name),
661
- did: rule.to.did, // root blocklet did
662
- interfaceName: rule.to.interfaceName, // root blocklet interface
663
- realDid: child.meta.did, // child blocklet did
664
- realInterfaceName: childWebInterface.name,
664
+ port: findInterfacePortByName(component, childWebInterface.name),
665
+ did: rule.to.did, // root component did
666
+ interfaceName: rule.to.interfaceName, // root component interface
667
+ componentId: id,
665
668
  },
666
669
  isProtected: isRootPath ? rule.isProtected : true,
667
670
  };
668
671
 
669
672
  rules.push(childRule);
670
- }
673
+ });
671
674
 
672
675
  // get root rule
673
676
  if (!occupied && blocklet.meta.group !== BlockletGroup.gateway) {
@@ -110,7 +110,7 @@ const getLogContent = async (action, args, context, result, info, node) => {
110
110
  case 'deleteComponent':
111
111
  return `removed component ${args.did} from blocklet ${getBlockletInfo(result, info)}`;
112
112
  case 'configBlocklet':
113
- return `updated following ${args.childDid ? `child ${args.childDid}` : ''} config for blocklet ${getBlockletInfo(result, info)}:\n${args.configs.map(x => `- ${x.key}: ${x.value}\n`)}`; // prettier-ignore
113
+ return `updated following config for blocklet ${getBlockletInfo(result, info)}:\n${args.configs.map(x => `- ${x.key}: ${x.value}\n`)}`; // prettier-ignore
114
114
  case 'upgradeBlocklet':
115
115
  return `upgraded blocklet ${getBlockletInfo(result, info)} to v${result.meta.version}`;
116
116
  case 'updateChildBlocklets':
@@ -52,22 +52,14 @@ class BlockletExtrasState extends BaseState {
52
52
  methods.forEach((method) => {
53
53
  this.extras.forEach((extra) => {
54
54
  const fn = camelCase(`${method} ${extra.name}`); // getConfigs, getRules
55
- this[fn] = this.generateExtraFn(method, extra);
56
- });
57
- });
58
-
59
- const childMethods = ['get', 'set', 'del'];
60
- childMethods.forEach((method) => {
61
- this.childExtras.forEach((extra) => {
62
- const childFn = camelCase(`${method} child ${extra.name}`); // getChildConfigs, getChildRules
63
- this[childFn] = this.generateExtraChildFn(method, extra);
55
+ this[fn] = this.generateFn(method, extra);
64
56
  });
65
57
  });
66
58
  }
67
59
 
68
- // generate extra functions
60
+ // generate functions
69
61
 
70
- generateExtraFn(method, extra) {
62
+ generateFn(method, extra) {
71
63
  if (method === 'get') {
72
64
  return this.generateGetFn(extra);
73
65
  }
@@ -86,11 +78,20 @@ class BlockletExtrasState extends BaseState {
86
78
  }
87
79
 
88
80
  generateGetFn(extra) {
89
- return async (did, path, defaultValue) => {
81
+ return async (dids, path, defaultValue) => {
82
+ // eslint-disable-next-line no-param-reassign
83
+ dids = [].concat(dids);
84
+ const [rootDid, ...childDids] = dids;
90
85
  const { dek } = this.options;
91
86
  const { name, afterGet = noop('data') } = extra;
92
- const item = await this.asyncDB.findOne({ did });
93
- const data = afterGet({ data: item ? item[name] : item, did, dek });
87
+
88
+ let item = await this.asyncDB.findOne({ did: rootDid });
89
+ while (item && childDids.length) {
90
+ const did = childDids.shift();
91
+ item = (item.children || []).find((x) => x.did === did);
92
+ }
93
+
94
+ const data = afterGet({ data: item ? item[name] : item, did: rootDid, dek });
94
95
  if (!path) {
95
96
  return data;
96
97
  }
@@ -99,43 +100,71 @@ class BlockletExtrasState extends BaseState {
99
100
  }
100
101
 
101
102
  generateSetFn(extra) {
102
- return async (did, data) => {
103
+ return async (dids, data) => {
104
+ // eslint-disable-next-line no-param-reassign
105
+ dids = [].concat(dids);
106
+ const [rootDid, ...childDids] = dids;
103
107
  const { dek } = this.options;
104
108
  const { name, beforeSet = noop('cur') } = extra;
105
- const item = await this.asyncDB.findOne({ did });
109
+ const exist = await this.asyncDB.findOne({ did: rootDid });
110
+
111
+ const item = exist || { did: rootDid };
112
+ let component = item;
113
+ while (childDids.length) {
114
+ const did = childDids.shift();
115
+ component.children = component.children || [];
116
+ let child = component.children.find((x) => x.did === did);
117
+ if (!child) {
118
+ child = { did };
119
+ component.children.push(child);
120
+ }
121
+ component = child;
122
+ }
106
123
 
107
- if (!item) {
108
- const insertData = {
109
- did,
110
- [name]: beforeSet({ old: undefined, cur: data, did, dek }),
111
- };
112
-
113
- const info = await this.asyncDB.insert(insertData);
114
- logger.info('create info success');
115
- return info[name];
124
+ const old = component[name];
125
+ const newData = beforeSet({ old, cur: data, did: rootDid, dek });
126
+ component[name] = newData;
127
+
128
+ if (!exist) {
129
+ await this.asyncDB.insert(item);
130
+ logger.info('create extra success', { name, dids });
131
+ } else {
132
+ await this.update(item._id, item);
133
+ logger.info('update extra success', { name, dids });
116
134
  }
117
135
 
118
- const itemNameValue = item[name];
119
- const updated = await this.update(item._id, {
120
- $set: {
121
- [name]: beforeSet({ old: itemNameValue, cur: data, did, dek }),
122
- },
123
- });
124
- return updated[name];
136
+ return newData;
125
137
  };
126
138
  }
127
139
 
128
140
  generateDelFn(extra) {
129
- return async (did) => {
141
+ return async (dids) => {
142
+ // eslint-disable-next-line no-param-reassign
143
+ dids = [].concat(dids);
144
+ const [rootDid, ...childDids] = dids;
130
145
  const { name } = extra;
131
- const item = await this.asyncDB.findOne({ did });
146
+ const item = await this.asyncDB.findOne({ did: rootDid });
132
147
 
133
148
  if (!item) {
134
- return item;
149
+ return null;
150
+ }
151
+
152
+ let component = item;
153
+ while (component && childDids.length) {
154
+ const did = childDids.shift();
155
+ component = (component.children || []).find((x) => x.did === did);
135
156
  }
136
157
 
137
- await this.update(item._id, { $set: { [name]: null } });
138
- return item[name];
158
+ if (!component) {
159
+ return null;
160
+ }
161
+
162
+ const updated = component[name];
163
+ component[name] = null;
164
+
165
+ await this.update(item._id, item);
166
+
167
+ return updated;
139
168
  };
140
169
  }
141
170
 
@@ -153,128 +182,6 @@ class BlockletExtrasState extends BaseState {
153
182
  return list;
154
183
  };
155
184
  }
156
-
157
- // generate extra child functions
158
-
159
- generateExtraChildFn(method, extra) {
160
- if (method === 'get') {
161
- return this.generateGetChildFn(extra);
162
- }
163
-
164
- if (method === 'set') {
165
- return this.generateSetChildFn(extra);
166
- }
167
-
168
- if (method === 'del') {
169
- return this.generateDelChildFn(extra);
170
- }
171
- }
172
-
173
- generateGetChildFn(extra) {
174
- return async (did, childDid) => {
175
- const { dek } = this.options;
176
- const { name, afterGet = noop('data') } = extra;
177
- const item = await this.asyncDB.findOne({ did });
178
- const children = (item || {}).children || [];
179
- const subItem = (children || []).find((x) => x.did === childDid);
180
- return afterGet({ data: subItem ? subItem[name] : null, did, dek });
181
- };
182
- }
183
-
184
- generateSetChildFn(extra) {
185
- return async (did, childDid, data) => {
186
- const { dek } = this.options;
187
- const { name, beforeSet = noop('cur') } = extra;
188
- const item = await this.asyncDB.findOne({ did });
189
-
190
- if (!item) {
191
- const newData = beforeSet({ old: undefined, cur: data, did, dek });
192
- const insertData = {
193
- did,
194
- children: [
195
- {
196
- did: childDid,
197
- [name]: newData,
198
- },
199
- ],
200
- };
201
- await this.asyncDB.insert(insertData);
202
-
203
- logger.info('create info success; insert child info success');
204
-
205
- return newData;
206
- }
207
-
208
- const children = (item || {}).children || [];
209
- const subItem = (children || []).find((x) => x.did === childDid);
210
-
211
- if (!subItem) {
212
- const newData = beforeSet({ old: undefined, cur: data, did, dek });
213
- await this.update(item._id, {
214
- $addToSet: {
215
- children: {
216
- did: childDid,
217
- [name]: newData,
218
- },
219
- },
220
- });
221
-
222
- logger.info('insert child info success');
223
- return newData;
224
- }
225
-
226
- const newData = beforeSet({ old: subItem[name], cur: data, did, dek });
227
-
228
- children.forEach((x) => {
229
- if (x.did === childDid) {
230
- x[name] = newData;
231
- }
232
- });
233
-
234
- await this.update(item._id, {
235
- $set: {
236
- children,
237
- },
238
- });
239
-
240
- return newData;
241
- };
242
- }
243
-
244
- generateDelChildFn(extra) {
245
- return async (did, childDid) => {
246
- const { name } = extra;
247
- const item = await this.asyncDB.findOne({ did });
248
-
249
- if (!item) {
250
- return null;
251
- }
252
-
253
- const children = (item || {}).children || [];
254
- const subItem = (children || []).find((x) => x.did === childDid);
255
-
256
- if (!subItem) {
257
- return null;
258
- }
259
-
260
- let updated = null;
261
-
262
- children.forEach((x) => {
263
- if (x.did === childDid) {
264
- updated = x[name];
265
- x[name] = null;
266
- }
267
- });
268
-
269
- await this.update(item._id, {
270
- $set: {
271
- children,
272
- },
273
- });
274
-
275
- return updated;
276
- };
277
- }
278
185
  }
279
186
 
280
187
  module.exports = BlockletExtrasState;
@@ -9,7 +9,7 @@ const detectPort = require('detect-port');
9
9
  const Lock = require('@abtnode/util/lib/lock');
10
10
  const security = require('@abtnode/util/lib/security');
11
11
  const { fixPerson, fixInterfaces } = require('@blocklet/meta/lib/fix');
12
- const { getDisplayName, forEachBlocklet } = require('@blocklet/meta/lib/util');
12
+ const { getDisplayName, forEachBlocklet, forEachBlockletSync, forEachChildSync } = require('@blocklet/meta/lib/util');
13
13
  const {
14
14
  BlockletStatus,
15
15
  BlockletSource,
@@ -33,35 +33,31 @@ const getExternalPortsFromMeta = (meta) =>
33
33
  (meta.interfaces || []).map((x) => x.port && x.port.external).filter(Boolean);
34
34
 
35
35
  const formatBlocklet = (blocklet, phase, dek) => {
36
- forEachBlocklet(
37
- blocklet,
38
- (b) => {
39
- if (b.meta) {
40
- fixPerson(b.meta);
41
- fixInterfaces(b.meta);
42
- }
36
+ forEachBlockletSync(blocklet, (b) => {
37
+ if (b.meta) {
38
+ fixPerson(b.meta);
39
+ fixInterfaces(b.meta);
40
+ }
43
41
 
44
- b.children = b.children || [];
42
+ b.children = b.children || [];
43
+
44
+ if (!b.environments || !b.meta || !dek) {
45
+ return;
46
+ }
45
47
 
46
- if (!b.environments || !b.meta || !dek) {
48
+ ['BLOCKLET_APP_SK'].forEach((key) => {
49
+ const env = b.environments.find((x) => x.key === key);
50
+ if (!env) {
47
51
  return;
48
52
  }
49
-
50
- ['BLOCKLET_APP_SK'].forEach((key) => {
51
- const env = b.environments.find((x) => x.key === key);
52
- if (!env) {
53
- return;
54
- }
55
- if (phase === 'onUpdate' && isHex(env.value) === true) {
56
- env.value = security.encrypt(env.value, b.meta.did, dek);
57
- }
58
- if (phase === 'onRead' && isHex(env.value) === false) {
59
- env.value = security.decrypt(env.value, b.meta.did, dek);
60
- }
61
- });
62
- },
63
- { sync: true }
64
- );
53
+ if (phase === 'onUpdate' && isHex(env.value) === true) {
54
+ env.value = security.encrypt(env.value, b.meta.did, dek);
55
+ }
56
+ if (phase === 'onRead' && isHex(env.value) === false) {
57
+ env.value = security.decrypt(env.value, b.meta.did, dek);
58
+ }
59
+ });
60
+ });
65
61
 
66
62
  return blocklet;
67
63
  };
@@ -416,7 +412,7 @@ class BlockletState extends BaseState {
416
412
  * @param {BlockletStatus} status blocklet status
417
413
  *
418
414
  * children status only different with parent before blocklet installation
419
- * @param {Array<{did}>} children
415
+ * @param {Array<componentId>} children
420
416
  */
421
417
  async setBlockletStatus(did, status, { children } = {}) {
422
418
  if (typeof status === 'undefined') {
@@ -440,44 +436,45 @@ class BlockletState extends BaseState {
440
436
  }
441
437
 
442
438
  // update children status
443
- updates.children = doc.children.map((child) => {
439
+ forEachChildSync(doc, (child, { id }) => {
444
440
  if (children === 'all') {
445
441
  child.status = status;
446
- return child;
442
+ return;
447
443
  }
448
444
 
449
445
  if (!children) {
450
- if (![BlockletStatus.waiting, BlockletStatus.upgrading, BlockletStatus.installing].includes(status)) {
446
+ if (
447
+ ![
448
+ BlockletStatus.waiting,
449
+ BlockletStatus.upgrading,
450
+ BlockletStatus.installing,
451
+ BlockletStatus.starting,
452
+ ].includes(status)
453
+ ) {
451
454
  child.status = status;
452
455
  }
453
456
 
454
- return child;
457
+ return;
455
458
  }
456
459
 
457
- const inputChild = children.find((x) => x.did === child.meta.did);
458
- if (inputChild) {
460
+ if (children.includes(id)) {
459
461
  child.status = status;
460
462
  }
461
- return child;
462
463
  });
463
464
 
465
+ updates.children = doc.children;
464
466
  return this.updateBlocklet(did, updates);
465
467
  }
466
468
 
467
- async fillChildrenPorts(children, { defaultPort = 0, oldChildren } = {}) {
469
+ async fillChildrenPorts(children, { defaultPort = 0, oldChildren, returnMaxPort } = {}) {
468
470
  let _maxPort = defaultPort;
469
471
  for (const child of children || []) {
470
472
  // generate ports
471
473
  const childMeta = child.meta;
474
+ const oldChild = (oldChildren || []).find((x) => x.meta.did === child.meta.did);
472
475
 
473
476
  // get skipOccupiedCheckPorts
474
- let skipOccupiedCheckPorts = [];
475
- if (Array.isArray(oldChildren)) {
476
- const oldChild = oldChildren.find((x) => x.meta.did === child.meta.did);
477
- if (oldChild) {
478
- skipOccupiedCheckPorts = getExternalPortsFromMeta(oldChild.meta);
479
- }
480
- }
477
+ const skipOccupiedCheckPorts = oldChild ? getExternalPortsFromMeta(oldChild.meta) : [];
481
478
 
482
479
  const ports = await this.getBlockletPorts({
483
480
  interfaces: childMeta.interfaces || [],
@@ -486,22 +483,17 @@ class BlockletState extends BaseState {
486
483
  });
487
484
  _maxPort = getMaxPort(ports);
488
485
 
489
- // fill old child's port to new child
490
- if (Array.isArray(oldChildren)) {
491
- const oldChild = oldChildren.find((x) => x.meta.did === child.meta.did);
492
- if (oldChild && oldChild.ports) {
493
- logger.info('Merge the previous ports to child blocklet', {
494
- did: child.meta.did,
495
- name: child.meta.name,
496
- oldPorts: oldChild.ports,
497
- ports,
498
- });
499
- Object.keys(ports).forEach((p) => {
500
- ports[p] = oldChild.ports[p] || ports[p];
501
- });
502
- child.ports = ports;
503
- continue; // eslint-disable-line
504
- }
486
+ if (oldChild && oldChild.ports) {
487
+ // fill old child's port to new child
488
+ logger.info('Merge the previous ports to child blocklet', {
489
+ did: child.meta.did,
490
+ name: child.meta.name,
491
+ oldPorts: oldChild.ports,
492
+ ports,
493
+ });
494
+ Object.keys(ports).forEach((p) => {
495
+ ports[p] = oldChild.ports[p] || ports[p];
496
+ });
505
497
  }
506
498
 
507
499
  // assign a new port to child
@@ -509,6 +501,20 @@ class BlockletState extends BaseState {
509
501
  child.ports = ports;
510
502
  }
511
503
 
504
+ for (const child of children || []) {
505
+ const oldChild = (oldChildren || []).find((x) => x.meta.did === child.meta.did);
506
+
507
+ _maxPort = await this.fillChildrenPorts(child.children || [], {
508
+ defaultPort: _maxPort,
509
+ oldChildren: oldChild?.children,
510
+ returnMaxPort: true,
511
+ });
512
+ }
513
+
514
+ if (returnMaxPort) {
515
+ return _maxPort;
516
+ }
517
+
512
518
  return children;
513
519
  }
514
520
 
@@ -528,10 +534,6 @@ class BlockletState extends BaseState {
528
534
  throw new Error(`mountPoint is required when adding component ${getDisplayName(child, true)}`);
529
535
  }
530
536
 
531
- if (meta.did === parent.meta.did) {
532
- throw new Error('Cannot add self as a component');
533
- }
534
-
535
537
  checkDuplicateComponents([child, ...newChildren]);
536
538
 
537
539
  newChildren.push({
@@ -543,6 +545,7 @@ class BlockletState extends BaseState {
543
545
  mode,
544
546
  dynamic: true,
545
547
  status: BlockletStatus.added,
548
+ children: child.children,
546
549
  });
547
550
 
548
551
  fixChildren(newChildren);