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
1 change: 0 additions & 1 deletion .github/workflows/_test-code-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ jobs:
strategy:
matrix:
node-version:
- "18"
- "20"
- "22"
- "24"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/_test-units.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
- "18"
- "20"
- "22"
- "24"
runs-on: ${{ matrix.os }}

steps:
Expand Down
7 changes: 4 additions & 3 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,11 @@ export class Client {
* @returns A valid prediction
*/
try {
if (Object.prototype.hasOwnProperty.call(localResponse.asDict(), "job")) {
return new AsyncPredictResponse(productClass, localResponse.asDict());
const asDict = await localResponse.asDict();
if (Object.prototype.hasOwnProperty.call(asDict, "job")) {
return new AsyncPredictResponse(productClass, asDict);
}
return new PredictResponse(productClass, localResponse.asDict());
return new PredictResponse(productClass, asDict);
} catch {
throw new MindeeError("No prediction found in local response.");
}
Expand Down
83 changes: 7 additions & 76 deletions src/clientV2.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import {
Base64Input, BufferInput, BytesInput, InputSource,
PathInput, StreamInput, UrlInput,
} from "./input";
import { InputSource } from "./input";
import { errorHandler } from "./errors/handler";
import { LOG_LEVELS, logger } from "./logger";

import { setTimeout } from "node:timers/promises";
import { ErrorResponse, InferenceResponse, JobResponse } from "./parsing/v2";
import { MindeeApiV2 } from "./http/mindeeApiV2";
import { MindeeHttpErrorV2 } from "./errors/mindeeError";
import { Readable } from "stream";

/**
* Parameters for the internal polling loop in {@link ClientV2.enqueueAndGetInference | enqueueAndGetInference()} .
Expand Down Expand Up @@ -160,6 +156,9 @@ export class ClientV2 {
if (inputSource === undefined) {
throw new Error("The 'enqueue' function requires an input document.");
}
if (!inputSource.isInitialized()) {
await inputSource.init();
}
return await this.mindeeApi.reqPostInferenceEnqueue(inputSource, params);
}

Expand Down Expand Up @@ -232,19 +231,19 @@ export class ClientV2 {
* Send a document to an endpoint and poll the server until the result is sent or
* until the maximum number of tries is reached.
*
* @param inputDoc document to parse.
* @param inputSource file or URL to parse.
* @param params parameters relating to prediction options.
*
* @typeParam T an extension of an `Inference`. Can be omitted as it will be inferred from the `productClass`.
* @category Synchronous
* @returns a `Promise` containing parsing results.
*/
async enqueueAndGetInference(
inputDoc: InputSource,
inputSource: InputSource,
params: InferenceParameters
): Promise<InferenceResponse> {
const validatedAsyncParams = this.#setAsyncParams(params.pollingOptions);
const enqueueResponse: JobResponse = await this.enqueueInference(inputDoc, params);
const enqueueResponse: JobResponse = await this.enqueueInference(inputSource, params);
if (enqueueResponse.job.id === undefined || enqueueResponse.job.id.length === 0) {
logger.error(`Failed enqueueing:\n${enqueueResponse.getRawHttp()}`);
throw Error("Enqueueing of the document failed.");
Expand Down Expand Up @@ -283,72 +282,4 @@ Job status: ${pollResults.job.status}.`
" seconds"
);
}

/**
* Load an input source from a local path.
* @param inputPath
*/
sourceFromPath(inputPath: string): PathInput {
return new PathInput({
inputPath: inputPath,
});
}

/**
* Load an input source from a base64 encoded string.
* @param inputString input content, as a string.
* @param filename file name.
*/
sourceFromBase64(inputString: string, filename: string): Base64Input {
return new Base64Input({
inputString: inputString,
filename: filename,
});
}

/**
* Load an input source from a `stream.Readable` object.
* @param inputStream input content, as a readable stream.
* @param filename file name.
*/
sourceFromStream(inputStream: Readable, filename: string): StreamInput {
return new StreamInput({
inputStream: inputStream,
filename: filename,
});
}

/**
* Load an input source from bytes.
* @param inputBytes input content, as a Uint8Array or Buffer.
* @param filename file name.
*/
sourceFromBytes(inputBytes: Uint8Array, filename: string): BytesInput {
return new BytesInput({
inputBytes: inputBytes,
filename: filename,
});
}

/**
* Load an input source from a Buffer.
* @param buffer input content, as a buffer.
* @param filename file name.
*/
sourceFromBuffer(buffer: Buffer, filename: string): BufferInput {
return new BufferInput({
buffer: buffer,
filename: filename,
});
}

/**
* Load an input source from a URL.
* @param url input url. Must be HTTPS.
*/
sourceFromUrl(url: string): UrlInput {
return new UrlInput({
url: url,
});
}
}
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * as product from "./product";
export { Client, PredictOptions } from "./client";
export { Client, PredictOptions, WorkflowOptions } from "./client";
export { ClientV2, InferenceParameters, PollingOptions } from "./clientV2";
export {
AsyncPredictResponse,
Expand All @@ -9,6 +9,10 @@ export {
Document,
Page,
} from "./parsing/common";
export {
InferenceResponse,
JobResponse,
} from "./parsing/v2";
export {
InputSource,
Base64Input,
Expand Down
25 changes: 20 additions & 5 deletions src/input/localResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { CommonResponse } from "../parsing/v2";
export class LocalResponse {
private file: Buffer;
private readonly inputHandle: Buffer | string;
protected initialized = false;

/**
* Creates an instance of LocalResponse.
Expand Down Expand Up @@ -39,13 +40,17 @@ export class LocalResponse {
} else {
throw new MindeeError("Incompatible type for input.");
}
this.initialized = true;
}

/**
* Returns the dictionary representation of the file.
* @returns A JSON-like object.
*/
asDict(): StringDict {
async asDict(): Promise<StringDict> {
if (!this.initialized) {
await this.init();
}
try {
const content = this.file.toString("utf-8");
return JSON.parse(content);
Expand All @@ -60,6 +65,11 @@ export class LocalResponse {
* @returns The HMAC signature of the local response.
*/
getHmacSignature(secretKey: string | Buffer | Uint8Array): string {
if (!this.initialized) {
throw new Error(
"The `init()` method must be called before calling `getHmacSignature()`."
);
}
const algorithm = "sha256";
try {
const hmac = crypto.createHmac(algorithm, secretKey);
Expand All @@ -76,7 +86,12 @@ export class LocalResponse {
* @param signature - The signature to be compared with.
* @returns True if the HMAC signature is valid.
*/
isValidHmacSignature(secretKey: string | Buffer | Uint8Array, signature: string): boolean {
public isValidHmacSignature(secretKey: string | Buffer | Uint8Array, signature: string): boolean {
if (!this.initialized) {
throw new Error(
"The `init()` method must be called before calling `isValidHmacSignature()`."
);
}
return signature === this.getHmacSignature(secretKey);
}

Expand All @@ -90,11 +105,11 @@ export class LocalResponse {
* @returns An instance of `responseClass` populated with the file content.
* @throws MindeeError If the provided class cannot be instantiated.
*/
public deserializeResponse<ResponseT extends CommonResponse>(
public async deserializeResponse<ResponseT extends CommonResponse>(
responseClass: new (serverResponse: StringDict) => ResponseT
): ResponseT {
): Promise<ResponseT> {
try {
return new responseClass(this.asDict());
return new responseClass(await this.asDict());
} catch {
throw new MindeeError("Invalid response provided.");
}
Expand Down
1 change: 1 addition & 0 deletions src/input/sources/base64Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ export class Base64Input extends LocalInputSource {
this.mimeType = await this.checkMimetype();
// clear out the string
this.inputString = "";
this.initialized = true;
}
}
1 change: 1 addition & 0 deletions src/input/sources/bufferInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export class BufferInput extends LocalInputSource {

async init(): Promise<void> {
this.mimeType = await this.checkMimetype();
this.initialized = true;
}
}
1 change: 1 addition & 0 deletions src/input/sources/bytesInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ export class BytesInput extends LocalInputSource {
this.fileObject = Buffer.from(this.inputBytes);
this.mimeType = await this.checkMimetype();
this.inputBytes = new Uint8Array(0);
this.initialized = true;
}
}
6 changes: 5 additions & 1 deletion src/input/sources/inputSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ export const INPUT_TYPE_BUFFER = "buffer";

export abstract class InputSource {
fileObject: Buffer | string = "";
protected initialized: boolean = false;

async init() {
throw new Error("not Implemented");
}
}

public isInitialized() {
return this.initialized;
}
}
32 changes: 23 additions & 9 deletions src/input/sources/localInputSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,7 @@ export abstract class LocalInputSource extends InputSource {
logger.debug(`Loading file from: ${inputType}`);
}

isPdf(): boolean {
return this.mimeType === "application/pdf";
}

async checkMimetype(): Promise<string> {
protected async checkMimetype(): Promise<string> {
if (!(this.fileObject instanceof Buffer)) {
throw new Error(
`MIME type cannot be verified on input source of type ${this.inputType}.`
Expand All @@ -87,11 +83,23 @@ export abstract class LocalInputSource extends InputSource {
return mimeType;
}

isPdf(): boolean {
if (!this.initialized) {
throw new Error(
"The `init()` method must be called before calling `isPdf()`."
);
}
return this.mimeType === "application/pdf";
}

/**
* Cut PDF pages.
* @param pageOptions
*/
async applyPageOptions(pageOptions: PageOptions) {
public async applyPageOptions(pageOptions: PageOptions) {
if (!this.initialized) {
await this.init();
}
if (!(this.fileObject instanceof Buffer)) {
throw new Error(
`Cannot modify an input source of type ${this.inputType}.`
Expand All @@ -104,7 +112,7 @@ export abstract class LocalInputSource extends InputSource {
/**
* Cut PDF pages.
* @param pageOptions
* @deprecated Deprecated in favor of {@link LocalInputSource.applyPageOptions applyPageOptions()}.
* @deprecated Deprecated in favor of {@link LocalInputSource.applyPageOptions}.
*/
async cutPdf(pageOptions: PageOptions) {
return this.applyPageOptions(pageOptions);
Expand All @@ -122,13 +130,16 @@ export abstract class LocalInputSource extends InputSource {
*
* @returns A Promise that resolves when the compression is complete.
*/
async compress(
public async compress(
quality: number = 85,
maxWidth: number | null = null,
maxHeight: number | null = null,
forceSourceText: boolean = false,
disableSourceText: boolean = true
) {
if (!this.initialized) {
await this.init();
}
let buffer: Buffer;
if (typeof this.fileObject === "string") {
buffer = Buffer.from(this.fileObject);
Expand All @@ -146,7 +157,10 @@ export abstract class LocalInputSource extends InputSource {
* Returns true if the object is a PDF and has source text. False otherwise.
* @return boolean
*/
async hasSourceText() {
public async hasSourceText() {
if (!this.initialized) {
await this.init();
}
if (!this.isPdf()){
return false;
}
Expand Down
1 change: 1 addition & 0 deletions src/input/sources/pathInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ export class PathInput extends LocalInputSource {
logger.debug(`Loading from: ${this.inputPath}`);
this.fileObject = Buffer.from(await fs.readFile(this.inputPath));
this.mimeType = await this.checkMimetype();
this.initialized = true;
}
}
1 change: 1 addition & 0 deletions src/input/sources/streamInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class StreamInput extends LocalInputSource {
async init() {
this.fileObject = await this.stream2buffer(this.inputStream);
this.mimeType = await this.checkMimetype();
this.initialized = true;
}

async stream2buffer(stream: Readable): Promise<Buffer> {
Expand Down
1 change: 1 addition & 0 deletions src/input/sources/urlInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class UrlInput extends InputSource {
throw new Error("URL must be HTTPS");
}
this.fileObject = this.url;
this.initialized = true;
}

private async fetchFileContent(options: {
Expand Down
10 changes: 6 additions & 4 deletions src/parsing/v2/field/fieldConfidence.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint-disable @typescript-eslint/naming-convention */

/**
* Confidence level of a field as returned by the V2 API.
*/
export enum FieldConfidence {
certain = "Certain",
high = "High",
medium = "Medium",
low = "Low",
Certain = "Certain",
High = "High",
Medium = "Medium",
Low = "Low",
}
Loading
Loading