@abtnode/core 1.17.5-beta-20251208-123021-e8c53f96 → 1.17.5-beta-20251211-104355-426d7eb6
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/lib/blocklet/manager/disk.js +49 -13
- package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +100 -70
- package/lib/blocklet/migration-dist/migration.cjs +10 -2
- package/lib/event/index.js +39 -0
- package/lib/migrations/index.js +4 -4
- package/lib/monitor/blocklet-runtime-monitor.js +3 -5
- package/lib/states/blocklet-child.js +119 -0
- package/lib/states/blocklet.js +264 -11
- package/lib/states/index.js +4 -1
- package/lib/util/blocklet.js +116 -51
- package/lib/util/default-node-config.js +5 -1
- package/lib/util/migration-sqlite-to-postgres.js +79 -6
- package/package.json +24 -24
|
@@ -905,6 +905,44 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
905
905
|
this.emit(BlockletEvents.statusChange, doc1);
|
|
906
906
|
const startedBlockletDids = [];
|
|
907
907
|
const errorBlockletDids = [];
|
|
908
|
+
const parentBlockletId = blocklet.id;
|
|
909
|
+
|
|
910
|
+
// Helper function to update child status immediately and emit events
|
|
911
|
+
const updateChildStatusImmediately = async (componentDid, status) => {
|
|
912
|
+
if (!states.blockletChild || !parentBlockletId) {
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
try {
|
|
917
|
+
await states.blockletChild.updateChildStatus(parentBlockletId, componentDid, status, false, {
|
|
918
|
+
operator,
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
// Get updated blocklet to emit events
|
|
922
|
+
const updatedBlocklet = await this.getBlocklet(did);
|
|
923
|
+
const componentsInfo = getComponentsInternalInfo(updatedBlocklet);
|
|
924
|
+
|
|
925
|
+
this.emit(BlockletInternalEvents.componentUpdated, {
|
|
926
|
+
appDid: blocklet.appDid,
|
|
927
|
+
components: componentsInfo.filter((c) => c.did === componentDid),
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
if (status === BlockletStatus.running) {
|
|
931
|
+
this.emit(BlockletInternalEvents.componentStarted, {
|
|
932
|
+
appDid: blocklet.appDid,
|
|
933
|
+
components: [{ did: componentDid }],
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
this.emit(BlockletEvents.statusChange, updatedBlocklet);
|
|
938
|
+
} catch (err) {
|
|
939
|
+
logger.error('Failed to update child status immediately', {
|
|
940
|
+
componentDid,
|
|
941
|
+
status,
|
|
942
|
+
error: err.message,
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
};
|
|
908
946
|
|
|
909
947
|
const notStartedComponentDids = await this.startRequiredComponents({
|
|
910
948
|
componentDids,
|
|
@@ -915,11 +953,13 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
915
953
|
e2eMode,
|
|
916
954
|
context,
|
|
917
955
|
atomic,
|
|
918
|
-
onStarted: (subDid) => {
|
|
956
|
+
onStarted: async (subDid) => {
|
|
919
957
|
startedBlockletDids.push({ did: subDid });
|
|
958
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.running);
|
|
920
959
|
},
|
|
921
|
-
onError: (subDid, error) => {
|
|
960
|
+
onError: async (subDid, error) => {
|
|
922
961
|
errorBlockletDids.push({ did: subDid, error });
|
|
962
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.error);
|
|
923
963
|
},
|
|
924
964
|
});
|
|
925
965
|
|
|
@@ -933,11 +973,13 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
933
973
|
e2eMode,
|
|
934
974
|
componentDids: [componentDid],
|
|
935
975
|
operator,
|
|
936
|
-
onStarted: (subDid) => {
|
|
976
|
+
onStarted: async (subDid) => {
|
|
937
977
|
startedBlockletDids.push({ did: subDid });
|
|
978
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.running);
|
|
938
979
|
},
|
|
939
|
-
onError: (subDid, error) => {
|
|
980
|
+
onError: async (subDid, error) => {
|
|
940
981
|
errorBlockletDids.push({ did: subDid, error });
|
|
982
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.error);
|
|
941
983
|
},
|
|
942
984
|
},
|
|
943
985
|
context
|
|
@@ -950,15 +992,12 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
950
992
|
let errorDescription = '';
|
|
951
993
|
let resultBlocklet = nextBlocklet;
|
|
952
994
|
|
|
995
|
+
// Status updates are now done immediately in callbacks, so we only need to handle final events and cleanup
|
|
953
996
|
if (startedBlockletDids.length) {
|
|
954
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
955
|
-
componentDids: startedBlockletDids.map((x) => x.did),
|
|
956
|
-
operator,
|
|
957
|
-
});
|
|
958
|
-
|
|
959
997
|
const finalBlocklet = await this.getBlocklet(did);
|
|
960
998
|
resultBlocklet = finalBlocklet;
|
|
961
999
|
|
|
1000
|
+
// Sync app config after all components started
|
|
962
1001
|
await this.configSynchronizer.throttledSyncAppConfig(finalBlocklet, { wait: 200 });
|
|
963
1002
|
const componentsInfo = getComponentsInternalInfo(finalBlocklet);
|
|
964
1003
|
this.emit(BlockletInternalEvents.componentUpdated, {
|
|
@@ -1009,10 +1048,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
1009
1048
|
componentDids: errorBlockletDids.map((x) => x.did),
|
|
1010
1049
|
shouldUpdateBlockletStatus: false,
|
|
1011
1050
|
});
|
|
1012
|
-
|
|
1013
|
-
componentDids: errorBlockletDids.map((x) => x.did),
|
|
1014
|
-
operator,
|
|
1015
|
-
});
|
|
1051
|
+
|
|
1016
1052
|
const finalBlocklet = await this.getBlocklet(did);
|
|
1017
1053
|
resultBlocklet = finalBlocklet;
|
|
1018
1054
|
this.emit(BlockletEvents.startFailed, {
|
|
@@ -173,6 +173,97 @@ const blueGreenStartBlocklet = async (
|
|
|
173
173
|
|
|
174
174
|
const startedBlockletDids = [];
|
|
175
175
|
const errorBlockletDids = [];
|
|
176
|
+
const appId = blocklet1.id;
|
|
177
|
+
|
|
178
|
+
const notificationChange = async () => {
|
|
179
|
+
// Get latest children from blocklet_children table instead of reloading entire blocklet
|
|
180
|
+
const latestChildren = await states.blocklet.loadChildren(appId);
|
|
181
|
+
|
|
182
|
+
// Merge latest children into blocklet1 for event emission
|
|
183
|
+
const finalBlocklet = {
|
|
184
|
+
...blocklet1,
|
|
185
|
+
children: latestChildren,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
await manager.configSynchronizer.throttledSyncAppConfig(finalBlocklet);
|
|
189
|
+
const componentsInfo = getComponentsInternalInfo(finalBlocklet);
|
|
190
|
+
manager.emit(BlockletInternalEvents.componentUpdated, {
|
|
191
|
+
appDid: blocklet1.appDid,
|
|
192
|
+
components: componentsInfo,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
manager.emit(BlockletInternalEvents.componentStarted, {
|
|
196
|
+
appDid: blocklet1.appDid,
|
|
197
|
+
components: startedBlockletDids.map((x) => ({ did: x.did })),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Emit statusChange event so UI can see the status change
|
|
201
|
+
manager.emit(BlockletEvents.statusChange, finalBlocklet);
|
|
202
|
+
|
|
203
|
+
manager.emit(BlockletEvents.started, { ...finalBlocklet, componentDids: startedBlockletDids.map((x) => x.did) });
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Helper function to update child status immediately and emit events
|
|
207
|
+
const updateChildStatusImmediately = async (componentDid, status, isGreen = false) => {
|
|
208
|
+
if (!states.blockletChild || !appId) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const updates = {
|
|
214
|
+
operator,
|
|
215
|
+
inProgressStart: Date.now(),
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
if (status === BlockletStatus.running) {
|
|
219
|
+
updates.startedAt = new Date();
|
|
220
|
+
updates.stoppedAt = null;
|
|
221
|
+
} else if (status === BlockletStatus.error || status === BlockletStatus.stopped) {
|
|
222
|
+
updates.stoppedAt = new Date();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await states.blockletChild.updateChildStatus(appId, componentDid, status, isGreen, updates);
|
|
226
|
+
|
|
227
|
+
if (status === BlockletStatus.running) {
|
|
228
|
+
await states.blockletChild.updateChildStatus(appId, componentDid, BlockletStatus.stopped, !isGreen, updates);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Get latest children from blocklet_children table and emit events immediately
|
|
232
|
+
const latestChildren = await states.blocklet.loadChildren(appId);
|
|
233
|
+
const updatedBlocklet = {
|
|
234
|
+
...blocklet1,
|
|
235
|
+
children: latestChildren,
|
|
236
|
+
};
|
|
237
|
+
const componentsInfo = getComponentsInternalInfo(updatedBlocklet);
|
|
238
|
+
|
|
239
|
+
manager.emit(BlockletInternalEvents.componentUpdated, {
|
|
240
|
+
appDid: blocklet1.appDid,
|
|
241
|
+
components: componentsInfo.filter((c) => c.did === componentDid),
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (status === BlockletStatus.running) {
|
|
245
|
+
manager.emit(BlockletInternalEvents.componentStarted, {
|
|
246
|
+
appDid: blocklet1.appDid,
|
|
247
|
+
components: [{ did: componentDid }],
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
manager.emit(BlockletEvents.statusChange, updatedBlocklet);
|
|
252
|
+
|
|
253
|
+
// Emit statusChange event immediately so UI can see each component's status change
|
|
254
|
+
manager.emit(BlockletEvents.started, {
|
|
255
|
+
...updatedBlocklet,
|
|
256
|
+
componentDids: startedBlockletDids.map((x) => x.did),
|
|
257
|
+
});
|
|
258
|
+
} catch (err) {
|
|
259
|
+
logger.error('Failed to update child status immediately', {
|
|
260
|
+
componentDid,
|
|
261
|
+
status,
|
|
262
|
+
isGreen,
|
|
263
|
+
error: err.message,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
};
|
|
176
267
|
|
|
177
268
|
for (const item of blueGreenComponentIds) {
|
|
178
269
|
if (!item.componentDids.length) {
|
|
@@ -274,6 +365,11 @@ const blueGreenStartBlocklet = async (
|
|
|
274
365
|
// 收集成功的组件(排除已经在 errorBlockletDids 中的组件)
|
|
275
366
|
startedBlockletDids.push({ did: subDid, isGreen: item.changeToGreen });
|
|
276
367
|
|
|
368
|
+
// Update status immediately
|
|
369
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.running, item.changeToGreen);
|
|
370
|
+
|
|
371
|
+
await manager.deleteProcess({ did, componentDids: [subDid], isGreen: !item.changeToGreen });
|
|
372
|
+
|
|
277
373
|
logger.info('Green environment started successfully', {
|
|
278
374
|
did,
|
|
279
375
|
componentDids: [subDid],
|
|
@@ -285,6 +381,9 @@ const blueGreenStartBlocklet = async (
|
|
|
285
381
|
// 收集失败的组件
|
|
286
382
|
errorBlockletDids.push({ did: subDid, error, isGreen: item.changeToGreen });
|
|
287
383
|
|
|
384
|
+
// Update status immediately
|
|
385
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.error, item.changeToGreen);
|
|
386
|
+
|
|
288
387
|
try {
|
|
289
388
|
await manager.deleteProcess({ did, componentDids: [subDid], isGreen: item.changeToGreen });
|
|
290
389
|
} catch (cleanupError) {
|
|
@@ -326,37 +425,6 @@ const blueGreenStartBlocklet = async (
|
|
|
326
425
|
});
|
|
327
426
|
}
|
|
328
427
|
|
|
329
|
-
const greenBlockletDids = errorBlockletDids.filter((x) => x.isGreen).map((x) => x.did);
|
|
330
|
-
const blueBlockletDids = errorBlockletDids.filter((x) => !x.isGreen).map((x) => x.did);
|
|
331
|
-
|
|
332
|
-
if (greenBlockletDids.length) {
|
|
333
|
-
await manager.deleteProcess({
|
|
334
|
-
did,
|
|
335
|
-
componentDids: greenBlockletDids,
|
|
336
|
-
shouldUpdateBlockletStatus: false,
|
|
337
|
-
isGreen: true,
|
|
338
|
-
});
|
|
339
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
|
|
340
|
-
componentDids: greenBlockletDids,
|
|
341
|
-
operator,
|
|
342
|
-
isGreen: true,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (blueBlockletDids.length) {
|
|
347
|
-
await manager.deleteProcess({
|
|
348
|
-
did,
|
|
349
|
-
componentDids: blueBlockletDids,
|
|
350
|
-
shouldUpdateBlockletStatus: false,
|
|
351
|
-
isGreen: false,
|
|
352
|
-
});
|
|
353
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
|
|
354
|
-
componentDids: blueBlockletDids,
|
|
355
|
-
operator,
|
|
356
|
-
isGreen: false,
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
|
|
360
428
|
const finalBlocklet = await manager.getBlocklet(did);
|
|
361
429
|
manager.emit(BlockletEvents.startFailed, {
|
|
362
430
|
...finalBlocklet,
|
|
@@ -368,45 +436,7 @@ const blueGreenStartBlocklet = async (
|
|
|
368
436
|
|
|
369
437
|
// 处理成功启动的组件
|
|
370
438
|
if (startedBlockletDids.length) {
|
|
371
|
-
|
|
372
|
-
const startedBlueBlockletDids = startedBlockletDids.filter((x) => !x.isGreen).map((x) => x.did);
|
|
373
|
-
|
|
374
|
-
if (startedGreenBlockletDids.length) {
|
|
375
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
376
|
-
componentDids: startedGreenBlockletDids,
|
|
377
|
-
operator,
|
|
378
|
-
isGreen: true,
|
|
379
|
-
});
|
|
380
|
-
await manager.deleteProcess({ did, componentDids: startedGreenBlockletDids, isGreen: false });
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (startedBlueBlockletDids.length) {
|
|
384
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
385
|
-
componentDids: startedBlueBlockletDids,
|
|
386
|
-
operator,
|
|
387
|
-
isGreen: false,
|
|
388
|
-
});
|
|
389
|
-
await manager.deleteProcess({ did, componentDids: startedBlueBlockletDids, isGreen: true });
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const finalBlocklet = await manager.getBlocklet(did);
|
|
393
|
-
|
|
394
|
-
await manager.configSynchronizer.throttledSyncAppConfig(finalBlocklet);
|
|
395
|
-
const componentsInfo = getComponentsInternalInfo(finalBlocklet);
|
|
396
|
-
manager.emit(BlockletInternalEvents.componentUpdated, {
|
|
397
|
-
appDid: blocklet1.appDid,
|
|
398
|
-
components: componentsInfo,
|
|
399
|
-
});
|
|
400
|
-
manager.emit(BlockletInternalEvents.componentStarted, {
|
|
401
|
-
appDid: blocklet1.appDid,
|
|
402
|
-
components: startedBlockletDids.map((x) => ({ did: x.did })),
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
manager.emit(BlockletEvents.statusChange, finalBlocklet);
|
|
406
|
-
manager.emit(BlockletEvents.started, {
|
|
407
|
-
...finalBlocklet,
|
|
408
|
-
componentDids: startedBlockletDids.map((x) => x.did),
|
|
409
|
-
});
|
|
439
|
+
await notificationChange();
|
|
410
440
|
}
|
|
411
441
|
|
|
412
442
|
// 根据情况更新 route table, 会判断只有包含多 interfaces 的 DID 才会更新 route table
|
|
@@ -514,7 +514,7 @@ module.exports = Object.freeze({
|
|
|
514
514
|
BLOCKLET_STORE: {
|
|
515
515
|
id: 'zNKqX7D8ZAYa77HgzpoFfnV3BFbcmSRrE9aT',
|
|
516
516
|
name: 'Official Store',
|
|
517
|
-
description: 'ArcBlock official
|
|
517
|
+
description: 'ArcBlock official store for production ready blocklets',
|
|
518
518
|
url: BLOCKLET_STORE_URL,
|
|
519
519
|
logoUrl: '/logo.png',
|
|
520
520
|
maintainer: 'arcblock',
|
|
@@ -522,11 +522,19 @@ module.exports = Object.freeze({
|
|
|
522
522
|
BLOCKLET_STORE_DEV: {
|
|
523
523
|
id: 'zNKmfUatDhzfMVACfr3u97eqndj8f1yXXw3m',
|
|
524
524
|
name: 'Dev Store',
|
|
525
|
-
description: 'ArcBlock
|
|
525
|
+
description: 'ArcBlock official store for demo and example blocklets',
|
|
526
526
|
url: BLOCKLET_STORE_URL_DEV,
|
|
527
527
|
maintainer: 'arcblock',
|
|
528
528
|
logoUrl: '/logo.png',
|
|
529
529
|
},
|
|
530
|
+
BLOCKLET_TEST_STORE: {
|
|
531
|
+
id: 'zNKirQVRx4xbyTPMkvH3kguRfofTJana8WBK',
|
|
532
|
+
name: 'Test Store',
|
|
533
|
+
description: 'ArcBlock official store for non-production ready blocklets',
|
|
534
|
+
url: TEST_STORE_URL,
|
|
535
|
+
maintainer: 'arcblock',
|
|
536
|
+
logoUrl: '/logo.png',
|
|
537
|
+
},
|
|
530
538
|
|
|
531
539
|
// application is a container, components have no hierarchy and are tiled in application
|
|
532
540
|
APP_STRUCT_VERSION: '2',
|
package/lib/event/index.js
CHANGED
|
@@ -25,6 +25,7 @@ const { encode } = require('@abtnode/util/lib/base32');
|
|
|
25
25
|
const dayjs = require('@abtnode/util/lib/dayjs');
|
|
26
26
|
|
|
27
27
|
const { isInstanceWorker } = require('@abtnode/util/lib/pm2/is-instance-worker');
|
|
28
|
+
const { isInServerlessMode } = require('@abtnode/util/lib/serverless');
|
|
28
29
|
const { NodeMonitSender } = require('../monitor/node-monit-sender');
|
|
29
30
|
const { isCLI } = require('../util');
|
|
30
31
|
|
|
@@ -44,6 +45,7 @@ const {
|
|
|
44
45
|
routingSnapshotPrefix,
|
|
45
46
|
} = require('./util');
|
|
46
47
|
const { ensureBlockletHasMultipleInterfaces } = require('../router/helper');
|
|
48
|
+
const { sendServerlessHeartbeat } = require('../util/launcher');
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
51
|
*
|
|
@@ -80,6 +82,10 @@ module.exports = ({
|
|
|
80
82
|
const events = new EventEmitter();
|
|
81
83
|
events.setMaxListeners(0);
|
|
82
84
|
|
|
85
|
+
// Throttle serverless heartbeat: only call once every 30 seconds
|
|
86
|
+
let lastServerlessHeartbeatTime = 0;
|
|
87
|
+
const SERVERLESS_HEARTBEAT_THROTTLE_MS = 30000;
|
|
88
|
+
|
|
83
89
|
let eventHandler = null;
|
|
84
90
|
events.setEventHandler = (handler) => {
|
|
85
91
|
if (typeof handler === 'function') {
|
|
@@ -521,6 +527,39 @@ module.exports = ({
|
|
|
521
527
|
} else {
|
|
522
528
|
onEvent(eventName, payload);
|
|
523
529
|
}
|
|
530
|
+
|
|
531
|
+
if (
|
|
532
|
+
[
|
|
533
|
+
BlockletEvents.started,
|
|
534
|
+
BlockletEvents.removed,
|
|
535
|
+
BlockletEvents.statusChange,
|
|
536
|
+
BlockletEvents.installed,
|
|
537
|
+
BlockletEvents.componentInstalled,
|
|
538
|
+
BlockletEvents.componentRemoved,
|
|
539
|
+
].includes(eventName)
|
|
540
|
+
) {
|
|
541
|
+
const now = Date.now();
|
|
542
|
+
const remainingMs = SERVERLESS_HEARTBEAT_THROTTLE_MS - (now - lastServerlessHeartbeatTime);
|
|
543
|
+
if (remainingMs <= 0) {
|
|
544
|
+
lastServerlessHeartbeatTime = now;
|
|
545
|
+
node
|
|
546
|
+
.getNodeInfo()
|
|
547
|
+
.then((nodeInfo) => {
|
|
548
|
+
if (isInServerlessMode({ mode: nodeInfo.mode })) {
|
|
549
|
+
logger.info('send serverless heartbeat', { eventName });
|
|
550
|
+
sendServerlessHeartbeat();
|
|
551
|
+
}
|
|
552
|
+
})
|
|
553
|
+
.catch((error) => {
|
|
554
|
+
logger.error('Failed to get node info to send serverless heartbeat', { error, eventName });
|
|
555
|
+
});
|
|
556
|
+
} else {
|
|
557
|
+
logger.debug('serverless heartbeat throttled', {
|
|
558
|
+
eventName,
|
|
559
|
+
remainingMs,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
524
563
|
};
|
|
525
564
|
|
|
526
565
|
const downloadAddedBlocklet = async () => {
|
package/lib/migrations/index.js
CHANGED
|
@@ -169,11 +169,11 @@ const runSchemaMigrations = async ({
|
|
|
169
169
|
|
|
170
170
|
// migrate server schema
|
|
171
171
|
dbPaths.server = getDbFilePath(path.join(dataDir, 'core/server.db'));
|
|
172
|
-
await doSchemaMigration(dbPaths.server, 'server');
|
|
172
|
+
await doSchemaMigration(dbPaths.server, 'server', true);
|
|
173
173
|
printSuccess(`Server schema successfully migrated: ${dbPaths.server}`);
|
|
174
174
|
// migrate service schema
|
|
175
175
|
dbPaths.service = getDbFilePath(path.join(dataDir, 'services/service.db'));
|
|
176
|
-
await doSchemaMigration(dbPaths.service, 'service');
|
|
176
|
+
await doSchemaMigration(dbPaths.service, 'service', true);
|
|
177
177
|
printSuccess(`Service schema successfully migrated: ${dbPaths.service}`);
|
|
178
178
|
|
|
179
179
|
// migrate blocklet schema
|
|
@@ -183,7 +183,7 @@ const runSchemaMigrations = async ({
|
|
|
183
183
|
if (env) {
|
|
184
184
|
const filePath = getDbFilePath(path.join(env.value, 'blocklet.db'));
|
|
185
185
|
dbPaths.blocklets.push(filePath);
|
|
186
|
-
await doSchemaMigration(filePath, 'blocklet');
|
|
186
|
+
await doSchemaMigration(filePath, 'blocklet', true);
|
|
187
187
|
printSuccess(`Blocklet schema successfully migrated: ${blocklet.appPid}: ${filePath}`);
|
|
188
188
|
} else {
|
|
189
189
|
printInfo(`Skip migrate schema for blocklet: ${blocklet.appPid}`);
|
|
@@ -193,7 +193,7 @@ const runSchemaMigrations = async ({
|
|
|
193
193
|
// migrate certificate manager schema
|
|
194
194
|
for (let i = 0; i < MODULES.length; i++) {
|
|
195
195
|
const filePath = getDbFilePath(path.join(dataDir, `modules/${MODULES[i]}/module.db`));
|
|
196
|
-
await doSchemaMigration(filePath, MODULES[i]);
|
|
196
|
+
await doSchemaMigration(filePath, MODULES[i], true);
|
|
197
197
|
dbPaths.certificateManagers.push(filePath);
|
|
198
198
|
printSuccess(`${MODULES[i]} schema successfully migrated: ${filePath}`);
|
|
199
199
|
}
|
|
@@ -230,16 +230,14 @@ class BlockletRuntimeMonitor extends EventEmitter {
|
|
|
230
230
|
|
|
231
231
|
insertThrottleMap.set(blockletDid, now);
|
|
232
232
|
|
|
233
|
-
return this.states.runtimeInsight.insert({ did: blockletDid, ...value }).catch((
|
|
233
|
+
return this.states.runtimeInsight.insert({ did: blockletDid, ...value }).catch((error) => {
|
|
234
|
+
const err = typeof error === 'string' ? error : error.message;
|
|
234
235
|
if (err.includes('duplicate key value violates unique constraint ')) {
|
|
235
236
|
console.error('RuntimeInsight insert error duplicate key value violates unique constraint');
|
|
236
237
|
return;
|
|
237
238
|
}
|
|
238
239
|
if (err.name === 'SequelizeValidationError') {
|
|
239
|
-
console.error(
|
|
240
|
-
'RuntimeInsight validation error',
|
|
241
|
-
err.errors.map((e) => e.message)
|
|
242
|
-
);
|
|
240
|
+
console.error('RuntimeInsight validation error:', err);
|
|
243
241
|
} else {
|
|
244
242
|
console.error('RuntimeInsight insert error', err);
|
|
245
243
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
const { BlockletStatus } = require('@blocklet/constant');
|
|
3
|
+
const BaseState = require('./base');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @extends BaseState<import('@abtnode/models').BlockletChildState>
|
|
7
|
+
*/
|
|
8
|
+
class BlockletChildState extends BaseState {
|
|
9
|
+
constructor(model, config = {}) {
|
|
10
|
+
super(model, config);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get children by parent blocklet ID
|
|
15
|
+
* @param {string} parentBlockletId - The parent blocklet ID
|
|
16
|
+
* @returns {Promise<Array>} - Array of children
|
|
17
|
+
*/
|
|
18
|
+
async getChildrenByParentId(parentBlockletId) {
|
|
19
|
+
if (!parentBlockletId) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
const children = await this.find({ parentBlockletId }, {}, { createdAt: 1 });
|
|
23
|
+
return children || [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Delete children by parent blocklet ID
|
|
28
|
+
* @param {string} parentBlockletId - The parent blocklet ID
|
|
29
|
+
* @returns {Promise<number>} - Number of deleted children
|
|
30
|
+
*/
|
|
31
|
+
deleteByParentId(parentBlockletId) {
|
|
32
|
+
if (!parentBlockletId) {
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
return this.remove({ parentBlockletId });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Update child status by parent blocklet ID and child DID
|
|
40
|
+
* @param {string} parentBlockletId - The parent blocklet ID
|
|
41
|
+
* @param {string} childDid - The child DID
|
|
42
|
+
* @param {number} status - The status to set
|
|
43
|
+
* @param {Object} additionalFields - Additional fields to update
|
|
44
|
+
* @param {boolean} isGreen - Whether the child is green
|
|
45
|
+
* @returns {Promise<Object>} - Updated child
|
|
46
|
+
*/
|
|
47
|
+
async updateChildStatus(parentBlockletId, childDid, status, isGreen = false, additionalFields = {}) {
|
|
48
|
+
if (!parentBlockletId) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const children = await this.getChildrenByParentId(parentBlockletId);
|
|
52
|
+
const child = children.find((c) => c.childDid === childDid);
|
|
53
|
+
|
|
54
|
+
if (!child) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const updates = {
|
|
59
|
+
...additionalFields,
|
|
60
|
+
};
|
|
61
|
+
if (isGreen) {
|
|
62
|
+
updates.greenStatus = status;
|
|
63
|
+
} else {
|
|
64
|
+
updates.status = status;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (status === BlockletStatus.running) {
|
|
68
|
+
updates.startedAt = new Date();
|
|
69
|
+
updates.stoppedAt = null;
|
|
70
|
+
} else if (status === BlockletStatus.stopped) {
|
|
71
|
+
updates.startedAt = null;
|
|
72
|
+
updates.stoppedAt = new Date();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const [, [updated]] = await this.update({ id: child.id }, { $set: updates });
|
|
76
|
+
return updated;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Batch update children status
|
|
81
|
+
* @param {string} parentBlockletId - The parent blocklet ID
|
|
82
|
+
* @param {Array<string>} childDids - Array of child DIDs to update
|
|
83
|
+
* @param {number} status - The status to set
|
|
84
|
+
* @param {Object} additionalFields - Additional fields to update
|
|
85
|
+
* @returns {Promise<Array>} - Updated children
|
|
86
|
+
*/
|
|
87
|
+
async batchUpdateChildrenStatus(parentBlockletId, childDids, status, additionalFields = {}) {
|
|
88
|
+
if (!parentBlockletId) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
const children = await this.getChildrenByParentId(parentBlockletId);
|
|
92
|
+
const updates = [];
|
|
93
|
+
|
|
94
|
+
for (const child of children) {
|
|
95
|
+
if (childDids.includes(child.childDid)) {
|
|
96
|
+
const childUpdates = {
|
|
97
|
+
status,
|
|
98
|
+
...additionalFields,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (status === BlockletStatus.running) {
|
|
102
|
+
childUpdates.startedAt = new Date();
|
|
103
|
+
childUpdates.stoppedAt = null;
|
|
104
|
+
} else if (status === BlockletStatus.stopped) {
|
|
105
|
+
childUpdates.startedAt = null;
|
|
106
|
+
childUpdates.stoppedAt = new Date();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// eslint-disable-next-line no-await-in-loop
|
|
110
|
+
await this.update({ id: child.id }, { $set: childUpdates });
|
|
111
|
+
updates.push({ ...child, ...childUpdates });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return updates;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = BlockletChildState;
|