@jupiterone/jupiterone-mcp
Version:
Model Context Protocol server for JupiterOne account rules and rule details
90 lines • 3.85 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.J1qlService = exports.J1qlQueryTimeoutError = void 0;
const queries_js_1 = require("../graphql/queries.js");
const appUrl_js_1 = require("../../utils/appUrl.js");
/**
* Thrown when a deferred query is still running after the poll budget is exhausted. A distinct type
* lets the widget pre-create validation gate treat a timeout as advisory (proceed) while a real
* query error still blocks — without changing execute-j1ql-query's own timeout handling.
*/
class J1qlQueryTimeoutError extends Error {
constructor(seconds) {
super(`Query timed out after ${seconds} seconds`);
this.name = 'J1qlQueryTimeoutError';
}
}
exports.J1qlQueryTimeoutError = J1qlQueryTimeoutError;
class J1qlService {
client;
abortSignal;
constructor(client, abortSignal) {
this.client = client;
this.abortSignal = abortSignal;
}
/**
* Construct query results URL based on subdomain
*/
constructQueryUrl(query, subdomain) {
const params = new URLSearchParams({ q: query });
return (0, appUrl_js_1.constructAppUrl)(`home?${params.toString()}`, subdomain);
}
/**
* Execute a J1QL query
*/
async executeJ1qlQuery({ query, variables, cursor, scopeFilters, flags, maxPollAttempts, }) {
// Use queryV2 for better error messages
const response = await this.client.request(queries_js_1.QUERY_V2, {
query,
variables,
cursor,
scopeFilters,
includeDeleted: flags?.includeDeleted,
returnRowMetadata: flags?.returnRowMeta,
returnComputedProperties: flags?.computedProperties,
});
// Handle deferred response
if (response.queryV2.type === 'deferred' && response.queryV2.url) {
// Poll the URL until results are ready
const maxAttempts = maxPollAttempts ?? 60; // 60 attempts = 1 minute max wait
const pollInterval = 1000; // 1 second between polls
for (let attempt = 0; attempt < maxAttempts; attempt++) {
// Honor the host's abort signal: it fires when the caller disconnects or a deadline passes.
this.abortSignal?.throwIfAborted();
const fetchResponse = await fetch(response.queryV2.url, { signal: this.abortSignal });
if (!fetchResponse.ok) {
throw new Error(`Failed to fetch query results: ${fetchResponse.statusText}`);
}
const result = await fetchResponse.json();
// Check if the query is complete
if (result.status === 'COMPLETE' || result.status === 'FAILED') {
if (result.status === 'FAILED') {
throw new Error(result.error || 'Query execution failed');
}
return result;
}
// If still in progress, wait before next poll
if (result.status === 'IN_PROGRESS') {
await new Promise(resolve => setTimeout(resolve, pollInterval));
continue;
}
// If we get an unexpected status, return the result as-is
return result;
}
throw new J1qlQueryTimeoutError(maxAttempts);
}
return response.queryV2;
}
/**
* List all entity types in the account
*/
async listEntityTypes() {
const response = await this.client.request(queries_js_1.GET_ENTITY_COUNTS);
return {
classes: response.getEntityCounts?.classes ?? {},
types: response.getEntityCounts?.types ?? {},
};
}
}
exports.J1qlService = J1qlService;
//# sourceMappingURL=j1ql-service.js.map