@dx-do/client 6.2.2 → 6.3.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 +425 -22
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +426 -23
- package/dist/index.esm.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/lib/model/oi/services.create.d.ts +2 -2
- package/dist/src/lib/model/oi/services.create.d.ts.map +1 -1
- package/dist/src/lib/model/oi/services.export.d.ts +27 -0
- package/dist/src/lib/model/oi/services.export.d.ts.map +1 -0
- package/dist/src/lib/model/oi/services.retrieve.d.ts +85 -0
- package/dist/src/lib/model/oi/services.retrieve.d.ts.map +1 -0
- package/dist/src/lib/model/oi/services.transform.d.ts +66 -0
- package/dist/src/lib/model/oi/services.transform.d.ts.map +1 -0
- package/dist/src/lib/services/logs.service.d.ts.map +1 -1
- package/dist/src/lib/services/service.service.d.ts +99 -2
- package/dist/src/lib/services/service.service.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -45995,6 +45995,226 @@ exports.OIAlarm = void 0;
|
|
|
45995
45995
|
};
|
|
45996
45996
|
})(exports.OIAlarm || (exports.OIAlarm = {}));
|
|
45997
45997
|
|
|
45998
|
+
/**
|
|
45999
|
+
* The `service export` / `service export-hierarchy` file format and the
|
|
46000
|
+
* `service import` boundary validator.
|
|
46001
|
+
*
|
|
46002
|
+
* The envelope wraps a re-postable store body ({@link CreateServices.CreateServiceBody})
|
|
46003
|
+
* with provenance metadata. `import` accepts either the envelope or a bare body.
|
|
46004
|
+
*/
|
|
46005
|
+
exports.ServiceExport = void 0;
|
|
46006
|
+
(function (ServiceExport) {
|
|
46007
|
+
ServiceExport.SCHEMA_VERSION = 1;
|
|
46008
|
+
// Structural guard for a store body — does not re-validate every field
|
|
46009
|
+
// (the server is the authority); just enough to reject obviously-wrong files.
|
|
46010
|
+
const BodyShape = v4.z.looseObject({
|
|
46011
|
+
vertices: v4.z.array(v4.z.looseObject({ attributes: v4.z.looseObject({ name: v4.z.string() }) })),
|
|
46012
|
+
edges: v4.z.array(v4.z.looseObject({
|
|
46013
|
+
sourceExternalId: v4.z.string(),
|
|
46014
|
+
targetExternalId: v4.z.string(),
|
|
46015
|
+
})),
|
|
46016
|
+
});
|
|
46017
|
+
const EnvelopeShape = v4.z.looseObject({
|
|
46018
|
+
schemaVersion: v4.z.literal(1),
|
|
46019
|
+
kind: v4.z.enum(['service', 'service-hierarchy']),
|
|
46020
|
+
sourceTenant: v4.z.string().optional(),
|
|
46021
|
+
exportedAt: v4.z.string(),
|
|
46022
|
+
body: BodyShape,
|
|
46023
|
+
});
|
|
46024
|
+
/** Wrap a store body in an export envelope. */
|
|
46025
|
+
function envelope(kind, body, sourceTenant, exportedAt) {
|
|
46026
|
+
return { schemaVersion: ServiceExport.SCHEMA_VERSION, kind, sourceTenant, exportedAt, body };
|
|
46027
|
+
}
|
|
46028
|
+
ServiceExport.envelope = envelope;
|
|
46029
|
+
/**
|
|
46030
|
+
* Validate an import file (already JSON-parsed) and return the store body.
|
|
46031
|
+
* Accepts a full export envelope or a bare `{ vertices, edges }` body.
|
|
46032
|
+
* @throws Error when the input is neither shape.
|
|
46033
|
+
*/
|
|
46034
|
+
function parseImport(raw) {
|
|
46035
|
+
const asEnvelope = EnvelopeShape.safeParse(raw);
|
|
46036
|
+
if (asEnvelope.success) {
|
|
46037
|
+
return asEnvelope.data.body;
|
|
46038
|
+
}
|
|
46039
|
+
const asBody = BodyShape.safeParse(raw);
|
|
46040
|
+
if (asBody.success) {
|
|
46041
|
+
return raw;
|
|
46042
|
+
}
|
|
46043
|
+
throw new Error('Import file is neither a service export envelope nor a bare {vertices, edges} body. ' +
|
|
46044
|
+
`Envelope error: ${asEnvelope.error.message}`);
|
|
46045
|
+
}
|
|
46046
|
+
ServiceExport.parseImport = parseImport;
|
|
46047
|
+
})(exports.ServiceExport || (exports.ServiceExport = {}));
|
|
46048
|
+
|
|
46049
|
+
/**
|
|
46050
|
+
* Pure (HTTP-free) transforms between the Service read shapes and the store
|
|
46051
|
+
* (`/oi/v2/sa/save`) body. Kept separate from {@link ServiceService} so the
|
|
46052
|
+
* edge/weight logic — the part with real correctness risk — is unit-testable.
|
|
46053
|
+
*
|
|
46054
|
+
* Empirical constraints these helpers encode (see plan Phase 0):
|
|
46055
|
+
* - A parent's direct-child health/risk weights must each total ≤ 1.0 (server rejects otherwise).
|
|
46056
|
+
* - `replace=true` makes each saved vertex's outgoing-edge set authoritative, so a
|
|
46057
|
+
* faithful re-save must carry the whole subtree's vertices + edges.
|
|
46058
|
+
*/
|
|
46059
|
+
exports.ServiceTransform = void 0;
|
|
46060
|
+
(function (ServiceTransform) {
|
|
46061
|
+
/**
|
|
46062
|
+
* Convert a retrieved (read) service vertex into a re-postable store vertex.
|
|
46063
|
+
*
|
|
46064
|
+
* @remarks externalId is keyed by the service **name**, not the server
|
|
46065
|
+
* `SA:<uuid>` id. The store API treats `externalId` as a payload-local handle
|
|
46066
|
+
* and resolves/creates services by `attributes.name`; keying edges by `SA:uuid`
|
|
46067
|
+
* makes the server silently drop them (so a `replace` save wipes the existing
|
|
46068
|
+
* edges and re-adds nothing). Names are tenant-unique, so this is unambiguous
|
|
46069
|
+
* and also makes exports portable across tenants.
|
|
46070
|
+
*/
|
|
46071
|
+
function retrievedToServiceVertex(v) {
|
|
46072
|
+
const a = v.attributes;
|
|
46073
|
+
return {
|
|
46074
|
+
externalId: a.name,
|
|
46075
|
+
attributes: {
|
|
46076
|
+
type: 'saService',
|
|
46077
|
+
name: a.name,
|
|
46078
|
+
state: 'ACTIVE',
|
|
46079
|
+
maintenance: a.maintenance ?? false,
|
|
46080
|
+
situationsIncludeChildServices: a.situationsIncludeChildServices ?? false,
|
|
46081
|
+
serviceContent: a.serviceContent ?? [],
|
|
46082
|
+
root_service: null,
|
|
46083
|
+
tags: a.tags ?? [],
|
|
46084
|
+
location: a.location ?? '',
|
|
46085
|
+
description: a.description ?? '',
|
|
46086
|
+
customProperties: a.customProperties ?? [],
|
|
46087
|
+
customMetrics: a.customMetrics ?? [],
|
|
46088
|
+
metrics: a.metrics ?? [],
|
|
46089
|
+
},
|
|
46090
|
+
};
|
|
46091
|
+
}
|
|
46092
|
+
ServiceTransform.retrievedToServiceVertex = retrievedToServiceVertex;
|
|
46093
|
+
/**
|
|
46094
|
+
* Build NAME-keyed store edges from a topology graph, preserving weights +
|
|
46095
|
+
* semantic. Endpoints are mapped from `SA:<uuid>` to service name via the
|
|
46096
|
+
* graph's vertices; edges whose endpoints aren't in `topo.vertices` are
|
|
46097
|
+
* dropped — this is exactly the incoming edge(s) from the root's parent, which
|
|
46098
|
+
* `saTopologyGraph` returns without the parent vertex (and which must not be
|
|
46099
|
+
* re-posted: the parent isn't re-saved, so its edge survives anyway).
|
|
46100
|
+
*/
|
|
46101
|
+
function edgesFromTopology(topo) {
|
|
46102
|
+
const idToName = new Map(topo.vertices.map((v) => [v.externalId, v.attributes.name]));
|
|
46103
|
+
return topo.edges
|
|
46104
|
+
.map((e) => ({
|
|
46105
|
+
s: idToName.get(e.sourceExternalId),
|
|
46106
|
+
t: idToName.get(e.targetExternalId),
|
|
46107
|
+
e,
|
|
46108
|
+
}))
|
|
46109
|
+
.filter((x) => x.s !== undefined && x.t !== undefined)
|
|
46110
|
+
.map((x) => ({
|
|
46111
|
+
sourceExternalId: x.s,
|
|
46112
|
+
targetExternalId: x.t,
|
|
46113
|
+
attributes: {
|
|
46114
|
+
health_weight: x.e.attributes?.health_weight ?? 1,
|
|
46115
|
+
risk_weight: x.e.attributes?.risk_weight ?? 1,
|
|
46116
|
+
semantic: 'AggregateOf',
|
|
46117
|
+
},
|
|
46118
|
+
}));
|
|
46119
|
+
}
|
|
46120
|
+
ServiceTransform.edgesFromTopology = edgesFromTopology;
|
|
46121
|
+
/** Merge two store bodies, de-duplicating vertices by externalId and edges by endpoints. */
|
|
46122
|
+
function mergeBodies(a, b) {
|
|
46123
|
+
const vertices = [];
|
|
46124
|
+
const seenV = new Set();
|
|
46125
|
+
for (const v of [...a.vertices, ...b.vertices]) {
|
|
46126
|
+
const key = v.externalId ?? v.attributes.name;
|
|
46127
|
+
if (!seenV.has(key)) {
|
|
46128
|
+
seenV.add(key);
|
|
46129
|
+
vertices.push(v);
|
|
46130
|
+
}
|
|
46131
|
+
}
|
|
46132
|
+
const edges = [];
|
|
46133
|
+
const seenE = new Set();
|
|
46134
|
+
for (const e of [...a.edges, ...b.edges]) {
|
|
46135
|
+
const key = `${e.sourceExternalId}|${e.targetExternalId}`;
|
|
46136
|
+
if (!seenE.has(key)) {
|
|
46137
|
+
seenE.add(key);
|
|
46138
|
+
edges.push(e);
|
|
46139
|
+
}
|
|
46140
|
+
}
|
|
46141
|
+
return { vertices, edges };
|
|
46142
|
+
}
|
|
46143
|
+
ServiceTransform.mergeBodies = mergeBodies;
|
|
46144
|
+
/** Insert or update the parent→child edge with the given weights. */
|
|
46145
|
+
function upsertEdge(edges, source, target, healthWeight, riskWeight) {
|
|
46146
|
+
const next = edges.filter((e) => !(e.sourceExternalId === source && e.targetExternalId === target));
|
|
46147
|
+
next.push({
|
|
46148
|
+
sourceExternalId: source,
|
|
46149
|
+
targetExternalId: target,
|
|
46150
|
+
attributes: {
|
|
46151
|
+
health_weight: healthWeight,
|
|
46152
|
+
risk_weight: riskWeight,
|
|
46153
|
+
semantic: 'AggregateOf',
|
|
46154
|
+
},
|
|
46155
|
+
});
|
|
46156
|
+
return next;
|
|
46157
|
+
}
|
|
46158
|
+
ServiceTransform.upsertEdge = upsertEdge;
|
|
46159
|
+
/** Remove the parent→child edge. */
|
|
46160
|
+
function removeEdge(edges, source, target) {
|
|
46161
|
+
return edges.filter((e) => !(e.sourceExternalId === source && e.targetExternalId === target));
|
|
46162
|
+
}
|
|
46163
|
+
ServiceTransform.removeEdge = removeEdge;
|
|
46164
|
+
/** Edges whose source is the given parent (its direct-child associations). */
|
|
46165
|
+
function childEdges(edges, parent) {
|
|
46166
|
+
return edges.filter((e) => e.sourceExternalId === parent);
|
|
46167
|
+
}
|
|
46168
|
+
ServiceTransform.childEdges = childEdges;
|
|
46169
|
+
/**
|
|
46170
|
+
* Keep only edges whose BOTH endpoints are in the given vertex-id set.
|
|
46171
|
+
*
|
|
46172
|
+
* `saTopologyGraph(root)` returns the root's subtree plus the *incoming* edge(s)
|
|
46173
|
+
* from the root's parent(s) — but not the parent vertices. Re-posting such a
|
|
46174
|
+
* dangling edge is rejected ("vertex referenced by edge is not present in the
|
|
46175
|
+
* input graph"). Dropping it is also correct: the parent is not re-saved, so its
|
|
46176
|
+
* edge to the root is preserved server-side (replace is destructive only per
|
|
46177
|
+
* saved vertex).
|
|
46178
|
+
*/
|
|
46179
|
+
function filterEdgesToVertices(edges, vertexIds) {
|
|
46180
|
+
return edges.filter((e) => vertexIds.has(e.sourceExternalId) && vertexIds.has(e.targetExternalId));
|
|
46181
|
+
}
|
|
46182
|
+
ServiceTransform.filterEdgesToVertices = filterEdgesToVertices;
|
|
46183
|
+
/**
|
|
46184
|
+
* Evenly distribute a parent's direct-child weights so each total is ≤ 1.0.
|
|
46185
|
+
* Uses floored thirds (e.g. 3 children → 0.333 each, sum 0.999) to avoid
|
|
46186
|
+
* float drift pushing the total over 100%.
|
|
46187
|
+
*/
|
|
46188
|
+
function rebalanceChildren(edges, parent) {
|
|
46189
|
+
const kids = childEdges(edges, parent);
|
|
46190
|
+
if (kids.length === 0)
|
|
46191
|
+
return edges;
|
|
46192
|
+
const w = Math.floor((1 / kids.length) * 1000) / 1000;
|
|
46193
|
+
return edges.map((e) => e.sourceExternalId === parent
|
|
46194
|
+
? {
|
|
46195
|
+
...e,
|
|
46196
|
+
attributes: {
|
|
46197
|
+
health_weight: w,
|
|
46198
|
+
risk_weight: w,
|
|
46199
|
+
semantic: 'AggregateOf',
|
|
46200
|
+
},
|
|
46201
|
+
}
|
|
46202
|
+
: e);
|
|
46203
|
+
}
|
|
46204
|
+
ServiceTransform.rebalanceChildren = rebalanceChildren;
|
|
46205
|
+
/** Throw if a parent's direct-child weights exceed 100% (the server's hard limit). */
|
|
46206
|
+
function assertWeightsWithinLimit(edges, parent) {
|
|
46207
|
+
const kids = childEdges(edges, parent);
|
|
46208
|
+
const health = kids.reduce((s, e) => s + (e.attributes?.health_weight ?? 0), 0);
|
|
46209
|
+
const risk = kids.reduce((s, e) => s + (e.attributes?.risk_weight ?? 0), 0);
|
|
46210
|
+
const eps = 1e-6;
|
|
46211
|
+
if (health > 1 + eps || risk > 1 + eps) {
|
|
46212
|
+
throw new Error(`Weight total for parent exceeds 100% (health=${health.toFixed(3)}, risk=${risk.toFixed(3)}). Lower the weights or omit them to auto-distribute evenly.`);
|
|
46213
|
+
}
|
|
46214
|
+
}
|
|
46215
|
+
ServiceTransform.assertWeightsWithinLimit = assertWeightsWithinLimit;
|
|
46216
|
+
})(exports.ServiceTransform || (exports.ServiceTransform = {}));
|
|
46217
|
+
|
|
45998
46218
|
exports.OIService = void 0;
|
|
45999
46219
|
(function (OIService) {
|
|
46000
46220
|
/** @internal */
|
|
@@ -73721,6 +73941,18 @@ function detectIPv4() {
|
|
|
73721
73941
|
}
|
|
73722
73942
|
return '127.0.0.1';
|
|
73723
73943
|
}
|
|
73944
|
+
/**
|
|
73945
|
+
* Coalesce a missing *or empty* value to a fallback.
|
|
73946
|
+
*
|
|
73947
|
+
* `??` only catches `null`/`undefined`, but an explicit `""` is a valid string
|
|
73948
|
+
* that would flow straight through. That breaks `temp_fields`: a blank component
|
|
73949
|
+
* collapses one of its space-separated slots, shifting every later field's
|
|
73950
|
+
* position when the SaaS Kafka consumer parses `agentbase_msg`. Treat `""`
|
|
73951
|
+
* exactly like an omitted value.
|
|
73952
|
+
*/
|
|
73953
|
+
function orDefault(value, fallback) {
|
|
73954
|
+
return value ? value : fallback;
|
|
73955
|
+
}
|
|
73724
73956
|
/**
|
|
73725
73957
|
* Service for querying and ingesting log analytics data.
|
|
73726
73958
|
*/
|
|
@@ -73749,14 +73981,17 @@ class LogsService {
|
|
|
73749
73981
|
exports.LogIngest.LogIngestOptionsSchema.parse(options);
|
|
73750
73982
|
const cohortId = this.dxSaasService.dxdoConfiguration.dxConfiguration.cohortId;
|
|
73751
73983
|
const tenantIdUpper = cohortId.toUpperCase();
|
|
73752
|
-
|
|
73753
|
-
|
|
73754
|
-
|
|
73755
|
-
const
|
|
73756
|
-
const
|
|
73757
|
-
const
|
|
73758
|
-
const
|
|
73759
|
-
const
|
|
73984
|
+
// `orDefault` (not `??`) so an explicit empty string is treated as omitted —
|
|
73985
|
+
// a blank value in any temp_fields component shifts every downstream
|
|
73986
|
+
// positional field. See the temp_fields comment below.
|
|
73987
|
+
const logtype = orDefault(options.logtype, 'generic');
|
|
73988
|
+
const host = orDefault(options.host, require$$0$4.hostname());
|
|
73989
|
+
const ip = orDefault(options.ip, detectIPv4());
|
|
73990
|
+
const agentInstance = orDefault(options.agentInstance, 'dx-do');
|
|
73991
|
+
const agentName = orDefault(options.agentName, 'Logs Collector');
|
|
73992
|
+
const containerId = orDefault(options.containerId, host);
|
|
73993
|
+
const containerName = orDefault(options.containerName, agentInstance);
|
|
73994
|
+
const file = orDefault(options.file, 'dx-do.cli');
|
|
73760
73995
|
const tag = `${logtype} logs`;
|
|
73761
73996
|
// temp_fields must have exactly 9 space-separated components before the message
|
|
73762
73997
|
// is appended by the SaaS filter (agentbase_msg = temp_fields + " " + message).
|
|
@@ -75427,19 +75662,90 @@ class ServiceService {
|
|
|
75427
75662
|
};
|
|
75428
75663
|
return this.dxSaaSService.oiPost('oi/v2/sa/serviceDetails', serviceDetailRequest);
|
|
75429
75664
|
}
|
|
75665
|
+
/**
|
|
75666
|
+
* Retrieve a Service — the full read-endpoint envelope (vertices + edges +
|
|
75667
|
+
* paging metadata). Backs the `service detail` and `service export` commands.
|
|
75668
|
+
*
|
|
75669
|
+
* @param serviceName - Service name (URL path segment).
|
|
75670
|
+
* @param subservices - When true, include the service's subservices in the response.
|
|
75671
|
+
*/
|
|
75672
|
+
async getService(serviceName, subservices = false) {
|
|
75673
|
+
return this.dxSaaSService.oiGet(`oi/v2/sa/services/${serviceName}`, { subservices });
|
|
75674
|
+
}
|
|
75675
|
+
/**
|
|
75676
|
+
* Returns the full reachable subtree (all descendants + association edges with
|
|
75677
|
+
* weights) for a service via `oi/v2/sa/saTopologyGraph`. Backs the hierarchy
|
|
75678
|
+
* read used by `export-hierarchy`/`associate`/`dissociate`.
|
|
75679
|
+
*
|
|
75680
|
+
* @remarks The documented `oi/v2/sa/service/details/hierarchy` path is not
|
|
75681
|
+
* routable on the tenant gateway; this topology endpoint is the working one.
|
|
75682
|
+
* Its vertex attributes are a subset — use {@link getService} per vertex when
|
|
75683
|
+
* faithful re-save attributes (tags, customProperties, metrics) are needed.
|
|
75684
|
+
*/
|
|
75685
|
+
async getServiceTopology(serviceName) {
|
|
75686
|
+
return this.dxSaaSService.oiPost('oi/v2/sa/saTopologyGraph', { serviceName, startTime: Date.now(), endTime: Date.now() });
|
|
75687
|
+
}
|
|
75688
|
+
/**
|
|
75689
|
+
* Builds a faithful, re-postable store body for a service's entire subtree:
|
|
75690
|
+
* topology for structure + a per-vertex {@link getService} for full attributes
|
|
75691
|
+
* (serviceContent, tags, customProperties, metrics). Shared by
|
|
75692
|
+
* `export-hierarchy`, `associate`, and `dissociate`.
|
|
75693
|
+
*
|
|
75694
|
+
* @remarks One GET per vertex — fine for typical (small) hierarchies; reads
|
|
75695
|
+
* are not throttled like saves.
|
|
75696
|
+
*/
|
|
75697
|
+
async buildHierarchyBody(rootName) {
|
|
75698
|
+
const topo = await this.getServiceTopology(rootName);
|
|
75699
|
+
const names = new Set([rootName]);
|
|
75700
|
+
topo.vertices.forEach((v) => names.add(v.attributes.name));
|
|
75701
|
+
const vertices = [];
|
|
75702
|
+
const seen = new Set();
|
|
75703
|
+
for (const name of names) {
|
|
75704
|
+
const full = await this.getService(name, false);
|
|
75705
|
+
const fv = full.vertices[0];
|
|
75706
|
+
if (fv && !seen.has(fv.externalId)) {
|
|
75707
|
+
seen.add(fv.externalId);
|
|
75708
|
+
vertices.push(exports.ServiceTransform.retrievedToServiceVertex(fv));
|
|
75709
|
+
}
|
|
75710
|
+
}
|
|
75711
|
+
// Drop edges that reference vertices outside this subtree (e.g. the incoming
|
|
75712
|
+
// edge from the root's parent, which saTopologyGraph returns without the
|
|
75713
|
+
// parent vertex). Re-posting such a dangling edge is rejected by the server.
|
|
75714
|
+
const presentIds = new Set(vertices
|
|
75715
|
+
.map((v) => v.externalId)
|
|
75716
|
+
.filter((id) => id !== undefined));
|
|
75717
|
+
const edges = exports.ServiceTransform.filterEdgesToVertices(exports.ServiceTransform.edgesFromTopology(topo), presentIds);
|
|
75718
|
+
return { vertices, edges };
|
|
75719
|
+
}
|
|
75720
|
+
/** Builds a single-service store body (no edges) for `service export`. */
|
|
75721
|
+
async exportServiceBody(serviceName) {
|
|
75722
|
+
const r = await this.getService(serviceName, false);
|
|
75723
|
+
if (!r.vertices.length) {
|
|
75724
|
+
throw new Error(`No service with name '${serviceName}' found`);
|
|
75725
|
+
}
|
|
75726
|
+
return {
|
|
75727
|
+
vertices: [exports.ServiceTransform.retrievedToServiceVertex(r.vertices[0])],
|
|
75728
|
+
edges: [],
|
|
75729
|
+
};
|
|
75730
|
+
}
|
|
75731
|
+
/**
|
|
75732
|
+
* Resolves a service's server externalId (`SA:<cohort>:<uuid>`).
|
|
75733
|
+
* @throws Error if the service does not exist.
|
|
75734
|
+
*/
|
|
75735
|
+
async getServiceExternalId(serviceName) {
|
|
75736
|
+
const r = await this.getService(serviceName, false);
|
|
75737
|
+
const id = r.vertices[0]?.externalId;
|
|
75738
|
+
if (!id)
|
|
75739
|
+
throw new Error(`No service with name '${serviceName}' found`);
|
|
75740
|
+
return id;
|
|
75741
|
+
}
|
|
75430
75742
|
/** Returns the overview vertex for a named service; throws if not found. */
|
|
75431
75743
|
async getServiceOverview(serviceName) {
|
|
75432
|
-
|
|
75433
|
-
|
|
75434
|
-
|
|
75435
|
-
}
|
|
75436
|
-
|
|
75437
|
-
if (graph.vertices.length == 1) {
|
|
75438
|
-
return graph.vertices[0];
|
|
75439
|
-
}
|
|
75440
|
-
else
|
|
75441
|
-
throw new Error(`No Service with name '${serviceName}' Found`);
|
|
75442
|
-
});
|
|
75744
|
+
const response = await this.getService(serviceName, false);
|
|
75745
|
+
if (response.vertices.length === 1) {
|
|
75746
|
+
return response.vertices[0];
|
|
75747
|
+
}
|
|
75748
|
+
throw new Error(`No Service with name '${serviceName}' Found`);
|
|
75443
75749
|
}
|
|
75444
75750
|
/** Returns paginated top-level (grouped) services up to `maxRequests` pages. */
|
|
75445
75751
|
async getTopLevelServices(pageSize = 100, maxRequests = 20) {
|
|
@@ -75529,12 +75835,109 @@ class ServiceService {
|
|
|
75529
75835
|
sirb.customFilter.and.expressions[0].or.expressions[0].value = serviceName;
|
|
75530
75836
|
return sirb;
|
|
75531
75837
|
}
|
|
75532
|
-
/**
|
|
75533
|
-
|
|
75534
|
-
|
|
75838
|
+
/**
|
|
75839
|
+
* Creates/updates one or more services via the store API (`/oi/v2/sa/save`).
|
|
75840
|
+
*
|
|
75841
|
+
* @remarks Edges not present in the payload are removed for the services it
|
|
75842
|
+
* covers — the store API treats the supplied edge set as authoritative.
|
|
75843
|
+
* @param createServiceBody - vertices + edges to store.
|
|
75844
|
+
* @param opts.replace - When true (default), replaces an existing service of the
|
|
75845
|
+
* same name rather than erroring.
|
|
75846
|
+
* @param opts.autoWeight - When true, the server computes edge health/risk weights,
|
|
75847
|
+
* overriding any weights supplied in the payload. Omitted by default.
|
|
75848
|
+
*/
|
|
75849
|
+
async createService(createServiceBody, opts = {}) {
|
|
75850
|
+
const params = { replace: opts.replace ?? true };
|
|
75851
|
+
if (opts.autoWeight !== undefined) {
|
|
75852
|
+
params['auto_weight'] = opts.autoWeight;
|
|
75853
|
+
}
|
|
75854
|
+
return this.dxSaaSService.tenantPost('/oi/v2/sa/save', createServiceBody, params);
|
|
75855
|
+
}
|
|
75856
|
+
/**
|
|
75857
|
+
* Builds the store body for associating `childName` under `parentName` as a
|
|
75858
|
+
* new `AggregateOf` child, WITHOUT saving (so callers can dry-run it).
|
|
75859
|
+
*
|
|
75860
|
+
* Because `replace=true` makes each saved vertex's edge set authoritative, the
|
|
75861
|
+
* body carries both services' full subtrees. Weights: explicit
|
|
75862
|
+
* `healthWeight`/`riskWeight` are applied to the new edge and validated against
|
|
75863
|
+
* the ≤100% limit; otherwise the parent's direct children are evenly rebalanced.
|
|
75864
|
+
*/
|
|
75865
|
+
async buildAssociateBody(parentName, childName, opts = {}) {
|
|
75866
|
+
if (parentName === childName) {
|
|
75867
|
+
throw new Error(`A service cannot be associated with itself ('${parentName}')`);
|
|
75868
|
+
}
|
|
75869
|
+
// Validate both services exist (throws "No service with name … found").
|
|
75870
|
+
await this.getServiceExternalId(parentName);
|
|
75871
|
+
await this.getServiceExternalId(childName);
|
|
75872
|
+
// Bodies are keyed by service name (see ServiceTransform.retrievedToServiceVertex).
|
|
75873
|
+
const body = exports.ServiceTransform.mergeBodies(await this.buildHierarchyBody(parentName), await this.buildHierarchyBody(childName));
|
|
75874
|
+
const explicit = opts.healthWeight !== undefined || opts.riskWeight !== undefined;
|
|
75875
|
+
body.edges = exports.ServiceTransform.upsertEdge(body.edges, parentName, childName, opts.healthWeight ?? 1, opts.riskWeight ?? 1);
|
|
75876
|
+
if (explicit) {
|
|
75877
|
+
exports.ServiceTransform.assertWeightsWithinLimit(body.edges, parentName);
|
|
75878
|
+
}
|
|
75879
|
+
else {
|
|
75880
|
+
body.edges = exports.ServiceTransform.rebalanceChildren(body.edges, parentName);
|
|
75881
|
+
}
|
|
75882
|
+
return body;
|
|
75883
|
+
}
|
|
75884
|
+
/** Associates `childName` under `parentName` and saves it. See {@link buildAssociateBody}. */
|
|
75885
|
+
async associateServices(parentName, childName, opts = {}) {
|
|
75886
|
+
const body = await this.buildAssociateBody(parentName, childName, opts);
|
|
75887
|
+
return this.createService(body, {
|
|
75535
75888
|
replace: true,
|
|
75889
|
+
autoWeight: opts.autoWeight,
|
|
75536
75890
|
});
|
|
75537
75891
|
}
|
|
75892
|
+
/**
|
|
75893
|
+
* Builds the store body for removing the `parentName`→`childName` association,
|
|
75894
|
+
* WITHOUT saving. The child (and its own subtree) is preserved as a now-detached
|
|
75895
|
+
* service.
|
|
75896
|
+
* @throws Error if `childName` is not a direct child of `parentName`.
|
|
75897
|
+
*/
|
|
75898
|
+
async buildDissociateBody(parentName, childName) {
|
|
75899
|
+
// Validate both services exist (throws "No service with name … found").
|
|
75900
|
+
await this.getServiceExternalId(parentName);
|
|
75901
|
+
await this.getServiceExternalId(childName);
|
|
75902
|
+
const body = await this.buildHierarchyBody(parentName);
|
|
75903
|
+
const before = body.edges.length;
|
|
75904
|
+
body.edges = exports.ServiceTransform.removeEdge(body.edges, parentName, childName);
|
|
75905
|
+
if (body.edges.length === before) {
|
|
75906
|
+
throw new Error(`'${childName}' is not a direct child of '${parentName}' — nothing to dissociate`);
|
|
75907
|
+
}
|
|
75908
|
+
return body;
|
|
75909
|
+
}
|
|
75910
|
+
/** Removes the `parentName`→`childName` association and saves it. See {@link buildDissociateBody}. */
|
|
75911
|
+
async dissociateServices(parentName, childName) {
|
|
75912
|
+
const body = await this.buildDissociateBody(parentName, childName);
|
|
75913
|
+
return this.createService(body, { replace: true });
|
|
75914
|
+
}
|
|
75915
|
+
/**
|
|
75916
|
+
* Builds the store body for updating a service's content queries, WITHOUT
|
|
75917
|
+
* saving. Re-saves the service's full subtree (via {@link buildHierarchyBody})
|
|
75918
|
+
* so its children edges are preserved — only the target vertex's
|
|
75919
|
+
* `serviceContent` changes.
|
|
75920
|
+
*
|
|
75921
|
+
* @param mode - 'set' replaces the content; 'add' appends to the existing content.
|
|
75922
|
+
* @returns the store body plus the service's previous content (for diffing).
|
|
75923
|
+
* @throws Error if the service does not exist.
|
|
75924
|
+
*/
|
|
75925
|
+
async buildServiceContentBody(serviceName, newContent, mode) {
|
|
75926
|
+
const body = await this.buildHierarchyBody(serviceName);
|
|
75927
|
+
const target = body.vertices.find((v) => v.attributes.name === serviceName);
|
|
75928
|
+
if (!target) {
|
|
75929
|
+
throw new Error(`No service with name '${serviceName}' found`);
|
|
75930
|
+
}
|
|
75931
|
+
const previousContent = target.attributes.serviceContent;
|
|
75932
|
+
target.attributes.serviceContent =
|
|
75933
|
+
mode === 'set' ? newContent : [...previousContent, ...newContent];
|
|
75934
|
+
return { body, previousContent };
|
|
75935
|
+
}
|
|
75936
|
+
/** Updates a service's content queries and saves it. See {@link buildServiceContentBody}. */
|
|
75937
|
+
async updateServiceContent(serviceName, newContent, mode) {
|
|
75938
|
+
const { body } = await this.buildServiceContentBody(serviceName, newContent, mode);
|
|
75939
|
+
return this.createService(body, { replace: true });
|
|
75940
|
+
}
|
|
75538
75941
|
/** Deletes a service by name; optionally deletes child services recursively. */
|
|
75539
75942
|
async deleteService(serviceName, recursive = false) {
|
|
75540
75943
|
return this.dxSaaSService.tenantPost('/oi/v2/sa/service/status', { name: serviceName, action: 'delete' }, { recursive });
|