@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.
- package/README.md +312 -33
- package/dist/src/cli.js +58 -7
- package/dist/src/client.d.ts +356 -5
- package/dist/src/client.js +803 -19
- package/dist/src/commands/auth.js +4 -2
- package/dist/src/commands/component.js +260 -141
- package/dist/src/commands/database-schemas.d.ts +2 -0
- package/dist/src/commands/database-schemas.js +188 -0
- package/dist/src/commands/database-views.d.ts +2 -0
- package/dist/src/commands/database-views.js +123 -0
- package/dist/src/commands/endpoints.js +114 -0
- package/dist/src/commands/env.js +44 -24
- package/dist/src/commands/events.d.ts +2 -0
- package/dist/src/commands/events.js +146 -0
- package/dist/src/commands/groups.d.ts +2 -0
- package/dist/src/commands/groups.js +169 -0
- package/dist/src/commands/index.d.ts +8 -0
- package/dist/src/commands/index.js +8 -0
- package/dist/src/commands/job-templates.d.ts +2 -0
- package/dist/src/commands/job-templates.js +101 -0
- package/dist/src/commands/metadata.js +29 -7
- package/dist/src/commands/project.js +10 -3
- package/dist/src/commands/role-groups.d.ts +2 -0
- package/dist/src/commands/role-groups.js +152 -0
- package/dist/src/commands/schedules.d.ts +2 -0
- package/dist/src/commands/schedules.js +141 -0
- package/dist/src/commands/terminal-service.d.ts +38 -0
- package/dist/src/commands/terminal-service.js +210 -0
- package/dist/src/commands/terminal.d.ts +22 -0
- package/dist/src/commands/terminal.js +511 -0
- package/dist/src/component-lock.d.ts +100 -2
- package/dist/src/component-lock.js +304 -15
- package/dist/src/config.d.ts +10 -2
- package/dist/src/config.js +49 -14
- package/dist/src/database-schema-artifacts.d.ts +7 -0
- package/dist/src/database-schema-artifacts.js +8 -0
- package/dist/src/env-sync.d.ts +0 -3
- package/dist/src/env-sync.js +3 -19
- package/dist/src/metadata-backfill.d.ts +16 -6
- package/dist/src/metadata-backfill.js +1069 -18
- package/dist/src/project.d.ts +2 -0
- package/dist/src/project.js +4 -17
- package/dist/src/prompt.js +10 -18
- package/dist/src/resource-metadata.d.ts +1 -0
- package/dist/src/resource-metadata.js +3 -0
- package/dist/src/resource-syncs/database-schema-sync.d.ts +117 -0
- package/dist/src/resource-syncs/database-schema-sync.js +2289 -0
- package/dist/src/resource-syncs/database-view-sync.d.ts +124 -0
- package/dist/src/resource-syncs/database-view-sync.js +1317 -0
- package/dist/src/resource-syncs/endpoint-sync.d.ts +96 -0
- package/dist/src/resource-syncs/endpoint-sync.js +1283 -0
- package/dist/src/resource-syncs/event-sync.d.ts +99 -0
- package/dist/src/resource-syncs/event-sync.js +949 -0
- package/dist/src/resource-syncs/group-sync.d.ts +86 -0
- package/dist/src/resource-syncs/group-sync.js +882 -0
- package/dist/src/resource-syncs/job-template-sync.d.ts +85 -0
- package/dist/src/resource-syncs/job-template-sync.js +782 -0
- package/dist/src/resource-syncs/role-group-sync.d.ts +83 -0
- package/dist/src/resource-syncs/role-group-sync.js +597 -0
- package/dist/src/resource-syncs/schedule-sync.d.ts +111 -0
- package/dist/src/resource-syncs/schedule-sync.js +1302 -0
- package/dist/src/resource-syncs/util.d.ts +19 -0
- package/dist/src/resource-syncs/util.js +116 -0
- package/dist/src/runtime-view.d.ts +1 -0
- package/dist/src/runtime-view.js +6 -1
- package/dist/src/sync-output.d.ts +38 -0
- package/dist/src/sync-output.js +131 -0
- package/dist/src/tracked-resources.d.ts +1 -1
- package/dist/src/tracked-resources.js +26 -2
- package/dist/src/types.d.ts +224 -0
- package/dist/src/ui.d.ts +3 -0
- package/dist/src/ui.js +67 -18
- package/dist/src/utils.d.ts +2 -0
- package/dist/src/utils.js +64 -0
- package/dist/src/workspace-component.d.ts +2 -0
- package/dist/src/workspace-component.js +34 -0
- package/dist/src/workspace-resource.d.ts +2 -0
- package/dist/src/workspace-resource.js +52 -0
- package/package.json +8 -3
package/dist/src/client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
116
|
-
?? (meta ?
|
|
117
|
-
?? (pagination ?
|
|
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 =
|
|
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
|
}
|