diff --git a/.github/workflows/_publish-code.yml b/.github/workflows/_publish-code.yml index fcc1a179..07bbe54d 100644 --- a/.github/workflows/_publish-code.yml +++ b/.github/workflows/_publish-code.yml @@ -11,13 +11,13 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Dependencies run: sudo apt-get install pcregrep - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: 26 cache: "npm" diff --git a/.github/workflows/_publish-docs.yml b/.github/workflows/_publish-docs.yml index 18fda033..d47832f3 100644 --- a/.github/workflows/_publish-docs.yml +++ b/.github/workflows/_publish-docs.yml @@ -11,10 +11,10 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: 26 cache: "npm" @@ -27,7 +27,7 @@ jobs: npm run docs:dist - name: Deploy - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs/_build diff --git a/.github/workflows/_static-analysis.yml b/.github/workflows/_static-analysis.yml index d45ed227..2747135f 100644 --- a/.github/workflows/_static-analysis.yml +++ b/.github/workflows/_static-analysis.yml @@ -10,33 +10,10 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Run Betterleaks - id: betterleaks - continue-on-error: true - # version 0.1.0 - uses: dortort/betterleaks-action@cd0b905aa4befb8f42f8b1ab98150180c9e77515 - with: - version: "v1.9.0" - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Upload Betterleaks report - if: always() - uses: actions/upload-artifact@v7 - with: - name: betterleaks-report - path: betterleaks-report.sarif - if-no-files-found: warn - - - name: Fail if Betterleaks found leaks - if: steps.betterleaks.outcome == 'failure' - run: exit 1 + uses: actions/checkout@v7 - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: 26 cache: "npm" diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index 9d87e0ff..db3f260a 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -33,12 +33,12 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} cache: "npm" @@ -84,12 +84,12 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} cache: "npm" diff --git a/.github/workflows/_test-smoke.yml b/.github/workflows/_test-smoke.yml index 175605d2..cc77d58f 100644 --- a/.github/workflows/_test-smoke.yml +++ b/.github/workflows/_test-smoke.yml @@ -26,12 +26,12 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} cache: "npm" diff --git a/.github/workflows/_test-units.yml b/.github/workflows/_test-units.yml index bcda8d6c..620a7d17 100644 --- a/.github/workflows/_test-units.yml +++ b/.github/workflows/_test-units.yml @@ -19,12 +19,12 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} cache: "npm" @@ -58,12 +58,12 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} cache: "npm" diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 61274f2b..a7a98329 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -6,12 +6,12 @@ on: jobs: publish_docs: - uses: mindee/mindee-api-nodejs/.github/workflows/_publish-docs.yml@main + uses: ./.github/workflows/_publish-docs.yml secrets: inherit publish_guide: - uses: mindee/mindee-api-nodejs/.github/workflows/_publish-guide.yml@main + uses: ./.github/workflows/_publish-guide.yml needs: publish_docs secrets: inherit publish_code: - uses: mindee/mindee-api-nodejs/.github/workflows/_publish-code.yml@main + uses: ./.github/workflows/_publish-code.yml secrets: inherit diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 72fcbf18..702727ec 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -8,17 +8,24 @@ permissions: pull-requests: read jobs: - static_analysis: + workflow-lint: + uses: mindee/client-lib-actions/.github/workflows/workflow-lint.yml@main + betterleaks: + uses: mindee/client-lib-actions/.github/workflows/betterleaks.yml@main + needs: workflow-lint + secrets: inherit + static-analysis: uses: ./.github/workflows/_static-analysis.yml - test_units: + needs: workflow-lint + test-units: uses: ./.github/workflows/_test-units.yml - needs: static_analysis + needs: [ betterleaks, static-analysis ] secrets: inherit - test_smoke: + test-smoke: uses: ./.github/workflows/_test-smoke.yml - needs: static_analysis + needs: [ betterleaks, static-analysis ] secrets: inherit - test_integrations: + test-integrations: uses: ./.github/workflows/_test-integrations.yml - needs: test_units + needs: test-units secrets: inherit diff --git a/.github/workflows/push-main-branch.yml b/.github/workflows/push-main-branch.yml index aaa80f19..8305e9bc 100644 --- a/.github/workflows/push-main-branch.yml +++ b/.github/workflows/push-main-branch.yml @@ -6,15 +6,19 @@ on: - main jobs: - static_analysis: - uses: mindee/mindee-api-nodejs/.github/workflows/_static-analysis.yml@main - test_units: - uses: mindee/mindee-api-nodejs/.github/workflows/_test-units.yml@main - needs: static_analysis + betterleaks: + uses: mindee/client-lib-actions/.github/workflows/betterleaks.yml@main + secrets: inherit + static-analysis: + uses: ./.github/workflows/_static-analysis.yml + secrets: inherit + test-units: + uses: ./.github/workflows/_test-units.yml + needs: [ betterleaks, static-analysis ] secrets: inherit tag: uses: mindee/client-lib-actions/.github/workflows/tag-version.yml@main - needs: test_units + needs: test-units release: uses: mindee/client-lib-actions/.github/workflows/create-release.yml@main needs: tag diff --git a/src/v1/client.ts b/src/v1/client.ts index 2495aec8..7e2815d2 100644 --- a/src/v1/client.ts +++ b/src/v1/client.ts @@ -15,7 +15,6 @@ import { } from "./http/index.js"; import { AsyncPredictResponse, - ExecutionPriority, FeedbackResponse, Inference, PredictResponse, @@ -66,25 +65,6 @@ export interface PredictOptions extends BaseOptions { workflowId?: string; } -/** - * Options relating to workflows. - * @category Workflow - */ -export interface WorkflowOptions extends BaseOptions { - /** - * Alias to give to the document. - */ - alias?: string; - /** - * Priority to give to the document. - */ - priority?: ExecutionPriority; - /** - * A unique, encrypted URL for accessing the document validation interface without requiring authentication. - */ - publicUrl?: string; -} - /** * Timer options forwarded to `setTimeout` in polling loops. */ diff --git a/src/v1/http/httpParams.ts b/src/v1/http/httpParams.ts index 53864d6c..0b650462 100644 --- a/src/v1/http/httpParams.ts +++ b/src/v1/http/httpParams.ts @@ -1,5 +1,4 @@ import { InputSource, PageOptions } from "@/input/index.js"; -import { ExecutionPriority } from "@/v1/parsing/common/index.js"; interface HTTPParams { /** @@ -37,21 +36,3 @@ export interface PredictParams extends HTTPParams { */ workflowId?: string; } - -/** - * Parameters sent to workflow execution endpoints. - */ -export interface WorkflowParams extends HTTPParams { - /** - * Custom alias assigned to the submitted file. - */ - alias?: string; - /** - * Processing priority of the workflow execution. - */ - priority?: ExecutionPriority; - /** - * Whether to request a public validation URL. - */ - publicUrl?: string; -} diff --git a/src/v1/http/index.ts b/src/v1/http/index.ts index 71a8069d..a1da360a 100644 --- a/src/v1/http/index.ts +++ b/src/v1/http/index.ts @@ -15,4 +15,4 @@ export { MindeeHttp504Error, handleError, } from "./errors.js"; -export type { PredictParams, WorkflowParams } from "./httpParams.js"; +export type { PredictParams } from "./httpParams.js"; diff --git a/src/v1/index.ts b/src/v1/index.ts index a0263960..44b0ed7f 100644 --- a/src/v1/index.ts +++ b/src/v1/index.ts @@ -6,8 +6,7 @@ export { LocalResponse } from "./parsing/localResponse.js"; export { Client } from "./client.js"; export type { OptionalAsyncOptions, - PredictOptions, - WorkflowOptions + PredictOptions } from "./client.js"; export { AsyncPredictResponse, diff --git a/src/v1/parsing/common/execution.ts b/src/v1/parsing/common/execution.ts deleted file mode 100644 index 1cc40a9a..00000000 --- a/src/v1/parsing/common/execution.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Inference } from "./inference.js"; -import { GeneratedV1Document } from "@/v1/product/generated/generatedV1Document.js"; -import { ExecutionFile } from "./executionFile.js"; -import { StringDict } from "@/parsing/stringDict.js"; -import { ExecutionPriority } from "./executionPriority.js"; -import { parseDate } from "@/parsing/dateParser.js"; - -/** - * Representation of an execution for a workflow. - * @category Workflow - */ -export class Execution { - /** - * Identifier for the batch to which the execution belongs. - */ - batchName: string; - - /** - * The time at which the execution started. - */ - createdAt: Date | null; - - /** - * File representation within a workflow execution. - */ - file: ExecutionFile; - - /** - * Identifier for the execution. - */ - id: string; - - /** - * Deserialized inference object. - */ - inference: T | null; - - /** - * Priority of the execution. - */ - priority: ExecutionPriority | null; - - /** - * The time at which the file was tagged as reviewed. - */ - reviewedAt: Date | null; - - /** - * The time at which the file was uploaded to a workflow. - */ - availableAt: Date | null; - - /** - * Reviewed fields and values. - */ - reviewedPrediction: GeneratedV1Document | null; - - /** - * Execution Status. - */ - status: string; - - /** - * Execution type. - */ - type: string | null; - - /** - * The time at which the file was uploaded to a workflow. - */ - uploadedAt: Date | null; - - /** - * Identifier for the workflow. - */ - workflowId: string; - - constructor(inferenceClass: new (serverResponse: StringDict) => T, jsonResponse: StringDict) { - this.batchName = jsonResponse["batch_name"]; - 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 = 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 = parseDate(jsonResponse["uploaded_at"]); - this.workflowId = jsonResponse["workflow_id"]; - } -} diff --git a/src/v1/parsing/common/executionFile.ts b/src/v1/parsing/common/executionFile.ts deleted file mode 100644 index 3adbc4f1..00000000 --- a/src/v1/parsing/common/executionFile.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { StringDict } from "@/parsing/stringDict.js"; - -/** - * Representation of an execution's file info. - * @category Workflow - */ -export class ExecutionFile { - - /** - * File name. - */ - name: string|null; - - /** - * Optional alias for the file. - */ - alias: string|null; - - constructor(jsonResponse: StringDict) { - this.name = jsonResponse["name"]; - this.alias = jsonResponse["alias"]; - } -} diff --git a/src/v1/parsing/common/executionPriority.ts b/src/v1/parsing/common/executionPriority.ts deleted file mode 100644 index d35ec8f7..00000000 --- a/src/v1/parsing/common/executionPriority.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Execution priority levels available for workflow requests. - */ -export enum ExecutionPriority { - /** - * Low-priority background processing. - */ - low = "low", - /** - * Standard processing priority. - */ - medium = "medium", - /** - * High-priority processing. - */ - high = "high" -} diff --git a/src/v1/parsing/common/index.ts b/src/v1/parsing/common/index.ts index fdce1498..1dab8480 100644 --- a/src/v1/parsing/common/index.ts +++ b/src/v1/parsing/common/index.ts @@ -1,7 +1,4 @@ export { Document } from "./document.js"; -export { Execution } from "./execution.js"; -export { ExecutionFile } from "./executionFile.js"; -export { ExecutionPriority } from "./executionPriority.js"; export { Inference } from "./inference.js"; export { FeedbackResponse } from "./feedback/feedbackResponse.js"; export { OrientationField } from "./orientation.js"; diff --git a/src/v2/client.ts b/src/v2/client.ts index 20b21a18..ec2d8469 100644 --- a/src/v2/client.ts +++ b/src/v2/client.ts @@ -1,3 +1,4 @@ +import { MindeeHttpErrorV2 } from "@/v2/http/index.js"; import { setTimeout } from "node:timers/promises"; import { Dispatcher } from "undici"; @@ -67,7 +68,7 @@ export class Client { } /** - * Enqueues a product inference job without waiting for completion. + * Send a file to the asynchronous processing queue for inference. */ async enqueue

( product: P, @@ -97,45 +98,59 @@ export class Client { } /** - * Retrieves the result of a previously enqueued request. + * Get the status of an inference that was previously enqueued. + * Can be used for polling. + * + * @param pollingUrl The URL to poll to retrieve the job. + * @typeParam T an extension of an `Inference`. Can be omitted as it will be inferred from the `productClass`. + * @returns a `Promise` containing a `Job`, which also contains a `Document` if the + * parsing is complete. + */ + async getJobByUrl(pollingUrl: string): Promise { + logger.debug(`Getting Job by URL: ${pollingUrl}`); + return await this.mindeeApi.reqGetJobByUrl(pollingUrl); + } + + /** + * Get the result of an inference that was previously enqueued. * * @param product the product to retrieve. - * @param inferenceId id of the queue to poll. + * @param url URL as given in the `getJob()` response. * @typeParam T an extension of an `Inference`. Can be omitted as it will be inferred from the `productClass`. * @returns a `Promise` containing the inference. */ - async getResult

( + async getResultByUrl

( product: P, - inferenceId: string + url: string ): Promise> { logger.debug( - `Attempting to get inference with ID: ${inferenceId} using response type: ${product.name}` + `Attempting to get inference from: ${url} using response type: ${product.name}` ); - return await this.mindeeApi.reqGetProductResultById(product, inferenceId); + return await this.mindeeApi.reqGetResultByUrl(product, url); } /** - * Retrieves the result of a previously enqueued request. - * This method is used when manually polling the server for the result URL. + * Get the result of an inference that was previously enqueued. + * Prefer using `getResultByUrl` using the result URL returned in the Job instance. * * @param product the product to retrieve. - * @param url URL as given in the `getJob()` response. + * @param inferenceId id of the queue to poll. * @typeParam T an extension of an `Inference`. Can be omitted as it will be inferred from the `productClass`. * @returns a `Promise` containing the inference. */ - async getResultByUrl

( + async getResult

( product: P, - url: string + inferenceId: string ): Promise> { logger.debug( - `Attempting to get inference from: ${url} using response type: ${product.name}` + `Attempting to get inference with ID: ${inferenceId} using response type: ${product.name}` ); - return await this.mindeeApi.reqGetProductResultByUrl(product, url); + return await this.mindeeApi.reqGetResultById(product, inferenceId); } /** - * Get the processing status of a previously enqueued request. - * Can be used for polling. + * Get the status of an inference that was previously enqueued. + * Prefer using `getJobByUrl` using the polling URL returned by the enqueue method. * * @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`. @@ -148,15 +163,12 @@ export class Client { } /** - * Enqueue a request and poll the server until the result is sent or - * until the maximum number of tries is reached. + * Send a file to the inference queue and return the results when complete. * * @param product the product to retrieve. * @param inputSource file or URL to parse. * @param params parameters relating to prediction options. - * * @param pollingOptions options for the polling loop, see {@link PollingOptions}. - * @typeParam T an extension of an `Inference`. Can be omitted as it will be inferred from the `productClass`. * @returns a `Promise` containing parsing results. */ async enqueueAndGetResult

( @@ -175,7 +187,7 @@ export class Client { const pollingOptionsInstance = pollingOptions instanceof PollingOptions ? pollingOptions : new PollingOptions(pollingOptions); - return await this.pollForResult( + return await this.pollForProductResult( product, pollingOptionsInstance, jobResponse ); } @@ -217,12 +229,15 @@ export class Client { ); let tryCounter = 0; - while (tryCounter < pollingOptions.maxRetries) { + const maxRetries = pollingOptions.maxRetries; + const pollingUrl = initialResponse.job.pollingUrl; + + while (tryCounter < maxRetries) { logger.debug( - `Poll attempt ${tryCounter + 1} of ${pollingOptions.maxRetries}` + `Poll attempt ${tryCounter + 1} of ${maxRetries}` ); - const jobResponse = await this.getJob(initialResponse.job.id); + const jobResponse = await this.getJobByUrl(pollingUrl); if (jobResponse.job.status === "Processed") { logger.debug( @@ -233,16 +248,13 @@ export class Client { } } + // normally the API handler will throw an error, this is a fallback if (jobResponse.job.status === "Failed") { - let detail = "No error detail available."; if (jobResponse.job.error?.detail) { - detail = typeof jobResponse.job.error.detail === "string" - ? jobResponse.job.error.detail - : JSON.stringify(jobResponse.job.error.detail); + throw new MindeeHttpErrorV2(jobResponse.job.error); } - throw new MindeeError( - `Parsing failed for job ${jobResponse.job.id}: ${detail}` + `Parsing failed for job ${jobResponse.job.id}: No error detail available.` ); } @@ -264,7 +276,7 @@ export class Client { * Poll until the inference is finished processing or the max number of attempts is reached. * @protected */ - protected async pollForResult

( + protected async pollForProductResult

( product: P, pollingOptions: PollingOptions, initialResponse: JobResponse, @@ -474,7 +486,6 @@ export class Client { pollingOptions: PollingOptions ): Promise> { logger.debug(`Polling for RAG document ID: ${initialResponse.id}`); - const maxRetries = pollingOptions.maxRetries + 1; logger.debug( `Waiting ${pollingOptions.initialDelaySec} seconds before attempting to retrieve the document...` @@ -485,17 +496,18 @@ export class Client { pollingOptions.initialTimerOptions ); + let tryCounter = 0; const documentId = initialResponse.id; - let retryCount = 1; + const maxRetries = pollingOptions.maxRetries; - while (retryCount < maxRetries) { + while (tryCounter < maxRetries) { logger.debug( - `Poll attempt ${retryCount} of ${pollingOptions.maxRetries}` + `Poll attempt ${tryCounter + 1} of ${pollingOptions.maxRetries}` ); const response = await this.getRagDocument(product, documentId); - retryCount++; + tryCounter++; switch (response.status) { case "Processing": @@ -512,6 +524,6 @@ export class Client { } } - throw new MindeeError(`RAG polling not complete after ${retryCount} attempts.`); + throw new MindeeError(`RAG polling not complete after ${tryCounter} attempts.`); } } diff --git a/src/v2/http/mindeeApiV2.ts b/src/v2/http/mindeeApiV2.ts index 7c1e76ac..b956e31e 100644 --- a/src/v2/http/mindeeApiV2.ts +++ b/src/v2/http/mindeeApiV2.ts @@ -105,11 +105,11 @@ export class MindeeApiV2 { * @param inferenceId The inference ID for the result. * @returns a `Promise` containing the parsed result. */ - async reqGetProductResultById

( + async reqGetResultById

( product: P, inferenceId: string, ): Promise> { - return this.reqGetProductResultByUrl( + return this.reqGetResultByUrl( product, `https://${this.settings.hostname}/v2/products/${product.slug}/results/${inferenceId}` ); @@ -122,7 +122,7 @@ export class MindeeApiV2 { * @param url The URL as returned by a Job's resultUrl property. * @returns a `Promise` containing the parsed result. */ - async reqGetProductResultByUrl

( + async reqGetResultByUrl

( product: P, url: string, ): Promise> { diff --git a/tests/v2/client/client.spec.ts b/tests/v2/client/client.spec.ts index ecbac638..db2b5c12 100644 --- a/tests/v2/client/client.spec.ts +++ b/tests/v2/client/client.spec.ts @@ -174,4 +174,25 @@ describe("MindeeV2 - Client", () => { assert.strictEqual(job.error, undefined); }); }); + + describe("getJobByUrl(url)", () => { + it("returns a fully-formed JobResponse", async () => { + const resp = await client.getJobByUrl( + "https://v2-client-host/v2/jobs/12345678-1234-1234-1234-123456789ABC" + ); + const job = resp.job; + assert.strictEqual(job.id, "12345678-1234-1234-1234-123456789ABC"); + assert.strictEqual(job.modelId, "87654321-4321-4321-4321-CBA987654321"); + assert.strictEqual(job.filename, "default_sample.jpg"); + assert.strictEqual(job.alias, "dummy-alias.jpg"); + assert.strictEqual(job.createdAt?.toISOString(), "2025-07-03T14:27:58.974Z"); + assert.strictEqual(job.status, "Processing"); + assert.strictEqual(job.pollingUrl, + "https://api-v2.mindee.net/v2/jobs/12345678-1234-1234-1234-123456789ABC" + ); + assert.strictEqual(job.resultUrl, null); + assert.strictEqual(job.webhooks.length, 0); + assert.strictEqual(job.error, undefined); + }); + }); });