@kumori/aurora-backend-handler 1.0.51 → 1.0.52

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.
@@ -222,6 +222,7 @@ export const createAccount = async (account: Account, security: Security) => {
222
222
  username: account.cloudProvider.username || "",
223
223
  password: account.cloudProvider.password || "",
224
224
  region: account.cloudProvider?.region || "",
225
+ project_id: account.cloudProvider?.project_id || "",
225
226
  }
226
227
  : {
227
228
  method: providerName,
@@ -333,6 +334,7 @@ export const createAccount = async (account: Account, security: Security) => {
333
334
  password: account.cloudProvider.password || "",
334
335
  region: account.cloudProvider?.region || "",
335
336
  domain: account.cloudProvider?.domain || "",
337
+ project_id: account.cloudProvider?.project_id || "",
336
338
  }
337
339
  : {
338
340
  method: providerName,
@@ -611,6 +613,7 @@ export const deleteAccount = async (account: Account, security: Security) => {
611
613
  credentials: {
612
614
  username: account.cloudProvider.username,
613
615
  password: account.cloudProvider.password,
616
+ project_id: account.cloudProvider.project_id || "",
614
617
  },
615
618
  },
616
619
  };
@@ -625,6 +628,7 @@ export const deleteAccount = async (account: Account, security: Security) => {
625
628
  username: account.cloudProvider.username,
626
629
  password: account.cloudProvider.password,
627
630
  domain: account.cloudProvider.domain,
631
+ project_id: account.cloudProvider.project_id || "",
628
632
  },
629
633
  },
630
634
  };
@@ -990,6 +994,7 @@ export const updateAccount = async (account: Account, security: Security) => {
990
994
  username: account.cloudProvider.username || "",
991
995
  password: account.cloudProvider.password || "",
992
996
  region: account.cloudProvider?.region || "",
997
+ project_id: account.cloudProvider?.project_id || "",
993
998
  }
994
999
  : {
995
1000
  method: providerName,
@@ -1099,6 +1104,7 @@ export const updateAccount = async (account: Account, security: Security) => {
1099
1104
  password: account.cloudProvider.password || "",
1100
1105
  region: account.cloudProvider?.region || "",
1101
1106
  domain: account.cloudProvider?.domain || "",
1107
+ project_id: account.cloudProvider?.project_id || "",
1102
1108
  }
1103
1109
  : {
1104
1110
  method: providerName,
@@ -7,6 +7,7 @@ import {
7
7
  makeGlobalWebSocketRequest,
8
8
  } from "../websocket-manager";
9
9
  import { Link, Notification, Service } from "@kumori/aurora-interfaces";
10
+ import { Revision } from "@kumori/aurora-interfaces/interfaces/revision-interface";
10
11
  let pendingLinks = new Map<string, Link[]>();
11
12
  /**
12
13
  * Function to deploy a service
@@ -15,13 +16,13 @@ let pendingLinks = new Map<string, Link[]>();
15
16
  export const deployService = async (data: Service, token: string) => {
16
17
  try {
17
18
  const url = new URL(
18
- `${environment.apiServer.baseUrl}/api/${environment.apiServer.apiVersion}/tenant/${data.tenant}/service/${data.name}`,
19
+ `${environment.apiServer.baseUrl}/api/${environment.apiServer.apiVersion}/tenant/${data.tenant}/service/${data.name}`
19
20
  );
20
21
  url.searchParams.append("dryrun", "false");
21
22
  url.searchParams.append("accept", "true");
22
23
  url.searchParams.append("wait", "30000");
23
24
  url.searchParams.append("validate", "true");
24
- if (data.dsl) {
25
+ if(data.dsl){
25
26
  url.searchParams.append("dsl", "true");
26
27
  }
27
28
  if (data.serviceData) {
@@ -32,7 +33,7 @@ export const deployService = async (data: Service, token: string) => {
32
33
  JSON.stringify({
33
34
  targetAccount: data.account,
34
35
  targetEnvironment: data.environment,
35
- }),
36
+ })
36
37
  );
37
38
  formData.append("labels", JSON.stringify({ project: data.project }));
38
39
  formData.append("comment", " ");
@@ -47,7 +48,7 @@ export const deployService = async (data: Service, token: string) => {
47
48
  const jsonResponse = await response.json();
48
49
 
49
50
  const isTimeout = jsonResponse?.events?.some(
50
- (event: any) => event.content === "_timeout_",
51
+ (event: any) => event.content === "_timeout_"
51
52
  );
52
53
 
53
54
  if (isTimeout) {
@@ -80,7 +81,7 @@ export const deployService = async (data: Service, token: string) => {
80
81
  const jsonResponse = await response.json();
81
82
 
82
83
  const isTimeout = jsonResponse?.events?.some(
83
- (event: any) => event.content === "_timeout_",
84
+ (event: any) => event.content === "_timeout_"
84
85
  );
85
86
 
86
87
  if (isTimeout) {
@@ -110,7 +111,7 @@ export const redeployService = async (data: Service) => {
110
111
  try {
111
112
  const formData = await deployServiceHelper(data);
112
113
  const url = new URL(
113
- `${environment.apiServer.baseUrl}/api/${environment.apiServer.apiVersion}/tenant/${data.tenant}/service/${data.name}/revision/${data.currentRevision}`,
114
+ `${environment.apiServer.baseUrl}/api/${environment.apiServer.apiVersion}/tenant/${data.tenant}/service/${data.name}/revision/${data.currentRevision}`
114
115
  );
115
116
  url.searchParams.append("dryrun", "false");
116
117
  url.searchParams.append("accept", "true");
@@ -129,7 +130,7 @@ export const redeployService = async (data: Service) => {
129
130
  const jsonResponse = await response.json();
130
131
 
131
132
  const isTimeout = jsonResponse?.events?.some(
132
- (event: any) => event.content === "_timeout_",
133
+ (event: any) => event.content === "_timeout_"
133
134
  );
134
135
 
135
136
  if (isTimeout) {
@@ -162,7 +163,7 @@ export const deleteService = async (data: Service, security: string) => {
162
163
  deleteBody,
163
164
  30000,
164
165
  "DELETE",
165
- data.name,
166
+ data.name
166
167
  );
167
168
  return response;
168
169
  } catch (err) {
@@ -187,7 +188,7 @@ export const restartService = async (data: Service, security: string) => {
187
188
  restartBody,
188
189
  30000,
189
190
  "RESTART",
190
- data.name,
191
+ data.name
191
192
  );
192
193
 
193
194
  const updatedService: Service = {
@@ -208,51 +209,30 @@ export const restartService = async (data: Service, security: string) => {
208
209
  }
209
210
  };
210
211
  const generateLinkBody = (data: Service, link: Link) => {
211
- const isOrigin = data.name === link.origin;
212
- const isTarget = data.name === link.target;
213
-
214
- const originInClient = isOrigin
215
- ? data.clientChannels.find(
216
- (channel) =>
217
- channel.name === link.originChannel ||
218
- channel.from === link.originChannel,
219
- )
220
- : undefined;
221
- const originInServer = isOrigin
222
- ? data.serverChannels.find(
223
- (channel) =>
224
- channel.name === link.originChannel ||
225
- channel.from === link.originChannel,
226
- )
227
- : undefined;
228
- const originInDuplex = isOrigin
229
- ? data.duplexChannels.find(
230
- (channel) =>
231
- channel.name === link.originChannel ||
232
- channel.from === link.originChannel,
233
- )
234
- : undefined;
235
- const targetInClient = isTarget
236
- ? data.clientChannels.find(
237
- (channel) =>
238
- channel.name === link.targetChannel ||
239
- channel.from === link.targetChannel,
240
- )
241
- : undefined;
242
- const targetInServer = isTarget
243
- ? data.serverChannels.find(
244
- (channel) =>
245
- channel.name === link.targetChannel ||
246
- channel.from === link.targetChannel,
247
- )
248
- : undefined;
249
- const targetInDuplex = isTarget
250
- ? data.duplexChannels.find(
251
- (channel) =>
252
- channel.name === link.targetChannel ||
253
- channel.from === link.targetChannel,
254
- )
255
- : undefined;
212
+ const originInClient = data.clientChannels.find(
213
+ (channel) =>
214
+ channel.name === link.originChannel || channel.from === link.originChannel
215
+ );
216
+ const originInServer = data.serverChannels.find(
217
+ (channel) =>
218
+ channel.name === link.originChannel || channel.from === link.originChannel
219
+ );
220
+ const originInDuplex = data.duplexChannels.find(
221
+ (channel) =>
222
+ channel.name === link.originChannel || channel.from === link.originChannel
223
+ );
224
+ const targetInClient = data.clientChannels.find(
225
+ (channel) =>
226
+ channel.name === link.targetChannel || channel.from === link.targetChannel
227
+ );
228
+ const targetInServer = data.serverChannels.find(
229
+ (channel) =>
230
+ channel.name === link.targetChannel || channel.from === link.targetChannel
231
+ );
232
+ const targetInDuplex = data.duplexChannels.find(
233
+ (channel) =>
234
+ channel.name === link.targetChannel || channel.from === link.targetChannel
235
+ );
256
236
 
257
237
  let linkBody;
258
238
  if (originInClient) {
@@ -311,7 +291,7 @@ const generateLinkBody = (data: Service, link: Link) => {
311
291
  };
312
292
  } else {
313
293
  console.warn(
314
- `No se encontraron canales para el enlace: origin=${link.origin}, target=${link.target}`,
294
+ `No se encontraron canales para el enlace: origin=${link.origin}, target=${link.target}`
315
295
  );
316
296
  linkBody = {
317
297
  client_tenant: data.tenant,
@@ -322,7 +302,6 @@ const generateLinkBody = (data: Service, link: Link) => {
322
302
  server_channel: link.targetChannel,
323
303
  };
324
304
  }
325
-
326
305
  return linkBody;
327
306
  };
328
307
  export const linkPendingServices = async (service: Service, token: string) => {
@@ -340,7 +319,7 @@ export const linkPendingServices = async (service: Service, token: string) => {
340
319
  linkBody,
341
320
  30000,
342
321
  "LINK",
343
- service.name,
322
+ service.name
344
323
  );
345
324
 
346
325
  const notification: Notification = {
@@ -374,7 +353,7 @@ export const linkPendingServices = async (service: Service, token: string) => {
374
353
  };
375
354
  eventHelper.notification.publish.creation(notification);
376
355
  }
377
- }),
356
+ })
378
357
  );
379
358
  }
380
359
  };
@@ -396,7 +375,7 @@ export const requestRevisionData = async (service: Service, token: string) => {
396
375
  "GET_REVISION",
397
376
  service.name,
398
377
  "service",
399
- service,
378
+ service
400
379
  );
401
380
  return response;
402
381
  } catch (err) {
@@ -423,163 +402,203 @@ export const updateService = async (
423
402
  // pendingLinks.set(data.name, newLinksToCreate);
424
403
  // await linkPendingServices(serviceWithNewLinks, token);
425
404
  // }
426
-
427
- const parameterObject: Record<string, any> = {};
428
- if (data.parameters && data.parameters.length > 0) {
429
- data.parameters.forEach((param) => {
430
- const key = param.configKey || param.name;
431
- const paramType = param.type?.toLowerCase();
432
- if (paramType === "number" || paramType === "integer") {
433
- parameterObject[key] = Number(param.value) || 0;
434
- } else if (paramType === "boolean") {
435
- parameterObject[key] = param.value === "true";
436
- } else {
437
- parameterObject[key] = param.value;
438
- }
439
- });
405
+ const url = new URL(
406
+ `${environment.apiServer.baseUrl}/api/${environment.apiServer.apiVersion}/tenant/${data.tenant}/service/${data.name}/revision/${data.currentRevision}`,
407
+ );
408
+ if (data.dsl) {
409
+ url.searchParams.append("dsl", "true");
440
410
  }
441
-
442
- const resourceObject: Record<string, any> = {};
443
- if (data.resources && data.resources.length > 0) {
444
- data.resources.forEach((resource) => {
445
- if (resource.type === "volume") {
446
- resourceObject[resource.name] = {
447
- volume: {
448
- kind: "storage",
449
- size: parseInt(resource.value) || 1,
450
- unit: "G",
451
- type: resource.kind,
452
- },
453
- };
454
- } else if (resource.type === "secret") {
455
- resourceObject[resource.name] = {
456
- secret: `${data.tenant}/${resource.value}`,
457
- };
458
- } else if (resource.type === "domain") {
459
- resourceObject[resource.name] = {
460
- domain: `${data.tenant}/${resource.value}`,
461
- };
462
- } else if (resource.type === "ca") {
463
- resourceObject[resource.name] = {
464
- ca: `${data.tenant}/${resource.value}`,
465
- };
466
- } else if (resource.type === "certificate") {
467
- resourceObject[resource.name] = {
468
- certificate: `${data.tenant}/${resource.value}`,
469
- };
470
- } else if (resource.type === "port") {
471
- resourceObject[resource.name] = {
472
- port: `${data.tenant}/${resource.value}`,
473
- };
474
- }
411
+ if (data.serviceData) {
412
+ const formData = new FormData();
413
+ formData.append("type", "update-bundle");
414
+ formData.append("bundle", data.serviceData);
415
+ formData.append(
416
+ "meta",
417
+ JSON.stringify({
418
+ targetAccount: data.account,
419
+ targetEnvironment: data.environment,
420
+ }),
421
+ );
422
+ formData.append("labels", JSON.stringify({ project: data.project }));
423
+ formData.append("comment", " ");
424
+ const response = await fetch(url.toString(), {
425
+ method: "POST",
426
+ body: formData,
475
427
  });
476
- }
477
-
478
- let previousRevision = 1;
479
- if (data.currentRevision) {
480
- previousRevision = parseInt(data.currentRevision.toString(), 10);
481
- if (isNaN(previousRevision)) {
482
- console.warn("currentRevision is not a valid number, using 1");
483
- previousRevision = 1;
428
+ if (!response.ok) {
429
+ throw new Error(`HTTP error! status: ${response.status}`);
484
430
  }
485
- } else {
486
- previousRevision = getLatestRevision(data.revisions) || 1;
487
- }
488
431
 
489
- const scaleConfig: any = {};
490
- if (data.role && data.role.length > 0) {
491
- scaleConfig.detail = {};
492
- data.role.forEach((role) => {
493
- scaleConfig.detail[role.name] = {
494
- hsize: role.hsize || scale || data.minReplicas || 1,
495
- };
496
- });
432
+ const jsonResponse = await response.json();
433
+ const isTimeout = jsonResponse?.events?.some(
434
+ (event: any) => event.content === "_timeout_",
435
+ );
436
+
437
+ if (isTimeout) {
438
+ console.error("Timeout en la petición:", {
439
+ isOk: false,
440
+ code: "TIMEOUT",
441
+ error: "_timeout_",
442
+ });
443
+ }
497
444
  } else {
498
- scaleConfig.hsize = scale || data.minReplicas || 1;
499
- }
500
- const meta = {
501
- scaling: {
502
- simple:
503
- data.role?.reduce(
504
- (acc, role) => {
505
- if (role.scalling && role.name) {
506
- acc[role.name] = {
507
- scale_up: {
508
- cpu: Math.min(parseInt(role.scalling.cpu.up) || 0, 100),
509
- memory: Math.min(
510
- parseInt(role.scalling.memory.up) || 0,
511
- 100,
512
- ),
513
- },
514
- scale_down: {
515
- cpu: Math.min(parseInt(role.scalling.cpu.down) || 0, 100),
516
- memory: Math.min(
517
- parseInt(role.scalling.memory.down) || 0,
518
- 100,
519
- ),
520
- },
521
- hysteresis: parseInt(role.scalling.histeresys) || 0,
522
- min_replicas: role.scalling.instances.min || 0,
523
- max_replicas: role.scalling.instances.max || 0,
524
- };
525
- }
526
- return acc;
527
- },
528
- {} as Record<string, any>,
529
- ) || {},
530
- },
531
- };
445
+ const parameterObject: Record<string, any> = {};
446
+ if (data.parameters && data.parameters.length > 0) {
447
+ data.parameters.forEach((param) => {
448
+ const key = param.configKey || param.name;
449
+ const paramType = param.type?.toLowerCase();
450
+ if (paramType === "number" || paramType === "integer") {
451
+ parameterObject[key] = Number(param.value) || 0;
452
+ } else if (paramType === "boolean") {
453
+ parameterObject[key] = param.value === "true";
454
+ } else {
455
+ parameterObject[key] = param.value;
456
+ }
457
+ });
458
+ }
532
459
 
533
- const updateBody = {
534
- spec: {
535
- type: "update-config",
536
- comment: "Service configuration update",
537
- config: {
538
- parameter: parameterObject,
539
- resource: resourceObject,
540
- resilience: 0,
541
- scale: scaleConfig,
542
- },
543
- meta: meta,
544
- },
545
- tenant: data.tenant,
546
- service: data.name,
547
- previous: previousRevision,
548
- };
460
+ const resourceObject: Record<string, any> = {};
461
+ if (data.resources && data.resources.length > 0) {
462
+ data.resources.forEach((resource) => {
463
+ if (resource.type === "volume") {
464
+ resourceObject[resource.name] = {
465
+ volume: {
466
+ kind: "storage",
467
+ size: parseInt(resource.value) || 1,
468
+ unit: "G",
469
+ type: resource.kind,
470
+ },
471
+ };
472
+ } else if (resource.type === "secret") {
473
+ resourceObject[resource.name] = {
474
+ secret: `${data.tenant}/${resource.value}`,
475
+ };
476
+ } else if (resource.type === "domain") {
477
+ resourceObject[resource.name] = {
478
+ domain: `${data.tenant}/${resource.value}`,
479
+ };
480
+ } else if (resource.type === "ca") {
481
+ resourceObject[resource.name] = {
482
+ ca: `${data.tenant}/${resource.value}`,
483
+ };
484
+ } else if (resource.type === "certificate") {
485
+ resourceObject[resource.name] = {
486
+ certificate: `${data.tenant}/${resource.value}`,
487
+ };
488
+ } else if (resource.type === "port") {
489
+ resourceObject[resource.name] = {
490
+ port: `${data.tenant}/${resource.value}`,
491
+ };
492
+ }
493
+ });
494
+ }
549
495
 
550
- const response = await makeGlobalWebSocketRequest(
551
- "revision:update_revision",
552
- updateBody,
553
- 30000,
554
- "UPDATE_CONFIG",
555
- data.name,
556
- );
496
+ let previousRevision = 1;
497
+ if (data.currentRevision) {
498
+ previousRevision = parseInt(data.currentRevision.toString(), 10);
499
+ if (isNaN(previousRevision)) {
500
+ console.warn("currentRevision is not a valid number, using 1");
501
+ previousRevision = 1;
502
+ }
503
+ } else {
504
+ previousRevision = getLatestRevision(data.revisions) || 1;
505
+ }
557
506
 
558
- const updatedService: Service = {
559
- ...data,
560
- status: {
561
- code: "PENDING",
562
- message: "",
563
- timestamp: "",
564
- args: [],
565
- },
566
- };
567
- eventHelper.service.publish.updated(updatedService);
507
+ const scaleConfig: any = {};
508
+ if (data.role && data.role.length > 0) {
509
+ scaleConfig.detail = {};
510
+ data.role.forEach((role) => {
511
+ scaleConfig.detail[role.name] = {
512
+ hsize: role.hsize || scale || data.minReplicas || 1,
513
+ };
514
+ });
515
+ } else {
516
+ scaleConfig.hsize = scale || data.minReplicas || 1;
517
+ }
518
+ const meta = {
519
+ scaling: {
520
+ simple:
521
+ data.role?.reduce(
522
+ (acc, role) => {
523
+ if (role.scalling && role.name) {
524
+ acc[role.name] = {
525
+ scale_up: {
526
+ cpu: Math.min(parseInt(role.scalling.cpu.up) || 0, 100),
527
+ memory: Math.min(
528
+ parseInt(role.scalling.memory.up) || 0,
529
+ 100,
530
+ ),
531
+ },
532
+ scale_down: {
533
+ cpu: Math.min(parseInt(role.scalling.cpu.down) || 0, 100),
534
+ memory: Math.min(
535
+ parseInt(role.scalling.memory.down) || 0,
536
+ 100,
537
+ ),
538
+ },
539
+ hysteresis: parseInt(role.scalling.histeresys) || 0,
540
+ min_replicas: role.scalling.instances.min || 0,
541
+ max_replicas: role.scalling.instances.max || 0,
542
+ };
543
+ }
544
+ return acc;
545
+ },
546
+ {} as Record<string, any>,
547
+ ) || {},
548
+ },
549
+ };
568
550
 
569
- const updateNotification: Notification = {
570
- type: "success",
571
- subtype: "service-updated",
572
- date: Date.now().toString(),
573
- status: "unread",
574
- callToAction: false,
575
- data: {
576
- service: data.name,
551
+ const updateBody = {
552
+ spec: {
553
+ type: "update-config",
554
+ comment: "Service configuration update",
555
+ config: {
556
+ parameter: parameterObject,
557
+ resource: resourceObject,
558
+ resilience: 0,
559
+ scale: scaleConfig,
560
+ },
561
+ meta: meta,
562
+ },
577
563
  tenant: data.tenant,
578
- },
579
- };
564
+ service: data.name,
565
+ previous: previousRevision,
566
+ };
567
+
568
+ const response = await makeGlobalWebSocketRequest(
569
+ "revision:update_revision",
570
+ updateBody,
571
+ 30000,
572
+ "UPDATE_CONFIG",
573
+ data.name,
574
+ );
575
+ const updatedService: Service = {
576
+ ...data,
577
+ status: {
578
+ code: "PENDING",
579
+ message: "",
580
+ timestamp: "",
581
+ args: [],
582
+ },
583
+ };
584
+ eventHelper.service.publish.updated(updatedService);
585
+
586
+ const updateNotification: Notification = {
587
+ type: "success",
588
+ subtype: "service-updated",
589
+ date: Date.now().toString(),
590
+ status: "unread",
591
+ callToAction: false,
592
+ data: {
593
+ service: data.name,
594
+ tenant: data.tenant,
595
+ },
596
+ };
597
+
598
+ eventHelper.notification.publish.creation(updateNotification);
599
+ return response;
600
+ }
580
601
 
581
- eventHelper.notification.publish.creation(updateNotification);
582
- return response;
583
602
  } catch (err) {
584
603
  console.error("Error updating service configuration via WebSocket:", err);
585
604
  const notification: Notification = {
@@ -604,7 +623,7 @@ export const updateService = async (
604
623
  };
605
624
  export const unlinkServices = async (service: Service, token: string) => {
606
625
  const deleteLinks: Link[] = service.links.filter(
607
- (link) => link.delete === true,
626
+ (link) => link.delete === true
608
627
  );
609
628
  if (deleteLinks.length > 0) {
610
629
  await Promise.all(
@@ -619,7 +638,7 @@ export const unlinkServices = async (service: Service, token: string) => {
619
638
  unlinkBody,
620
639
  30000,
621
640
  "UNLINK",
622
- service.name,
641
+ service.name
623
642
  );
624
643
 
625
644
  const unlinkNotification: Notification = {
@@ -655,7 +674,7 @@ export const unlinkServices = async (service: Service, token: string) => {
655
674
  };
656
675
  eventHelper.notification.publish.creation(notification);
657
676
  }
658
- }),
677
+ })
659
678
  );
660
679
  }
661
680
  };
@@ -680,7 +699,7 @@ export const updateServiceLinks = async (link: Link, token: string) => {
680
699
  linkBody,
681
700
  30000,
682
701
  "UNLINK",
683
- link.origin,
702
+ link.origin
684
703
  );
685
704
 
686
705
  // const unlinkNotification: Notification = {
@@ -726,7 +745,7 @@ export const updateServiceLinks = async (link: Link, token: string) => {
726
745
  linkBody,
727
746
  30000,
728
747
  "LINK",
729
- link.origin,
748
+ link.origin
730
749
  );
731
750
 
732
751
  const notification: Notification = {
@@ -782,7 +801,7 @@ export const changeRevision = async (data: Service, token: string) => {
782
801
  revisionBody,
783
802
  30000,
784
803
  "UPDATE_REVISION",
785
- data.name,
804
+ data.name
786
805
  );
787
806
  const notification: Notification = {
788
807
  type: "success",
@@ -814,10 +833,10 @@ export const changeRevision = async (data: Service, token: string) => {
814
833
  eventHelper.notification.publish.creation(notification);
815
834
  }
816
835
  };
817
- function getLatestRevision(revisions: string[]): number | null {
836
+ function getLatestRevision(revisions: Revision[]): number | null {
818
837
  if (!revisions || revisions.length === 0) {
819
838
  return null;
820
839
  }
821
-
822
- return Math.max(...revisions.map(Number));
823
- }
840
+
841
+ return Math.max(...revisions.map((revision) => Number(revision.id)));
842
+ }