@revoengine/cli 1.0.9 → 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 (81) hide show
  1. package/README.md +464 -8
  2. package/dist/src/cli.js +65 -6
  3. package/dist/src/client.d.ts +366 -5
  4. package/dist/src/client.js +954 -13
  5. package/dist/src/commands/auth.js +4 -2
  6. package/dist/src/commands/component.js +839 -412
  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.d.ts +2 -0
  13. package/dist/src/commands/env.js +380 -0
  14. package/dist/src/commands/events.d.ts +2 -0
  15. package/dist/src/commands/events.js +146 -0
  16. package/dist/src/commands/groups.d.ts +2 -0
  17. package/dist/src/commands/groups.js +169 -0
  18. package/dist/src/commands/index.d.ts +10 -0
  19. package/dist/src/commands/index.js +10 -0
  20. package/dist/src/commands/job-templates.d.ts +2 -0
  21. package/dist/src/commands/job-templates.js +101 -0
  22. package/dist/src/commands/metadata.d.ts +2 -0
  23. package/dist/src/commands/metadata.js +159 -0
  24. package/dist/src/commands/project.js +82 -1
  25. package/dist/src/commands/role-groups.d.ts +2 -0
  26. package/dist/src/commands/role-groups.js +152 -0
  27. package/dist/src/commands/schedules.d.ts +2 -0
  28. package/dist/src/commands/schedules.js +141 -0
  29. package/dist/src/commands/terminal-service.d.ts +38 -0
  30. package/dist/src/commands/terminal-service.js +210 -0
  31. package/dist/src/commands/terminal.d.ts +22 -0
  32. package/dist/src/commands/terminal.js +511 -0
  33. package/dist/src/component-lock.d.ts +126 -2
  34. package/dist/src/component-lock.js +378 -15
  35. package/dist/src/config.d.ts +20 -0
  36. package/dist/src/config.js +121 -6
  37. package/dist/src/database-schema-artifacts.d.ts +7 -0
  38. package/dist/src/database-schema-artifacts.js +8 -0
  39. package/dist/src/env-sync.d.ts +83 -0
  40. package/dist/src/env-sync.js +315 -0
  41. package/dist/src/metadata-backfill.d.ts +56 -0
  42. package/dist/src/metadata-backfill.js +1176 -0
  43. package/dist/src/project.d.ts +17 -9
  44. package/dist/src/project.js +87 -11
  45. package/dist/src/prompt.js +10 -18
  46. package/dist/src/resource-metadata.d.ts +25 -0
  47. package/dist/src/resource-metadata.js +132 -0
  48. package/dist/src/resource-syncs/database-schema-sync.d.ts +117 -0
  49. package/dist/src/resource-syncs/database-schema-sync.js +2289 -0
  50. package/dist/src/resource-syncs/database-view-sync.d.ts +124 -0
  51. package/dist/src/resource-syncs/database-view-sync.js +1317 -0
  52. package/dist/src/resource-syncs/endpoint-sync.d.ts +96 -0
  53. package/dist/src/resource-syncs/endpoint-sync.js +1283 -0
  54. package/dist/src/resource-syncs/event-sync.d.ts +99 -0
  55. package/dist/src/resource-syncs/event-sync.js +949 -0
  56. package/dist/src/resource-syncs/group-sync.d.ts +86 -0
  57. package/dist/src/resource-syncs/group-sync.js +882 -0
  58. package/dist/src/resource-syncs/job-template-sync.d.ts +85 -0
  59. package/dist/src/resource-syncs/job-template-sync.js +782 -0
  60. package/dist/src/resource-syncs/role-group-sync.d.ts +83 -0
  61. package/dist/src/resource-syncs/role-group-sync.js +597 -0
  62. package/dist/src/resource-syncs/schedule-sync.d.ts +111 -0
  63. package/dist/src/resource-syncs/schedule-sync.js +1302 -0
  64. package/dist/src/resource-syncs/util.d.ts +19 -0
  65. package/dist/src/resource-syncs/util.js +116 -0
  66. package/dist/src/runtime-view.d.ts +1 -0
  67. package/dist/src/runtime-view.js +6 -1
  68. package/dist/src/sync-output.d.ts +38 -0
  69. package/dist/src/sync-output.js +131 -0
  70. package/dist/src/tracked-resources.d.ts +7 -0
  71. package/dist/src/tracked-resources.js +61 -0
  72. package/dist/src/types.d.ts +227 -0
  73. package/dist/src/ui.d.ts +3 -0
  74. package/dist/src/ui.js +68 -10
  75. package/dist/src/utils.d.ts +2 -0
  76. package/dist/src/utils.js +64 -0
  77. package/dist/src/workspace-component.d.ts +2 -0
  78. package/dist/src/workspace-component.js +34 -0
  79. package/dist/src/workspace-resource.d.ts +2 -0
  80. package/dist/src/workspace-resource.js +52 -0
  81. package/package.json +8 -3
@@ -1,30 +1,152 @@
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;
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(',');
42
+ const ACTIVE_COMPONENT_LIST_FILTER = {
43
+ 'filter[and][0][field]': 'deletedAt',
44
+ 'filter[and][0][op]': 'isNull',
45
+ };
46
+ function isRecord(value) {
47
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
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
+ }
59
+ function readNumber(value) {
60
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
61
+ }
62
+ function unwrapList(value) {
63
+ if (Array.isArray(value)) {
64
+ return value;
65
+ }
66
+ if (value && typeof value === 'object') {
67
+ const candidate = value;
68
+ if (Array.isArray(candidate.data)) {
69
+ return candidate.data;
70
+ }
71
+ if (Array.isArray(candidate.items)) {
72
+ return candidate.items;
73
+ }
74
+ if (Array.isArray(candidate.results)) {
75
+ return candidate.results;
76
+ }
77
+ }
78
+ return [];
79
+ }
80
+ function resolveNextListRequest(value) {
81
+ if (typeof value === 'string' && value) {
82
+ return {
83
+ path: value,
84
+ };
85
+ }
86
+ if (!isRecord(value)) {
87
+ return null;
88
+ }
89
+ for (const key of ['path', 'url', 'href']) {
90
+ if (typeof value[key] === 'string' && value[key]) {
91
+ return {
92
+ path: value[key],
93
+ };
94
+ }
95
+ }
96
+ const query = {};
97
+ for (const key of ['cursor', 'page', 'skip', 'take', 'limit', 'offset']) {
98
+ const candidate = value[key];
99
+ if (typeof candidate === 'string' || typeof candidate === 'number') {
100
+ query[key] = candidate;
101
+ }
102
+ }
103
+ return Object.keys(query).length > 0 ? { query } : null;
104
+ }
105
+ function buildActiveComponentListQuery(skip, take = COMPONENT_LIST_PAGE_SIZE) {
106
+ return {
107
+ take,
108
+ skip,
109
+ count: true,
110
+ ...ACTIVE_COMPONENT_LIST_FILTER,
111
+ };
112
+ }
113
+ function withActiveComponentListFilter(request) {
114
+ if (!request.query) {
115
+ return request;
116
+ }
117
+ return {
118
+ ...request,
119
+ query: {
120
+ ...request.query,
121
+ count: request.query.count ?? true,
122
+ ...ACTIVE_COMPONENT_LIST_FILTER,
123
+ },
124
+ };
125
+ }
126
+ function unwrapListPage(value) {
127
+ const items = unwrapList(value);
128
+ if (!isRecord(value)) {
129
+ return {
130
+ items,
131
+ total: null,
132
+ nextRequest: null,
133
+ };
134
+ }
135
+ const meta = isRecord(value.meta) ? value.meta : null;
136
+ const pagination = isRecord(value.pagination) ? value.pagination : null;
137
+ const total = readNumber(value.total)
138
+ ?? readNumber(value.count)
139
+ ?? (meta ? readNumber(meta.total) ?? readNumber(meta.count) : null)
140
+ ?? (pagination ? readNumber(pagination.total) ?? readNumber(pagination.count) : null);
141
+ const nextRequest = resolveNextListRequest(value.next)
142
+ ?? (meta ? resolveNextListRequest(meta.next) : null)
143
+ ?? (pagination ? resolveNextListRequest(pagination.next) : null);
144
+ return {
145
+ items,
146
+ total,
147
+ nextRequest,
148
+ };
149
+ }
28
150
  function normalizeBaseUrl(baseUrl) {
29
151
  const url = new URL(baseUrl || DEFAULT_BASE_URL);
30
152
  url.pathname = url.pathname.replace(/\/+$/, '');
@@ -71,12 +193,68 @@ function buildUrl(baseUrl, requestPath, query) {
71
193
  }
72
194
  return url;
73
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
+ }
74
246
  function normalizeToken(token) {
75
247
  if (!token) {
76
248
  return '';
77
249
  }
78
250
  return token.startsWith('Bearer ') ? token : `Bearer ${token}`;
79
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
+ }
80
258
  function defaultSpinnerLabel(method, path) {
81
259
  const upperMethod = method.toUpperCase();
82
260
  if (path === '/api/v1/me') {
@@ -220,13 +398,20 @@ export class RevoClient {
220
398
  baseUrl;
221
399
  instance;
222
400
  token;
401
+ production;
223
402
  fetchImpl;
403
+ productionWriteConfirmed = false;
404
+ confirmProductionWrite;
405
+ trustedServiceOrigins;
224
406
  constructor(options = {}) {
225
407
  const config = resolveRuntimeConfig(options);
226
408
  this.baseUrl = config.baseUrl;
227
409
  this.instance = config.instance || '';
228
410
  this.token = config.token;
411
+ this.production = options.production === true || config.production === true;
229
412
  this.fetchImpl = options.fetch || globalThis.fetch;
413
+ this.confirmProductionWrite = options.confirmProductionWrite || confirmProductionWriteInTerminal;
414
+ this.trustedServiceOrigins = resolveTrustedServiceOrigins(options.trustedServiceOrigins);
230
415
  }
231
416
  get authHeader() {
232
417
  return normalizeToken(this.token);
@@ -295,10 +480,14 @@ export class RevoClient {
295
480
  }
296
481
  async request(method, path, options = {}) {
297
482
  this.assertReady();
483
+ if (options.productionWrite !== false) {
484
+ await this.assertProductionWriteConfirmed(method, path);
485
+ }
298
486
  if (options.authGuard !== false && path !== '/api/v1/me') {
299
487
  await this.validateSession();
300
488
  }
301
489
  const url = buildUrl(this.baseUrl, path, options.query);
490
+ assertCredentialDestination(this.baseUrl, url, options.allowExternalOrigin, this.trustedServiceOrigins);
302
491
  const headers = new Headers(options.headers || {});
303
492
  headers.set('Authorization', this.authHeader);
304
493
  headers.set('x-api-key', this.token);
@@ -317,6 +506,10 @@ export class RevoClient {
317
506
  const spinner = createSpinner({
318
507
  text: options.spinnerLabel || defaultSpinnerLabel(method, path),
319
508
  });
509
+ const abortController = options.timeoutMs === undefined ? undefined : new AbortController();
510
+ const timeout = abortController
511
+ ? setTimeout(() => abortController.abort(), options.timeoutMs)
512
+ : undefined;
320
513
  spinner.start();
321
514
  try {
322
515
  await delay(REQUEST_DELAY_MS);
@@ -324,7 +517,10 @@ export class RevoClient {
324
517
  method: method.toUpperCase(),
325
518
  headers,
326
519
  body: body,
520
+ redirect: 'manual',
521
+ ...(abortController ? { signal: abortController.signal } : {}),
327
522
  });
523
+ assertNoAuthenticatedRedirect(response);
328
524
  const data = await readResponseData(response);
329
525
  if (!response.ok && response.status !== 304) {
330
526
  if (response.status === 401) {
@@ -333,12 +529,12 @@ export class RevoClient {
333
529
  status: 'not_authenticated',
334
530
  checkedAt: Date.now(),
335
531
  });
336
- 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);
337
533
  }
338
534
  if (response.status === 403 && path !== '/api/v1/me') {
339
- throw new PermissionDeniedError(path, `Access denied for ${path}.`, data);
535
+ throw new PermissionDeniedError(path, `Access denied for ${path}.`, data, response.headers);
340
536
  }
341
- 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);
342
538
  }
343
539
  const previous = readAuthValidationState(this.authValidationKey);
344
540
  saveAuthValidationState({
@@ -355,9 +551,27 @@ export class RevoClient {
355
551
  };
356
552
  }
357
553
  finally {
554
+ if (timeout) {
555
+ clearTimeout(timeout);
556
+ }
358
557
  spinner.stop();
359
558
  }
360
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
+ }
361
575
  async requestData(method, path, options = {}) {
362
576
  const response = await this.request(method, path, options);
363
577
  return response.data;
@@ -369,24 +583,356 @@ export class RevoClient {
369
583
  }
370
584
  return this.requestData('GET', '/api/v1/me', { authGuard: false });
371
585
  }
372
- async listEndpoints() {
373
- 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' } });
374
916
  }
375
917
  async getEditorTypes(requestPath = '/v1/editor/types') {
376
918
  return this.requestData('GET', requestPath, {
377
919
  spinnerLabel: 'Loading editor types',
920
+ allowExternalOrigin: true,
378
921
  });
379
922
  }
380
923
  async debugComponent(requestPath, body) {
381
924
  return this.requestData('POST', requestPath, {
382
925
  body,
383
926
  spinnerLabel: 'Debugging component',
927
+ allowExternalOrigin: true,
384
928
  });
385
929
  }
386
930
  async *debugComponentStream(requestPath, body) {
387
931
  this.assertReady();
932
+ await this.assertProductionWriteConfirmed('POST', requestPath);
388
933
  await this.validateSession();
389
934
  const url = buildUrl(this.baseUrl, requestPath);
935
+ assertCredentialDestination(this.baseUrl, url, true, this.trustedServiceOrigins);
390
936
  const headers = new Headers();
391
937
  headers.set('Accept', 'text/event-stream');
392
938
  headers.set('Content-Type', 'application/json');
@@ -400,7 +946,9 @@ export class RevoClient {
400
946
  method: 'POST',
401
947
  headers,
402
948
  body: JSON.stringify(body),
949
+ redirect: 'manual',
403
950
  });
951
+ assertNoAuthenticatedRedirect(response);
404
952
  if (!response.ok) {
405
953
  const data = await readResponseData(response);
406
954
  if (response.status === 401) {
@@ -409,12 +957,12 @@ export class RevoClient {
409
957
  status: 'not_authenticated',
410
958
  checkedAt: Date.now(),
411
959
  });
412
- 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);
413
961
  }
414
962
  if (response.status === 403) {
415
- throw new PermissionDeniedError(requestPath, `Access denied for ${requestPath}.`, data);
963
+ throw new PermissionDeniedError(requestPath, `Access denied for ${requestPath}.`, data, response.headers);
416
964
  }
417
- 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);
418
966
  }
419
967
  for await (const event of readSseEvents(response)) {
420
968
  yield event;
@@ -428,11 +976,404 @@ export class RevoClient {
428
976
  query: options.query,
429
977
  });
430
978
  }
979
+ async listAllComponents() {
980
+ const components = [];
981
+ const seenRequests = new Set();
982
+ let nextRequest = {
983
+ query: buildActiveComponentListQuery(0),
984
+ };
985
+ let discoveredTotal = null;
986
+ while (nextRequest) {
987
+ const requestKey = JSON.stringify(nextRequest);
988
+ if (seenRequests.has(requestKey)) {
989
+ throw new Error('Component list pagination loop detected while listing all components.');
990
+ }
991
+ seenRequests.add(requestKey);
992
+ const pageValue = await this.listComponents(nextRequest);
993
+ const page = unwrapListPage(pageValue);
994
+ if (page.total !== null) {
995
+ discoveredTotal = page.total;
996
+ }
997
+ for (const item of page.items) {
998
+ if (isRecord(item)) {
999
+ components.push(item);
1000
+ }
1001
+ }
1002
+ if (page.total !== null && components.length >= page.total) {
1003
+ nextRequest = null;
1004
+ continue;
1005
+ }
1006
+ if (page.nextRequest) {
1007
+ nextRequest = withActiveComponentListFilter(page.nextRequest);
1008
+ continue;
1009
+ }
1010
+ if (nextRequest.query
1011
+ && typeof nextRequest.query.take === 'number'
1012
+ && page.items.length === nextRequest.query.take) {
1013
+ nextRequest = {
1014
+ query: buildActiveComponentListQuery(components.length, nextRequest.query.take),
1015
+ };
1016
+ continue;
1017
+ }
1018
+ nextRequest = null;
1019
+ }
1020
+ return { components, discoveredTotal };
1021
+ }
431
1022
  async getComponent(componentId) {
432
1023
  return this.requestData('GET', `/api/v1/component/${componentId}`);
433
1024
  }
1025
+ async createComponent(body) {
1026
+ return this.requestData('POST', '/api/v1/component', {
1027
+ body,
1028
+ spinnerLabel: 'Creating component',
1029
+ });
1030
+ }
1031
+ async updateComponent(componentId, body) {
1032
+ return this.requestData('PUT', `/api/v1/component/${componentId}`, {
1033
+ body,
1034
+ spinnerLabel: 'Updating component',
1035
+ });
1036
+ }
1037
+ async deleteComponent(componentId) {
1038
+ return this.request('DELETE', `/api/v1/component/${componentId}`, {
1039
+ spinnerLabel: 'Deleting component',
1040
+ });
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
+ }
434
1374
  async saveComponentElements(componentId, body) {
435
1375
  return this.request('POST', `/api/v1/component/${componentId}/save`, { body });
436
1376
  }
437
1377
  }
1378
+ export { COMPONENT_LIST_PAGE_SIZE };
438
1379
  export { buildUrl, normalizeToken };