mindee
Version:
Mindee Client Library for Node.js
143 lines (142 loc) • 6.75 kB
JavaScript
import { setTimeout } from "node:timers/promises";
import { MindeeError } from "../errors/index.js";
import { errorHandler } from "../errors/handler.js";
import { LOG_LEVELS, logger } from "../logger.js";
import { MindeeApiV2 } from "./http/mindeeApiV2.js";
import { MindeeHttpErrorV2 } from "./http/errors.js";
import { PollingOptions } from "./clientOptions/index.js";
/**
* Mindee Client V2 class that centralizes most basic operations.
*/
export class Client {
/**
* @param {ClientOptions} options options for the initialization of a client.
*/
constructor({ apiKey, debug, dispatcher } = {
apiKey: undefined,
debug: false,
dispatcher: undefined,
}) {
this.mindeeApi = new MindeeApiV2(dispatcher, apiKey);
errorHandler.throwOnError = true;
logger.level =
debug ?? process.env.MINDEE_DEBUG
? LOG_LEVELS["debug"]
: LOG_LEVELS["warn"];
logger.debug("Client V2 Initialized");
}
/**
* Search for models available to the account.
* @param name Optional name filter.
* @param modelType Optional model type filter.
* @returns a `Promise` containing the search response.
*/
async searchModels(name, modelType) {
return await this.mindeeApi.reqGetSearchModel(name, modelType);
}
async enqueue(product, inputSource, params) {
if (inputSource === undefined) {
throw new MindeeError("An input document is required.");
}
const paramsInstance = params instanceof product.parametersClass
? params
: new product.parametersClass(params);
await inputSource.init();
const jobResponse = await this.mindeeApi.reqPostProductEnqueue(product, inputSource, paramsInstance);
if (jobResponse.job.id === undefined || jobResponse.job.id.length === 0) {
logger.error(`Failed enqueueing:\n${jobResponse.getRawHttp()}`);
throw new MindeeError("Enqueueing of the document failed.");
}
logger.debug(`Successfully enqueued document with job ID: ${jobResponse.job.id}.`);
return jobResponse;
}
/**
* Retrieves the result of a previously enqueued request.
*
* @param product the product to retrieve.
* @param inferenceId id of the queue to poll.
* @typeParam T an extension of an `Inference`. Can be omitted as it will be inferred from the `productClass`.
* @returns a `Promise` containing the inference.
*/
async getResult(product, inferenceId) {
logger.debug(`Attempting to get inference with ID: ${inferenceId} using response type: ${product.name}`);
return await this.mindeeApi.reqGetProductResultById(product, inferenceId);
}
/**
* Retrieves the result of a previously enqueued request.
* This method is used when manually polling the server for the result URL.
*
* @param product the product to retrieve.
* @param url URL as given in the `getJob()` response.
* @typeParam T an extension of an `Inference`. Can be omitted as it will be inferred from the `productClass`.
* @returns a `Promise` containing the inference.
*/
async getResultByUrl(product, url) {
logger.debug(`Attempting to get inference from: ${url} using response type: ${product.name}`);
return await this.mindeeApi.reqGetProductResultByUrl(product, url);
}
/**
* Get the processing status of a previously enqueued request.
* Can be used for polling.
*
* @param jobId id of the queue to poll.
* @typeParam T an extension of an `Inference`. Can be omitted as it will be inferred from the `productClass`.
* @returns a `Promise` containing a `Job`, which also contains a `Document` if the
* parsing is complete.
*/
async getJob(jobId) {
return await this.mindeeApi.reqGetJobById(jobId);
}
/**
* Enqueue a request and poll the server until the result is sent or
* until the maximum number of tries is reached.
*
* @param product the product to retrieve.
* @param inputSource file or URL to parse.
* @param params parameters relating to prediction options.
*
* @param pollingOptions options for the polling loop, see {@link PollingOptions}.
* @typeParam T an extension of an `Inference`. Can be omitted as it will be inferred from the `productClass`.
* @returns a `Promise` containing parsing results.
*/
async enqueueAndGetResult(product, inputSource, params, pollingOptions) {
const paramsInstance = new product.parametersClass(params);
const pollingOptionsInstance = new PollingOptions(pollingOptions);
const jobResponse = await this.enqueue(product, inputSource, paramsInstance);
return await this.pollForResult(product, pollingOptionsInstance, jobResponse);
}
/**
* Send a document to an endpoint and poll the server until the result is sent or
* until the maximum number of tries is reached.
* @protected
*/
async pollForResult(product, pollingOptions, jobResponse) {
logger.debug(`Waiting ${pollingOptions.initialDelaySec} seconds before polling.`);
await setTimeout(pollingOptions.initialDelaySec * 1000, undefined, pollingOptions.initialTimerOptions);
logger.debug(`Start polling for inference using job ID: ${jobResponse.job.id}.`);
let retryCounter = 1;
let pollResults;
while (retryCounter < pollingOptions.maxRetries + 1) {
logger.debug(`Attempt ${retryCounter} of ${pollingOptions.maxRetries}`);
pollResults = await this.mindeeApi.reqGetJobByUrl(jobResponse.job.pollingUrl);
const error = pollResults.job.error;
if (error) {
throw new MindeeHttpErrorV2(error);
}
logger.debug(`Job status: ${pollResults.job.status}.`);
if (pollResults.job.status === "Failed") {
break;
}
if (pollResults.job.status === "Processed") {
if (!pollResults.job.resultUrl) {
throw new MindeeError("The result URL is undefined. This is a server error, try again later or contact support.");
}
return this.getResultByUrl(product, pollResults.job.resultUrl);
}
await setTimeout(pollingOptions.delaySec * 1000, undefined, pollingOptions.recurringTimerOptions);
retryCounter++;
}
throw new MindeeError(`Polling failed to retrieve a result after ${retryCounter} attempts. ` +
"You can increase poll attempts by passing the pollingOptions argument to enqueueAndGetResult()");
}
}