Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/http/apiCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface RequestOptions {
timeoutSecs: number;
headers: any;
body?: FormData | string;
queryParams?: Record<string, string>;
}

export interface BaseHttpResponse {
Expand Down Expand Up @@ -41,9 +42,14 @@ export async function sendRequestAndReadResponse(
options: RequestOptions,
url?: string,
): Promise<BaseHttpResponse> {
url ??= `https://${options.hostname}${options.path}`;
const requestUrl = new URL(url ?? `https://${options.hostname}${options.path}`);
if (options.queryParams) {
for (const [key, value] of Object.entries(options.queryParams)) {
requestUrl.searchParams.set(key, value);
}
}
const response = await request(
url,
requestUrl,
{
method: options.method,
headers: options.headers,
Expand Down
27 changes: 19 additions & 8 deletions src/v2/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MindeeError } from "@/errors/index.js";
import { errorHandler } from "@/errors/handler.js";
import { LOG_LEVELS, logger } from "@/logger.js";
import { ErrorResponse, JobResponse } from "./parsing/index.js";
import { SearchResponse } from "./parsing/search/index.js";
import { MindeeApiV2 } from "./http/mindeeApiV2.js";
import { MindeeHttpErrorV2 } from "./http/errors.js";
import { PollingOptions, PollingOptionsConstructor } from "./clientOptions/index.js";
Expand Down Expand Up @@ -54,6 +55,16 @@ export class Client {
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?: string, modelType?: string): Promise<SearchResponse> {
return await this.mindeeApi.reqGetSearchModel(name, modelType);
}

async enqueue<P extends typeof BaseProduct>(
product: P,
inputSource: InputSource,
Expand All @@ -66,7 +77,7 @@ export class Client {
? params
: new product.parametersClass(params);
await inputSource.init();
const jobResponse = await this.mindeeApi.enqueueProduct(
const jobResponse = await this.mindeeApi.reqPostProductEnqueue(
product, inputSource, paramsInstance
);
if (jobResponse.job.id === undefined || jobResponse.job.id.length === 0) {
Expand Down Expand Up @@ -94,7 +105,7 @@ export class Client {
logger.debug(
`Attempting to get inference with ID: ${inferenceId} using response type: ${product.name}`
);
return await this.mindeeApi.getProductResultById(product, inferenceId);
return await this.mindeeApi.reqGetProductResultById(product, inferenceId);
}

/**
Expand All @@ -113,7 +124,7 @@ export class Client {
logger.debug(
`Attempting to get inference from: ${url} using response type: ${product.name}`
);
return await this.mindeeApi.getProductResultByUrl(product, url);
return await this.mindeeApi.reqGetProductResultByUrl(product, url);
}

/**
Expand All @@ -126,7 +137,7 @@ export class Client {
* parsing is complete.
*/
async getJob(jobId: string): Promise<JobResponse> {
return await this.mindeeApi.getJob(jobId);
return await this.mindeeApi.reqGetJobById(jobId);
}

/**
Expand Down Expand Up @@ -155,7 +166,7 @@ export class Client {
product, inputSource, paramsInstance
);
return await this.pollForResult(
product, pollingOptionsInstance, jobResponse.job.id
product, pollingOptionsInstance, jobResponse
);
}

Expand All @@ -167,7 +178,7 @@ export class Client {
protected async pollForResult<P extends typeof BaseProduct>(
product: typeof BaseProduct,
pollingOptions: PollingOptions,
jobId: string,
jobResponse: JobResponse,
): Promise<InstanceType<P["responseClass"]>> {
logger.debug(
`Waiting ${pollingOptions.initialDelaySec} seconds before polling.`
Expand All @@ -178,15 +189,15 @@ export class Client {
pollingOptions.initialTimerOptions
);
logger.debug(
`Start polling for inference using job ID: ${jobId}.`
`Start polling for inference using job ID: ${jobResponse.job.id}.`
);
let retryCounter: number = 1;
let pollResults: JobResponse;
while (retryCounter < pollingOptions.maxRetries + 1) {
logger.debug(
`Attempt ${retryCounter} of ${pollingOptions.maxRetries}`
);
pollResults = await this.getJob(jobId);
pollResults = await this.mindeeApi.reqGetJobByUrl(jobResponse.job.pollingUrl);
const error: ErrorResponse | undefined = pollResults.job.error;
if (error) {
throw new MindeeHttpErrorV2(error);
Expand Down
158 changes: 82 additions & 76 deletions src/v2/http/mindeeApiV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { MindeeDeserializationError, MindeeError } from "@/errors/index.js";
import { MindeeHttpErrorV2 } from "./errors.js";
import { logger } from "@/logger.js";
import { BaseProduct } from "@/v2/product/baseProduct.js";
import { SearchResponse } from "@/v2/parsing/search/index.js";

/**
* Mindee V2 API handler.
Expand All @@ -29,36 +30,89 @@ export class MindeeApiV2 {
}

/**
* Sends a file to the product inference queue.
* @param product product to enqueue.
* @param inputSource Local file loaded as an input.
* 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 reqGetSearchModel(name?: string, modelType?: string): Promise<SearchResponse> {
const queryParams: Record<string, string> = {};
if (name) queryParams["name"] = name;
if (modelType) queryParams["model_type"] = modelType;
const options: RequestOptions = {
method: "GET",
headers: this.settings.baseHeaders,
hostname: this.settings.hostname,
path: "/v2/search/models",
queryParams: queryParams,
timeoutSecs: this.settings.timeoutSecs,
};
const response: BaseHttpResponse = await sendRequestAndReadResponse(this.settings.dispatcher, options);
return this.#processResponse(response, SearchResponse);
}

/**
* Sends a document to the inference queue.
* @param product Product to enqueue.
* @param inputSource Local or remote file as an input.
* @param params {ExtractionParameters} parameters relating to the enqueueing options.
* @throws Error if the server's response contains an error.
* @returns a `Promise` containing a job response.
*/
async enqueueProduct(
async reqPostProductEnqueue(
product: typeof BaseProduct,
inputSource: InputSource,
params: BaseParameters
): Promise<JobResponse> {
await inputSource.init();
const result: BaseHttpResponse = await this.#reqPostProductEnqueue(
product, inputSource, params
);
const form = params.getFormData();
if (inputSource instanceof LocalInputSource) {
form.set("file", new Blob([inputSource.fileObject]), inputSource.filename);
} else {
form.set("url", (inputSource as UrlInput).url);
}
const path = `/v2/products/${product.slug}/enqueue`;
const options: RequestOptions = {
method: "POST",
headers: this.settings.baseHeaders,
hostname: this.settings.hostname,
path: path,
body: form,
timeoutSecs: this.settings.timeoutSecs,
};
const result: BaseHttpResponse = await sendRequestAndReadResponse(this.settings.dispatcher, options);

if (result.data.error !== undefined) {
throw new MindeeHttpErrorV2(result.data.error);
}
return this.#processResponse(result, JobResponse);
}

/**
* Get the specified Job.
* Get the specified Job by its ID.
* Throws an error if the server's response contains an error.
* @param jobId The Job ID as returned by the enqueue request.
* @returns a `Promise` containing the job response.
*/
async getJob(jobId: string): Promise<JobResponse> {
const response = await this.#reqGetJob(jobId);
async reqGetJobById(jobId: string): Promise<JobResponse> {
return this.reqGetJobByUrl(
`https://${this.settings.hostname}/v2/jobs/${jobId}`
);
}

/**
* Get the specified Job from a polling URL.
* Throws an error if the server's response contains an error.
* @param pollingUrl The polling URL as returned by a Job's pollingUrl property.
* @returns a `Promise` containing the job response.
*/
async reqGetJobByUrl(pollingUrl: string): Promise<JobResponse> {
if (!pollingUrl.startsWith("https://")) {
throw new MindeeError(`Invalid URL: ${pollingUrl}`);
}
const options: RequestOptions = {
method: "GET",
headers: this.settings.baseHeaders,
timeoutSecs: this.settings.timeoutSecs,
};
const response: BaseHttpResponse = await sendRequestAndReadResponse(this.settings.dispatcher, options, pollingUrl);
return this.#processResponse(response, JobResponse);
}

Expand All @@ -69,14 +123,14 @@ export class MindeeApiV2 {
* @param inferenceId The inference ID for the result.
* @returns a `Promise` containing the parsed result.
*/
async getProductResultById<P extends typeof BaseProduct>(
async reqGetProductResultById<P extends typeof BaseProduct>(
product: P,
inferenceId: string,
): Promise<InstanceType<P["responseClass"]>> {
const queueResponse: BaseHttpResponse = await this.#reqGetProductResult(
return this.reqGetProductResultByUrl(
product,
`https://${this.settings.hostname}/v2/products/${product.slug}/results/${inferenceId}`
);
return this.#processResponse(queueResponse, product.responseClass) as InstanceType<P["responseClass"]>;
}

/**
Expand All @@ -86,12 +140,21 @@ export class MindeeApiV2 {
* @param url The URL as returned by a Job's resultUrl property.
* @returns a `Promise` containing the parsed result.
*/
async getProductResultByUrl<P extends typeof BaseProduct>(
async reqGetProductResultByUrl<P extends typeof BaseProduct>(
product: P,
url: string,
): Promise<InstanceType<P["responseClass"]>> {
const queueResponse: BaseHttpResponse = await this.#reqGetProductResult(url);
return this.#processResponse(queueResponse, product.responseClass) as InstanceType<P["responseClass"]>;
const options: RequestOptions = {
method: "GET",
headers: this.settings.baseHeaders,
timeoutSecs: this.settings.timeoutSecs,
};
if (!url.startsWith("https://")) {
throw new MindeeError(`Invalid URL: ${url}`);
}
const response: BaseHttpResponse = await sendRequestAndReadResponse(this.settings.dispatcher, options, url);

return this.#processResponse(response, product.responseClass) as InstanceType<P["responseClass"]>;
}

#processResponse<T extends BaseResponse>(
Expand Down Expand Up @@ -125,61 +188,4 @@ export class MindeeApiV2 {
throw new MindeeDeserializationError("Couldn't deserialize response object.");
}
}

/**
* Sends a document to the inference queue.
* @param product Product to enqueue.
* @param inputSource Local or remote file as an input.
* @param params {ExtractionParameters} parameters relating to the enqueueing options.
*/
async #reqPostProductEnqueue(
product: typeof BaseProduct,
inputSource: InputSource,
params: BaseParameters
): Promise<BaseHttpResponse> {
const form = params.getFormData();
if (inputSource instanceof LocalInputSource) {
form.set("file", new Blob([inputSource.fileObject]), inputSource.filename);
} else {
form.set("url", (inputSource as UrlInput).url);
}
const path = `/v2/products/${product.slug}/enqueue`;
const options: RequestOptions = {
method: "POST",
headers: this.settings.baseHeaders,
hostname: this.settings.hostname,
path: path,
body: form,
timeoutSecs: this.settings.timeoutSecs,
};
return await sendRequestAndReadResponse(this.settings.dispatcher, options);
}

async #reqGetJob(jobId: string): Promise<BaseHttpResponse> {
const options: RequestOptions = {
method: "GET",
headers: this.settings.baseHeaders,
hostname: this.settings.hostname,
path: `/v2/jobs/${jobId}`,
timeoutSecs: this.settings.timeoutSecs,
};
return await sendRequestAndReadResponse(this.settings.dispatcher, options);
}

/**
* Make a request to GET the status of a document in the queue.
* @param url URL path to the result.
* @returns a `Promise` containing the parsed result.
*/
async #reqGetProductResult(url: string): Promise<BaseHttpResponse> {
const options: RequestOptions = {
method: "GET",
headers: this.settings.baseHeaders,
timeoutSecs: this.settings.timeoutSecs,
};
if (!url.startsWith("https://")) {
throw new MindeeError(`Invalid URL: ${url}`);
}
return await sendRequestAndReadResponse(this.settings.dispatcher, options, url);
}
}
4 changes: 4 additions & 0 deletions src/v2/parsing/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { PaginationMetadata } from "./paginationMetadata.js";
export { SearchModel } from "./searchModel.js";
export { SearchResponse } from "./searchResponse.js";
export { ModelWebhook } from "./modelWebhook.js";
27 changes: 27 additions & 0 deletions src/v2/parsing/search/modelWebhook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { StringDict } from "@/parsing/index.js";

/**
* Model webhook info.
*/
export class ModelWebhook {
/**
* ID of the webhook.
*/
public id: string;

/**
* Name of the webhook.
*/
public name: string;

/**
* URL of the webhook.
*/
public url: string;

constructor(serverResponse: StringDict) {
this.id = serverResponse["id"];
this.name = serverResponse["name"];
this.url = serverResponse["url"];
}
}
Loading
Loading