@nexusm/sdk 5.0.0 → 5.2.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.d.mts +164 -20
- package/dist/index.d.ts +164 -20
- package/dist/index.js +109 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +108 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -163,6 +163,12 @@ var ValidationError = class extends ApiError {
|
|
|
163
163
|
this.details = details;
|
|
164
164
|
}
|
|
165
165
|
};
|
|
166
|
+
var UpstreamInterceptError = class extends ApiError {
|
|
167
|
+
constructor(message, statusCode, response) {
|
|
168
|
+
super(message, statusCode, response, "NEXUS_UPSTREAM_INTERCEPT");
|
|
169
|
+
this.name = "UpstreamInterceptError";
|
|
170
|
+
}
|
|
171
|
+
};
|
|
166
172
|
var NotFoundError = class extends ApiError {
|
|
167
173
|
constructor(message, response) {
|
|
168
174
|
super(message, 404, response, "NEXUS_NOT_FOUND_ERROR");
|
|
@@ -574,6 +580,35 @@ var OfflineQueue = class {
|
|
|
574
580
|
};
|
|
575
581
|
|
|
576
582
|
// src/http/client.ts
|
|
583
|
+
function redirectHost(location) {
|
|
584
|
+
if (typeof location !== "string" || location === "") return "an unknown host";
|
|
585
|
+
try {
|
|
586
|
+
return new URL(location).host;
|
|
587
|
+
} catch {
|
|
588
|
+
return "an unparseable location";
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function upstreamRedirectError(response, url) {
|
|
592
|
+
const headers = response.headers ?? {};
|
|
593
|
+
const host = redirectHost(headers.location ?? headers.Location);
|
|
594
|
+
return new UpstreamInterceptError(
|
|
595
|
+
`Request to ${url ?? "the API"} was redirected (HTTP ${response.status}) to ${host} instead of being answered by Nexus. This is typically an expired or missing edge credential (e.g. a Cloudflare Access service token) \u2014 the API itself was never reached.`,
|
|
596
|
+
response.status,
|
|
597
|
+
response.data
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
function assertNotIntercepted(response) {
|
|
601
|
+
if (response.config?.responseType === "text") return;
|
|
602
|
+
const headers = response.headers ?? {};
|
|
603
|
+
const raw = headers["content-type"] ?? headers["Content-Type"];
|
|
604
|
+
if (typeof raw !== "string" || raw === "") return;
|
|
605
|
+
if (raw.toLowerCase().includes("json")) return;
|
|
606
|
+
throw new UpstreamInterceptError(
|
|
607
|
+
`Request to ${response.config?.url ?? "the API"} returned HTTP ${response.status} with content-type "${raw}" where JSON was expected. Something between this client and Nexus answered the request (auth edge, proxy, or captive portal); treating it as data would look like an empty result.`,
|
|
608
|
+
response.status,
|
|
609
|
+
response.data
|
|
610
|
+
);
|
|
611
|
+
}
|
|
577
612
|
var HttpClient = class {
|
|
578
613
|
/**
|
|
579
614
|
* Create a new HTTP client.
|
|
@@ -591,7 +626,14 @@ var HttpClient = class {
|
|
|
591
626
|
}
|
|
592
627
|
this.axios = axios.create({
|
|
593
628
|
baseURL: config.baseUrl,
|
|
594
|
-
timeout: config.timeout
|
|
629
|
+
timeout: config.timeout,
|
|
630
|
+
// Never follow redirects (Kairos#66 / Aether#372). An auth edge such as
|
|
631
|
+
// Cloudflare Access answers an unauthenticated call with 302 → its own
|
|
632
|
+
// login page; following it yields a 200 with HTML, which is
|
|
633
|
+
// indistinguishable from an empty result to the caller. With
|
|
634
|
+
// maxRedirects: 0 the 3xx fails axios' status validation and reaches the
|
|
635
|
+
// error interceptor, which turns it into an UpstreamInterceptError.
|
|
636
|
+
maxRedirects: 0
|
|
595
637
|
});
|
|
596
638
|
this.setupRequestInterceptor();
|
|
597
639
|
this.setupResponseInterceptor();
|
|
@@ -727,6 +769,30 @@ var HttpClient = class {
|
|
|
727
769
|
this.cache.invalidate(path.split("/").filter(Boolean)[0] ?? path);
|
|
728
770
|
return result;
|
|
729
771
|
}
|
|
772
|
+
/**
|
|
773
|
+
* Send a GET request and return the raw response body as a string.
|
|
774
|
+
*
|
|
775
|
+
* Intended for file-download endpoints (e.g. `GET /dashboard/export`) that
|
|
776
|
+
* return `text/csv` or `application/json` as a raw file stream rather than a
|
|
777
|
+
* JSON-parsed object. The retry and auth interceptors still apply; the
|
|
778
|
+
* response cache is intentionally bypassed (export payloads are not cacheable
|
|
779
|
+
* at the SDK layer).
|
|
780
|
+
*
|
|
781
|
+
* @param path - URL path relative to the base URL (e.g. `/dashboard/export`).
|
|
782
|
+
* @param params - Optional query parameters.
|
|
783
|
+
* @param signal - Optional {@link AbortSignal} to cancel the request.
|
|
784
|
+
* @returns The raw response body as a string.
|
|
785
|
+
*/
|
|
786
|
+
async getText(path, params, signal) {
|
|
787
|
+
return this.retry.execute(async () => {
|
|
788
|
+
const response = await this.axios.get(path, {
|
|
789
|
+
params,
|
|
790
|
+
signal,
|
|
791
|
+
responseType: "text"
|
|
792
|
+
});
|
|
793
|
+
return response.data;
|
|
794
|
+
});
|
|
795
|
+
}
|
|
730
796
|
/**
|
|
731
797
|
* Send a DELETE request.
|
|
732
798
|
*
|
|
@@ -782,8 +848,11 @@ var HttpClient = class {
|
|
|
782
848
|
*/
|
|
783
849
|
setupResponseInterceptor() {
|
|
784
850
|
this.axios.interceptors.response.use(
|
|
785
|
-
// Success handler -- pass through
|
|
786
|
-
(response) =>
|
|
851
|
+
// Success handler -- pass through, except for a 2xx that is not JSON.
|
|
852
|
+
(response) => {
|
|
853
|
+
assertNotIntercepted(response);
|
|
854
|
+
return response;
|
|
855
|
+
},
|
|
787
856
|
// Error handler -- normalise into NexusError hierarchy
|
|
788
857
|
(error) => {
|
|
789
858
|
if (axios.isCancel(error)) {
|
|
@@ -797,6 +866,11 @@ var HttpClient = class {
|
|
|
797
866
|
)
|
|
798
867
|
);
|
|
799
868
|
}
|
|
869
|
+
if (error.response && error.response.status >= 300 && error.response.status < 400) {
|
|
870
|
+
return Promise.reject(
|
|
871
|
+
upstreamRedirectError(error.response, error.config?.url)
|
|
872
|
+
);
|
|
873
|
+
}
|
|
800
874
|
if (error.response) {
|
|
801
875
|
const apiError = ApiError.fromResponse(error.response);
|
|
802
876
|
const reqUrl = error.config?.url ?? "";
|
|
@@ -1343,6 +1417,35 @@ var ErrorService = class extends BaseService {
|
|
|
1343
1417
|
}
|
|
1344
1418
|
};
|
|
1345
1419
|
|
|
1420
|
+
// src/services/dashboard.ts
|
|
1421
|
+
var DashboardService = class extends BaseService {
|
|
1422
|
+
/**
|
|
1423
|
+
* Export a dashboard dataset as raw file content.
|
|
1424
|
+
*
|
|
1425
|
+
* Sends `GET /dashboard/export` with the given query parameters and returns
|
|
1426
|
+
* the raw response body as a string. The string is exactly the file the
|
|
1427
|
+
* server would send for a browser download:
|
|
1428
|
+
* - `format: 'csv'` (default) → comma-separated text
|
|
1429
|
+
* - `format: 'json'` → JSON text (not parsed into an object)
|
|
1430
|
+
*
|
|
1431
|
+
* @param params - Dataset selection and format options.
|
|
1432
|
+
* @param options - Optional request options (e.g. AbortSignal).
|
|
1433
|
+
* @returns Raw file body string.
|
|
1434
|
+
*
|
|
1435
|
+
* @throws {ApiError} HTTP 401 — missing or invalid API key.
|
|
1436
|
+
* @throws {ApiError} HTTP 403 — `target_tenant_id` requires admin scope.
|
|
1437
|
+
* @throws {ApiError} HTTP 422 — `dataset` is not one of the six whitelisted values.
|
|
1438
|
+
*/
|
|
1439
|
+
async export(params, options) {
|
|
1440
|
+
const query = { dataset: params.dataset };
|
|
1441
|
+
if (params.format !== void 0) query["format"] = params.format;
|
|
1442
|
+
if (params.target_tenant_id !== void 0) {
|
|
1443
|
+
query["target_tenant_id"] = params.target_tenant_id;
|
|
1444
|
+
}
|
|
1445
|
+
return this.http.getText("/dashboard/export", query, options?.signal);
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
|
|
1346
1449
|
// src/client.ts
|
|
1347
1450
|
var NexusClient = class {
|
|
1348
1451
|
/**
|
|
@@ -1364,6 +1467,7 @@ var NexusClient = class {
|
|
|
1364
1467
|
this.tenants = new TenantService(this.http);
|
|
1365
1468
|
this.feedback = new FeedbackService(this.http);
|
|
1366
1469
|
this.errors = new ErrorService(this.http);
|
|
1470
|
+
this.dashboard = new DashboardService(this.http);
|
|
1367
1471
|
if (resolved.autoErrorReport) {
|
|
1368
1472
|
this.http.onApiError = (statusCode, method, url, detail) => {
|
|
1369
1473
|
this.errors.submit({
|
|
@@ -1422,6 +1526,7 @@ export {
|
|
|
1422
1526
|
RateLimitError,
|
|
1423
1527
|
TenantService,
|
|
1424
1528
|
TimeoutError,
|
|
1529
|
+
UpstreamInterceptError,
|
|
1425
1530
|
ValidationError,
|
|
1426
1531
|
apiKeyCreateSchema,
|
|
1427
1532
|
contextRequestSchema,
|