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 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 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/docs/code_samples/default_v2.txt b/docs/code_samples/default_v2.txt new file mode 100644 index 000000000..51cbc21b5 --- /dev/null +++ b/docs/code_samples/default_v2.txt @@ -0,0 +1,26 @@ +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" }); +const modelId = "MY_MODEL_ID"; + +// Load a file from disk +const inputSource = mindeeClient.docFromPath("/path/to/the/file.ext"); +const params = { + modelId: modelId, + // If set to `true`, will enable Retrieval-Augmented Generation. + rag: false +}; + +const apiResponse = mindeeClient.enqueueAndGetInference( + inputSource, + params +); + +// Handle the response Promise +apiResponse.then((resp) => { + // print a string summary + console.log(resp.inference.toString()); +}); diff --git a/package-lock.json b/package-lock.json index 1cfad9096..3d809b927 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.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": { @@ -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": { @@ -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": { @@ -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.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", "dev": true, "license": "MIT", "engines": { @@ -270,19 +270,33 @@ } }, "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/@gerrit0/mini-shiki": { + "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.8.1", + "@shikijs/langs": "^3.8.1", + "@shikijs/themes": "^3.8.1", + "@shikijs/types": "^3.8.1", + "@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 +753,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 +837,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.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": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1" + "@shikijs/types": "3.8.1", + "@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.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": "1.29.2" + "@shikijs/types": "3.8.1" } }, "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.8.1", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.8.1.tgz", + "integrity": "sha512-Vu3t3BBLifc0GB0UPg2Pox1naTemrrvyZv2lkiSw3QayVV60me1ujFQwPZGgUTmwXl1yhCPW8Lieesm0CYruLQ==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.2" + "@shikijs/types": "3.8.1" } }, "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.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": { - "@shikijs/vscode-textmate": "^10.0.1", + "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, @@ -941,9 +928,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 +951,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 +959,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.120", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.120.tgz", + "integrity": "sha512-WtCGHFXnVI8WHLxDAt5TbnCM4eSE+nI0QN2NJtwzcgMhht2eNz6V9evJrk+lwC8bCY8OWV5Ym8Jz7ZEyGnKnMA==", "dev": true, "license": "MIT", "dependencies": { @@ -1006,17 +983,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.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.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.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", @@ -1030,22 +1007,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.33.1", + "@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.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", - "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", + "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.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.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": { @@ -1061,14 +1038,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.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.33.1", - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/tsconfig-utils": "^8.37.0", + "@typescript-eslint/types": "^8.37.0", "debug": "^4.3.4" }, "engines": { @@ -1083,14 +1060,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.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.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1101,9 +1078,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.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": { @@ -1118,14 +1095,15 @@ } }, "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.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.33.1", - "@typescript-eslint/utils": "8.33.1", + "@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" }, @@ -1142,9 +1120,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.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": { @@ -1156,16 +1134,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.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.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.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", @@ -1185,16 +1163,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.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.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" + "@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" @@ -1209,14 +1187,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.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.33.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.37.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1227,9 +1205,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 +1217,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 +1230,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 +1379,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 +1467,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 +1503,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 +1663,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 +1777,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 +1786,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 +1830,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 +1912,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.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "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/core": "^0.14.0", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", + "@eslint/js": "9.31.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -2040,9 +1936,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 +1973,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 +1998,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 +2028,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 +2039,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 +2075,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 +2093,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": { @@ -2443,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": { @@ -2638,44 +2535,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 +2561,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 +2896,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 +2913,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 +2975,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 +2997,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 +3056,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 +3080,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 +3295,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 +3392,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 +3551,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 +3573,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 +3671,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 +3793,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 +3890,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 +3941,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 +3958,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 +3994,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 +4230,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..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", @@ -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": { 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..5e83abb0c --- /dev/null +++ b/src/clientV2.ts @@ -0,0 +1,286 @@ +import { + LocalInputSource, +} from "./input"; +import { errorHandler } from "./errors/handler"; +import { LOG_LEVELS, logger } from "./logger"; + +import { setTimeout } from "node:timers/promises"; +import { ErrorResponse, InferenceResponse, JobResponse } from "./parsing/v2"; +import { MindeeApiV2 } from "./http/mindeeApiV2"; +import { BaseClient } from "./baseClient"; +import { MindeeHttpErrorV2 } from "./errors/mindeeError"; + +/** + * 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; + maxRetries?: number; + initialTimerOptions?: { + ref?: boolean, + signal?: AbortSignal + }; + recurringTimerOptions?: { + ref?: boolean, + signal?: AbortSignal + } +} + +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 { + modelId: string; + rag?: boolean; + alias?: string; + webhookIds?: string[]; + pollingOptions?: PollingOptions; + 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 { + apiKey?: string; + throwOnError?: boolean; + 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 enqueueInference( + inputSource: LocalInputSource, + params: InferenceParameters + ): Promise { + if (inputSource === undefined) { + throw new Error("The 'enqueue' function requires an input document."); + } + return await this.mindeeApi.reqPostInferenceEnqueue(inputSource, params); + } + + /** + * 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`. + * @category Asynchronous + * @returns a `Promise` containing a `Job`, which also contains a `Document` if the + * parsing is complete. + */ + async getJob(jobId: string): Promise { + return await this.mindeeApi.reqGetJob(jobId); + } + + /** + * 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: PollingOptions | undefined = undefined): ValidatedPollingOptions { + const minDelaySec = 1; + const minInitialDelay = 1; + const minRetries = 2; + let newAsyncParams: PollingOptions; + 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 ValidatedPollingOptions; + } + + /** + * 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 enqueueAndGetInference( + inputDoc: LocalInputSource, + params: InferenceParameters + ): Promise { + const validatedAsyncParams = this.#setAsyncParams(params.pollingOptions); + 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."); + } + 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 = await this.getJob(queueId); + while (retryCounter < validatedAsyncParams.maxRetries) { + 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.getJob(queueId); + retryCounter++; + } + const error: ErrorResponse | undefined = pollResults.job.error; + if (error) { + throw new MindeeHttpErrorV2(error.status, error.detail); + } + throw Error( + "Asynchronous parsing request timed out after " + + validatedAsyncParams.delaySec * retryCounter + + " seconds" + ); + } +} diff --git a/src/errors/mindeeError.ts b/src/errors/mindeeError.ts index 1925c6998..1ed773c71 100644 --- a/src/errors/mindeeError.ts +++ b/src/errors/mindeeError.ts @@ -33,4 +33,22 @@ export class MindeePdfError extends MindeeError { } } +export class MindeeApiV2Error extends MindeeError { + constructor(message: string) { + super(message); + this.name = "MindeeApiV2Error"; + } +} + +export class MindeeHttpErrorV2 extends MindeeError { + public status: number; + public detail: string; + constructor(status: number, detail: string) { + super(`HTTP ${status} - ${detail}`); + this.status = status; + this.detail = detail; + this.name = "MindeeHttpErrorV2"; + } +} + diff --git a/src/http/apiSettings.ts b/src/http/apiSettings.ts index 01b1af4ec..ecd9c506f 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 { @@ -37,25 +31,8 @@ export class ApiSettings { "User-Agent": this.getUserAgent(), Authorization: `Token ${this.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..49cf7d4c3 --- /dev/null +++ b/src/http/apiSettingsV2.ts @@ -0,0 +1,57 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { logger } from "../logger"; +import { BaseSettings, MindeeApiConstructorProps } from "./baseSettings"; +import { MindeeApiV2Error } from "../errors/mindeeError"; + +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 { + 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 MindeeApiV2Error( + "Your API V2 key could not be set, check your Client Configuration\n." + + `You can set this using the ${API_V2_KEY_ENVVAR_NAME} environment variable.` + ); + } + this.baseHeaders = { + "User-Agent": this.getUserAgent(), + Authorization: `${apiKey}`, + }; + } + + + protected apiKeyFromEnv(): string { + const envVarValue = process.env[API_V2_KEY_ENVVAR_NAME]; + if (envVarValue) { + logger.debug( + `Set API key from environment: ${API_V2_KEY_ENVVAR_NAME}` + ); + return envVarValue; + } + return ""; + } + + protected hostnameFromEnv(): string { + const envVarValue = process.env[API_V2_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..37b99ed12 100644 --- a/src/http/baseEndpoint.ts +++ b/src/http/baseEndpoint.ts @@ -34,9 +34,9 @@ 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); + await inputDoc.applyPageOptions(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..7aa76f433 --- /dev/null +++ b/src/http/mindeeApiV2.ts @@ -0,0 +1,142 @@ +import { ApiSettingsV2 } from "./apiSettingsV2"; +import { InferenceParameters } 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 {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: InferenceParameters): Promise { + await inputDoc.init(); + 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, JobResponse); + } + + + /** + * 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. + * @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 reqGetJob(jobId: string): Promise { + const queueResponse: EndpointResponse = await this.#inferenceResultReqGet(jobId, "jobs"); + return this.#processResponse(queueResponse, JobResponse); + } + + #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.data?.statusMessage ?? "Unknown error." + ); + } + try { + return new responseType(result.data); + } catch (e) { + logger.error(`Raised '${e}' Couldn't deserialize response object:\n${JSON.stringify(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 {InferenceParameters} parameters relating to the enqueueing options. + */ + #documentEnqueuePost(inputDoc: LocalInputSource, params: InferenceParameters): 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 the status of a document in the queue. + * @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. + */ + #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/${slug}/${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(); diff --git a/src/index.ts b/src/index.ts index 0bb1c9843..114ba5991 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, InferenceParameters, PollingOptions } from "./clientV2"; export { AsyncPredictResponse, PredictResponse, 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/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. * diff --git a/src/parsing/common/dateParser.ts b/src/parsing/common/dateParser.ts new file mode 100644 index 000000000..37d762943 --- /dev/null +++ b/src/parsing/common/dateParser.ts @@ -0,0 +1,9 @@ +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/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/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/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/field/baseField.ts b/src/parsing/v2/field/baseField.ts new file mode 100644 index 000000000..cf1b55015 --- /dev/null +++ b/src/parsing/v2/field/baseField.ts @@ -0,0 +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(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/fieldFactory.ts b/src/parsing/v2/field/fieldFactory.ts new file mode 100644 index 000000000..afc2e46fa --- /dev/null +++ b/src/parsing/v2/field/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/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 new file mode 100644 index 000000000..bfbd648fa --- /dev/null +++ b/src/parsing/v2/field/index.ts @@ -0,0 +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 new file mode 100644 index 000000000..5f4d759d2 --- /dev/null +++ b/src/parsing/v2/field/inferenceFields.ts @@ -0,0 +1,46 @@ +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).map( ([key, value]) => { + return [key, createField(value, 1)]; + })); + this._indentLevel = indentLevel; + } + + toString(indent: number = this._indentLevel): string { + if (this.size === 0) { + return ""; + } + + const padding = " ".repeat(indent); + const lines: string[] = []; + + for (const [fieldKey, fieldValue] of this.entries()) { + let line = `${padding}:${fieldKey}:`; + + if (fieldValue.constructor.name === "ListField") { + const listField = fieldValue as ListField; + if (Array.isArray(listField.items) && listField.items.length > 0) { + line += listField.toString(); + } + } 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() : ""; + } + + lines.push(line); + } + + return lines.join("\n").trimEnd(); + } +} diff --git a/src/parsing/v2/field/listField.ts b/src/parsing/v2/field/listField.ts new file mode 100644 index 000000000..c42e53b8e --- /dev/null +++ b/src/parsing/v2/field/listField.ts @@ -0,0 +1,45 @@ +import { MindeeApiV2Error } from "../../../errors/mindeeError"; +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 { + /** + * Items contained in the list. + */ + public items: Array; + + constructor(serverResponse: StringDict, indentLevel = 0) { + super(serverResponse, 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 createField(item, indentLevel + 1); + }); + } + + toString(): string { + if (!this.items || this.items.length === 0) { + return "\n"; + } + + 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 new file mode 100644 index 000000000..f6779cfb6 --- /dev/null +++ b/src/parsing/v2/field/objectField.ts @@ -0,0 +1,23 @@ +import { BaseField } from "./baseField"; +import { InferenceFields } from "./inferenceFields"; +import { StringDict } from "../../common"; + +export class ObjectField extends BaseField { + readonly fields: InferenceFields; + + constructor(serverResponse: StringDict, indentLevel = 0) { + super(serverResponse, indentLevel); + + this.fields = new InferenceFields(serverResponse["fields"], this._indentLevel + 1); + } + + + + toString(): string { + 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 new file mode 100644 index 000000000..597c46763 --- /dev/null +++ b/src/parsing/v2/field/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(serverResponse, indentLevel); + this.value = + serverResponse["value"] !== undefined ? (serverResponse["value"] as any) : null; + } + + toString(): string { + return this.value !== null && this.value !== undefined + ? this.value.toString() + : ""; + } +} diff --git a/src/parsing/v2/index.ts b/src/parsing/v2/index.ts new file mode 100644 index 000000000..3ecaf89e7 --- /dev/null +++ b/src/parsing/v2/index.ts @@ -0,0 +1,12 @@ +export { CommonResponse } from "./commonResponse"; +export { ErrorResponse } from "./errorResponse"; +export { Inference } from "./inference"; +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 { RawText } from "./rawText"; +export { JobResponseWebhook } from "./jobResponseWebhook"; diff --git a/src/parsing/v2/inference.ts b/src/parsing/v2/inference.ts new file mode 100644 index 000000000..01f9d8d08 --- /dev/null +++ b/src/parsing/v2/inference.ts @@ -0,0 +1,44 @@ +import { StringDict } from "../common"; +import { InferenceResultModel } from "./inferenceResultModel"; +import { InferenceResult } from "./inferenceResult"; +import { InferenceResultFile } from "./inferenceResultFile"; + +export class Inference { + /** + * Model info for the inference. + */ + public model: InferenceResultModel; + /** + * File info for the inference. + */ + public file: InferenceResultFile; + /** + * Result of the inference. + */ + public result: InferenceResult; + /** + * ID of the inference. + */ + public id?: string; + + constructor(serverResponse: StringDict) { + 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"]; + } + } + + toString(): string { + return ( + "Inference\n" + + "#########\n" + + "Model\n" + + "=====\n" + + `:ID: ${this.model.id}\n\n` + + this.file.toString() + "\n" + + this.result + "\n" + ); + } +} 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..92900f536 --- /dev/null +++ b/src/parsing/v2/inferenceResult.ts @@ -0,0 +1,40 @@ +import { InferenceFields } from "./field/inferenceFields"; +import { InferenceResultOptions } from "./inferenceResultOptions"; +import { StringDict } from "../common"; + +export class InferenceResult { + /** + * Fields contained in the inference. + */ + public fields: InferenceFields; + + /** + * Potential options retrieved alongside the inference. + */ + public options?: InferenceResultOptions; + + constructor(serverResponse: StringDict) { + this.fields = new InferenceFields(serverResponse["fields"]); + if (serverResponse["options"]) { + this.options = new InferenceResultOptions(serverResponse["options"]); + } + } + + toString(): string { + const parts: string[] = [ + "Fields", + "======", + this.fields.toString(), + ]; + + if (this.options) { + parts.push( + "Options", + "=======", + this.options.toString() + ); + } + + return parts.join("\n"); + } +} diff --git a/src/parsing/v2/inferenceResultFile.ts b/src/parsing/v2/inferenceResultFile.ts new file mode 100644 index 000000000..aac8887a2 --- /dev/null +++ b/src/parsing/v2/inferenceResultFile.ts @@ -0,0 +1,25 @@ +import { StringDict } from "../common"; + +export class InferenceResultFile { + /** + * 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"]; + } + + toString () { + return( + "File\n" + + "====\n" + + `:Name: ${this.name}\n` + + `:Alias:${this.alias ? " " + this.alias : ""}\n`); + } +} diff --git a/src/parsing/v2/inferenceResultModel.ts b/src/parsing/v2/inferenceResultModel.ts new file mode 100644 index 000000000..d6d86bcf8 --- /dev/null +++ b/src/parsing/v2/inferenceResultModel.ts @@ -0,0 +1,12 @@ +import { StringDict } from "../common"; + +export class InferenceResultModel { + /** + * ID of the model. + */ + public id: string; + + constructor(serverResponse: StringDict) { + this.id = serverResponse["id"]; + } +} diff --git a/src/parsing/v2/inferenceResultOptions.ts b/src/parsing/v2/inferenceResultOptions.ts new file mode 100644 index 000000000..09d66c86d --- /dev/null +++ b/src/parsing/v2/inferenceResultOptions.ts @@ -0,0 +1,15 @@ +import { StringDict } from "../common"; +import { RawText } from "./rawText"; + +export class InferenceResultOptions { + /** + * 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/job.ts b/src/parsing/v2/job.ts new file mode 100644 index 000000000..48f5c31a1 --- /dev/null +++ b/src/parsing/v2/job.ts @@ -0,0 +1,72 @@ +import { StringDict } from "../common"; +import { ErrorResponse } from "./errorResponse"; +import { JobResponseWebhook } from "./jobResponseWebhook"; +import { parseDate } from "../common/dateParser"; + +/** + * 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"]; + 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 = parseDate(serverResponse["created_at"]); + 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"]; + } +} 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/jobResponseWebhook.ts b/src/parsing/v2/jobResponseWebhook.ts new file mode 100644 index 000000000..c495e5b5e --- /dev/null +++ b/src/parsing/v2/jobResponseWebhook.ts @@ -0,0 +1,33 @@ +import { ErrorResponse } from "./errorResponse"; +import { StringDict, parseDate } from "../common"; + +/** + * JobResponseWebhook information. + */ +export class JobResponseWebhook { + /** + * JobResponseWebhook 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/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/tests/fields/amount.spec.ts b/tests/parsing/standard/amount.spec.ts similarity index 93% rename from tests/fields/amount.spec.ts rename to tests/parsing/standard/amount.spec.ts index 308e16453..25d66c79d 100644 --- a/tests/fields/amount.spec.ts +++ b/tests/parsing/standard/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/standard/classification.spec.ts similarity index 89% rename from tests/fields/classification.spec.ts rename to tests/parsing/standard/classification.spec.ts index 64eb586a8..d56db0e90 100644 --- a/tests/fields/classification.spec.ts +++ b/tests/parsing/standard/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/standard/date.spec.ts similarity index 94% rename from tests/fields/date.spec.ts rename to tests/parsing/standard/date.spec.ts index 0eb7edc29..433a7ff58 100644 --- a/tests/fields/date.spec.ts +++ b/tests/parsing/standard/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/standard/field.spec.ts similarity index 97% rename from tests/fields/field.spec.ts rename to tests/parsing/standard/field.spec.ts index 7fc7ea8b0..642617750 100644 --- a/tests/fields/field.spec.ts +++ b/tests/parsing/standard/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/standard/locale.spec.ts similarity index 95% rename from tests/fields/locale.spec.ts rename to tests/parsing/standard/locale.spec.ts index 0dac29f0a..0f1c66890 100644 --- a/tests/fields/locale.spec.ts +++ b/tests/parsing/standard/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/standard/orientation.spec.ts similarity index 92% rename from tests/fields/orientation.spec.ts rename to tests/parsing/standard/orientation.spec.ts index 412b8c280..2ebee99c2 100644 --- a/tests/fields/orientation.spec.ts +++ b/tests/parsing/standard/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/standard/paymentDetails.spec.ts similarity index 97% rename from tests/fields/paymentDetails.spec.ts rename to tests/parsing/standard/paymentDetails.spec.ts index 9273df981..ff5601dad 100644 --- a/tests/fields/paymentDetails.spec.ts +++ b/tests/parsing/standard/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/standard/position.spec.ts similarity index 95% rename from tests/fields/position.spec.ts rename to tests/parsing/standard/position.spec.ts index 9129982c6..ff00813eb 100644 --- a/tests/fields/position.spec.ts +++ b/tests/parsing/standard/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/standard/tax.spec.ts similarity index 96% rename from tests/fields/tax.spec.ts rename to tests/parsing/standard/tax.spec.ts index 4795358e1..b0156faec 100644 --- a/tests/fields/tax.spec.ts +++ b/tests/parsing/standard/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/standard/text.spec.ts similarity index 94% rename from tests/fields/text.spec.ts rename to tests/parsing/standard/text.spec.ts index 168d8a48f..1c05b3477 100644 --- a/tests/fields/text.spec.ts +++ b/tests/parsing/standard/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", () => { diff --git a/tests/parsing/v2/inference.spec.ts b/tests/parsing/v2/inference.spec.ts new file mode 100644 index 000000000..2276f79a7 --- /dev/null +++ b/tests/parsing/v2/inference.spec.ts @@ -0,0 +1,192 @@ +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"; +import { promises as fs } from "node:fs"; + +const resourcesPath = path.join(__dirname, "..", "..", "data"); +const v2DataDir = path.join(resourcesPath, "v2"); +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 localResponse = new LocalResponse(resourcePath); + await localResponse.init(); + return localResponse.deserializeResponse(InferenceResponse); +} + +describe("inference", async () => { + 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); + }); +}); 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 diff --git a/tests/v2/clientV2.integration.ts b/tests/v2/clientV2.integration.ts new file mode 100644 index 000000000..906013ae5 --- /dev/null +++ b/tests/v2/clientV2.integration.ts @@ -0,0 +1,88 @@ +import { expect } from "chai"; +import path from "node:path"; + +import { ClientV2, InferenceParameters } 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"] ?? ""; + + client = new ClientV2({ apiKey }); + }); + + it("Empty, multi-page PDF – enqueue & parse must succeed", async () => { + const source = new PathInput({ inputPath: emptyPdfPath }); + const params: InferenceParameters = { 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: InferenceParameters = { 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: InferenceParameters = { 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/v2/clientV2.spec.ts b/tests/v2/clientV2.spec.ts new file mode 100644 index 000000000..c4c154560 --- /dev/null +++ b/tests/v2/clientV2.spec.ts @@ -0,0 +1,160 @@ +/* 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 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. + */ +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, + }, + }); +} + +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; + + 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.enqueueInference(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.enqueueAndGetInference( + inputDoc, + { modelId: "dummy-model" } + ), + MindeeHttpErrorV2 + ); + }); + + it("loading an inference 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 = localResp.deserializeResponse(InferenceResponse); + + 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.enqueueInference(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.getJob( + "12345678-1234-1234-1234-123456789ABC" + ); + + + 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"); + expect(job.alias).to.equal("dummy-alias.jpg"); + 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" + ); + expect(job.resultUrl).to.be.null; + expect(job.webhooks).to.have.length(0); + expect(job.error).to.be.undefined; + }); + }); +});