@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.
- package/README.md +464 -8
- package/dist/src/cli.js +65 -6
- package/dist/src/client.d.ts +366 -5
- package/dist/src/client.js +954 -13
- package/dist/src/commands/auth.js +4 -2
- package/dist/src/commands/component.js +839 -412
- 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.d.ts +2 -0
- package/dist/src/commands/env.js +380 -0
- 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 +10 -0
- package/dist/src/commands/index.js +10 -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.d.ts +2 -0
- package/dist/src/commands/metadata.js +159 -0
- package/dist/src/commands/project.js +82 -1
- 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 +126 -2
- package/dist/src/component-lock.js +378 -15
- package/dist/src/config.d.ts +20 -0
- package/dist/src/config.js +121 -6
- 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 +83 -0
- package/dist/src/env-sync.js +315 -0
- package/dist/src/metadata-backfill.d.ts +56 -0
- package/dist/src/metadata-backfill.js +1176 -0
- package/dist/src/project.d.ts +17 -9
- package/dist/src/project.js +87 -11
- package/dist/src/prompt.js +10 -18
- package/dist/src/resource-metadata.d.ts +25 -0
- package/dist/src/resource-metadata.js +132 -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 +7 -0
- package/dist/src/tracked-resources.js +61 -0
- package/dist/src/types.d.ts +227 -0
- package/dist/src/ui.d.ts +3 -0
- package/dist/src/ui.js +68 -10
- 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,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
|
-
|
|
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 };
|