@dx-do/client 6.3.5 → 7.0.0
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/dist/index.cjs.js +556 -12
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +556 -13
- package/dist/index.esm.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/lib/model/datastore/datastore-schemas.d.ts.map +1 -1
- package/dist/src/lib/model/datastore/views/index.d.ts +1 -0
- package/dist/src/lib/model/datastore/views/index.d.ts.map +1 -1
- package/dist/src/lib/model/datastore/views/service-universe.d.ts +294 -0
- package/dist/src/lib/model/datastore/views/service-universe.d.ts.map +1 -0
- package/dist/src/lib/services/asm.service.d.ts.map +1 -1
- package/dist/src/lib/services/authorization.service.d.ts +10 -0
- package/dist/src/lib/services/authorization.service.d.ts.map +1 -1
- package/dist/src/lib/services/datastore/datastore-service-universe.service.d.ts +129 -0
- package/dist/src/lib/services/datastore/datastore-service-universe.service.d.ts.map +1 -0
- package/dist/src/lib/services/dx-saas.service.d.ts.map +1 -1
- package/dist/src/lib/services/service-monolith.d.ts +2 -0
- package/dist/src/lib/services/service-monolith.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -2036,7 +2036,7 @@ utils$3.reduceDescriptors(AxiosHeaders$1.prototype, ({ value }, key) => {
|
|
|
2036
2036
|
|
|
2037
2037
|
utils$3.freezeMethods(AxiosHeaders$1);
|
|
2038
2038
|
|
|
2039
|
-
const REDACTED = '[REDACTED ****]';
|
|
2039
|
+
const REDACTED$1 = '[REDACTED ****]';
|
|
2040
2040
|
|
|
2041
2041
|
function hasOwnOrPrototypeToJSON(source) {
|
|
2042
2042
|
if (utils$3.hasOwnProp(source, 'toJSON')) {
|
|
@@ -2091,7 +2091,7 @@ function redactConfig(config, redactKeys) {
|
|
|
2091
2091
|
|
|
2092
2092
|
result = Object.create(null);
|
|
2093
2093
|
for (const [key, value] of Object.entries(source)) {
|
|
2094
|
-
const reducedValue = lowerKeys.has(key.toLowerCase()) ? REDACTED : visit(value);
|
|
2094
|
+
const reducedValue = lowerKeys.has(key.toLowerCase()) ? REDACTED$1 : visit(value);
|
|
2095
2095
|
if (!utils$3.isUndefined(reducedValue)) {
|
|
2096
2096
|
result[key] = reducedValue;
|
|
2097
2097
|
}
|
|
@@ -14712,6 +14712,18 @@ var setToStringTag = esSetTostringtag;
|
|
|
14712
14712
|
var hasOwn$1 = hasown;
|
|
14713
14713
|
var populate = populate$1;
|
|
14714
14714
|
|
|
14715
|
+
/**
|
|
14716
|
+
* Escape CR, LF, and `"` in a multipart `name`/`filename` parameter, so a field
|
|
14717
|
+
* name or filename can not break out of its header line to inject headers or
|
|
14718
|
+
* smuggle additional parts. Matches the WHATWG HTML multipart/form-data encoding.
|
|
14719
|
+
*
|
|
14720
|
+
* @param {string} str - the parameter value to escape
|
|
14721
|
+
* @returns {string} the escaped value
|
|
14722
|
+
*/
|
|
14723
|
+
function escapeHeaderParam(str) {
|
|
14724
|
+
return String(str).replace(/\r/g, '%0D').replace(/\n/g, '%0A').replace(/"/g, '%22');
|
|
14725
|
+
}
|
|
14726
|
+
|
|
14715
14727
|
/**
|
|
14716
14728
|
* Create readable "multipart/form-data" streams.
|
|
14717
14729
|
* Can be used to submit forms
|
|
@@ -14877,7 +14889,7 @@ FormData$1.prototype._multiPartHeader = function (field, value, options) {
|
|
|
14877
14889
|
var contents = '';
|
|
14878
14890
|
var headers = {
|
|
14879
14891
|
// add custom disposition as third element or keep it two elements if not
|
|
14880
|
-
'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || []),
|
|
14892
|
+
'Content-Disposition': ['form-data', 'name="' + escapeHeaderParam(field) + '"'].concat(contentDisposition || []),
|
|
14881
14893
|
// if no content type. allow it to be empty array
|
|
14882
14894
|
'Content-Type': [].concat(contentType || [])
|
|
14883
14895
|
};
|
|
@@ -14931,7 +14943,7 @@ FormData$1.prototype._getContentDisposition = function (value, options) { // esl
|
|
|
14931
14943
|
}
|
|
14932
14944
|
|
|
14933
14945
|
if (filename) {
|
|
14934
|
-
return 'filename="' + filename + '"';
|
|
14946
|
+
return 'filename="' + escapeHeaderParam(filename) + '"';
|
|
14935
14947
|
}
|
|
14936
14948
|
};
|
|
14937
14949
|
|
|
@@ -61955,6 +61967,99 @@ class StrictMap extends Map {
|
|
|
61955
61967
|
}
|
|
61956
61968
|
}
|
|
61957
61969
|
|
|
61970
|
+
/**
|
|
61971
|
+
* Redaction helpers for keeping auth tokens and other secrets out of logs.
|
|
61972
|
+
*
|
|
61973
|
+
* Auth tokens live in axios `error.config.headers` — e.g. `Authorization: Bearer
|
|
61974
|
+
* <userToken>` and `X-Asm-Auth: <dxASMToken>`. Anything that serializes a whole
|
|
61975
|
+
* error / config / headers object (`JSON.stringify`, an AxiosError's `.toJSON()`,
|
|
61976
|
+
* `console.log(err)`, or a logger that deep-prints its argument) will spill them.
|
|
61977
|
+
* These helpers strip the secrets before anything is logged or re-thrown.
|
|
61978
|
+
*/
|
|
61979
|
+
const REDACTED = '[REDACTED]';
|
|
61980
|
+
/** Lowercased object-key names whose values must never be logged. */
|
|
61981
|
+
const SENSITIVE_KEYS = new Set([
|
|
61982
|
+
'authorization',
|
|
61983
|
+
'x-asm-auth',
|
|
61984
|
+
'cookie',
|
|
61985
|
+
'set-cookie',
|
|
61986
|
+
'proxy-authorization',
|
|
61987
|
+
'usertoken',
|
|
61988
|
+
'dxasmtoken',
|
|
61989
|
+
'tenanttoken',
|
|
61990
|
+
'agenttoken',
|
|
61991
|
+
'accesstoken',
|
|
61992
|
+
'refreshtoken',
|
|
61993
|
+
'password',
|
|
61994
|
+
'apikey',
|
|
61995
|
+
'x-api-key',
|
|
61996
|
+
]);
|
|
61997
|
+
/** Matches `Bearer <token>` anywhere in a string. */
|
|
61998
|
+
const BEARER = /Bearer\s+\S+/gi;
|
|
61999
|
+
function redactString(value) {
|
|
62000
|
+
return value.replace(BEARER, `Bearer ${REDACTED}`);
|
|
62001
|
+
}
|
|
62002
|
+
/**
|
|
62003
|
+
* Returns a deep copy of `value` with sensitive object keys masked and any
|
|
62004
|
+
* `Bearer <token>` substrings scrubbed. Safe to pass anything: it is
|
|
62005
|
+
* circular-reference safe (axios errors are circular), and `Error` instances are
|
|
62006
|
+
* reduced to `{ name, message, stack }` (all scrubbed) so their token-bearing
|
|
62007
|
+
* `config`/`request`/`response` are dropped entirely.
|
|
62008
|
+
*/
|
|
62009
|
+
function redactValue(value, seen = new WeakSet()) {
|
|
62010
|
+
if (typeof value === 'string')
|
|
62011
|
+
return redactString(value);
|
|
62012
|
+
if (value === null || typeof value !== 'object')
|
|
62013
|
+
return value;
|
|
62014
|
+
if (seen.has(value))
|
|
62015
|
+
return '[Circular]';
|
|
62016
|
+
seen.add(value);
|
|
62017
|
+
if (Array.isArray(value)) {
|
|
62018
|
+
return value.map((entry) => redactValue(entry, seen));
|
|
62019
|
+
}
|
|
62020
|
+
if (value instanceof Error) {
|
|
62021
|
+
return {
|
|
62022
|
+
name: value.name,
|
|
62023
|
+
message: redactString(value.message),
|
|
62024
|
+
stack: value.stack ? redactString(value.stack) : undefined,
|
|
62025
|
+
};
|
|
62026
|
+
}
|
|
62027
|
+
const out = {};
|
|
62028
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
62029
|
+
out[key] = SENSITIVE_KEYS.has(key.toLowerCase())
|
|
62030
|
+
? REDACTED
|
|
62031
|
+
: redactValue(entry, seen);
|
|
62032
|
+
}
|
|
62033
|
+
return out;
|
|
62034
|
+
}
|
|
62035
|
+
/**
|
|
62036
|
+
* Strips auth tokens from an (possibly Axios) error IN PLACE and returns it, so it
|
|
62037
|
+
* stays a throwable `Error` with its stack intact but no longer carries the token
|
|
62038
|
+
* in `config.headers`, `response.config.headers`, or the raw request header string.
|
|
62039
|
+
* Non-error / non-axios values are returned unchanged.
|
|
62040
|
+
*
|
|
62041
|
+
* Use this at the axios boundary (a response error interceptor) so every catch,
|
|
62042
|
+
* re-throw, and top-level handler downstream sees a token-free error.
|
|
62043
|
+
*/
|
|
62044
|
+
function sanitizeAxiosError(err) {
|
|
62045
|
+
if (!err || typeof err !== 'object')
|
|
62046
|
+
return err;
|
|
62047
|
+
const e = err;
|
|
62048
|
+
// Only touch things that look like axios errors.
|
|
62049
|
+
if (!e.isAxiosError && !e.config && !e.response)
|
|
62050
|
+
return err;
|
|
62051
|
+
if (e.config?.headers)
|
|
62052
|
+
e.config.headers = redactValue(e.config.headers);
|
|
62053
|
+
if (e.response?.config?.headers) {
|
|
62054
|
+
e.response.config.headers = redactValue(e.response.config.headers);
|
|
62055
|
+
}
|
|
62056
|
+
// Node's ClientRequest keeps the raw request (incl. the Authorization line) in `_header`.
|
|
62057
|
+
if (e.request && typeof e.request._header === 'string') {
|
|
62058
|
+
e.request._header = redactString(e.request._header);
|
|
62059
|
+
}
|
|
62060
|
+
return err;
|
|
62061
|
+
}
|
|
62062
|
+
|
|
61958
62063
|
/**
|
|
61959
62064
|
* Service for APM agents: query by name/regex, list names/statuses, traces,
|
|
61960
62065
|
* thread dumps, and license/device reporting.
|
|
@@ -64597,12 +64702,15 @@ class AsmService {
|
|
|
64597
64702
|
baseURL =
|
|
64598
64703
|
this.dxSaaSService.dxdoConfiguration.dxConfiguration.dxASMbaseAPIURL;
|
|
64599
64704
|
}
|
|
64600
|
-
|
|
64705
|
+
const client = createClient({
|
|
64601
64706
|
baseURL,
|
|
64602
64707
|
headers: {
|
|
64603
64708
|
'X-Asm-Auth': this.dxSaaSService.dxdoConfiguration.dxConfiguration.dxASMToken,
|
|
64604
64709
|
},
|
|
64605
64710
|
});
|
|
64711
|
+
// Strip the X-Asm-Auth token from any error before it can be logged/propagated.
|
|
64712
|
+
client.instance.interceptors.response.use((response) => response, (error) => Promise.reject(sanitizeAxiosError(error)));
|
|
64713
|
+
this.localClient = client;
|
|
64606
64714
|
}
|
|
64607
64715
|
return this.localClient;
|
|
64608
64716
|
}
|
|
@@ -64615,7 +64723,7 @@ class AsmService {
|
|
|
64615
64723
|
return foldersList.data;
|
|
64616
64724
|
})
|
|
64617
64725
|
.catch((error) => {
|
|
64618
|
-
this.log.error(error);
|
|
64726
|
+
this.log.error('ASM foldersList failed', error instanceof Error ? error.message : String(error));
|
|
64619
64727
|
throw error;
|
|
64620
64728
|
});
|
|
64621
64729
|
}
|
|
@@ -64723,6 +64831,23 @@ class AuthorizationService {
|
|
|
64723
64831
|
}
|
|
64724
64832
|
return raw;
|
|
64725
64833
|
}
|
|
64834
|
+
/**
|
|
64835
|
+
* Returns true if a user with the given identifier exists on the tenant.
|
|
64836
|
+
* Matches case-insensitively against each account's `userId` (the login
|
|
64837
|
+
* principal — usually the upper-cased email) and any of its `emailIds`.
|
|
64838
|
+
*
|
|
64839
|
+
* @remarks Fetches the user list via {@link getUsersV2} (a single page, up
|
|
64840
|
+
* to 5000 accounts). A tenant with more users than that could yield a false
|
|
64841
|
+
* negative for an account beyond the page.
|
|
64842
|
+
*/
|
|
64843
|
+
async userExists(userIdentifier) {
|
|
64844
|
+
const needle = userIdentifier.trim().toUpperCase();
|
|
64845
|
+
if (needle.length === 0)
|
|
64846
|
+
return false;
|
|
64847
|
+
const response = await this.getUsersV2(1, 5000);
|
|
64848
|
+
return response.users.some((user) => (user.userId ?? '').toUpperCase() === needle ||
|
|
64849
|
+
(user.emailIds ?? []).some((email) => (email.emailAddr ?? '').toUpperCase() === needle));
|
|
64850
|
+
}
|
|
64726
64851
|
/** Reformats a cohort id to upper-cased, dashed GUID form (`8-4-4-4-12`). */
|
|
64727
64852
|
toGuidCohortId(cohortId) {
|
|
64728
64853
|
const hex = cohortId.replace(/-/g, '').toLowerCase();
|
|
@@ -70251,6 +70376,9 @@ class DxSaasService {
|
|
|
70251
70376
|
.replace(/-/g, '')
|
|
70252
70377
|
.toLowerCase();
|
|
70253
70378
|
this.initializeProxyIfNecessary();
|
|
70379
|
+
// Strip auth tokens from any error this client surfaces, so nothing downstream
|
|
70380
|
+
// (handleAxiosError, re-throws, top-level catches) can log the Authorization header.
|
|
70381
|
+
this._httpClient.interceptors.response.use((response) => response, (error) => Promise.reject(sanitizeAxiosError(error)));
|
|
70254
70382
|
if (process__namespace.env.DXDO_LOG_REQUESTS) {
|
|
70255
70383
|
log.info('Logging requests');
|
|
70256
70384
|
const requestLogFile = process__namespace.env.DXDO_REQUEST_LOG_FILE ??
|
|
@@ -71210,20 +71338,21 @@ class DxSaasService {
|
|
|
71210
71338
|
}
|
|
71211
71339
|
static handleAxiosError(reason, isStream = false) {
|
|
71212
71340
|
if (reason.isAxiosError && reason.response) {
|
|
71213
|
-
|
|
71214
|
-
|
|
71215
|
-
DxSaasService.staticLog.
|
|
71216
|
-
DxSaasService.staticLog.error('[Axios Error
|
|
71341
|
+
// NB: request headers are already token-scrubbed by the response interceptor;
|
|
71342
|
+
// redactValue is belt-and-suspenders in case this is ever called off-path.
|
|
71343
|
+
DxSaasService.staticLog.debug('[Axios Error DEBUG]', redactValue(reason.toJSON()));
|
|
71344
|
+
DxSaasService.staticLog.error('[Axios Error BASIC]', `${reason.request.method} ${reason.config?.url} ${JSON.stringify(redactValue(reason.config?.params ?? {}))}`);
|
|
71345
|
+
DxSaasService.staticLog.error('[Axios Error BODY]', redactValue(reason.config?.data ?? {}));
|
|
71217
71346
|
if (isStream) {
|
|
71218
71347
|
DxSaasService.staticLog.error(`[Axios Error STATUS] ${reason.response.status} / ${reason.message} `);
|
|
71219
71348
|
}
|
|
71220
71349
|
else {
|
|
71221
|
-
DxSaasService.staticLog.error(`[Axios Error STATUS] ${reason.response.status} / ${reason.message} / ${JSON.stringify(reason.response.data)}`);
|
|
71350
|
+
DxSaasService.staticLog.error(`[Axios Error STATUS] ${reason.response.status} / ${reason.message} / ${JSON.stringify(redactValue(reason.response.data))}`);
|
|
71222
71351
|
}
|
|
71223
71352
|
return new SimpleHTTPError(reason.response.status, reason.message);
|
|
71224
71353
|
}
|
|
71225
71354
|
else
|
|
71226
|
-
return reason; //
|
|
71355
|
+
return sanitizeAxiosError(reason); // scrub before propagating
|
|
71227
71356
|
}
|
|
71228
71357
|
static async legacyAuth(v3Configuration) {
|
|
71229
71358
|
return this.legacyAxaRequest(v3Configuration, 'GET', 'ess/security/v1/me', false, false);
|
|
@@ -78728,6 +78857,419 @@ class DataStoreNASSService {
|
|
|
78728
78857
|
}
|
|
78729
78858
|
}
|
|
78730
78859
|
|
|
78860
|
+
/**
|
|
78861
|
+
* Service Universe models — an **OI-owned universe (view) scoped to a curated
|
|
78862
|
+
* list of services**, managed through the tenant `views/*` API. The wire shape
|
|
78863
|
+
* here matches the real server payload (verified against a live tenant): `views`
|
|
78864
|
+
* is an **array** of typed sub-views and access grants carry a `permissions`
|
|
78865
|
+
* array. (This is deliberately distinct from the object-shaped `ViewBodySchema`
|
|
78866
|
+
* in `./query`, which models a different, unused surface.)
|
|
78867
|
+
*/
|
|
78868
|
+
/** Access permission granted on a service universe. */
|
|
78869
|
+
const ServiceUniversePermissionSchema = v4.z
|
|
78870
|
+
.enum(['READ', 'WRITE'])
|
|
78871
|
+
.describe('Permission granted on the universe');
|
|
78872
|
+
/** A single user or group access grant on a service universe. */
|
|
78873
|
+
const ServiceUniverseAccessEntrySchema = v4.z.looseObject({
|
|
78874
|
+
name: v4.z.string().describe('User email or group name being granted access'),
|
|
78875
|
+
permissions: v4.z
|
|
78876
|
+
.array(ServiceUniversePermissionSchema)
|
|
78877
|
+
.optional()
|
|
78878
|
+
.describe('Permissions granted to this principal (READ and/or WRITE)'),
|
|
78879
|
+
canManage: v4.z
|
|
78880
|
+
.boolean()
|
|
78881
|
+
.optional()
|
|
78882
|
+
.describe('Whether the principal may manage (re-share) the universe'),
|
|
78883
|
+
attributeInclude: v4.z
|
|
78884
|
+
.array(v4.z.unknown())
|
|
78885
|
+
.optional()
|
|
78886
|
+
.describe('Attribute-based include scoping for this grant'),
|
|
78887
|
+
attributeExclude: v4.z
|
|
78888
|
+
.array(v4.z.unknown())
|
|
78889
|
+
.optional()
|
|
78890
|
+
.describe('Attribute-based exclude scoping for this grant'),
|
|
78891
|
+
isValid: v4.z
|
|
78892
|
+
.array(v4.z.string())
|
|
78893
|
+
.optional()
|
|
78894
|
+
.describe('Server-side validation hints for the principal (UI-supplied)'),
|
|
78895
|
+
});
|
|
78896
|
+
/** Access policy for a service universe (per-user and per-group grants). */
|
|
78897
|
+
const ServiceUniverseAccessSchema = v4.z.looseObject({
|
|
78898
|
+
users: v4.z
|
|
78899
|
+
.array(ServiceUniverseAccessEntrySchema)
|
|
78900
|
+
.optional()
|
|
78901
|
+
.describe('Per-user access grants'),
|
|
78902
|
+
groups: v4.z
|
|
78903
|
+
.array(ServiceUniverseAccessEntrySchema)
|
|
78904
|
+
.optional()
|
|
78905
|
+
.describe('Per-group access grants'),
|
|
78906
|
+
});
|
|
78907
|
+
/** The TAS scoping of a service universe — its inner filter selects the services. */
|
|
78908
|
+
const ServiceUniverseTasSpecSchema = v4.z.looseObject({
|
|
78909
|
+
filter: TasFilterSchema.optional().describe('TAS filter selecting the topology; for a service universe this is a SERVICE-op filter over the chosen service names'),
|
|
78910
|
+
includeMetricFilter: v4.z
|
|
78911
|
+
.boolean()
|
|
78912
|
+
.optional()
|
|
78913
|
+
.describe('Whether a metric filter is applied; defaults to false'),
|
|
78914
|
+
projection: v4.z
|
|
78915
|
+
.string()
|
|
78916
|
+
.optional()
|
|
78917
|
+
.describe("Projection mode, e.g. 'NO_TYPE'"),
|
|
78918
|
+
projectionFilter: v4.z
|
|
78919
|
+
.looseObject({
|
|
78920
|
+
attributes: v4.z
|
|
78921
|
+
.array(v4.z.string())
|
|
78922
|
+
.optional()
|
|
78923
|
+
.describe('Attributes to project, e.g. ["name"]'),
|
|
78924
|
+
})
|
|
78925
|
+
.optional()
|
|
78926
|
+
.describe('Which vertex attributes to project'),
|
|
78927
|
+
});
|
|
78928
|
+
/** A TAS sub-view of a service universe. */
|
|
78929
|
+
const ServiceUniverseTasViewSchema = v4.z.looseObject({
|
|
78930
|
+
type: v4.z.literal('tas').describe('Discriminator: topology (TAS) sub-view'),
|
|
78931
|
+
filter: ServiceUniverseTasSpecSchema.describe('TAS scoping for this sub-view'),
|
|
78932
|
+
permissions: v4.z
|
|
78933
|
+
.array(ServiceUniversePermissionSchema)
|
|
78934
|
+
.optional()
|
|
78935
|
+
.describe('Permissions on this sub-view'),
|
|
78936
|
+
});
|
|
78937
|
+
/** A NASS (metrics) sub-view of a service universe. */
|
|
78938
|
+
const ServiceUniverseNassViewSchema = v4.z.looseObject({
|
|
78939
|
+
type: v4.z.literal('nass').describe('Discriminator: metrics (NASS) sub-view'),
|
|
78940
|
+
filter: v4.z
|
|
78941
|
+
.looseObject({
|
|
78942
|
+
filter: v4.z
|
|
78943
|
+
.looseObject({ op: v4.z.string().describe('Filter op, e.g. "ALL"') })
|
|
78944
|
+
.optional()
|
|
78945
|
+
.describe('NASS filter tree'),
|
|
78946
|
+
serviceFilter: v4.z
|
|
78947
|
+
.unknown()
|
|
78948
|
+
.optional()
|
|
78949
|
+
.describe('Optional service-scoped NASS filter'),
|
|
78950
|
+
})
|
|
78951
|
+
.optional()
|
|
78952
|
+
.describe('NASS scoping for this sub-view'),
|
|
78953
|
+
permissions: v4.z
|
|
78954
|
+
.array(ServiceUniversePermissionSchema)
|
|
78955
|
+
.optional()
|
|
78956
|
+
.describe('Permissions on this sub-view'),
|
|
78957
|
+
});
|
|
78958
|
+
/** A service universe sub-view: TAS (service-scoped topology) or NASS (metrics). */
|
|
78959
|
+
const ServiceUniverseViewSchema = v4.z
|
|
78960
|
+
.discriminatedUnion('type', [
|
|
78961
|
+
ServiceUniverseTasViewSchema,
|
|
78962
|
+
ServiceUniverseNassViewSchema,
|
|
78963
|
+
])
|
|
78964
|
+
.describe('A TAS or NASS sub-view of the universe');
|
|
78965
|
+
/** Metadata for a service universe. */
|
|
78966
|
+
const ServiceUniverseAttributesSchema = v4.z.looseObject({
|
|
78967
|
+
owner: v4.z
|
|
78968
|
+
.string()
|
|
78969
|
+
.optional()
|
|
78970
|
+
.describe("Owning product; service universes are 'OI'"),
|
|
78971
|
+
label: v4.z.string().describe('Human-readable universe name'),
|
|
78972
|
+
description: v4.z.string().optional().describe('Universe description'),
|
|
78973
|
+
});
|
|
78974
|
+
/**
|
|
78975
|
+
* The create/update request body for a service universe
|
|
78976
|
+
* (`POST/PUT views/view`).
|
|
78977
|
+
*/
|
|
78978
|
+
const ServiceUniverseBodySchema = v4.z.looseObject({
|
|
78979
|
+
inactive: v4.z
|
|
78980
|
+
.boolean()
|
|
78981
|
+
.optional()
|
|
78982
|
+
.describe('Whether the universe is inactive (hidden from active use)'),
|
|
78983
|
+
attributes: ServiceUniverseAttributesSchema.describe('Universe metadata (owner, label, description)'),
|
|
78984
|
+
access: ServiceUniverseAccessSchema.optional().describe('Access policy (users/groups)'),
|
|
78985
|
+
views: v4.z
|
|
78986
|
+
.array(ServiceUniverseViewSchema)
|
|
78987
|
+
.describe('The TAS + NASS sub-views defining the universe scope'),
|
|
78988
|
+
});
|
|
78989
|
+
/**
|
|
78990
|
+
* A summary row from `GET oi/v2/universe/getAllUniverseList`.
|
|
78991
|
+
*/
|
|
78992
|
+
const ServiceUniverseListItemSchema = v4.z.looseObject({
|
|
78993
|
+
id: v4.z.string().describe('Universe (view) id, e.g. "VIEW1821"'),
|
|
78994
|
+
name: v4.z.string().optional().describe('Universe label'),
|
|
78995
|
+
description: v4.z.string().optional().describe('Universe description'),
|
|
78996
|
+
owner: v4.z
|
|
78997
|
+
.string()
|
|
78998
|
+
.optional()
|
|
78999
|
+
.describe("Owning product; service universes are 'OI'"),
|
|
79000
|
+
inactive: v4.z.boolean().optional().describe('Whether the universe is inactive'),
|
|
79001
|
+
});
|
|
79002
|
+
const ServiceUniverseListResponseSchema = v4.z
|
|
79003
|
+
.array(ServiceUniverseListItemSchema)
|
|
79004
|
+
.describe('All universes returned by getAllUniverseList');
|
|
79005
|
+
/**
|
|
79006
|
+
* A full service universe as returned by `POST views/queryView` (op:ID).
|
|
79007
|
+
* Loosely modeled — the detail response echoes the body plus server-resolved
|
|
79008
|
+
* fields (viewId, resolved access).
|
|
79009
|
+
*/
|
|
79010
|
+
const ServiceUniverseSchema = v4.z.looseObject({
|
|
79011
|
+
viewId: v4.z.string().optional().describe('Server-assigned view id'),
|
|
79012
|
+
inactive: v4.z.boolean().optional().describe('Whether the universe is inactive'),
|
|
79013
|
+
attributes: ServiceUniverseAttributesSchema.optional().describe('Universe metadata'),
|
|
79014
|
+
access: ServiceUniverseAccessSchema.optional().describe('Access policy'),
|
|
79015
|
+
views: v4.z
|
|
79016
|
+
.array(v4.z.unknown())
|
|
79017
|
+
.optional()
|
|
79018
|
+
.describe('The universe sub-views (TAS/NASS)'),
|
|
79019
|
+
});
|
|
79020
|
+
/** Response wrapper for `POST views/queryView`. */
|
|
79021
|
+
const ServiceUniverseQueryResponseSchema = v4.z.looseObject({
|
|
79022
|
+
views: v4.z
|
|
79023
|
+
.array(ServiceUniverseSchema)
|
|
79024
|
+
.optional()
|
|
79025
|
+
.describe('Universes matching the query filter'),
|
|
79026
|
+
});
|
|
79027
|
+
|
|
79028
|
+
/**
|
|
79029
|
+
* Service for **Service Universes** — OI-owned universes (views) scoped to a
|
|
79030
|
+
* curated list of services, managed via the tenant `views/*` API plus the OI
|
|
79031
|
+
* universe-list endpoint. The "View sample" preview runs the SERVICE-op filter
|
|
79032
|
+
* as a TAS graph query.
|
|
79033
|
+
*
|
|
79034
|
+
* @example
|
|
79035
|
+
* ```ts
|
|
79036
|
+
* const svc = new ServiceUniverseService(dxSaasService, log);
|
|
79037
|
+
* const body = ServiceUniverseService.buildBody({ label: 'IoT', serviceNames: ['IoT RDS'] });
|
|
79038
|
+
* await svc.createServiceUniverse(body);
|
|
79039
|
+
* ```
|
|
79040
|
+
*/
|
|
79041
|
+
class ServiceUniverseService {
|
|
79042
|
+
dxSaasService;
|
|
79043
|
+
log;
|
|
79044
|
+
constructor(dxSaasService, log) {
|
|
79045
|
+
this.dxSaasService = dxSaasService;
|
|
79046
|
+
this.log = log;
|
|
79047
|
+
}
|
|
79048
|
+
/**
|
|
79049
|
+
* Lists service universes (OI-owned) via
|
|
79050
|
+
* `GET oi/v2/universe/getAllUniverseList` (VIEWALL).
|
|
79051
|
+
*
|
|
79052
|
+
* @remarks Response-validated (mismatch → `log.warn`, raw still returned).
|
|
79053
|
+
*/
|
|
79054
|
+
async listServiceUniverses() {
|
|
79055
|
+
const raw = await this.dxSaasService.oiGet('oi/v2/universe/getAllUniverseList');
|
|
79056
|
+
const result = ServiceUniverseListResponseSchema.safeParse(raw);
|
|
79057
|
+
if (!result.success) {
|
|
79058
|
+
this.log.warn('ServiceUniverseListResponse validation warning:', result.error.message);
|
|
79059
|
+
}
|
|
79060
|
+
const all = raw ?? [];
|
|
79061
|
+
return all.filter((u) => (u.owner ?? '').toUpperCase() === 'OI');
|
|
79062
|
+
}
|
|
79063
|
+
/**
|
|
79064
|
+
* Returns a single service universe by its view id via
|
|
79065
|
+
* `POST views/queryView` (op:ID). Throws if not exactly one match.
|
|
79066
|
+
*/
|
|
79067
|
+
async getServiceUniverse(viewId) {
|
|
79068
|
+
const raw = await this.dxSaasService.tenantPost('views/queryView', {
|
|
79069
|
+
filter: { op: 'ID', ids: [viewId], input: { op: 'ALL' } },
|
|
79070
|
+
includeInactive: true,
|
|
79071
|
+
});
|
|
79072
|
+
const result = ServiceUniverseQueryResponseSchema.safeParse(raw);
|
|
79073
|
+
if (!result.success) {
|
|
79074
|
+
this.log.warn('ServiceUniverseQueryResponse validation warning:', result.error.message);
|
|
79075
|
+
}
|
|
79076
|
+
const views = raw.views ?? [];
|
|
79077
|
+
if (views.length !== 1) {
|
|
79078
|
+
throw new Error(`Expected exactly one universe with viewId '${viewId}', found ${views.length}.`);
|
|
79079
|
+
}
|
|
79080
|
+
return views[0];
|
|
79081
|
+
}
|
|
79082
|
+
/**
|
|
79083
|
+
* Creates a service universe via `POST views/view`.
|
|
79084
|
+
*
|
|
79085
|
+
* @throws ZodError if the body fails schema validation.
|
|
79086
|
+
*/
|
|
79087
|
+
async createServiceUniverse(body) {
|
|
79088
|
+
ServiceUniverseBodySchema.parse(body);
|
|
79089
|
+
return this.dxSaasService.tenantPost('views/view', body);
|
|
79090
|
+
}
|
|
79091
|
+
/**
|
|
79092
|
+
* Updates a service universe via `PUT views/view/{viewId}`.
|
|
79093
|
+
*
|
|
79094
|
+
* @throws ZodError if the body fails schema validation.
|
|
79095
|
+
*/
|
|
79096
|
+
async updateServiceUniverse(viewId, body) {
|
|
79097
|
+
ServiceUniverseBodySchema.parse(body);
|
|
79098
|
+
return this.dxSaasService.tenantPut(`views/view/${encodeURIComponent(viewId)}`, body);
|
|
79099
|
+
}
|
|
79100
|
+
/**
|
|
79101
|
+
* PUTs a full universe body to `views/view/{viewId}` **without** re-validating
|
|
79102
|
+
* it against the client schema. Used by the access commands, which round-trip
|
|
79103
|
+
* the universe's existing views verbatim (a UI-created universe may carry view
|
|
79104
|
+
* shapes our builder wouldn't reconstruct); only the `access` policy changes.
|
|
79105
|
+
*/
|
|
79106
|
+
async putServiceUniverse(viewId, body) {
|
|
79107
|
+
return this.dxSaasService.tenantPut(`views/view/${encodeURIComponent(viewId)}`, body);
|
|
79108
|
+
}
|
|
79109
|
+
/** Deletes a service universe via `DELETE views/view/{viewId}`. */
|
|
79110
|
+
async deleteServiceUniverse(viewId) {
|
|
79111
|
+
return this.dxSaasService.tenantDelete(`views/view/${encodeURIComponent(viewId)}`);
|
|
79112
|
+
}
|
|
79113
|
+
/**
|
|
79114
|
+
* Checks which of the given service names actually exist on the tenant, via
|
|
79115
|
+
* `GET oi/v2/sa/services/{name}` per name. A 404 means the service does not
|
|
79116
|
+
* exist; any other error is re-thrown (so a transient failure is never
|
|
79117
|
+
* silently reported as "missing").
|
|
79118
|
+
*
|
|
79119
|
+
* @throws Error if any name could not be verified for a non-404 reason.
|
|
79120
|
+
*/
|
|
79121
|
+
async resolveServiceNames(serviceNames) {
|
|
79122
|
+
const existing = [];
|
|
79123
|
+
const missing = [];
|
|
79124
|
+
const errored = [];
|
|
79125
|
+
await dist$1.PromisePool.for(serviceNames)
|
|
79126
|
+
.withConcurrency(5)
|
|
79127
|
+
.process(async (name) => {
|
|
79128
|
+
try {
|
|
79129
|
+
await this.dxSaasService.oiGet(`oi/v2/sa/services/${encodeURIComponent(name)}`, { subservices: false });
|
|
79130
|
+
existing.push(name);
|
|
79131
|
+
}
|
|
79132
|
+
catch (err) {
|
|
79133
|
+
if (err.httpCode === 404) {
|
|
79134
|
+
missing.push(name);
|
|
79135
|
+
}
|
|
79136
|
+
else {
|
|
79137
|
+
errored.push({ name, error: err });
|
|
79138
|
+
}
|
|
79139
|
+
}
|
|
79140
|
+
});
|
|
79141
|
+
if (errored.length > 0) {
|
|
79142
|
+
const first = errored[0].error;
|
|
79143
|
+
const reason = first instanceof Error ? first.message : String(first);
|
|
79144
|
+
throw new Error(`Could not verify ${errored.length} service name(s) (${errored
|
|
79145
|
+
.map((e) => e.name)
|
|
79146
|
+
.join(', ')}): ${reason}`);
|
|
79147
|
+
}
|
|
79148
|
+
return { existing, missing };
|
|
79149
|
+
}
|
|
79150
|
+
/**
|
|
79151
|
+
* "View sample": runs the SERVICE-scoped filter as a TAS graph query
|
|
79152
|
+
* (`POST tas/graph/query`) and returns the matching entities — the dry-run
|
|
79153
|
+
* preview of what a universe over `serviceNames` would contain.
|
|
79154
|
+
*
|
|
79155
|
+
* @remarks Response-validated (mismatch → `log.warn`, raw still returned).
|
|
79156
|
+
*/
|
|
79157
|
+
async sampleServices(serviceNames, options = {}) {
|
|
79158
|
+
const query = {
|
|
79159
|
+
filter: {
|
|
79160
|
+
op: 'OR',
|
|
79161
|
+
input: [
|
|
79162
|
+
{
|
|
79163
|
+
op: 'SERVICE',
|
|
79164
|
+
values: serviceNames,
|
|
79165
|
+
includeServiceHierarchy: options.includeServiceHierarchy ?? true,
|
|
79166
|
+
excludeSubServices: options.excludeSubServices ?? false,
|
|
79167
|
+
},
|
|
79168
|
+
],
|
|
79169
|
+
},
|
|
79170
|
+
authorizationView: 'VIEWALL',
|
|
79171
|
+
schema: 'SA',
|
|
79172
|
+
limit: options.limit ?? 200,
|
|
79173
|
+
};
|
|
79174
|
+
TasQuerySchema.parse(query);
|
|
79175
|
+
const raw = await this.dxSaasService.tenantPost('tas/graph/query', query);
|
|
79176
|
+
const result = TasGraphSchema.safeParse(raw);
|
|
79177
|
+
if (!result.success) {
|
|
79178
|
+
this.log.warn('ServiceUniverse sample (TasGraph) validation warning:', result.error.message);
|
|
79179
|
+
}
|
|
79180
|
+
return raw;
|
|
79181
|
+
}
|
|
79182
|
+
/**
|
|
79183
|
+
* Builds a service universe request body: a SERVICE-scoped TAS sub-view over
|
|
79184
|
+
* `serviceNames` (or an ALL view when empty) plus an ALL NASS sub-view. Mirrors
|
|
79185
|
+
* the shape the DXO2 Service Universe UI submits.
|
|
79186
|
+
*/
|
|
79187
|
+
/** A user/group access grant: READ+WRITE, no attribute scoping (per current UX). */
|
|
79188
|
+
static accessEntry(name, canManage) {
|
|
79189
|
+
return {
|
|
79190
|
+
name,
|
|
79191
|
+
permissions: ['READ', 'WRITE'],
|
|
79192
|
+
canManage,
|
|
79193
|
+
attributeInclude: [],
|
|
79194
|
+
attributeExclude: [],
|
|
79195
|
+
};
|
|
79196
|
+
}
|
|
79197
|
+
/**
|
|
79198
|
+
* Returns a copy of `existing` with a user/group access grant added (or
|
|
79199
|
+
* updated in place if the principal already has a grant). Preserves the
|
|
79200
|
+
* universe's server-managed fields and views verbatim — only `access`
|
|
79201
|
+
* changes. Pass exactly one of `user` / `group`.
|
|
79202
|
+
*/
|
|
79203
|
+
static withAccessEntry(existing, target) {
|
|
79204
|
+
const isGroup = !!target.group;
|
|
79205
|
+
const name = (target.user ?? target.group);
|
|
79206
|
+
const users = [...(existing.access?.users ?? [])];
|
|
79207
|
+
const groups = [...(existing.access?.groups ?? [])];
|
|
79208
|
+
const list = isGroup ? groups : users;
|
|
79209
|
+
const next = list.filter((e) => e.name !== name);
|
|
79210
|
+
next.push(this.accessEntry(name, target.canManage));
|
|
79211
|
+
const access = isGroup
|
|
79212
|
+
? { users, groups: next }
|
|
79213
|
+
: { users: next, groups };
|
|
79214
|
+
return {
|
|
79215
|
+
...existing,
|
|
79216
|
+
access,
|
|
79217
|
+
};
|
|
79218
|
+
}
|
|
79219
|
+
/**
|
|
79220
|
+
* Returns a copy of `existing` with a user/group access grant removed.
|
|
79221
|
+
* Preserves everything else verbatim. Pass exactly one of `user` / `group`.
|
|
79222
|
+
*/
|
|
79223
|
+
static withoutAccessEntry(existing, target) {
|
|
79224
|
+
const isGroup = !!target.group;
|
|
79225
|
+
const name = (target.user ?? target.group);
|
|
79226
|
+
const users = (existing.access?.users ?? []).filter((e) => isGroup || e.name !== name);
|
|
79227
|
+
const groups = (existing.access?.groups ?? []).filter((e) => !isGroup || e.name !== name);
|
|
79228
|
+
return {
|
|
79229
|
+
...existing,
|
|
79230
|
+
access: { users, groups },
|
|
79231
|
+
};
|
|
79232
|
+
}
|
|
79233
|
+
static buildBody(input) {
|
|
79234
|
+
const { label, description, serviceNames, inactive, includeServiceHierarchy, excludeSubServices, access, } = input;
|
|
79235
|
+
const tasFilter = serviceNames.length === 0
|
|
79236
|
+
? { op: 'ALL' }
|
|
79237
|
+
: {
|
|
79238
|
+
op: 'SERVICE',
|
|
79239
|
+
values: serviceNames,
|
|
79240
|
+
includeServiceHierarchy: includeServiceHierarchy ?? true,
|
|
79241
|
+
excludeSubServices: excludeSubServices ?? false,
|
|
79242
|
+
};
|
|
79243
|
+
return {
|
|
79244
|
+
inactive: inactive ?? false,
|
|
79245
|
+
attributes: { owner: 'OI', label, description: description ?? '' },
|
|
79246
|
+
access: access ?? { groups: [], users: [] },
|
|
79247
|
+
views: [
|
|
79248
|
+
{
|
|
79249
|
+
type: 'tas',
|
|
79250
|
+
filter: {
|
|
79251
|
+
filter: tasFilter,
|
|
79252
|
+
includeMetricFilter: false,
|
|
79253
|
+
projection: 'NO_TYPE',
|
|
79254
|
+
projectionFilter: { attributes: ['name'] },
|
|
79255
|
+
},
|
|
79256
|
+
permissions: ['READ', 'WRITE'],
|
|
79257
|
+
},
|
|
79258
|
+
{
|
|
79259
|
+
type: 'nass',
|
|
79260
|
+
filter: {
|
|
79261
|
+
filter: { op: 'ALL' },
|
|
79262
|
+
serviceFilter: serviceNames.length === 0
|
|
79263
|
+
? undefined
|
|
79264
|
+
: { op: 'SERVICE', values: serviceNames },
|
|
79265
|
+
},
|
|
79266
|
+
permissions: ['READ', 'WRITE'],
|
|
79267
|
+
},
|
|
79268
|
+
],
|
|
79269
|
+
};
|
|
79270
|
+
}
|
|
79271
|
+
}
|
|
79272
|
+
|
|
78731
79273
|
var NullSimpleLog = Logging.NullSimpleLog;
|
|
78732
79274
|
/**
|
|
78733
79275
|
* Creates and returns a container of all DX client services (ACC, alerts, alarms,
|
|
@@ -78762,6 +79304,7 @@ function createServiceMonolith(dxDoConfiguration, log = new NullSimpleLog()) {
|
|
|
78762
79304
|
dxDataStoreNASSService: new DataStoreNASSService(dxSaaSService, log),
|
|
78763
79305
|
dxDataStoreNASSQLService: new DataStoreNASSQLService(dxSaaSService, log),
|
|
78764
79306
|
dxDataStoreTASService: new DataStoreTASService(dxSaaSService, log),
|
|
79307
|
+
dxServiceUniverseService: new ServiceUniverseService(dxSaaSService, log),
|
|
78765
79308
|
dxEventService: new EventService(dxSaaSService, log),
|
|
78766
79309
|
dxExperienceService: new ExperienceService(dxSaaSService, log),
|
|
78767
79310
|
dxFeaturesService: new DataStoreFeaturesService(dxSaaSService, log),
|
|
@@ -78966,6 +79509,7 @@ exports.SLIService = SLIService;
|
|
|
78966
79509
|
exports.SQLService = SQLService;
|
|
78967
79510
|
exports.ServiceFilterSpecifierSchema = ServiceFilterSpecifierSchema;
|
|
78968
79511
|
exports.ServiceService = ServiceService;
|
|
79512
|
+
exports.ServiceUniverseService = ServiceUniverseService;
|
|
78969
79513
|
exports.SessionService = SessionService;
|
|
78970
79514
|
exports.SimpleHTTPError = SimpleHTTPError;
|
|
78971
79515
|
exports.SituationService = SituationService;
|