From 019f865dbb83c84193c34d8920a1805d40af7cd5 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:49:52 +0200 Subject: [PATCH 01/24] add support for basic deserialization classes --- src/errors/mindeeError.ts | 7 +++ src/parsing/index.ts | 1 + src/parsing/v2/baseField.ts | 40 +++++++++++++++++ src/parsing/v2/commonResponse.ts | 24 ++++++++++ src/parsing/v2/errorResponse.ts | 20 +++++++++ src/parsing/v2/index.ts | 15 +++++++ src/parsing/v2/inference.ts | 32 +++++++++++++ src/parsing/v2/inferenceFields.ts | 13 ++++++ src/parsing/v2/inferenceFile.ts | 17 +++++++ src/parsing/v2/inferenceModel.ts | 12 +++++ src/parsing/v2/inferenceOptions.ts | 15 +++++++ src/parsing/v2/inferenceResponse.ts | 15 +++++++ src/parsing/v2/inferenceResult.ts | 30 +++++++++++++ src/parsing/v2/job.ts | 69 +++++++++++++++++++++++++++++ src/parsing/v2/jobResponse.ts | 15 +++++++ src/parsing/v2/listField.ts | 35 +++++++++++++++ src/parsing/v2/objectField.ts | 31 +++++++++++++ src/parsing/v2/rawText.ts | 21 +++++++++ src/parsing/v2/simpleField.ts | 18 ++++++++ 19 files changed, 430 insertions(+) create mode 100644 src/parsing/v2/baseField.ts create mode 100644 src/parsing/v2/commonResponse.ts create mode 100644 src/parsing/v2/errorResponse.ts create mode 100644 src/parsing/v2/index.ts create mode 100644 src/parsing/v2/inference.ts create mode 100644 src/parsing/v2/inferenceFields.ts create mode 100644 src/parsing/v2/inferenceFile.ts create mode 100644 src/parsing/v2/inferenceModel.ts create mode 100644 src/parsing/v2/inferenceOptions.ts create mode 100644 src/parsing/v2/inferenceResponse.ts create mode 100644 src/parsing/v2/inferenceResult.ts create mode 100644 src/parsing/v2/job.ts create mode 100644 src/parsing/v2/jobResponse.ts create mode 100644 src/parsing/v2/listField.ts create mode 100644 src/parsing/v2/objectField.ts create mode 100644 src/parsing/v2/rawText.ts create mode 100644 src/parsing/v2/simpleField.ts diff --git a/src/errors/mindeeError.ts b/src/errors/mindeeError.ts index 1925c6998..34ee4b12c 100644 --- a/src/errors/mindeeError.ts +++ b/src/errors/mindeeError.ts @@ -33,4 +33,11 @@ export class MindeePdfError extends MindeeError { } } +export class MindeeApiV2Error extends MindeeError { + constructor(message: string) { + super(message); + this.name = "MindeeApiV2Error"; + } +} + diff --git a/src/parsing/index.ts b/src/parsing/index.ts index ca44413f0..6960d97be 100644 --- a/src/parsing/index.ts +++ b/src/parsing/index.ts @@ -2,3 +2,4 @@ export * as common from "./common"; export * as custom from "./custom"; export * as standard from "./standard"; export * as generated from "./generated"; +export * as v2 from "./v2"; diff --git a/src/parsing/v2/baseField.ts b/src/parsing/v2/baseField.ts new file mode 100644 index 000000000..f5458e1fe --- /dev/null +++ b/src/parsing/v2/baseField.ts @@ -0,0 +1,40 @@ +import { StringDict } from "../common"; +import { MindeeApiV2Error } from "../../errors/mindeeError"; +import { ListField } from "./listField"; +import { ObjectField } from "./objectField"; +import { SimpleField } from "./simpleField"; + +export abstract class BaseField { + protected _indentLevel: number; + + protected constructor(indentLevel = 0) { + this._indentLevel = indentLevel; + } + + /** + * Factory helper, mirrors `BaseField.create_field` in Python. + */ + static createField(serverResponse: StringDict, indentLevel = 0) { + if (typeof serverResponse !== "object" || serverResponse === null) { + throw new MindeeApiV2Error( + `Unrecognized field format ${JSON.stringify(serverResponse)}.` + ); + } + + if ("items" in serverResponse) { + return new ListField(serverResponse, indentLevel); + } + + if ("fields" in serverResponse) { + return new ObjectField(serverResponse, indentLevel); + } + + if ("value" in serverResponse) { + return new SimpleField(serverResponse, indentLevel); + } + + throw new MindeeApiV2Error( + `Unrecognized field format in ${JSON.stringify(serverResponse)}.` + ); + } +} diff --git a/src/parsing/v2/commonResponse.ts b/src/parsing/v2/commonResponse.ts new file mode 100644 index 000000000..824f3e6b1 --- /dev/null +++ b/src/parsing/v2/commonResponse.ts @@ -0,0 +1,24 @@ +import { StringDict } from "../common"; + + +export abstract class CommonResponse { + /** + * Raw text representation of the API's response. + */ + private readonly rawHttp: StringDict; + + /** + * @param serverResponse JSON response from the server. + */ + protected constructor(serverResponse: StringDict) { + this.rawHttp = serverResponse; + } + + /** + * Raw HTTP request sent from server, as a JSON-like structure + * @returns The HTTP request + */ + getRawHttp(): StringDict { + return this.rawHttp; + } +} diff --git a/src/parsing/v2/errorResponse.ts b/src/parsing/v2/errorResponse.ts new file mode 100644 index 000000000..f205e59ac --- /dev/null +++ b/src/parsing/v2/errorResponse.ts @@ -0,0 +1,20 @@ +import { StringDict } from "../common"; + +export class ErrorResponse { + /** + * The HTTP code status. + */ + public status: number; + /** + * The detail on the error. + */ + public detail: string; + + /** + * @param serverResponse JSON response from the server. + */ + constructor(serverResponse: StringDict) { + this.status = serverResponse["status"]; + this.detail = serverResponse["detail"]; + } +} diff --git a/src/parsing/v2/index.ts b/src/parsing/v2/index.ts new file mode 100644 index 000000000..d255cbfb3 --- /dev/null +++ b/src/parsing/v2/index.ts @@ -0,0 +1,15 @@ +export { CommonResponse } from "./commonResponse"; +export { ErrorResponse } from "./errorResponse"; +export { Inference } from "./inference"; +export { InferenceFields } from "./inferenceFields"; +export { InferenceFile } from "./inferenceFile"; +export { InferenceModel } from "./inferenceModel"; +export { InferenceOptions } from "./inferenceOptions"; +export { InferenceResponse } from "./inferenceResponse"; +export { InferenceResult } from "./inferenceResult"; +export { Job } from "./job"; +export { JobResponse } from "./jobResponse"; +export { ListField } from "./listField"; +export { ObjectField } from "./objectField"; +export { RawText } from "./rawText"; +export { SimpleField } from "./simpleField"; diff --git a/src/parsing/v2/inference.ts b/src/parsing/v2/inference.ts new file mode 100644 index 000000000..dcd35c279 --- /dev/null +++ b/src/parsing/v2/inference.ts @@ -0,0 +1,32 @@ +import { StringDict } from "../common"; +import { InferenceModel } from "./inferenceModel"; +import { InferenceResult } from "./inferenceResult"; +import { InferenceFile } from "./inferenceFile"; + +export class Inference { + /** + * Model info for the inference. + */ + public model: InferenceModel; + /** + * File info for the inference. + */ + public file: InferenceFile; + /** + * Result of the inference. + */ + public result: InferenceResult; + /** + * ID of the inference. + */ + public id?: string; + + constructor(serverResponse: StringDict) { + this.model = new InferenceModel(serverResponse["model"]); + this.file = new InferenceFile(serverResponse["file"]); + this.result = new InferenceResult(serverResponse["result"]); + if ("id" in serverResponse) { + this.id = serverResponse["id"]; + } + } +} diff --git a/src/parsing/v2/inferenceFields.ts b/src/parsing/v2/inferenceFields.ts new file mode 100644 index 000000000..49d8c91d0 --- /dev/null +++ b/src/parsing/v2/inferenceFields.ts @@ -0,0 +1,13 @@ +import { StringDict } from "../common"; +import { ListField } from "./listField"; +import { ObjectField } from "./objectField"; +import { SimpleField } from "./simpleField"; + +export class InferenceFields extends Map { + protected _indentLevel: number; + + constructor(serverResponse: StringDict, indentLevel = 0) { + super(serverResponse.entries()); + this._indentLevel = indentLevel; + } +} diff --git a/src/parsing/v2/inferenceFile.ts b/src/parsing/v2/inferenceFile.ts new file mode 100644 index 000000000..3ee8be472 --- /dev/null +++ b/src/parsing/v2/inferenceFile.ts @@ -0,0 +1,17 @@ +import { StringDict } from "../common"; + +export class InferenceFile { + /** + * Name of the file. + */ + public name: string; + /** + * Optional alias for the file. + */ + public alias: string; + + constructor(serverResponse: StringDict) { + this.name = serverResponse["name"]; + this.alias = serverResponse["alias"]; + } +} diff --git a/src/parsing/v2/inferenceModel.ts b/src/parsing/v2/inferenceModel.ts new file mode 100644 index 000000000..a3a0f59ca --- /dev/null +++ b/src/parsing/v2/inferenceModel.ts @@ -0,0 +1,12 @@ +import { StringDict } from "../common"; + +export class InferenceModel { + /** + * ID of the model. + */ + public id: string; + + constructor(serverResponse: StringDict) { + this.id = serverResponse["id"]; + } +} diff --git a/src/parsing/v2/inferenceOptions.ts b/src/parsing/v2/inferenceOptions.ts new file mode 100644 index 000000000..0fabb6b1c --- /dev/null +++ b/src/parsing/v2/inferenceOptions.ts @@ -0,0 +1,15 @@ +import { StringDict } from "../common"; +import { RawText } from "./rawText"; + +export class InferenceOptions { + /** + * List of texts found per page. + */ + public rawTexts: Array; + + constructor(serverResponse: StringDict) { + this.rawTexts = serverResponse["raw_texts"] ? serverResponse["raw_texts"].map( + (rawText: StringDict) => new RawText(rawText) + ) : []; + } +} diff --git a/src/parsing/v2/inferenceResponse.ts b/src/parsing/v2/inferenceResponse.ts new file mode 100644 index 000000000..3e6a55e8b --- /dev/null +++ b/src/parsing/v2/inferenceResponse.ts @@ -0,0 +1,15 @@ +import { CommonResponse } from "./commonResponse"; +import { Inference } from "./inference"; +import { StringDict } from "../common"; + +export class InferenceResponse extends CommonResponse { + /** + * Inference result. + */ + public inference: Inference; + + constructor(serverResponse: StringDict) { + super(serverResponse); + this.inference = new Inference(serverResponse["inference"]); + } +} diff --git a/src/parsing/v2/inferenceResult.ts b/src/parsing/v2/inferenceResult.ts new file mode 100644 index 000000000..fee8a3759 --- /dev/null +++ b/src/parsing/v2/inferenceResult.ts @@ -0,0 +1,30 @@ +import { InferenceFields } from "./inferenceFields"; +import { InferenceOptions } from "./inferenceOptions"; +import { StringDict } from "../common"; + +export class InferenceResult { + /** + * Fields contained in the inference. + */ + public fields: InferenceFields; + + /** + * Potential options retrieved alongside the inference. + */ + public options?: InferenceOptions; + + constructor(serverResponse: StringDict) { + this.fields = new InferenceFields(serverResponse["fields"]); + if (serverResponse["options"]) { + this.options = new InferenceOptions(serverResponse["options"]); + } + } + + toString(): string { + let outStr: string = `:fields: ${this.fields}`; + if (this.options) { + outStr += `\n:options: ${this.options}`; + } + return outStr; + } +} diff --git a/src/parsing/v2/job.ts b/src/parsing/v2/job.ts new file mode 100644 index 000000000..907b9ca8b --- /dev/null +++ b/src/parsing/v2/job.ts @@ -0,0 +1,69 @@ +import { StringDict } from "../common"; +import { ErrorResponse } from "./errorResponse"; + +/** + * Job information for a V2 polling attempt. + */ +export class Job { + /** + * Job ID. + */ + public id: string; + + /** + * Error response if any. + */ + public error?: ErrorResponse; + /** + * Timestamp of the job creation. + */ + public createdAt: Date | null; + /** + * ID of the model. + */ + public modelId: string; + /** + * Name for the file. + */ + public filename: string; + /** + * Optional alias for the file. + */ + public alias: string; + /** + * Status of the job. + */ + public status: string; + /** + * URL to poll for the job status. + */ + public pollingUrl: string; + /** + * URL to poll for the job result, redirects to the result if available. + */ + public resultUrl?: string; + /** + * ID of webhooks associated with the job. + */ + public webhooks: Array; + + constructor(serverResponse: StringDict) { + this.id = serverResponse["id"]; + this.status = serverResponse["status"]; + if ("error" in serverResponse) { + this.error = new ErrorResponse(serverResponse["error"]); + } + this.createdAt = serverResponse["created_at"] ? this.parseDate(serverResponse["created_at"]) : null; + this.modelId = serverResponse["model_id"]; + this.pollingUrl = serverResponse["polling_url"]; + this.filename = serverResponse["filename"]; + this.resultUrl = serverResponse["result_url"]; + this.alias = serverResponse["alias"]; + this.webhooks = serverResponse["webhooks"]; + } + + private parseDate(dateString: string | null): Date | null { + if (!dateString) return null; + return new Date(dateString); + } +} diff --git a/src/parsing/v2/jobResponse.ts b/src/parsing/v2/jobResponse.ts new file mode 100644 index 000000000..f8405ae19 --- /dev/null +++ b/src/parsing/v2/jobResponse.ts @@ -0,0 +1,15 @@ +import { CommonResponse } from "./commonResponse"; +import { StringDict } from "../common"; +import { Job } from "./job"; + +export class JobResponse extends CommonResponse { + /** + * Job for the polling. + */ + public job: Job; + + constructor(serverResponse: StringDict) { + super(serverResponse); + this.job = new Job(serverResponse["job"]); + } +} diff --git a/src/parsing/v2/listField.ts b/src/parsing/v2/listField.ts new file mode 100644 index 000000000..d42cb2905 --- /dev/null +++ b/src/parsing/v2/listField.ts @@ -0,0 +1,35 @@ +import { MindeeApiV2Error } from "../../errors/mindeeError"; +import { StringDict } from "../common"; +import { BaseField } from "./baseField"; +import { ObjectField } from "./objectField"; +import { SimpleField } from "./simpleField"; + +export class ListField extends BaseField { + /** + * Items contained in the list. + */ + public items: Array; + + constructor(serverResponse: StringDict, indentLevel = 0) { + super(indentLevel); + + if (!Array.isArray(serverResponse["items"])) { + throw new MindeeApiV2Error( + `Expected "items" to be an array in ${JSON.stringify(serverResponse)}.` + ); + } + this.items = serverResponse["items"].map((item) => { + return BaseField.createField(item, indentLevel + 1); + }); + } + + toString(): string { + if (this.items.length === 0) { + return ""; + } + const out = this.items + .map((item) => `* ${item.toString().slice(2)}`) + .join(""); + return "\n" + out; + } +} diff --git a/src/parsing/v2/objectField.ts b/src/parsing/v2/objectField.ts new file mode 100644 index 000000000..069fc9563 --- /dev/null +++ b/src/parsing/v2/objectField.ts @@ -0,0 +1,31 @@ +import { BaseField } from "./baseField"; +import { ListField } from "./listField"; +import { InferenceFields } from "./inferenceFields"; +import { StringDict } from "../common"; + +export class ObjectField extends BaseField { + readonly fields: InferenceFields; + + constructor(serverResponse: StringDict, indentLevel = 0) { + super(indentLevel); + + this.fields = new InferenceFields(serverResponse["fields"], this._indentLevel + 1); + } + + toString(): string { + let out = ""; + for (const [key, value] of this.fields.entries()) { + if (value instanceof ListField) { + const needsValue = value.items.length > 0; + const valueStr = + needsValue && value.toString() + ? " ".repeat(this._indentLevel) + value.toString() + : ""; + out += `${" ".repeat(this._indentLevel)}:${key}: ${valueStr}`; + } else { + out += `${" ".repeat(this._indentLevel)}:${key}: ${value.toString()}`; + } + } + return out; + } +} diff --git a/src/parsing/v2/rawText.ts b/src/parsing/v2/rawText.ts new file mode 100644 index 000000000..0a5c73932 --- /dev/null +++ b/src/parsing/v2/rawText.ts @@ -0,0 +1,21 @@ +import { StringDict } from "../common"; + + +export class RawText { + /** + * The page number the text was found on. + */ + public page: number; + /** + * The text content found on the page. + */ + public content: string; + + /** + * @param serverResponse JSON response from the server. + */ + constructor(serverResponse: StringDict) { + this.page = serverResponse["page"]; + this.content = serverResponse["content"]; + } +} diff --git a/src/parsing/v2/simpleField.ts b/src/parsing/v2/simpleField.ts new file mode 100644 index 000000000..02237182e --- /dev/null +++ b/src/parsing/v2/simpleField.ts @@ -0,0 +1,18 @@ +import { BaseField } from "./baseField"; +import { StringDict } from "../common"; + +export class SimpleField extends BaseField { + readonly value: string | number | boolean | null; + + constructor(serverResponse: StringDict, indentLevel = 0) { + super(indentLevel); + this.value = + serverResponse["value"] !== undefined ? (serverResponse["value"] as any) : null; + } + + toString(): string { + return this.value !== null && this.value !== undefined + ? `${this.value}\n` + : "\n"; + } +} From ad1d1c233ac75c0656da29a3b7ca772459ee5ab0 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:51:23 +0200 Subject: [PATCH 02/24] fix vunlerabilities --- package-lock.json | 865 +++++++++++----------------------------------- package.json | 8 +- 2 files changed, 199 insertions(+), 674 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1cfad9096..e3a4de2f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,15 +27,15 @@ "@types/mocha": "^10.0.10", "@types/node": "^18.15.11", "@types/tmp": "^0.2.6", - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", + "@typescript-eslint/eslint-plugin": "^8.36.0", + "@typescript-eslint/parser": "^8.36.0", "chai": "^4.3.10", "eslint": "^9.15.0", "eslint-plugin-jsdoc": "^50.5.0", - "mocha": "^11.1.0", + "mocha": "^11.7.1", "nock": "^13.5.6", "ts-node": "^10.9.2", - "typedoc": "~0.26.11", + "typedoc": "~0.28.7", "typescript": "^5.6.3" }, "engines": { @@ -43,9 +43,9 @@ } }, "node_modules/@cantoo/pdf-lib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@cantoo/pdf-lib/-/pdf-lib-2.4.1.tgz", - "integrity": "sha512-YAheO4lZK5HqOH6pi9TlO2jNys2Sfp8/5bAoIO0jctErBCRxu3x58uMsv/C4dc0raKZB3pb1jobRMhFuFy/eCA==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@cantoo/pdf-lib/-/pdf-lib-2.4.2.tgz", + "integrity": "sha512-ZqMiY8XEyM6Rc3WjpsQnrZYwCdyf/Emg2J3RbmSxoIKN1Kpa/93uIaO9cx/14dJoC6vkcAtMhrYsO7YLB8i8Lg==", "license": "MIT", "dependencies": { "@pdf-lib/standard-fonts": "^1.0.0", @@ -71,9 +71,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz", + "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==", "license": "MIT", "optional": true, "dependencies": { @@ -127,9 +127,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -142,9 +142,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -166,9 +166,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -213,9 +213,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -247,9 +247,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.30.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz", + "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==", "dev": true, "license": "MIT", "engines": { @@ -270,19 +270,46 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.7.0.tgz", + "integrity": "sha512-7iY9wg4FWXmeoFJpUL2u+tsmh0d0jcEJHAIzVxl3TG4KL493JNnisdLAILZ77zcD+z3J0keEXZ+lFzUgzQzPDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.7.0", + "@shikijs/langs": "^3.7.0", + "@shikijs/themes": "^3.7.0", + "@shikijs/types": "^3.7.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -739,9 +766,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", "dev": true, "license": "MIT" }, @@ -823,72 +850,45 @@ "node": ">=14" } }, - "node_modules/@shikijs/core": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.29.2.tgz", - "integrity": "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/engine-javascript": "1.29.2", - "@shikijs/engine-oniguruma": "1.29.2", - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz", - "integrity": "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "oniguruma-to-es": "^2.2.0" - } - }, "node_modules/@shikijs/engine-oniguruma": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", - "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.7.0.tgz", + "integrity": "sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1" + "@shikijs/types": "3.7.0", + "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-1.29.2.tgz", - "integrity": "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.7.0.tgz", + "integrity": "sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.2" + "@shikijs/types": "3.7.0" } }, "node_modules/@shikijs/themes": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-1.29.2.tgz", - "integrity": "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.7.0.tgz", + "integrity": "sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.2" + "@shikijs/types": "3.7.0" } }, "node_modules/@shikijs/types": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", - "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.7.0.tgz", + "integrity": "sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^10.0.1", + "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, @@ -941,9 +941,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -964,16 +964,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/@types/mocha": { "version": "10.0.10", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", @@ -982,9 +972,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.110", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.110.tgz", - "integrity": "sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q==", + "version": "18.19.117", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.117.tgz", + "integrity": "sha512-hcxGs9TfQGghOM8atpRT+bBMUX7V8WosdYt98bQ59wUToJck55eCOlemJ+0FpOZOQw5ff7LSi9+IO56KvYEFyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1006,17 +996,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", - "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz", + "integrity": "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/type-utils": "8.33.1", - "@typescript-eslint/utils": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.36.0", + "@typescript-eslint/type-utils": "8.36.0", + "@typescript-eslint/utils": "8.36.0", + "@typescript-eslint/visitor-keys": "8.36.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -1030,22 +1020,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.33.1", + "@typescript-eslint/parser": "^8.36.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", - "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.36.0.tgz", + "integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.36.0", + "@typescript-eslint/types": "8.36.0", + "@typescript-eslint/typescript-estree": "8.36.0", + "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4" }, "engines": { @@ -1061,14 +1051,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.36.0.tgz", + "integrity": "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/tsconfig-utils": "^8.36.0", + "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "engines": { @@ -1083,14 +1073,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.36.0.tgz", + "integrity": "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" + "@typescript-eslint/types": "8.36.0", + "@typescript-eslint/visitor-keys": "8.36.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1101,9 +1091,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.36.0.tgz", + "integrity": "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==", "dev": true, "license": "MIT", "engines": { @@ -1118,14 +1108,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", - "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.36.0.tgz", + "integrity": "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/typescript-estree": "8.36.0", + "@typescript-eslint/utils": "8.36.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -1142,9 +1132,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.36.0.tgz", + "integrity": "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==", "dev": true, "license": "MIT", "engines": { @@ -1156,16 +1146,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.36.0.tgz", + "integrity": "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/project-service": "8.36.0", + "@typescript-eslint/tsconfig-utils": "8.36.0", + "@typescript-eslint/types": "8.36.0", + "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1185,16 +1175,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.36.0.tgz", + "integrity": "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" + "@typescript-eslint/scope-manager": "8.36.0", + "@typescript-eslint/types": "8.36.0", + "@typescript-eslint/typescript-estree": "8.36.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1209,14 +1199,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.36.0.tgz", + "integrity": "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.36.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1227,9 +1217,9 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1239,13 +1229,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -1259,9 +1242,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -1408,9 +1391,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1496,17 +1479,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/chai": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", @@ -1543,28 +1515,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -1725,17 +1675,6 @@ "node": ">= 0.8" } }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/commander": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", @@ -1850,16 +1789,6 @@ "node": ">=0.4.0" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -1869,20 +1798,6 @@ "node": ">=8" } }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/diff": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", @@ -1927,13 +1842,6 @@ "dev": true, "license": "MIT" }, - "node_modules/emoji-regex-xs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", - "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", - "dev": true, - "license": "MIT" - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -2016,19 +1924,19 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.30.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz", + "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", + "@eslint/js": "9.30.1", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -2040,9 +1948,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2077,9 +1985,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.7.1.tgz", - "integrity": "sha512-XBnVA5g2kUVokTNUiE1McEPse5n9/mNUmuJcx52psT6zBs2eVcXSmQBvjfa7NZdfLVSy3u1pEDDUxoxpwy89WA==", + "version": "50.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.8.0.tgz", + "integrity": "sha512-UyGb5755LMFWPrZTEqqvTJ3urLz1iqj+bYOHFNag+sw3NvaMWP9K2z+uIn37XfNALmQLQyrBlJ5mkiVPL7ADEg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2102,9 +2010,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2132,9 +2040,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2143,9 +2051,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2179,15 +2087,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2197,9 +2105,9 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2638,44 +2546,6 @@ "node": ">= 0.4" } }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -2702,17 +2572,6 @@ ], "license": "MIT" }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -3048,28 +2907,6 @@ "node": ">= 0.4" } }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -3087,100 +2924,6 @@ "node": ">= 8" } }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -3243,9 +2986,9 @@ } }, "node_modules/mocha": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.5.0.tgz", - "integrity": "sha512-VKDjhy6LMTKm0WgNEdlY77YVsD49LZnPSXJAaPNL9NRYQADxvORsyG1DIQY6v53BKTnlNbEE2MbVCDbnxr4K3w==", + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.1.tgz", + "integrity": "sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A==", "dev": true, "license": "MIT", "dependencies": { @@ -3265,7 +3008,7 @@ "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", + "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" @@ -3324,9 +3067,9 @@ } }, "node_modules/node-html-better-parser": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/node-html-better-parser/-/node-html-better-parser-1.4.11.tgz", - "integrity": "sha512-rXYKBD30q6Iw/Y8o2ueJILB29Z3RrqQQN/U2PDg4Iz2TSZP/KmqdcGp+pl8o5uYZlvIKh+A4UfOK98pKIb3cRw==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/node-html-better-parser/-/node-html-better-parser-1.5.1.tgz", + "integrity": "sha512-K3OUfP3UvIgoxlcoj6e9zeszeEk4MfhmiG7aiRRFEdoNqnfILCtL/AoLJ8UWFvDlRJOgKPRIECqWxbr25btnyQ==", "license": "MIT", "dependencies": { "html-entities": "^2.3.2" @@ -3348,18 +3091,6 @@ "url": "https://github.com/sponsors/Fdawgs" } }, - "node_modules/oniguruma-to-es": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz", - "integrity": "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex-xs": "^1.0.0", - "regex": "^5.1.1", - "regex-recursion": "^5.1.1" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3575,17 +3306,6 @@ "node": ">= 8" } }, - "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3683,34 +3403,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/regex": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz", - "integrity": "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-recursion": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-5.1.1.tgz", - "integrity": "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "regex": "^5.1.1", - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "dev": true, - "license": "MIT" - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3870,23 +3562,6 @@ "node": ">=8" } }, - "node_modules/shiki": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.29.2.tgz", - "integrity": "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/core": "1.29.2", - "@shikijs/engine-javascript": "1.29.2", - "@shikijs/engine-oniguruma": "1.29.2", - "@shikijs/langs": "1.29.2", - "@shikijs/themes": "1.29.2", - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" - } - }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -3909,17 +3584,6 @@ "is-arrayish": "^0.3.1" } }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", @@ -4018,21 +3682,6 @@ "node": ">=8" } }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dev": true, - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -4155,17 +3804,6 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -4263,32 +3901,33 @@ } }, "node_modules/typedoc": { - "version": "0.26.11", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.11.tgz", - "integrity": "sha512-sFEgRRtrcDl2FxVP58Ze++ZK2UQAEvtvvH8rRlig1Ja3o7dDaMHmaBfvJmdGnNEFaLTpQsN8dpvZaTqJSu/Ugw==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.7.tgz", + "integrity": "sha512-lpz0Oxl6aidFkmS90VQDQjk/Qf2iw0IUvFqirdONBdj7jPSN9mGXhy66BcGNDxx5ZMyKKiBVAREvPEzT6Uxipw==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@gerrit0/mini-shiki": "^3.7.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", - "shiki": "^1.16.2", - "yaml": "^2.5.1" + "yaml": "^2.8.0" }, "bin": { "typedoc": "bin/typedoc" }, "engines": { - "node": ">= 18" + "node": ">= 18", + "pnpm": ">= 10" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x" + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4313,79 +3952,6 @@ "dev": true, "license": "MIT" }, - "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4403,36 +3969,6 @@ "dev": true, "license": "MIT" }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/web-streams-polyfill": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", @@ -4469,9 +4005,9 @@ } }, "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.3.tgz", + "integrity": "sha512-slxCaKbYjEdFT/o2rH9xS1hf4uRDch1w7Uo+apxhZ+sf/1d9e0ZVkn42kPNGP2dgjIx6YFvSevj0zHvbWe2jdw==", "dev": true, "license": "Apache-2.0" }, @@ -4705,17 +4241,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } } } } diff --git a/package.json b/package.json index aa1f5bf3e..f07d6ccb3 100644 --- a/package.json +++ b/package.json @@ -44,15 +44,15 @@ "@types/mocha": "^10.0.10", "@types/node": "^18.15.11", "@types/tmp": "^0.2.6", - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", + "@typescript-eslint/eslint-plugin": "^8.36.0", + "@typescript-eslint/parser": "^8.36.0", "chai": "^4.3.10", "eslint": "^9.15.0", "eslint-plugin-jsdoc": "^50.5.0", - "mocha": "^11.1.0", + "mocha": "^11.7.1", "nock": "^13.5.6", "ts-node": "^10.9.2", - "typedoc": "~0.26.11", + "typedoc": "~0.28.7", "typescript": "^5.6.3" }, "dependencies": { From 41460b09bb59340ddffb379b47f80c59c74ccbb6 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:29:54 +0200 Subject: [PATCH 03/24] add http logic --- src/baseClient.ts | 73 +++++++++++ src/client.ts | 95 ++------------ src/clientV2.ts | 239 +++++++++++++++++++++++++++++++++++ src/errors/mindeeError.ts | 11 ++ src/http/apiSettings.ts | 31 +---- src/http/apiSettingsV2.ts | 56 ++++++++ src/http/baseEndpoint.ts | 4 +- src/http/baseSettings.ts | 35 +++++ src/http/endpoint.ts | 12 +- src/http/mindeeApiV2.ts | 156 +++++++++++++++++++++++ src/http/workflowEndpoint.ts | 4 +- 11 files changed, 595 insertions(+), 121 deletions(-) create mode 100644 src/baseClient.ts create mode 100644 src/clientV2.ts create mode 100644 src/http/apiSettingsV2.ts create mode 100644 src/http/baseSettings.ts create mode 100644 src/http/mindeeApiV2.ts diff --git a/src/baseClient.ts b/src/baseClient.ts new file mode 100644 index 000000000..aa7e2a554 --- /dev/null +++ b/src/baseClient.ts @@ -0,0 +1,73 @@ +import { Base64Input, BufferInput, BytesInput, PathInput, StreamInput, UrlInput } from "./input"; +import { Readable } from "stream"; + + +export abstract class BaseClient { + /** + * Load an input document from a local path. + * @param inputPath + */ + docFromPath(inputPath: string): PathInput { + return new PathInput({ + inputPath: inputPath, + }); + } + + /** + * Load an input document from a base64 encoded string. + * @param inputString input content, as a string. + * @param filename file name. + */ + docFromBase64(inputString: string, filename: string): Base64Input { + return new Base64Input({ + inputString: inputString, + filename: filename, + }); + } + + /** + * Load an input document from a `stream.Readable` object. + * @param inputStream input content, as a readable stream. + * @param filename file name. + */ + docFromStream(inputStream: Readable, filename: string): StreamInput { + return new StreamInput({ + inputStream: inputStream, + filename: filename, + }); + } + + /** + * Load an input document from bytes. + * @param inputBytes input content, as a Uint8Array or Buffer. + * @param filename file name. + */ + docFromBytes(inputBytes: Uint8Array, filename: string): BytesInput { + return new BytesInput({ + inputBytes: inputBytes, + filename: filename, + }); + } + + /** + * Load an input document from a URL. + * @param url input url. Must be HTTPS. + */ + docFromUrl(url: string): UrlInput { + return new UrlInput({ + url: url, + }); + } + + /** + * Load an input document from a Buffer. + * @param buffer input content, as a buffer. + * @param filename file name. + */ + docFromBuffer(buffer: Buffer, filename: string): BufferInput { + return new BufferInput({ + buffer: buffer, + filename: filename, + }); + } +} diff --git a/src/client.ts b/src/client.ts index 750d9a174..7df1bff49 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,14 +1,7 @@ -import { Readable } from "stream"; import { - Base64Input, - BufferInput, - BytesInput, InputSource, LocalResponse, PageOptions, - PathInput, - StreamInput, - UrlInput, } from "./input"; import { ApiSettings, Endpoint, EndpointResponse, STANDARD_API_OWNER } from "./http"; import { @@ -28,6 +21,7 @@ import { setTimeout } from "node:timers/promises"; import { MindeeError } from "./errors"; import { WorkflowResponse } from "./parsing/common/workflowResponse"; import { WorkflowEndpoint } from "./http/workflowEndpoint"; +import { BaseClient } from "./baseClient"; /** * Common options for workflows & predictions. @@ -135,7 +129,7 @@ export interface ClientOptions { * * @category Client */ -export class Client { +export class Client extends BaseClient { /** Key of the API. */ protected apiKey: string; @@ -149,6 +143,7 @@ export class Client { debug: false, } ) { + super(); this.apiKey = apiKey ? apiKey : ""; errorHandler.throwOnError = throwOnError ?? true; logger.level = @@ -210,7 +205,7 @@ export class Client { const endpoint = params?.endpoint ?? this.#initializeOTSEndpoint(productClass); if (inputSource === undefined) { - throw new Error("The 'parse' function requires an input document."); + throw new Error("The 'enqueue' function requires an input document."); } const rawResponse = await endpoint.predictAsync({ inputDoc: inputSource, @@ -285,7 +280,7 @@ export class Client { ): Promise> { const workflowEndpoint = new WorkflowEndpoint(this.#buildApiSettings(), workflowId); if (inputSource === undefined) { - throw new Error("The 'parse' function requires an input document."); + throw new Error("The 'executeWorkflow' function requires an input document."); } const rawResponse = await workflowEndpoint.executeWorkflow({ inputDoc: inputSource, @@ -402,7 +397,7 @@ export class Client { const enqueueResponse: AsyncPredictResponse = await this.enqueue( productClass, inputSource, - asyncParams + validatedAsyncParams ); if (enqueueResponse.job.id === undefined || enqueueResponse.job.id.length === 0) { throw Error("Enqueueing of the document failed."); @@ -411,21 +406,21 @@ export class Client { logger.debug( `Successfully enqueued document with job id: ${queueId}.` ); - await setTimeout(validatedAsyncParams.initialDelaySec * 1000, undefined, asyncParams.initialTimerOptions); + await setTimeout(validatedAsyncParams.initialDelaySec * 1000, undefined, validatedAsyncParams.initialTimerOptions); let retryCounter: number = 1; let pollResults: AsyncPredictResponse; - pollResults = await this.parseQueued(productClass, queueId, asyncParams); + pollResults = await this.parseQueued(productClass, queueId, validatedAsyncParams); while (retryCounter < validatedAsyncParams.maxRetries) { logger.debug( `Polling server for parsing result with queueId: ${queueId}. -Attempt n°${retryCounter}/${asyncParams.maxRetries}. +Attempt n°${retryCounter}/${validatedAsyncParams.maxRetries}. Job status: ${pollResults.job.status}.` ); if (pollResults.job.status === "completed") { break; } - await setTimeout(validatedAsyncParams.delaySec * 1000, undefined, asyncParams.recurringTimerOptions); - pollResults = await this.parseQueued(productClass, queueId, asyncParams); + await setTimeout(validatedAsyncParams.delaySec * 1000, undefined, validatedAsyncParams.recurringTimerOptions); + pollResults = await this.parseQueued(productClass, queueId, validatedAsyncParams); retryCounter++; } if (pollResults.job.status !== "completed") { @@ -570,72 +565,4 @@ Job status: ${pollResults.job.status}.` InferenceFactory.getEndpoint(productClass); return [endpointName, endpointVersion]; } - - /** - * Load an input document from a local path. - * @param inputPath - */ - docFromPath(inputPath: string): PathInput { - return new PathInput({ - inputPath: inputPath, - }); - } - - /** - * Load an input document from a base64 encoded string. - * @param inputString input content, as a string. - * @param filename file name. - */ - docFromBase64(inputString: string, filename: string): Base64Input { - return new Base64Input({ - inputString: inputString, - filename: filename, - }); - } - - /** - * Load an input document from a `stream.Readable` object. - * @param inputStream input content, as a readable stream. - * @param filename file name. - */ - docFromStream(inputStream: Readable, filename: string): StreamInput { - return new StreamInput({ - inputStream: inputStream, - filename: filename, - }); - } - - /** - * Load an input document from bytes. - * @param inputBytes input content, as a Uint8Array or Buffer. - * @param filename file name. - */ - docFromBytes(inputBytes: Uint8Array, filename: string): BytesInput { - return new BytesInput({ - inputBytes: inputBytes, - filename: filename, - }); - } - - /** - * Load an input document from a URL. - * @param url input url. Must be HTTPS. - */ - docFromUrl(url: string): UrlInput { - return new UrlInput({ - url: url, - }); - } - - /** - * Load an input document from a Buffer. - * @param buffer input content, as a buffer. - * @param filename file name. - */ - docFromBuffer(buffer: Buffer, filename: string): BufferInput { - return new BufferInput({ - buffer: buffer, - filename: filename, - }); - } } diff --git a/src/clientV2.ts b/src/clientV2.ts new file mode 100644 index 000000000..789b1448f --- /dev/null +++ b/src/clientV2.ts @@ -0,0 +1,239 @@ +import { + LocalInputSource, + LocalResponse, + PageOptions, +} from "./input"; +import { errorHandler } from "./errors/handler"; +import { LOG_LEVELS, logger } from "./logger"; + +import { setTimeout } from "node:timers/promises"; +import { MindeeError } from "./errors"; +import { InferenceResponse, JobResponse } from "./parsing/v2"; +import { MindeeApiV2 } from "./http/mindeeApiV2"; +import { BaseClient } from "./baseClient"; + +/** + * Asynchronous polling parameters. + */ +interface OptionalPollingOptions { + initialDelaySec?: number; + delaySec?: number; + maxRetries?: number; + initialTimerOptions?: { + ref?: boolean, + signal?: AbortSignal + }; + recurringTimerOptions?: { + ref?: boolean, + signal?: AbortSignal + } +} + +interface PollingOptions { + initialDelaySec: number; + delaySec: number; + maxRetries: number; + initialTimerOptions?: { + ref?: boolean, + signal?: AbortSignal + }; + recurringTimerOptions?: { + ref?: boolean, + signal?: AbortSignal + } +} + +export interface InferencePredictOptions { + /** ID of the model. **Required**. */ + modelId: string; + /** Enable Retrieval-Augmented Generation (RAG). */ + rag?: boolean; + /** Optional alias for the file. */ + alias?: string; + /** IDs of the webhooks that should receive the API response. */ + webhookIds?: string[]; + /** Page-level inference options. */ + pageOptions?: PageOptions; + /** Polling options. */ + pollingOptions?: OptionalPollingOptions; + /** Set to `false` if the file must remain open after parsing. */ + closeFile?: boolean; +} + + +export interface ClientOptions { + /** Your API key for all endpoints. */ + apiKey?: string; + /** Raise an `Error` on errors. */ + throwOnError?: boolean; + /** Log debug messages. */ + debug?: boolean; +} + +/** + * Mindee Client V2 class that centralizes most basic operations. + * + * @category ClientV2 + */ +export class ClientV2 extends BaseClient { + /** Key of the API. */ + protected mindeeApi: MindeeApiV2; + + /** + * @param {ClientOptions} options options for the initialization of a client. + */ + constructor( + { apiKey, throwOnError, debug }: ClientOptions = { + apiKey: "", + throwOnError: true, + debug: false, + } + ) { + super(); + this.mindeeApi = new MindeeApiV2(apiKey); + errorHandler.throwOnError = throwOnError ?? true; + logger.level = + debug ?? process.env.MINDEE_DEBUG + ? LOG_LEVELS["debug"] + : LOG_LEVELS["warn"]; + logger.debug("ClientV2 initialized"); + } + + /** + * Send the document to an asynchronous endpoint and return its ID in the queue. + * @param inputSource file to parse. + * @param params parameters relating to prediction options. + * @category Asynchronous + * @returns a `Promise` containing the job (queue) corresponding to a document. + */ + async enqueue( + inputSource: LocalInputSource, + params: InferencePredictOptions + ): Promise { + if (inputSource === undefined) { + throw new Error("The 'enqueue' function requires an input document."); + } + return await this.mindeeApi.predictAsyncReqPost(inputSource, params); + } + + /** + * Polls a queue and returns its status as well as the inference results if the parsing is done. + * + * @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`. + * @category Asynchronous + * @returns a `Promise` containing a `Job`, which also contains a `Document` if the + * parsing is complete. + */ + async parseQueued(jobId: string): Promise { + return await this.mindeeApi.getQueuedDocument(jobId); + } + + /** + * Load an inference. + * + * @param localResponse Local response to load. + * @category V2 + * @returns A valid prediction + */ + loadInference( + localResponse: LocalResponse + ): InferenceResponse { + try { + return new InferenceResponse(localResponse.asDict()); + } catch { + throw new MindeeError("No prediction found in local response."); + } + } + + /** + * Checks the values for asynchronous parsing. Returns their corrected value if they are undefined. + * @param asyncParams parameters related to asynchronous parsing + * @returns A valid `AsyncOptions`. + */ + #setAsyncParams(asyncParams: OptionalPollingOptions | undefined = undefined): PollingOptions { + const minDelaySec = 1; + const minInitialDelay = 1; + const minRetries = 2; + let newAsyncParams: OptionalPollingOptions; + if (asyncParams === undefined) { + newAsyncParams = { + delaySec: 1.5, + initialDelaySec: 2, + maxRetries: 80 + }; + } else { + newAsyncParams = { ...asyncParams }; + if ( + !newAsyncParams.delaySec || + !newAsyncParams.initialDelaySec || + !newAsyncParams.maxRetries + ) { + throw Error("Invalid polling options."); + } + if (newAsyncParams.delaySec < minDelaySec) { + throw Error(`Cannot set auto-parsing delay to less than ${minDelaySec} second(s).`); + } + if (newAsyncParams.initialDelaySec < minInitialDelay) { + throw Error(`Cannot set initial parsing delay to less than ${minInitialDelay} second(s).`); + } + if (newAsyncParams.maxRetries < minRetries) { + throw Error(`Cannot set retry to less than ${minRetries}.`); + } + } + return newAsyncParams as PollingOptions; + } + + /** + * 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 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 enqueueAndParse( + inputDoc: LocalInputSource, + params: InferencePredictOptions + ): Promise { + const validatedAsyncParams = this.#setAsyncParams(params.pollingOptions); + const enqueueResponse: JobResponse = await this.enqueue(inputDoc, 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."); + } + const queueId: string = enqueueResponse.job.id; + logger.debug( + `Successfully enqueued document with job id: ${queueId}.` + ); + + await setTimeout(validatedAsyncParams.initialDelaySec * 1000, undefined, validatedAsyncParams.initialTimerOptions); + let retryCounter: number = 1; + let pollResults: JobResponse | InferenceResponse; + pollResults = await this.parseQueued(queueId); + while (retryCounter < validatedAsyncParams.maxRetries) { + if (pollResults instanceof InferenceResponse) { + break; + } + logger.debug( + `Polling server for parsing result with queueId: ${queueId}. +Attempt n°${retryCounter}/${validatedAsyncParams.maxRetries}. +Job status: ${pollResults.job.status}.` + ); + await setTimeout(validatedAsyncParams.delaySec * 1000, undefined, validatedAsyncParams.recurringTimerOptions); + pollResults = await this.parseQueued(queueId); + retryCounter++; + } + if (pollResults instanceof JobResponse) { + throw Error( + "Asynchronous parsing request timed out after " + + validatedAsyncParams.delaySec * retryCounter + + " seconds" + ); + } + return pollResults; + } +} diff --git a/src/errors/mindeeError.ts b/src/errors/mindeeError.ts index 34ee4b12c..7328c1a52 100644 --- a/src/errors/mindeeError.ts +++ b/src/errors/mindeeError.ts @@ -40,4 +40,15 @@ export class MindeeApiV2Error extends MindeeError { } } +export class MindeeHttpErrorV2 extends MindeeError { + public code: number; + public detail: string; + constructor(code: number, detail: string) { + super(`HTTP ${code} - ${detail}`); + this.code = code; + this.detail = detail; + this.name = "MindeeHttpErrorV2"; + } +} + diff --git a/src/http/apiSettings.ts b/src/http/apiSettings.ts index 01b1af4ec..07e908f01 100644 --- a/src/http/apiSettings.ts +++ b/src/http/apiSettings.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { logger } from "../logger"; -import { version as sdkVersion } from "../../package.json"; -import * as os from "os"; +import { BaseSettings, MindeeApiConstructorProps } from "./baseSettings"; export const API_KEY_ENVVAR_NAME: string = "MINDEE_API_KEY"; export const API_HOST_ENVVAR_NAME: string = "MINDEE_API_HOST"; @@ -9,19 +8,14 @@ export const STANDARD_API_OWNER: string = "mindee"; export const TIMEOUT_DEFAULT: number = 120; const DEFAULT_MINDEE_API_HOST: string = "api.mindee.net"; -interface MindeeApiConstructorProps { - apiKey: string; -} - -export class ApiSettings { +export class ApiSettings extends BaseSettings { apiKey: string; baseHeaders: Record; - hostname: string; - timeout: number; constructor({ apiKey = "", }: MindeeApiConstructorProps) { + super(); if (!apiKey || apiKey.length === 0) { this.apiKey = this.apiKeyFromEnv(); } else { @@ -35,27 +29,10 @@ export class ApiSettings { } this.baseHeaders = { "User-Agent": this.getUserAgent(), - Authorization: `Token ${this.apiKey}`, + Authorization: `Token ${apiKey}`, }; - this.hostname = this.hostnameFromEnv(); - this.timeout = process.env.MINDEE_REQUEST_TIMEOUT ? parseInt(process.env.MINDEE_REQUEST_TIMEOUT) : TIMEOUT_DEFAULT; } - protected getUserAgent(): string { - let platform = os.type().toLowerCase(); - if (platform.includes("darwin")) { - platform = "macos"; - } - else if (platform.includes("window")) { - platform = "windows"; - } - else if (platform.includes("bsd")) { - platform = "bsd"; - } - return `mindee-api-nodejs@v${sdkVersion} nodejs-${ - process.version - } ${platform}`; - } protected apiKeyFromEnv(): string { const envVarValue = process.env[API_KEY_ENVVAR_NAME]; diff --git a/src/http/apiSettingsV2.ts b/src/http/apiSettingsV2.ts new file mode 100644 index 000000000..c1f1003b2 --- /dev/null +++ b/src/http/apiSettingsV2.ts @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { logger } from "../logger"; +import { BaseSettings, MindeeApiConstructorProps } from "./baseSettings"; + +export const API_KEY_ENVVAR_NAME: string = "MINDEE_V2_API_KEY"; +export const API_HOST_ENVVAR_NAME: string = "MINDEE_V2_API_HOST"; +const DEFAULT_MINDEE_API_HOST: string = "api-v2.mindee.net"; + +export class ApiSettingsV2 extends BaseSettings { + apiKey: string; + baseHeaders: Record; + + constructor({ + apiKey = "", + }: MindeeApiConstructorProps) { + super(); + if (!apiKey || apiKey.length === 0) { + this.apiKey = this.apiKeyFromEnv(); + } else { + this.apiKey = apiKey; + } + if (!this.apiKey || this.apiKey.length === 0) { + throw new Error( + "Your API V2 key could not be set, check your Client Configuration\n." + + `You can set this using the ${API_KEY_ENVVAR_NAME} environment variable.` + ); + } + this.baseHeaders = { + "User-Agent": this.getUserAgent(), + Authorization: `${apiKey}`, + }; + } + + + protected apiKeyFromEnv(): string { + const envVarValue = process.env[API_KEY_ENVVAR_NAME]; + if (envVarValue) { + logger.debug( + `Set API key from environment: ${API_KEY_ENVVAR_NAME}` + ); + return envVarValue; + } + return ""; + } + + protected hostnameFromEnv(): string { + const envVarValue = process.env[API_HOST_ENVVAR_NAME]; + if (envVarValue) { + logger.debug(`Set the API hostname to ${envVarValue}`); + return envVarValue; + } + return DEFAULT_MINDEE_API_HOST; + } + + +} diff --git a/src/http/baseEndpoint.ts b/src/http/baseEndpoint.ts index 573e6023c..598920ed1 100644 --- a/src/http/baseEndpoint.ts +++ b/src/http/baseEndpoint.ts @@ -34,7 +34,7 @@ export abstract class BaseEndpoint { * @param inputDoc input document. * @param pageOptions page cutting options. */ - protected async cutDocPages(inputDoc: InputSource, pageOptions: PageOptions) { + public static async cutDocPages(inputDoc: InputSource, pageOptions: PageOptions) { if (inputDoc instanceof LocalInputSource && inputDoc.isPdf()) { await inputDoc.cutPdf(pageOptions); } @@ -47,7 +47,7 @@ export abstract class BaseEndpoint { * @param reject promise rejection reason. * @returns the processed request. */ - protected readResponse( + public static readResponse( options: RequestOptions, resolve: (value: EndpointResponse | PromiseLike) => void, reject: (reason?: any) => void diff --git a/src/http/baseSettings.ts b/src/http/baseSettings.ts new file mode 100644 index 000000000..90be69302 --- /dev/null +++ b/src/http/baseSettings.ts @@ -0,0 +1,35 @@ +import { version as sdkVersion } from "../../package.json"; +import * as os from "os"; +import { TIMEOUT_DEFAULT } from "./apiSettings"; + +export interface MindeeApiConstructorProps { + apiKey?: string; +} + +export abstract class BaseSettings { + hostname: string; + timeout: number; + + protected constructor() { + this.hostname = this.hostnameFromEnv(); + this.timeout = process.env.MINDEE_REQUEST_TIMEOUT ? parseInt(process.env.MINDEE_REQUEST_TIMEOUT) : TIMEOUT_DEFAULT; + } + + protected getUserAgent(): string { + let platform = os.type().toLowerCase(); + if (platform.includes("darwin")) { + platform = "macos"; + } + else if (platform.includes("window")) { + platform = "windows"; + } + else if (platform.includes("bsd")) { + platform = "bsd"; + } + return `mindee-api-nodejs@v${sdkVersion} nodejs-${ + process.version + } ${platform}`; + } + protected abstract apiKeyFromEnv(): string; + protected abstract hostnameFromEnv(): string; +} diff --git a/src/http/endpoint.ts b/src/http/endpoint.ts index 31806c206..bba83afb9 100644 --- a/src/http/endpoint.ts +++ b/src/http/endpoint.ts @@ -51,7 +51,7 @@ export class Endpoint extends BaseEndpoint { async predict(params: PredictParams): Promise { await params.inputDoc.init(); if (params.pageOptions !== undefined) { - await super.cutDocPages(params.inputDoc, params.pageOptions); + await BaseEndpoint.cutDocPages(params.inputDoc, params.pageOptions); } const response = await this.#predictReqPost( params.inputDoc, @@ -76,7 +76,7 @@ export class Endpoint extends BaseEndpoint { async predictAsync(params: PredictParams): Promise { await params.inputDoc.init(); if (params.pageOptions !== undefined) { - await super.cutDocPages(params.inputDoc, params.pageOptions); + await BaseEndpoint.cutDocPages(params.inputDoc, params.pageOptions); } const response = await this.#predictAsyncReqPost( params.inputDoc, @@ -227,7 +227,7 @@ export class Endpoint extends BaseEndpoint { path: path, timeout: this.settings.timeout, }; - const req = this.readResponse(options, resolve, reject); + const req = BaseEndpoint.readResponse(options, resolve, reject); form.pipe(req); // potential ECONNRESET if we don't end the request. req.end(); @@ -290,7 +290,7 @@ export class Endpoint extends BaseEndpoint { hostname: this.settings.hostname, path: `${this.urlRoot}/documents/queue/${queueId}`, }; - const req = this.readResponse(options, resolve, reject); + const req = BaseEndpoint.readResponse(options, resolve, reject); // potential ECONNRESET if we don't end the request. req.end(); }); @@ -308,7 +308,7 @@ export class Endpoint extends BaseEndpoint { hostname: this.settings.hostname, path: `${this.urlRoot}/documents/${documentId}`, }; - const req = this.readResponse(options, resolve, reject); + const req = BaseEndpoint.readResponse(options, resolve, reject); // potential ECONNRESET if we don't end the request. req.end(); }); @@ -327,7 +327,7 @@ export class Endpoint extends BaseEndpoint { hostname: this.settings.hostname, path: `/v1/documents/${documentId}/feedback`, }; - const req: ClientRequest = this.readResponse(options, resolve, reject); + const req: ClientRequest = BaseEndpoint.readResponse(options, resolve, reject); req.write(JSON.stringify(feedback)); // potential ECONNRESET if we don't end the request. diff --git a/src/http/mindeeApiV2.ts b/src/http/mindeeApiV2.ts new file mode 100644 index 000000000..46b1cbaf1 --- /dev/null +++ b/src/http/mindeeApiV2.ts @@ -0,0 +1,156 @@ +import { ApiSettingsV2 } from "./apiSettingsV2"; +import { InferencePredictOptions } from "../clientV2"; +import { InferenceResponse, JobResponse } from "../parsing/v2"; +import FormData from "form-data"; +import { RequestOptions } from "https"; +import { BaseEndpoint, EndpointResponse } from "./baseEndpoint"; +import { LocalInputSource } from "../input"; +import { MindeeApiV2Error, MindeeHttpErrorV2 } from "../errors/mindeeError"; +import { logger } from "../logger"; + +export class MindeeApiV2 { + settings: ApiSettingsV2; + + constructor(apiKey?: string) { + this.settings = new ApiSettingsV2({ apiKey: apiKey }); + } + + /** + * Sends a file to the inference queue. + * @param inputDoc Local file loaded as an input. + * @param params {InferencePredictOptions} parameters relating to the enqueueing options. + * @category V2 + * @throws Error if the server's response contains one. + * @returns a `Promise` containing a job response. + */ + async predictAsyncReqPost(inputDoc: LocalInputSource, params: InferencePredictOptions): Promise { + await inputDoc.init(); + if (params.pageOptions !== undefined) { + await BaseEndpoint.cutDocPages(inputDoc, params.pageOptions); + } + if (params.modelId === undefined || params.modelId === null || params.modelId === "") { + throw new Error("Model ID must be provided"); + } + const result: EndpointResponse = await this.#documentEnqueuePost(inputDoc, params); + if (result.data.error?.code !== undefined) { + throw new MindeeHttpErrorV2( + result.data.error.code, + result.data.error.message ?? "Unknown error." + ); + } + return this.#processResponse(result, "job") as JobResponse; + } + + + /** + * Requests the results of a queued document from the API. + * Throws an error if the server's response contains one. + * @param jobId The document's ID in the queue. + * @category Asynchronous + * @returns a `Promise` containing either the parsed result, or information on the queue. + */ + async getQueuedDocument(jobId: string): Promise { + const queueResponse: EndpointResponse = await this.#documentQueueReqGet(jobId); + const queueStatusCode = queueResponse.messageObj.statusCode; + if ( + queueStatusCode === 302 && + queueResponse.messageObj.headers.location !== undefined + ) { + const docId = queueResponse.messageObj.headers.location.split("/").pop(); + if (docId !== undefined) { + return this.#processResponse(await this.#documentGetReq(docId), "inference") as InferenceResponse; + } + } + return this.#processResponse(queueResponse, "job") as JobResponse; + } + + #processResponse(result: EndpointResponse, responseType: string): JobResponse | InferenceResponse { + if (result.messageObj?.statusCode && result.messageObj?.statusCode > 399) { + throw new MindeeHttpErrorV2( + result.messageObj?.statusCode ?? -1, result.messageObj?.statusMessage ?? "Unknown error." + ); + } + try { + if (responseType === "inference") { + return new InferenceResponse(result.data); + } + return new JobResponse(result.data); + } catch (e) { + logger.error(`Raised '${e}' Couldn't deserialize response object:\n${result.data}`); + throw new MindeeApiV2Error("Couldn't deserialize response object."); + } + } + + /** + * Sends a document to the inference queue. + * + * @param inputDoc Local file loaded as an input. + * @param params {InferencePredictOptions} parameters relating to the enqueueing options. + */ + #documentEnqueuePost(inputDoc: LocalInputSource, params: InferencePredictOptions): Promise { + const form = new FormData(); + + form.append("model_id", params.modelId); + if (params.rag) { + form.append("rag", "true"); + } + if (params.webhookIds && params.webhookIds.length > 0) { + form.append("webhook_ids", params.webhookIds.join(",")); + } + form.append("file", inputDoc.fileObject, { + filename: inputDoc.filename, + }); + const path = "/v2/inferences/enqueue"; + const headers = { ...this.settings.baseHeaders, ...form.getHeaders() }; + const options: RequestOptions = { + method: "POST", + headers: headers, + hostname: this.settings.hostname, + path: path, + timeout: this.settings.timeout, + }; + return new Promise((resolve, reject) => { + const req = BaseEndpoint.readResponse(options, resolve, reject); + form.pipe(req); + // potential ECONNRESET if we don't end the request. + req.end(); + }); + } + + /** + * Make a request to GET a document. + * @param queueId + */ + #documentGetReq(queueId: string): Promise { + return new Promise((resolve, reject) => { + const options = { + method: "GET", + headers: this.settings.baseHeaders, + hostname: this.settings.hostname, + path: `/inferences/${queueId}`, + }; + const req = BaseEndpoint.readResponse(options, resolve, reject); + // potential ECONNRESET if we don't end the request. + req.end(); + }); + } + + + /** + * Make a request to GET the status of a document in the queue. + * @param queueId + */ + #documentQueueReqGet(queueId: string): Promise { + return new Promise((resolve, reject) => { + const options = { + method: "GET", + headers: this.settings.baseHeaders, + hostname: this.settings.hostname, + path: `/jobs/${queueId}`, + }; + const req = BaseEndpoint.readResponse(options, resolve, reject); + // potential ECONNRESET if we don't end the request. + req.end(); + }); + } +} diff --git a/src/http/workflowEndpoint.ts b/src/http/workflowEndpoint.ts index 0e867a4c1..44e1a6cba 100644 --- a/src/http/workflowEndpoint.ts +++ b/src/http/workflowEndpoint.ts @@ -32,7 +32,7 @@ export class WorkflowEndpoint extends BaseEndpoint { async executeWorkflow(params: WorkflowParams): Promise { await params.inputDoc.init(); if (params.pageOptions !== undefined) { - await super.cutDocPages(params.inputDoc, params.pageOptions); + await BaseEndpoint.cutDocPages(params.inputDoc, params.pageOptions); } const response = await this.#workflowReqPost(params); if (!isValidSyncResponse(response)) { @@ -117,7 +117,7 @@ export class WorkflowEndpoint extends BaseEndpoint { path: path, timeout: this.settings.timeout, }; - const req = this.readResponse(options, resolve, reject); + const req = BaseEndpoint.readResponse(options, resolve, reject); form.pipe(req); // potential ECONNRESET if we don't end the request. req.end(); From 21f59d826fc7a8551bc50c541f6c47f9583d9299 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:36:43 +0200 Subject: [PATCH 04/24] more elegant solution for job/inference internal deserialization --- src/http/mindeeApiV2.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/http/mindeeApiV2.ts b/src/http/mindeeApiV2.ts index 46b1cbaf1..887d41f66 100644 --- a/src/http/mindeeApiV2.ts +++ b/src/http/mindeeApiV2.ts @@ -38,7 +38,7 @@ export class MindeeApiV2 { result.data.error.message ?? "Unknown error." ); } - return this.#processResponse(result, "job") as JobResponse; + return this.#processResponse(result, JobResponse); } @@ -58,23 +58,21 @@ export class MindeeApiV2 { ) { const docId = queueResponse.messageObj.headers.location.split("/").pop(); if (docId !== undefined) { - return this.#processResponse(await this.#documentGetReq(docId), "inference") as InferenceResponse; + return this.#processResponse(await this.#documentGetReq(docId), InferenceResponse); } } - return this.#processResponse(queueResponse, "job") as JobResponse; + return this.#processResponse(queueResponse, JobResponse); } - #processResponse(result: EndpointResponse, responseType: string): JobResponse | InferenceResponse { + #processResponse + (result: EndpointResponse, responseType: new (data: { [key: string]: any; } ) => T): T { if (result.messageObj?.statusCode && result.messageObj?.statusCode > 399) { throw new MindeeHttpErrorV2( result.messageObj?.statusCode ?? -1, result.messageObj?.statusMessage ?? "Unknown error." ); } try { - if (responseType === "inference") { - return new InferenceResponse(result.data); - } - return new JobResponse(result.data); + return new responseType(result.data); } catch (e) { logger.error(`Raised '${e}' Couldn't deserialize response object:\n${result.data}`); throw new MindeeApiV2Error("Couldn't deserialize response object."); From 40036150458c02f2e208209df87d73b6031851c1 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:41:26 +0200 Subject: [PATCH 05/24] add code sample + fix syntax --- docs/code_samples/default_v2.txt | 20 +++++++++++++ src/http/mindeeApiV2.ts | 15 +++++----- src/index.ts | 1 + src/parsing/common/dateParser.ts | 6 ++++ src/parsing/common/execution.ts | 14 ++++----- src/parsing/common/index.ts | 1 + src/parsing/v2/baseField.ts | 33 -------------------- src/parsing/v2/fieldFactory.ts | 32 ++++++++++++++++++++ src/parsing/v2/index.ts | 1 + src/parsing/v2/inference.ts | 14 +++++++++ src/parsing/v2/inferenceFields.ts | 50 ++++++++++++++++++++++++++++--- src/parsing/v2/inferenceResult.ts | 2 +- src/parsing/v2/job.ts | 23 +++++++------- src/parsing/v2/listField.ts | 3 +- src/parsing/v2/webhook.ts | 33 ++++++++++++++++++++ tests/clientV2.spec.ts | 0 tests/test_code_samples.sh | 18 +++++++++++ 17 files changed, 201 insertions(+), 65 deletions(-) create mode 100644 docs/code_samples/default_v2.txt create mode 100644 src/parsing/common/dateParser.ts create mode 100644 src/parsing/v2/fieldFactory.ts create mode 100644 src/parsing/v2/webhook.ts create mode 100644 tests/clientV2.spec.ts diff --git a/docs/code_samples/default_v2.txt b/docs/code_samples/default_v2.txt new file mode 100644 index 000000000..d1666811d --- /dev/null +++ b/docs/code_samples/default_v2.txt @@ -0,0 +1,20 @@ +const mindee = require("mindee"); +// for TS or modules: +// import * as mindee from "mindee"; + +// Init a new client +const mindeeClient = new mindee.ClientV2({ apiKey: "MY_API_KEY" }); + +// Load a file from disk +const inputSource = mindeeClient.docFromPath("/path/to/the/file.ext"); + +const apiResponse = mindeeClient.enqueueAndParse( + inputSource, + { modelId: "MY_MODEL_ID" } +); + +// Handle the response Promise +apiResponse.then((resp) => { + // print a string summary + console.log(resp.inference.toString()); +}); diff --git a/src/http/mindeeApiV2.ts b/src/http/mindeeApiV2.ts index 887d41f66..64a62d03e 100644 --- a/src/http/mindeeApiV2.ts +++ b/src/http/mindeeApiV2.ts @@ -5,7 +5,7 @@ import FormData from "form-data"; import { RequestOptions } from "https"; import { BaseEndpoint, EndpointResponse } from "./baseEndpoint"; import { LocalInputSource } from "../input"; -import { MindeeApiV2Error, MindeeHttpErrorV2 } from "../errors/mindeeError"; +import { MindeeHttpErrorV2 } from "../errors/mindeeError"; import { logger } from "../logger"; export class MindeeApiV2 { @@ -65,8 +65,8 @@ export class MindeeApiV2 { } #processResponse - (result: EndpointResponse, responseType: new (data: { [key: string]: any; } ) => T): T { - if (result.messageObj?.statusCode && result.messageObj?.statusCode > 399) { + (result: EndpointResponse, responseType: new (data: { [key: string]: any; }) => T): T { + if (result.messageObj?.statusCode && (result.messageObj?.statusCode > 399 || result.messageObj?.statusCode < 200)) { throw new MindeeHttpErrorV2( result.messageObj?.statusCode ?? -1, result.messageObj?.statusMessage ?? "Unknown error." ); @@ -74,8 +74,9 @@ export class MindeeApiV2 { try { return new responseType(result.data); } catch (e) { - logger.error(`Raised '${e}' Couldn't deserialize response object:\n${result.data}`); - throw new MindeeApiV2Error("Couldn't deserialize response object."); + logger.error(`Raised '${e}' Couldn't deserialize response object:\n${JSON.stringify(result.data)}`); + throw e; + // throw new MindeeApiV2Error("Couldn't deserialize response object."); } } @@ -125,7 +126,7 @@ export class MindeeApiV2 { method: "GET", headers: this.settings.baseHeaders, hostname: this.settings.hostname, - path: `/inferences/${queueId}`, + path: `/v2/inferences/${queueId}`, }; const req = BaseEndpoint.readResponse(options, resolve, reject); // potential ECONNRESET if we don't end the request. @@ -144,7 +145,7 @@ export class MindeeApiV2 { method: "GET", headers: this.settings.baseHeaders, hostname: this.settings.hostname, - path: `/jobs/${queueId}`, + path: `/v2/jobs/${queueId}`, }; const req = BaseEndpoint.readResponse(options, resolve, reject); // potential ECONNRESET if we don't end the request. diff --git a/src/index.ts b/src/index.ts index 0bb1c9843..298297762 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export * as product from "./product"; export { Client, PredictOptions } from "./client"; +export { ClientV2, InferencePredictOptions } from "./clientV2"; export { AsyncPredictResponse, PredictResponse, diff --git a/src/parsing/common/dateParser.ts b/src/parsing/common/dateParser.ts new file mode 100644 index 000000000..6ee5b8432 --- /dev/null +++ b/src/parsing/common/dateParser.ts @@ -0,0 +1,6 @@ +export function parseDate(dateString: string | null): Date | null { + if (!dateString) { + return null; + } + return new Date(dateString); +} diff --git a/src/parsing/common/execution.ts b/src/parsing/common/execution.ts index 8465ae43d..24ff111ad 100644 --- a/src/parsing/common/execution.ts +++ b/src/parsing/common/execution.ts @@ -3,6 +3,7 @@ import { GeneratedV1Document } from "../../product/generated/generatedV1Document import { ExecutionFile } from "./executionFile"; import { StringDict } from "./stringDict"; import { ExecutionPriority } from "./executionPriority"; +import { parseDate } from "./dateParser"; /** * Representation of an execution for a workflow. @@ -50,23 +51,18 @@ export class Execution { constructor(inferenceClass: new (serverResponse: StringDict) => T, jsonResponse: StringDict) { this.batchName = jsonResponse["batch_name"]; - this.createdAt = jsonResponse["created_at"] ? this.parseDate(jsonResponse["created_at"]) : null; + this.createdAt = parseDate(jsonResponse["created_at"]); this.file = jsonResponse["file"]; this.id = jsonResponse["id"]; this.inference = jsonResponse["inference"] ? new inferenceClass(jsonResponse["inference"]) : null; this.priority = jsonResponse["priority"]; - this.reviewedAt = this.parseDate(jsonResponse["reviewed_at"]); - this.availableAt = this.parseDate(jsonResponse["available_at"]); + this.reviewedAt = parseDate(jsonResponse["reviewed_at"]); + this.availableAt = parseDate(jsonResponse["available_at"]); this.reviewedPrediction = jsonResponse["reviewed_prediction"] ? new GeneratedV1Document(jsonResponse["reviewed_prediction"]) : null; this.status = jsonResponse["status"]; this.type = jsonResponse["type"]; - this.uploadedAt = this.parseDate(jsonResponse["uploaded_at"]); + this.uploadedAt = parseDate(jsonResponse["uploaded_at"]); this.workflowId = jsonResponse["workflow_id"]; } - - private parseDate(dateString: string | null): Date | null { - if (!dateString) return null; - return new Date(dateString); - } } diff --git a/src/parsing/common/index.ts b/src/parsing/common/index.ts index 05b1639c9..914cf4ccd 100644 --- a/src/parsing/common/index.ts +++ b/src/parsing/common/index.ts @@ -13,3 +13,4 @@ export { Page } from "./page"; export { cleanOutString, lineSeparator } from "./summaryHelper"; export * as extras from "./extras"; export { floatToString, cleanSpecialChars } from "./summaryHelper"; +export { parseDate } from "./dateParser"; diff --git a/src/parsing/v2/baseField.ts b/src/parsing/v2/baseField.ts index f5458e1fe..35e091aa8 100644 --- a/src/parsing/v2/baseField.ts +++ b/src/parsing/v2/baseField.ts @@ -1,40 +1,7 @@ -import { StringDict } from "../common"; -import { MindeeApiV2Error } from "../../errors/mindeeError"; -import { ListField } from "./listField"; -import { ObjectField } from "./objectField"; -import { SimpleField } from "./simpleField"; - export abstract class BaseField { protected _indentLevel: number; protected constructor(indentLevel = 0) { this._indentLevel = indentLevel; } - - /** - * Factory helper, mirrors `BaseField.create_field` in Python. - */ - static createField(serverResponse: StringDict, indentLevel = 0) { - if (typeof serverResponse !== "object" || serverResponse === null) { - throw new MindeeApiV2Error( - `Unrecognized field format ${JSON.stringify(serverResponse)}.` - ); - } - - if ("items" in serverResponse) { - return new ListField(serverResponse, indentLevel); - } - - if ("fields" in serverResponse) { - return new ObjectField(serverResponse, indentLevel); - } - - if ("value" in serverResponse) { - return new SimpleField(serverResponse, indentLevel); - } - - throw new MindeeApiV2Error( - `Unrecognized field format in ${JSON.stringify(serverResponse)}.` - ); - } } diff --git a/src/parsing/v2/fieldFactory.ts b/src/parsing/v2/fieldFactory.ts new file mode 100644 index 000000000..9f0a8e660 --- /dev/null +++ b/src/parsing/v2/fieldFactory.ts @@ -0,0 +1,32 @@ +/** + * Factory helper. + */ +import { StringDict } from "../common"; +import { MindeeApiV2Error } from "../../errors/mindeeError"; +import { ListField } from "./listField"; +import { ObjectField } from "./objectField"; +import { SimpleField } from "./simpleField"; + +export function createField(serverResponse: StringDict, indentLevel = 0) { + if (typeof serverResponse !== "object" || serverResponse === null) { + throw new MindeeApiV2Error( + `Unrecognized field format ${JSON.stringify(serverResponse)}.` + ); + } + + if ("items" in serverResponse) { + return new ListField(serverResponse, indentLevel); + } + + if ("fields" in serverResponse) { + return new ObjectField(serverResponse, indentLevel); + } + + if ("value" in serverResponse) { + return new SimpleField(serverResponse, indentLevel); + } + + throw new MindeeApiV2Error( + `Unrecognized field format in ${JSON.stringify(serverResponse)}.` + ); +} diff --git a/src/parsing/v2/index.ts b/src/parsing/v2/index.ts index d255cbfb3..178473057 100644 --- a/src/parsing/v2/index.ts +++ b/src/parsing/v2/index.ts @@ -13,3 +13,4 @@ export { ListField } from "./listField"; export { ObjectField } from "./objectField"; export { RawText } from "./rawText"; export { SimpleField } from "./simpleField"; +export { Webhook } from "./webhook"; diff --git a/src/parsing/v2/inference.ts b/src/parsing/v2/inference.ts index dcd35c279..e8cbbabbd 100644 --- a/src/parsing/v2/inference.ts +++ b/src/parsing/v2/inference.ts @@ -29,4 +29,18 @@ export class Inference { this.id = serverResponse["id"]; } } + + toString(): string { + return ( + "Inference\n" + + "#########\n" + + `:model: ${this.model.id}\n` + + ":file:\n" + + ` :name: ${this.file.name}\n` + + ` :alias: ${this.file.alias}\n\n` + + "Result\n" + + "======\n" + + `${this.result}\n` + ); + } } diff --git a/src/parsing/v2/inferenceFields.ts b/src/parsing/v2/inferenceFields.ts index 49d8c91d0..b7bdb6e18 100644 --- a/src/parsing/v2/inferenceFields.ts +++ b/src/parsing/v2/inferenceFields.ts @@ -1,13 +1,55 @@ import { StringDict } from "../common"; -import { ListField } from "./listField"; -import { ObjectField } from "./objectField"; -import { SimpleField } from "./simpleField"; +import type { ListField } from "./listField"; +import type { ObjectField } from "./objectField"; +import type { SimpleField } from "./simpleField"; + export class InferenceFields extends Map { protected _indentLevel: number; constructor(serverResponse: StringDict, indentLevel = 0) { - super(serverResponse.entries()); + super(Object.entries(serverResponse)); this._indentLevel = indentLevel; } + + toString(): string { + let outStr = ""; + + for (const [fieldKey, fieldValue] of this.entries()) { + const indent = " ".repeat(this._indentLevel); + + if ( + fieldValue && + fieldValue.constructor.name === "ListField" + ) { + const listField = fieldValue as ListField; + let valueStr = indent; + + if (Array.isArray(listField.items) && listField.items.length > 0) { + valueStr = indent + listField.toString(); + } + + outStr += `${indent}:${fieldKey}: ${valueStr}`; + } else if ( + fieldValue && + fieldValue.constructor.name === "ObjectField" + ) { + const objectField = fieldValue as ObjectField; + outStr += `${indent}:${fieldKey}: ${objectField.fields.toString()}`; + } else if ( + fieldValue && + fieldValue.constructor.name === "SimpleField" + ) { + const simpleField = fieldValue as SimpleField; + const simpleStr = simpleField.toString(); + outStr += `${indent}:${fieldKey}: ${simpleStr}`; + } else { + outStr += `${indent}:${fieldKey}: `; + } + + outStr += "\n"; + } + return outStr.trimEnd(); + } + } diff --git a/src/parsing/v2/inferenceResult.ts b/src/parsing/v2/inferenceResult.ts index fee8a3759..f7aec08bf 100644 --- a/src/parsing/v2/inferenceResult.ts +++ b/src/parsing/v2/inferenceResult.ts @@ -21,7 +21,7 @@ export class InferenceResult { } toString(): string { - let outStr: string = `:fields: ${this.fields}`; + let outStr: string = `:fields:\n${this.fields}`; if (this.options) { outStr += `\n:options: ${this.options}`; } diff --git a/src/parsing/v2/job.ts b/src/parsing/v2/job.ts index 907b9ca8b..c054f3966 100644 --- a/src/parsing/v2/job.ts +++ b/src/parsing/v2/job.ts @@ -1,5 +1,7 @@ import { StringDict } from "../common"; import { ErrorResponse } from "./errorResponse"; +import { Webhook } from "./webhook"; +import { parseDate } from "../common/dateParser"; /** * Job information for a V2 polling attempt. @@ -33,7 +35,7 @@ export class Job { /** * Status of the job. */ - public status: string; + public status?: string; /** * URL to poll for the job status. */ @@ -45,15 +47,21 @@ export class Job { /** * ID of webhooks associated with the job. */ - public webhooks: Array; + public webhooks: Array; constructor(serverResponse: StringDict) { this.id = serverResponse["id"]; - this.status = serverResponse["status"]; - if ("error" in serverResponse) { + if (serverResponse["status"] !== undefined) { + this.status = serverResponse["status"]; + } + if ( + serverResponse["error"] !== undefined && + serverResponse["error"] !== null && + Object.keys(serverResponse["error"]).length > 0 + ) { this.error = new ErrorResponse(serverResponse["error"]); } - this.createdAt = serverResponse["created_at"] ? this.parseDate(serverResponse["created_at"]) : null; + this.createdAt = parseDate(serverResponse["created_at"]); this.modelId = serverResponse["model_id"]; this.pollingUrl = serverResponse["polling_url"]; this.filename = serverResponse["filename"]; @@ -61,9 +69,4 @@ export class Job { this.alias = serverResponse["alias"]; this.webhooks = serverResponse["webhooks"]; } - - private parseDate(dateString: string | null): Date | null { - if (!dateString) return null; - return new Date(dateString); - } } diff --git a/src/parsing/v2/listField.ts b/src/parsing/v2/listField.ts index d42cb2905..6020d5acd 100644 --- a/src/parsing/v2/listField.ts +++ b/src/parsing/v2/listField.ts @@ -3,6 +3,7 @@ import { StringDict } from "../common"; import { BaseField } from "./baseField"; import { ObjectField } from "./objectField"; import { SimpleField } from "./simpleField"; +import { createField } from "./fieldFactory"; export class ListField extends BaseField { /** @@ -19,7 +20,7 @@ export class ListField extends BaseField { ); } this.items = serverResponse["items"].map((item) => { - return BaseField.createField(item, indentLevel + 1); + return createField(item, indentLevel + 1); }); } diff --git a/src/parsing/v2/webhook.ts b/src/parsing/v2/webhook.ts new file mode 100644 index 000000000..2d872deae --- /dev/null +++ b/src/parsing/v2/webhook.ts @@ -0,0 +1,33 @@ +import { ErrorResponse } from "./errorResponse"; +import { StringDict, parseDate } from "../common"; + +/** + * Webhook information. + */ +export class Webhook { + /** + * Webhook ID. + */ + public id: string; + /** + * Created at date. + */ + public createdAt: Date | null; + /** + * Status of the webhook. + */ + public status: string; + /** + * Error response, if any. + */ + public error?: ErrorResponse; + + constructor(serverResponse: StringDict) { + this.id = serverResponse["id"]; + this.createdAt = parseDate(serverResponse["created_at"]); + this.status = serverResponse["status"]; + if (serverResponse["error"] !== undefined) { + this.error = new ErrorResponse(serverResponse["error"]); + } + } +} diff --git a/tests/clientV2.spec.ts b/tests/clientV2.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_code_samples.sh b/tests/test_code_samples.sh index cfb8430a7..a3529d7ae 100755 --- a/tests/test_code_samples.sh +++ b/tests/test_code_samples.sh @@ -5,6 +5,8 @@ OUTPUT_FILE='../test_code_samples/_test.js' ACCOUNT=$1 ENDPOINT=$2 API_KEY=$3 +API_KEY_V2=$4 +MODEL_ID=$5 rm -fr ../test_code_samples mkdir ../test_code_samples @@ -15,6 +17,14 @@ cd - for f in $(find docs/code_samples -maxdepth 1 -name "*.txt" -not -name "workflow_*.txt" | sort -h) do + if echo "${f}" | grep -q "default_v2.txt"; then + if [ -z "${API_KEY_V2}" ] || [ -z "${MODEL_ID}" ]; then + echo "Skipping ${f} (API_KEY_V2 or MODEL_ID not supplied)" + echo + continue + fi + fi + echo "###############################################" echo "${f}" echo "###############################################" @@ -23,6 +33,14 @@ do sed "s/my-api-key/$API_KEY/" "${f}" > $OUTPUT_FILE sed -i "s/\/path\/to\/the\/file.ext/..\/mindee-api-nodejs\/tests\/data\/file_types\/pdf\/blank_1.pdf/" $OUTPUT_FILE + if echo "${f}" | grep -q "default_v2.txt" + then + sed -i "s/MY_API_KEY/$API_KEY_V2/" $OUTPUT_FILE + sed -i "s/MY_MODEL_ID/$MODEL_ID/" $OUTPUT_FILE + else + sed -i "s/my-api-key/$API_KEY/" $OUTPUT_FILE + fi + if echo "$f" | grep -q "custom_v1.txt" then sed -i "s/my-account/$ACCOUNT/g" $OUTPUT_FILE From 10659d28357a4589b158efb97690b35ab0b20bfa Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:50:59 +0200 Subject: [PATCH 06/24] fix unit test --- src/errors/mindeeError.ts | 8 +- src/http/apiSettingsV2.ts | 15 ++-- src/http/mindeeApiV2.ts | 7 +- tests/clientV2.spec.ts | 162 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 12 deletions(-) diff --git a/src/errors/mindeeError.ts b/src/errors/mindeeError.ts index 7328c1a52..1ed773c71 100644 --- a/src/errors/mindeeError.ts +++ b/src/errors/mindeeError.ts @@ -41,11 +41,11 @@ export class MindeeApiV2Error extends MindeeError { } export class MindeeHttpErrorV2 extends MindeeError { - public code: number; + public status: number; public detail: string; - constructor(code: number, detail: string) { - super(`HTTP ${code} - ${detail}`); - this.code = code; + constructor(status: number, detail: string) { + super(`HTTP ${status} - ${detail}`); + this.status = status; this.detail = detail; this.name = "MindeeHttpErrorV2"; } diff --git a/src/http/apiSettingsV2.ts b/src/http/apiSettingsV2.ts index c1f1003b2..49cf7d4c3 100644 --- a/src/http/apiSettingsV2.ts +++ b/src/http/apiSettingsV2.ts @@ -1,9 +1,10 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { logger } from "../logger"; import { BaseSettings, MindeeApiConstructorProps } from "./baseSettings"; +import { MindeeApiV2Error } from "../errors/mindeeError"; -export const API_KEY_ENVVAR_NAME: string = "MINDEE_V2_API_KEY"; -export const API_HOST_ENVVAR_NAME: string = "MINDEE_V2_API_HOST"; +export const API_V2_KEY_ENVVAR_NAME: string = "MINDEE_V2_API_KEY"; +export const API_V2_HOST_ENVVAR_NAME: string = "MINDEE_V2_API_HOST"; const DEFAULT_MINDEE_API_HOST: string = "api-v2.mindee.net"; export class ApiSettingsV2 extends BaseSettings { @@ -20,9 +21,9 @@ export class ApiSettingsV2 extends BaseSettings { this.apiKey = apiKey; } if (!this.apiKey || this.apiKey.length === 0) { - throw new Error( + throw new MindeeApiV2Error( "Your API V2 key could not be set, check your Client Configuration\n." - + `You can set this using the ${API_KEY_ENVVAR_NAME} environment variable.` + + `You can set this using the ${API_V2_KEY_ENVVAR_NAME} environment variable.` ); } this.baseHeaders = { @@ -33,10 +34,10 @@ export class ApiSettingsV2 extends BaseSettings { protected apiKeyFromEnv(): string { - const envVarValue = process.env[API_KEY_ENVVAR_NAME]; + const envVarValue = process.env[API_V2_KEY_ENVVAR_NAME]; if (envVarValue) { logger.debug( - `Set API key from environment: ${API_KEY_ENVVAR_NAME}` + `Set API key from environment: ${API_V2_KEY_ENVVAR_NAME}` ); return envVarValue; } @@ -44,7 +45,7 @@ export class ApiSettingsV2 extends BaseSettings { } protected hostnameFromEnv(): string { - const envVarValue = process.env[API_HOST_ENVVAR_NAME]; + const envVarValue = process.env[API_V2_HOST_ENVVAR_NAME]; if (envVarValue) { logger.debug(`Set the API hostname to ${envVarValue}`); return envVarValue; diff --git a/src/http/mindeeApiV2.ts b/src/http/mindeeApiV2.ts index 64a62d03e..fdc28368a 100644 --- a/src/http/mindeeApiV2.ts +++ b/src/http/mindeeApiV2.ts @@ -67,8 +67,13 @@ export class MindeeApiV2 { #processResponse (result: EndpointResponse, responseType: new (data: { [key: string]: any; }) => T): T { if (result.messageObj?.statusCode && (result.messageObj?.statusCode > 399 || result.messageObj?.statusCode < 200)) { + if (result.data?.status !== null) { + throw new MindeeHttpErrorV2( + result.data?.status, result.data?.detail ?? "Unknown error." + ); + } throw new MindeeHttpErrorV2( - result.messageObj?.statusCode ?? -1, result.messageObj?.statusMessage ?? "Unknown error." + result.messageObj?.statusCode ?? -1, result.data?.statusMessage ?? "Unknown error." ); } try { diff --git a/tests/clientV2.spec.ts b/tests/clientV2.spec.ts index e69de29bb..ccceccffa 100644 --- a/tests/clientV2.spec.ts +++ b/tests/clientV2.spec.ts @@ -0,0 +1,162 @@ +/* eslint-disable @typescript-eslint/naming-convention,camelcase */ +import { expect } from "chai"; +import nock from "nock"; +import path from "node:path"; +import { ClientV2 } from "../src"; +import { MindeeHttpErrorV2 } from "../src/errors/mindeeError"; +import { + LocalResponse, + PathInput, +} from "../src/input"; +import { JobResponse } from "../src/parsing/v2"; +import { Job } from "../src/parsing/v2"; +import assert from "node:assert/strict"; +/** + * Injects a minimal set of environment variables so that the SDK behaves + * as if it had been configured by the user. + */ +function dummyEnvvars(): void { + process.env.MINDEE_V2_API_KEY = "dummy"; + process.env.MINDEE_V2_API_HOST = "dummy-url"; +} + +function setNockInterceptors(): void { + nock("https://dummy-url") + .persist() + .post(/.*/) + .reply(400, { status: 400, detail: "forced failure from test" }); + + nock("https://dummy-url") + .persist() + .get(/.*/) + .reply(200, { + job: { + id: "12345678-1234-1234-1234-123456789ABC", + model_id: "87654321-4321-4321-4321-CBA987654321", + filename: "default_sample.jpg", + alias: "dummy-alias.jpg", + created_at: "2025-07-03T14:27:58.974451", + status: "Processing", + polling_url: + "https://api-v2.mindee.net/v2/jobs/12345678-1234-1234-1234-123456789ABC", + result_url: null, + webhooks: [], + error: null, + }, + }); +} + +before(() => { + dummyEnvvars(); + setNockInterceptors(); +}); + +after(() => { + delete process.env.MINDEE_API_KEY; + delete process.env.MINDEE_V2_BASE_URL; + nock.cleanAll(); +}); +const resourcesPath = path.join(__dirname, "./data"); +const fileTypesDir = path.join(resourcesPath, "file_types"); +const v2DataDir = path.join(resourcesPath, "v2"); + +describe("ClientV2", () => { + describe("Client configured via environment variables", () => { + let client: ClientV2; + + beforeEach(() => { + client = new ClientV2({ apiKey: "dummy" }); + }); + + it("inherits base URL, token & headers from the env / options", () => { + const api = (client as any).mindeeApi; + expect(api.settings.apiKey).to.equal("dummy"); + expect(api.settings.hostname).to.equal("dummy-url"); + expect(api.settings.baseHeaders.Authorization).to.equal("dummy"); + expect(api.settings.baseHeaders["User-Agent"]).to.match(/mindee/i); + }); + + it("enqueue(path) rejects with MindeeHttpErrorV2 on 4xx", async () => { + const filePath = path.join(fileTypesDir, "receipt.jpg"); + const inputDoc = client.docFromPath(filePath); + + await assert.rejects( + client.enqueue(inputDoc, { modelId: "dummy-model" }), + MindeeHttpErrorV2 + ); + }); + + it("enqueueAndParse(path) rejects with MindeeHttpErrorV2 on 4xx", async () => { + const filePath = path.join(fileTypesDir, "receipt.jpg"); + const inputDoc = client.docFromPath(filePath); + await assert.rejects( + client.enqueueAndParse( + inputDoc, + { modelId: "dummy-model" } + ), + MindeeHttpErrorV2 + ); + }); + + it("loadInference(LocalResponse) works on stored JSON fixtures", async () => { + const jsonPath = path.join( + v2DataDir, + "products", + "financial_document", + "complete.json" + ); + + const localResp = new LocalResponse(jsonPath); + await localResp.init(); + const prediction = client.loadInference(localResp); + + expect(prediction.inference.model.id).to.equal( + "12345678-1234-1234-1234-123456789abc" + ); + }); + + it("bubble-up HTTP errors with details", async () => { + const input = new PathInput({ + inputPath: path.join( + v2DataDir, + "products", + "financial_document", + "default_sample.jpg" + ), + }); + + try { + await client.enqueue(input, { modelId: "dummy-model" }); + expect.fail("enqueue() should have thrown"); + } catch (err) { + expect(err).to.be.instanceOf(MindeeHttpErrorV2); + const httpErr = err as MindeeHttpErrorV2; + expect(httpErr.status).to.equal(400); + expect(httpErr.detail).to.equal("forced failure from test"); + } + }); + + it("parseQueued(jobId) returns a fully-formed JobResponse", async () => { + const resp = await client.parseQueued( + "12345678-1234-1234-1234-123456789ABC" + ); + + expect(resp).to.be.instanceOf(JobResponse); + expect((resp as JobResponse).job).to.be.instanceOf(Job); + + const job = (resp as JobResponse).job; + expect(job.id).to.equal("12345678-1234-1234-1234-123456789ABC"); + expect(job.modelId).to.equal("87654321-4321-4321-4321-CBA987654321"); + expect(job.filename).to.equal("default_sample.jpg"); + expect(job.alias).to.equal("dummy-alias.jpg"); + expect(job.createdAt?.toISOString()).to.equal("2025-07-03T12:27:58.974Z"); + expect(job.status).to.equal("Processing"); + expect(job.pollingUrl).to.equal( + "https://api-v2.mindee.net/v2/jobs/12345678-1234-1234-1234-123456789ABC" + ); + expect(job.resultUrl).to.be.null; + expect(job.webhooks).to.have.length(0); + expect(job.error).to.be.undefined; + }); + }); +}); From 30096bc9971419bfbf40f85125e3ee1dfb53bcbf Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:15:25 +0200 Subject: [PATCH 07/24] update client syntaxes to match other SDKs --- src/clientV2.ts | 56 +++++++++++++++++++++++++++-------------- src/http/mindeeApiV2.ts | 56 ++++++++++++++++------------------------- tests/clientV2.spec.ts | 14 ++++------- 3 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/clientV2.ts b/src/clientV2.ts index 789b1448f..ec67bf06b 100644 --- a/src/clientV2.ts +++ b/src/clientV2.ts @@ -8,9 +8,10 @@ import { LOG_LEVELS, logger } from "./logger"; import { setTimeout } from "node:timers/promises"; import { MindeeError } from "./errors"; -import { InferenceResponse, JobResponse } from "./parsing/v2"; +import { ErrorResponse, InferenceResponse, JobResponse } from "./parsing/v2"; import { MindeeApiV2 } from "./http/mindeeApiV2"; import { BaseClient } from "./baseClient"; +import { MindeeHttpErrorV2 } from "./errors/mindeeError"; /** * Asynchronous polling parameters. @@ -106,18 +107,32 @@ export class ClientV2 extends BaseClient { * @category Asynchronous * @returns a `Promise` containing the job (queue) corresponding to a document. */ - async enqueue( + async enqueueInference( inputSource: LocalInputSource, params: InferencePredictOptions ): Promise { if (inputSource === undefined) { throw new Error("The 'enqueue' function requires an input document."); } - return await this.mindeeApi.predictAsyncReqPost(inputSource, params); + return await this.mindeeApi.reqPostInferenceEnqueue(inputSource, params); } /** - * Polls a queue and returns its status as well as the inference results if the parsing is done. + * Retrieves an inference. + * + * @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`. + * @category Asynchronous + * @returns a `Promise` containing a `Job`, which also contains a `Document` if the + * parsing is complete. + */ + async getInference(inferenceId: string): Promise { + return await this.mindeeApi.reqGetInference(inferenceId); + } + + /** + * Get the status of an inference that was previously enqueued. + * 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`. @@ -125,8 +140,8 @@ export class ClientV2 extends BaseClient { * @returns a `Promise` containing a `Job`, which also contains a `Document` if the * parsing is complete. */ - async parseQueued(jobId: string): Promise { - return await this.mindeeApi.getQueuedDocument(jobId); + async getJob(jobId: string): Promise { + return await this.mindeeApi.reqGetJob(jobId); } /** @@ -195,12 +210,12 @@ export class ClientV2 extends BaseClient { * @category Synchronous * @returns a `Promise` containing parsing results. */ - async enqueueAndParse( + async enqueueAndGetInference( inputDoc: LocalInputSource, params: InferencePredictOptions ): Promise { const validatedAsyncParams = this.#setAsyncParams(params.pollingOptions); - const enqueueResponse: JobResponse = await this.enqueue(inputDoc, params); + const enqueueResponse: JobResponse = await this.enqueueInference(inputDoc, 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."); @@ -212,28 +227,31 @@ export class ClientV2 extends BaseClient { await setTimeout(validatedAsyncParams.initialDelaySec * 1000, undefined, validatedAsyncParams.initialTimerOptions); let retryCounter: number = 1; - let pollResults: JobResponse | InferenceResponse; - pollResults = await this.parseQueued(queueId); + let pollResults: JobResponse = await this.getJob(queueId); while (retryCounter < validatedAsyncParams.maxRetries) { - if (pollResults instanceof InferenceResponse) { + if (pollResults.job.status === "Failed") { break; } + if (pollResults.job.status === "Processed") { + return this.getInference(pollResults.job.id); + } logger.debug( `Polling server for parsing result with queueId: ${queueId}. Attempt n°${retryCounter}/${validatedAsyncParams.maxRetries}. Job status: ${pollResults.job.status}.` ); await setTimeout(validatedAsyncParams.delaySec * 1000, undefined, validatedAsyncParams.recurringTimerOptions); - pollResults = await this.parseQueued(queueId); + pollResults = await this.getJob(queueId); retryCounter++; } - if (pollResults instanceof JobResponse) { - throw Error( - "Asynchronous parsing request timed out after " + - validatedAsyncParams.delaySec * retryCounter + - " seconds" - ); + const error: ErrorResponse | undefined = pollResults.job.error; + if (error) { + throw new MindeeHttpErrorV2(error.status, error.detail); } - return pollResults; + throw Error( + "Asynchronous parsing request timed out after " + + validatedAsyncParams.delaySec * retryCounter + + " seconds" + ); } } diff --git a/src/http/mindeeApiV2.ts b/src/http/mindeeApiV2.ts index fdc28368a..e1012aa83 100644 --- a/src/http/mindeeApiV2.ts +++ b/src/http/mindeeApiV2.ts @@ -23,7 +23,7 @@ export class MindeeApiV2 { * @throws Error if the server's response contains one. * @returns a `Promise` containing a job response. */ - async predictAsyncReqPost(inputDoc: LocalInputSource, params: InferencePredictOptions): Promise { + async reqPostInferenceEnqueue(inputDoc: LocalInputSource, params: InferencePredictOptions): Promise { await inputDoc.init(); if (params.pageOptions !== undefined) { await BaseEndpoint.cutDocPages(inputDoc, params.pageOptions); @@ -42,6 +42,18 @@ export class MindeeApiV2 { } + /** + * Requests the job of a queued document from the API. + * Throws an error if the server's response contains one. + * @param inferenceId The document's ID in the queue. + * @category Asynchronous + * @returns a `Promise` containing either the parsed result, or information on the queue. + */ + async reqGetInference(inferenceId: string): Promise { + const queueResponse: EndpointResponse = await this.#inferenceResultReqGet(inferenceId, "inferences"); + return this.#processResponse(queueResponse, InferenceResponse); + } + /** * Requests the results of a queued document from the API. * Throws an error if the server's response contains one. @@ -49,18 +61,8 @@ export class MindeeApiV2 { * @category Asynchronous * @returns a `Promise` containing either the parsed result, or information on the queue. */ - async getQueuedDocument(jobId: string): Promise { - const queueResponse: EndpointResponse = await this.#documentQueueReqGet(jobId); - const queueStatusCode = queueResponse.messageObj.statusCode; - if ( - queueStatusCode === 302 && - queueResponse.messageObj.headers.location !== undefined - ) { - const docId = queueResponse.messageObj.headers.location.split("/").pop(); - if (docId !== undefined) { - return this.#processResponse(await this.#documentGetReq(docId), InferenceResponse); - } - } + async reqGetJob(jobId: string): Promise { + const queueResponse: EndpointResponse = await this.#inferenceResultReqGet(jobId, "jobs"); return this.#processResponse(queueResponse, JobResponse); } @@ -121,36 +123,20 @@ export class MindeeApiV2 { }); } - /** - * Make a request to GET a document. - * @param queueId - */ - #documentGetReq(queueId: string): Promise { - return new Promise((resolve, reject) => { - const options = { - method: "GET", - headers: this.settings.baseHeaders, - hostname: this.settings.hostname, - path: `/v2/inferences/${queueId}`, - }; - const req = BaseEndpoint.readResponse(options, resolve, reject); - // potential ECONNRESET if we don't end the request. - req.end(); - }); - } - - /** * Make a request to GET the status of a document in the queue. - * @param queueId + * @param queueId ID of either the job or the inference. + * @param slug "jobs" or "inferences"... + * @category Asynchronous + * @returns a `Promise` containing either the parsed result, or information on the queue. */ - #documentQueueReqGet(queueId: string): Promise { + #inferenceResultReqGet(queueId: string, slug: string): Promise { return new Promise((resolve, reject) => { const options = { method: "GET", headers: this.settings.baseHeaders, hostname: this.settings.hostname, - path: `/v2/jobs/${queueId}`, + path: `/v2/${slug}/${queueId}`, }; const req = BaseEndpoint.readResponse(options, resolve, reject); // potential ECONNRESET if we don't end the request. diff --git a/tests/clientV2.spec.ts b/tests/clientV2.spec.ts index ccceccffa..7c40506e1 100644 --- a/tests/clientV2.spec.ts +++ b/tests/clientV2.spec.ts @@ -8,8 +8,6 @@ import { LocalResponse, PathInput, } from "../src/input"; -import { JobResponse } from "../src/parsing/v2"; -import { Job } from "../src/parsing/v2"; import assert from "node:assert/strict"; /** * Injects a minimal set of environment variables so that the SDK behaves @@ -81,7 +79,7 @@ describe("ClientV2", () => { const inputDoc = client.docFromPath(filePath); await assert.rejects( - client.enqueue(inputDoc, { modelId: "dummy-model" }), + client.enqueueInference(inputDoc, { modelId: "dummy-model" }), MindeeHttpErrorV2 ); }); @@ -90,7 +88,7 @@ describe("ClientV2", () => { const filePath = path.join(fileTypesDir, "receipt.jpg"); const inputDoc = client.docFromPath(filePath); await assert.rejects( - client.enqueueAndParse( + client.enqueueAndGetInference( inputDoc, { modelId: "dummy-model" } ), @@ -126,7 +124,7 @@ describe("ClientV2", () => { }); try { - await client.enqueue(input, { modelId: "dummy-model" }); + await client.enqueueInference(input, { modelId: "dummy-model" }); expect.fail("enqueue() should have thrown"); } catch (err) { expect(err).to.be.instanceOf(MindeeHttpErrorV2); @@ -137,14 +135,12 @@ describe("ClientV2", () => { }); it("parseQueued(jobId) returns a fully-formed JobResponse", async () => { - const resp = await client.parseQueued( + const resp = await client.getJob( "12345678-1234-1234-1234-123456789ABC" ); - expect(resp).to.be.instanceOf(JobResponse); - expect((resp as JobResponse).job).to.be.instanceOf(Job); - const job = (resp as JobResponse).job; + const job = resp.job; expect(job.id).to.equal("12345678-1234-1234-1234-123456789ABC"); expect(job.modelId).to.equal("87654321-4321-4321-4321-CBA987654321"); expect(job.filename).to.equal("default_sample.jpg"); From 2c4eb03b74019bd89af3e4b9a023d40136bb68d1 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:40:25 +0200 Subject: [PATCH 08/24] uniformize syntaxes to fit other SDK changes --- README.md | 21 ------------------- src/clientV2.ts | 9 +++----- src/http/mindeeApiV2.ts | 10 ++++----- src/index.ts | 2 +- src/parsing/v2/{ => field}/baseField.ts | 0 src/parsing/v2/{ => field}/fieldFactory.ts | 4 ++-- src/parsing/v2/field/index.ts | 4 ++++ src/parsing/v2/{ => field}/inferenceFields.ts | 2 +- src/parsing/v2/{ => field}/listField.ts | 4 ++-- src/parsing/v2/{ => field}/objectField.ts | 2 +- src/parsing/v2/{ => field}/simpleField.ts | 2 +- src/parsing/v2/index.ts | 12 ++++------- src/parsing/v2/inference.ts | 12 +++++------ src/parsing/v2/inferenceResult.ts | 8 +++---- ...nferenceFile.ts => inferenceResultFile.ts} | 2 +- ...erenceModel.ts => inferenceResultModel.ts} | 2 +- ...ceOptions.ts => inferenceResultOptions.ts} | 2 +- src/parsing/v2/job.ts | 4 ++-- .../v2/{webhook.ts => jobResponseWebhook.ts} | 6 +++--- 19 files changed, 42 insertions(+), 66 deletions(-) rename src/parsing/v2/{ => field}/baseField.ts (100%) rename src/parsing/v2/{ => field}/fieldFactory.ts (88%) create mode 100644 src/parsing/v2/field/index.ts rename src/parsing/v2/{ => field}/inferenceFields.ts (97%) rename src/parsing/v2/{ => field}/listField.ts (89%) rename src/parsing/v2/{ => field}/objectField.ts (95%) rename src/parsing/v2/{ => field}/simpleField.ts (91%) rename src/parsing/v2/{inferenceFile.ts => inferenceResultFile.ts} (89%) rename src/parsing/v2/{inferenceModel.ts => inferenceResultModel.ts} (83%) rename src/parsing/v2/{inferenceOptions.ts => inferenceResultOptions.ts} (90%) rename src/parsing/v2/{webhook.ts => jobResponseWebhook.ts} (87%) diff --git a/README.md b/README.md index 8fbd34cfe..7cd9bb433 100644 --- a/README.md +++ b/README.md @@ -154,27 +154,6 @@ const apiResponse = mindeeClient.parse( }); ``` -## Further Reading -Complete details on the working of the library are available in the following guides: - -* [Node.js Getting Started](https://developers.mindee.com/docs/nodejs-getting-started) -* [Node.js Generated API](https://developers.mindee.com/docs/nodejs-generated-ocr) -* [Node.js Custom OCR (Deprecated)](https://developers.mindee.com/docs/nodejs-api-builder) -* [Node.js Invoice OCR](https://developers.mindee.com/docs/nodejs-invoice-ocr) -* [Node.js International Id OCR](https://developers.mindee.com/docs/nodejs-international-id-ocr) -* [Node.js Receipt OCR](https://developers.mindee.com/docs/nodejs-receipt-ocr) -* [Node.js Resume OCR](https://developers.mindee.com/docs/nodejs-resume-ocr) -* [Node.js Financial Document OCR](https://developers.mindee.com/docs/nodejs-financial-document-ocr) -* [Node.js Passport OCR](https://developers.mindee.com/docs/nodejs-passport-ocr) -* [Node.js FR Bank Account Detail OCR](https://developers.mindee.com/docs/nodejs-fr-bank-account-details-ocr) -* [Node.js FR Health Card OCR](https://developers.mindee.com/docs/nodejs-fr-health-card-ocr) -* [Node.js FR ID Card OCR](https://developers.mindee.com/docs/nodejs-fr-carte-nationale-didentite-ocr) -* [Node.js US Bank Check OCR](https://developers.mindee.com/docs/nodejs-us-bank-check-ocr) -* [Node.js Barcode Reader API](https://developers.mindee.com/docs/nodejs-barcode-reader-ocr) -* [Node.js Cropper API](https://developers.mindee.com/docs/nodejs-cropper-ocr) -* [Node.js Invoice Splitter API](https://developers.mindee.com/docs/nodejs-invoice-splitter-ocr) -* [Node.js Multi Receipts Detector API](https://developers.mindee.com/docs/nodejs-multi-receipts-detector-ocr) - You can also take a look at the **[Reference Documentation](https://mindee.github.io/mindee-api-nodejs/)**. ## License diff --git a/src/clientV2.ts b/src/clientV2.ts index ec67bf06b..3339bafba 100644 --- a/src/clientV2.ts +++ b/src/clientV2.ts @@ -1,7 +1,6 @@ import { LocalInputSource, LocalResponse, - PageOptions, } from "./input"; import { errorHandler } from "./errors/handler"; import { LOG_LEVELS, logger } from "./logger"; @@ -44,7 +43,7 @@ interface PollingOptions { } } -export interface InferencePredictOptions { +export interface InferenceParams { /** ID of the model. **Required**. */ modelId: string; /** Enable Retrieval-Augmented Generation (RAG). */ @@ -53,8 +52,6 @@ export interface InferencePredictOptions { alias?: string; /** IDs of the webhooks that should receive the API response. */ webhookIds?: string[]; - /** Page-level inference options. */ - pageOptions?: PageOptions; /** Polling options. */ pollingOptions?: OptionalPollingOptions; /** Set to `false` if the file must remain open after parsing. */ @@ -109,7 +106,7 @@ export class ClientV2 extends BaseClient { */ async enqueueInference( inputSource: LocalInputSource, - params: InferencePredictOptions + params: InferenceParams ): Promise { if (inputSource === undefined) { throw new Error("The 'enqueue' function requires an input document."); @@ -212,7 +209,7 @@ export class ClientV2 extends BaseClient { */ async enqueueAndGetInference( inputDoc: LocalInputSource, - params: InferencePredictOptions + params: InferenceParams ): Promise { const validatedAsyncParams = this.#setAsyncParams(params.pollingOptions); const enqueueResponse: JobResponse = await this.enqueueInference(inputDoc, params); diff --git a/src/http/mindeeApiV2.ts b/src/http/mindeeApiV2.ts index e1012aa83..6c748fb1a 100644 --- a/src/http/mindeeApiV2.ts +++ b/src/http/mindeeApiV2.ts @@ -1,5 +1,5 @@ import { ApiSettingsV2 } from "./apiSettingsV2"; -import { InferencePredictOptions } from "../clientV2"; +import { InferenceParams } from "../clientV2"; import { InferenceResponse, JobResponse } from "../parsing/v2"; import FormData from "form-data"; import { RequestOptions } from "https"; @@ -18,12 +18,12 @@ export class MindeeApiV2 { /** * Sends a file to the inference queue. * @param inputDoc Local file loaded as an input. - * @param params {InferencePredictOptions} parameters relating to the enqueueing options. + * @param params {InferenceParams} parameters relating to the enqueueing options. * @category V2 * @throws Error if the server's response contains one. * @returns a `Promise` containing a job response. */ - async reqPostInferenceEnqueue(inputDoc: LocalInputSource, params: InferencePredictOptions): Promise { + async reqPostInferenceEnqueue(inputDoc: LocalInputSource, params: InferenceParams): Promise { await inputDoc.init(); if (params.pageOptions !== undefined) { await BaseEndpoint.cutDocPages(inputDoc, params.pageOptions); @@ -91,9 +91,9 @@ export class MindeeApiV2 { * Sends a document to the inference queue. * * @param inputDoc Local file loaded as an input. - * @param params {InferencePredictOptions} parameters relating to the enqueueing options. + * @param params {InferenceParams} parameters relating to the enqueueing options. */ - #documentEnqueuePost(inputDoc: LocalInputSource, params: InferencePredictOptions): Promise { + #documentEnqueuePost(inputDoc: LocalInputSource, params: InferenceParams): Promise { const form = new FormData(); form.append("model_id", params.modelId); diff --git a/src/index.ts b/src/index.ts index 298297762..6e76e9b3e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ export * as product from "./product"; export { Client, PredictOptions } from "./client"; -export { ClientV2, InferencePredictOptions } from "./clientV2"; +export { ClientV2, InferenceParams } from "./clientV2"; export { AsyncPredictResponse, PredictResponse, diff --git a/src/parsing/v2/baseField.ts b/src/parsing/v2/field/baseField.ts similarity index 100% rename from src/parsing/v2/baseField.ts rename to src/parsing/v2/field/baseField.ts diff --git a/src/parsing/v2/fieldFactory.ts b/src/parsing/v2/field/fieldFactory.ts similarity index 88% rename from src/parsing/v2/fieldFactory.ts rename to src/parsing/v2/field/fieldFactory.ts index 9f0a8e660..afc2e46fa 100644 --- a/src/parsing/v2/fieldFactory.ts +++ b/src/parsing/v2/field/fieldFactory.ts @@ -1,8 +1,8 @@ /** * Factory helper. */ -import { StringDict } from "../common"; -import { MindeeApiV2Error } from "../../errors/mindeeError"; +import { StringDict } from "../../common"; +import { MindeeApiV2Error } from "../../../errors/mindeeError"; import { ListField } from "./listField"; import { ObjectField } from "./objectField"; import { SimpleField } from "./simpleField"; diff --git a/src/parsing/v2/field/index.ts b/src/parsing/v2/field/index.ts new file mode 100644 index 000000000..2e67e4308 --- /dev/null +++ b/src/parsing/v2/field/index.ts @@ -0,0 +1,4 @@ +export { InferenceFields } from "./inferenceFields"; +export { ListField } from "./listField"; +export { ObjectField } from "./objectField"; +export { SimpleField } from "./simpleField"; diff --git a/src/parsing/v2/inferenceFields.ts b/src/parsing/v2/field/inferenceFields.ts similarity index 97% rename from src/parsing/v2/inferenceFields.ts rename to src/parsing/v2/field/inferenceFields.ts index b7bdb6e18..a64bb62b9 100644 --- a/src/parsing/v2/inferenceFields.ts +++ b/src/parsing/v2/field/inferenceFields.ts @@ -1,4 +1,4 @@ -import { StringDict } from "../common"; +import { StringDict } from "../../common"; import type { ListField } from "./listField"; import type { ObjectField } from "./objectField"; import type { SimpleField } from "./simpleField"; diff --git a/src/parsing/v2/listField.ts b/src/parsing/v2/field/listField.ts similarity index 89% rename from src/parsing/v2/listField.ts rename to src/parsing/v2/field/listField.ts index 6020d5acd..4bb727450 100644 --- a/src/parsing/v2/listField.ts +++ b/src/parsing/v2/field/listField.ts @@ -1,5 +1,5 @@ -import { MindeeApiV2Error } from "../../errors/mindeeError"; -import { StringDict } from "../common"; +import { MindeeApiV2Error } from "../../../errors/mindeeError"; +import { StringDict } from "../../common"; import { BaseField } from "./baseField"; import { ObjectField } from "./objectField"; import { SimpleField } from "./simpleField"; diff --git a/src/parsing/v2/objectField.ts b/src/parsing/v2/field/objectField.ts similarity index 95% rename from src/parsing/v2/objectField.ts rename to src/parsing/v2/field/objectField.ts index 069fc9563..d65a1628a 100644 --- a/src/parsing/v2/objectField.ts +++ b/src/parsing/v2/field/objectField.ts @@ -1,7 +1,7 @@ import { BaseField } from "./baseField"; import { ListField } from "./listField"; import { InferenceFields } from "./inferenceFields"; -import { StringDict } from "../common"; +import { StringDict } from "../../common"; export class ObjectField extends BaseField { readonly fields: InferenceFields; diff --git a/src/parsing/v2/simpleField.ts b/src/parsing/v2/field/simpleField.ts similarity index 91% rename from src/parsing/v2/simpleField.ts rename to src/parsing/v2/field/simpleField.ts index 02237182e..d8d74a3ea 100644 --- a/src/parsing/v2/simpleField.ts +++ b/src/parsing/v2/field/simpleField.ts @@ -1,5 +1,5 @@ import { BaseField } from "./baseField"; -import { StringDict } from "../common"; +import { StringDict } from "../../common"; export class SimpleField extends BaseField { readonly value: string | number | boolean | null; diff --git a/src/parsing/v2/index.ts b/src/parsing/v2/index.ts index 178473057..3ecaf89e7 100644 --- a/src/parsing/v2/index.ts +++ b/src/parsing/v2/index.ts @@ -1,16 +1,12 @@ export { CommonResponse } from "./commonResponse"; export { ErrorResponse } from "./errorResponse"; export { Inference } from "./inference"; -export { InferenceFields } from "./inferenceFields"; -export { InferenceFile } from "./inferenceFile"; -export { InferenceModel } from "./inferenceModel"; -export { InferenceOptions } from "./inferenceOptions"; +export { InferenceResultFile } from "./inferenceResultFile"; +export { InferenceResultModel } from "./inferenceResultModel"; +export { InferenceResultOptions } from "./inferenceResultOptions"; export { InferenceResponse } from "./inferenceResponse"; export { InferenceResult } from "./inferenceResult"; export { Job } from "./job"; export { JobResponse } from "./jobResponse"; -export { ListField } from "./listField"; -export { ObjectField } from "./objectField"; export { RawText } from "./rawText"; -export { SimpleField } from "./simpleField"; -export { Webhook } from "./webhook"; +export { JobResponseWebhook } from "./jobResponseWebhook"; diff --git a/src/parsing/v2/inference.ts b/src/parsing/v2/inference.ts index e8cbbabbd..85a2bb0d0 100644 --- a/src/parsing/v2/inference.ts +++ b/src/parsing/v2/inference.ts @@ -1,17 +1,17 @@ import { StringDict } from "../common"; -import { InferenceModel } from "./inferenceModel"; +import { InferenceResultModel } from "./inferenceResultModel"; import { InferenceResult } from "./inferenceResult"; -import { InferenceFile } from "./inferenceFile"; +import { InferenceResultFile } from "./inferenceResultFile"; export class Inference { /** * Model info for the inference. */ - public model: InferenceModel; + public model: InferenceResultModel; /** * File info for the inference. */ - public file: InferenceFile; + public file: InferenceResultFile; /** * Result of the inference. */ @@ -22,8 +22,8 @@ export class Inference { public id?: string; constructor(serverResponse: StringDict) { - this.model = new InferenceModel(serverResponse["model"]); - this.file = new InferenceFile(serverResponse["file"]); + this.model = new InferenceResultModel(serverResponse["model"]); + this.file = new InferenceResultFile(serverResponse["file"]); this.result = new InferenceResult(serverResponse["result"]); if ("id" in serverResponse) { this.id = serverResponse["id"]; diff --git a/src/parsing/v2/inferenceResult.ts b/src/parsing/v2/inferenceResult.ts index f7aec08bf..339347b95 100644 --- a/src/parsing/v2/inferenceResult.ts +++ b/src/parsing/v2/inferenceResult.ts @@ -1,5 +1,5 @@ -import { InferenceFields } from "./inferenceFields"; -import { InferenceOptions } from "./inferenceOptions"; +import { InferenceFields } from "./field/inferenceFields"; +import { InferenceResultOptions } from "./inferenceResultOptions"; import { StringDict } from "../common"; export class InferenceResult { @@ -11,12 +11,12 @@ export class InferenceResult { /** * Potential options retrieved alongside the inference. */ - public options?: InferenceOptions; + public options?: InferenceResultOptions; constructor(serverResponse: StringDict) { this.fields = new InferenceFields(serverResponse["fields"]); if (serverResponse["options"]) { - this.options = new InferenceOptions(serverResponse["options"]); + this.options = new InferenceResultOptions(serverResponse["options"]); } } diff --git a/src/parsing/v2/inferenceFile.ts b/src/parsing/v2/inferenceResultFile.ts similarity index 89% rename from src/parsing/v2/inferenceFile.ts rename to src/parsing/v2/inferenceResultFile.ts index 3ee8be472..37df0bbd1 100644 --- a/src/parsing/v2/inferenceFile.ts +++ b/src/parsing/v2/inferenceResultFile.ts @@ -1,6 +1,6 @@ import { StringDict } from "../common"; -export class InferenceFile { +export class InferenceResultFile { /** * Name of the file. */ diff --git a/src/parsing/v2/inferenceModel.ts b/src/parsing/v2/inferenceResultModel.ts similarity index 83% rename from src/parsing/v2/inferenceModel.ts rename to src/parsing/v2/inferenceResultModel.ts index a3a0f59ca..d6d86bcf8 100644 --- a/src/parsing/v2/inferenceModel.ts +++ b/src/parsing/v2/inferenceResultModel.ts @@ -1,6 +1,6 @@ import { StringDict } from "../common"; -export class InferenceModel { +export class InferenceResultModel { /** * ID of the model. */ diff --git a/src/parsing/v2/inferenceOptions.ts b/src/parsing/v2/inferenceResultOptions.ts similarity index 90% rename from src/parsing/v2/inferenceOptions.ts rename to src/parsing/v2/inferenceResultOptions.ts index 0fabb6b1c..09d66c86d 100644 --- a/src/parsing/v2/inferenceOptions.ts +++ b/src/parsing/v2/inferenceResultOptions.ts @@ -1,7 +1,7 @@ import { StringDict } from "../common"; import { RawText } from "./rawText"; -export class InferenceOptions { +export class InferenceResultOptions { /** * List of texts found per page. */ diff --git a/src/parsing/v2/job.ts b/src/parsing/v2/job.ts index c054f3966..48f5c31a1 100644 --- a/src/parsing/v2/job.ts +++ b/src/parsing/v2/job.ts @@ -1,6 +1,6 @@ import { StringDict } from "../common"; import { ErrorResponse } from "./errorResponse"; -import { Webhook } from "./webhook"; +import { JobResponseWebhook } from "./jobResponseWebhook"; import { parseDate } from "../common/dateParser"; /** @@ -47,7 +47,7 @@ export class Job { /** * ID of webhooks associated with the job. */ - public webhooks: Array; + public webhooks: Array; constructor(serverResponse: StringDict) { this.id = serverResponse["id"]; diff --git a/src/parsing/v2/webhook.ts b/src/parsing/v2/jobResponseWebhook.ts similarity index 87% rename from src/parsing/v2/webhook.ts rename to src/parsing/v2/jobResponseWebhook.ts index 2d872deae..c495e5b5e 100644 --- a/src/parsing/v2/webhook.ts +++ b/src/parsing/v2/jobResponseWebhook.ts @@ -2,11 +2,11 @@ import { ErrorResponse } from "./errorResponse"; import { StringDict, parseDate } from "../common"; /** - * Webhook information. + * JobResponseWebhook information. */ -export class Webhook { +export class JobResponseWebhook { /** - * Webhook ID. + * JobResponseWebhook ID. */ public id: string; /** From d5c393330d3ba5be95099b35b0afbee978984a0f Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:48:04 +0200 Subject: [PATCH 09/24] update CI trigger --- tests/{fields => parsing/common}/amount.spec.ts | 2 +- tests/{fields => parsing/common}/classification.spec.ts | 2 +- tests/{fields => parsing/common}/date.spec.ts | 2 +- tests/{fields => parsing/common}/field.spec.ts | 2 +- tests/{fields => parsing/common}/locale.spec.ts | 2 +- tests/{fields => parsing/common}/orientation.spec.ts | 2 +- tests/{fields => parsing/common}/paymentDetails.spec.ts | 2 +- tests/{fields => parsing/common}/position.spec.ts | 2 +- tests/{fields => parsing/common}/tax.spec.ts | 2 +- tests/{fields => parsing/common}/text.spec.ts | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) rename tests/{fields => parsing/common}/amount.spec.ts (93%) rename tests/{fields => parsing/common}/classification.spec.ts (89%) rename tests/{fields => parsing/common}/date.spec.ts (94%) rename tests/{fields => parsing/common}/field.spec.ts (97%) rename tests/{fields => parsing/common}/locale.spec.ts (95%) rename tests/{fields => parsing/common}/orientation.spec.ts (92%) rename tests/{fields => parsing/common}/paymentDetails.spec.ts (97%) rename tests/{fields => parsing/common}/position.spec.ts (95%) rename tests/{fields => parsing/common}/tax.spec.ts (96%) rename tests/{fields => parsing/common}/text.spec.ts (94%) diff --git a/tests/fields/amount.spec.ts b/tests/parsing/common/amount.spec.ts similarity index 93% rename from tests/fields/amount.spec.ts rename to tests/parsing/common/amount.spec.ts index 308e16453..25d66c79d 100644 --- a/tests/fields/amount.spec.ts +++ b/tests/parsing/common/amount.spec.ts @@ -1,4 +1,4 @@ -import { AmountField } from "../../src/parsing/standard"; +import { AmountField } from "../../../src/parsing/standard"; import { expect } from "chai"; describe("Test AmountField field", () => { diff --git a/tests/fields/classification.spec.ts b/tests/parsing/common/classification.spec.ts similarity index 89% rename from tests/fields/classification.spec.ts rename to tests/parsing/common/classification.spec.ts index 64eb586a8..d56db0e90 100644 --- a/tests/fields/classification.spec.ts +++ b/tests/parsing/common/classification.spec.ts @@ -1,4 +1,4 @@ -import { ClassificationField } from "../../src/parsing/standard"; +import { ClassificationField } from "../../../src/parsing/standard"; import { expect } from "chai"; describe("Test Classification field", () => { diff --git a/tests/fields/date.spec.ts b/tests/parsing/common/date.spec.ts similarity index 94% rename from tests/fields/date.spec.ts rename to tests/parsing/common/date.spec.ts index 0eb7edc29..433a7ff58 100644 --- a/tests/fields/date.spec.ts +++ b/tests/parsing/common/date.spec.ts @@ -1,4 +1,4 @@ -import { DateField } from "../../src/parsing/standard"; +import { DateField } from "../../../src/parsing/standard"; import { expect } from "chai"; describe("Test Date field", () => { diff --git a/tests/fields/field.spec.ts b/tests/parsing/common/field.spec.ts similarity index 97% rename from tests/fields/field.spec.ts rename to tests/parsing/common/field.spec.ts index 7fc7ea8b0..642617750 100644 --- a/tests/fields/field.spec.ts +++ b/tests/parsing/common/field.spec.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import { Field } from "../../src/parsing/standard"; +import { Field } from "../../../src/parsing/standard"; describe("Test different inits of Field", () => { it("Should create a Field", () => { diff --git a/tests/fields/locale.spec.ts b/tests/parsing/common/locale.spec.ts similarity index 95% rename from tests/fields/locale.spec.ts rename to tests/parsing/common/locale.spec.ts index 0dac29f0a..0f1c66890 100644 --- a/tests/fields/locale.spec.ts +++ b/tests/parsing/common/locale.spec.ts @@ -1,4 +1,4 @@ -import { LocaleField } from "../../src/parsing/standard"; +import { LocaleField } from "../../../src/parsing/standard"; import { expect } from "chai"; describe("Test LocaleField field", () => { diff --git a/tests/fields/orientation.spec.ts b/tests/parsing/common/orientation.spec.ts similarity index 92% rename from tests/fields/orientation.spec.ts rename to tests/parsing/common/orientation.spec.ts index 412b8c280..2ebee99c2 100644 --- a/tests/fields/orientation.spec.ts +++ b/tests/parsing/common/orientation.spec.ts @@ -1,4 +1,4 @@ -import { OrientationField } from "../../src/parsing/common"; +import { OrientationField } from "../../../src/parsing/common"; import { expect } from "chai"; describe("Test Orientation field", () => { diff --git a/tests/fields/paymentDetails.spec.ts b/tests/parsing/common/paymentDetails.spec.ts similarity index 97% rename from tests/fields/paymentDetails.spec.ts rename to tests/parsing/common/paymentDetails.spec.ts index 9273df981..ff5601dad 100644 --- a/tests/fields/paymentDetails.spec.ts +++ b/tests/parsing/common/paymentDetails.spec.ts @@ -1,4 +1,4 @@ -import { PaymentDetailsField } from "../../src/parsing/standard"; +import { PaymentDetailsField } from "../../../src/parsing/standard"; import { expect } from "chai"; describe("Test PaymentDetailsField field", () => { diff --git a/tests/fields/position.spec.ts b/tests/parsing/common/position.spec.ts similarity index 95% rename from tests/fields/position.spec.ts rename to tests/parsing/common/position.spec.ts index 9129982c6..ff00813eb 100644 --- a/tests/fields/position.spec.ts +++ b/tests/parsing/common/position.spec.ts @@ -1,4 +1,4 @@ -import { PositionField } from "../../src/parsing/standard"; +import { PositionField } from "../../../src/parsing/standard"; import { expect } from "chai"; describe("Test Position field", () => { diff --git a/tests/fields/tax.spec.ts b/tests/parsing/common/tax.spec.ts similarity index 96% rename from tests/fields/tax.spec.ts rename to tests/parsing/common/tax.spec.ts index 4795358e1..b0156faec 100644 --- a/tests/fields/tax.spec.ts +++ b/tests/parsing/common/tax.spec.ts @@ -1,4 +1,4 @@ -import { TaxField } from "../../src/parsing/standard"; +import { TaxField } from "../../../src/parsing/standard"; import { expect } from "chai"; describe("Test Tax field", () => { diff --git a/tests/fields/text.spec.ts b/tests/parsing/common/text.spec.ts similarity index 94% rename from tests/fields/text.spec.ts rename to tests/parsing/common/text.spec.ts index 168d8a48f..1c05b3477 100644 --- a/tests/fields/text.spec.ts +++ b/tests/parsing/common/text.spec.ts @@ -1,4 +1,4 @@ -import { StringField } from "../../src/parsing/standard"; +import { StringField } from "../../../src/parsing/standard"; import { expect } from "chai"; describe("Test String field", () => { From cea6d341814e03ec47a1255f15b977c481e47189 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:48:40 +0200 Subject: [PATCH 10/24] fix CI trigger typo --- .github/workflows/pull-request.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index a2dada2a6..6527968fb 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -3,18 +3,22 @@ name: Pull Request on: pull_request: +permissions: + contents: read + pull-requests: read + jobs: static_analysis: - uses: mindee/mindee-api-nodejs/.github/workflows/_static-analysis.yml@main + uses: ./.github/workflows/_static-analysis.yml test_units: - uses: mindee/mindee-api-nodejs/.github/workflows/_test-units.yml@main + uses: ./.github/workflows/_test-units.yml needs: static_analysis secrets: inherit test_integrations: - uses: mindee/mindee-api-nodejs/.github/workflows/_test-integrations.yml@main + uses: ./.github/workflows/_test-integrations.yml needs: test_units secrets: inherit test_code_samples: - uses: mindee/mindee-api-nodejs/.github/workflows/_test-code-samples.yml@main + uses: ./.github/workflows/_test-code-samples.yml needs: test_units secrets: inherit From c71261cfbd1c7c21b1348604497f9598f0bb62ac Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:20:53 +0200 Subject: [PATCH 11/24] fix tests + fix inferenceFields instanciation --- src/http/mindeeApiV2.ts | 3 -- src/parsing/v2/field/inferenceFields.ts | 5 +- .../{common => standard}/amount.spec.ts | 0 .../classification.spec.ts | 0 .../parsing/{common => standard}/date.spec.ts | 0 .../{common => standard}/field.spec.ts | 0 .../{common => standard}/locale.spec.ts | 0 .../{common => standard}/orientation.spec.ts | 0 .../paymentDetails.spec.ts | 0 .../{common => standard}/position.spec.ts | 0 .../parsing/{common => standard}/tax.spec.ts | 0 .../parsing/{common => standard}/text.spec.ts | 0 tests/parsing/v2/inference.spec.ts | 47 +++++++++++++++++++ 13 files changed, 51 insertions(+), 4 deletions(-) rename tests/parsing/{common => standard}/amount.spec.ts (100%) rename tests/parsing/{common => standard}/classification.spec.ts (100%) rename tests/parsing/{common => standard}/date.spec.ts (100%) rename tests/parsing/{common => standard}/field.spec.ts (100%) rename tests/parsing/{common => standard}/locale.spec.ts (100%) rename tests/parsing/{common => standard}/orientation.spec.ts (100%) rename tests/parsing/{common => standard}/paymentDetails.spec.ts (100%) rename tests/parsing/{common => standard}/position.spec.ts (100%) rename tests/parsing/{common => standard}/tax.spec.ts (100%) rename tests/parsing/{common => standard}/text.spec.ts (100%) create mode 100644 tests/parsing/v2/inference.spec.ts diff --git a/src/http/mindeeApiV2.ts b/src/http/mindeeApiV2.ts index 6c748fb1a..88a25c89c 100644 --- a/src/http/mindeeApiV2.ts +++ b/src/http/mindeeApiV2.ts @@ -25,9 +25,6 @@ export class MindeeApiV2 { */ async reqPostInferenceEnqueue(inputDoc: LocalInputSource, params: InferenceParams): Promise { await inputDoc.init(); - if (params.pageOptions !== undefined) { - await BaseEndpoint.cutDocPages(inputDoc, params.pageOptions); - } if (params.modelId === undefined || params.modelId === null || params.modelId === "") { throw new Error("Model ID must be provided"); } diff --git a/src/parsing/v2/field/inferenceFields.ts b/src/parsing/v2/field/inferenceFields.ts index a64bb62b9..500269038 100644 --- a/src/parsing/v2/field/inferenceFields.ts +++ b/src/parsing/v2/field/inferenceFields.ts @@ -2,13 +2,16 @@ import { StringDict } from "../../common"; import type { ListField } from "./listField"; import type { ObjectField } from "./objectField"; import type { SimpleField } from "./simpleField"; +import { createField } from "./fieldFactory"; export class InferenceFields extends Map { protected _indentLevel: number; constructor(serverResponse: StringDict, indentLevel = 0) { - super(Object.entries(serverResponse)); + super(Object.entries(serverResponse).map( ([key, value]) => { + return [key, createField(value, 1)]; + })); this._indentLevel = indentLevel; } diff --git a/tests/parsing/common/amount.spec.ts b/tests/parsing/standard/amount.spec.ts similarity index 100% rename from tests/parsing/common/amount.spec.ts rename to tests/parsing/standard/amount.spec.ts diff --git a/tests/parsing/common/classification.spec.ts b/tests/parsing/standard/classification.spec.ts similarity index 100% rename from tests/parsing/common/classification.spec.ts rename to tests/parsing/standard/classification.spec.ts diff --git a/tests/parsing/common/date.spec.ts b/tests/parsing/standard/date.spec.ts similarity index 100% rename from tests/parsing/common/date.spec.ts rename to tests/parsing/standard/date.spec.ts diff --git a/tests/parsing/common/field.spec.ts b/tests/parsing/standard/field.spec.ts similarity index 100% rename from tests/parsing/common/field.spec.ts rename to tests/parsing/standard/field.spec.ts diff --git a/tests/parsing/common/locale.spec.ts b/tests/parsing/standard/locale.spec.ts similarity index 100% rename from tests/parsing/common/locale.spec.ts rename to tests/parsing/standard/locale.spec.ts diff --git a/tests/parsing/common/orientation.spec.ts b/tests/parsing/standard/orientation.spec.ts similarity index 100% rename from tests/parsing/common/orientation.spec.ts rename to tests/parsing/standard/orientation.spec.ts diff --git a/tests/parsing/common/paymentDetails.spec.ts b/tests/parsing/standard/paymentDetails.spec.ts similarity index 100% rename from tests/parsing/common/paymentDetails.spec.ts rename to tests/parsing/standard/paymentDetails.spec.ts diff --git a/tests/parsing/common/position.spec.ts b/tests/parsing/standard/position.spec.ts similarity index 100% rename from tests/parsing/common/position.spec.ts rename to tests/parsing/standard/position.spec.ts diff --git a/tests/parsing/common/tax.spec.ts b/tests/parsing/standard/tax.spec.ts similarity index 100% rename from tests/parsing/common/tax.spec.ts rename to tests/parsing/standard/tax.spec.ts diff --git a/tests/parsing/common/text.spec.ts b/tests/parsing/standard/text.spec.ts similarity index 100% rename from tests/parsing/common/text.spec.ts rename to tests/parsing/standard/text.spec.ts diff --git a/tests/parsing/v2/inference.spec.ts b/tests/parsing/v2/inference.spec.ts new file mode 100644 index 000000000..2730199ad --- /dev/null +++ b/tests/parsing/v2/inference.spec.ts @@ -0,0 +1,47 @@ +import { expect } from "chai"; +import path from "node:path"; +import { InferenceResponse } from "../../../src/parsing/v2"; +import { ClientV2, LocalResponse } from "../../../src"; +import { ListField, ObjectField, SimpleField } from "../../../src/parsing/v2/field"; + +const resourcesPath = path.join(__dirname, "..", "..", "data"); +const v2DataDir = path.join(resourcesPath, "v2"); +const blankPath = path.join(v2DataDir, "products", "financial_document", "blank.json"); +async function loadV2Inference(resourcePath: string): Promise { + const dummyClient = new ClientV2({ apiKey: "dummy-key" }); + const localResponse = new LocalResponse(resourcePath); + await localResponse.init(); + return dummyClient.loadInference(localResponse); +} + +describe("inference", async () => { + it("should load a blank prediction with valid property", async () => { + const response = await loadV2Inference(blankPath); + const fields = response.inference.result.fields; + + expect(fields).to.be.not.empty; + expect(fields.size).to.be.eq(21); + expect(fields.has("taxes")).to.be.true; + expect(fields.get("taxes")).to.not.be.null; + expect(fields.get("taxes")).to.be.an.instanceof(ListField); + + expect(fields.get("supplier_address")).to.not.be.null; + expect(fields.get("supplier_address")).to.be.an.instanceof(ObjectField); + for (const entry of fields.values()) { + if (entry instanceof SimpleField && entry.value === null) { + continue; + } + switch(entry.constructor.name) { + case "SimpleField": + expect((entry as SimpleField).value).to.not.be.null; + break; + case "ObjectField": + expect((entry as ObjectField).fields).to.not.be.null; + break; + case "ListField": + expect((entry as ListField).items).to.not.be.null; + break; + } + } + }); +}); From 62b3db13d8ad390f2e134955c77aefa8a99a276a Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:22:54 +0200 Subject: [PATCH 12/24] update mocha targetting to work for windows files --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f07d6ccb3..ad02d6c8d 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "build": "tsc --build", "build-for-dist": "tsc --build && cp LICENSE README.md CHANGELOG.md ./dist", "clean": "rm -rf ./dist ./docs/_build", - "test": "mocha 'tests/**/*.spec.ts' --config .mocharc.json", - "test-integration": "mocha 'tests/**/*.integration.ts'", + "test": "mocha \"tests/**/*.spec.ts\" --config .mocharc.json", + "test-integration": "mocha \"tests/**/*.integration.ts\"", "lint": "eslint './src/**/*.ts' --report-unused-disable-directives && echo 'Your .ts files look good.'", "lint-fix": "eslint './src/**/*.ts' --fix", "docs": "typedoc --out docs/_build ./src/index.ts", From ee578e1bee423e60c81cca5bc72fec1d7575cfef Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:25:12 +0200 Subject: [PATCH 13/24] fix invalid date --- src/parsing/common/dateParser.ts | 3 +++ tests/clientV2.spec.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/parsing/common/dateParser.ts b/src/parsing/common/dateParser.ts index 6ee5b8432..37d762943 100644 --- a/src/parsing/common/dateParser.ts +++ b/src/parsing/common/dateParser.ts @@ -2,5 +2,8 @@ export function parseDate(dateString: string | null): Date | null { if (!dateString) { return null; } + if (!/Z$/.test(dateString) && !/[+-][0-9]{2}:[0-9]{2}$/.test(dateString)) { + dateString += "Z"; + } return new Date(dateString); } diff --git a/tests/clientV2.spec.ts b/tests/clientV2.spec.ts index 7c40506e1..eeb9c2a71 100644 --- a/tests/clientV2.spec.ts +++ b/tests/clientV2.spec.ts @@ -145,7 +145,7 @@ describe("ClientV2", () => { expect(job.modelId).to.equal("87654321-4321-4321-4321-CBA987654321"); expect(job.filename).to.equal("default_sample.jpg"); expect(job.alias).to.equal("dummy-alias.jpg"); - expect(job.createdAt?.toISOString()).to.equal("2025-07-03T12:27:58.974Z"); + expect(job.createdAt?.toISOString()).to.equal("2025-07-03T14:27:58.974Z"); expect(job.status).to.equal("Processing"); expect(job.pollingUrl).to.equal( "https://api-v2.mindee.net/v2/jobs/12345678-1234-1234-1234-123456789ABC" From 86cd8d0f7942774477b4a8243620fbc78c862016 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:45:14 +0200 Subject: [PATCH 14/24] fix test --- tests/clientV2.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/clientV2.spec.ts b/tests/clientV2.spec.ts index eeb9c2a71..15c7a5260 100644 --- a/tests/clientV2.spec.ts +++ b/tests/clientV2.spec.ts @@ -50,7 +50,7 @@ before(() => { }); after(() => { - delete process.env.MINDEE_API_KEY; + delete process.env.MINDEE_V2_API_KEY; delete process.env.MINDEE_V2_BASE_URL; nock.cleanAll(); }); From 0f11a4f86ce6d4761d292713c516e849f641f441 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:19:27 +0200 Subject: [PATCH 15/24] fix display & add all unit tests --- src/clientV2.ts | 2 +- src/parsing/v2/field/baseField.ts | 16 +- src/parsing/v2/field/fieldConfidence.ts | 9 ++ src/parsing/v2/field/fieldLocation.ts | 23 +++ src/parsing/v2/field/index.ts | 2 + src/parsing/v2/field/inferenceFields.ts | 48 +++--- src/parsing/v2/field/listField.ts | 23 ++- src/parsing/v2/field/objectField.ts | 24 +-- src/parsing/v2/field/simpleField.ts | 6 +- src/parsing/v2/inference.ts | 12 +- src/parsing/v2/inferenceResult.ts | 16 +- src/parsing/v2/inferenceResultFile.ts | 8 + tests/parsing/v2/inference.spec.ts | 202 ++++++++++++++++++++---- 13 files changed, 295 insertions(+), 96 deletions(-) create mode 100644 src/parsing/v2/field/fieldConfidence.ts create mode 100644 src/parsing/v2/field/fieldLocation.ts diff --git a/src/clientV2.ts b/src/clientV2.ts index 3339bafba..78e4769f5 100644 --- a/src/clientV2.ts +++ b/src/clientV2.ts @@ -154,7 +154,7 @@ export class ClientV2 extends BaseClient { try { return new InferenceResponse(localResponse.asDict()); } catch { - throw new MindeeError("No prediction found in local response."); + throw new MindeeError("No inference found in local response."); } } diff --git a/src/parsing/v2/field/baseField.ts b/src/parsing/v2/field/baseField.ts index 35e091aa8..cf1b55015 100644 --- a/src/parsing/v2/field/baseField.ts +++ b/src/parsing/v2/field/baseField.ts @@ -1,7 +1,21 @@ +import { FieldConfidence } from "./fieldConfidence"; +import { FieldLocation } from "./fieldLocation"; +import { StringDict } from "../../common"; + export abstract class BaseField { protected _indentLevel: number; + public locations: Array | undefined; + public confidence: FieldConfidence | undefined; - protected constructor(indentLevel = 0) { + protected constructor(rawResponse: StringDict, indentLevel = 0) { this._indentLevel = indentLevel; + if (rawResponse["locations"]) { + this.locations = rawResponse["locations"].map((location: StringDict | undefined) => { + return location ? new FieldLocation(location) : ""; + }); + } + if (rawResponse["confidence"] !== undefined) { + this.confidence = rawResponse["confidence"] as FieldConfidence; + } } } diff --git a/src/parsing/v2/field/fieldConfidence.ts b/src/parsing/v2/field/fieldConfidence.ts new file mode 100644 index 000000000..ca666f026 --- /dev/null +++ b/src/parsing/v2/field/fieldConfidence.ts @@ -0,0 +1,9 @@ +/** + * Confidence level of a field as returned by the V2 API. + */ +export enum FieldConfidence { + certain = "Certain", + high = "High", + medium = "Medium", + low = "Low", +} diff --git a/src/parsing/v2/field/fieldLocation.ts b/src/parsing/v2/field/fieldLocation.ts new file mode 100644 index 000000000..dbaf64332 --- /dev/null +++ b/src/parsing/v2/field/fieldLocation.ts @@ -0,0 +1,23 @@ +import { Polygon } from "../../../geometry"; +import { StringDict } from "../../common"; + +/** + * Location of a field. + */ +export class FieldLocation { + /** Free polygon made up of points (can be null when not provided). */ + readonly polygon: Polygon | null; + + /** Page ID. */ + readonly page: number | undefined; + + constructor(serverResponse: StringDict) { + this.polygon = new Polygon(serverResponse["polygon"]); + this.page = "number" in serverResponse && + typeof serverResponse["page"] === "number" ? serverResponse["page"] : undefined; + } + + toString(): string { + return this.polygon?.toString() ?? ""; + } +} diff --git a/src/parsing/v2/field/index.ts b/src/parsing/v2/field/index.ts index 2e67e4308..bfbd648fa 100644 --- a/src/parsing/v2/field/index.ts +++ b/src/parsing/v2/field/index.ts @@ -1,4 +1,6 @@ export { InferenceFields } from "./inferenceFields"; +export { FieldConfidence } from "./fieldConfidence"; +export { FieldLocation } from "./fieldLocation"; export { ListField } from "./listField"; export { ObjectField } from "./objectField"; export { SimpleField } from "./simpleField"; diff --git a/src/parsing/v2/field/inferenceFields.ts b/src/parsing/v2/field/inferenceFields.ts index 500269038..5f4d759d2 100644 --- a/src/parsing/v2/field/inferenceFields.ts +++ b/src/parsing/v2/field/inferenceFields.ts @@ -15,44 +15,32 @@ export class InferenceFields extends Map 0) { - valueStr = indent + listField.toString(); + line += listField.toString(); } - - outStr += `${indent}:${fieldKey}: ${valueStr}`; - } else if ( - fieldValue && - fieldValue.constructor.name === "ObjectField" - ) { - const objectField = fieldValue as ObjectField; - outStr += `${indent}:${fieldKey}: ${objectField.fields.toString()}`; - } else if ( - fieldValue && - fieldValue.constructor.name === "SimpleField" - ) { - const simpleField = fieldValue as SimpleField; - const simpleStr = simpleField.toString(); - outStr += `${indent}:${fieldKey}: ${simpleStr}`; - } else { - outStr += `${indent}:${fieldKey}: `; + } else if (fieldValue.constructor.name === "ObjectField") { + line += fieldValue.toString(); + } else if (fieldValue.constructor.name === "SimpleField") { + const val = (fieldValue as SimpleField).value; + line += val !== null && val !== undefined ? " " + val.toString() : ""; } - outStr += "\n"; + lines.push(line); } - return outStr.trimEnd(); - } + return lines.join("\n").trimEnd(); + } } diff --git a/src/parsing/v2/field/listField.ts b/src/parsing/v2/field/listField.ts index 4bb727450..c42e53b8e 100644 --- a/src/parsing/v2/field/listField.ts +++ b/src/parsing/v2/field/listField.ts @@ -12,7 +12,7 @@ export class ListField extends BaseField { public items: Array; constructor(serverResponse: StringDict, indentLevel = 0) { - super(indentLevel); + super(serverResponse, indentLevel); if (!Array.isArray(serverResponse["items"])) { throw new MindeeApiV2Error( @@ -25,12 +25,21 @@ export class ListField extends BaseField { } toString(): string { - if (this.items.length === 0) { - return ""; + if (!this.items || this.items.length === 0) { + return "\n"; } - const out = this.items - .map((item) => `* ${item.toString().slice(2)}`) - .join(""); - return "\n" + out; + + const parts: string[] = [""]; + for (const item of this.items) { + if (!item) continue; + + if (item instanceof ObjectField) { + parts.push(item.toStringFromList()); + } else { + parts.push(item.toString()); + } + } + return parts.join("\n * "); } + } diff --git a/src/parsing/v2/field/objectField.ts b/src/parsing/v2/field/objectField.ts index d65a1628a..f6779cfb6 100644 --- a/src/parsing/v2/field/objectField.ts +++ b/src/parsing/v2/field/objectField.ts @@ -1,5 +1,4 @@ import { BaseField } from "./baseField"; -import { ListField } from "./listField"; import { InferenceFields } from "./inferenceFields"; import { StringDict } from "../../common"; @@ -7,25 +6,18 @@ export class ObjectField extends BaseField { readonly fields: InferenceFields; constructor(serverResponse: StringDict, indentLevel = 0) { - super(indentLevel); + super(serverResponse, indentLevel); this.fields = new InferenceFields(serverResponse["fields"], this._indentLevel + 1); } + + toString(): string { - let out = ""; - for (const [key, value] of this.fields.entries()) { - if (value instanceof ListField) { - const needsValue = value.items.length > 0; - const valueStr = - needsValue && value.toString() - ? " ".repeat(this._indentLevel) + value.toString() - : ""; - out += `${" ".repeat(this._indentLevel)}:${key}: ${valueStr}`; - } else { - out += `${" ".repeat(this._indentLevel)}:${key}: ${value.toString()}`; - } - } - return out; + return "\n" + (this.fields ? this.fields.toString(1) : ""); + } + + toStringFromList(): string{ + return this.fields? this.fields.toString(2).substring(4) : ""; } } diff --git a/src/parsing/v2/field/simpleField.ts b/src/parsing/v2/field/simpleField.ts index d8d74a3ea..597c46763 100644 --- a/src/parsing/v2/field/simpleField.ts +++ b/src/parsing/v2/field/simpleField.ts @@ -5,14 +5,14 @@ export class SimpleField extends BaseField { readonly value: string | number | boolean | null; constructor(serverResponse: StringDict, indentLevel = 0) { - super(indentLevel); + super(serverResponse, indentLevel); this.value = serverResponse["value"] !== undefined ? (serverResponse["value"] as any) : null; } toString(): string { return this.value !== null && this.value !== undefined - ? `${this.value}\n` - : "\n"; + ? this.value.toString() + : ""; } } diff --git a/src/parsing/v2/inference.ts b/src/parsing/v2/inference.ts index 85a2bb0d0..01f9d8d08 100644 --- a/src/parsing/v2/inference.ts +++ b/src/parsing/v2/inference.ts @@ -34,13 +34,11 @@ export class Inference { return ( "Inference\n" + "#########\n" + - `:model: ${this.model.id}\n` + - ":file:\n" + - ` :name: ${this.file.name}\n` + - ` :alias: ${this.file.alias}\n\n` + - "Result\n" + - "======\n" + - `${this.result}\n` + "Model\n" + + "=====\n" + + `:ID: ${this.model.id}\n\n` + + this.file.toString() + "\n" + + this.result + "\n" ); } } diff --git a/src/parsing/v2/inferenceResult.ts b/src/parsing/v2/inferenceResult.ts index 339347b95..92900f536 100644 --- a/src/parsing/v2/inferenceResult.ts +++ b/src/parsing/v2/inferenceResult.ts @@ -21,10 +21,20 @@ export class InferenceResult { } toString(): string { - let outStr: string = `:fields:\n${this.fields}`; + const parts: string[] = [ + "Fields", + "======", + this.fields.toString(), + ]; + if (this.options) { - outStr += `\n:options: ${this.options}`; + parts.push( + "Options", + "=======", + this.options.toString() + ); } - return outStr; + + return parts.join("\n"); } } diff --git a/src/parsing/v2/inferenceResultFile.ts b/src/parsing/v2/inferenceResultFile.ts index 37df0bbd1..aac8887a2 100644 --- a/src/parsing/v2/inferenceResultFile.ts +++ b/src/parsing/v2/inferenceResultFile.ts @@ -14,4 +14,12 @@ export class InferenceResultFile { this.name = serverResponse["name"]; this.alias = serverResponse["alias"]; } + + toString () { + return( + "File\n" + + "====\n" + + `:Name: ${this.name}\n` + + `:Alias:${this.alias ? " " + this.alias : ""}\n`); + } } diff --git a/tests/parsing/v2/inference.spec.ts b/tests/parsing/v2/inference.spec.ts index 2730199ad..0df6841cb 100644 --- a/tests/parsing/v2/inference.spec.ts +++ b/tests/parsing/v2/inference.spec.ts @@ -3,10 +3,19 @@ import path from "node:path"; import { InferenceResponse } from "../../../src/parsing/v2"; import { ClientV2, LocalResponse } from "../../../src"; import { ListField, ObjectField, SimpleField } from "../../../src/parsing/v2/field"; +import { promises as fs } from "node:fs"; const resourcesPath = path.join(__dirname, "..", "..", "data"); const v2DataDir = path.join(resourcesPath, "v2"); -const blankPath = path.join(v2DataDir, "products", "financial_document", "blank.json"); +const findocPath = path.join(v2DataDir, "products", "financial_document"); +const inferencePath = path.join(v2DataDir, "inference"); +const deepNestedFieldPath = path.join(inferencePath, "deep_nested_fields.json"); +const standardFieldPath = path.join(inferencePath, "standard_field_types.json"); +const standardFieldRstPath = path.join(inferencePath, "standard_field_types.rst"); +const rawTextPath = path.join(inferencePath, "raw_texts.json"); +const blankPath = path.join(findocPath, "blank.json"); +const completePath = path.join(findocPath, "complete.json"); + async function loadV2Inference(resourcePath: string): Promise { const dummyClient = new ClientV2({ apiKey: "dummy-key" }); const localResponse = new LocalResponse(resourcePath); @@ -15,33 +24,170 @@ async function loadV2Inference(resourcePath: string): Promise } describe("inference", async () => { - it("should load a blank prediction with valid property", async () => { - const response = await loadV2Inference(blankPath); - const fields = response.inference.result.fields; - - expect(fields).to.be.not.empty; - expect(fields.size).to.be.eq(21); - expect(fields.has("taxes")).to.be.true; - expect(fields.get("taxes")).to.not.be.null; - expect(fields.get("taxes")).to.be.an.instanceof(ListField); - - expect(fields.get("supplier_address")).to.not.be.null; - expect(fields.get("supplier_address")).to.be.an.instanceof(ObjectField); - for (const entry of fields.values()) { - if (entry instanceof SimpleField && entry.value === null) { - continue; - } - switch(entry.constructor.name) { - case "SimpleField": - expect((entry as SimpleField).value).to.not.be.null; - break; - case "ObjectField": - expect((entry as ObjectField).fields).to.not.be.null; - break; - case "ListField": - expect((entry as ListField).items).to.not.be.null; - break; + describe("simple", async () => { + it("should load a blank inference with valid properties", async () => { + const response = await loadV2Inference(blankPath); + const fields = response.inference.result.fields; + + expect(fields).to.be.not.empty; + expect(fields.size).to.be.eq(21); + expect(fields.has("taxes")).to.be.true; + expect(fields.get("taxes")).to.not.be.null; + expect(fields.get("taxes")).to.be.an.instanceof(ListField); + + expect(fields.get("supplier_address")).to.not.be.null; + expect(fields.get("supplier_address")).to.be.an.instanceof(ObjectField); + for (const entry of fields.values()) { + if (entry instanceof SimpleField && entry.value === null) { + continue; + } + switch (entry.constructor.name) { + case "SimpleField": + expect((entry as SimpleField).value).to.not.be.null; + break; + case "ObjectField": + expect((entry as ObjectField).fields).to.not.be.null; + break; + case "ListField": + expect((entry as ListField).items).to.not.be.null; + break; + } } - } + }); + + it("should load a complete inference with valid properties", async () => { + const response = await loadV2Inference(completePath); + const inf = response.inference; + + expect(inf).to.not.be.undefined; + expect(inf.id).to.eq("12345678-1234-1234-1234-123456789abc"); + + const model = inf.model; + expect(model).to.not.be.undefined; + expect(model.id).to.eq("12345678-1234-1234-1234-123456789abc"); + + const file = inf.file; + expect(file).to.not.be.undefined; + expect(file.name).to.eq("complete.jpg"); + expect(file.alias ?? null).to.be.null; + + const fields = inf.result.fields; + expect(fields).to.be.not.empty; + expect(fields.size).to.be.eq(21); + + const dateField = fields.get("date") as SimpleField; + expect(dateField).to.not.be.undefined; + expect(dateField.value).to.eq("2019-11-02"); + + expect(fields.has("taxes")).to.be.true; + const taxes = fields.get("taxes"); + expect(taxes).to.be.instanceOf(ListField); + + const taxesList = taxes as ListField; + expect(taxesList.items).to.have.lengthOf(1); + expect(taxes?.toString()).to.be.a("string").and.not.be.empty; + + const firstTaxItem = taxesList.items[0]; + expect(firstTaxItem).to.be.instanceOf(ObjectField); + + const taxItemObj = firstTaxItem as ObjectField; + expect(taxItemObj.fields.size).to.eq(3); + + const baseField = taxItemObj.fields.get("base") as SimpleField; + expect(baseField.value).to.eq(31.5); + + expect(fields.has("supplier_address")).to.be.true; + const supplierAddress = fields.get("supplier_address"); + expect(supplierAddress).to.be.instanceOf(ObjectField); + + const supplierObj = supplierAddress as ObjectField; + const countryField = supplierObj.fields.get("country") as SimpleField; + expect(countryField.value).to.eq("USA"); + expect(countryField.toString()).to.eq("USA"); + expect(supplierAddress?.toString()).to.be.a("string").and.not.be.empty; + + const customerAddr = fields.get("customer_address") as ObjectField; + const cityField = customerAddr.fields.get("city") as SimpleField; + expect(cityField.value).to.eq("New York"); + + expect(inf.result.options).to.be.undefined; + }); + }); + + describe("nested", async () => { + it("should load a deep nested object", async () => { + const response = await loadV2Inference(deepNestedFieldPath); + const fields = response.inference.result.fields; + expect(fields.get("field_simple")).to.be.an.instanceof(SimpleField); + expect(fields.get("field_object")).to.be.an.instanceof(ObjectField); + + const fieldObject = fields.get("field_object") as ObjectField; + const lvl1 = fieldObject.fields; + + expect(lvl1.get("sub_object_list")).to.be.an.instanceof(ListField); + expect(lvl1.get("sub_object_object")).to.be.an.instanceof(ObjectField); + + const subObjectObject = lvl1.get("sub_object_object") as ObjectField; + const lvl2 = subObjectObject.fields; + + expect( + lvl2.get("sub_object_object_sub_object_list") + ).to.be.an.instanceof(ListField); + + const nestedList = lvl2.get( + "sub_object_object_sub_object_list" + ) as ListField; + expect(nestedList.items).to.not.be.empty; + expect(nestedList.items[0]).to.be.an.instanceof(ObjectField); + + const firstItemObj = nestedList.items[0] as ObjectField; + const deepSimple = firstItemObj.fields.get( + "sub_object_object_sub_object_list_simple" + ) as SimpleField; + + expect(deepSimple).to.not.be.undefined; + expect(deepSimple.value).to.eq("value_9"); + }); + }); + + describe("standard field types", async () => { + it("should recognize all field variants", async () => { + const response = await loadV2Inference(standardFieldPath); + const fields = response.inference.result.fields; + + expect(fields.get("field_simple")).to.be.instanceOf(SimpleField); + expect(fields.get("field_object")).to.be.instanceOf(ObjectField); + expect(fields.get("field_simple_list")).to.be.instanceOf(ListField); + expect(fields.get("field_object_list")).to.be.instanceOf(ListField); + }); + }); + + describe("options", async () => { + it("raw texts should be exposed", async () => { + const response = await loadV2Inference(rawTextPath); + const opts = response.inference.result.options; + + expect(opts).to.not.be.undefined; + const rawTexts = + (opts as any).rawTexts ?? (opts as any).getRawTexts?.() ?? []; + + expect(rawTexts).to.be.an("array").and.have.lengthOf(2); + + const first = rawTexts[0]; + expect(first.page).to.eq(0); + expect(first.content).to.eq( + "This is the raw text of the first page..." + ); + }); + }); + + describe("rst display", async () => { + it("to be properly exposed", async () => { + const response = await loadV2Inference(standardFieldPath); + const rstString = await fs.readFile(standardFieldRstPath, "utf8"); + + expect(response.inference).to.not.be.null; + expect(response.inference.toString()).to.be.eq(rstString); + }).timeout(10000); }); }); From 149490be7cf414e0e964993c551374c514bf3795 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:32:36 +0200 Subject: [PATCH 16/24] fix header setting for V1 --- src/http/apiSettings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/apiSettings.ts b/src/http/apiSettings.ts index 07e908f01..ecd9c506f 100644 --- a/src/http/apiSettings.ts +++ b/src/http/apiSettings.ts @@ -29,7 +29,7 @@ export class ApiSettings extends BaseSettings { } this.baseHeaders = { "User-Agent": this.getUserAgent(), - Authorization: `Token ${apiKey}`, + Authorization: `Token ${this.apiKey}`, }; } From 6a594d92d9f5f289717cad39c553fd8a7aa6031e Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Fri, 18 Jul 2025 17:44:13 +0200 Subject: [PATCH 17/24] done? --- docs/code_samples/default_v2.txt | 5 +- src/clientV2.ts | 19 ------ src/http/mindeeApiV2.ts | 5 +- src/input/localResponse.ts | 21 +++++++ tests/parsing/v2/inference.spec.ts | 3 +- tests/v2/clientV2.integration.ts | 92 ++++++++++++++++++++++++++++++ tests/{ => v2}/clientV2.spec.ts | 34 +++++------ 7 files changed, 137 insertions(+), 42 deletions(-) create mode 100644 tests/v2/clientV2.integration.ts rename tests/{ => v2}/clientV2.spec.ts (88%) diff --git a/docs/code_samples/default_v2.txt b/docs/code_samples/default_v2.txt index d1666811d..fc4dacc2c 100644 --- a/docs/code_samples/default_v2.txt +++ b/docs/code_samples/default_v2.txt @@ -4,13 +4,14 @@ const mindee = require("mindee"); // Init a new client const mindeeClient = new mindee.ClientV2({ apiKey: "MY_API_KEY" }); +const modelId = "MY_MODEL_ID"; // Load a file from disk const inputSource = mindeeClient.docFromPath("/path/to/the/file.ext"); -const apiResponse = mindeeClient.enqueueAndParse( +const apiResponse = mindeeClient.enqueueAndGetInference( inputSource, - { modelId: "MY_MODEL_ID" } + { modelId: modelId, rag: false } ); // Handle the response Promise diff --git a/src/clientV2.ts b/src/clientV2.ts index 78e4769f5..69b64050c 100644 --- a/src/clientV2.ts +++ b/src/clientV2.ts @@ -1,12 +1,10 @@ import { LocalInputSource, - LocalResponse, } from "./input"; import { errorHandler } from "./errors/handler"; import { LOG_LEVELS, logger } from "./logger"; import { setTimeout } from "node:timers/promises"; -import { MindeeError } from "./errors"; import { ErrorResponse, InferenceResponse, JobResponse } from "./parsing/v2"; import { MindeeApiV2 } from "./http/mindeeApiV2"; import { BaseClient } from "./baseClient"; @@ -141,23 +139,6 @@ export class ClientV2 extends BaseClient { return await this.mindeeApi.reqGetJob(jobId); } - /** - * Load an inference. - * - * @param localResponse Local response to load. - * @category V2 - * @returns A valid prediction - */ - loadInference( - localResponse: LocalResponse - ): InferenceResponse { - try { - return new InferenceResponse(localResponse.asDict()); - } catch { - throw new MindeeError("No inference found in local response."); - } - } - /** * Checks the values for asynchronous parsing. Returns their corrected value if they are undefined. * @param asyncParams parameters related to asynchronous parsing diff --git a/src/http/mindeeApiV2.ts b/src/http/mindeeApiV2.ts index 88a25c89c..b6183ef83 100644 --- a/src/http/mindeeApiV2.ts +++ b/src/http/mindeeApiV2.ts @@ -5,7 +5,7 @@ import FormData from "form-data"; import { RequestOptions } from "https"; import { BaseEndpoint, EndpointResponse } from "./baseEndpoint"; import { LocalInputSource } from "../input"; -import { MindeeHttpErrorV2 } from "../errors/mindeeError"; +import { MindeeApiV2Error, MindeeHttpErrorV2 } from "../errors/mindeeError"; import { logger } from "../logger"; export class MindeeApiV2 { @@ -79,8 +79,7 @@ export class MindeeApiV2 { return new responseType(result.data); } catch (e) { logger.error(`Raised '${e}' Couldn't deserialize response object:\n${JSON.stringify(result.data)}`); - throw e; - // throw new MindeeApiV2Error("Couldn't deserialize response object."); + throw new MindeeApiV2Error("Couldn't deserialize response object."); } } diff --git a/src/input/localResponse.ts b/src/input/localResponse.ts index cb97c4dff..8e0cad2e4 100644 --- a/src/input/localResponse.ts +++ b/src/input/localResponse.ts @@ -3,6 +3,7 @@ import * as fs from "node:fs/promises"; import { StringDict } from "../parsing/common"; import { MindeeError } from "../errors"; import { Buffer } from "buffer"; +import { CommonResponse } from "../parsing/v2"; /** * Local response loaded from a file. @@ -79,4 +80,24 @@ export class LocalResponse { return signature === this.getHmacSignature(secretKey); } + /** + * Deserialize the loaded local response into the requested CommonResponse`-derived class. + * + * Typically used when dealing with V2 webhook callbacks. + * + * @typeParam ResponseT - A class that extends `CommonResponse`. + * @param responseClass - The constructor of the class into which + * the payload should be deserialized. + * @returns An instance of `responseClass` populated with the file content. + * @throws MindeeError If the provided class cannot be instantiated. + */ + public deserializeResponse( + responseClass: new (serverResponse: StringDict) => ResponseT + ): ResponseT { + try { + return new responseClass(this.asDict()); + } catch { + throw new MindeeError("Invalid class specified for deserialization."); + } + } } diff --git a/tests/parsing/v2/inference.spec.ts b/tests/parsing/v2/inference.spec.ts index 0df6841cb..2276f79a7 100644 --- a/tests/parsing/v2/inference.spec.ts +++ b/tests/parsing/v2/inference.spec.ts @@ -17,10 +17,9 @@ const blankPath = path.join(findocPath, "blank.json"); const completePath = path.join(findocPath, "complete.json"); async function loadV2Inference(resourcePath: string): Promise { - const dummyClient = new ClientV2({ apiKey: "dummy-key" }); const localResponse = new LocalResponse(resourcePath); await localResponse.init(); - return dummyClient.loadInference(localResponse); + return localResponse.deserializeResponse(InferenceResponse); } describe("inference", async () => { diff --git a/tests/v2/clientV2.integration.ts b/tests/v2/clientV2.integration.ts new file mode 100644 index 000000000..aedc64cf7 --- /dev/null +++ b/tests/v2/clientV2.integration.ts @@ -0,0 +1,92 @@ +import { expect } from "chai"; +import path from "node:path"; + +import { ClientV2, InferenceParams } from "../../src"; +import { PathInput } from "../../src/input"; +import { SimpleField } from "../../src/parsing/v2/field"; +import { MindeeHttpErrorV2 } from "../../src/errors/mindeeError"; + +describe("MindeeClientV2 – integration tests (V2)", () => { + let client: ClientV2; + let modelId: string; + + const dataDir = path.join(__dirname, "..", "data"); + const emptyPdfPath = path.join( + dataDir, + "file_types", + "pdf", + "multipage_cut-2.pdf", + ); + const sampleImagePath = path.join( + dataDir, + "products", + "financial_document", + "default_sample.jpg", + ); + + beforeEach(async () => { + const apiKey = process.env["MINDEE_V2_API_KEY"] ?? ""; + modelId = process.env["MINDEE_V2_FINDOC_MODEL_ID"] ?? ""; + + if (apiKey.trim() === "" || modelId.trim() === "") { + throw new Error("No API key provided."); + } + + client = new ClientV2({ apiKey }); + }); + + it("Empty, multi-page PDF – enqueue & parse must succeed", async () => { + const source = new PathInput({ inputPath: emptyPdfPath }); + const params: InferenceParams = { modelId }; + + const response = await client.enqueueAndGetInference(source, params); + + expect(response).to.exist; + const inf = response.inference; + expect(inf).to.exist; + + expect(inf.file?.name).to.equal("multipage_cut-2.pdf"); + expect(inf.model?.id).to.equal(modelId); + + expect(inf.result).to.exist; + expect(inf.result.options).to.be.undefined; + }).timeout(60000); + + it("Filled, single-page image – enqueue & parse must succeed", async () => { + const source = new PathInput({ inputPath: sampleImagePath }); + const params: InferenceParams = { modelId, rag: false }; + + const response = await client.enqueueAndGetInference(source, params); + + const inf = response.inference; + expect(inf.file?.name).to.equal("default_sample.jpg"); + expect(inf.model?.id).to.equal(modelId); + + const supplierField = inf.result.fields.get("supplier_name") as SimpleField; + expect(supplierField).to.be.instanceOf(SimpleField); + expect(supplierField.value).to.equal("John Smith"); + }).timeout(60000); + + it("Invalid model ID – enqueue must raise 422", async () => { + const source = new PathInput({ inputPath: emptyPdfPath }); + const badParams: InferenceParams = { modelId: "INVALID MODEL ID" }; + + try { + await client.enqueueInference(source, badParams); + expect.fail("Expected the call to throw, but it succeeded."); + } catch (err) { + expect(err).to.be.instanceOf(MindeeHttpErrorV2); + expect((err as MindeeHttpErrorV2).status).to.equal(422); + } + }).timeout(60000); + + it("Invalid job ID – getInference must raise 404", async () => { + try { + await client.getInference("00000000-0000-0000-0000-000000000000"); + expect.fail("Expected the call to throw, but it succeeded."); + } catch (err) { + expect(err).to.be.instanceOf(MindeeHttpErrorV2); + expect((err as MindeeHttpErrorV2).status).to.equal(422); + } + }).timeout(60000); +}); diff --git a/tests/clientV2.spec.ts b/tests/v2/clientV2.spec.ts similarity index 88% rename from tests/clientV2.spec.ts rename to tests/v2/clientV2.spec.ts index 15c7a5260..c4c154560 100644 --- a/tests/clientV2.spec.ts +++ b/tests/v2/clientV2.spec.ts @@ -2,13 +2,14 @@ import { expect } from "chai"; import nock from "nock"; import path from "node:path"; -import { ClientV2 } from "../src"; -import { MindeeHttpErrorV2 } from "../src/errors/mindeeError"; +import { ClientV2 } from "../../src"; +import { MindeeHttpErrorV2 } from "../../src/errors/mindeeError"; import { LocalResponse, PathInput, -} from "../src/input"; +} from "../../src/input"; import assert from "node:assert/strict"; +import { InferenceResponse } from "../../src/parsing/v2"; /** * Injects a minimal set of environment variables so that the SDK behaves * as if it had been configured by the user. @@ -44,21 +45,22 @@ function setNockInterceptors(): void { }); } -before(() => { - dummyEnvvars(); - setNockInterceptors(); -}); - -after(() => { - delete process.env.MINDEE_V2_API_KEY; - delete process.env.MINDEE_V2_BASE_URL; - nock.cleanAll(); -}); -const resourcesPath = path.join(__dirname, "./data"); +const resourcesPath = path.join(__dirname, "..", "data"); const fileTypesDir = path.join(resourcesPath, "file_types"); const v2DataDir = path.join(resourcesPath, "v2"); describe("ClientV2", () => { + before(() => { + setNockInterceptors(); + dummyEnvvars(); + }); + + after(() => { + nock.cleanAll(); + delete process.env.MINDEE_V2_API_KEY; + delete process.env.MINDEE_V2_API_HOST; + }); + describe("Client configured via environment variables", () => { let client: ClientV2; @@ -96,7 +98,7 @@ describe("ClientV2", () => { ); }); - it("loadInference(LocalResponse) works on stored JSON fixtures", async () => { + it("loading an inference works on stored JSON fixtures", async () => { const jsonPath = path.join( v2DataDir, "products", @@ -106,7 +108,7 @@ describe("ClientV2", () => { const localResp = new LocalResponse(jsonPath); await localResp.init(); - const prediction = client.loadInference(localResp); + const prediction = localResp.deserializeResponse(InferenceResponse); expect(prediction.inference.model.id).to.equal( "12345678-1234-1234-1234-123456789abc" From c2067b64035893c935271878748102d2611da7d5 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Fri, 18 Jul 2025 17:52:24 +0200 Subject: [PATCH 18/24] fix test? --- docs/code_samples/default_v2.txt | 6 +++++- tests/v2/clientV2.integration.ts | 4 ---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/code_samples/default_v2.txt b/docs/code_samples/default_v2.txt index fc4dacc2c..e70879d2d 100644 --- a/docs/code_samples/default_v2.txt +++ b/docs/code_samples/default_v2.txt @@ -11,7 +11,11 @@ const inputSource = mindeeClient.docFromPath("/path/to/the/file.ext"); const apiResponse = mindeeClient.enqueueAndGetInference( inputSource, - { modelId: modelId, rag: false } + { + modelId: modelId, + // If set to `true`, will enable Retrieval-Augmented Generation. + rag: false + } ); // Handle the response Promise diff --git a/tests/v2/clientV2.integration.ts b/tests/v2/clientV2.integration.ts index aedc64cf7..6bd4572f8 100644 --- a/tests/v2/clientV2.integration.ts +++ b/tests/v2/clientV2.integration.ts @@ -28,10 +28,6 @@ describe("MindeeClientV2 – integration tests (V2)", () => { const apiKey = process.env["MINDEE_V2_API_KEY"] ?? ""; modelId = process.env["MINDEE_V2_FINDOC_MODEL_ID"] ?? ""; - if (apiKey.trim() === "" || modelId.trim() === "") { - throw new Error("No API key provided."); - } - client = new ClientV2({ apiKey }); }); From bc45930c13f934328509bfda3ddc505cc8e9b4a8 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:04:48 +0200 Subject: [PATCH 19/24] fix workflows --- .github/workflows/_test-code-samples.yml | 2 +- .github/workflows/_test-integrations.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/_test-code-samples.yml b/.github/workflows/_test-code-samples.yml index d33e974b3..549e2c30b 100644 --- a/.github/workflows/_test-code-samples.yml +++ b/.github/workflows/_test-code-samples.yml @@ -35,5 +35,5 @@ jobs: - name: Tests sample code run: | - ./tests/test_code_samples.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} ${{ secrets.MINDEE_API_KEY_SE_TESTS }} + ./tests/test_code_samples.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} ${{ secrets.MINDEE_API_KEY_SE_TESTS }} ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index 32cf2028d..efc5ae29f 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -45,4 +45,6 @@ jobs: env: MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }} WORKFLOW_ID: ${{ secrets.WORKFLOW_ID_SE_TESTS }} + MINDEE_V2_API_KEY: ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} + MINDEE_V2_FINDOC_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} run: npm run test-integration From b2c9ec9e5fec5d1d23397a0ac0b67544542fe107 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Mon, 21 Jul 2025 09:32:05 +0200 Subject: [PATCH 20/24] bump packages to cover vulnerability --- package-lock.json | 205 ++++++++++++++++++++++------------------------ 1 file changed, 97 insertions(+), 108 deletions(-) diff --git a/package-lock.json b/package-lock.json index e3a4de2f5..3d809b927 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,9 +71,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz", - "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", "license": "MIT", "optional": true, "dependencies": { @@ -176,9 +176,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -247,9 +247,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.30.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz", - "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", "dev": true, "license": "MIT", "engines": { @@ -283,30 +283,17 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@gerrit0/mini-shiki": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.7.0.tgz", - "integrity": "sha512-7iY9wg4FWXmeoFJpUL2u+tsmh0d0jcEJHAIzVxl3TG4KL493JNnisdLAILZ77zcD+z3J0keEXZ+lFzUgzQzPDg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.8.1.tgz", + "integrity": "sha512-HVZW+8pxoOExr5ZMPK15U79jQAZTO/S6i5byQyyZGjtNj+qaYd82cizTncwFzTQgiLo8uUBym6vh+/1tfJklTw==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/engine-oniguruma": "^3.7.0", - "@shikijs/langs": "^3.7.0", - "@shikijs/themes": "^3.7.0", - "@shikijs/types": "^3.7.0", + "@shikijs/engine-oniguruma": "^3.8.1", + "@shikijs/langs": "^3.8.1", + "@shikijs/themes": "^3.8.1", + "@shikijs/types": "^3.8.1", "@shikijs/vscode-textmate": "^10.0.2" } }, @@ -851,40 +838,40 @@ } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.7.0.tgz", - "integrity": "sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.8.1.tgz", + "integrity": "sha512-KGQJZHlNY7c656qPFEQpIoqOuC4LrxjyNndRdzk5WKB/Ie87+NJCF1xo9KkOUxwxylk7rT6nhlZyTGTC4fCe1g==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.7.0", + "@shikijs/types": "3.8.1", "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.7.0.tgz", - "integrity": "sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.8.1.tgz", + "integrity": "sha512-TjOFg2Wp1w07oKnXjs0AUMb4kJvujML+fJ1C5cmEj45lhjbUXtziT1x2bPQb9Db6kmPhkG5NI2tgYW1/DzhUuQ==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.7.0" + "@shikijs/types": "3.8.1" } }, "node_modules/@shikijs/themes": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.7.0.tgz", - "integrity": "sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.8.1.tgz", + "integrity": "sha512-Vu3t3BBLifc0GB0UPg2Pox1naTemrrvyZv2lkiSw3QayVV60me1ujFQwPZGgUTmwXl1yhCPW8Lieesm0CYruLQ==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.7.0" + "@shikijs/types": "3.8.1" } }, "node_modules/@shikijs/types": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.7.0.tgz", - "integrity": "sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.8.1.tgz", + "integrity": "sha512-5C39Q8/8r1I26suLh+5TPk1DTrbY/kn3IdWA5HdizR0FhlhD05zx5nKCqhzSfDHH3p4S0ZefxWd77DLV+8FhGg==", "dev": true, "license": "MIT", "dependencies": { @@ -972,9 +959,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.117", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.117.tgz", - "integrity": "sha512-hcxGs9TfQGghOM8atpRT+bBMUX7V8WosdYt98bQ59wUToJck55eCOlemJ+0FpOZOQw5ff7LSi9+IO56KvYEFyQ==", + "version": "18.19.120", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.120.tgz", + "integrity": "sha512-WtCGHFXnVI8WHLxDAt5TbnCM4eSE+nI0QN2NJtwzcgMhht2eNz6V9evJrk+lwC8bCY8OWV5Ym8Jz7ZEyGnKnMA==", "dev": true, "license": "MIT", "dependencies": { @@ -996,17 +983,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz", - "integrity": "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", + "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.36.0", - "@typescript-eslint/type-utils": "8.36.0", - "@typescript-eslint/utils": "8.36.0", - "@typescript-eslint/visitor-keys": "8.36.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/type-utils": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -1020,22 +1007,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.36.0", + "@typescript-eslint/parser": "^8.37.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.36.0.tgz", - "integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", + "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.36.0", - "@typescript-eslint/types": "8.36.0", - "@typescript-eslint/typescript-estree": "8.36.0", - "@typescript-eslint/visitor-keys": "8.36.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4" }, "engines": { @@ -1051,14 +1038,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.36.0.tgz", - "integrity": "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", + "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.36.0", - "@typescript-eslint/types": "^8.36.0", + "@typescript-eslint/tsconfig-utils": "^8.37.0", + "@typescript-eslint/types": "^8.37.0", "debug": "^4.3.4" }, "engines": { @@ -1073,14 +1060,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.36.0.tgz", - "integrity": "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", + "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.36.0", - "@typescript-eslint/visitor-keys": "8.36.0" + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1091,9 +1078,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.36.0.tgz", - "integrity": "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", + "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", "dev": true, "license": "MIT", "engines": { @@ -1108,14 +1095,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.36.0.tgz", - "integrity": "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", + "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.36.0", - "@typescript-eslint/utils": "8.36.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -1132,9 +1120,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.36.0.tgz", - "integrity": "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", + "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", "dev": true, "license": "MIT", "engines": { @@ -1146,16 +1134,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.36.0.tgz", - "integrity": "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", + "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.36.0", - "@typescript-eslint/tsconfig-utils": "8.36.0", - "@typescript-eslint/types": "8.36.0", - "@typescript-eslint/visitor-keys": "8.36.0", + "@typescript-eslint/project-service": "8.37.0", + "@typescript-eslint/tsconfig-utils": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1175,16 +1163,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.36.0.tgz", - "integrity": "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", + "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.36.0", - "@typescript-eslint/types": "8.36.0", - "@typescript-eslint/typescript-estree": "8.36.0" + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1199,13 +1187,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.36.0.tgz", - "integrity": "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", + "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.36.0", + "@typescript-eslint/types": "8.37.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -1924,9 +1912,9 @@ } }, "node_modules/eslint": { - "version": "9.30.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz", - "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1934,9 +1922,9 @@ "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.30.1", + "@eslint/js": "9.31.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -2351,14 +2339,15 @@ } }, "node_modules/form-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.3.tgz", - "integrity": "sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.35" }, "engines": { From 7f7f41d033bd20e8bdd43dd679d12ad7915c323d Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Mon, 21 Jul 2025 10:39:34 +0200 Subject: [PATCH 21/24] apply suggested changes --- docs/code_samples/default_v2.txt | 11 ++++++----- src/clientV2.ts | 26 +++++++++----------------- src/http/mindeeApiV2.ts | 10 +++++----- src/index.ts | 2 +- tests/v2/clientV2.integration.ts | 8 ++++---- 5 files changed, 25 insertions(+), 32 deletions(-) diff --git a/docs/code_samples/default_v2.txt b/docs/code_samples/default_v2.txt index e70879d2d..e78908fda 100644 --- a/docs/code_samples/default_v2.txt +++ b/docs/code_samples/default_v2.txt @@ -8,14 +8,15 @@ const modelId = "MY_MODEL_ID"; // Load a file from disk const inputSource = mindeeClient.docFromPath("/path/to/the/file.ext"); +const params: mindee.InferenceParameters = { + modelId: modelId, + // If set to `true`, will enable Retrieval-Augmented Generation. + rag: false +}; const apiResponse = mindeeClient.enqueueAndGetInference( inputSource, - { - modelId: modelId, - // If set to `true`, will enable Retrieval-Augmented Generation. - rag: false - } + params ); // Handle the response Promise diff --git a/src/clientV2.ts b/src/clientV2.ts index 69b64050c..13d5d7430 100644 --- a/src/clientV2.ts +++ b/src/clientV2.ts @@ -13,7 +13,7 @@ import { MindeeHttpErrorV2 } from "./errors/mindeeError"; /** * Asynchronous polling parameters. */ -interface OptionalPollingOptions { +export interface PollingOptions { initialDelaySec?: number; delaySec?: number; maxRetries?: number; @@ -27,21 +27,13 @@ interface OptionalPollingOptions { } } -interface PollingOptions { +interface ValidatedPollingOptions extends PollingOptions{ initialDelaySec: number; delaySec: number; maxRetries: number; - initialTimerOptions?: { - ref?: boolean, - signal?: AbortSignal - }; - recurringTimerOptions?: { - ref?: boolean, - signal?: AbortSignal - } } -export interface InferenceParams { +export interface InferenceParameters { /** ID of the model. **Required**. */ modelId: string; /** Enable Retrieval-Augmented Generation (RAG). */ @@ -51,7 +43,7 @@ export interface InferenceParams { /** IDs of the webhooks that should receive the API response. */ webhookIds?: string[]; /** Polling options. */ - pollingOptions?: OptionalPollingOptions; + pollingOptions?: PollingOptions; /** Set to `false` if the file must remain open after parsing. */ closeFile?: boolean; } @@ -104,7 +96,7 @@ export class ClientV2 extends BaseClient { */ async enqueueInference( inputSource: LocalInputSource, - params: InferenceParams + params: InferenceParameters ): Promise { if (inputSource === undefined) { throw new Error("The 'enqueue' function requires an input document."); @@ -144,11 +136,11 @@ export class ClientV2 extends BaseClient { * @param asyncParams parameters related to asynchronous parsing * @returns A valid `AsyncOptions`. */ - #setAsyncParams(asyncParams: OptionalPollingOptions | undefined = undefined): PollingOptions { + #setAsyncParams(asyncParams: PollingOptions | undefined = undefined): ValidatedPollingOptions { const minDelaySec = 1; const minInitialDelay = 1; const minRetries = 2; - let newAsyncParams: OptionalPollingOptions; + let newAsyncParams: PollingOptions; if (asyncParams === undefined) { newAsyncParams = { delaySec: 1.5, @@ -174,7 +166,7 @@ export class ClientV2 extends BaseClient { throw Error(`Cannot set retry to less than ${minRetries}.`); } } - return newAsyncParams as PollingOptions; + return newAsyncParams as ValidatedPollingOptions; } /** @@ -190,7 +182,7 @@ export class ClientV2 extends BaseClient { */ async enqueueAndGetInference( inputDoc: LocalInputSource, - params: InferenceParams + params: InferenceParameters ): Promise { const validatedAsyncParams = this.#setAsyncParams(params.pollingOptions); const enqueueResponse: JobResponse = await this.enqueueInference(inputDoc, params); diff --git a/src/http/mindeeApiV2.ts b/src/http/mindeeApiV2.ts index b6183ef83..7aa76f433 100644 --- a/src/http/mindeeApiV2.ts +++ b/src/http/mindeeApiV2.ts @@ -1,5 +1,5 @@ import { ApiSettingsV2 } from "./apiSettingsV2"; -import { InferenceParams } from "../clientV2"; +import { InferenceParameters } from "../clientV2"; import { InferenceResponse, JobResponse } from "../parsing/v2"; import FormData from "form-data"; import { RequestOptions } from "https"; @@ -18,12 +18,12 @@ export class MindeeApiV2 { /** * Sends a file to the inference queue. * @param inputDoc Local file loaded as an input. - * @param params {InferenceParams} parameters relating to the enqueueing options. + * @param params {InferenceParameters} parameters relating to the enqueueing options. * @category V2 * @throws Error if the server's response contains one. * @returns a `Promise` containing a job response. */ - async reqPostInferenceEnqueue(inputDoc: LocalInputSource, params: InferenceParams): Promise { + async reqPostInferenceEnqueue(inputDoc: LocalInputSource, params: InferenceParameters): Promise { await inputDoc.init(); if (params.modelId === undefined || params.modelId === null || params.modelId === "") { throw new Error("Model ID must be provided"); @@ -87,9 +87,9 @@ export class MindeeApiV2 { * Sends a document to the inference queue. * * @param inputDoc Local file loaded as an input. - * @param params {InferenceParams} parameters relating to the enqueueing options. + * @param params {InferenceParameters} parameters relating to the enqueueing options. */ - #documentEnqueuePost(inputDoc: LocalInputSource, params: InferenceParams): Promise { + #documentEnqueuePost(inputDoc: LocalInputSource, params: InferenceParameters): Promise { const form = new FormData(); form.append("model_id", params.modelId); diff --git a/src/index.ts b/src/index.ts index 6e76e9b3e..114ba5991 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ export * as product from "./product"; export { Client, PredictOptions } from "./client"; -export { ClientV2, InferenceParams } from "./clientV2"; +export { ClientV2, InferenceParameters, PollingOptions } from "./clientV2"; export { AsyncPredictResponse, PredictResponse, diff --git a/tests/v2/clientV2.integration.ts b/tests/v2/clientV2.integration.ts index 6bd4572f8..906013ae5 100644 --- a/tests/v2/clientV2.integration.ts +++ b/tests/v2/clientV2.integration.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import path from "node:path"; -import { ClientV2, InferenceParams } from "../../src"; +import { ClientV2, InferenceParameters } from "../../src"; import { PathInput } from "../../src/input"; import { SimpleField } from "../../src/parsing/v2/field"; import { MindeeHttpErrorV2 } from "../../src/errors/mindeeError"; @@ -33,7 +33,7 @@ describe("MindeeClientV2 – integration tests (V2)", () => { it("Empty, multi-page PDF – enqueue & parse must succeed", async () => { const source = new PathInput({ inputPath: emptyPdfPath }); - const params: InferenceParams = { modelId }; + const params: InferenceParameters = { modelId }; const response = await client.enqueueAndGetInference(source, params); @@ -50,7 +50,7 @@ describe("MindeeClientV2 – integration tests (V2)", () => { it("Filled, single-page image – enqueue & parse must succeed", async () => { const source = new PathInput({ inputPath: sampleImagePath }); - const params: InferenceParams = { modelId, rag: false }; + const params: InferenceParameters = { modelId, rag: false }; const response = await client.enqueueAndGetInference(source, params); @@ -65,7 +65,7 @@ describe("MindeeClientV2 – integration tests (V2)", () => { it("Invalid model ID – enqueue must raise 422", async () => { const source = new PathInput({ inputPath: emptyPdfPath }); - const badParams: InferenceParams = { modelId: "INVALID MODEL ID" }; + const badParams: InferenceParameters = { modelId: "INVALID MODEL ID" }; try { await client.enqueueInference(source, badParams); From b3b3eeb27268db8b36f915635f48488192ffb76d Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:10:23 +0200 Subject: [PATCH 22/24] add docstrings --- src/clientV2.ts | 85 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/src/clientV2.ts b/src/clientV2.ts index 13d5d7430..5e83abb0c 100644 --- a/src/clientV2.ts +++ b/src/clientV2.ts @@ -11,8 +11,38 @@ import { BaseClient } from "./baseClient"; import { MindeeHttpErrorV2 } from "./errors/mindeeError"; /** - * Asynchronous polling parameters. + * Parameters for the internal polling loop in {@link ClientV2.enqueueAndGetInference | enqueueAndGetInference()} . + * + * Default behavior: + * - `initialDelaySec` = 2s + * - `delaySec` = 1.5s + * - `maxRetries` = 80 + * + * Validation rules: + * - `initialDelaySec` >= 1 + * - `delaySec` >= 1 + * - `maxRetries` >= 2 + * + * The `initialTimerOptions` and `recurringTimerOptions` objects let you pass an + * `AbortSignal` or make the timer `unref`-ed to the `setTimeout()`. + * + * @property initialDelaySec Number of seconds to wait **before the first poll**. + * @property delaySec Interval in seconds between two consecutive polls. + * @property maxRetries Maximum number of polling attempts (including the first one). + * @property initialTimerOptions Options passed to the initial `setTimeout()`. + * @property recurringTimerOptions Options passed to every recurring `setTimeout()`. + * + * @category ClientV2 + * @example + * const params = { + * initialDelaySec: 4, + * delaySec: 2, + * maxRetries: 50 + * }; + * + * const inference = await client.enqueueAndGetInference(inputDoc, params); */ + export interface PollingOptions { initialDelaySec?: number; delaySec?: number; @@ -27,34 +57,63 @@ export interface PollingOptions { } } -interface ValidatedPollingOptions extends PollingOptions{ +interface ValidatedPollingOptions extends PollingOptions { initialDelaySec: number; delaySec: number; maxRetries: number; } +/** + * Parameters accepted by the asynchronous **inference** v2 endpoint. + * + * All fields are optional except `modelId`. + * + * @property modelId Identifier of the model that must process the document. **Required**. + * @property rag When `true`, activates Retrieval-Augmented Generation (RAG). + * @property alias Custom alias assigned to the uploaded document. + * @property webhookIds List of webhook UUIDs that will receive the final API response. + * @property pollingOptions Client-side polling configuration (see {@link PollingOptions}). + * @property closeFile By default the file is closed once the upload is finished, set to `false` to keep it open. + * @category ClientV2 + * @example + * const params = { + * modelId: "YOUR_MODEL_ID", + * rag: true, + * alias: "YOUR_ALIAS", + * webhookIds: ["YOUR_WEBHOOK_ID_1", "YOUR_WEBHOOK_ID_2"], + * pollingOptions: { + * initialDelaySec: 2, + * delaySec: 1.5, + * } + * }; + */ export interface InferenceParameters { - /** ID of the model. **Required**. */ modelId: string; - /** Enable Retrieval-Augmented Generation (RAG). */ rag?: boolean; - /** Optional alias for the file. */ alias?: string; - /** IDs of the webhooks that should receive the API response. */ webhookIds?: string[]; - /** Polling options. */ pollingOptions?: PollingOptions; - /** Set to `false` if the file must remain open after parsing. */ closeFile?: boolean; } - +/** + * Options for the V2 Mindee Client. + * + * @property apiKey Your API key for all endpoints. + * @property throwOnError Raise an `Error` on errors. + * @property debug Log debug messages. + * + * @category ClientV2 + * @example + * const client = new MindeeClientV2({ + * apiKey: "YOUR_API_KEY", + * throwOnError: true, + * debug: false + * }); + */ export interface ClientOptions { - /** Your API key for all endpoints. */ apiKey?: string; - /** Raise an `Error` on errors. */ throwOnError?: boolean; - /** Log debug messages. */ debug?: boolean; } @@ -197,7 +256,7 @@ export class ClientV2 extends BaseClient { await setTimeout(validatedAsyncParams.initialDelaySec * 1000, undefined, validatedAsyncParams.initialTimerOptions); let retryCounter: number = 1; - let pollResults: JobResponse = await this.getJob(queueId); + let pollResults: JobResponse = await this.getJob(queueId); while (retryCounter < validatedAsyncParams.maxRetries) { if (pollResults.job.status === "Failed") { break; From 532ffcf1e10af406aa22477f0715ba80f87d957f Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:15:16 +0200 Subject: [PATCH 23/24] fix code sample --- docs/code_samples/default_v2.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/code_samples/default_v2.txt b/docs/code_samples/default_v2.txt index e78908fda..51cbc21b5 100644 --- a/docs/code_samples/default_v2.txt +++ b/docs/code_samples/default_v2.txt @@ -8,7 +8,7 @@ const modelId = "MY_MODEL_ID"; // Load a file from disk const inputSource = mindeeClient.docFromPath("/path/to/the/file.ext"); -const params: mindee.InferenceParameters = { +const params = { modelId: modelId, // If set to `true`, will enable Retrieval-Augmented Generation. rag: false From f4401e2386f3d27fe8509ca382a1c57a9c5edbb1 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:49:42 +0200 Subject: [PATCH 24/24] deprecate cutPdf() --- src/http/baseEndpoint.ts | 2 +- src/input/sources/localInputSource.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/http/baseEndpoint.ts b/src/http/baseEndpoint.ts index 598920ed1..37b99ed12 100644 --- a/src/http/baseEndpoint.ts +++ b/src/http/baseEndpoint.ts @@ -36,7 +36,7 @@ export abstract class BaseEndpoint { */ public static async cutDocPages(inputDoc: InputSource, pageOptions: PageOptions) { if (inputDoc instanceof LocalInputSource && inputDoc.isPdf()) { - await inputDoc.cutPdf(pageOptions); + await inputDoc.applyPageOptions(pageOptions); } } diff --git a/src/input/sources/localInputSource.ts b/src/input/sources/localInputSource.ts index e4862669b..c9b9f8c80 100644 --- a/src/input/sources/localInputSource.ts +++ b/src/input/sources/localInputSource.ts @@ -91,7 +91,7 @@ export abstract class LocalInputSource extends InputSource { * Cut PDF pages. * @param pageOptions */ - async cutPdf(pageOptions: PageOptions) { + async applyPageOptions(pageOptions: PageOptions) { if (!(this.fileObject instanceof Buffer)) { throw new Error( `Cannot modify an input source of type ${this.inputType}.` @@ -101,6 +101,15 @@ export abstract class LocalInputSource extends InputSource { this.fileObject = processedPdf.file; } + /** + * Cut PDF pages. + * @param pageOptions + * @deprecated Deprecated in favor of {@link LocalInputSource.applyPageOptions applyPageOptions()}. + */ + async cutPdf(pageOptions: PageOptions) { + return this.applyPageOptions(pageOptions); + } + /** * Compresses the file object, either as a PDF or an image. *