@revoengine/cli 1.0.10 → 1.0.11

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.
Files changed (79) hide show
  1. package/README.md +312 -33
  2. package/dist/src/cli.js +58 -7
  3. package/dist/src/client.d.ts +356 -5
  4. package/dist/src/client.js +803 -19
  5. package/dist/src/commands/auth.js +4 -2
  6. package/dist/src/commands/component.js +260 -141
  7. package/dist/src/commands/database-schemas.d.ts +2 -0
  8. package/dist/src/commands/database-schemas.js +188 -0
  9. package/dist/src/commands/database-views.d.ts +2 -0
  10. package/dist/src/commands/database-views.js +123 -0
  11. package/dist/src/commands/endpoints.js +114 -0
  12. package/dist/src/commands/env.js +44 -24
  13. package/dist/src/commands/events.d.ts +2 -0
  14. package/dist/src/commands/events.js +146 -0
  15. package/dist/src/commands/groups.d.ts +2 -0
  16. package/dist/src/commands/groups.js +169 -0
  17. package/dist/src/commands/index.d.ts +8 -0
  18. package/dist/src/commands/index.js +8 -0
  19. package/dist/src/commands/job-templates.d.ts +2 -0
  20. package/dist/src/commands/job-templates.js +101 -0
  21. package/dist/src/commands/metadata.js +29 -7
  22. package/dist/src/commands/project.js +10 -3
  23. package/dist/src/commands/role-groups.d.ts +2 -0
  24. package/dist/src/commands/role-groups.js +152 -0
  25. package/dist/src/commands/schedules.d.ts +2 -0
  26. package/dist/src/commands/schedules.js +141 -0
  27. package/dist/src/commands/terminal-service.d.ts +38 -0
  28. package/dist/src/commands/terminal-service.js +210 -0
  29. package/dist/src/commands/terminal.d.ts +22 -0
  30. package/dist/src/commands/terminal.js +511 -0
  31. package/dist/src/component-lock.d.ts +100 -2
  32. package/dist/src/component-lock.js +304 -15
  33. package/dist/src/config.d.ts +10 -2
  34. package/dist/src/config.js +49 -14
  35. package/dist/src/database-schema-artifacts.d.ts +7 -0
  36. package/dist/src/database-schema-artifacts.js +8 -0
  37. package/dist/src/env-sync.d.ts +0 -3
  38. package/dist/src/env-sync.js +3 -19
  39. package/dist/src/metadata-backfill.d.ts +16 -6
  40. package/dist/src/metadata-backfill.js +1069 -18
  41. package/dist/src/project.d.ts +2 -0
  42. package/dist/src/project.js +4 -17
  43. package/dist/src/prompt.js +10 -18
  44. package/dist/src/resource-metadata.d.ts +1 -0
  45. package/dist/src/resource-metadata.js +3 -0
  46. package/dist/src/resource-syncs/database-schema-sync.d.ts +117 -0
  47. package/dist/src/resource-syncs/database-schema-sync.js +2289 -0
  48. package/dist/src/resource-syncs/database-view-sync.d.ts +124 -0
  49. package/dist/src/resource-syncs/database-view-sync.js +1317 -0
  50. package/dist/src/resource-syncs/endpoint-sync.d.ts +96 -0
  51. package/dist/src/resource-syncs/endpoint-sync.js +1283 -0
  52. package/dist/src/resource-syncs/event-sync.d.ts +99 -0
  53. package/dist/src/resource-syncs/event-sync.js +949 -0
  54. package/dist/src/resource-syncs/group-sync.d.ts +86 -0
  55. package/dist/src/resource-syncs/group-sync.js +882 -0
  56. package/dist/src/resource-syncs/job-template-sync.d.ts +85 -0
  57. package/dist/src/resource-syncs/job-template-sync.js +782 -0
  58. package/dist/src/resource-syncs/role-group-sync.d.ts +83 -0
  59. package/dist/src/resource-syncs/role-group-sync.js +597 -0
  60. package/dist/src/resource-syncs/schedule-sync.d.ts +111 -0
  61. package/dist/src/resource-syncs/schedule-sync.js +1302 -0
  62. package/dist/src/resource-syncs/util.d.ts +19 -0
  63. package/dist/src/resource-syncs/util.js +116 -0
  64. package/dist/src/runtime-view.d.ts +1 -0
  65. package/dist/src/runtime-view.js +6 -1
  66. package/dist/src/sync-output.d.ts +38 -0
  67. package/dist/src/sync-output.js +131 -0
  68. package/dist/src/tracked-resources.d.ts +1 -1
  69. package/dist/src/tracked-resources.js +26 -2
  70. package/dist/src/types.d.ts +224 -0
  71. package/dist/src/ui.d.ts +3 -0
  72. package/dist/src/ui.js +67 -18
  73. package/dist/src/utils.d.ts +2 -0
  74. package/dist/src/utils.js +64 -0
  75. package/dist/src/workspace-component.d.ts +2 -0
  76. package/dist/src/workspace-component.js +34 -0
  77. package/dist/src/workspace-resource.d.ts +2 -0
  78. package/dist/src/workspace-resource.js +52 -0
  79. package/package.json +8 -3
@@ -1,31 +1,44 @@
1
1
  import { AUTH_VALIDATION_TTL_MS, buildAuthValidationKey, DEFAULT_BASE_URL, readAuthValidationState, resolveRuntimeConfig, saveAuthValidationState, } from "./config.js";
2
+ import { isInteractiveTerminal, promptConfirm } from "./prompt.js";
2
3
  import { createSpinner } from "./spinner.js";
3
4
  export class ApiError extends Error {
4
5
  status;
5
6
  data;
6
- constructor(status, message, data) {
7
+ headers;
8
+ constructor(status, message, data, headers) {
7
9
  super(message);
8
10
  this.name = 'ApiError';
9
11
  this.status = status;
10
12
  this.data = data;
13
+ this.headers = headers;
11
14
  }
12
15
  }
13
16
  export class AuthenticationError extends ApiError {
14
- constructor(status, message, data) {
15
- super(status, message, data);
17
+ constructor(status, message, data, headers) {
18
+ super(status, message, data, headers);
16
19
  this.name = 'AuthenticationError';
17
20
  }
18
21
  }
19
22
  export class PermissionDeniedError extends ApiError {
20
23
  path;
21
- constructor(path, message, data) {
22
- super(403, message, data);
24
+ constructor(path, message, data, headers) {
25
+ super(403, message, data, headers);
23
26
  this.name = 'PermissionDeniedError';
24
27
  this.path = path;
25
28
  }
26
29
  }
27
30
  const REQUEST_DELAY_MS = 50;
28
31
  const COMPONENT_LIST_PAGE_SIZE = 200;
32
+ const DATABASE_SCHEMA_LIST_PAGE_SIZE = 200;
33
+ const DATABASE_SCHEMA_THIN_FIELDS = [
34
+ 'databaseId', 'name', 'category', 'metadata', 'type', 'parent', 'master', 'version',
35
+ 'deletedAt', 'deletedBy',
36
+ ].join(',');
37
+ const DATABASE_SCHEMA_PULL_FIELDS = [
38
+ 'databaseId', 'name', 'category', 'desc', 'metadata', 'type', 'definition', 'partition',
39
+ 'parent', 'master', 'version', 'audit', 'restricted', 'tags',
40
+ 'deletedAt', 'deletedBy',
41
+ ].join(',');
29
42
  const ACTIVE_COMPONENT_LIST_FILTER = {
30
43
  'filter[and][0][field]': 'deletedAt',
31
44
  'filter[and][0][op]': 'isNull',
@@ -33,6 +46,16 @@ const ACTIVE_COMPONENT_LIST_FILTER = {
33
46
  function isRecord(value) {
34
47
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
35
48
  }
49
+ function hasDatabaseSchemaParent(database) {
50
+ if (typeof database.parent === 'string') {
51
+ return database.parent.trim().length > 0;
52
+ }
53
+ if (!isRecord(database.parent)) {
54
+ return false;
55
+ }
56
+ return [database.parent.databaseId, database.parent.id, database.parent.name]
57
+ .some((value) => typeof value === 'string' && value.trim().length > 0);
58
+ }
36
59
  function readNumber(value) {
37
60
  return typeof value === 'number' && Number.isFinite(value) ? value : null;
38
61
  }
@@ -48,10 +71,13 @@ function unwrapList(value) {
48
71
  if (Array.isArray(candidate.items)) {
49
72
  return candidate.items;
50
73
  }
74
+ if (Array.isArray(candidate.results)) {
75
+ return candidate.results;
76
+ }
51
77
  }
52
78
  return [];
53
79
  }
54
- function resolveNextComponentListRequest(value) {
80
+ function resolveNextListRequest(value) {
55
81
  if (typeof value === 'string' && value) {
56
82
  return {
57
83
  path: value,
@@ -97,7 +123,7 @@ function withActiveComponentListFilter(request) {
97
123
  },
98
124
  };
99
125
  }
100
- function unwrapComponentListPage(value) {
126
+ function unwrapListPage(value) {
101
127
  const items = unwrapList(value);
102
128
  if (!isRecord(value)) {
103
129
  return {
@@ -112,9 +138,9 @@ function unwrapComponentListPage(value) {
112
138
  ?? readNumber(value.count)
113
139
  ?? (meta ? readNumber(meta.total) ?? readNumber(meta.count) : null)
114
140
  ?? (pagination ? readNumber(pagination.total) ?? readNumber(pagination.count) : null);
115
- const nextRequest = resolveNextComponentListRequest(value.next)
116
- ?? (meta ? resolveNextComponentListRequest(meta.next) : null)
117
- ?? (pagination ? resolveNextComponentListRequest(pagination.next) : null);
141
+ const nextRequest = resolveNextListRequest(value.next)
142
+ ?? (meta ? resolveNextListRequest(meta.next) : null)
143
+ ?? (pagination ? resolveNextListRequest(pagination.next) : null);
118
144
  return {
119
145
  items,
120
146
  total,
@@ -167,12 +193,68 @@ function buildUrl(baseUrl, requestPath, query) {
167
193
  }
168
194
  return url;
169
195
  }
196
+ function isLocalhost(hostname) {
197
+ return hostname === 'localhost';
198
+ }
199
+ function isMatchingFirstPartySandbox(base, target) {
200
+ if (base.hostname === 'app.revo.com' && target.hostname === 'sbx.revoengine.com') {
201
+ return true;
202
+ }
203
+ if (!base.hostname.startsWith('api.')) {
204
+ return false;
205
+ }
206
+ const serviceDomain = base.hostname.slice('api.'.length);
207
+ return (serviceDomain === 'revoengine.com' || serviceDomain.endsWith('.revong.com'))
208
+ && target.hostname === `sbx.${serviceDomain}`;
209
+ }
210
+ function resolveTrustedServiceOrigins(configured) {
211
+ const values = configured ?? (process.env.REVO_TRUSTED_SERVICE_ORIGINS || '').split(',').filter(Boolean);
212
+ return new Set(values.map((value) => {
213
+ const url = new URL(value.trim());
214
+ if (url.username || url.password || url.protocol !== 'https:' || url.pathname !== '/' || url.search || url.hash) {
215
+ throw new Error('REVO_TRUSTED_SERVICE_ORIGINS contains an invalid origin.');
216
+ }
217
+ return url.origin;
218
+ }));
219
+ }
220
+ function assertCredentialDestination(baseUrl, url, allowExternalOrigin = false, trustedServiceOrigins = new Set()) {
221
+ const base = new URL(baseUrl);
222
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) {
223
+ throw new Error('Refusing to send API credentials to an invalid URL.');
224
+ }
225
+ if (url.origin === base.origin) {
226
+ return;
227
+ }
228
+ if (!allowExternalOrigin) {
229
+ throw new Error('Refusing to send API credentials to an external origin.');
230
+ }
231
+ if (url.protocol !== 'https:' && !(isLocalhost(base.hostname) && isLocalhost(url.hostname))) {
232
+ throw new Error('External service endpoints must use HTTPS.');
233
+ }
234
+ if (isLocalhost(base.hostname) && isLocalhost(url.hostname)) {
235
+ return;
236
+ }
237
+ if (!isMatchingFirstPartySandbox(base, url) && !trustedServiceOrigins.has(url.origin)) {
238
+ throw new Error('External service origin is not trusted. Set REVO_TRUSTED_SERVICE_ORIGINS explicitly.');
239
+ }
240
+ }
241
+ function assertNoAuthenticatedRedirect(response) {
242
+ if (response.status >= 300 && response.status < 400 && response.status !== 304) {
243
+ throw new Error('Authenticated request was redirected; refusing to forward API credentials.');
244
+ }
245
+ }
170
246
  function normalizeToken(token) {
171
247
  if (!token) {
172
248
  return '';
173
249
  }
174
250
  return token.startsWith('Bearer ') ? token : `Bearer ${token}`;
175
251
  }
252
+ async function confirmProductionWriteInTerminal(details) {
253
+ if (!isInteractiveTerminal()) {
254
+ return false;
255
+ }
256
+ return promptConfirm(`PRODUCTION: send ${details.method} ${details.path} to ${details.baseUrl}?`, false);
257
+ }
176
258
  function defaultSpinnerLabel(method, path) {
177
259
  const upperMethod = method.toUpperCase();
178
260
  if (path === '/api/v1/me') {
@@ -316,13 +398,20 @@ export class RevoClient {
316
398
  baseUrl;
317
399
  instance;
318
400
  token;
401
+ production;
319
402
  fetchImpl;
403
+ productionWriteConfirmed = false;
404
+ confirmProductionWrite;
405
+ trustedServiceOrigins;
320
406
  constructor(options = {}) {
321
407
  const config = resolveRuntimeConfig(options);
322
408
  this.baseUrl = config.baseUrl;
323
409
  this.instance = config.instance || '';
324
410
  this.token = config.token;
411
+ this.production = options.production === true || config.production === true;
325
412
  this.fetchImpl = options.fetch || globalThis.fetch;
413
+ this.confirmProductionWrite = options.confirmProductionWrite || confirmProductionWriteInTerminal;
414
+ this.trustedServiceOrigins = resolveTrustedServiceOrigins(options.trustedServiceOrigins);
326
415
  }
327
416
  get authHeader() {
328
417
  return normalizeToken(this.token);
@@ -391,10 +480,14 @@ export class RevoClient {
391
480
  }
392
481
  async request(method, path, options = {}) {
393
482
  this.assertReady();
483
+ if (options.productionWrite !== false) {
484
+ await this.assertProductionWriteConfirmed(method, path);
485
+ }
394
486
  if (options.authGuard !== false && path !== '/api/v1/me') {
395
487
  await this.validateSession();
396
488
  }
397
489
  const url = buildUrl(this.baseUrl, path, options.query);
490
+ assertCredentialDestination(this.baseUrl, url, options.allowExternalOrigin, this.trustedServiceOrigins);
398
491
  const headers = new Headers(options.headers || {});
399
492
  headers.set('Authorization', this.authHeader);
400
493
  headers.set('x-api-key', this.token);
@@ -413,6 +506,10 @@ export class RevoClient {
413
506
  const spinner = createSpinner({
414
507
  text: options.spinnerLabel || defaultSpinnerLabel(method, path),
415
508
  });
509
+ const abortController = options.timeoutMs === undefined ? undefined : new AbortController();
510
+ const timeout = abortController
511
+ ? setTimeout(() => abortController.abort(), options.timeoutMs)
512
+ : undefined;
416
513
  spinner.start();
417
514
  try {
418
515
  await delay(REQUEST_DELAY_MS);
@@ -420,7 +517,10 @@ export class RevoClient {
420
517
  method: method.toUpperCase(),
421
518
  headers,
422
519
  body: body,
520
+ redirect: 'manual',
521
+ ...(abortController ? { signal: abortController.signal } : {}),
423
522
  });
523
+ assertNoAuthenticatedRedirect(response);
424
524
  const data = await readResponseData(response);
425
525
  if (!response.ok && response.status !== 304) {
426
526
  if (response.status === 401) {
@@ -429,12 +529,12 @@ export class RevoClient {
429
529
  status: 'not_authenticated',
430
530
  checkedAt: Date.now(),
431
531
  });
432
- throw new AuthenticationError(401, 'Not authenticated. Run `revo auth login`.', data);
532
+ throw new AuthenticationError(401, 'Not authenticated. Run `revo auth login`.', data, response.headers);
433
533
  }
434
534
  if (response.status === 403 && path !== '/api/v1/me') {
435
- throw new PermissionDeniedError(path, `Access denied for ${path}.`, data);
535
+ throw new PermissionDeniedError(path, `Access denied for ${path}.`, data, response.headers);
436
536
  }
437
- throw new ApiError(response.status, toErrorMessage(typeof data === 'string' ? data : JSON.stringify(data), response.status), data);
537
+ throw new ApiError(response.status, toErrorMessage(typeof data === 'string' ? data : JSON.stringify(data), response.status), data, response.headers);
438
538
  }
439
539
  const previous = readAuthValidationState(this.authValidationKey);
440
540
  saveAuthValidationState({
@@ -451,9 +551,27 @@ export class RevoClient {
451
551
  };
452
552
  }
453
553
  finally {
554
+ if (timeout) {
555
+ clearTimeout(timeout);
556
+ }
454
557
  spinner.stop();
455
558
  }
456
559
  }
560
+ async assertProductionWriteConfirmed(method, path) {
561
+ const normalizedMethod = method.toUpperCase();
562
+ if (!this.production || this.productionWriteConfirmed || !['POST', 'PUT', 'PATCH', 'DELETE'].includes(normalizedMethod)) {
563
+ return;
564
+ }
565
+ const confirmed = await this.confirmProductionWrite({
566
+ baseUrl: this.baseUrl,
567
+ method: normalizedMethod,
568
+ path,
569
+ });
570
+ if (!confirmed) {
571
+ throw new Error('Production write cancelled. Confirm the operation in an interactive terminal.');
572
+ }
573
+ this.productionWriteConfirmed = true;
574
+ }
457
575
  async requestData(method, path, options = {}) {
458
576
  const response = await this.request(method, path, options);
459
577
  return response.data;
@@ -465,24 +583,356 @@ export class RevoClient {
465
583
  }
466
584
  return this.requestData('GET', '/api/v1/me', { authGuard: false });
467
585
  }
468
- async listEndpoints() {
469
- return this.requestData('GET', '/api/v1/endpoints');
586
+ async listEndpoints(options = {}) {
587
+ return this.requestData('GET', options.path || '/api/v1/endpoints', {
588
+ query: options.query,
589
+ });
590
+ }
591
+ async listDatabaseSchemas(options = {}) {
592
+ return this.requestData('GET', options.path || '/api/v1/databases', {
593
+ query: options.query,
594
+ });
595
+ }
596
+ async listAllDatabaseSchemas(options = {}) {
597
+ const databases = [];
598
+ const databaseIds = new Set();
599
+ const seenRequests = new Set();
600
+ const fields = options.projection === 'pull'
601
+ ? DATABASE_SCHEMA_PULL_FIELDS
602
+ : DATABASE_SCHEMA_THIN_FIELDS;
603
+ const filter = JSON.stringify({
604
+ and: [
605
+ { field: 'deletedAt', op: 'isNull' },
606
+ ...(options.skipPartitions ? [{ field: 'parent', op: 'isNull' }] : []),
607
+ ],
608
+ });
609
+ let nextRequest = {
610
+ query: {
611
+ take: DATABASE_SCHEMA_LIST_PAGE_SIZE,
612
+ skip: 0,
613
+ count: true,
614
+ fields,
615
+ sort: JSON.stringify({ databaseId: 'ASC' }),
616
+ filter,
617
+ },
618
+ };
619
+ let discoveredTotal = null;
620
+ while (nextRequest) {
621
+ const requestKey = JSON.stringify(nextRequest);
622
+ if (seenRequests.has(requestKey)) {
623
+ throw new Error('Database-schema list pagination loop detected while listing all databases.');
624
+ }
625
+ seenRequests.add(requestKey);
626
+ const pageValue = await this.listDatabaseSchemas(nextRequest);
627
+ const page = unwrapListPage(pageValue);
628
+ if (page.total !== null && !options.skipPartitions) {
629
+ if (discoveredTotal !== null && discoveredTotal !== page.total) {
630
+ throw new Error('Database-schema inventory changed during offset pagination; retry the command from a stable snapshot.');
631
+ }
632
+ discoveredTotal = page.total;
633
+ }
634
+ for (const item of page.items) {
635
+ if (isRecord(item)) {
636
+ const database = item;
637
+ if (!database.deletedAt && !database.deletedBy && !(options.skipPartitions && hasDatabaseSchemaParent(database))) {
638
+ const databaseId = String(database.databaseId || database.id || '');
639
+ if (!databaseId || databaseIds.has(databaseId)) {
640
+ continue;
641
+ }
642
+ databaseIds.add(databaseId);
643
+ databases.push(database);
644
+ }
645
+ }
646
+ }
647
+ if (page.total !== null && (Number(nextRequest.query?.skip || 0) + page.items.length) >= page.total) {
648
+ nextRequest = null;
649
+ }
650
+ else if (page.nextRequest) {
651
+ nextRequest = {
652
+ ...page.nextRequest,
653
+ query: { ...page.nextRequest.query, fields, sort: JSON.stringify({ databaseId: 'ASC' }), filter },
654
+ };
655
+ }
656
+ else if (nextRequest.query && page.items.length === DATABASE_SCHEMA_LIST_PAGE_SIZE) {
657
+ nextRequest = {
658
+ query: {
659
+ ...nextRequest.query,
660
+ skip: Number(nextRequest.query.skip || 0) + page.items.length,
661
+ count: false,
662
+ },
663
+ };
664
+ }
665
+ else {
666
+ nextRequest = null;
667
+ }
668
+ }
669
+ if (!options.skipPartitions && discoveredTotal !== null && databases.length !== discoveredTotal) {
670
+ throw new Error('Database-schema inventory changed during offset pagination; retry the command from a stable snapshot.');
671
+ }
672
+ return { databases, discoveredTotal };
673
+ }
674
+ async findActiveDatabaseSchemasByField(field, value, path) {
675
+ const databases = [];
676
+ const databaseIds = new Set();
677
+ const filter = JSON.stringify({
678
+ and: [
679
+ { field: 'deletedAt', op: 'isNull' },
680
+ { field, op: 'eq', value, ...(path ? { path } : {}) },
681
+ ],
682
+ });
683
+ let skip = 0;
684
+ let discoveredTotal = null;
685
+ do {
686
+ const page = unwrapListPage(await this.listDatabaseSchemas({
687
+ query: {
688
+ take: DATABASE_SCHEMA_LIST_PAGE_SIZE,
689
+ skip,
690
+ count: true,
691
+ fields: DATABASE_SCHEMA_PULL_FIELDS,
692
+ sort: JSON.stringify({ databaseId: 'ASC' }),
693
+ filter,
694
+ },
695
+ }));
696
+ if (page.total !== null) {
697
+ if (discoveredTotal !== null && discoveredTotal !== page.total) {
698
+ throw new Error('Database-schema filtered inventory changed during offset pagination; retry the command from a stable snapshot.');
699
+ }
700
+ discoveredTotal = page.total;
701
+ }
702
+ for (const item of page.items) {
703
+ if (!isRecord(item) || item.deletedAt || item.deletedBy) {
704
+ continue;
705
+ }
706
+ const database = item;
707
+ const databaseId = String(database.databaseId || database.id || '');
708
+ if (!databaseId || databaseIds.has(databaseId)) {
709
+ continue;
710
+ }
711
+ databaseIds.add(databaseId);
712
+ databases.push(database);
713
+ }
714
+ skip += page.items.length;
715
+ if (page.items.length === 0) {
716
+ break;
717
+ }
718
+ if (discoveredTotal !== null && skip >= discoveredTotal) {
719
+ break;
720
+ }
721
+ if (discoveredTotal === null && page.items.length < DATABASE_SCHEMA_LIST_PAGE_SIZE) {
722
+ break;
723
+ }
724
+ } while (true);
725
+ if (discoveredTotal !== null && databases.length !== discoveredTotal) {
726
+ throw new Error('Database-schema filtered inventory changed during offset pagination; retry the command from a stable snapshot.');
727
+ }
728
+ return databases;
729
+ }
730
+ async getDatabaseSchemaStats() {
731
+ return this.requestData('GET', '/api/v1/databases/stats');
732
+ }
733
+ async getDatabaseSchema(databaseId) {
734
+ return this.requestData('GET', `/api/v1/databases/${encodeURIComponent(databaseId)}`);
735
+ }
736
+ async createDatabaseSchema(body) {
737
+ return this.requestData('POST', '/api/v1/databases', {
738
+ body,
739
+ spinnerLabel: 'Creating database schema',
740
+ });
741
+ }
742
+ async updateDatabaseSchemaMetadata(databaseId, body) {
743
+ return this.requestData('PUT', `/api/v1/databases/${encodeURIComponent(databaseId)}`, {
744
+ body,
745
+ spinnerLabel: 'Updating database schema metadata',
746
+ });
747
+ }
748
+ async updateDatabaseSchema(databaseId, body) {
749
+ return this.requestData('PUT', `/api/v1/databases/${encodeURIComponent(databaseId)}`, {
750
+ body,
751
+ spinnerLabel: 'Updating database schema',
752
+ });
753
+ }
754
+ async deleteDatabaseSchema(databaseId) {
755
+ return this.requestData('DELETE', `/api/v1/databases/${encodeURIComponent(databaseId)}`, {
756
+ spinnerLabel: 'Deleting database schema',
757
+ });
758
+ }
759
+ async listAllDatabaseViews() {
760
+ const views = [];
761
+ const databaseViewIds = new Set();
762
+ let discoveredTotal = null;
763
+ for (let skip = 0;; skip += DATABASE_SCHEMA_LIST_PAGE_SIZE) {
764
+ const page = await this.requestData('GET', '/api/v1/databases/views', {
765
+ query: {
766
+ take: DATABASE_SCHEMA_LIST_PAGE_SIZE,
767
+ skip,
768
+ fields: JSON.stringify(['databaseViewId', 'deletedAt', 'sourceDatabaseIds']),
769
+ sort: JSON.stringify({ databaseViewId: 'ASC' }),
770
+ },
771
+ });
772
+ const items = Array.isArray(page) ? page : Array.isArray(page.data) ? page.data : [];
773
+ for (const item of items) {
774
+ const databaseViewId = String(item.databaseViewId || item.id || '');
775
+ if (!databaseViewId || databaseViewIds.has(databaseViewId)) {
776
+ throw new Error('Database View inventory changed during offset pagination; retry the command from a stable snapshot.');
777
+ }
778
+ databaseViewIds.add(databaseViewId);
779
+ views.push(item);
780
+ }
781
+ const hasExplicitNext = !Array.isArray(page) && typeof page.next === 'boolean';
782
+ const pageTotal = !Array.isArray(page)
783
+ ? typeof page.results === 'number' ? page.results : page.total
784
+ : undefined;
785
+ if (typeof pageTotal === 'number') {
786
+ if (discoveredTotal !== null && discoveredTotal !== pageTotal) {
787
+ throw new Error('Database View inventory changed during offset pagination; retry the command from a stable snapshot.');
788
+ }
789
+ discoveredTotal = pageTotal;
790
+ }
791
+ if (items.length === 0
792
+ || (hasExplicitNext ? page.next !== true : items.length < DATABASE_SCHEMA_LIST_PAGE_SIZE)
793
+ || (typeof pageTotal === 'number' && views.length >= pageTotal)) {
794
+ if (discoveredTotal !== null && views.length !== discoveredTotal) {
795
+ throw new Error('Database View inventory changed during offset pagination; retry the command from a stable snapshot.');
796
+ }
797
+ return views;
798
+ }
799
+ }
800
+ }
801
+ async getDatabaseView(databaseViewId) {
802
+ return this.requestData('GET', `/api/v1/databases/views/${encodeURIComponent(databaseViewId)}`);
803
+ }
804
+ async listAllDatabaseViewsForSync() {
805
+ const summaries = await this.listAllDatabaseViews();
806
+ const activeSummaries = summaries.filter((view) => !view.deletedAt && !view.deletedBy);
807
+ const seenIds = new Set();
808
+ const records = await Promise.all(activeSummaries.map(async (summary) => {
809
+ const databaseViewId = String(summary.databaseViewId || summary.id || '');
810
+ if (!databaseViewId || seenIds.has(databaseViewId)) {
811
+ return undefined;
812
+ }
813
+ seenIds.add(databaseViewId);
814
+ const response = await this.getDatabaseView(databaseViewId);
815
+ if (isRecord(response) && isRecord(response.data)) {
816
+ return response.data;
817
+ }
818
+ return response;
819
+ }));
820
+ return records
821
+ .filter((record) => record !== undefined && !record.deletedAt && !record.deletedBy)
822
+ .sort((left, right) => String(left.databaseViewId || left.id || '').localeCompare(String(right.databaseViewId || right.id || '')));
823
+ }
824
+ async previewDatabaseView(body) {
825
+ return this.requestData('POST', '/api/v1/databases/views/preview', {
826
+ body,
827
+ productionWrite: false,
828
+ spinnerLabel: 'Previewing Database View',
829
+ timeoutMs: 30_000,
830
+ });
831
+ }
832
+ async createDatabaseView(body) {
833
+ return this.requestData('POST', '/api/v1/databases/views', {
834
+ body,
835
+ spinnerLabel: 'Creating Database View',
836
+ });
837
+ }
838
+ async updateDatabaseView(databaseViewId, body) {
839
+ return this.requestData('PUT', `/api/v1/databases/views/${encodeURIComponent(databaseViewId)}`, {
840
+ body,
841
+ spinnerLabel: 'Updating Database View',
842
+ });
843
+ }
844
+ async listAllEndpoints() {
845
+ const endpoints = [];
846
+ const seenRequests = new Set();
847
+ let nextRequest = {
848
+ query: { take: COMPONENT_LIST_PAGE_SIZE, skip: 0, count: true },
849
+ };
850
+ let discoveredTotal = null;
851
+ while (nextRequest) {
852
+ const requestKey = JSON.stringify(nextRequest);
853
+ if (seenRequests.has(requestKey)) {
854
+ throw new Error('Endpoint list pagination loop detected while listing all endpoints.');
855
+ }
856
+ seenRequests.add(requestKey);
857
+ const pageValue = await this.listEndpoints(nextRequest);
858
+ const page = unwrapListPage(pageValue);
859
+ if (page.total !== null) {
860
+ discoveredTotal = page.total;
861
+ }
862
+ for (const item of page.items) {
863
+ if (isRecord(item)) {
864
+ endpoints.push(item);
865
+ }
866
+ }
867
+ if (page.total !== null && endpoints.length >= page.total) {
868
+ nextRequest = null;
869
+ }
870
+ else if (page.nextRequest) {
871
+ nextRequest = page.nextRequest;
872
+ }
873
+ else if (nextRequest.query && typeof nextRequest.query.take === 'number' && page.items.length === nextRequest.query.take) {
874
+ nextRequest = {
875
+ query: { take: nextRequest.query.take, skip: endpoints.length, count: true },
876
+ };
877
+ }
878
+ else {
879
+ nextRequest = null;
880
+ }
881
+ }
882
+ return { endpoints, discoveredTotal };
883
+ }
884
+ async getEndpoint(endpointId) {
885
+ return this.requestData('GET', `/api/v1/endpoints/${encodeURIComponent(endpointId)}`);
886
+ }
887
+ async createEndpoint(body) {
888
+ return this.requestData('POST', '/api/v1/endpoints', {
889
+ body,
890
+ spinnerLabel: 'Creating endpoint',
891
+ });
892
+ }
893
+ async updateEndpoint(endpointId, body) {
894
+ return this.requestData('PUT', `/api/v1/endpoints/${encodeURIComponent(endpointId)}`, {
895
+ body,
896
+ spinnerLabel: 'Updating endpoint',
897
+ });
898
+ }
899
+ async activateEndpoint(endpointId) {
900
+ await this.requestData('POST', `/api/v1/endpoints/${encodeURIComponent(endpointId)}/activate`, {
901
+ spinnerLabel: 'Activating endpoint',
902
+ });
903
+ }
904
+ async disableEndpoint(endpointId) {
905
+ await this.requestData('POST', `/api/v1/endpoints/${encodeURIComponent(endpointId)}/disable`, {
906
+ spinnerLabel: 'Disabling endpoint',
907
+ });
908
+ }
909
+ async restoreEndpoint(endpointId) {
910
+ await this.requestData('POST', `/api/v1/endpoints/${encodeURIComponent(endpointId)}/restore`, {
911
+ spinnerLabel: 'Restoring endpoint',
912
+ });
913
+ }
914
+ async getComponentSnapshotVersion(componentId, version) {
915
+ return this.requestData('GET', `/api/v1/activity/${encodeURIComponent(componentId)}/${encodeURIComponent(String(version))}`, { query: { refType: 'COMPONENTS' } });
470
916
  }
471
917
  async getEditorTypes(requestPath = '/v1/editor/types') {
472
918
  return this.requestData('GET', requestPath, {
473
919
  spinnerLabel: 'Loading editor types',
920
+ allowExternalOrigin: true,
474
921
  });
475
922
  }
476
923
  async debugComponent(requestPath, body) {
477
924
  return this.requestData('POST', requestPath, {
478
925
  body,
479
926
  spinnerLabel: 'Debugging component',
927
+ allowExternalOrigin: true,
480
928
  });
481
929
  }
482
930
  async *debugComponentStream(requestPath, body) {
483
931
  this.assertReady();
932
+ await this.assertProductionWriteConfirmed('POST', requestPath);
484
933
  await this.validateSession();
485
934
  const url = buildUrl(this.baseUrl, requestPath);
935
+ assertCredentialDestination(this.baseUrl, url, true, this.trustedServiceOrigins);
486
936
  const headers = new Headers();
487
937
  headers.set('Accept', 'text/event-stream');
488
938
  headers.set('Content-Type', 'application/json');
@@ -496,7 +946,9 @@ export class RevoClient {
496
946
  method: 'POST',
497
947
  headers,
498
948
  body: JSON.stringify(body),
949
+ redirect: 'manual',
499
950
  });
951
+ assertNoAuthenticatedRedirect(response);
500
952
  if (!response.ok) {
501
953
  const data = await readResponseData(response);
502
954
  if (response.status === 401) {
@@ -505,12 +957,12 @@ export class RevoClient {
505
957
  status: 'not_authenticated',
506
958
  checkedAt: Date.now(),
507
959
  });
508
- throw new AuthenticationError(401, 'Not authenticated. Run `revo auth login`.', data);
960
+ throw new AuthenticationError(401, 'Not authenticated. Run `revo auth login`.', data, response.headers);
509
961
  }
510
962
  if (response.status === 403) {
511
- throw new PermissionDeniedError(requestPath, `Access denied for ${requestPath}.`, data);
963
+ throw new PermissionDeniedError(requestPath, `Access denied for ${requestPath}.`, data, response.headers);
512
964
  }
513
- throw new ApiError(response.status, toErrorMessage(typeof data === 'string' ? data : JSON.stringify(data), response.status), data);
965
+ throw new ApiError(response.status, toErrorMessage(typeof data === 'string' ? data : JSON.stringify(data), response.status), data, response.headers);
514
966
  }
515
967
  for await (const event of readSseEvents(response)) {
516
968
  yield event;
@@ -538,7 +990,7 @@ export class RevoClient {
538
990
  }
539
991
  seenRequests.add(requestKey);
540
992
  const pageValue = await this.listComponents(nextRequest);
541
- const page = unwrapComponentListPage(pageValue);
993
+ const page = unwrapListPage(pageValue);
542
994
  if (page.total !== null) {
543
995
  discoveredTotal = page.total;
544
996
  }
@@ -587,6 +1039,338 @@ export class RevoClient {
587
1039
  spinnerLabel: 'Deleting component',
588
1040
  });
589
1041
  }
1042
+ async listRoleGroups(options = {}) {
1043
+ return this.requestData('GET', options.path || '/api/v1/role-groups', {
1044
+ query: options.query,
1045
+ });
1046
+ }
1047
+ async listAllRoleGroups() {
1048
+ const roleGroups = [];
1049
+ const seenRequests = new Set();
1050
+ let nextRequest = {
1051
+ query: { take: COMPONENT_LIST_PAGE_SIZE, skip: 0, count: true },
1052
+ };
1053
+ let discoveredTotal = null;
1054
+ while (nextRequest) {
1055
+ const requestKey = JSON.stringify(nextRequest);
1056
+ if (seenRequests.has(requestKey)) {
1057
+ throw new Error('Role-group list pagination loop detected while listing all role groups.');
1058
+ }
1059
+ seenRequests.add(requestKey);
1060
+ const pageValue = await this.listRoleGroups(nextRequest);
1061
+ const page = unwrapListPage(pageValue);
1062
+ if (page.total !== null) {
1063
+ discoveredTotal = page.total;
1064
+ }
1065
+ for (const item of page.items) {
1066
+ if (isRecord(item)) {
1067
+ roleGroups.push(item);
1068
+ }
1069
+ }
1070
+ if (page.total !== null && roleGroups.length >= page.total) {
1071
+ nextRequest = null;
1072
+ }
1073
+ else if (page.nextRequest) {
1074
+ nextRequest = page.nextRequest;
1075
+ }
1076
+ else if (nextRequest.query && typeof nextRequest.query.take === 'number' && page.items.length === nextRequest.query.take) {
1077
+ nextRequest = { query: { ...nextRequest.query, skip: roleGroups.length } };
1078
+ }
1079
+ else {
1080
+ nextRequest = null;
1081
+ }
1082
+ }
1083
+ return { roleGroups, discoveredTotal };
1084
+ }
1085
+ async getRoleGroup(roleGroupId) {
1086
+ return this.requestData('GET', `/api/v1/role-groups/${roleGroupId}`);
1087
+ }
1088
+ async listGroups(options = {}) {
1089
+ return this.requestData('GET', options.path || '/api/v1/groups', {
1090
+ query: options.query,
1091
+ });
1092
+ }
1093
+ async listAllGroups() {
1094
+ const groups = [];
1095
+ const seenRequests = new Set();
1096
+ let nextRequest = {
1097
+ query: { take: COMPONENT_LIST_PAGE_SIZE, skip: 0, count: true },
1098
+ };
1099
+ let discoveredTotal = null;
1100
+ while (nextRequest) {
1101
+ const requestKey = JSON.stringify(nextRequest);
1102
+ if (seenRequests.has(requestKey)) {
1103
+ throw new Error('Group list pagination loop detected while listing all groups.');
1104
+ }
1105
+ seenRequests.add(requestKey);
1106
+ const pageValue = await this.listGroups(nextRequest);
1107
+ const page = unwrapListPage(pageValue);
1108
+ if (page.total !== null) {
1109
+ discoveredTotal = page.total;
1110
+ }
1111
+ for (const item of page.items) {
1112
+ if (isRecord(item)) {
1113
+ groups.push(item);
1114
+ }
1115
+ }
1116
+ if (page.total !== null && groups.length >= page.total) {
1117
+ nextRequest = null;
1118
+ }
1119
+ else if (page.nextRequest) {
1120
+ nextRequest = page.nextRequest;
1121
+ }
1122
+ else if (nextRequest.query && typeof nextRequest.query.take === 'number' && page.items.length === nextRequest.query.take) {
1123
+ nextRequest = { query: { ...nextRequest.query, skip: groups.length } };
1124
+ }
1125
+ else {
1126
+ nextRequest = null;
1127
+ }
1128
+ }
1129
+ return { groups, discoveredTotal };
1130
+ }
1131
+ async getGroup(groupId) {
1132
+ return this.requestData('GET', `/api/v1/groups/${groupId}`);
1133
+ }
1134
+ async listJobTemplates(options = {}) {
1135
+ return this.requestData('GET', options.path || '/api/v1/automation/template', {
1136
+ query: options.query,
1137
+ });
1138
+ }
1139
+ async listAllJobTemplates() {
1140
+ const jobTemplates = [];
1141
+ const seenRequests = new Set();
1142
+ let nextRequest = { query: buildActiveComponentListQuery(0) };
1143
+ let discoveredTotal = null;
1144
+ while (nextRequest) {
1145
+ const requestKey = JSON.stringify(nextRequest);
1146
+ if (seenRequests.has(requestKey)) {
1147
+ throw new Error('Job-template list pagination loop detected while listing all job templates.');
1148
+ }
1149
+ seenRequests.add(requestKey);
1150
+ const pageValue = await this.listJobTemplates(nextRequest);
1151
+ const page = unwrapListPage(pageValue);
1152
+ if (page.total !== null) {
1153
+ discoveredTotal = page.total;
1154
+ }
1155
+ for (const item of page.items) {
1156
+ if (isRecord(item)) {
1157
+ jobTemplates.push(item);
1158
+ }
1159
+ }
1160
+ if (page.total !== null && jobTemplates.length >= page.total) {
1161
+ nextRequest = null;
1162
+ }
1163
+ else if (page.nextRequest) {
1164
+ nextRequest = withActiveComponentListFilter(page.nextRequest);
1165
+ }
1166
+ else if (nextRequest.query && typeof nextRequest.query.take === 'number' && page.items.length === nextRequest.query.take) {
1167
+ nextRequest = { query: buildActiveComponentListQuery(jobTemplates.length, nextRequest.query.take) };
1168
+ }
1169
+ else {
1170
+ nextRequest = null;
1171
+ }
1172
+ }
1173
+ return { jobTemplates, discoveredTotal };
1174
+ }
1175
+ async getJobTemplate(jobTemplateId) {
1176
+ return this.requestData('GET', `/api/v1/automation/template/${jobTemplateId}`);
1177
+ }
1178
+ async createJobTemplate(body) {
1179
+ return this.requestData('POST', '/api/v1/automation/template', {
1180
+ body,
1181
+ spinnerLabel: 'Creating job template',
1182
+ });
1183
+ }
1184
+ async updateJobTemplate(jobTemplateId, body) {
1185
+ return this.requestData('PUT', `/api/v1/automation/template/${jobTemplateId}`, {
1186
+ body,
1187
+ spinnerLabel: 'Updating job template',
1188
+ });
1189
+ }
1190
+ async listSchedules(options = {}) {
1191
+ return this.requestData('GET', options.path || '/api/v1/automation/schedule', {
1192
+ query: options.query,
1193
+ });
1194
+ }
1195
+ async listAllSchedules() {
1196
+ const schedules = [];
1197
+ const seenRequests = new Set();
1198
+ let nextRequest = { query: buildActiveComponentListQuery(0) };
1199
+ let discoveredTotal = null;
1200
+ while (nextRequest) {
1201
+ const requestKey = JSON.stringify(nextRequest);
1202
+ if (seenRequests.has(requestKey)) {
1203
+ throw new Error('Schedule list pagination loop detected while listing all schedules.');
1204
+ }
1205
+ seenRequests.add(requestKey);
1206
+ const pageValue = await this.listSchedules(nextRequest);
1207
+ const page = unwrapListPage(pageValue);
1208
+ if (page.total !== null) {
1209
+ discoveredTotal = page.total;
1210
+ }
1211
+ for (const item of page.items) {
1212
+ if (isRecord(item)) {
1213
+ schedules.push(item);
1214
+ }
1215
+ }
1216
+ if (page.total !== null && schedules.length >= page.total) {
1217
+ nextRequest = null;
1218
+ }
1219
+ else if (page.nextRequest) {
1220
+ nextRequest = withActiveComponentListFilter(page.nextRequest);
1221
+ }
1222
+ else if (nextRequest.query && typeof nextRequest.query.take === 'number' && page.items.length === nextRequest.query.take) {
1223
+ nextRequest = { query: buildActiveComponentListQuery(schedules.length, nextRequest.query.take) };
1224
+ }
1225
+ else {
1226
+ nextRequest = null;
1227
+ }
1228
+ }
1229
+ return { schedules, discoveredTotal };
1230
+ }
1231
+ async getSchedule(scheduleId) {
1232
+ return this.requestData('GET', `/api/v1/automation/schedule/${scheduleId}`);
1233
+ }
1234
+ async createSchedule(body) {
1235
+ return this.requestData('POST', '/api/v1/automation/schedule', {
1236
+ body,
1237
+ spinnerLabel: 'Creating schedule',
1238
+ });
1239
+ }
1240
+ async updateSchedule(scheduleId, body) {
1241
+ return this.requestData('PUT', `/api/v1/automation/schedule/${scheduleId}`, {
1242
+ body,
1243
+ spinnerLabel: 'Updating schedule',
1244
+ });
1245
+ }
1246
+ async activateSchedule(scheduleId) {
1247
+ return this.requestData('POST', `/api/v1/automation/schedule/${scheduleId}/activate`, {
1248
+ spinnerLabel: 'Activating schedule',
1249
+ });
1250
+ }
1251
+ async listEvents(options = {}) {
1252
+ return this.requestData('GET', options.path || '/api/v1/automation/event', {
1253
+ query: options.query,
1254
+ });
1255
+ }
1256
+ async listAllEvents({ includeDeleted = false } = {}) {
1257
+ const events = [];
1258
+ const seenRequests = new Set();
1259
+ let nextRequest = {
1260
+ query: includeDeleted
1261
+ ? { take: COMPONENT_LIST_PAGE_SIZE, skip: 0, count: true }
1262
+ : buildActiveComponentListQuery(0),
1263
+ };
1264
+ let discoveredTotal = null;
1265
+ while (nextRequest) {
1266
+ const requestKey = JSON.stringify(nextRequest);
1267
+ if (seenRequests.has(requestKey)) {
1268
+ throw new Error('Event list pagination loop detected while listing all events.');
1269
+ }
1270
+ seenRequests.add(requestKey);
1271
+ const pageValue = await this.listEvents(nextRequest);
1272
+ const page = unwrapListPage(pageValue);
1273
+ if (page.total !== null) {
1274
+ discoveredTotal = page.total;
1275
+ }
1276
+ for (const item of page.items) {
1277
+ if (isRecord(item)) {
1278
+ events.push(item);
1279
+ }
1280
+ }
1281
+ if (page.total !== null && events.length >= page.total) {
1282
+ nextRequest = null;
1283
+ }
1284
+ else if (page.nextRequest) {
1285
+ nextRequest = includeDeleted
1286
+ ? page.nextRequest
1287
+ : withActiveComponentListFilter(page.nextRequest);
1288
+ }
1289
+ else if (nextRequest.query && typeof nextRequest.query.take === 'number' && page.items.length === nextRequest.query.take) {
1290
+ nextRequest = {
1291
+ query: includeDeleted
1292
+ ? { take: nextRequest.query.take, skip: events.length, count: true }
1293
+ : buildActiveComponentListQuery(events.length, nextRequest.query.take),
1294
+ };
1295
+ }
1296
+ else {
1297
+ nextRequest = null;
1298
+ }
1299
+ }
1300
+ return { events, discoveredTotal };
1301
+ }
1302
+ async getEvent(eventId) {
1303
+ return this.requestData('GET', `/api/v1/automation/event/${eventId}`);
1304
+ }
1305
+ async createEvent(body) {
1306
+ return this.requestData('POST', '/api/v1/automation/event', {
1307
+ body,
1308
+ spinnerLabel: 'Creating event',
1309
+ });
1310
+ }
1311
+ async updateEvent(eventId, body) {
1312
+ return this.requestData('PUT', `/api/v1/automation/event/${eventId}`, {
1313
+ body,
1314
+ spinnerLabel: 'Updating event',
1315
+ });
1316
+ }
1317
+ async activateEvent(eventId) {
1318
+ return this.requestData('POST', `/api/v1/automation/event/${eventId}/activate`, {
1319
+ spinnerLabel: 'Activating event',
1320
+ });
1321
+ }
1322
+ async disableEvent(eventId) {
1323
+ return this.requestData('POST', `/api/v1/automation/event/${eventId}/disable`, {
1324
+ spinnerLabel: 'Disabling event',
1325
+ });
1326
+ }
1327
+ async createRoleGroup(body) {
1328
+ return this.requestData('POST', '/api/v1/role-groups', {
1329
+ body,
1330
+ spinnerLabel: 'Creating role group',
1331
+ });
1332
+ }
1333
+ async updateRoleGroup(roleGroupId, body) {
1334
+ return this.requestData('PUT', `/api/v1/role-groups/${roleGroupId}`, {
1335
+ body,
1336
+ spinnerLabel: 'Updating role group',
1337
+ });
1338
+ }
1339
+ async updateGroup(groupId, body) {
1340
+ return this.requestData('PUT', `/api/v1/groups/${groupId}`, {
1341
+ body,
1342
+ spinnerLabel: 'Updating group',
1343
+ });
1344
+ }
1345
+ async createGroup(body) {
1346
+ return this.requestData('POST', '/api/v1/groups', {
1347
+ body,
1348
+ spinnerLabel: 'Creating group',
1349
+ });
1350
+ }
1351
+ async addRoleGroupRole(roleGroupId, roleKey) {
1352
+ return this.requestData('POST', `/api/v1/role-groups/${roleGroupId}/roleAdd`, {
1353
+ body: { key: roleKey },
1354
+ spinnerLabel: 'Adding role-group role',
1355
+ });
1356
+ }
1357
+ async removeRoleGroupRole(roleGroupId, roleKey) {
1358
+ return this.requestData('POST', `/api/v1/role-groups/${roleGroupId}/roleDelete`, {
1359
+ body: { key: roleKey },
1360
+ spinnerLabel: 'Removing role-group role',
1361
+ });
1362
+ }
1363
+ async deleteRoleGroup(roleGroupId) {
1364
+ return this.request('DELETE', `/api/v1/role-groups/${roleGroupId}`, {
1365
+ spinnerLabel: 'Deleting role group',
1366
+ });
1367
+ }
1368
+ async listSystemRoles() {
1369
+ return this.requestData('GET', '/api/v1/roles');
1370
+ }
1371
+ async listAllSystemRoles() {
1372
+ return unwrapList(await this.listSystemRoles()).filter(isRecord);
1373
+ }
590
1374
  async saveComponentElements(componentId, body) {
591
1375
  return this.request('POST', `/api/v1/component/${componentId}/save`, { body });
592
1376
  }