-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathclient.ts
More file actions
200 lines (189 loc) · 6.79 KB
/
client.ts
File metadata and controls
200 lines (189 loc) · 6.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import { setTimeout } from "node:timers/promises";
import { Dispatcher } from "undici";
import { InputSource } from "@/input/index.js";
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 { MindeeApiV2 } from "./http/mindeeApiV2.js";
import { MindeeHttpErrorV2 } from "./http/errors.js";
import { PollingOptions, PollingOptionsConstructor } from "./clientOptions/index.js";
import { BaseProduct } from "@/v2/product/baseProduct.js";
/**
* Options for the V2 Mindee Client.
* @example
* const client = new MindeeClientV2({
* apiKey: "YOUR_API_KEY",
* throwOnError: true,
* debug: false
* });
*/
export interface ClientOptions {
/** Your API key for all endpoints. */
apiKey?: string;
/** Log debug messages. */
debug?: boolean;
/** Custom Dispatcher instance for the HTTP requests. */
dispatcher?: Dispatcher;
}
/**
* Mindee Client V2 class that centralizes most basic operations.
*/
export class Client {
/** Mindee V2 API handler. */
protected mindeeApi: MindeeApiV2;
/**
* @param {ClientOptions} options options for the initialization of a client.
*/
constructor(
{ apiKey, debug, dispatcher }: ClientOptions = {
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");
}
async enqueue<P extends typeof BaseProduct>(
product: P,
inputSource: InputSource,
params: InstanceType<P["parametersClass"]> | ConstructorParameters<P["parametersClass"]>[0],
): Promise<JobResponse> {
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.enqueueProduct(
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<P extends typeof BaseProduct>(
product: P,
inferenceId: string
): Promise<InstanceType<P["responseClass"]>> {
logger.debug(
`Attempting to get inference with ID: ${inferenceId} using response type: ${product.name}`
);
return await this.mindeeApi.getProductResultById(product, inferenceId);
}
/**
* 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: string): Promise<JobResponse> {
return await this.mindeeApi.getJob(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<P extends typeof BaseProduct>(
product: P,
inputSource: InputSource,
params: InstanceType<P["parametersClass"]> | ConstructorParameters<P["parametersClass"]>[0],
pollingOptions?: PollingOptionsConstructor,
): Promise<InstanceType<P["responseClass"]>> {
const paramsInstance = new product.parametersClass(params);
const pollingOptionsInstance = new PollingOptions(pollingOptions);
const jobResponse: JobResponse = await this.enqueue(
product, inputSource, paramsInstance
);
return await this.pollForResult(
product, pollingOptionsInstance, jobResponse.job.id
);
}
/**
* 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
*/
protected async pollForResult<P extends typeof BaseProduct>(
product: typeof BaseProduct,
pollingOptions: PollingOptions,
jobId: string,
): Promise<InstanceType<P["responseClass"]>> {
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: ${jobId}.`
);
let retryCounter: number = 1;
let pollResults: JobResponse;
while (retryCounter < pollingOptions.maxRetries + 1) {
logger.debug(
`Attempt ${retryCounter} of ${pollingOptions.maxRetries}`
);
pollResults = await this.getJob(jobId);
const error: ErrorResponse | undefined = 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.mindeeApi.getProductResultByUrl(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()"
);
}
}