├── .gitattributes ├── .github └── workflows │ ├── ci.yaml │ ├── speakeasy_sdk_generation.yml │ └── speakeasy_sdk_publish.yaml ├── .gitignore ├── .npmignore ├── .speakeasy ├── gen.lock ├── workflow.lock └── workflow.yaml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── FUNCTIONS.md ├── LICENSE.md ├── Makefile ├── README.md ├── RELEASES.md ├── RUNTIMES.md ├── USAGE.md ├── codeSamples.yaml ├── docs ├── lib │ └── utils │ │ └── retryconfig.md ├── sdk │ └── models │ │ ├── errors │ │ ├── detail.md │ │ ├── httpvalidationerror.md │ │ └── servererror.md │ │ ├── operations │ │ ├── partitionrequest.md │ │ └── partitionresponse.md │ │ └── shared │ │ ├── files.md │ │ ├── loc.md │ │ ├── outputformat.md │ │ ├── partitionparameters.md │ │ ├── security.md │ │ ├── strategy.md │ │ ├── validationerror.md │ │ ├── vlmmodel.md │ │ └── vlmmodelprovider.md └── sdks │ ├── general │ └── README.md │ └── unstructuredclient │ └── README.md ├── eslint.config.mjs ├── files.gen ├── gen.yaml ├── jsr.json ├── overlay_client.yaml ├── package-lock.json ├── package.json ├── src ├── core.ts ├── funcs │ └── generalPartition.ts ├── hooks │ ├── custom │ │ ├── FixArrayParamsHook.ts │ │ ├── HttpsCheckHook.ts │ │ ├── LoggerHook.ts │ │ ├── SplitPdfHook.ts │ │ ├── common.ts │ │ └── utils │ │ │ ├── form.ts │ │ │ ├── general.ts │ │ │ ├── index.ts │ │ │ ├── pdf.ts │ │ │ └── request.ts │ ├── hooks.ts │ ├── index.ts │ ├── registration.ts │ └── types.ts ├── index.ts ├── lib │ ├── base64.ts │ ├── config.ts │ ├── dlv.ts │ ├── encodings.ts │ ├── files.ts │ ├── http.ts │ ├── is-plain-object.ts │ ├── logger.ts │ ├── matchers.ts │ ├── primitives.ts │ ├── retries.ts │ ├── schemas.ts │ ├── sdks.ts │ ├── security.ts │ └── url.ts ├── mcp-server │ ├── build.mts │ ├── cli.ts │ ├── cli │ │ └── start │ │ │ ├── command.ts │ │ │ └── impl.ts │ ├── console-logger.ts │ ├── extensions.ts │ ├── mcp-server.ts │ ├── prompts.ts │ ├── resources.ts │ ├── scopes.ts │ ├── server.extensions.ts │ ├── server.ts │ ├── shared.ts │ ├── tools.ts │ └── tools │ │ └── generalPartition.ts └── sdk │ ├── general.ts │ ├── index.ts │ ├── models │ ├── errors │ │ ├── httpclienterrors.ts │ │ ├── httpvalidationerror.ts │ │ ├── index.ts │ │ ├── sdkerror.ts │ │ ├── sdkvalidationerror.ts │ │ └── servererror.ts │ ├── operations │ │ ├── index.ts │ │ └── partition.ts │ └── shared │ │ ├── index.ts │ │ ├── partitionparameters.ts │ │ ├── security.ts │ │ └── validationerror.ts │ ├── sdk.ts │ └── types │ ├── async.ts │ ├── blobs.ts │ ├── constdatetime.ts │ ├── enums.ts │ ├── fp.ts │ ├── index.ts │ ├── operations.ts │ ├── rfcdate.ts │ └── streams.ts ├── test ├── data │ ├── fake.doc │ ├── layout-parser-paper-fast.pdf │ ├── layout-parser-paper.pdf │ └── list-item-example-1.pdf ├── integration │ ├── HttpsCheckHook.test.ts │ └── SplitPdfHook.test.ts └── unit │ ├── FixArrayParamsHook.test.ts │ ├── HttpsCheckHook.test.ts │ └── utils │ └── pdf.test.ts ├── tsconfig.json └── vitest.config.mjs /.gitattributes: -------------------------------------------------------------------------------- 1 | # This allows generated code to be indexed correctly 2 | *.ts linguist-generated=false -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | merge_group: 9 | branches: [ main ] 10 | 11 | permissions: 12 | id-token: write 13 | contents: read 14 | 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | test_unit: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Setup node 25 | uses: actions/setup-node@v3 26 | with: 27 | cache: npm 28 | cache-dependency-path: ./package.json 29 | node-version: 18 30 | - name: Install dependencies 31 | run: make install 32 | - name: Build dependencies 33 | run: make build 34 | - name: Run linter 35 | run: make check 36 | - name: Run unit tests 37 | run: make test-unit 38 | 39 | 40 | test_integration: 41 | runs-on: ubuntu-latest 42 | services: 43 | unstructured-api: 44 | image: quay.io/unstructured-io/unstructured-api:latest 45 | ports: 46 | - 8000:8000 47 | steps: 48 | - uses: actions/checkout@v4 49 | - name: Setup node 50 | uses: actions/setup-node@v3 51 | with: 52 | cache: npm 53 | cache-dependency-path: ./package.json 54 | node-version: 18 55 | - name: Install dependencies 56 | run: make install 57 | - name: Build dependencies 58 | run: make build 59 | - name: Run integration tests 60 | run: make test-integration 61 | -------------------------------------------------------------------------------- /.github/workflows/speakeasy_sdk_generation.yml: -------------------------------------------------------------------------------- 1 | name: Generate 2 | permissions: 3 | checks: write 4 | contents: write 5 | pull-requests: write 6 | statuses: write 7 | "on": 8 | workflow_dispatch: 9 | inputs: 10 | force: 11 | description: Force generation of SDKs 12 | type: boolean 13 | default: false 14 | schedule: 15 | - cron: 0 0 * * * 16 | jobs: 17 | generate: 18 | uses: speakeasy-api/sdk-generation-action/.github/workflows/workflow-executor.yaml@v15 19 | with: 20 | force: ${{ github.event.inputs.force }} 21 | mode: pr 22 | speakeasy_version: latest 23 | secrets: 24 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 25 | npm_token: ${{ secrets.NPM_TOKEN }} 26 | speakeasy_api_key: ${{ secrets.SPEAKEASY_API_KEY }} 27 | -------------------------------------------------------------------------------- /.github/workflows/speakeasy_sdk_publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | "on": 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - RELEASES.md 8 | jobs: 9 | publish: 10 | uses: speakeasy-api/sdk-generation-action/.github/workflows/sdk-publish.yaml@v15 11 | secrets: 12 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 13 | npm_token: ${{ secrets.NPM_TOKEN }} 14 | speakeasy_api_key: ${{ secrets.SPEAKEASY_API_KEY }} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **/.speakeasy/temp/ 3 | **/.speakeasy/logs/ 4 | .DS_Store 5 | /mcp-server 6 | /bin 7 | /.eslintcache 8 | /react-query 9 | /.speakeasy/reports 10 | /__tests__ 11 | /funcs 12 | /core.* 13 | /esm 14 | /dist 15 | /.tshy 16 | /.tshy-* 17 | /models 18 | /sdk/models/errors 19 | /sdk/types 20 | /lib 21 | /sdk 22 | /hooks 23 | /index.* 24 | /cjs 25 | /node_modules 26 | /.tsbuildinfo 27 | dist/ 28 | node_modules/ 29 | # Custom ignores 30 | openapi.json 31 | openapi_client.json 32 | .idea/ 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !/FUNCTIONS.md 3 | !/RUNTIMES.md 4 | !/REACT_QUERY.md 5 | !/**/*.ts 6 | !/**/*.js 7 | !/**/*.mjs 8 | !/**/*.json 9 | !/**/*.map 10 | 11 | /eslint.config.mjs 12 | /cjs 13 | /.tshy 14 | /.tshy-* 15 | /__tests__ 16 | -------------------------------------------------------------------------------- /.speakeasy/gen.lock: -------------------------------------------------------------------------------- 1 | lockVersion: 2.0.0 2 | id: f42cb8e6-e2ce-4565-b975-5a9f38b94d5a 3 | management: 4 | docChecksum: 3e42d77bf491e4e7a999ec6d27d41fd1 5 | docVersion: 1.1.17 6 | speakeasyVersion: 1.552.0 7 | generationVersion: 2.610.0 8 | releaseVersion: 0.24.4 9 | configChecksum: c0e0d6524bed361c1a8ebeda0e6333df 10 | repoURL: https://github.com/Unstructured-IO/unstructured-js-client.git 11 | repoSubDirectory: . 12 | installationURL: https://github.com/Unstructured-IO/unstructured-js-client 13 | published: true 14 | features: 15 | typescript: 16 | acceptHeaders: 2.81.2 17 | additionalDependencies: 0.1.0 18 | constsAndDefaults: 0.1.11 19 | core: 3.21.9 20 | defaultEnabledRetries: 0.1.0 21 | enumUnions: 0.1.0 22 | envVarSecurityUsage: 0.1.2 23 | examples: 2.81.5 24 | globalSecurity: 2.82.13 25 | globalSecurityCallbacks: 0.1.0 26 | globalServerURLs: 2.82.5 27 | mcpServer: 0.9.2 28 | nameOverrides: 2.81.2 29 | nullables: 0.1.1 30 | openEnums: 0.1.1 31 | responseFormat: 0.2.3 32 | retries: 2.83.0 33 | sdkHooks: 0.2.0 34 | serverIDs: 2.81.2 35 | unions: 2.85.8 36 | uploadStreams: 0.1.0 37 | generatedFiles: 38 | - .gitattributes 39 | - .npmignore 40 | - FUNCTIONS.md 41 | - RUNTIMES.md 42 | - USAGE.md 43 | - docs/lib/utils/retryconfig.md 44 | - docs/sdk/models/errors/detail.md 45 | - docs/sdk/models/errors/httpvalidationerror.md 46 | - docs/sdk/models/errors/servererror.md 47 | - docs/sdk/models/operations/partitionrequest.md 48 | - docs/sdk/models/operations/partitionresponse.md 49 | - docs/sdk/models/shared/files.md 50 | - docs/sdk/models/shared/loc.md 51 | - docs/sdk/models/shared/outputformat.md 52 | - docs/sdk/models/shared/partitionparameters.md 53 | - docs/sdk/models/shared/security.md 54 | - docs/sdk/models/shared/strategy.md 55 | - docs/sdk/models/shared/validationerror.md 56 | - docs/sdk/models/shared/vlmmodel.md 57 | - docs/sdk/models/shared/vlmmodelprovider.md 58 | - docs/sdks/general/README.md 59 | - docs/sdks/unstructuredclient/README.md 60 | - eslint.config.mjs 61 | - jsr.json 62 | - package.json 63 | - src/core.ts 64 | - src/funcs/generalPartition.ts 65 | - src/hooks/hooks.ts 66 | - src/hooks/index.ts 67 | - src/hooks/types.ts 68 | - src/index.ts 69 | - src/lib/base64.ts 70 | - src/lib/config.ts 71 | - src/lib/dlv.ts 72 | - src/lib/encodings.ts 73 | - src/lib/files.ts 74 | - src/lib/http.ts 75 | - src/lib/is-plain-object.ts 76 | - src/lib/logger.ts 77 | - src/lib/matchers.ts 78 | - src/lib/primitives.ts 79 | - src/lib/retries.ts 80 | - src/lib/schemas.ts 81 | - src/lib/sdks.ts 82 | - src/lib/security.ts 83 | - src/lib/url.ts 84 | - src/mcp-server/build.mts 85 | - src/mcp-server/cli.ts 86 | - src/mcp-server/cli/start/command.ts 87 | - src/mcp-server/cli/start/impl.ts 88 | - src/mcp-server/console-logger.ts 89 | - src/mcp-server/extensions.ts 90 | - src/mcp-server/mcp-server.ts 91 | - src/mcp-server/prompts.ts 92 | - src/mcp-server/resources.ts 93 | - src/mcp-server/scopes.ts 94 | - src/mcp-server/server.ts 95 | - src/mcp-server/shared.ts 96 | - src/mcp-server/tools.ts 97 | - src/mcp-server/tools/generalPartition.ts 98 | - src/sdk/general.ts 99 | - src/sdk/index.ts 100 | - src/sdk/models/errors/httpclienterrors.ts 101 | - src/sdk/models/errors/httpvalidationerror.ts 102 | - src/sdk/models/errors/index.ts 103 | - src/sdk/models/errors/sdkerror.ts 104 | - src/sdk/models/errors/sdkvalidationerror.ts 105 | - src/sdk/models/errors/servererror.ts 106 | - src/sdk/models/operations/index.ts 107 | - src/sdk/models/operations/partition.ts 108 | - src/sdk/models/shared/index.ts 109 | - src/sdk/models/shared/partitionparameters.ts 110 | - src/sdk/models/shared/security.ts 111 | - src/sdk/models/shared/validationerror.ts 112 | - src/sdk/sdk.ts 113 | - src/sdk/types/async.ts 114 | - src/sdk/types/blobs.ts 115 | - src/sdk/types/constdatetime.ts 116 | - src/sdk/types/enums.ts 117 | - src/sdk/types/fp.ts 118 | - src/sdk/types/index.ts 119 | - src/sdk/types/operations.ts 120 | - src/sdk/types/rfcdate.ts 121 | - src/sdk/types/streams.ts 122 | - tsconfig.json 123 | examples: 124 | partition: 125 | speakeasy-default-partition: 126 | requestBody: 127 | multipart/form-data: {"chunking_strategy": "by_title", "coordinates": false, "files": {"": "{\"summary\":\"File to be partitioned\",\"externalValue\":\"https://github.com/Unstructured-IO/unstructured/blob/98d3541909f64290b5efb65a226fc3ee8a7cc5ee/example-docs/layout-parser-paper.pdf\"}"}, "include_page_breaks": false, "include_slide_notes": true, "multipage_sections": true, "output_format": "application/json", "overlap": 0, "overlap_all": false, "pdf_infer_table_structure": true, "split_pdf_allow_failed": false, "split_pdf_concurrency_level": 5, "split_pdf_page": true, "split_pdf_page_range": [1, 10], "strategy": "auto", "unique_element_ids": false, "vlm_model": "gpt-4o", "vlm_model_provider": "openai", "xml_keep_tags": false} 128 | responses: 129 | "200": 130 | application/json: [{"type": "Title", "element_id": "6aa0ff22f91bbe7e26e8e25ca8052acd", "text": "LayoutParser: A Unified Toolkit for Deep Learning Based Document Image Analysis", "metadata": {"languages": ["eng"], "page_number": 1, "filename": "layout-parser-paper.pdf", "filetype": "application/pdf"}}] 131 | text/csv: "" 132 | "422": 133 | application/json: {"detail": ""} 134 | 5XX: 135 | application/json: {"detail": "An error occurred"} 136 | examplesVersion: 1.0.2 137 | generatedTests: {} 138 | -------------------------------------------------------------------------------- /.speakeasy/workflow.lock: -------------------------------------------------------------------------------- 1 | speakeasyVersion: 1.552.0 2 | sources: 3 | my-source: 4 | sourceNamespace: my-source 5 | sourceRevisionDigest: sha256:11dc672fb20011d88c36c9e88092258b76ffe07dbf7d4d9493a4a8a63de97e0b 6 | sourceBlobDigest: sha256:d70162f1aa06e4b75fdb38608d8ca25948651246e8da161f0947af385732aff3 7 | tags: 8 | - latest 9 | - speakeasy-sdk-regen-1748046653 10 | - 1.1.17 11 | targets: 12 | unstructed-typescript: 13 | source: my-source 14 | sourceNamespace: my-source 15 | sourceRevisionDigest: sha256:11dc672fb20011d88c36c9e88092258b76ffe07dbf7d4d9493a4a8a63de97e0b 16 | sourceBlobDigest: sha256:d70162f1aa06e4b75fdb38608d8ca25948651246e8da161f0947af385732aff3 17 | codeSamplesNamespace: my-source-typescript-code-samples 18 | codeSamplesRevisionDigest: sha256:116507a44839dec1f86e45a1a782df522e4ff29c592635c14f2f5e8196ec91d7 19 | workflow: 20 | workflowVersion: 1.0.0 21 | speakeasyVersion: latest 22 | sources: 23 | my-source: 24 | inputs: 25 | - location: https://api.unstructuredapp.io/general/openapi.json 26 | overlays: 27 | - location: ./overlay_client.yaml 28 | registry: 29 | location: registry.speakeasyapi.dev/unstructured/unstructured5xr/my-source 30 | targets: 31 | unstructed-typescript: 32 | target: typescript 33 | source: my-source 34 | publish: 35 | npm: 36 | token: $NPM_TOKEN 37 | codeSamples: 38 | output: codeSamples.yaml 39 | registry: 40 | location: registry.speakeasyapi.dev/unstructured/unstructured5xr/my-source-typescript-code-samples 41 | -------------------------------------------------------------------------------- /.speakeasy/workflow.yaml: -------------------------------------------------------------------------------- 1 | workflowVersion: 1.0.0 2 | speakeasyVersion: latest 3 | sources: 4 | my-source: 5 | inputs: 6 | - location: https://api.unstructuredapp.io/general/openapi.json 7 | overlays: 8 | - location: ./overlay_client.yaml 9 | registry: 10 | location: registry.speakeasyapi.dev/unstructured/unstructured5xr/my-source 11 | targets: 12 | unstructed-typescript: 13 | target: typescript 14 | source: my-source 15 | publish: 16 | npm: 17 | token: $NPM_TOKEN 18 | codeSamples: 19 | output: codeSamples.yaml 20 | registry: 21 | location: registry.speakeasyapi.dev/unstructured/unstructured5xr/my-source-typescript-code-samples 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.17.0 2 | 3 | ### Fixes 4 | Improves retry mechanisms for splitting logic for PDFs to be more resilient to intermittent failures. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to This Repository 2 | 3 | Thank you for your interest in contributing to this repository. Please note that this repository contains generated code. As such, we do not accept direct changes or pull requests. Instead, we encourage you to follow the guidelines below to report issues and suggest improvements. 4 | 5 | ## How to Report Issues 6 | 7 | If you encounter any bugs or have suggestions for improvements, please open an issue on GitHub. When reporting an issue, please provide as much detail as possible to help us reproduce the problem. This includes: 8 | 9 | - A clear and descriptive title 10 | - Steps to reproduce the issue 11 | - Expected and actual behavior 12 | - Any relevant logs, screenshots, or error messages 13 | - Information about your environment (e.g., operating system, software versions) 14 | - For example can be collected using the `npx envinfo` command from your terminal if you have Node.js installed 15 | 16 | ## Issue Triage and Upstream Fixes 17 | 18 | We will review and triage issues as quickly as possible. Our goal is to address bugs and incorporate improvements in the upstream source code. Fixes will be included in the next generation of the generated code. 19 | 20 | ## Contact 21 | 22 | If you have any questions or need further assistance, please feel free to reach out by opening an issue. 23 | 24 | Thank you for your understanding and cooperation! 25 | 26 | The Maintainers 27 | -------------------------------------------------------------------------------- /FUNCTIONS.md: -------------------------------------------------------------------------------- 1 | # Standalone Functions 2 | 3 | > [!NOTE] 4 | > This section is useful if you are using a bundler and targetting browsers and 5 | > runtimes where the size of an application affects performance and load times. 6 | 7 | Every method in this SDK is also available as a standalone function. This 8 | alternative API is suitable when targetting the browser or serverless runtimes 9 | and using a bundler to build your application since all unused functionality 10 | will be tree-shaken away. This includes code for unused methods, Zod schemas, 11 | encoding helpers and response handlers. The result is dramatically smaller 12 | impact on the application's final bundle size which grows very slowly as you use 13 | more and more functionality from this SDK. 14 | 15 | Calling methods through the main SDK class remains a valid and generally more 16 | more ergonomic option. Standalone functions represent an optimisation for a 17 | specific category of applications. 18 | 19 | ## Example 20 | 21 | ```typescript 22 | import { openAsBlob } from "node:fs"; 23 | import { UnstructuredClientCore } from "unstructured-client/core.js"; 24 | import { generalPartition } from "unstructured-client/funcs/generalPartition.js"; 25 | import { SDKValidationError } from "unstructured-client/sdk/models/errors/sdkvalidationerror.js"; 26 | import { VLMModel, VLMModelProvider } from "unstructured-client/sdk/models/shared"; 27 | 28 | // Use `UnstructuredClientCore` for best tree-shaking performance. 29 | // You can create one instance of it to use across an application. 30 | const unstructuredClient = new UnstructuredClientCore(); 31 | 32 | async function run() { 33 | const res = await generalPartition(unstructuredClient, { 34 | partitionParameters: { 35 | chunkingStrategy: "by_title", 36 | files: await openAsBlob("example.file"), 37 | splitPdfPageRange: [ 38 | 1, 39 | 10, 40 | ], 41 | vlmModel: VLMModel.Gpt4o, 42 | vlmModelProvider: VLMModelProvider.Openai, 43 | }, 44 | }); 45 | 46 | switch (true) { 47 | case res.ok: 48 | // The success case will be handled outside of the switch block 49 | break; 50 | case res.error instanceof SDKValidationError: 51 | // Pretty-print validation errors. 52 | return console.log(res.error.pretty()); 53 | case res.error instanceof Error: 54 | return console.log(res.error); 55 | default: 56 | // TypeScript's type checking will fail on the following line if the above 57 | // cases were not exhaustive. 58 | res.error satisfies never; 59 | throw new Error("Assertion failed: expected error checks to be exhaustive: " + res.error); 60 | } 61 | 62 | 63 | const { value: result } = res; 64 | 65 | // Handle the result 66 | console.log(result); 67 | } 68 | 69 | run(); 70 | ``` 71 | 72 | ## Result types 73 | 74 | Standalone functions differ from SDK methods in that they return a 75 | `Result` type to capture _known errors_ and document them using 76 | the type system. By avoiding throwing errors, application code maintains clear 77 | control flow and error-handling become part of the regular flow of application 78 | code. 79 | 80 | > We use the term "known errors" because standalone functions, and JavaScript 81 | > code in general, can still throw unexpected errors such as `TypeError`s, 82 | > `RangeError`s and `DOMException`s. Exhaustively catching all errors may be 83 | > something this SDK addresses in the future. Nevertheless, there is still a lot 84 | > of benefit from capturing most errors and turning them into values. 85 | 86 | The second reason for this style of programming is because these functions will 87 | typically be used in front-end applications where exception throwing is 88 | sometimes discouraged or considered unidiomatic. React and similar ecosystems 89 | and libraries tend to promote this style of programming so that components 90 | render useful content under all states (loading, success, error and so on). 91 | 92 | The general pattern when calling standalone functions looks like this: 93 | 94 | ```typescript 95 | import { Core } from ""; 96 | import { fetchSomething } from "/funcs/fetchSomething.js"; 97 | 98 | const client = new Core(); 99 | 100 | async function run() { 101 | const result = await fetchSomething(client, { id: "123" }); 102 | if (!result.ok) { 103 | // You can throw the error or handle it. It's your choice now. 104 | throw result.error; 105 | } 106 | 107 | console.log(result.value); 108 | } 109 | 110 | run(); 111 | ``` 112 | 113 | Notably, `result.error` above will have an explicit type compared to a try-catch 114 | variation where the error in the catch block can only be of type `unknown` (or 115 | `any` depending on your TypeScript settings). -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Unstructured-IO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE_NAME := unstructured-js-client 2 | CURRENT_DIR := $(shell pwd) 3 | ARCH := $(shell uname -m) 4 | DOCKER_IMAGE ?= quay.io/unstructured-io/unstructured-api:latest 5 | OPENAPI_DOCS_URL ?= https://api.unstructuredapp.io/general/openapi.json 6 | 7 | ########### 8 | # Install # 9 | ########### 10 | 11 | ## install: installs all modules 12 | .PHONY: install 13 | install: 14 | npm install 15 | 16 | ########### 17 | # Build # 18 | ########### 19 | 20 | ## build: build client 21 | .PHONY: build 22 | build: 23 | npm run build 24 | 25 | ## check: lint the client 26 | .PHONY: check 27 | check: 28 | npx eslint src/ 29 | 30 | ############# 31 | # Test # 32 | ############# 33 | 34 | ## test-unit: run unit tests 35 | .PHONY: test-unit 36 | test-unit: 37 | npx vitest --dir test/unit --run --reporter verbose --config vitest.config.mjs 38 | 39 | ## test-integration: run integration tests 40 | .PHONY: test-integration 41 | test-integration: 42 | npx vitest --dir test/integration --run --reporter verbose --config vitest.config.mjs 43 | 44 | ## test: run all tests 45 | .PHONY: test 46 | test: test-unit test-integration 47 | 48 | ############# 49 | # Speakeasy # 50 | ############# 51 | 52 | ## client-generate: generate speakeasy client locally 53 | .PHONY: client-generate 54 | client-generate: 55 | wget -nv -q -O openapi.json ${OPENAPI_DOCS_URL} 56 | speakeasy overlay validate -o ./overlay_client.yaml 57 | speakeasy overlay apply -s ./openapi.json -o ./overlay_client.yaml > ./openapi_client.json 58 | speakeasy generate sdk -s ./openapi_client.json -o ./ -l typescript 59 | -------------------------------------------------------------------------------- /RUNTIMES.md: -------------------------------------------------------------------------------- 1 | # Supported JavaScript runtimes 2 | 3 | This SDK is intended to be used in JavaScript runtimes that support ECMAScript 2020 or newer. The SDK uses the following features: 4 | 5 | * [Web Fetch API][web-fetch] 6 | * [Web Streams API][web-streams] and in particular `ReadableStream` 7 | * [Async iterables][async-iter] using `Symbol.asyncIterator` 8 | 9 | [web-fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API 10 | [web-streams]: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API 11 | [async-iter]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols 12 | 13 | Runtime environments that are explicitly supported are: 14 | 15 | - Evergreen browsers which include: Chrome, Safari, Edge, Firefox 16 | - Node.js active and maintenance LTS releases 17 | - Currently, this is v18 and v20 18 | - Bun v1 and above 19 | - Deno v1.39 20 | - Note that Deno does not currently have native support for streaming file uploads backed by the filesystem ([issue link][deno-file-streaming]) 21 | 22 | [deno-file-streaming]: https://github.com/denoland/deno/issues/11018 23 | 24 | ## Recommended TypeScript compiler options 25 | 26 | The following `tsconfig.json` options are recommended for projects using this 27 | SDK in order to get static type support for features like async iterables, 28 | streams and `fetch`-related APIs ([`for await...of`][for-await-of], 29 | [`AbortSignal`][abort-signal], [`Request`][request], [`Response`][response] and 30 | so on): 31 | 32 | [for-await-of]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of 33 | [abort-signal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal 34 | [request]: https://developer.mozilla.org/en-US/docs/Web/API/Request 35 | [response]: https://developer.mozilla.org/en-US/docs/Web/API/Response 36 | 37 | ```jsonc 38 | { 39 | "compilerOptions": { 40 | "target": "es2020", // or higher 41 | "lib": ["es2020", "dom", "dom.iterable"], 42 | } 43 | } 44 | ``` 45 | 46 | While `target` can be set to older ECMAScript versions, it may result in extra, 47 | unnecessary compatibility code being generated if you are not targeting old 48 | runtimes. -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | 2 | ```typescript 3 | import { openAsBlob } from "node:fs"; 4 | import { UnstructuredClient } from "unstructured-client"; 5 | import { 6 | VLMModel, 7 | VLMModelProvider, 8 | } from "unstructured-client/sdk/models/shared"; 9 | 10 | const unstructuredClient = new UnstructuredClient(); 11 | 12 | async function run() { 13 | const result = await unstructuredClient.general.partition({ 14 | partitionParameters: { 15 | chunkingStrategy: "by_title", 16 | files: await openAsBlob("example.file"), 17 | splitPdfPageRange: [ 18 | 1, 19 | 10, 20 | ], 21 | vlmModel: VLMModel.Gpt4o, 22 | vlmModelProvider: VLMModelProvider.Openai, 23 | }, 24 | }); 25 | 26 | // Handle the result 27 | console.log(result); 28 | } 29 | 30 | run(); 31 | 32 | ``` 33 | -------------------------------------------------------------------------------- /codeSamples.yaml: -------------------------------------------------------------------------------- 1 | overlay: 1.0.0 2 | info: 3 | title: CodeSamples overlay for typescript target 4 | version: 0.0.0 5 | actions: 6 | - target: $["paths"]["/general/v0/general"]["post"] 7 | update: 8 | "x-codeSamples": 9 | - "lang": "typescript" 10 | "label": "partition" 11 | "source": "import { openAsBlob } from \"node:fs\";\nimport { UnstructuredClient } from \"unstructured-client\";\nimport { VLMModel, VLMModelProvider } from \"unstructured-client/sdk/models/shared\";\n\nconst unstructuredClient = new UnstructuredClient();\n\nasync function run() {\n const result = await unstructuredClient.general.partition({\n partitionParameters: {\n files: await openAsBlob(\"example.file\"),\n vlmModelProvider: VLMModelProvider.Openai,\n vlmModel: VLMModel.Gpt4o,\n chunkingStrategy: \"by_title\",\n splitPdfPageRange: [\n 1,\n 10,\n ],\n },\n });\n\n // Handle the result\n console.log(result);\n}\n\nrun();" 12 | -------------------------------------------------------------------------------- /docs/lib/utils/retryconfig.md: -------------------------------------------------------------------------------- 1 | # RetryConfig 2 | 3 | Allows customizing the default retry configuration. It is only permitted in methods that accept retry policies. 4 | 5 | ## Fields 6 | 7 | | Name | Type | Description | Example | 8 | | ------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | ----------- | 9 | | `strategy` | `"backoff" | "none"` | The retry strategy to use. | `"backoff"` | 10 | | `backoff` | [BackoffStrategy](#backoffstrategy) | When strategy is "backoff", this configurates for the backoff parameters. | | 11 | | `retryConnectionErrors` | `*boolean*` | When strategy is "backoff", this determines whether or not to retry on connection errors. | `true` | 12 | 13 | ## BackoffStrategy 14 | 15 | The backoff strategy allows retrying a request with an exponential backoff between each retry. 16 | 17 | ### Fields 18 | 19 | | Name | Type | Description | Example | 20 | | ------------------ | ------------ | ----------------------------------------- | -------- | 21 | | `initialInterval` | `*number*` | The initial interval in milliseconds. | `500` | 22 | | `maxInterval` | `*number*` | The maximum interval in milliseconds. | `60000` | 23 | | `exponent` | `*number*` | The exponent to use for the backoff. | `1.5` | 24 | | `maxElapsedTime` | `*number*` | The maximum elapsed time in milliseconds. | `300000` | -------------------------------------------------------------------------------- /docs/sdk/models/errors/detail.md: -------------------------------------------------------------------------------- 1 | # Detail 2 | 3 | 4 | ## Supported Types 5 | 6 | ### `shared.ValidationError[]` 7 | 8 | ```typescript 9 | const value: shared.ValidationError[] = [ 10 | { 11 | loc: [ 12 | 569227, 13 | ], 14 | msg: "", 15 | type: "", 16 | }, 17 | ]; 18 | ``` 19 | 20 | ### `string` 21 | 22 | ```typescript 23 | const value: string = ""; 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /docs/sdk/models/errors/httpvalidationerror.md: -------------------------------------------------------------------------------- 1 | # HTTPValidationError 2 | 3 | ## Example Usage 4 | 5 | ```typescript 6 | import { HTTPValidationError } from "unstructured-client/sdk/models/errors"; 7 | 8 | // No examples available for this model 9 | ``` 10 | 11 | ## Fields 12 | 13 | | Field | Type | Required | Description | 14 | | ------------------ | ------------------ | ------------------ | ------------------ | 15 | | `detail` | *errors.Detail* | :heavy_minus_sign: | N/A | -------------------------------------------------------------------------------- /docs/sdk/models/errors/servererror.md: -------------------------------------------------------------------------------- 1 | # ServerError 2 | 3 | ## Example Usage 4 | 5 | ```typescript 6 | import { ServerError } from "unstructured-client/sdk/models/errors"; 7 | 8 | // No examples available for this model 9 | ``` 10 | 11 | ## Fields 12 | 13 | | Field | Type | Required | Description | 14 | | ------------------ | ------------------ | ------------------ | ------------------ | 15 | | `detail` | *string* | :heavy_minus_sign: | N/A | -------------------------------------------------------------------------------- /docs/sdk/models/operations/partitionrequest.md: -------------------------------------------------------------------------------- 1 | # PartitionRequest 2 | 3 | ## Example Usage 4 | 5 | ```typescript 6 | import { PartitionRequest } from "unstructured-client/sdk/models/operations"; 7 | 8 | // No examples available for this model 9 | ``` 10 | 11 | ## Fields 12 | 13 | | Field | Type | Required | Description | 14 | | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | 15 | | `partitionParameters` | [shared.PartitionParameters](../../../sdk/models/shared/partitionparameters.md) | :heavy_check_mark: | N/A | 16 | | `unstructuredApiKey` | *string* | :heavy_minus_sign: | N/A | -------------------------------------------------------------------------------- /docs/sdk/models/operations/partitionresponse.md: -------------------------------------------------------------------------------- 1 | # PartitionResponse 2 | 3 | 4 | ## Supported Types 5 | 6 | ### `string` 7 | 8 | ```typescript 9 | const value: string = ""; 10 | ``` 11 | 12 | ### `{ [k: string]: any }[]` 13 | 14 | ```typescript 15 | const value: { [k: string]: any }[] = [ 16 | { 17 | "type": "Title", 18 | "element_id": "6aa0ff22f91bbe7e26e8e25ca8052acd", 19 | "text": 20 | "LayoutParser: A Unified Toolkit for Deep Learning Based Document Image Analysis", 21 | "metadata": { 22 | "languages": [ 23 | "eng", 24 | ], 25 | "page_number": 1, 26 | "filename": "layout-parser-paper.pdf", 27 | "filetype": "application/pdf", 28 | }, 29 | }, 30 | ]; 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /docs/sdk/models/shared/files.md: -------------------------------------------------------------------------------- 1 | # Files 2 | 3 | ## Example Usage 4 | 5 | ```typescript 6 | import { Files } from "unstructured-client/sdk/models/shared"; 7 | 8 | // No examples available for this model 9 | ``` 10 | 11 | ## Fields 12 | 13 | | Field | Type | Required | Description | Example | 14 | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 15 | | `content` | *ReadableStream* | :heavy_check_mark: | N/A | {
"summary": "File to be partitioned",
"externalValue": "https://github.com/Unstructured-IO/unstructured/blob/98d3541909f64290b5efb65a226fc3ee8a7cc5ee/example-docs/layout-parser-paper.pdf"
} | 16 | | `fileName` | *string* | :heavy_check_mark: | N/A | {
"summary": "File to be partitioned",
"externalValue": "https://github.com/Unstructured-IO/unstructured/blob/98d3541909f64290b5efb65a226fc3ee8a7cc5ee/example-docs/layout-parser-paper.pdf"
} | -------------------------------------------------------------------------------- /docs/sdk/models/shared/loc.md: -------------------------------------------------------------------------------- 1 | # Loc 2 | 3 | 4 | ## Supported Types 5 | 6 | ### `string` 7 | 8 | ```typescript 9 | const value: string = ""; 10 | ``` 11 | 12 | ### `number` 13 | 14 | ```typescript 15 | const value: number = 128403; 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- /docs/sdk/models/shared/outputformat.md: -------------------------------------------------------------------------------- 1 | # OutputFormat 2 | 3 | The format of the response. Supported formats are application/json and text/csv. Default: application/json. 4 | 5 | ## Example Usage 6 | 7 | ```typescript 8 | import { OutputFormat } from "unstructured-client/sdk/models/shared"; 9 | 10 | let value: OutputFormat = OutputFormat.ApplicationJson; 11 | ``` 12 | 13 | ## Values 14 | 15 | This is an open enum. Unrecognized values will be captured as the `Unrecognized` branded type. 16 | 17 | | Name | Value | 18 | | ---------------------- | ---------------------- | 19 | | `ApplicationJson` | application/json | 20 | | `TextCsv` | text/csv | 21 | | - | `Unrecognized` | -------------------------------------------------------------------------------- /docs/sdk/models/shared/security.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | ## Example Usage 4 | 5 | ```typescript 6 | import { Security } from "unstructured-client/sdk/models/shared"; 7 | 8 | let value: Security = { 9 | apiKeyAuth: "YOUR_API_KEY", 10 | }; 11 | ``` 12 | 13 | ## Fields 14 | 15 | | Field | Type | Required | Description | Example | 16 | | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | 17 | | `apiKeyAuth` | *string* | :heavy_minus_sign: | N/A | YOUR_API_KEY | -------------------------------------------------------------------------------- /docs/sdk/models/shared/strategy.md: -------------------------------------------------------------------------------- 1 | # Strategy 2 | 3 | The strategy to use for partitioning PDF/image. Options are fast, hi_res, auto. Default: hi_res 4 | 5 | ## Example Usage 6 | 7 | ```typescript 8 | import { Strategy } from "unstructured-client/sdk/models/shared"; 9 | 10 | let value: Strategy = Strategy.Vlm; 11 | ``` 12 | 13 | ## Values 14 | 15 | This is an open enum. Unrecognized values will be captured as the `Unrecognized` branded type. 16 | 17 | | Name | Value | 18 | | ---------------------- | ---------------------- | 19 | | `Fast` | fast | 20 | | `HiRes` | hi_res | 21 | | `Auto` | auto | 22 | | `OcrOnly` | ocr_only | 23 | | `OdOnly` | od_only | 24 | | `Vlm` | vlm | 25 | | - | `Unrecognized` | -------------------------------------------------------------------------------- /docs/sdk/models/shared/validationerror.md: -------------------------------------------------------------------------------- 1 | # ValidationError 2 | 3 | ## Example Usage 4 | 5 | ```typescript 6 | import { ValidationError } from "unstructured-client/sdk/models/shared"; 7 | 8 | let value: ValidationError = { 9 | loc: [ 10 | 598752, 11 | ], 12 | msg: "", 13 | type: "", 14 | }; 15 | ``` 16 | 17 | ## Fields 18 | 19 | | Field | Type | Required | Description | 20 | | ------------------ | ------------------ | ------------------ | ------------------ | 21 | | `loc` | *shared.Loc*[] | :heavy_check_mark: | N/A | 22 | | `msg` | *string* | :heavy_check_mark: | N/A | 23 | | `type` | *string* | :heavy_check_mark: | N/A | -------------------------------------------------------------------------------- /docs/sdk/models/shared/vlmmodel.md: -------------------------------------------------------------------------------- 1 | # VLMModel 2 | 3 | The VLM Model to use. 4 | 5 | ## Example Usage 6 | 7 | ```typescript 8 | import { VLMModel } from "unstructured-client/sdk/models/shared"; 9 | 10 | let value: VLMModel = VLMModel.Gpt4o; 11 | ``` 12 | 13 | ## Values 14 | 15 | This is an open enum. Unrecognized values will be captured as the `Unrecognized` branded type. 16 | 17 | | Name | Value | 18 | | -------------------------------------------- | -------------------------------------------- | 19 | | `Claude35Sonnet20241022` | claude-3-5-sonnet-20241022 | 20 | | `Claude37Sonnet20250219` | claude-3-7-sonnet-20250219 | 21 | | `Gpt4o` | gpt-4o | 22 | | `Gemini15Pro` | gemini-1.5-pro | 23 | | `UsAmazonNovaProV10` | us.amazon.nova-pro-v1:0 | 24 | | `UsAmazonNovaLiteV10` | us.amazon.nova-lite-v1:0 | 25 | | `UsAnthropicClaude35Sonnet20241022V20` | us.anthropic.claude-3-5-sonnet-20241022-v2:0 | 26 | | `UsAnthropicClaude3Opus20240229V10` | us.anthropic.claude-3-opus-20240229-v1:0 | 27 | | `UsAnthropicClaude3Haiku20240307V10` | us.anthropic.claude-3-haiku-20240307-v1:0 | 28 | | `UsAnthropicClaude3Sonnet20240229V10` | us.anthropic.claude-3-sonnet-20240229-v1:0 | 29 | | `UsMetaLlama3290bInstructV10` | us.meta.llama3-2-90b-instruct-v1:0 | 30 | | `UsMetaLlama3211bInstructV10` | us.meta.llama3-2-11b-instruct-v1:0 | 31 | | `Gemini20Flash001` | gemini-2.0-flash-001 | 32 | | - | `Unrecognized` | -------------------------------------------------------------------------------- /docs/sdk/models/shared/vlmmodelprovider.md: -------------------------------------------------------------------------------- 1 | # VLMModelProvider 2 | 3 | The VLM Model provider to use. 4 | 5 | ## Example Usage 6 | 7 | ```typescript 8 | import { VLMModelProvider } from "unstructured-client/sdk/models/shared"; 9 | 10 | let value: VLMModelProvider = VLMModelProvider.Openai; 11 | ``` 12 | 13 | ## Values 14 | 15 | This is an open enum. Unrecognized values will be captured as the `Unrecognized` branded type. 16 | 17 | | Name | Value | 18 | | ---------------------- | ---------------------- | 19 | | `Openai` | openai | 20 | | `Anthropic` | anthropic | 21 | | `Bedrock` | bedrock | 22 | | `AnthropicBedrock` | anthropic_bedrock | 23 | | `Vertexai` | vertexai | 24 | | `Google` | google | 25 | | `AzureOpenai` | azure_openai | 26 | | - | `Unrecognized` | -------------------------------------------------------------------------------- /docs/sdks/general/README.md: -------------------------------------------------------------------------------- 1 | # General 2 | (*general*) 3 | 4 | ## Overview 5 | 6 | ### Available Operations 7 | 8 | * [partition](#partition) - Summary 9 | 10 | ## partition 11 | 12 | Description 13 | 14 | ### Example Usage 15 | 16 | ```typescript 17 | import { openAsBlob } from "node:fs"; 18 | import { UnstructuredClient } from "unstructured-client"; 19 | import { VLMModel, VLMModelProvider } from "unstructured-client/sdk/models/shared"; 20 | 21 | const unstructuredClient = new UnstructuredClient(); 22 | 23 | async function run() { 24 | const result = await unstructuredClient.general.partition({ 25 | partitionParameters: { 26 | chunkingStrategy: "by_title", 27 | files: await openAsBlob("example.file"), 28 | splitPdfPageRange: [ 29 | 1, 30 | 10, 31 | ], 32 | vlmModel: VLMModel.Gpt4o, 33 | vlmModelProvider: VLMModelProvider.Openai, 34 | }, 35 | }); 36 | 37 | // Handle the result 38 | console.log(result); 39 | } 40 | 41 | run(); 42 | ``` 43 | 44 | ### Standalone function 45 | 46 | The standalone function version of this method: 47 | 48 | ```typescript 49 | import { openAsBlob } from "node:fs"; 50 | import { UnstructuredClientCore } from "unstructured-client/core.js"; 51 | import { generalPartition } from "unstructured-client/funcs/generalPartition.js"; 52 | import { VLMModel, VLMModelProvider } from "unstructured-client/sdk/models/shared"; 53 | 54 | // Use `UnstructuredClientCore` for best tree-shaking performance. 55 | // You can create one instance of it to use across an application. 56 | const unstructuredClient = new UnstructuredClientCore(); 57 | 58 | async function run() { 59 | const res = await generalPartition(unstructuredClient, { 60 | partitionParameters: { 61 | chunkingStrategy: "by_title", 62 | files: await openAsBlob("example.file"), 63 | splitPdfPageRange: [ 64 | 1, 65 | 10, 66 | ], 67 | vlmModel: VLMModel.Gpt4o, 68 | vlmModelProvider: VLMModelProvider.Openai, 69 | }, 70 | }); 71 | 72 | if (!res.ok) { 73 | throw res.error; 74 | } 75 | 76 | const { value: result } = res; 77 | 78 | // Handle the result 79 | console.log(result); 80 | } 81 | 82 | run(); 83 | ``` 84 | 85 | ### Parameters 86 | 87 | | Parameter | Type | Required | Description | 88 | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 89 | | `request` | [operations.PartitionRequest](../../sdk/models/operations/partitionrequest.md) | :heavy_check_mark: | The request object to use for the request. | 90 | | `options` | RequestOptions | :heavy_minus_sign: | Used to set various options for making HTTP requests. | 91 | | `options.fetchOptions` | [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#options) | :heavy_minus_sign: | Options that are passed to the underlying HTTP request. This can be used to inject extra headers for examples. All `Request` options, except `method` and `body`, are allowed. | 92 | | `options.retries` | [RetryConfig](../../lib/utils/retryconfig.md) | :heavy_minus_sign: | Enables retrying HTTP requests under certain failure conditions. | 93 | 94 | ### Response 95 | 96 | **Promise\<[operations.PartitionResponse](../../sdk/models/operations/partitionresponse.md)\>** 97 | 98 | ### Errors 99 | 100 | | Error Type | Status Code | Content Type | 101 | | -------------------------- | -------------------------- | -------------------------- | 102 | | errors.HTTPValidationError | 422 | application/json | 103 | | errors.ServerError | 5XX | application/json | 104 | | errors.SDKError | 4XX | \*/\* | -------------------------------------------------------------------------------- /docs/sdks/unstructuredclient/README.md: -------------------------------------------------------------------------------- 1 | # UnstructuredClient SDK 2 | 3 | ## Overview 4 | 5 | ### Available Operations 6 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | import tseslint from "typescript-eslint"; 4 | 5 | /** @type {import('eslint').Linter.Config[]} */ 6 | export default [ 7 | { files: ["**/*.{js,mjs,cjs,ts}"] }, 8 | { languageOptions: { globals: globals.browser } }, 9 | pluginJs.configs.recommended, 10 | ...tseslint.configs.recommended, 11 | { 12 | rules: { 13 | "no-constant-condition": "off", 14 | "no-useless-escape": "off", 15 | // Handled by typescript compiler 16 | "@typescript-eslint/no-unused-vars": "off", 17 | "@typescript-eslint/no-explicit-any": "off", 18 | "@typescript-eslint/no-empty-object-type": "off", 19 | "@typescript-eslint/no-namespace": "off", 20 | }, 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /files.gen: -------------------------------------------------------------------------------- 1 | src/sdk/general.ts 2 | src/sdk/sdk.ts 3 | .eslintrc.yml 4 | jest.config.js 5 | package-lock.json 6 | package.json 7 | src/index.ts 8 | src/internal/utils/contenttype.ts 9 | src/internal/utils/headers.ts 10 | src/internal/utils/index.ts 11 | src/internal/utils/pathparams.ts 12 | src/internal/utils/queryparams.ts 13 | src/internal/utils/requestbody.ts 14 | src/internal/utils/retries.ts 15 | src/internal/utils/security.ts 16 | src/internal/utils/utils.ts 17 | src/sdk/index.ts 18 | src/sdk/models/errors/sdkerror.ts 19 | src/sdk/types/index.ts 20 | src/sdk/types/rfcdate.ts 21 | tsconfig.json 22 | src/sdk/models/operations/partition.ts 23 | src/sdk/models/errors/httpvalidationerror.ts 24 | src/sdk/models/shared/validationerror.ts 25 | src/sdk/models/shared/partitionparameters.ts 26 | src/sdk/models/shared/security.ts 27 | src/sdk/models/errors/index.ts 28 | src/sdk/models/operations/index.ts 29 | src/sdk/models/shared/index.ts 30 | docs/sdk/models/operations/partitionresponse.md 31 | docs/sdk/models/errors/httpvalidationerror.md 32 | docs/sdk/models/shared/validationerror.md 33 | docs/sdk/models/shared/files.md 34 | docs/sdk/models/shared/partitionparameters.md 35 | docs/sdk/models/shared/security.md 36 | USAGE.md 37 | .gitattributes -------------------------------------------------------------------------------- /gen.yaml: -------------------------------------------------------------------------------- 1 | configVersion: 2.0.0 2 | generation: 3 | sdkClassName: unstructured_client 4 | usageSnippets: 5 | optionalPropertyRendering: withExample 6 | fixes: 7 | nameResolutionFeb2025: false 8 | parameterOrderingFeb2024: false 9 | requestResponseComponentNamesFeb2024: false 10 | securityFeb2025: false 11 | sharedErrorComponentsApr2025: false 12 | auth: 13 | oAuth2ClientCredentialsEnabled: false 14 | oAuth2PasswordEnabled: false 15 | typescript: 16 | version: 0.24.4 17 | additionalDependencies: 18 | dependencies: 19 | async: ^3.2.5 20 | pdf-lib: ^1.17.1 21 | devDependencies: 22 | '@types/async': ^3.2.24 23 | vitest: ^2.1.3 24 | peerDependencies: {} 25 | additionalPackageJSON: {} 26 | author: Unstructured 27 | clientServerStatusCodesAsErrors: true 28 | defaultErrorName: SDKError 29 | enableCustomCodeRegions: false 30 | enableMCPServer: true 31 | enableReactQuery: false 32 | enumFormat: enum 33 | flattenGlobalSecurity: false 34 | flatteningOrder: body-first 35 | imports: 36 | option: openapi 37 | paths: 38 | callbacks: sdk/models/callbacks 39 | errors: sdk/models/errors 40 | operations: sdk/models/operations 41 | shared: sdk/models/shared 42 | webhooks: sdk/models/webhooks 43 | inputModelSuffix: input 44 | jsonpath: legacy 45 | license: MIT 46 | maxMethodParams: 0 47 | methodArguments: require-security-and-request 48 | moduleFormat: dual 49 | outputModelSuffix: output 50 | packageName: unstructured-client 51 | responseFormat: flat 52 | templateVersion: v2 53 | useIndexModules: true 54 | -------------------------------------------------------------------------------- /jsr.json: -------------------------------------------------------------------------------- 1 | 2 | 3 | { 4 | "name": "unstructured-client", 5 | "version": "0.24.4", 6 | "exports": { 7 | ".": "./src/index.ts", 8 | "./sdk/models/errors": "./src/sdk/models/errors/index.ts", 9 | "./sdk/models/shared": "./src/sdk/models/shared/index.ts", 10 | "./sdk/models/operations": "./src/sdk/models/operations/index.ts", 11 | "./lib/config": "./src/lib/config.ts", 12 | "./lib/http": "./src/lib/http.ts", 13 | "./lib/retries": "./src/lib/retries.ts", 14 | "./lib/sdks": "./src/lib/sdks.ts", 15 | "./types": "./src/sdk/types/index.ts" 16 | }, 17 | "publish": { 18 | "include": [ 19 | "LICENSE", 20 | "README.md", 21 | "RUNTIMES.md", 22 | "USAGE.md", 23 | "jsr.json", 24 | "src/**/*.ts" 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /overlay_client.yaml: -------------------------------------------------------------------------------- 1 | overlay: 1.0.0 2 | info: 3 | title: Overlay openapi.json to add client-specific features 4 | version: 0.0.0 5 | actions: 6 | - target: $["components"]["schemas"]["partition_parameters"]["properties"] 7 | update: 8 | "split_pdf_page": 9 | { 10 | "type": "boolean", 11 | "title": "Split Pdf Page", 12 | "description": "Should the pdf file be split at client. Ignored on backend.", 13 | "default": true, 14 | } 15 | - target: $["components"]["schemas"]["partition_parameters"]["properties"] 16 | update: 17 | "split_pdf_concurrency_level": 18 | { 19 | "type": "integer", 20 | "title": "Split Pdf Concurrency Level", 21 | "description": "Number of maximum concurrent requests made when splitting PDF. Ignored on backend.", 22 | "default": 5, 23 | } 24 | - target: $["components"]["schemas"]["partition_parameters"]["properties"] 25 | update: 26 | "split_pdf_page_range": 27 | { 28 | "type": "array", 29 | "title": "Split Pdf Page Range", 30 | "description": "When `split_pdf_page is set to `True`, this parameter selects a subset of the pdf to send to the API. The parameter is a list of 2 integers within the range [1, length_of_pdf]. An Error is thrown if the given range is invalid. Ignored on backend.", 31 | "items": {"type": "integer"}, 32 | "minItems": 2, 33 | "maxItems": 2, 34 | "example": [1, 10], 35 | } 36 | - target: $["components"]["schemas"]["partition_parameters"]["properties"] 37 | update: 38 | "split_pdf_allow_failed": 39 | { 40 | "title": "Split Pdf Allow Failed", 41 | "description": "When `split_pdf_page` is set to `True`, this parameter defines the behavior when some of the parallel requests fail. By default `split_pdf_allow_failed` is set to `False` and any failed request send to the API will make the whole process break and raise an Exception. If `split_pdf_allow_failed` is set to `True`, the errors encountered while sending parallel requests will not break the processing - the resuling list of Elements will miss the data from errored pages.", 42 | "type": "boolean", 43 | "default": false, 44 | } 45 | - target: $["components"]["schemas"]["partition_parameters"]["properties"]["tracking_enabled"] 46 | description: Remove tracking_enabled from partition_parameters 47 | remove: true 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unstructured-client", 3 | "version": "0.24.4", 4 | "author": "Unstructured", 5 | "type": "module", 6 | "bin": { 7 | "mcp": "bin/mcp-server.js" 8 | }, 9 | "tshy": { 10 | "sourceDialects": [ 11 | "unstructured-client/source" 12 | ], 13 | "exports": { 14 | ".": "./src/index.ts", 15 | "./package.json": "./package.json", 16 | "./sdk/types": "./src/sdk/types/index.ts", 17 | "./sdk/models/errors": "./src/sdk/models/errors/index.ts", 18 | "./sdk/models/shared": "./src/sdk/models/shared/index.ts", 19 | "./sdk/models/operations": "./src/sdk/models/operations/index.ts", 20 | "./*.js": "./src/*.ts", 21 | "./*": "./src/*.ts" 22 | } 23 | }, 24 | "sideEffects": false, 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/Unstructured-IO/unstructured-js-client.git", 28 | "directory": "." 29 | }, 30 | "scripts": { 31 | "lint": "eslint --cache --max-warnings=0 src", 32 | "build:mcp": "bun src/mcp-server/build.mts", 33 | "build": "npm run build:mcp && tshy", 34 | "prepublishOnly": "npm run build" 35 | }, 36 | "peerDependencies": { 37 | "@modelcontextprotocol/sdk": ">=1.5.0 <1.10.0", 38 | "zod": ">= 3" 39 | }, 40 | "peerDependenciesMeta": { 41 | "@modelcontextprotocol/sdk": { 42 | "optional": true 43 | } 44 | }, 45 | "devDependencies": { 46 | "@eslint/js": "^9.19.0", 47 | "@modelcontextprotocol/sdk": ">=1.5.0 <1.10.0", 48 | "@stricli/core": "^1.1.1", 49 | "@types/async": "^3.2.24", 50 | "@types/express": "^4.17.21", 51 | "bun": "^1.2.2", 52 | "bun-types": "^1.2.2", 53 | "eslint": "^9.19.0", 54 | "express": "^4.21.2", 55 | "globals": "^15.14.0", 56 | "tshy": "^2.0.0", 57 | "typescript": "^5.4.5", 58 | "typescript-eslint": "^8.22.0", 59 | "vitest": "^2.1.3", 60 | "zod": "^3.25.17" 61 | }, 62 | "dependencies": { 63 | "async": "^3.2.5", 64 | "pdf-lib": "^1.17.1" 65 | }, 66 | "exports": { 67 | ".": { 68 | "import": { 69 | "unstructured-client/source": "./src/index.ts", 70 | "types": "./dist/esm/index.d.ts", 71 | "default": "./dist/esm/index.js" 72 | }, 73 | "require": { 74 | "types": "./dist/commonjs/index.d.ts", 75 | "default": "./dist/commonjs/index.js" 76 | } 77 | }, 78 | "./package.json": "./package.json", 79 | "./sdk/types": { 80 | "import": { 81 | "unstructured-client/source": "./src/sdk/types/index.ts", 82 | "types": "./dist/esm/sdk/types/index.d.ts", 83 | "default": "./dist/esm/sdk/types/index.js" 84 | }, 85 | "require": { 86 | "types": "./dist/commonjs/sdk/types/index.d.ts", 87 | "default": "./dist/commonjs/sdk/types/index.js" 88 | } 89 | }, 90 | "./sdk/models/errors": { 91 | "import": { 92 | "unstructured-client/source": "./src/sdk/models/errors/index.ts", 93 | "types": "./dist/esm/sdk/models/errors/index.d.ts", 94 | "default": "./dist/esm/sdk/models/errors/index.js" 95 | }, 96 | "require": { 97 | "types": "./dist/commonjs/sdk/models/errors/index.d.ts", 98 | "default": "./dist/commonjs/sdk/models/errors/index.js" 99 | } 100 | }, 101 | "./sdk/models/shared": { 102 | "import": { 103 | "unstructured-client/source": "./src/sdk/models/shared/index.ts", 104 | "types": "./dist/esm/sdk/models/shared/index.d.ts", 105 | "default": "./dist/esm/sdk/models/shared/index.js" 106 | }, 107 | "require": { 108 | "types": "./dist/commonjs/sdk/models/shared/index.d.ts", 109 | "default": "./dist/commonjs/sdk/models/shared/index.js" 110 | } 111 | }, 112 | "./sdk/models/operations": { 113 | "import": { 114 | "unstructured-client/source": "./src/sdk/models/operations/index.ts", 115 | "types": "./dist/esm/sdk/models/operations/index.d.ts", 116 | "default": "./dist/esm/sdk/models/operations/index.js" 117 | }, 118 | "require": { 119 | "types": "./dist/commonjs/sdk/models/operations/index.d.ts", 120 | "default": "./dist/commonjs/sdk/models/operations/index.js" 121 | } 122 | }, 123 | "./*.js": { 124 | "import": { 125 | "unstructured-client/source": "./src/*.ts", 126 | "types": "./dist/esm/*.d.ts", 127 | "default": "./dist/esm/*.js" 128 | }, 129 | "require": { 130 | "types": "./dist/commonjs/*.d.ts", 131 | "default": "./dist/commonjs/*.js" 132 | } 133 | }, 134 | "./*": { 135 | "import": { 136 | "unstructured-client/source": "./src/*.ts", 137 | "types": "./dist/esm/*.d.ts", 138 | "default": "./dist/esm/*.js" 139 | }, 140 | "require": { 141 | "types": "./dist/commonjs/*.d.ts", 142 | "default": "./dist/commonjs/*.js" 143 | } 144 | } 145 | }, 146 | "main": "./dist/commonjs/index.js", 147 | "types": "./dist/commonjs/index.d.ts", 148 | "module": "./dist/esm/index.js" 149 | } 150 | -------------------------------------------------------------------------------- /src/core.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { ClientSDK } from "./lib/sdks.js"; 6 | 7 | /** 8 | * A minimal client to use when calling standalone SDK functions. Typically, an 9 | * instance of this class would be instantiated once at the start of an 10 | * application and passed around through some dependency injection mechanism to 11 | * parts of an application that need to make SDK calls. 12 | */ 13 | export class UnstructuredClientCore extends ClientSDK {} 14 | -------------------------------------------------------------------------------- /src/hooks/custom/FixArrayParamsHook.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | type BeforeRequestContext, 4 | BeforeRequestHook, 5 | } from "../types.js"; 6 | import { prepareRequestHeaders } from "./utils/request.js"; 7 | 8 | /** 9 | * If the given key in FormData is present and contains a comma-separated list of values, 10 | * split the values into separate entries with the key suffixed by "[]". 11 | * 12 | * @param formData - The FormData object to modify. 13 | * @param key - The key to extract and split. 14 | */ 15 | function flattenArrayParameter(formData: FormData, key: string): void { 16 | const value = formData.get(key); 17 | if (formData && typeof value === "string" && value.includes(",")) { 18 | formData.delete(key); 19 | const values = value.split(",").map(v => v.trim()).filter(Boolean); 20 | for (const v of values) { 21 | formData.append(`${key}[]`, v); 22 | } 23 | } 24 | } 25 | /** 26 | * Represents a hook for fixing array parameters before sending a request. 27 | */ 28 | export class FixArrayParamsHook implements BeforeRequestHook { 29 | /** 30 | * Fixes specific array parameters in the request. 31 | * The SDK creates FormData with {extract_image_block_types: "a,b,c"}, 32 | * and the server expects it to be {extract_image_block_types[]: ["a", "b", "c"]}. 33 | * Speakeasy will fix this upstream soon. 34 | * 35 | * @param _hookCtx - The context object for the hook, containing metadata about the request. 36 | * @param request - The original Request object. 37 | * @returns A new Request object with modified form data and headers. 38 | */ 39 | async beforeRequest( 40 | _hookCtx: BeforeRequestContext, 41 | request: Request 42 | ): Promise { 43 | const requestClone = request.clone(); 44 | const formData = await requestClone.formData(); 45 | 46 | flattenArrayParameter(formData, "extract_image_block_types"); 47 | 48 | const headers = prepareRequestHeaders(requestClone); 49 | 50 | return new Request(requestClone, { 51 | body: formData, 52 | headers: headers, 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/hooks/custom/HttpsCheckHook.ts: -------------------------------------------------------------------------------- 1 | import { BASE_HOSTNAME_REGEX, BASE_PROTOCOL } from "./common.js"; 2 | import { SDKInitHook, SDKInitOptions } from "../types.js"; 3 | 4 | /** 5 | * Represents a hook that performs base host HTTPS check during SDK initialization. 6 | */ 7 | export class HttpsCheckHook implements SDKInitHook { 8 | /** 9 | * Performs the base host HTTPS check during SDK initialization. If hostname 10 | * matches "*.unstructuredapp.io" and the protocol is not "https:", the protocol 11 | * is updated to "https:". 12 | * @param opts - The SDK initialization options. 13 | * @returns The updated SDK initialization options. 14 | */ 15 | sdkInit(opts: SDKInitOptions): SDKInitOptions { 16 | const { baseURL, client } = opts; 17 | 18 | if (baseURL) { 19 | // -- pathname should always be empty 20 | baseURL.pathname = "/"; 21 | 22 | if (BASE_HOSTNAME_REGEX.test(baseURL.hostname) && baseURL.protocol !== BASE_PROTOCOL) { 23 | console.warn("Base URL protocol is not HTTPS. Updating to HTTPS."); 24 | const newBaseURL = baseURL.href.replace(baseURL.protocol, BASE_PROTOCOL); 25 | return {baseURL: new URL(newBaseURL), client: client}; 26 | } 27 | } 28 | 29 | return { baseURL: baseURL, client: client }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/hooks/custom/LoggerHook.ts: -------------------------------------------------------------------------------- 1 | import { AfterErrorContext, AfterErrorHook, AfterSuccessContext, AfterSuccessHook } from "../types.js"; 2 | 3 | /** 4 | * Represents a hook that logs status and information that the request will be retried 5 | * after encountering a 5xx error. 6 | */ 7 | export class LoggerHook implements AfterSuccessHook, AfterErrorHook { 8 | private retriesCounter: Map = new Map(); 9 | 10 | /** 11 | * Log retries to give users visibility into requests. 12 | * @param response - The response object received from the server. 13 | * @param error - The error object representing the encountered error. 14 | * @param operationID - The unique identifier for the operation being logged. 15 | */ 16 | private logRetries( 17 | response: Response | null, 18 | error: unknown, 19 | operationID: string 20 | ): void { 21 | if (response && response.status >= 500) { 22 | console.warn( 23 | "Failed to process a request due to API server error with status code %d. " + 24 | "Attempting retry number %d after sleep.", 25 | response.status, 26 | this.retriesCounter.get(operationID) 27 | ); 28 | if (response.statusText) { 29 | console.warn("Server message - %s", response.statusText); 30 | } 31 | } else if (error) { 32 | console.info( 33 | `Failed to process a request due to connection error - ${(error as Error).message}. ` + 34 | `Attempting retry number ${this.retriesCounter.get(operationID)} after sleep.` 35 | ); 36 | } 37 | } 38 | 39 | /** 40 | * Handles successful responses, resetting the retry counter for the operation. 41 | * Logs a success message indicating that the document was successfully partitioned. 42 | * @param hookCtx - The context object containing information about the request. 43 | * @param response - The response object received from the server. 44 | * @returns The response object. 45 | */ 46 | afterSuccess(hookCtx: AfterSuccessContext, response: Response): Response { 47 | this.retriesCounter.delete(hookCtx.operationID); 48 | // NOTE: In case of split page partition this means - at least one of the splits was partitioned successfully 49 | return response; 50 | } 51 | 52 | /** 53 | * Executes after an error occurs during a request. 54 | * @param hookCtx - The context object containing information about the request. 55 | * @param response - The response object received from the server. 56 | * @param error - The error object representing the encountered error. 57 | * @returns An object containing the updated response and error. 58 | */ 59 | afterError( 60 | hookCtx: AfterErrorContext, 61 | response: Response | null, 62 | error: unknown 63 | ): { response: Response | null; error: unknown } { 64 | const currentCount = this.retriesCounter.get(hookCtx.operationID) || 0; 65 | this.retriesCounter.set(hookCtx.operationID, currentCount + 1); 66 | this.logRetries(response, error, hookCtx.operationID); 67 | 68 | if (response && response.status === 200) { 69 | return { response, error }; 70 | } 71 | console.error("Failed to partition the document."); 72 | if (response) { 73 | console.error(`Server responded with ${response.status} - ${response.statusText}`); 74 | } 75 | if (error) { 76 | console.error(`Following error occurred - ${(error as Error).message}`); 77 | } 78 | return { response, error }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/hooks/custom/common.ts: -------------------------------------------------------------------------------- 1 | import {HTTPClient} from "../../lib/http.js"; 2 | 3 | /** 4 | * Regular expression pattern for matching base hostnames in the form of "*.unstructuredapp.io". 5 | */ 6 | export const BASE_HOSTNAME_REGEX = /^.*\.unstructuredapp\.io$/; 7 | 8 | /** 9 | * The base protocol used for HTTPS requests. 10 | */ 11 | export const BASE_PROTOCOL = "https:"; 12 | 13 | export const PARTITION_FORM_FILES_KEY = "files"; 14 | export const PARTITION_FORM_SPLIT_PDF_PAGE_KEY = "split_pdf_page"; 15 | export const PARTITION_FORM_SPLIT_PDF_ALLOW_FAILED_KEY = "split_pdf_allow_failed"; 16 | export const PARTITION_FORM_STARTING_PAGE_NUMBER_KEY = "starting_page_number"; 17 | export const PARTITION_FORM_SPLIT_PDF_PAGE_RANGE_KEY = "split_pdf_page_range"; 18 | export const PARTITION_FORM_SPLIT_PDF_CONCURRENCY_LEVEL = 19 | "split_pdf_concurrency_level"; 20 | 21 | export const EXTRACT_IMAGE_BLOCK_TYPES = "extract_image_block_types"; 22 | 23 | export const DEFAULT_STARTING_PAGE_NUMBER = 1; 24 | export const DEFAULT_NUMBER_OF_PARALLEL_REQUESTS = 10; 25 | export const DEFAULT_SPLIT_PDF_ALLOW_FAILED_KEY = false; 26 | export const MAX_NUMBER_OF_PARALLEL_REQUESTS = 50; 27 | 28 | export const MIN_PAGES_PER_THREAD = 2; 29 | export const MAX_PAGES_PER_THREAD = 20; 30 | 31 | export class HTTPClientExtension extends HTTPClient { 32 | constructor() { 33 | super(); 34 | } 35 | 36 | override async request(request: Request): Promise { 37 | if (request.url === "https://no-op/") { 38 | return new Response('{}', { 39 | headers: [ 40 | ["fake-response", "fake-response"] 41 | ], 42 | status: 200, 43 | statusText: 'OK_NO_OP' 44 | }); 45 | } 46 | return super.request(request); 47 | } 48 | } 49 | 50 | export function generateGuid() { 51 | return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 52 | } -------------------------------------------------------------------------------- /src/hooks/custom/utils/form.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DEFAULT_NUMBER_OF_PARALLEL_REQUESTS, DEFAULT_SPLIT_PDF_ALLOW_FAILED_KEY, 3 | DEFAULT_STARTING_PAGE_NUMBER, 4 | MAX_NUMBER_OF_PARALLEL_REQUESTS, PARTITION_FORM_SPLIT_PDF_ALLOW_FAILED_KEY, 5 | PARTITION_FORM_SPLIT_PDF_CONCURRENCY_LEVEL, 6 | PARTITION_FORM_SPLIT_PDF_PAGE_RANGE_KEY, 7 | PARTITION_FORM_STARTING_PAGE_NUMBER_KEY, 8 | } from "../common.js"; 9 | 10 | /** 11 | * Retrieves an integer parameter from the given form data. 12 | * If the parameter is not found or is not a valid integer, the default value is returned. 13 | * 14 | * @param formData - The form data object. 15 | * @param parameterName - The name of the parameter to retrieve. 16 | * @param defaultValue - The default value to use if the parameter is not found or is not 17 | * a valid integer. 18 | * @returns The integer value of the parameter. 19 | */ 20 | function getIntegerParameter( 21 | formData: FormData, 22 | parameterName: string, 23 | defaultValue: number 24 | ): number { 25 | let numberParameter = defaultValue; 26 | const formDataParameter = formData.get(parameterName); 27 | 28 | if (formDataParameter === null) { 29 | return numberParameter; 30 | } 31 | 32 | const formDataNumberParameter = parseInt(formDataParameter as string); 33 | 34 | if (isNaN(formDataNumberParameter)) { 35 | console.warn( 36 | `'${parameterName}' is not a valid integer. Using default value '${defaultValue}'.` 37 | ); 38 | } else { 39 | numberParameter = formDataNumberParameter; 40 | } 41 | 42 | return numberParameter; 43 | } 44 | 45 | /** 46 | * Retrieves a boolean parameter from the given form data. 47 | * If the parameter is not found or does not have true/false value, the default value is returned. 48 | * 49 | * @param formData - The form data object. 50 | * @param parameterName - The name of the parameter to retrieve. 51 | * @param defaultValue - The default value to use if the parameter is not found or is not 52 | * a true/false string. 53 | * @returns The boolean value of the parameter. 54 | */ 55 | function getBooleanParameter( 56 | formData: FormData, 57 | parameterName: string, 58 | defaultValue: boolean 59 | ): boolean { 60 | let booleanParameter = defaultValue; 61 | const formDataParameter = formData.get(parameterName); 62 | 63 | if (formDataParameter === null) { 64 | return booleanParameter; 65 | } 66 | 67 | const formDataBooleanParameterString = formDataParameter as string; 68 | 69 | if (formDataBooleanParameterString.toLowerCase() === "true") { 70 | booleanParameter = true; 71 | } else if (formDataBooleanParameterString.toLowerCase() === "false") { 72 | booleanParameter = false; 73 | } else { 74 | console.warn( 75 | `'${parameterName}' is not a valid boolean. Using default value '${defaultValue}'.` 76 | ); 77 | } 78 | 79 | return booleanParameter; 80 | } 81 | 82 | /** 83 | * Retrieves and validates a page range from FormData, ensuring that the start and end values are defined and within bounds. 84 | * 85 | * @param formData - The FormData object containing the page range parameter. 86 | * @param maxPages - The maximum number of pages in the document. 87 | * @returns {[number, number]} - A tuple containing the validated start and end page numbers. 88 | * 89 | * @throws Will throw an error if the page range is invalid or out of bounds. 90 | */ 91 | export function getSplitPdfPageRange(formData: FormData, maxPages: number): [number, number] { 92 | const formDataParameter = formData.get(PARTITION_FORM_SPLIT_PDF_PAGE_RANGE_KEY); 93 | const pageRange = String(formDataParameter).split(",").map(Number) 94 | 95 | const start = pageRange[0] || 1; 96 | const end = pageRange[1] || maxPages; 97 | 98 | if (!(start > 0 && start <= maxPages) || !(end > 0 && end <= maxPages) || !(start <= end)) { 99 | const msg = `Page range (${start} to ${end}) is out of bounds. Values should be between 1 and ${maxPages}.`; 100 | console.error(msg); 101 | throw new Error(msg); 102 | } 103 | 104 | return [start, end]; 105 | } 106 | 107 | /** 108 | * Gets the number of maximum requests that can be made when splitting PDF. 109 | * - The number of maximum requests is determined by the value of the request parameter 110 | * `split_pdf_thread`. 111 | * - If the parameter is not set or has an invalid value, the default number of 112 | * parallel requests (5) is used. 113 | * - If the number of maximum requests is greater than the maximum allowed (15), it is 114 | * clipped to the maximum value. 115 | * - If the number of maximum requests is less than 1, the default number of parallel 116 | * requests (5) is used. 117 | * 118 | * @returns The number of maximum requests to use when calling the API to split a PDF. 119 | */ 120 | export function getSplitPdfConcurrencyLevel(formData: FormData): number { 121 | let splitPdfConcurrencyLevel = getIntegerParameter( 122 | formData, 123 | PARTITION_FORM_SPLIT_PDF_CONCURRENCY_LEVEL, 124 | DEFAULT_NUMBER_OF_PARALLEL_REQUESTS 125 | ); 126 | 127 | if (splitPdfConcurrencyLevel > MAX_NUMBER_OF_PARALLEL_REQUESTS) { 128 | console.warn( 129 | `Clipping '${PARTITION_FORM_SPLIT_PDF_CONCURRENCY_LEVEL}' to ${MAX_NUMBER_OF_PARALLEL_REQUESTS}.` 130 | ); 131 | splitPdfConcurrencyLevel = MAX_NUMBER_OF_PARALLEL_REQUESTS; 132 | } else if (splitPdfConcurrencyLevel < 1) { 133 | console.warn( 134 | `'${PARTITION_FORM_SPLIT_PDF_CONCURRENCY_LEVEL}' is less than 1.` 135 | ); 136 | splitPdfConcurrencyLevel = DEFAULT_NUMBER_OF_PARALLEL_REQUESTS; 137 | } 138 | return splitPdfConcurrencyLevel; 139 | } 140 | 141 | /** 142 | * Gets the allowFailed parameter which decides whether the partial requests can fail or not 143 | * when using splitPdfPage parameter. 144 | * - The number of maximum requests is determined by the value of the request parameter 145 | * `split_pdf_thread`. 146 | * - If the parameter is not set or has an invalid value, the default number of 147 | * parallel requests (5) is used. 148 | * - If the number of maximum requests is greater than the maximum allowed (15), it is 149 | * clipped to the maximum value. 150 | * - If the number of maximum requests is less than 1, the default number of parallel 151 | * requests (5) is used. 152 | * 153 | * @returns The number of maximum requests to use when calling the API to split a PDF. 154 | */ 155 | export function getSplitPdfAllowFailed(formData: FormData): boolean { 156 | const splitPdfAllowFailed = getBooleanParameter( 157 | formData, 158 | PARTITION_FORM_SPLIT_PDF_ALLOW_FAILED_KEY, 159 | DEFAULT_SPLIT_PDF_ALLOW_FAILED_KEY 160 | ); 161 | return splitPdfAllowFailed; 162 | } 163 | 164 | /** 165 | * Retrieves the starting page number from the provided form data. 166 | * If the starting page number is not a valid integer or less than 1, 167 | * it will use the default value `1`. 168 | * 169 | * @param formData - Request form data. 170 | * @returns The starting page number. 171 | */ 172 | export function getStartingPageNumber(formData: FormData): number { 173 | let startingPageNumber = getIntegerParameter( 174 | formData, 175 | PARTITION_FORM_STARTING_PAGE_NUMBER_KEY, 176 | DEFAULT_STARTING_PAGE_NUMBER 177 | ); 178 | 179 | if (startingPageNumber < 1) { 180 | console.warn( 181 | `'${PARTITION_FORM_STARTING_PAGE_NUMBER_KEY}' is less than 1. Using default value '${DEFAULT_STARTING_PAGE_NUMBER}'.` 182 | ); 183 | startingPageNumber = DEFAULT_STARTING_PAGE_NUMBER; 184 | } 185 | 186 | return startingPageNumber; 187 | } 188 | -------------------------------------------------------------------------------- /src/hooks/custom/utils/general.ts: -------------------------------------------------------------------------------- 1 | export function stringToBoolean(string: string): boolean { 2 | return string.toLocaleLowerCase() === "true"; 3 | } 4 | -------------------------------------------------------------------------------- /src/hooks/custom/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./form.js"; 2 | export * from "./general.js"; 3 | export * from "./pdf.js"; 4 | export * from "./request.js"; 5 | -------------------------------------------------------------------------------- /src/hooks/custom/utils/pdf.ts: -------------------------------------------------------------------------------- 1 | import { PDFDocument } from "pdf-lib"; 2 | 3 | import { MAX_PAGES_PER_THREAD, MIN_PAGES_PER_THREAD } from "../common.js"; 4 | 5 | interface PdfSplit { 6 | content: Blob; 7 | startPage: number; 8 | endPage: number; 9 | } 10 | 11 | /** 12 | * Converts range of pages (including start and end page values) of a PDF document 13 | * to a Blob object. 14 | * @param pdf - The PDF document. 15 | * @param startPage - Number of the first page of split. 16 | * @param endPage - Number of the last page of split. 17 | * @returns A Promise that resolves to a Blob object representing the converted pages. 18 | */ 19 | export async function pdfPagesToBlob( 20 | pdf: PDFDocument, 21 | startPage: number, 22 | endPage: number 23 | ): Promise { 24 | const subPdf = await PDFDocument.create(); 25 | // Create an array with page indices to copy 26 | // Converts 1-based page numbers to 0-based page indices 27 | const pageIndices = Array.from( 28 | { length: endPage - startPage + 1 }, 29 | (_, index) => startPage + index - 1 30 | ); 31 | const pages = await subPdf.copyPages(pdf, pageIndices); 32 | for (const page of pages) { 33 | subPdf.addPage(page); 34 | } 35 | const subPdfBytes = await subPdf.save(); 36 | return new Blob([subPdfBytes], { 37 | type: "application/pdf", 38 | }); 39 | } 40 | 41 | /** 42 | * Calculates the optimal split size for processing pages with a specified concurrency level. 43 | * 44 | * @param pagesCount - The total number of pages to process. 45 | * @param concurrencyLevel - The level of concurrency to be used. 46 | * @returns A promise that resolves to the optimal number of pages per split, 47 | * ensuring it does not exceed the maximum or fall below the minimum threshold. 48 | */ 49 | export async function getOptimalSplitSize( 50 | pagesCount: number, 51 | concurrencyLevel: number 52 | ): Promise { 53 | let splitSize = MAX_PAGES_PER_THREAD; 54 | if (pagesCount < MAX_PAGES_PER_THREAD * concurrencyLevel) { 55 | splitSize = Math.ceil(pagesCount / concurrencyLevel); 56 | } 57 | splitSize = Math.max(splitSize, MIN_PAGES_PER_THREAD); 58 | 59 | return splitSize; 60 | } 61 | 62 | 63 | /** 64 | * Retrieves an array of splits, with the start and end page numbers, from a PDF file. 65 | * Distribution of pages per split is made in as much uniform manner as possible. 66 | * 67 | * @param pdf - The PDF file to extract pages from. 68 | * @param splitSize - The number of pages per split. 69 | * @param [pageRangeStart=1] - The starting page of the range to be split (1-based index). Defaults to the first page of the document. 70 | * @param [pageRangeEnd=pdf.getPageCount()] - The ending page of the range to be split (1-based index). Defaults to the last page of the document. 71 | * @returns A promise that resolves to an array of objects containing Blob files and 72 | * start and end page numbers from the original document. 73 | */ 74 | export async function splitPdf( 75 | pdf: PDFDocument, 76 | splitSize: number, 77 | pageRangeStart?: number, 78 | pageRangeEnd?: number 79 | ): Promise { 80 | const pdfSplits: PdfSplit[] = []; 81 | 82 | const startPage = pageRangeStart || 1; 83 | const endPage = pageRangeEnd || pdf.getPageCount(); 84 | const pagesCount = endPage - startPage + 1 85 | 86 | const numberOfSplits = Math.ceil(pagesCount / splitSize); 87 | 88 | for (let i = 0; i < numberOfSplits; ++i) { 89 | const offset = i * splitSize; 90 | const splitStartPage = offset + startPage; 91 | const splitEndPage = Math.min(endPage, splitStartPage + splitSize - 1); 92 | 93 | const pdfSplit = await pdfPagesToBlob(pdf, splitStartPage, splitEndPage); 94 | pdfSplits.push({ content: pdfSplit, startPage: splitStartPage, endPage: splitEndPage }); 95 | } 96 | 97 | return pdfSplits; 98 | } 99 | 100 | /** 101 | * Checks if the given file is a PDF by loading the file as a PDF using the `PDFDocument.load` method. 102 | * @param file - The file to check. 103 | * @returns A promise that resolves to three values, first is a boolean representing 104 | * whether there was an error during PDF load, second is a PDFDocument object or null 105 | * (depending if there was an error), and the third is the number of pages in the PDF. 106 | * The number of pages is 0 if there was an error while loading the file. 107 | */ 108 | export async function loadPdf( 109 | file: File | null 110 | ): Promise<[boolean, PDFDocument | null, number]> { 111 | if (!file) { 112 | return [true, null, 0]; 113 | } 114 | 115 | try { 116 | const arrayBuffer = await file.arrayBuffer(); 117 | const pdf = await PDFDocument.load(arrayBuffer); 118 | const pagesCount = pdf.getPages().length; 119 | return [false, pdf, pagesCount]; 120 | } catch (e) { 121 | return [true, null, 0]; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/hooks/custom/utils/request.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PARTITION_FORM_FILES_KEY, 3 | PARTITION_FORM_SPLIT_PDF_PAGE_KEY, 4 | PARTITION_FORM_STARTING_PAGE_NUMBER_KEY, 5 | } from "../common.js"; 6 | 7 | /** 8 | * Removes the "content-length" header from the passed response headers. 9 | * 10 | * @param response - The response object. 11 | * @returns The modified headers object. 12 | */ 13 | export function prepareResponseHeaders(response: Response): Headers { 14 | const headers = new Headers(response.headers); 15 | headers.delete("content-length"); 16 | return headers; 17 | } 18 | 19 | /** 20 | * Prepares the response body by extracting and flattening the JSON elements from 21 | * an array of responses. 22 | * 23 | * @param responses - An array of Response objects. 24 | * @returns A Promise that resolves to a string representation of the flattened 25 | * JSON elements. 26 | */ 27 | export async function prepareResponseBody( 28 | responses: Response[] 29 | ): Promise { 30 | const allElements: any[] = []; 31 | let index = 1; 32 | for (const res of responses) { 33 | if (res.status != 200) { 34 | console.warn("Failed to partition set #%d, its elements will be omitted in the final result.", index); 35 | } 36 | 37 | const resElements = await res.json(); 38 | allElements.push(resElements); 39 | index++; 40 | } 41 | return JSON.stringify(allElements.flat()); 42 | } 43 | 44 | /** 45 | * Removes the "content-type" header from the given request headers. 46 | * 47 | * @param request - The request object containing the headers. 48 | * @returns The modified headers object. 49 | */ 50 | export function prepareRequestHeaders(request: Request): Headers { 51 | const headers = new Headers(request.headers); 52 | headers.delete("content-type"); 53 | return headers; 54 | } 55 | 56 | /** 57 | * Prepares the request body for splitting a PDF. 58 | * 59 | * @param formData - The original form data. 60 | * @param fileContent - The content of the pages to be split. 61 | * @param fileName - The name of the file. 62 | * @param startingPageNumber - Real first page number of the split. 63 | * @returns A Promise that resolves to a FormData object representing 64 | * the prepared request body. 65 | */ 66 | export async function prepareRequestBody( 67 | formData: FormData, 68 | fileContent: Blob, 69 | fileName: string, 70 | startingPageNumber: number 71 | ): Promise { 72 | const newFormData = new FormData(); 73 | for (const [key, value] of formData.entries()) { 74 | if ( 75 | ![ 76 | PARTITION_FORM_STARTING_PAGE_NUMBER_KEY, 77 | PARTITION_FORM_SPLIT_PDF_PAGE_KEY, 78 | PARTITION_FORM_FILES_KEY, 79 | ].includes(key) 80 | ) { 81 | newFormData.append(key, value); 82 | } 83 | } 84 | 85 | newFormData.append(PARTITION_FORM_SPLIT_PDF_PAGE_KEY, "false"); 86 | newFormData.append(PARTITION_FORM_FILES_KEY, fileContent, fileName); 87 | newFormData.append( 88 | PARTITION_FORM_STARTING_PAGE_NUMBER_KEY, 89 | startingPageNumber.toString() 90 | ); 91 | 92 | return newFormData; 93 | } 94 | -------------------------------------------------------------------------------- /src/hooks/hooks.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { RequestInput } from "../lib/http.js"; 6 | import { 7 | AfterErrorContext, 8 | AfterErrorHook, 9 | AfterSuccessContext, 10 | AfterSuccessHook, 11 | BeforeCreateRequestContext, 12 | BeforeCreateRequestHook, 13 | BeforeRequestContext, 14 | BeforeRequestHook, 15 | Hook, 16 | Hooks, 17 | SDKInitHook, 18 | SDKInitOptions, 19 | } from "./types.js"; 20 | 21 | import { initHooks } from "./registration.js"; 22 | 23 | export class SDKHooks implements Hooks { 24 | sdkInitHooks: SDKInitHook[] = []; 25 | beforeCreateRequestHooks: BeforeCreateRequestHook[] = []; 26 | beforeRequestHooks: BeforeRequestHook[] = []; 27 | afterSuccessHooks: AfterSuccessHook[] = []; 28 | afterErrorHooks: AfterErrorHook[] = []; 29 | 30 | constructor() { 31 | const presetHooks: Array = []; 32 | 33 | for (const hook of presetHooks) { 34 | if ("sdkInit" in hook) { 35 | this.registerSDKInitHook(hook); 36 | } 37 | if ("beforeCreateRequest" in hook) { 38 | this.registerBeforeCreateRequestHook(hook); 39 | } 40 | if ("beforeRequest" in hook) { 41 | this.registerBeforeRequestHook(hook); 42 | } 43 | if ("afterSuccess" in hook) { 44 | this.registerAfterSuccessHook(hook); 45 | } 46 | if ("afterError" in hook) { 47 | this.registerAfterErrorHook(hook); 48 | } 49 | } 50 | initHooks(this); 51 | } 52 | 53 | registerSDKInitHook(hook: SDKInitHook) { 54 | this.sdkInitHooks.push(hook); 55 | } 56 | 57 | registerBeforeCreateRequestHook(hook: BeforeCreateRequestHook) { 58 | this.beforeCreateRequestHooks.push(hook); 59 | } 60 | 61 | registerBeforeRequestHook(hook: BeforeRequestHook) { 62 | this.beforeRequestHooks.push(hook); 63 | } 64 | 65 | registerAfterSuccessHook(hook: AfterSuccessHook) { 66 | this.afterSuccessHooks.push(hook); 67 | } 68 | 69 | registerAfterErrorHook(hook: AfterErrorHook) { 70 | this.afterErrorHooks.push(hook); 71 | } 72 | 73 | sdkInit(opts: SDKInitOptions): SDKInitOptions { 74 | return this.sdkInitHooks.reduce((opts, hook) => hook.sdkInit(opts), opts); 75 | } 76 | 77 | beforeCreateRequest( 78 | hookCtx: BeforeCreateRequestContext, 79 | input: RequestInput, 80 | ): RequestInput { 81 | let inp = input; 82 | 83 | for (const hook of this.beforeCreateRequestHooks) { 84 | inp = hook.beforeCreateRequest(hookCtx, inp); 85 | } 86 | 87 | return inp; 88 | } 89 | 90 | async beforeRequest( 91 | hookCtx: BeforeRequestContext, 92 | request: Request, 93 | ): Promise { 94 | let req = request; 95 | 96 | for (const hook of this.beforeRequestHooks) { 97 | req = await hook.beforeRequest(hookCtx, req); 98 | } 99 | 100 | return req; 101 | } 102 | 103 | async afterSuccess( 104 | hookCtx: AfterSuccessContext, 105 | response: Response, 106 | ): Promise { 107 | let res = response; 108 | 109 | for (const hook of this.afterSuccessHooks) { 110 | res = await hook.afterSuccess(hookCtx, res); 111 | } 112 | 113 | return res; 114 | } 115 | 116 | async afterError( 117 | hookCtx: AfterErrorContext, 118 | response: Response | null, 119 | error: unknown, 120 | ): Promise<{ response: Response | null; error: unknown }> { 121 | let res = response; 122 | let err = error; 123 | 124 | for (const hook of this.afterErrorHooks) { 125 | const result = await hook.afterError(hookCtx, res, err); 126 | res = result.response; 127 | err = result.error; 128 | } 129 | 130 | return { response: res, error: err }; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export * from "./hooks.js"; 6 | export * from "./types.js"; 7 | -------------------------------------------------------------------------------- /src/hooks/registration.ts: -------------------------------------------------------------------------------- 1 | import { Hooks } from "./types.js"; 2 | 3 | import { LoggerHook } from "./custom/LoggerHook.js"; 4 | import { SplitPdfHook } from "./custom/SplitPdfHook.js"; 5 | import { HttpsCheckHook } from "./custom/HttpsCheckHook.js"; 6 | import { FixArrayParamsHook } from "./custom/FixArrayParamsHook.js"; 7 | 8 | /* 9 | * This file is only ever generated once on the first generation and then is free to be modified. 10 | * Any hooks you wish to add should be registered in the initHooks function. Feel free to define them 11 | * in this file or in separate files in the hooks folder. 12 | */ 13 | 14 | export function initHooks(hooks: Hooks) { 15 | // Add hooks by calling hooks.register{ClientInit/BeforeRequest/AfterSuccess/AfterError}Hook 16 | // with an instance of a hook that implements that specific Hook interface 17 | // Hooks are registered per SDK instance, and are valid for the lifetime of the SDK instance 18 | 19 | // Initialize hooks 20 | const loggerHook = new LoggerHook(); 21 | const splitPdfHook = new SplitPdfHook(); 22 | const httpsCheckHook = new HttpsCheckHook(); 23 | const fixArrayParamsHook = new FixArrayParamsHook(); 24 | 25 | // NOTE: logger_hook should stay registered last as logs the status of 26 | // request and whether it will be retried which can be changed by e.g. split_pdf_hook 27 | 28 | // Register SDK init hooks 29 | hooks.registerSDKInitHook(httpsCheckHook); 30 | hooks.registerSDKInitHook(splitPdfHook); 31 | 32 | // Register before request hooks 33 | hooks.registerBeforeRequestHook(fixArrayParamsHook) 34 | hooks.registerBeforeRequestHook(splitPdfHook); 35 | 36 | // Register after success hooks 37 | hooks.registerAfterSuccessHook(splitPdfHook); 38 | hooks.registerAfterSuccessHook(loggerHook) 39 | 40 | // Register after error hooks 41 | hooks.registerAfterErrorHook(splitPdfHook); 42 | hooks.registerAfterErrorHook(loggerHook); 43 | } 44 | -------------------------------------------------------------------------------- /src/hooks/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { HTTPClient, RequestInput } from "../lib/http.js"; 6 | import { RetryConfig } from "../lib/retries.js"; 7 | import { SecurityState } from "../lib/security.js"; 8 | 9 | export type HookContext = { 10 | baseURL: string | URL; 11 | operationID: string; 12 | oAuth2Scopes: string[] | null; 13 | securitySource?: any | (() => Promise); 14 | retryConfig: RetryConfig; 15 | resolvedSecurity: SecurityState | null; 16 | }; 17 | 18 | export type Awaitable = T | Promise; 19 | 20 | export type SDKInitOptions = { 21 | baseURL: URL | null; 22 | client: HTTPClient; 23 | }; 24 | 25 | export type BeforeCreateRequestContext = HookContext & {}; 26 | export type BeforeRequestContext = HookContext & {}; 27 | export type AfterSuccessContext = HookContext & {}; 28 | export type AfterErrorContext = HookContext & {}; 29 | 30 | /** 31 | * SDKInitHook is called when the SDK is initializing. The 32 | * hook can return a new baseURL and HTTP client to be used by the SDK. 33 | */ 34 | export interface SDKInitHook { 35 | sdkInit: (opts: SDKInitOptions) => SDKInitOptions; 36 | } 37 | 38 | export interface BeforeCreateRequestHook { 39 | /** 40 | * A hook that is called before the SDK creates a `Request` object. The hook 41 | * can modify how a request is constructed since certain modifications, like 42 | * changing the request URL, cannot be done on a request object directly. 43 | */ 44 | beforeCreateRequest: ( 45 | hookCtx: BeforeCreateRequestContext, 46 | input: RequestInput, 47 | ) => RequestInput; 48 | } 49 | 50 | export interface BeforeRequestHook { 51 | /** 52 | * A hook that is called before the SDK sends a request. The hook can 53 | * introduce instrumentation code such as logging, tracing and metrics or 54 | * replace the request before it is sent or throw an error to stop the 55 | * request from being sent. 56 | */ 57 | beforeRequest: ( 58 | hookCtx: BeforeRequestContext, 59 | request: Request, 60 | ) => Awaitable; 61 | } 62 | 63 | export interface AfterSuccessHook { 64 | /** 65 | * A hook that is called after the SDK receives a response. The hook can 66 | * introduce instrumentation code such as logging, tracing and metrics or 67 | * modify the response before it is handled or throw an error to stop the 68 | * response from being handled. 69 | */ 70 | afterSuccess: ( 71 | hookCtx: AfterSuccessContext, 72 | response: Response, 73 | ) => Awaitable; 74 | } 75 | 76 | export interface AfterErrorHook { 77 | /** 78 | * A hook that is called after the SDK encounters an error, or a 79 | * non-successful response. The hook can introduce instrumentation code such 80 | * as logging, tracing and metrics or modify the response or error values. 81 | */ 82 | afterError: ( 83 | hookCtx: AfterErrorContext, 84 | response: Response | null, 85 | error: unknown, 86 | ) => Awaitable<{ 87 | response: Response | null; 88 | error: unknown; 89 | }>; 90 | } 91 | 92 | export interface Hooks { 93 | /** Registers a hook to be used by the SDK for initialization event. */ 94 | registerSDKInitHook(hook: SDKInitHook): void; 95 | /** Registers a hook to be used by the SDK for to modify `Request` construction. */ 96 | registerBeforeCreateRequestHook(hook: BeforeCreateRequestHook): void; 97 | /** Registers a hook to be used by the SDK for the before request event. */ 98 | registerBeforeRequestHook(hook: BeforeRequestHook): void; 99 | /** Registers a hook to be used by the SDK for the after success event. */ 100 | registerAfterSuccessHook(hook: AfterSuccessHook): void; 101 | /** Registers a hook to be used by the SDK for the after error event. */ 102 | registerAfterErrorHook(hook: AfterErrorHook): void; 103 | } 104 | 105 | export type Hook = 106 | | SDKInitHook 107 | | BeforeCreateRequestHook 108 | | BeforeRequestHook 109 | | AfterSuccessHook 110 | | AfterErrorHook; 111 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export * from "./lib/config.js"; 6 | export * as files from "./lib/files.js"; 7 | export * from "./sdk/sdk.js"; 8 | -------------------------------------------------------------------------------- /src/lib/base64.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as z from "zod"; 6 | 7 | export function bytesToBase64(u8arr: Uint8Array): string { 8 | return btoa(String.fromCodePoint(...u8arr)); 9 | } 10 | 11 | export function bytesFromBase64(encoded: string): Uint8Array { 12 | return Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0)); 13 | } 14 | 15 | export function stringToBytes(str: string): Uint8Array { 16 | return new TextEncoder().encode(str); 17 | } 18 | 19 | export function stringFromBytes(u8arr: Uint8Array): string { 20 | return new TextDecoder().decode(u8arr); 21 | } 22 | 23 | export function stringToBase64(str: string): string { 24 | return bytesToBase64(stringToBytes(str)); 25 | } 26 | 27 | export function stringFromBase64(b64str: string): string { 28 | return stringFromBytes(bytesFromBase64(b64str)); 29 | } 30 | 31 | export const zodOutbound = z 32 | .instanceof(Uint8Array) 33 | .or(z.string().transform(stringToBytes)); 34 | 35 | export const zodInbound = z 36 | .instanceof(Uint8Array) 37 | .or(z.string().transform(bytesFromBase64)); 38 | -------------------------------------------------------------------------------- /src/lib/config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as shared from "../sdk/models/shared/index.js"; 6 | import { HTTPClient } from "./http.js"; 7 | import { Logger } from "./logger.js"; 8 | import { RetryConfig } from "./retries.js"; 9 | import { Params, pathToFunc } from "./url.js"; 10 | 11 | /** 12 | * Serverless SaaS API 13 | */ 14 | export const ServerSaasApi = "saas-api"; 15 | /** 16 | * Development server 17 | */ 18 | export const ServerDevelopment = "development"; 19 | /** 20 | * Contains the list of servers available to the SDK 21 | */ 22 | export const ServerList = { 23 | [ServerSaasApi]: "https://api.unstructuredapp.io", 24 | [ServerDevelopment]: "http://localhost:8000", 25 | } as const; 26 | 27 | export type SDKOptions = { 28 | /** 29 | * The security details required to authenticate the SDK 30 | */ 31 | security?: shared.Security | (() => Promise) | undefined; 32 | 33 | httpClient?: HTTPClient; 34 | /** 35 | * Allows overriding the default server used by the SDK 36 | */ 37 | server?: keyof typeof ServerList | undefined; 38 | /** 39 | * Allows overriding the default server URL used by the SDK 40 | */ 41 | serverURL?: string | undefined; 42 | /** 43 | * Allows overriding the default retry config used by the SDK 44 | */ 45 | retryConfig?: RetryConfig; 46 | timeoutMs?: number; 47 | debugLogger?: Logger; 48 | }; 49 | 50 | export function serverURLFromOptions(options: SDKOptions): URL | null { 51 | let serverURL = options.serverURL; 52 | 53 | const params: Params = {}; 54 | 55 | if (!serverURL) { 56 | const server = options.server ?? ServerSaasApi; 57 | serverURL = ServerList[server] || ""; 58 | } 59 | 60 | const u = pathToFunc(serverURL)(params); 61 | return new URL(u); 62 | } 63 | 64 | export const SDK_METADATA = { 65 | language: "typescript", 66 | openapiDocVersion: "1.1.17", 67 | sdkVersion: "0.24.4", 68 | genVersion: "2.610.0", 69 | userAgent: 70 | "speakeasy-sdk/typescript 0.24.4 2.610.0 1.1.17 unstructured-client", 71 | } as const; 72 | -------------------------------------------------------------------------------- /src/lib/dlv.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | /* 6 | MIT License 7 | 8 | Copyright (c) 2024 Jason Miller (http://jasonformat.com) 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | this software and associated documentation files (the "Software"), to deal in 12 | the Software without restriction, including without limitation the rights to 13 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 14 | the Software, and to permit persons to whom the Software is furnished to do so, 15 | subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 22 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 23 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 24 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | /** 29 | * @param obj The object to walk 30 | * @param key The key path to walk the object with 31 | * @param def A default value to return if the result is undefined 32 | * 33 | * @example 34 | * dlv(obj, "a.b.c.d") 35 | * @example 36 | * dlv(object, ["a", "b", "c", "d"]) 37 | * @example 38 | * dlv(object, "foo.bar.baz", "Hello, default value!") 39 | */ 40 | export function dlv( 41 | obj: any, 42 | key: string | string[], 43 | def?: T, 44 | p?: number, 45 | undef?: never, 46 | ): T | undefined { 47 | key = Array.isArray(key) ? key : key.split("."); 48 | for (p = 0; p < key.length; p++) { 49 | const k = key[p]; 50 | obj = k != null && obj ? obj[k] : undef; 51 | } 52 | return obj === undef ? def : obj; 53 | } 54 | -------------------------------------------------------------------------------- /src/lib/files.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | /** 6 | * Consumes a stream and returns a concatenated array buffer. Useful in 7 | * situations where we need to read the whole file because it forms part of a 8 | * larger payload containing other fields, and we can't modify the underlying 9 | * request structure. 10 | */ 11 | export async function readableStreamToArrayBuffer( 12 | readable: ReadableStream, 13 | ): Promise { 14 | const reader = readable.getReader(); 15 | const chunks: Uint8Array[] = []; 16 | 17 | let totalLength = 0; 18 | let done = false; 19 | 20 | while (!done) { 21 | const { value, done: doneReading } = await reader.read(); 22 | 23 | if (doneReading) { 24 | done = true; 25 | } else { 26 | chunks.push(value); 27 | totalLength += value.length; 28 | } 29 | } 30 | 31 | const concatenatedChunks = new Uint8Array(totalLength); 32 | let offset = 0; 33 | 34 | for (const chunk of chunks) { 35 | concatenatedChunks.set(chunk, offset); 36 | offset += chunk.length; 37 | } 38 | 39 | return concatenatedChunks.buffer as ArrayBuffer; 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/is-plain-object.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | /* 6 | MIT License 7 | 8 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | this software and associated documentation files (the "Software"), to deal in 12 | the Software without restriction, including without limitation the rights to 13 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 14 | the Software, and to permit persons to whom the Software is furnished to do so, 15 | subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 22 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 23 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 24 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | // Taken from https://github.com/sindresorhus/is-plain-obj/blob/97f38e8836f86a642cce98fc6ab3058bc36df181/index.js 29 | 30 | export function isPlainObject(value: unknown): value is object { 31 | if (typeof value !== "object" || value === null) { 32 | return false; 33 | } 34 | 35 | const prototype = Object.getPrototypeOf(value); 36 | return ( 37 | (prototype === null || 38 | prototype === Object.prototype || 39 | Object.getPrototypeOf(prototype) === null) && 40 | !(Symbol.toStringTag in value) && 41 | !(Symbol.iterator in value) 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/logger.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export interface Logger { 6 | group(label?: string): void; 7 | groupEnd(): void; 8 | log(message: any, ...args: any[]): void; 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/primitives.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | class InvariantError extends Error { 6 | constructor(message: string) { 7 | super(message); 8 | this.name = "InvariantError"; 9 | } 10 | } 11 | 12 | export function invariant( 13 | condition: unknown, 14 | message: string, 15 | ): asserts condition { 16 | if (!condition) { 17 | throw new InvariantError(message); 18 | } 19 | } 20 | 21 | export type ExactPartial = { 22 | [P in keyof T]?: T[P] | undefined; 23 | }; 24 | 25 | export type Remap = { 26 | [k in keyof Inp as Mapping[k] extends string /* if we have a string mapping for this key then use it */ 27 | ? Mapping[k] 28 | : Mapping[k] extends null /* if the mapping is to `null` then drop the key */ 29 | ? never 30 | : k /* otherwise keep the key as-is */]: Inp[k]; 31 | }; 32 | 33 | /** 34 | * Converts or omits an object's keys according to a mapping. 35 | * 36 | * @param inp An object whose keys will be remapped 37 | * @param mappings A mapping of original keys to new keys. If a key is not present in the mapping, it will be left as is. If a key is mapped to `null`, it will be removed in the resulting object. 38 | * @returns A new object with keys remapped or omitted according to the mappings 39 | */ 40 | export function remap< 41 | Inp extends Record, 42 | const Mapping extends { [k in keyof Inp]?: string | null }, 43 | >(inp: Inp, mappings: Mapping): Remap { 44 | let out: any = {}; 45 | 46 | if (!Object.keys(mappings).length) { 47 | out = inp; 48 | return out; 49 | } 50 | 51 | for (const [k, v] of Object.entries(inp)) { 52 | const j = mappings[k]; 53 | if (j === null) { 54 | continue; 55 | } 56 | out[j ?? k] = v; 57 | } 58 | 59 | return out; 60 | } 61 | 62 | export function combineSignals( 63 | ...signals: Array 64 | ): AbortSignal | null { 65 | const filtered: AbortSignal[] = []; 66 | for (const signal of signals) { 67 | if (signal) { 68 | filtered.push(signal); 69 | } 70 | } 71 | 72 | switch (filtered.length) { 73 | case 0: 74 | case 1: 75 | return filtered[0] || null; 76 | default: 77 | if ("any" in AbortSignal && typeof AbortSignal.any === "function") { 78 | return AbortSignal.any(filtered); 79 | } 80 | return abortSignalAny(filtered); 81 | } 82 | } 83 | 84 | export function abortSignalAny(signals: AbortSignal[]): AbortSignal { 85 | const controller = new AbortController(); 86 | const result = controller.signal; 87 | if (!signals.length) { 88 | return controller.signal; 89 | } 90 | 91 | if (signals.length === 1) { 92 | return signals[0] || controller.signal; 93 | } 94 | 95 | for (const signal of signals) { 96 | if (signal.aborted) { 97 | return signal; 98 | } 99 | } 100 | 101 | function abort(this: AbortSignal) { 102 | controller.abort(this.reason); 103 | clean(); 104 | } 105 | 106 | const signalRefs: WeakRef[] = []; 107 | function clean() { 108 | for (const signalRef of signalRefs) { 109 | const signal = signalRef.deref(); 110 | if (signal) { 111 | signal.removeEventListener("abort", abort); 112 | } 113 | } 114 | } 115 | 116 | for (const signal of signals) { 117 | signalRefs.push(new WeakRef(signal)); 118 | signal.addEventListener("abort", abort); 119 | } 120 | 121 | return result; 122 | } 123 | 124 | export function compactMap( 125 | values: Record, 126 | ): Record { 127 | const out: Record = {}; 128 | 129 | for (const [k, v] of Object.entries(values)) { 130 | if (typeof v !== "undefined") { 131 | out[k] = v; 132 | } 133 | } 134 | 135 | return out; 136 | } 137 | 138 | export function allRequired>( 139 | v: V, 140 | ): 141 | | { 142 | [K in keyof V]: NonNullable; 143 | } 144 | | undefined { 145 | if (Object.values(v).every((x) => x == null)) { 146 | return void 0; 147 | } 148 | 149 | return v as ReturnType>; 150 | } 151 | -------------------------------------------------------------------------------- /src/lib/retries.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { isConnectionError, isTimeoutError } from "./http.js"; 6 | 7 | export type BackoffStrategy = { 8 | initialInterval: number; 9 | maxInterval: number; 10 | exponent: number; 11 | maxElapsedTime: number; 12 | }; 13 | 14 | const defaultBackoff: BackoffStrategy = { 15 | initialInterval: 500, 16 | maxInterval: 60000, 17 | exponent: 1.5, 18 | maxElapsedTime: 3600000, 19 | }; 20 | 21 | export type RetryConfig = 22 | | { strategy: "none" } 23 | | { 24 | strategy: "backoff"; 25 | backoff?: BackoffStrategy; 26 | retryConnectionErrors?: boolean; 27 | }; 28 | 29 | /** 30 | * PermanentError is an error that is not recoverable. Throwing this error will 31 | * cause a retry loop to terminate. 32 | */ 33 | export class PermanentError extends Error { 34 | /** The underlying cause of the error. */ 35 | override readonly cause: unknown; 36 | 37 | constructor(message: string, options?: { cause?: unknown }) { 38 | let msg = message; 39 | if (options?.cause) { 40 | msg += `: ${options.cause}`; 41 | } 42 | 43 | super(msg, options); 44 | this.name = "PermanentError"; 45 | // In older runtimes, the cause field would not have been assigned through 46 | // the super() call. 47 | if (typeof this.cause === "undefined") { 48 | this.cause = options?.cause; 49 | } 50 | 51 | Object.setPrototypeOf(this, PermanentError.prototype); 52 | } 53 | } 54 | 55 | /** 56 | * TemporaryError is an error is used to signal that an HTTP request can be 57 | * retried as part of a retry loop. If retry attempts are exhausted and this 58 | * error is thrown, the response will be returned to the caller. 59 | */ 60 | export class TemporaryError extends Error { 61 | response: Response; 62 | 63 | constructor(message: string, response: Response) { 64 | super(message); 65 | this.response = response; 66 | this.name = "TemporaryError"; 67 | 68 | Object.setPrototypeOf(this, TemporaryError.prototype); 69 | } 70 | } 71 | 72 | export async function retry( 73 | fetchFn: () => Promise, 74 | options: { 75 | config: RetryConfig; 76 | statusCodes: string[]; 77 | }, 78 | ): Promise { 79 | switch (options.config.strategy) { 80 | case "backoff": 81 | return retryBackoff( 82 | wrapFetcher(fetchFn, { 83 | statusCodes: options.statusCodes, 84 | retryConnectionErrors: !!options.config.retryConnectionErrors, 85 | }), 86 | options.config.backoff ?? defaultBackoff, 87 | ); 88 | default: 89 | return await fetchFn(); 90 | } 91 | } 92 | 93 | function wrapFetcher( 94 | fn: () => Promise, 95 | options: { 96 | statusCodes: string[]; 97 | retryConnectionErrors: boolean; 98 | }, 99 | ): () => Promise { 100 | return async () => { 101 | try { 102 | const res = await fn(); 103 | if (isRetryableResponse(res, options.statusCodes)) { 104 | throw new TemporaryError( 105 | "Response failed with retryable status code", 106 | res, 107 | ); 108 | } 109 | 110 | return res; 111 | } catch (err: unknown) { 112 | if (err instanceof TemporaryError) { 113 | throw err; 114 | } 115 | 116 | if ( 117 | options.retryConnectionErrors && 118 | (isTimeoutError(err) || isConnectionError(err)) 119 | ) { 120 | throw err; 121 | } 122 | 123 | throw new PermanentError("Permanent error", { cause: err }); 124 | } 125 | }; 126 | } 127 | 128 | const codeRangeRE = new RegExp("^[0-9]xx$", "i"); 129 | 130 | function isRetryableResponse(res: Response, statusCodes: string[]): boolean { 131 | const actual = `${res.status}`; 132 | 133 | return statusCodes.some((code) => { 134 | if (!codeRangeRE.test(code)) { 135 | return code === actual; 136 | } 137 | 138 | const expectFamily = code.charAt(0); 139 | if (!expectFamily) { 140 | throw new Error("Invalid status code range"); 141 | } 142 | 143 | const actualFamily = actual.charAt(0); 144 | if (!actualFamily) { 145 | throw new Error(`Invalid response status code: ${actual}`); 146 | } 147 | 148 | return actualFamily === expectFamily; 149 | }); 150 | } 151 | 152 | async function retryBackoff( 153 | fn: () => Promise, 154 | strategy: BackoffStrategy, 155 | ): Promise { 156 | const { maxElapsedTime, initialInterval, exponent, maxInterval } = strategy; 157 | 158 | const start = Date.now(); 159 | let x = 0; 160 | 161 | while (true) { 162 | try { 163 | const res = await fn(); 164 | return res; 165 | } catch (err: unknown) { 166 | if (err instanceof PermanentError) { 167 | throw err.cause; 168 | } 169 | const elapsed = Date.now() - start; 170 | if (elapsed > maxElapsedTime) { 171 | if (err instanceof TemporaryError) { 172 | return err.response; 173 | } 174 | 175 | throw err; 176 | } 177 | 178 | let retryInterval = 0; 179 | if (err instanceof TemporaryError) { 180 | retryInterval = retryIntervalFromResponse(err.response); 181 | } 182 | 183 | if (retryInterval <= 0) { 184 | retryInterval = 185 | initialInterval * Math.pow(x, exponent) + Math.random() * 1000; 186 | } 187 | 188 | const d = Math.min(retryInterval, maxInterval); 189 | 190 | await delay(d); 191 | x++; 192 | } 193 | } 194 | } 195 | 196 | function retryIntervalFromResponse(res: Response): number { 197 | const retryVal = res.headers.get("retry-after") || ""; 198 | if (!retryVal) { 199 | return 0; 200 | } 201 | 202 | const parsedNumber = Number(retryVal); 203 | if (Number.isInteger(parsedNumber)) { 204 | return parsedNumber * 1000; 205 | } 206 | 207 | const parsedDate = Date.parse(retryVal); 208 | if (Number.isInteger(parsedDate)) { 209 | const deltaMS = parsedDate - Date.now(); 210 | return deltaMS > 0 ? Math.ceil(deltaMS) : 0; 211 | } 212 | 213 | return 0; 214 | } 215 | 216 | async function delay(delay: number): Promise { 217 | return new Promise((resolve) => setTimeout(resolve, delay)); 218 | } 219 | -------------------------------------------------------------------------------- /src/lib/schemas.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { 6 | output, 7 | ZodEffects, 8 | ZodError, 9 | ZodObject, 10 | ZodRawShape, 11 | ZodTypeAny, 12 | } from "zod"; 13 | import { SDKValidationError } from "../sdk/models/errors/sdkvalidationerror.js"; 14 | import { ERR, OK, Result } from "../sdk/types/fp.js"; 15 | 16 | /** 17 | * Utility function that executes some code which may throw a ZodError. It 18 | * intercepts this error and converts it to an SDKValidationError so as to not 19 | * leak Zod implementation details to user code. 20 | */ 21 | export function parse( 22 | rawValue: Inp, 23 | fn: (value: Inp) => Out, 24 | errorMessage: string, 25 | ): Out { 26 | try { 27 | return fn(rawValue); 28 | } catch (err) { 29 | if (err instanceof ZodError) { 30 | throw new SDKValidationError(errorMessage, err, rawValue); 31 | } 32 | throw err; 33 | } 34 | } 35 | 36 | /** 37 | * Utility function that executes some code which may result in a ZodError. It 38 | * intercepts this error and converts it to an SDKValidationError so as to not 39 | * leak Zod implementation details to user code. 40 | */ 41 | export function safeParse( 42 | rawValue: Inp, 43 | fn: (value: Inp) => Out, 44 | errorMessage: string, 45 | ): Result { 46 | try { 47 | return OK(fn(rawValue)); 48 | } catch (err) { 49 | return ERR(new SDKValidationError(errorMessage, err, rawValue)); 50 | } 51 | } 52 | 53 | export function collectExtraKeys< 54 | Shape extends ZodRawShape, 55 | Catchall extends ZodTypeAny, 56 | K extends string, 57 | >( 58 | obj: ZodObject, 59 | extrasKey: K, 60 | optional: boolean, 61 | ): ZodEffects< 62 | typeof obj, 63 | & output> 64 | & { 65 | [k in K]: Record>; 66 | } 67 | > { 68 | return obj.transform((val) => { 69 | const extras: Record> = {}; 70 | const { shape } = obj; 71 | for (const [key] of Object.entries(val)) { 72 | if (key in shape) { 73 | continue; 74 | } 75 | 76 | const v = val[key]; 77 | if (typeof v === "undefined") { 78 | continue; 79 | } 80 | 81 | extras[key] = v; 82 | delete val[key]; 83 | } 84 | 85 | if (optional && Object.keys(extras).length === 0) { 86 | return val; 87 | } 88 | 89 | return { ...val, [extrasKey]: extras }; 90 | }); 91 | } 92 | -------------------------------------------------------------------------------- /src/lib/security.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as shared from "../sdk/models/shared/index.js"; 6 | 7 | type OAuth2PasswordFlow = { 8 | username: string; 9 | password?: string | undefined; 10 | clientID: string; 11 | clientSecret?: string | undefined; 12 | tokenURL: string; 13 | }; 14 | 15 | export enum SecurityErrorCode { 16 | Incomplete = "incomplete", 17 | UnrecognisedSecurityType = "unrecognized_security_type", 18 | } 19 | 20 | export class SecurityError extends Error { 21 | constructor( 22 | public code: SecurityErrorCode, 23 | message: string, 24 | ) { 25 | super(message); 26 | this.name = "SecurityError"; 27 | } 28 | 29 | static incomplete(): SecurityError { 30 | return new SecurityError( 31 | SecurityErrorCode.Incomplete, 32 | "Security requirements not met in order to perform the operation", 33 | ); 34 | } 35 | static unrecognizedType(type: string): SecurityError { 36 | return new SecurityError( 37 | SecurityErrorCode.UnrecognisedSecurityType, 38 | `Unrecognised security type: ${type}`, 39 | ); 40 | } 41 | } 42 | 43 | export type SecurityState = { 44 | basic: { username?: string | undefined; password?: string | undefined }; 45 | headers: Record; 46 | queryParams: Record; 47 | cookies: Record; 48 | oauth2: ({ type: "password" } & OAuth2PasswordFlow) | { type: "none" }; 49 | }; 50 | 51 | type SecurityInputBasic = { 52 | type: "http:basic"; 53 | value: 54 | | { username?: string | undefined; password?: string | undefined } 55 | | null 56 | | undefined; 57 | }; 58 | 59 | type SecurityInputBearer = { 60 | type: "http:bearer"; 61 | value: string | null | undefined; 62 | fieldName: string; 63 | }; 64 | 65 | type SecurityInputAPIKey = { 66 | type: "apiKey:header" | "apiKey:query" | "apiKey:cookie"; 67 | value: string | null | undefined; 68 | fieldName: string; 69 | }; 70 | 71 | type SecurityInputOIDC = { 72 | type: "openIdConnect"; 73 | value: string | null | undefined; 74 | fieldName: string; 75 | }; 76 | 77 | type SecurityInputOAuth2 = { 78 | type: "oauth2"; 79 | value: string | null | undefined; 80 | fieldName: string; 81 | }; 82 | 83 | type SecurityInputOAuth2ClientCredentials = { 84 | type: "oauth2:client_credentials"; 85 | value: 86 | | { clientID?: string | undefined; clientSecret?: string | undefined } 87 | | null 88 | | string 89 | | undefined; 90 | fieldName?: string; 91 | }; 92 | 93 | type SecurityInputOAuth2PasswordCredentials = { 94 | type: "oauth2:password"; 95 | value: 96 | | string 97 | | null 98 | | undefined; 99 | fieldName?: string; 100 | }; 101 | 102 | type SecurityInputCustom = { 103 | type: "http:custom"; 104 | value: any | null | undefined; 105 | fieldName?: string; 106 | }; 107 | 108 | export type SecurityInput = 109 | | SecurityInputBasic 110 | | SecurityInputBearer 111 | | SecurityInputAPIKey 112 | | SecurityInputOAuth2 113 | | SecurityInputOAuth2ClientCredentials 114 | | SecurityInputOAuth2PasswordCredentials 115 | | SecurityInputOIDC 116 | | SecurityInputCustom; 117 | 118 | export function resolveSecurity( 119 | ...options: SecurityInput[][] 120 | ): SecurityState | null { 121 | const state: SecurityState = { 122 | basic: {}, 123 | headers: {}, 124 | queryParams: {}, 125 | cookies: {}, 126 | oauth2: { type: "none" }, 127 | }; 128 | 129 | const option = options.find((opts) => { 130 | return opts.every((o) => { 131 | if (o.value == null) { 132 | return false; 133 | } else if (o.type === "http:basic") { 134 | return o.value.username != null || o.value.password != null; 135 | } else if (o.type === "http:custom") { 136 | return null; 137 | } else if (o.type === "oauth2:password") { 138 | return ( 139 | typeof o.value === "string" && !!o.value 140 | ); 141 | } else if (o.type === "oauth2:client_credentials") { 142 | if (typeof o.value == "string") { 143 | return !!o.value; 144 | } 145 | return o.value.clientID != null || o.value.clientSecret != null; 146 | } else if (typeof o.value === "string") { 147 | return !!o.value; 148 | } else { 149 | throw new Error( 150 | `Unrecognized security type: ${o.type} (value type: ${typeof o 151 | .value})`, 152 | ); 153 | } 154 | }); 155 | }); 156 | if (option == null) { 157 | return null; 158 | } 159 | 160 | option.forEach((spec) => { 161 | if (spec.value == null) { 162 | return; 163 | } 164 | 165 | const { type } = spec; 166 | 167 | switch (type) { 168 | case "apiKey:header": 169 | state.headers[spec.fieldName] = spec.value; 170 | break; 171 | case "apiKey:query": 172 | state.queryParams[spec.fieldName] = spec.value; 173 | break; 174 | case "apiKey:cookie": 175 | state.cookies[spec.fieldName] = spec.value; 176 | break; 177 | case "http:basic": 178 | applyBasic(state, spec); 179 | break; 180 | case "http:custom": 181 | break; 182 | case "http:bearer": 183 | applyBearer(state, spec); 184 | break; 185 | case "oauth2": 186 | applyBearer(state, spec); 187 | break; 188 | case "oauth2:password": 189 | applyBearer(state, spec); 190 | break; 191 | case "oauth2:client_credentials": 192 | break; 193 | case "openIdConnect": 194 | applyBearer(state, spec); 195 | break; 196 | default: 197 | spec satisfies never; 198 | throw SecurityError.unrecognizedType(type); 199 | } 200 | }); 201 | 202 | return state; 203 | } 204 | 205 | function applyBasic( 206 | state: SecurityState, 207 | spec: SecurityInputBasic, 208 | ) { 209 | if (spec.value == null) { 210 | return; 211 | } 212 | 213 | state.basic = spec.value; 214 | } 215 | 216 | function applyBearer( 217 | state: SecurityState, 218 | spec: 219 | | SecurityInputBearer 220 | | SecurityInputOAuth2 221 | | SecurityInputOIDC 222 | | SecurityInputOAuth2PasswordCredentials, 223 | ) { 224 | if (typeof spec.value !== "string" || !spec.value) { 225 | return; 226 | } 227 | 228 | let value = spec.value; 229 | if (value.slice(0, 7).toLowerCase() !== "bearer ") { 230 | value = `Bearer ${value}`; 231 | } 232 | 233 | if (spec.fieldName !== undefined) { 234 | state.headers[spec.fieldName] = value; 235 | } 236 | } 237 | 238 | export function resolveGlobalSecurity( 239 | security: Partial | null | undefined, 240 | ): SecurityState | null { 241 | return resolveSecurity( 242 | [ 243 | { 244 | fieldName: "unstructured-api-key", 245 | type: "apiKey:header", 246 | value: security?.apiKeyAuth, 247 | }, 248 | ], 249 | ); 250 | } 251 | 252 | export async function extractSecurity< 253 | T extends string | Record, 254 | >(sec: T | (() => Promise) | undefined): Promise { 255 | if (sec == null) { 256 | return; 257 | } 258 | 259 | return typeof sec === "function" ? sec() : sec; 260 | } 261 | -------------------------------------------------------------------------------- /src/lib/url.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | const hasOwn = Object.prototype.hasOwnProperty; 6 | 7 | export type Params = Partial>; 8 | 9 | export function pathToFunc( 10 | pathPattern: string, 11 | options?: { charEncoding?: "percent" | "none" }, 12 | ): (params?: Params) => string { 13 | const paramRE = /\{([a-zA-Z0-9_]+?)\}/g; 14 | 15 | return function buildURLPath(params: Record = {}): string { 16 | return pathPattern.replace(paramRE, function (_, placeholder) { 17 | if (!hasOwn.call(params, placeholder)) { 18 | throw new Error(`Parameter '${placeholder}' is required`); 19 | } 20 | 21 | const value = params[placeholder]; 22 | if (typeof value !== "string" && typeof value !== "number") { 23 | throw new Error( 24 | `Parameter '${placeholder}' must be a string or number`, 25 | ); 26 | } 27 | 28 | return options?.charEncoding === "percent" 29 | ? encodeURIComponent(`${value}`) 30 | : `${value}`; 31 | }); 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/mcp-server/build.mts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { build } from "bun"; 4 | 5 | const entrypoint = "./src/mcp-server/mcp-server.ts"; 6 | 7 | await build({ 8 | entrypoints: [entrypoint], 9 | outdir: "./bin", 10 | sourcemap: "linked", 11 | target: "node", 12 | format: "esm", 13 | minify: false, 14 | throw: true, 15 | banner: "#!/usr/bin/env node", 16 | }); 17 | -------------------------------------------------------------------------------- /src/mcp-server/cli.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { CommandContext, StricliProcess } from "@stricli/core"; 6 | 7 | export interface LocalContext extends CommandContext { 8 | readonly process: StricliProcess; 9 | } 10 | 11 | export function buildContext(process: NodeJS.Process): LocalContext { 12 | return { process: process as StricliProcess }; 13 | } 14 | -------------------------------------------------------------------------------- /src/mcp-server/cli/start/command.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { buildCommand } from "@stricli/core"; 6 | import * as z from "zod"; 7 | import { ServerList } from "../../../lib/config.js"; 8 | import { consoleLoggerLevels } from "../../console-logger.js"; 9 | import { mcpScopes } from "../../scopes.js"; 10 | 11 | export const startCommand = buildCommand({ 12 | loader: async () => { 13 | const { main } = await import("./impl.js"); 14 | return main; 15 | }, 16 | parameters: { 17 | flags: { 18 | transport: { 19 | kind: "enum", 20 | brief: "The transport to use for communicating with the server", 21 | default: "stdio", 22 | values: ["stdio", "sse"], 23 | }, 24 | port: { 25 | kind: "parsed", 26 | brief: "The port to use when the SSE transport is enabled", 27 | default: "2718", 28 | parse: (val: string) => 29 | z.coerce.number().int().gte(0).lt(65536).parse(val), 30 | }, 31 | tool: { 32 | kind: "parsed", 33 | brief: "Specify tools to mount on the server", 34 | optional: true, 35 | variadic: true, 36 | parse: (value) => { 37 | return z.string().parse(value); 38 | }, 39 | }, 40 | ...(mcpScopes.length 41 | ? { 42 | scope: { 43 | kind: "enum", 44 | brief: 45 | "Mount tools/resources that match given scope (repeatable flag)", 46 | values: mcpScopes, 47 | variadic: true, 48 | optional: true, 49 | }, 50 | } 51 | : {}), 52 | "api-key-auth": { 53 | kind: "parsed", 54 | brief: "Sets the apiKeyAuth auth field for the API", 55 | optional: true, 56 | parse: (value) => { 57 | return z.string().parse(value); 58 | }, 59 | }, 60 | "server-url": { 61 | kind: "parsed", 62 | brief: "Overrides the default server URL used by the SDK", 63 | optional: true, 64 | parse: (value) => new URL(value).toString(), 65 | }, 66 | server: { 67 | kind: "enum", 68 | brief: "Selects a predefined server used by the SDK", 69 | optional: true, 70 | values: Object.keys(ServerList) as Array, 71 | }, 72 | "log-level": { 73 | kind: "enum", 74 | brief: "The log level to use for the server", 75 | default: "info", 76 | values: consoleLoggerLevels, 77 | }, 78 | env: { 79 | kind: "parsed", 80 | brief: "Environment variables made available to the server", 81 | optional: true, 82 | variadic: true, 83 | parse: (val: string) => { 84 | const sepIdx = val.indexOf("="); 85 | if (sepIdx === -1) { 86 | throw new Error("Invalid environment variable format"); 87 | } 88 | 89 | const key = val.slice(0, sepIdx); 90 | const value = val.slice(sepIdx + 1); 91 | 92 | return [ 93 | z.string().nonempty({ 94 | message: "Environment variable key must be a non-empty string", 95 | }).parse(key), 96 | z.string().nonempty({ 97 | message: "Environment variable value must be a non-empty string", 98 | }).parse(value), 99 | ] satisfies [string, string]; 100 | }, 101 | }, 102 | }, 103 | }, 104 | docs: { 105 | brief: "Run the Model Context Protocol server", 106 | }, 107 | }); 108 | -------------------------------------------------------------------------------- /src/mcp-server/cli/start/impl.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; 6 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 7 | import express from "express"; 8 | import { SDKOptions } from "../../../lib/config.js"; 9 | import { LocalContext } from "../../cli.js"; 10 | import { 11 | ConsoleLoggerLevel, 12 | createConsoleLogger, 13 | } from "../../console-logger.js"; 14 | import { MCPScope } from "../../scopes.js"; 15 | import { createMCPServer } from "../../server.js"; 16 | 17 | interface StartCommandFlags { 18 | readonly transport: "stdio" | "sse"; 19 | readonly port: number; 20 | readonly tool?: string[]; 21 | readonly scope?: MCPScope[]; 22 | readonly "api-key-auth"?: string | undefined; 23 | readonly "server-url"?: string; 24 | readonly server?: SDKOptions["server"]; 25 | readonly "log-level": ConsoleLoggerLevel; 26 | readonly env?: [string, string][]; 27 | } 28 | 29 | export async function main(this: LocalContext, flags: StartCommandFlags) { 30 | flags.env?.forEach(([key, value]) => { 31 | process.env[key] = value; 32 | }); 33 | 34 | switch (flags.transport) { 35 | case "stdio": 36 | await startStdio(flags); 37 | break; 38 | case "sse": 39 | await startSSE(flags); 40 | break; 41 | default: 42 | throw new Error(`Invalid transport: ${flags.transport}`); 43 | } 44 | } 45 | 46 | async function startStdio(flags: StartCommandFlags) { 47 | const logger = createConsoleLogger(flags["log-level"]); 48 | const transport = new StdioServerTransport(); 49 | const server = createMCPServer({ 50 | logger, 51 | allowedTools: flags.tool, 52 | scopes: flags.scope, 53 | security: { apiKeyAuth: flags["api-key-auth"] ?? "" }, 54 | serverURL: flags["server-url"], 55 | server: flags.server, 56 | }); 57 | await server.connect(transport); 58 | 59 | const abort = async () => { 60 | await server.close(); 61 | process.exit(0); 62 | }; 63 | process.on("SIGTERM", abort); 64 | process.on("SIGINT", abort); 65 | } 66 | 67 | async function startSSE(flags: StartCommandFlags) { 68 | const logger = createConsoleLogger(flags["log-level"]); 69 | const app = express(); 70 | const mcpServer = createMCPServer({ 71 | logger, 72 | allowedTools: flags.tool, 73 | scopes: flags.scope, 74 | security: { apiKeyAuth: flags["api-key-auth"] ?? "" }, 75 | serverURL: flags["server-url"], 76 | server: flags.server, 77 | }); 78 | let transport: SSEServerTransport | undefined; 79 | const controller = new AbortController(); 80 | 81 | app.get("/sse", async (_req, res) => { 82 | transport = new SSEServerTransport("/message", res); 83 | 84 | await mcpServer.connect(transport); 85 | 86 | mcpServer.server.onclose = async () => { 87 | res.end(); 88 | }; 89 | }); 90 | 91 | app.post("/message", async (req, res) => { 92 | if (!transport) { 93 | throw new Error("Server transport not initialized"); 94 | } 95 | 96 | await transport.handlePostMessage(req, res); 97 | }); 98 | 99 | const httpServer = app.listen(flags.port, "0.0.0.0", () => { 100 | const ha = httpServer.address(); 101 | const host = typeof ha === "string" ? ha : `${ha?.address}:${ha?.port}`; 102 | logger.info("MCP HTTP server started", { host }); 103 | }); 104 | 105 | let closing = false; 106 | controller.signal.addEventListener("abort", async () => { 107 | if (closing) { 108 | logger.info("Received second signal. Forcing shutdown."); 109 | process.exit(1); 110 | } 111 | closing = true; 112 | 113 | logger.info("Shutting down MCP server"); 114 | 115 | await mcpServer.close(); 116 | 117 | logger.info("Shutting down HTTP server"); 118 | 119 | const timer = setTimeout(() => { 120 | logger.info("Forcing shutdown"); 121 | process.exit(1); 122 | }, 5000); 123 | 124 | httpServer.close(() => { 125 | clearTimeout(timer); 126 | logger.info("Graceful shutdown complete"); 127 | process.exit(0); 128 | }); 129 | }); 130 | 131 | const abort = () => controller.abort(); 132 | process.on("SIGTERM", abort); 133 | process.on("SIGINT", abort); 134 | } 135 | -------------------------------------------------------------------------------- /src/mcp-server/console-logger.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export const consoleLoggerLevels = [ 6 | "debug", 7 | "warning", 8 | "info", 9 | "error", 10 | ] as const; 11 | 12 | export type ConsoleLoggerLevel = (typeof consoleLoggerLevels)[number]; 13 | 14 | export type ConsoleLogger = { 15 | [key in ConsoleLoggerLevel]: ( 16 | message: string, 17 | data?: Record, 18 | ) => void; 19 | }; 20 | 21 | export function createConsoleLogger(level: ConsoleLoggerLevel): ConsoleLogger { 22 | const min = consoleLoggerLevels.indexOf(level); 23 | const noop = () => {}; 24 | 25 | const logger: ConsoleLogger = { 26 | debug: noop, 27 | warning: noop, 28 | info: noop, 29 | error: noop, 30 | }; 31 | 32 | return consoleLoggerLevels.reduce((logger, level, i) => { 33 | if (i < min) { 34 | return logger; 35 | } 36 | 37 | logger[level] = log.bind(null, level); 38 | 39 | return logger; 40 | }, logger); 41 | } 42 | 43 | function log( 44 | level: ConsoleLoggerLevel, 45 | message: string, 46 | data?: Record, 47 | ) { 48 | let line = ""; 49 | const allData = [{ msg: message, l: level }, data]; 50 | 51 | for (const ctx of allData) { 52 | for (const [key, value] of Object.entries(ctx || {})) { 53 | if (value == null) { 54 | line += ` ${key}=<${value}>`; 55 | } else if (typeof value === "function") { 56 | line += ` ${key}=`; 57 | } else if (typeof value === "symbol") { 58 | line += ` ${key}=${value.toString()}`; 59 | } else if (typeof value === "string") { 60 | const v = value.search(/\s/g) >= 0 ? JSON.stringify(value) : value; 61 | line += ` ${key}=${v}`; 62 | } else if (typeof value !== "object") { 63 | line += ` ${key}=${value}`; 64 | } else { 65 | line += ` ${key}="${JSON.stringify(value)}"`; 66 | } 67 | } 68 | } 69 | 70 | console.error(line); 71 | } 72 | -------------------------------------------------------------------------------- /src/mcp-server/extensions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { ZodRawShape } from "zod"; 6 | import { PromptArgsRawShape, PromptDefinition } from "./prompts.js"; 7 | import { ResourceDefinition, ResourceTemplateDefinition } from "./resources.js"; 8 | import { ToolDefinition } from "./tools.js"; 9 | 10 | export type Register = { 11 | tool: (def: ToolDefinition) => void; 12 | resource: (def: ResourceDefinition) => void; 13 | resourceTemplate: (def: ResourceTemplateDefinition) => void; 14 | prompt: ( 15 | prompt: PromptDefinition, 16 | ) => void; 17 | }; 18 | -------------------------------------------------------------------------------- /src/mcp-server/mcp-server.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { buildApplication, buildRouteMap, run } from "@stricli/core"; 6 | import process from "node:process"; 7 | import { buildContext } from "./cli.js"; 8 | import { startCommand } from "./cli/start/command.js"; 9 | 10 | const routes = buildRouteMap({ 11 | routes: { 12 | start: startCommand, 13 | }, 14 | docs: { 15 | brief: "MCP server CLI", 16 | }, 17 | }); 18 | 19 | export const app = buildApplication(routes, { 20 | name: "mcp", 21 | versionInfo: { 22 | currentVersion: "0.24.4", 23 | }, 24 | }); 25 | 26 | run(app, process.argv.slice(2), buildContext(process)); 27 | -------------------------------------------------------------------------------- /src/mcp-server/prompts.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 6 | import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js"; 7 | import { GetPromptResult } from "@modelcontextprotocol/sdk/types.js"; 8 | import { 9 | objectOutputType, 10 | ZodOptional, 11 | ZodType, 12 | ZodTypeAny, 13 | ZodTypeDef, 14 | } from "zod"; 15 | import { UnstructuredClientCore } from "../core.js"; 16 | import { ConsoleLogger } from "./console-logger.js"; 17 | import { MCPScope } from "./scopes.js"; 18 | 19 | // '@modelcontextprotocol/sdk' currently does not export this type 20 | export type PromptArgsRawShape = { 21 | [k: string]: 22 | | ZodType 23 | | ZodOptional>; 24 | }; 25 | 26 | export type PromptDefinition< 27 | Args extends undefined | PromptArgsRawShape = undefined, 28 | > = Args extends PromptArgsRawShape ? { 29 | name: string; 30 | description?: string; 31 | scopes?: MCPScope[]; 32 | args: Args; 33 | prompt: ( 34 | client: UnstructuredClientCore, 35 | args: objectOutputType, 36 | extra: RequestHandlerExtra, 37 | ) => GetPromptResult | Promise; 38 | } 39 | : { 40 | name: string; 41 | description?: string; 42 | scopes?: MCPScope[]; 43 | args?: undefined; 44 | prompt: ( 45 | client: UnstructuredClientCore, 46 | extra: RequestHandlerExtra, 47 | ) => GetPromptResult | Promise; 48 | }; 49 | 50 | // Optional function to assist with formatting prompt results 51 | export async function formatResult(value: string): Promise { 52 | return { 53 | messages: [ 54 | { 55 | role: "user", 56 | content: { 57 | type: "text", 58 | text: value, 59 | }, 60 | }, 61 | ], 62 | }; 63 | } 64 | 65 | export function createRegisterPrompt( 66 | logger: ConsoleLogger, 67 | server: McpServer, 68 | sdk: UnstructuredClientCore, 69 | allowedScopes: Set, 70 | ): ( 71 | prompt: PromptDefinition, 72 | ) => void { 73 | return ( 74 | prompt: PromptDefinition, 75 | ): void => { 76 | const scopes = prompt.scopes ?? []; 77 | if (allowedScopes.size > 0 && scopes.length === 0) { 78 | return; 79 | } 80 | 81 | if ( 82 | allowedScopes.size > 0 83 | && !scopes.every((s: MCPScope) => allowedScopes.has(s)) 84 | ) { 85 | return; 86 | } 87 | 88 | if (prompt.args) { 89 | if (prompt.description) { 90 | server.prompt( 91 | prompt.name, 92 | prompt.description, 93 | prompt.args, 94 | async (args, ctx) => prompt.prompt(sdk, args, ctx), 95 | ); 96 | } else { 97 | server.prompt( 98 | prompt.name, 99 | prompt.args, 100 | async (args, ctx) => prompt.prompt(sdk, args, ctx), 101 | ); 102 | } 103 | } else { 104 | if (prompt.description) { 105 | server.prompt( 106 | prompt.name, 107 | prompt.description, 108 | async (ctx) => prompt.prompt(sdk, ctx), 109 | ); 110 | } else { 111 | server.prompt(prompt.name, async (ctx) => prompt.prompt(sdk, ctx)); 112 | } 113 | } 114 | 115 | logger.debug("Registered prompt", { name: prompt.name }); 116 | }; 117 | } 118 | -------------------------------------------------------------------------------- /src/mcp-server/resources.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { 6 | McpServer, 7 | ResourceMetadata, 8 | ResourceTemplate, 9 | } from "@modelcontextprotocol/sdk/server/mcp.js"; 10 | import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js"; 11 | import { Variables } from "@modelcontextprotocol/sdk/shared/uriTemplate.js"; 12 | import { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; 13 | import { UnstructuredClientCore } from "../core.js"; 14 | import { ConsoleLogger } from "./console-logger.js"; 15 | import { MCPScope } from "./scopes.js"; 16 | import { isAsyncIterable, isBinaryData, valueToBase64 } from "./shared.js"; 17 | 18 | export type ReadResourceCallback = ( 19 | client: UnstructuredClientCore, 20 | uri: URL, 21 | extra: RequestHandlerExtra, 22 | ) => ReadResourceResult | Promise; 23 | 24 | export type ResourceDefinition = { 25 | name: string; 26 | description?: string; 27 | metadata?: ResourceMetadata; 28 | scopes?: MCPScope[]; 29 | resource: string; 30 | read: ReadResourceCallback; 31 | }; 32 | 33 | export type ReadResourceTemplateCallback = ( 34 | client: UnstructuredClientCore, 35 | uri: URL, 36 | vars: Variables, 37 | extra: RequestHandlerExtra, 38 | ) => ReadResourceResult | Promise; 39 | 40 | export type ResourceTemplateDefinition = { 41 | name: string; 42 | description: string; 43 | metadata?: ResourceMetadata; 44 | scopes?: MCPScope[]; 45 | resource: ResourceTemplate; 46 | read: ReadResourceTemplateCallback; 47 | }; 48 | 49 | // Optional function to assist with formatting resource results 50 | export async function formatResult( 51 | value: unknown, 52 | uri: URL, 53 | init: { mimeType?: string | undefined; response?: Response | undefined }, 54 | ): Promise { 55 | if (typeof value === "undefined") { 56 | return { contents: [] }; 57 | } 58 | 59 | let contents: ReadResourceResult["contents"] = []; 60 | 61 | const mimeType = init.mimeType ?? init.response?.headers.get("content-type") 62 | ?? ""; 63 | 64 | if (mimeType.search(/\bjson\b/g) !== -1) { 65 | contents = [{ uri: uri.toString(), mimeType, text: JSON.stringify(value) }]; 66 | } else if ( 67 | mimeType.startsWith("text/event-stream") 68 | && isAsyncIterable(value) 69 | ) { 70 | contents = [ 71 | { 72 | uri: uri.toString(), 73 | mimeType: "application/json", 74 | text: await stringifySSEToJSON(value), 75 | }, 76 | ]; 77 | } else if ( 78 | (mimeType.startsWith("text/") || mimeType.startsWith("application/")) 79 | && typeof value === "string" 80 | ) { 81 | contents = [{ uri: uri.toString(), mimeType, text: value }]; 82 | } else if (isBinaryData(value)) { 83 | const blob = await valueToBase64(value); 84 | contents = blob == null ? [] : [{ uri: uri.toString(), blob, mimeType }]; 85 | } else { 86 | throw new Error(`Unsupported content type: "${mimeType}"`); 87 | } 88 | 89 | return { contents }; 90 | } 91 | 92 | async function stringifySSEToJSON( 93 | value: AsyncIterable, 94 | ): Promise { 95 | const payloads = []; 96 | 97 | for await (const chunk of value) { 98 | payloads.push(chunk); 99 | } 100 | 101 | return JSON.stringify(payloads); 102 | } 103 | 104 | export function createRegisterResource( 105 | logger: ConsoleLogger, 106 | server: McpServer, 107 | sdk: UnstructuredClientCore, 108 | allowedScopes: Set, 109 | ): (resource: ResourceDefinition) => void { 110 | return (resource: ResourceDefinition): void => { 111 | const scopes = resource.scopes ?? []; 112 | if (allowedScopes.size > 0 && scopes.length === 0) { 113 | return; 114 | } 115 | 116 | if ( 117 | allowedScopes.size > 0 118 | && !scopes.every((s: MCPScope) => allowedScopes.has(s)) 119 | ) { 120 | return; 121 | } 122 | 123 | const metadata: ResourceMetadata = { 124 | ...resource.metadata, 125 | description: resource.description, 126 | }; 127 | 128 | server.resource( 129 | resource.name, 130 | resource.resource, 131 | metadata, 132 | async (uri, ctx) => resource.read(sdk, uri, ctx), 133 | ); 134 | 135 | logger.debug("Registered resource", { name: resource.name }); 136 | }; 137 | } 138 | 139 | export function createRegisterResourceTemplate( 140 | logger: ConsoleLogger, 141 | server: McpServer, 142 | sdk: UnstructuredClientCore, 143 | allowedScopes: Set, 144 | ): (resource: ResourceTemplateDefinition) => void { 145 | return (resource: ResourceTemplateDefinition): void => { 146 | const scopes = resource.scopes ?? []; 147 | if (allowedScopes.size > 0 && scopes.length === 0) { 148 | return; 149 | } 150 | 151 | if ( 152 | allowedScopes.size > 0 153 | && !scopes.every((s: MCPScope) => allowedScopes.has(s)) 154 | ) { 155 | return; 156 | } 157 | 158 | const metadata: ResourceMetadata = { 159 | ...resource.metadata, 160 | description: resource.description, 161 | }; 162 | 163 | server.resource( 164 | resource.name, 165 | resource.resource, 166 | metadata, 167 | async (uri, vars, ctx) => resource.read(sdk, uri, vars, ctx), 168 | ); 169 | 170 | logger.debug("Registered resource template", { name: resource.name }); 171 | }; 172 | } 173 | -------------------------------------------------------------------------------- /src/mcp-server/scopes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export const mcpScopes = [] as const; 6 | 7 | export type MCPScope = (typeof mcpScopes)[number]; 8 | -------------------------------------------------------------------------------- /src/mcp-server/server.extensions.ts: -------------------------------------------------------------------------------- 1 | import { formatResult, ToolDefinition } from "./tools.js"; 2 | import { Register } from "./extensions.js"; 3 | import { generalPartition } from "../funcs/generalPartition.js"; 4 | import fs from 'node:fs/promises'; 5 | import { UnstructuredClientCore } from "../core.js"; 6 | import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js"; 7 | import { z } from "zod"; 8 | import { Strategy, Strategy$inboundSchema, StrategyOpen } from "../sdk/models/shared/partitionparameters.js"; 9 | 10 | 11 | type FileRequest = { 12 | strategy?: StrategyOpen | undefined; 13 | file_content?: string | undefined; 14 | file_path?: string | undefined; 15 | file_name: string; 16 | }; 17 | 18 | const FileRequest$inboundSchema: z.ZodType< 19 | FileRequest, 20 | z.ZodTypeDef, 21 | unknown 22 | > = z.object({ 23 | file_content: z.string().optional(), 24 | file_path: z.string().optional(), 25 | file_name: z.string(), 26 | strategy: Strategy$inboundSchema.default(Strategy.HiRes), 27 | }); 28 | 29 | const customToolArg = { 30 | request: FileRequest$inboundSchema 31 | }; 32 | 33 | 34 | 35 | export const tool$generalPartitionCorrect: ToolDefinition = { 36 | name: "correct_general-partition", 37 | description: `use this tool to pass a file to unstructured. You must BASE64 ENCODE uploaded file content before passing to unstructured. Alternatively, if the user did not upload a file they can provide a full local file path. `, 38 | args: customToolArg, 39 | tool: async (client: UnstructuredClientCore, args, ctx: RequestHandlerExtra) => { 40 | let data: Uint8Array; 41 | if (args.request.file_content) { 42 | try { 43 | data = new Uint8Array(Buffer.from(args.request.file_content, 'base64')); 44 | } catch (e) { 45 | return { 46 | content: [{ 47 | type: "text", 48 | text: `You must BASE64 encode this file content then pass it to the tool.`, 49 | }], 50 | isError: true, 51 | }; 52 | } 53 | } else if (args.request.file_path) { 54 | data = new Uint8Array(await fs.readFile(args.request.file_path)); 55 | } else { 56 | return { 57 | content: [{ 58 | type: "text", 59 | text: `A full file path for file content must be provided`, 60 | }], 61 | isError: true, 62 | }; 63 | } 64 | const [result, apiCall] = await generalPartition( 65 | client, 66 | { 67 | partitionParameters: { 68 | files: { 69 | content: data, 70 | fileName: args.request.file_name, 71 | }, 72 | strategy: args.request.strategy, 73 | } 74 | }, 75 | { fetchOptions: { signal: ctx.signal } }, 76 | ).$inspect(); 77 | 78 | if (!result.ok) { 79 | return { 80 | content: [{ type: "text", text: result.error.message }], 81 | isError: true, 82 | }; 83 | } 84 | 85 | const value = result.value; 86 | 87 | return formatResult(value, apiCall); 88 | }, 89 | }; 90 | 91 | export function registerMCPExtensions(register: Register): void { 92 | register.tool(tool$generalPartitionCorrect); 93 | } -------------------------------------------------------------------------------- /src/mcp-server/server.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 6 | import { UnstructuredClientCore } from "../core.js"; 7 | import { SDKOptions } from "../lib/config.js"; 8 | import type { ConsoleLogger } from "./console-logger.js"; 9 | import { Register } from "./extensions.js"; 10 | import { createRegisterPrompt } from "./prompts.js"; 11 | import { 12 | createRegisterResource, 13 | createRegisterResourceTemplate, 14 | } from "./resources.js"; 15 | import { MCPScope } from "./scopes.js"; 16 | import { registerMCPExtensions } from "./server.extensions.js"; 17 | import { createRegisterTool } from "./tools.js"; 18 | import { tool$generalPartition } from "./tools/generalPartition.js"; 19 | 20 | export function createMCPServer(deps: { 21 | logger: ConsoleLogger; 22 | allowedTools?: string[] | undefined; 23 | scopes?: MCPScope[] | undefined; 24 | serverURL?: string | undefined; 25 | security?: SDKOptions["security"] | undefined; 26 | server?: SDKOptions["server"] | undefined; 27 | }) { 28 | const server = new McpServer({ 29 | name: "UnstructuredClient", 30 | version: "0.24.4", 31 | }); 32 | 33 | const client = new UnstructuredClientCore({ 34 | security: deps.security, 35 | serverURL: deps.serverURL, 36 | server: deps.server, 37 | }); 38 | 39 | const scopes = new Set(deps.scopes); 40 | 41 | const allowedTools = deps.allowedTools && new Set(deps.allowedTools); 42 | const tool = createRegisterTool( 43 | deps.logger, 44 | server, 45 | client, 46 | scopes, 47 | allowedTools, 48 | ); 49 | const resource = createRegisterResource(deps.logger, server, client, scopes); 50 | const resourceTemplate = createRegisterResourceTemplate( 51 | deps.logger, 52 | server, 53 | client, 54 | scopes, 55 | ); 56 | const prompt = createRegisterPrompt(deps.logger, server, client, scopes); 57 | const register = { tool, resource, resourceTemplate, prompt }; 58 | void register; // suppress unused warnings 59 | 60 | tool(tool$generalPartition); 61 | 62 | registerMCPExtensions(register satisfies Register); 63 | 64 | return server; 65 | } 66 | -------------------------------------------------------------------------------- /src/mcp-server/shared.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as z from "zod"; 6 | import { bytesToBase64 } from "../lib/base64.js"; 7 | 8 | type BinaryData = 9 | | Uint8Array 10 | | ArrayBuffer 11 | | Blob 12 | | ReadableStream 13 | | Response 14 | | string; 15 | 16 | export async function consumeStream( 17 | stream: ReadableStream, 18 | ): Promise { 19 | const reader = stream.getReader(); 20 | const chunks: Uint8Array[] = []; 21 | 22 | try { 23 | while (true) { 24 | const { done, value } = await reader.read(); 25 | if (value != null) chunks.push(value); 26 | if (done) break; 27 | } 28 | } finally { 29 | reader.releaseLock(); 30 | } 31 | 32 | return new Uint8Array(await new Blob(chunks).arrayBuffer()); 33 | } 34 | 35 | export function isAsyncIterable( 36 | value: unknown, 37 | ): value is AsyncIterable { 38 | return ( 39 | typeof value === "object" && value != null && Symbol.asyncIterator in value 40 | ); 41 | } 42 | 43 | export function isBinaryData(value: unknown): value is BinaryData { 44 | return ( 45 | value instanceof Uint8Array 46 | || value instanceof ArrayBuffer 47 | || value instanceof Blob 48 | || value instanceof ReadableStream 49 | || value instanceof Response 50 | || typeof value === "string" 51 | ); 52 | } 53 | 54 | const base64Schema = z.string().base64(); 55 | 56 | export async function valueToBase64( 57 | value: BinaryData | null | undefined, 58 | ): Promise { 59 | if (value == null) { 60 | return null; 61 | } else if (value instanceof Uint8Array) { 62 | return bytesToBase64(value); 63 | } else if (value instanceof ArrayBuffer) { 64 | return bytesToBase64(new Uint8Array(value)); 65 | } else if (value instanceof Response || value instanceof Blob) { 66 | return bytesToBase64(new Uint8Array(await value.arrayBuffer())); 67 | } else if (value instanceof ReadableStream) { 68 | return bytesToBase64(await consumeStream(value)); 69 | } else if (typeof value === "string") { 70 | return base64Schema.parse(value); 71 | } else { 72 | value satisfies never; 73 | throw new Error(`Unsupported image value type: ${typeof value}`); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/mcp-server/tools.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 6 | import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js"; 7 | import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; 8 | import { objectOutputType, ZodRawShape, ZodTypeAny } from "zod"; 9 | import { UnstructuredClientCore } from "../core.js"; 10 | import { ConsoleLogger } from "./console-logger.js"; 11 | import { MCPScope } from "./scopes.js"; 12 | import { isAsyncIterable, isBinaryData, valueToBase64 } from "./shared.js"; 13 | 14 | export type ToolDefinition = 15 | Args extends ZodRawShape ? { 16 | name: string; 17 | description: string; 18 | scopes?: MCPScope[]; 19 | args: Args; 20 | tool: ( 21 | client: UnstructuredClientCore, 22 | args: objectOutputType, 23 | extra: RequestHandlerExtra, 24 | ) => CallToolResult | Promise; 25 | } 26 | : { 27 | name: string; 28 | description: string; 29 | scopes?: MCPScope[]; 30 | args?: undefined; 31 | tool: ( 32 | client: UnstructuredClientCore, 33 | extra: RequestHandlerExtra, 34 | ) => CallToolResult | Promise; 35 | }; 36 | 37 | // Optional function to assist with formatting tool results 38 | export async function formatResult( 39 | value: unknown, 40 | init: { response?: Response | undefined }, 41 | ): Promise { 42 | if (typeof value === "undefined") { 43 | return { content: [] }; 44 | } 45 | 46 | const { response } = init; 47 | const contentType = response?.headers.get("content-type") ?? ""; 48 | let content: CallToolResult["content"] = []; 49 | 50 | if (contentType.search(/\bjson\b/g)) { 51 | content = [{ type: "text", text: JSON.stringify(value) }]; 52 | } else if ( 53 | contentType.startsWith("text/event-stream") 54 | && isAsyncIterable(value) 55 | ) { 56 | content = await consumeSSE(value); 57 | } else if (contentType.startsWith("text/") && typeof value === "string") { 58 | content = [{ type: "text", text: value }]; 59 | } else if (isBinaryData(value) && contentType.startsWith("image/")) { 60 | const data = await valueToBase64(value); 61 | content = data == null 62 | ? [] 63 | : [{ type: "image", data, mimeType: contentType }]; 64 | } else { 65 | return { 66 | content: [{ 67 | type: "text", 68 | text: `Unsupported content type: "${contentType}"`, 69 | }], 70 | isError: true, 71 | }; 72 | } 73 | 74 | return { content }; 75 | } 76 | 77 | async function consumeSSE( 78 | value: AsyncIterable, 79 | ): Promise { 80 | const content: CallToolResult["content"] = []; 81 | 82 | for await (const chunk of value) { 83 | if (typeof chunk === "string") { 84 | content.push({ type: "text", text: chunk }); 85 | } else { 86 | content.push({ type: "text", text: JSON.stringify(chunk) }); 87 | } 88 | } 89 | 90 | return content; 91 | } 92 | 93 | export function createRegisterTool( 94 | logger: ConsoleLogger, 95 | server: McpServer, 96 | sdk: UnstructuredClientCore, 97 | allowedScopes: Set, 98 | allowedTools?: Set, 99 | ): (tool: ToolDefinition) => void { 100 | return (tool: ToolDefinition): void => { 101 | if (allowedTools && !allowedTools.has(tool.name)) { 102 | return; 103 | } 104 | 105 | const scopes = tool.scopes ?? []; 106 | if (allowedScopes.size > 0 && scopes.length === 0) { 107 | return; 108 | } 109 | 110 | if ( 111 | allowedScopes.size > 0 112 | && !scopes.every((s: MCPScope) => allowedScopes.has(s)) 113 | ) { 114 | return; 115 | } 116 | 117 | if (tool.args) { 118 | server.tool(tool.name, tool.description, tool.args, async (args, ctx) => { 119 | return tool.tool(sdk, args, ctx); 120 | }); 121 | } else { 122 | server.tool(tool.name, tool.description, async (ctx) => { 123 | return tool.tool(sdk, ctx); 124 | }); 125 | } 126 | 127 | logger.debug("Registered tool", { name: tool.name }); 128 | }; 129 | } 130 | -------------------------------------------------------------------------------- /src/mcp-server/tools/generalPartition.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { generalPartition } from "../../funcs/generalPartition.js"; 6 | import * as operations from "../../sdk/models/operations/index.js"; 7 | import { formatResult, ToolDefinition } from "../tools.js"; 8 | 9 | const args = { 10 | request: operations.PartitionRequest$inboundSchema, 11 | }; 12 | 13 | export const tool$generalPartition: ToolDefinition = { 14 | name: "general-partition", 15 | description: `Summary 16 | 17 | Description`, 18 | args, 19 | tool: async (client, args, ctx) => { 20 | const [result, apiCall] = await generalPartition( 21 | client, 22 | args.request, 23 | { fetchOptions: { signal: ctx.signal } }, 24 | ).$inspect(); 25 | 26 | if (!result.ok) { 27 | return { 28 | content: [{ type: "text", text: result.error.message }], 29 | isError: true, 30 | }; 31 | } 32 | 33 | const value = result.value; 34 | 35 | return formatResult(value, apiCall); 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /src/sdk/general.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { 6 | generalPartition, 7 | PartitionAcceptEnum, 8 | } from "../funcs/generalPartition.js"; 9 | import { ClientSDK, RequestOptions } from "../lib/sdks.js"; 10 | import * as operations from "./models/operations/index.js"; 11 | import { unwrapAsync } from "./types/fp.js"; 12 | 13 | export { PartitionAcceptEnum } from "../funcs/generalPartition.js"; 14 | 15 | export class General extends ClientSDK { 16 | /** 17 | * Summary 18 | * 19 | * @remarks 20 | * Description 21 | */ 22 | async partition( 23 | request: operations.PartitionRequest, 24 | options?: RequestOptions & { acceptHeaderOverride?: PartitionAcceptEnum }, 25 | ): Promise { 26 | return unwrapAsync(generalPartition( 27 | this, 28 | request, 29 | options, 30 | )); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/sdk/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export * from "./sdk.js"; 6 | -------------------------------------------------------------------------------- /src/sdk/models/errors/httpclienterrors.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | /** 6 | * Base class for all HTTP errors. 7 | */ 8 | export class HTTPClientError extends Error { 9 | /** The underlying cause of the error. */ 10 | override readonly cause: unknown; 11 | override name = "HTTPClientError"; 12 | constructor(message: string, opts?: { cause?: unknown }) { 13 | let msg = message; 14 | if (opts?.cause) { 15 | msg += `: ${opts.cause}`; 16 | } 17 | 18 | super(msg, opts); 19 | // In older runtimes, the cause field would not have been assigned through 20 | // the super() call. 21 | if (typeof this.cause === "undefined") { 22 | this.cause = opts?.cause; 23 | } 24 | } 25 | } 26 | 27 | /** 28 | * An error to capture unrecognised or unexpected errors when making HTTP calls. 29 | */ 30 | export class UnexpectedClientError extends HTTPClientError { 31 | override name = "UnexpectedClientError"; 32 | } 33 | 34 | /** 35 | * An error that is raised when any inputs used to create a request are invalid. 36 | */ 37 | export class InvalidRequestError extends HTTPClientError { 38 | override name = "InvalidRequestError"; 39 | } 40 | 41 | /** 42 | * An error that is raised when a HTTP request was aborted by the client error. 43 | */ 44 | export class RequestAbortedError extends HTTPClientError { 45 | override readonly name = "RequestAbortedError"; 46 | } 47 | 48 | /** 49 | * An error that is raised when a HTTP request timed out due to an AbortSignal 50 | * signal timeout. 51 | */ 52 | export class RequestTimeoutError extends HTTPClientError { 53 | override readonly name = "RequestTimeoutError"; 54 | } 55 | 56 | /** 57 | * An error that is raised when a HTTP client is unable to make a request to 58 | * a server. 59 | */ 60 | export class ConnectionError extends HTTPClientError { 61 | override readonly name = "ConnectionError"; 62 | } 63 | -------------------------------------------------------------------------------- /src/sdk/models/errors/httpvalidationerror.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as z from "zod"; 6 | import { safeParse } from "../../../lib/schemas.js"; 7 | import { Result as SafeParseResult } from "../../types/fp.js"; 8 | import * as shared from "../shared/index.js"; 9 | import { SDKValidationError } from "./sdkvalidationerror.js"; 10 | 11 | export type Detail = Array | string; 12 | 13 | export type HTTPValidationErrorData = { 14 | detail?: Array | string | undefined; 15 | }; 16 | 17 | export class HTTPValidationError extends Error { 18 | detail?: Array | string | undefined; 19 | 20 | /** The original data that was passed to this error instance. */ 21 | data$: HTTPValidationErrorData; 22 | 23 | constructor(err: HTTPValidationErrorData) { 24 | const message = "message" in err && typeof err.message === "string" 25 | ? err.message 26 | : `API error occurred: ${JSON.stringify(err)}`; 27 | super(message); 28 | this.data$ = err; 29 | 30 | if (err.detail != null) this.detail = err.detail; 31 | 32 | this.name = "HTTPValidationError"; 33 | } 34 | } 35 | 36 | /** @internal */ 37 | export const Detail$inboundSchema: z.ZodType = z 38 | .union([z.array(shared.ValidationError$inboundSchema), z.string()]); 39 | 40 | /** @internal */ 41 | export type Detail$Outbound = Array | string; 42 | 43 | /** @internal */ 44 | export const Detail$outboundSchema: z.ZodType< 45 | Detail$Outbound, 46 | z.ZodTypeDef, 47 | Detail 48 | > = z.union([z.array(shared.ValidationError$outboundSchema), z.string()]); 49 | 50 | /** 51 | * @internal 52 | * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. 53 | */ 54 | export namespace Detail$ { 55 | /** @deprecated use `Detail$inboundSchema` instead. */ 56 | export const inboundSchema = Detail$inboundSchema; 57 | /** @deprecated use `Detail$outboundSchema` instead. */ 58 | export const outboundSchema = Detail$outboundSchema; 59 | /** @deprecated use `Detail$Outbound` instead. */ 60 | export type Outbound = Detail$Outbound; 61 | } 62 | 63 | export function detailToJSON(detail: Detail): string { 64 | return JSON.stringify(Detail$outboundSchema.parse(detail)); 65 | } 66 | 67 | export function detailFromJSON( 68 | jsonString: string, 69 | ): SafeParseResult { 70 | return safeParse( 71 | jsonString, 72 | (x) => Detail$inboundSchema.parse(JSON.parse(x)), 73 | `Failed to parse 'Detail' from JSON`, 74 | ); 75 | } 76 | 77 | /** @internal */ 78 | export const HTTPValidationError$inboundSchema: z.ZodType< 79 | HTTPValidationError, 80 | z.ZodTypeDef, 81 | unknown 82 | > = z.object({ 83 | detail: z.union([z.array(shared.ValidationError$inboundSchema), z.string()]) 84 | .optional(), 85 | }) 86 | .transform((v) => { 87 | return new HTTPValidationError(v); 88 | }); 89 | 90 | /** @internal */ 91 | export type HTTPValidationError$Outbound = { 92 | detail?: Array | string | undefined; 93 | }; 94 | 95 | /** @internal */ 96 | export const HTTPValidationError$outboundSchema: z.ZodType< 97 | HTTPValidationError$Outbound, 98 | z.ZodTypeDef, 99 | HTTPValidationError 100 | > = z.instanceof(HTTPValidationError) 101 | .transform(v => v.data$) 102 | .pipe(z.object({ 103 | detail: z.union([ 104 | z.array(shared.ValidationError$outboundSchema), 105 | z.string(), 106 | ]).optional(), 107 | })); 108 | 109 | /** 110 | * @internal 111 | * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. 112 | */ 113 | export namespace HTTPValidationError$ { 114 | /** @deprecated use `HTTPValidationError$inboundSchema` instead. */ 115 | export const inboundSchema = HTTPValidationError$inboundSchema; 116 | /** @deprecated use `HTTPValidationError$outboundSchema` instead. */ 117 | export const outboundSchema = HTTPValidationError$outboundSchema; 118 | /** @deprecated use `HTTPValidationError$Outbound` instead. */ 119 | export type Outbound = HTTPValidationError$Outbound; 120 | } 121 | -------------------------------------------------------------------------------- /src/sdk/models/errors/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export * from "./httpclienterrors.js"; 6 | export * from "./httpvalidationerror.js"; 7 | export * from "./sdkerror.js"; 8 | export * from "./sdkvalidationerror.js"; 9 | export * from "./servererror.js"; 10 | -------------------------------------------------------------------------------- /src/sdk/models/errors/sdkerror.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export class SDKError extends Error { 6 | public readonly statusCode: number; 7 | public readonly contentType: string; 8 | 9 | constructor( 10 | message: string, 11 | public readonly rawResponse: Response, 12 | public readonly body: string = "", 13 | ) { 14 | const statusCode = rawResponse.status; 15 | const contentType = rawResponse.headers.get("content-type") || ""; 16 | const bodyString = body.length > 0 ? `\n${body}` : ""; 17 | 18 | super( 19 | `${message}: Status ${statusCode} Content-Type ${contentType} Body ${bodyString}`, 20 | ); 21 | 22 | this.statusCode = statusCode; 23 | this.contentType = contentType; 24 | 25 | this.name = "SDKError"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/sdk/models/errors/sdkvalidationerror.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as z from "zod"; 6 | 7 | export class SDKValidationError extends Error { 8 | /** 9 | * The raw value that failed validation. 10 | */ 11 | public readonly rawValue: unknown; 12 | 13 | /** 14 | * The raw message that failed validation. 15 | */ 16 | public readonly rawMessage: unknown; 17 | 18 | constructor(message: string, cause: unknown, rawValue: unknown) { 19 | super(`${message}: ${cause}`); 20 | this.name = "SDKValidationError"; 21 | this.cause = cause; 22 | this.rawValue = rawValue; 23 | this.rawMessage = message; 24 | } 25 | 26 | /** 27 | * Return a pretty-formatted error message if the underlying validation error 28 | * is a ZodError or some other recognized error type, otherwise return the 29 | * default error message. 30 | */ 31 | public pretty(): string { 32 | if (this.cause instanceof z.ZodError) { 33 | return `${this.rawMessage}\n${formatZodError(this.cause)}`; 34 | } else { 35 | return this.toString(); 36 | } 37 | } 38 | } 39 | 40 | export function formatZodError(err: z.ZodError, level = 0): string { 41 | let pre = " ".repeat(level); 42 | pre = level > 0 ? `│${pre}` : pre; 43 | pre += " ".repeat(level); 44 | 45 | let message = ""; 46 | const append = (str: string) => (message += `\n${pre}${str}`); 47 | 48 | const len = err.issues.length; 49 | const headline = len === 1 ? `${len} issue found` : `${len} issues found`; 50 | 51 | if (len) { 52 | append(`┌ ${headline}:`); 53 | } 54 | 55 | for (const issue of err.issues) { 56 | let path = issue.path.join("."); 57 | path = path ? `.${path}` : ""; 58 | append(`│ • [${path}]: ${issue.message} (${issue.code})`); 59 | switch (issue.code) { 60 | case "invalid_literal": 61 | case "invalid_type": { 62 | append(`│ Want: ${issue.expected}`); 63 | append(`│ Got: ${issue.received}`); 64 | break; 65 | } 66 | case "unrecognized_keys": { 67 | append(`│ Keys: ${issue.keys.join(", ")}`); 68 | break; 69 | } 70 | case "invalid_enum_value": { 71 | append(`│ Allowed: ${issue.options.join(", ")}`); 72 | append(`│ Got: ${issue.received}`); 73 | break; 74 | } 75 | case "invalid_union_discriminator": { 76 | append(`│ Allowed: ${issue.options.join(", ")}`); 77 | break; 78 | } 79 | case "invalid_union": { 80 | const len = issue.unionErrors.length; 81 | append( 82 | `│ ✖︎ Attemped to deserialize into one of ${len} union members:`, 83 | ); 84 | issue.unionErrors.forEach((err, i) => { 85 | append(`│ ✖︎ Member ${i + 1} of ${len}`); 86 | append(`${formatZodError(err, level + 1)}`); 87 | }); 88 | } 89 | } 90 | } 91 | 92 | if (err.issues.length) { 93 | append(`└─*`); 94 | } 95 | 96 | return message.slice(1); 97 | } 98 | -------------------------------------------------------------------------------- /src/sdk/models/errors/servererror.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as z from "zod"; 6 | 7 | export type ServerErrorData = { 8 | detail?: string | undefined; 9 | }; 10 | 11 | export class ServerError extends Error { 12 | detail?: string | undefined; 13 | 14 | /** The original data that was passed to this error instance. */ 15 | data$: ServerErrorData; 16 | 17 | constructor(err: ServerErrorData) { 18 | const message = "message" in err && typeof err.message === "string" 19 | ? err.message 20 | : `API error occurred: ${JSON.stringify(err)}`; 21 | super(message); 22 | this.data$ = err; 23 | 24 | if (err.detail != null) this.detail = err.detail; 25 | 26 | this.name = "ServerError"; 27 | } 28 | } 29 | 30 | /** @internal */ 31 | export const ServerError$inboundSchema: z.ZodType< 32 | ServerError, 33 | z.ZodTypeDef, 34 | unknown 35 | > = z.object({ 36 | detail: z.string().optional(), 37 | }) 38 | .transform((v) => { 39 | return new ServerError(v); 40 | }); 41 | 42 | /** @internal */ 43 | export type ServerError$Outbound = { 44 | detail?: string | undefined; 45 | }; 46 | 47 | /** @internal */ 48 | export const ServerError$outboundSchema: z.ZodType< 49 | ServerError$Outbound, 50 | z.ZodTypeDef, 51 | ServerError 52 | > = z.instanceof(ServerError) 53 | .transform(v => v.data$) 54 | .pipe(z.object({ 55 | detail: z.string().optional(), 56 | })); 57 | 58 | /** 59 | * @internal 60 | * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. 61 | */ 62 | export namespace ServerError$ { 63 | /** @deprecated use `ServerError$inboundSchema` instead. */ 64 | export const inboundSchema = ServerError$inboundSchema; 65 | /** @deprecated use `ServerError$outboundSchema` instead. */ 66 | export const outboundSchema = ServerError$outboundSchema; 67 | /** @deprecated use `ServerError$Outbound` instead. */ 68 | export type Outbound = ServerError$Outbound; 69 | } 70 | -------------------------------------------------------------------------------- /src/sdk/models/operations/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export * from "./partition.js"; 6 | -------------------------------------------------------------------------------- /src/sdk/models/operations/partition.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as z from "zod"; 6 | import { remap as remap$ } from "../../../lib/primitives.js"; 7 | import { safeParse } from "../../../lib/schemas.js"; 8 | import { Result as SafeParseResult } from "../../types/fp.js"; 9 | import { SDKValidationError } from "../errors/sdkvalidationerror.js"; 10 | import * as shared from "../shared/index.js"; 11 | 12 | export type PartitionRequest = { 13 | partitionParameters: shared.PartitionParameters; 14 | unstructuredApiKey?: string | null | undefined; 15 | }; 16 | 17 | export type PartitionResponse = string | Array<{ [k: string]: any }>; 18 | 19 | /** @internal */ 20 | export const PartitionRequest$inboundSchema: z.ZodType< 21 | PartitionRequest, 22 | z.ZodTypeDef, 23 | unknown 24 | > = z.object({ 25 | partition_parameters: shared.PartitionParameters$inboundSchema, 26 | "unstructured-api-key": z.nullable(z.string()).optional(), 27 | }).transform((v) => { 28 | return remap$(v, { 29 | "partition_parameters": "partitionParameters", 30 | "unstructured-api-key": "unstructuredApiKey", 31 | }); 32 | }); 33 | 34 | /** @internal */ 35 | export type PartitionRequest$Outbound = { 36 | partition_parameters: shared.PartitionParameters$Outbound; 37 | "unstructured-api-key"?: string | null | undefined; 38 | }; 39 | 40 | /** @internal */ 41 | export const PartitionRequest$outboundSchema: z.ZodType< 42 | PartitionRequest$Outbound, 43 | z.ZodTypeDef, 44 | PartitionRequest 45 | > = z.object({ 46 | partitionParameters: shared.PartitionParameters$outboundSchema, 47 | unstructuredApiKey: z.nullable(z.string()).optional(), 48 | }).transform((v) => { 49 | return remap$(v, { 50 | partitionParameters: "partition_parameters", 51 | unstructuredApiKey: "unstructured-api-key", 52 | }); 53 | }); 54 | 55 | /** 56 | * @internal 57 | * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. 58 | */ 59 | export namespace PartitionRequest$ { 60 | /** @deprecated use `PartitionRequest$inboundSchema` instead. */ 61 | export const inboundSchema = PartitionRequest$inboundSchema; 62 | /** @deprecated use `PartitionRequest$outboundSchema` instead. */ 63 | export const outboundSchema = PartitionRequest$outboundSchema; 64 | /** @deprecated use `PartitionRequest$Outbound` instead. */ 65 | export type Outbound = PartitionRequest$Outbound; 66 | } 67 | 68 | export function partitionRequestToJSON( 69 | partitionRequest: PartitionRequest, 70 | ): string { 71 | return JSON.stringify( 72 | PartitionRequest$outboundSchema.parse(partitionRequest), 73 | ); 74 | } 75 | 76 | export function partitionRequestFromJSON( 77 | jsonString: string, 78 | ): SafeParseResult { 79 | return safeParse( 80 | jsonString, 81 | (x) => PartitionRequest$inboundSchema.parse(JSON.parse(x)), 82 | `Failed to parse 'PartitionRequest' from JSON`, 83 | ); 84 | } 85 | 86 | /** @internal */ 87 | export const PartitionResponse$inboundSchema: z.ZodType< 88 | PartitionResponse, 89 | z.ZodTypeDef, 90 | unknown 91 | > = z.union([z.string(), z.array(z.record(z.any()))]); 92 | 93 | /** @internal */ 94 | export type PartitionResponse$Outbound = string | Array<{ [k: string]: any }>; 95 | 96 | /** @internal */ 97 | export const PartitionResponse$outboundSchema: z.ZodType< 98 | PartitionResponse$Outbound, 99 | z.ZodTypeDef, 100 | PartitionResponse 101 | > = z.union([z.string(), z.array(z.record(z.any()))]); 102 | 103 | /** 104 | * @internal 105 | * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. 106 | */ 107 | export namespace PartitionResponse$ { 108 | /** @deprecated use `PartitionResponse$inboundSchema` instead. */ 109 | export const inboundSchema = PartitionResponse$inboundSchema; 110 | /** @deprecated use `PartitionResponse$outboundSchema` instead. */ 111 | export const outboundSchema = PartitionResponse$outboundSchema; 112 | /** @deprecated use `PartitionResponse$Outbound` instead. */ 113 | export type Outbound = PartitionResponse$Outbound; 114 | } 115 | 116 | export function partitionResponseToJSON( 117 | partitionResponse: PartitionResponse, 118 | ): string { 119 | return JSON.stringify( 120 | PartitionResponse$outboundSchema.parse(partitionResponse), 121 | ); 122 | } 123 | 124 | export function partitionResponseFromJSON( 125 | jsonString: string, 126 | ): SafeParseResult { 127 | return safeParse( 128 | jsonString, 129 | (x) => PartitionResponse$inboundSchema.parse(JSON.parse(x)), 130 | `Failed to parse 'PartitionResponse' from JSON`, 131 | ); 132 | } 133 | -------------------------------------------------------------------------------- /src/sdk/models/shared/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export * from "./partitionparameters.js"; 6 | export * from "./security.js"; 7 | export * from "./validationerror.js"; 8 | -------------------------------------------------------------------------------- /src/sdk/models/shared/security.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as z from "zod"; 6 | import { remap as remap$ } from "../../../lib/primitives.js"; 7 | import { safeParse } from "../../../lib/schemas.js"; 8 | import { Result as SafeParseResult } from "../../types/fp.js"; 9 | import { SDKValidationError } from "../errors/sdkvalidationerror.js"; 10 | 11 | export type Security = { 12 | apiKeyAuth?: string | undefined; 13 | }; 14 | 15 | /** @internal */ 16 | export const Security$inboundSchema: z.ZodType< 17 | Security, 18 | z.ZodTypeDef, 19 | unknown 20 | > = z.object({ 21 | ApiKeyAuth: z.string().optional(), 22 | }).transform((v) => { 23 | return remap$(v, { 24 | "ApiKeyAuth": "apiKeyAuth", 25 | }); 26 | }); 27 | 28 | /** @internal */ 29 | export type Security$Outbound = { 30 | ApiKeyAuth?: string | undefined; 31 | }; 32 | 33 | /** @internal */ 34 | export const Security$outboundSchema: z.ZodType< 35 | Security$Outbound, 36 | z.ZodTypeDef, 37 | Security 38 | > = z.object({ 39 | apiKeyAuth: z.string().optional(), 40 | }).transform((v) => { 41 | return remap$(v, { 42 | apiKeyAuth: "ApiKeyAuth", 43 | }); 44 | }); 45 | 46 | /** 47 | * @internal 48 | * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. 49 | */ 50 | export namespace Security$ { 51 | /** @deprecated use `Security$inboundSchema` instead. */ 52 | export const inboundSchema = Security$inboundSchema; 53 | /** @deprecated use `Security$outboundSchema` instead. */ 54 | export const outboundSchema = Security$outboundSchema; 55 | /** @deprecated use `Security$Outbound` instead. */ 56 | export type Outbound = Security$Outbound; 57 | } 58 | 59 | export function securityToJSON(security: Security): string { 60 | return JSON.stringify(Security$outboundSchema.parse(security)); 61 | } 62 | 63 | export function securityFromJSON( 64 | jsonString: string, 65 | ): SafeParseResult { 66 | return safeParse( 67 | jsonString, 68 | (x) => Security$inboundSchema.parse(JSON.parse(x)), 69 | `Failed to parse 'Security' from JSON`, 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /src/sdk/models/shared/validationerror.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as z from "zod"; 6 | import { safeParse } from "../../../lib/schemas.js"; 7 | import { Result as SafeParseResult } from "../../types/fp.js"; 8 | import { SDKValidationError } from "../errors/sdkvalidationerror.js"; 9 | 10 | export type Loc = string | number; 11 | 12 | export type ValidationError = { 13 | loc: Array; 14 | msg: string; 15 | type: string; 16 | }; 17 | 18 | /** @internal */ 19 | export const Loc$inboundSchema: z.ZodType = z.union( 20 | [z.string(), z.number().int()], 21 | ); 22 | 23 | /** @internal */ 24 | export type Loc$Outbound = string | number; 25 | 26 | /** @internal */ 27 | export const Loc$outboundSchema: z.ZodType = z 28 | .union([z.string(), z.number().int()]); 29 | 30 | /** 31 | * @internal 32 | * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. 33 | */ 34 | export namespace Loc$ { 35 | /** @deprecated use `Loc$inboundSchema` instead. */ 36 | export const inboundSchema = Loc$inboundSchema; 37 | /** @deprecated use `Loc$outboundSchema` instead. */ 38 | export const outboundSchema = Loc$outboundSchema; 39 | /** @deprecated use `Loc$Outbound` instead. */ 40 | export type Outbound = Loc$Outbound; 41 | } 42 | 43 | export function locToJSON(loc: Loc): string { 44 | return JSON.stringify(Loc$outboundSchema.parse(loc)); 45 | } 46 | 47 | export function locFromJSON( 48 | jsonString: string, 49 | ): SafeParseResult { 50 | return safeParse( 51 | jsonString, 52 | (x) => Loc$inboundSchema.parse(JSON.parse(x)), 53 | `Failed to parse 'Loc' from JSON`, 54 | ); 55 | } 56 | 57 | /** @internal */ 58 | export const ValidationError$inboundSchema: z.ZodType< 59 | ValidationError, 60 | z.ZodTypeDef, 61 | unknown 62 | > = z.object({ 63 | loc: z.array(z.union([z.string(), z.number().int()])), 64 | msg: z.string(), 65 | type: z.string(), 66 | }); 67 | 68 | /** @internal */ 69 | export type ValidationError$Outbound = { 70 | loc: Array; 71 | msg: string; 72 | type: string; 73 | }; 74 | 75 | /** @internal */ 76 | export const ValidationError$outboundSchema: z.ZodType< 77 | ValidationError$Outbound, 78 | z.ZodTypeDef, 79 | ValidationError 80 | > = z.object({ 81 | loc: z.array(z.union([z.string(), z.number().int()])), 82 | msg: z.string(), 83 | type: z.string(), 84 | }); 85 | 86 | /** 87 | * @internal 88 | * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. 89 | */ 90 | export namespace ValidationError$ { 91 | /** @deprecated use `ValidationError$inboundSchema` instead. */ 92 | export const inboundSchema = ValidationError$inboundSchema; 93 | /** @deprecated use `ValidationError$outboundSchema` instead. */ 94 | export const outboundSchema = ValidationError$outboundSchema; 95 | /** @deprecated use `ValidationError$Outbound` instead. */ 96 | export type Outbound = ValidationError$Outbound; 97 | } 98 | 99 | export function validationErrorToJSON( 100 | validationError: ValidationError, 101 | ): string { 102 | return JSON.stringify(ValidationError$outboundSchema.parse(validationError)); 103 | } 104 | 105 | export function validationErrorFromJSON( 106 | jsonString: string, 107 | ): SafeParseResult { 108 | return safeParse( 109 | jsonString, 110 | (x) => ValidationError$inboundSchema.parse(JSON.parse(x)), 111 | `Failed to parse 'ValidationError' from JSON`, 112 | ); 113 | } 114 | -------------------------------------------------------------------------------- /src/sdk/sdk.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { ClientSDK } from "../lib/sdks.js"; 6 | import { General } from "./general.js"; 7 | 8 | export class UnstructuredClient extends ClientSDK { 9 | private _general?: General; 10 | get general(): General { 11 | return (this._general ??= new General(this._options)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/sdk/types/async.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export type APICall = 6 | | { 7 | status: "complete"; 8 | request: Request; 9 | response: Response; 10 | } 11 | | { 12 | status: "request-error"; 13 | request: Request; 14 | response?: undefined; 15 | } 16 | | { 17 | status: "invalid"; 18 | request?: undefined; 19 | response?: undefined; 20 | }; 21 | 22 | export class APIPromise implements Promise { 23 | readonly #promise: Promise<[T, APICall]>; 24 | readonly #unwrapped: Promise; 25 | 26 | readonly [Symbol.toStringTag] = "APIPromise"; 27 | 28 | constructor(p: [T, APICall] | Promise<[T, APICall]>) { 29 | this.#promise = p instanceof Promise ? p : Promise.resolve(p); 30 | this.#unwrapped = 31 | p instanceof Promise 32 | ? this.#promise.then(([value]) => value) 33 | : Promise.resolve(p[0]); 34 | } 35 | 36 | then( 37 | onfulfilled?: 38 | | ((value: T) => TResult1 | PromiseLike) 39 | | null 40 | | undefined, 41 | onrejected?: 42 | | ((reason: any) => TResult2 | PromiseLike) 43 | | null 44 | | undefined, 45 | ): Promise { 46 | return this.#promise.then( 47 | onfulfilled ? ([value]) => onfulfilled(value) : void 0, 48 | onrejected, 49 | ); 50 | } 51 | 52 | catch( 53 | onrejected?: 54 | | ((reason: any) => TResult | PromiseLike) 55 | | null 56 | | undefined, 57 | ): Promise { 58 | return this.#unwrapped.catch(onrejected); 59 | } 60 | 61 | finally(onfinally?: (() => void) | null | undefined): Promise { 62 | return this.#unwrapped.finally(onfinally); 63 | } 64 | 65 | $inspect(): Promise<[T, APICall]> { 66 | return this.#promise; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/sdk/types/blobs.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as z from "zod"; 6 | 7 | export const blobLikeSchema: z.ZodType = 8 | z.custom(isBlobLike, { 9 | message: "expected a Blob, File or Blob-like object", 10 | fatal: true, 11 | }); 12 | 13 | export function isBlobLike(val: unknown): val is Blob { 14 | if (val instanceof Blob) { 15 | return true; 16 | } 17 | 18 | if (typeof val !== "object" || val == null || !(Symbol.toStringTag in val)) { 19 | return false; 20 | } 21 | 22 | const name = val[Symbol.toStringTag]; 23 | if (typeof name !== "string") { 24 | return false; 25 | } 26 | if (name !== "Blob" && name !== "File") { 27 | return false; 28 | } 29 | 30 | return "stream" in val && typeof val.stream === "function"; 31 | } 32 | -------------------------------------------------------------------------------- /src/sdk/types/constdatetime.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import * as z from "zod"; 6 | 7 | export function constDateTime( 8 | val: string, 9 | ): z.ZodType { 10 | return z.custom((v) => { 11 | return ( 12 | typeof v === "string" && new Date(v).getTime() === new Date(val).getTime() 13 | ); 14 | }, `Value must be equivelant to ${val}`); 15 | } 16 | -------------------------------------------------------------------------------- /src/sdk/types/enums.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | declare const __brand: unique symbol; 6 | export type Unrecognized = T & { [__brand]: "unrecognized" }; 7 | 8 | export function catchUnrecognizedEnum(value: T): Unrecognized { 9 | return value as Unrecognized; 10 | } 11 | 12 | type Prettify = { [K in keyof T]: T[K] } & {}; 13 | export type ClosedEnum = T[keyof T]; 14 | export type OpenEnum = 15 | | Prettify 16 | | Unrecognized; 17 | -------------------------------------------------------------------------------- /src/sdk/types/fp.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | /** 6 | * A monad that captures the result of a function call or an error if it was not 7 | * successful. Railway programming, enabled by this type, can be a nicer 8 | * alternative to traditional exception throwing because it allows functions to 9 | * declare all _known_ errors with static types and then check for them 10 | * exhaustively in application code. Thrown exception have a type of `unknown` 11 | * and break out of regular control flow of programs making them harder to 12 | * inspect and more verbose work with due to try-catch blocks. 13 | */ 14 | export type Result = 15 | | { ok: true; value: T; error?: never } 16 | | { ok: false; value?: never; error: E }; 17 | 18 | export function OK(value: V): Result { 19 | return { ok: true, value }; 20 | } 21 | 22 | export function ERR(error: E): Result { 23 | return { ok: false, error }; 24 | } 25 | 26 | /** 27 | * unwrap is a convenience function for extracting a value from a result or 28 | * throwing if there was an error. 29 | */ 30 | export function unwrap(r: Result): T { 31 | if (!r.ok) { 32 | throw r.error; 33 | } 34 | return r.value; 35 | } 36 | 37 | /** 38 | * unwrapAsync is a convenience function for resolving a value from a Promise 39 | * of a result or rejecting if an error occurred. 40 | */ 41 | export async function unwrapAsync( 42 | pr: Promise>, 43 | ): Promise { 44 | const r = await pr; 45 | if (!r.ok) { 46 | throw r.error; 47 | } 48 | 49 | return r.value; 50 | } 51 | -------------------------------------------------------------------------------- /src/sdk/types/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export { blobLikeSchema, isBlobLike } from "./blobs.js"; 6 | export { catchUnrecognizedEnum } from "./enums.js"; 7 | export type { ClosedEnum, OpenEnum, Unrecognized } from "./enums.js"; 8 | export type { Result } from "./fp.js"; 9 | export type { PageIterator, Paginator } from "./operations.js"; 10 | export { createPageIterator } from "./operations.js"; 11 | export { RFCDate } from "./rfcdate.js"; 12 | -------------------------------------------------------------------------------- /src/sdk/types/operations.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | import { Result } from "./fp.js"; 6 | 7 | export type Paginator = () => Promise }> | null; 8 | 9 | export type PageIterator = V & { 10 | next: Paginator; 11 | [Symbol.asyncIterator]: () => AsyncIterableIterator; 12 | "~next"?: PageState | undefined; 13 | }; 14 | 15 | export function createPageIterator( 16 | page: V & { next: Paginator }, 17 | halt: (v: V) => boolean, 18 | ): { 19 | [Symbol.asyncIterator]: () => AsyncIterableIterator; 20 | } { 21 | return { 22 | [Symbol.asyncIterator]: async function* paginator() { 23 | yield page; 24 | if (halt(page)) { 25 | return; 26 | } 27 | 28 | let p: typeof page | null = page; 29 | for (p = await p.next(); p != null; p = await p.next()) { 30 | yield p; 31 | if (halt(p)) { 32 | return; 33 | } 34 | } 35 | }, 36 | }; 37 | } 38 | 39 | /** 40 | * This utility create a special iterator that yields a single value and 41 | * terminates. It is useful in paginated SDK functions that have early return 42 | * paths when things go wrong. 43 | */ 44 | export function haltIterator( 45 | v: V, 46 | ): PageIterator { 47 | return { 48 | ...v, 49 | next: () => null, 50 | [Symbol.asyncIterator]: async function* paginator() { 51 | yield v; 52 | }, 53 | }; 54 | } 55 | 56 | /** 57 | * Converts an async iterator of `Result` into an async iterator of `V`. 58 | * When error results occur, the underlying error value is thrown. 59 | */ 60 | export async function unwrapResultIterator( 61 | iteratorPromise: Promise, PageState>>, 62 | ): Promise> { 63 | const resultIter = await iteratorPromise; 64 | 65 | if (!resultIter.ok) { 66 | throw resultIter.error; 67 | } 68 | 69 | return { 70 | ...resultIter.value, 71 | next: unwrapPaginator(resultIter.next), 72 | "~next": resultIter["~next"], 73 | [Symbol.asyncIterator]: async function* paginator() { 74 | for await (const page of resultIter) { 75 | if (!page.ok) { 76 | throw page.error; 77 | } 78 | yield page.value; 79 | } 80 | }, 81 | }; 82 | } 83 | 84 | function unwrapPaginator( 85 | paginator: Paginator>, 86 | ): Paginator { 87 | return () => { 88 | const nextResult = paginator(); 89 | if (nextResult == null) { 90 | return null; 91 | } 92 | return nextResult.then((res) => { 93 | if (!res.ok) { 94 | throw res.error; 95 | } 96 | const out = { 97 | ...res.value, 98 | next: unwrapPaginator(res.next), 99 | }; 100 | return out; 101 | }); 102 | }; 103 | } 104 | 105 | export const URL_OVERRIDE = Symbol("URL_OVERRIDE"); 106 | -------------------------------------------------------------------------------- /src/sdk/types/rfcdate.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | const dateRE = /^\d{4}-\d{2}-\d{2}$/; 6 | 7 | export class RFCDate { 8 | private serialized: string; 9 | 10 | /** 11 | * Creates a new RFCDate instance using today's date. 12 | */ 13 | static today(): RFCDate { 14 | return new RFCDate(new Date()); 15 | } 16 | 17 | /** 18 | * Creates a new RFCDate instance using the provided input. 19 | * If a string is used then in must be in the format YYYY-MM-DD. 20 | * 21 | * @param date A Date object or a date string in YYYY-MM-DD format 22 | * @example 23 | * new RFCDate("2022-01-01") 24 | * @example 25 | * new RFCDate(new Date()) 26 | */ 27 | constructor(date: Date | string) { 28 | if (typeof date === "string" && !dateRE.test(date)) { 29 | throw new RangeError( 30 | "RFCDate: date strings must be in the format YYYY-MM-DD: " + date, 31 | ); 32 | } 33 | 34 | const value = new Date(date); 35 | if (isNaN(+value)) { 36 | throw new RangeError("RFCDate: invalid date provided: " + date); 37 | } 38 | 39 | this.serialized = value.toISOString().slice(0, "YYYY-MM-DD".length); 40 | if (!dateRE.test(this.serialized)) { 41 | throw new TypeError( 42 | `RFCDate: failed to build valid date with given value: ${date} serialized to ${this.serialized}`, 43 | ); 44 | } 45 | } 46 | 47 | toJSON(): string { 48 | return this.toString(); 49 | } 50 | 51 | toString(): string { 52 | return this.serialized; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/sdk/types/streams.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. 3 | */ 4 | 5 | export function isReadableStream( 6 | val: unknown, 7 | ): val is ReadableStream { 8 | if (typeof val !== "object" || val === null) { 9 | return false; 10 | } 11 | 12 | // Check for the presence of methods specific to ReadableStream 13 | const stream = val as ReadableStream; 14 | 15 | // ReadableStream has methods like getReader, cancel, and tee 16 | return ( 17 | typeof stream.getReader === "function" && 18 | typeof stream.cancel === "function" && 19 | typeof stream.tee === "function" 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /test/data/fake.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unstructured-IO/unstructured-js-client/d7987e7f4d5377e6daf104fff4a49fb7850c79c0/test/data/fake.doc -------------------------------------------------------------------------------- /test/data/layout-parser-paper-fast.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unstructured-IO/unstructured-js-client/d7987e7f4d5377e6daf104fff4a49fb7850c79c0/test/data/layout-parser-paper-fast.pdf -------------------------------------------------------------------------------- /test/data/layout-parser-paper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unstructured-IO/unstructured-js-client/d7987e7f4d5377e6daf104fff4a49fb7850c79c0/test/data/layout-parser-paper.pdf -------------------------------------------------------------------------------- /test/data/list-item-example-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unstructured-IO/unstructured-js-client/d7987e7f4d5377e6daf104fff4a49fb7850c79c0/test/data/list-item-example-1.pdf -------------------------------------------------------------------------------- /test/integration/HttpsCheckHook.test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | 3 | import { UnstructuredClient } from "../../src"; 4 | import { PartitionResponse } from "../../src/sdk/models/operations"; 5 | import { PartitionParameters, Strategy } from "../../src/sdk/models/shared"; 6 | import { describe, it, expect} from 'vitest'; 7 | 8 | const localServer = "http://localhost:8000" 9 | 10 | describe("HttpsCheckHook integration tests", () => { 11 | const FAKE_API_KEY = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 12 | 13 | it.each([ 14 | localServer, 15 | `${localServer}/general/v0/general`, 16 | ])("should throw error when given filename is empty", async (serverURL) => { 17 | const client = new UnstructuredClient({ 18 | serverURL: serverURL, 19 | security: { 20 | apiKeyAuth: FAKE_API_KEY, 21 | }, 22 | }); 23 | 24 | const file = { 25 | content: readFileSync("test/data/layout-parser-paper-fast.pdf"), 26 | fileName: "test/data/layout-parser-paper-fast.pdf", 27 | }; 28 | 29 | const requestParams: PartitionParameters = { 30 | files: file, 31 | strategy: Strategy.Fast, 32 | }; 33 | 34 | const res: PartitionResponse = await client.general.partition({ 35 | partitionParameters: { 36 | ...requestParams, 37 | splitPdfPage: false, 38 | }, 39 | }); 40 | 41 | expect(res.length).toBeGreaterThan(0) 42 | }); 43 | }); -------------------------------------------------------------------------------- /test/unit/FixArrayParamsHook.test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | 3 | import { UnstructuredClient } from "../../src"; 4 | import { PartitionResponse } from "../../src/sdk/models/operations"; 5 | import { PartitionParameters, Strategy } from "../../src/sdk/models/shared"; 6 | import { describe, it, expect, vi, beforeEach} from 'vitest'; 7 | 8 | describe("FixArrayParamsHook unit tests", () => { 9 | beforeEach(() => { 10 | // Reset the mock before each test 11 | vi.resetAllMocks(); 12 | }); 13 | 14 | // Assert that array parameters are sent in the correct format 15 | // This should work with and without pdf splitting 16 | it.each([ 17 | {splitPdfPage: false}, 18 | {splitPdfPage: true}, 19 | ])( 20 | "should send extract_image_block_types in the correct format", async ({splitPdfPage}) => { 21 | const client = new UnstructuredClient({}); 22 | 23 | const file = { 24 | content: readFileSync("test/data/layout-parser-paper-fast.pdf"), 25 | fileName: "test/data/layout-parser-paper-fast.pdf", 26 | }; 27 | 28 | const requestParams: PartitionParameters = { 29 | files: file, 30 | strategy: Strategy.Fast, 31 | extractImageBlockTypes: ["a", "b", "c"], 32 | splitPdfPage: splitPdfPage, 33 | }; 34 | 35 | const fetchMock = vi.fn().mockResolvedValue( 36 | new Response( 37 | JSON.stringify([ 38 | { 39 | type: "Image", 40 | element_id: "2fe9cbfbf0ff1bd64cc4705347dbd1d6", 41 | text: "This is a test", 42 | metadata: {}, 43 | }, 44 | ]), 45 | { 46 | status: 200, 47 | headers: { "Content-Type": "application/json" }, 48 | } 49 | ) 50 | ); 51 | 52 | vi.stubGlobal("fetch", fetchMock); 53 | 54 | const res: PartitionResponse = await client.general.partition({ 55 | partitionParameters: requestParams, 56 | }); 57 | 58 | expect(fetchMock).toHaveBeenCalledTimes(1); 59 | 60 | const request = fetchMock.mock.calls[0][0]; 61 | const formData = await request.formData(); 62 | const extract_image_block_types = formData.getAll( 63 | "extract_image_block_types[]" 64 | ); 65 | 66 | expect(extract_image_block_types).toEqual(["a", "b", "c"]); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/unit/HttpsCheckHook.test.ts: -------------------------------------------------------------------------------- 1 | import { HttpsCheckHook } from "../../src/hooks/custom/HttpsCheckHook"; 2 | import { HTTPClient } from "../../src/lib/http"; 3 | import { describe, it, expect, vi, afterEach } from 'vitest'; 4 | 5 | describe("HttpsCheckHook", () => { 6 | const consoleSpy = vi.spyOn(global.console, "warn"); 7 | 8 | afterEach(() => { 9 | consoleSpy.mockClear(); 10 | }); 11 | 12 | it.each([ 13 | { url: "http://example.unstructuredapp.io" }, 14 | { url: "ws://example.unstructuredapp.io" }, 15 | ])( 16 | "should update the protocol to HTTPS if the base hostname matches '*.unstructuredapp.io' and the protocol is not HTTPS", 17 | ({ url }) => { 18 | const baseURL = new URL(url); 19 | const client = new HTTPClient(); 20 | const opts = { baseURL, client }; 21 | const hook = new HttpsCheckHook(); 22 | 23 | const result = hook.sdkInit(opts); 24 | 25 | expect(result.baseURL?.protocol).toBe("https:"); 26 | expect(consoleSpy).toHaveBeenCalledTimes(1); 27 | } 28 | ); 29 | 30 | it("should not update the protocol to HTTPS if the base hostname doesn't match '*.unstructuredapp.io'", () => { 31 | const baseURL = new URL("http://example.someotherdomain.com"); 32 | const client = new HTTPClient(); 33 | const opts = { baseURL, client }; 34 | const hook = new HttpsCheckHook(); 35 | 36 | const result = hook.sdkInit(opts); 37 | 38 | expect(result.baseURL?.protocol).toBe("http:"); 39 | expect(consoleSpy).not.toHaveBeenCalled(); 40 | }); 41 | 42 | it("should not update the protocol to HTTPS if the base hostname matches '*.unstructuredapp.io' and the protocol is HTTPS", () => { 43 | const baseURL = new URL("https://example.unstructuredapp.io"); 44 | const client = new HTTPClient(); 45 | const opts = { baseURL, client }; 46 | const hook = new HttpsCheckHook(); 47 | 48 | const result = hook.sdkInit(opts); 49 | 50 | expect(result.baseURL?.protocol).toBe("https:"); 51 | expect(consoleSpy).not.toHaveBeenCalled(); 52 | }); 53 | 54 | it("should not update anything if URL is null", () => { 55 | const baseURL = null; 56 | const client = new HTTPClient(); 57 | const opts = { baseURL, client }; 58 | const hook = new HttpsCheckHook(); 59 | 60 | const result = hook.sdkInit(opts); 61 | 62 | expect(result.baseURL).toBeNull(); 63 | expect(consoleSpy).not.toHaveBeenCalled(); 64 | }); 65 | 66 | it("should update the pathname to empty", () => { 67 | const baseURL = new URL("https://example.unstructuredapp.io/general/v0/general"); 68 | const client = new HTTPClient(); 69 | const opts = { baseURL, client }; 70 | const hook = new HttpsCheckHook(); 71 | 72 | const result = hook.sdkInit(opts); 73 | 74 | expect(result.baseURL?.pathname).toBe("/"); 75 | expect(consoleSpy).not.toHaveBeenCalled(); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/unit/utils/pdf.test.ts: -------------------------------------------------------------------------------- 1 | import { PDFDocument } from "pdf-lib"; 2 | import { readFileSync } from "node:fs"; 3 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; 4 | 5 | import { 6 | loadPdf, 7 | pdfPagesToBlob, 8 | splitPdf, 9 | } from "../../../src/hooks/custom/utils"; 10 | import { getOptimalSplitSize } from "../../../src/hooks/custom/utils"; 11 | import { MAX_PAGES_PER_THREAD, MIN_PAGES_PER_THREAD } from "../../../src/hooks/custom/common.js"; 12 | 13 | describe("Pdf utility functions", () => { 14 | const filename = "test/data/layout-parser-paper.pdf"; 15 | let file: Buffer; 16 | let pdf: PDFDocument; 17 | 18 | beforeEach(async () => { 19 | file = readFileSync(filename); 20 | pdf = await PDFDocument.load(file); 21 | }); 22 | 23 | afterEach(() => { 24 | vi.clearAllMocks(); 25 | }); 26 | 27 | describe("pdfPagesToBlob", () => { 28 | it("should convert range of pages to a Blob object", async () => { 29 | const copyMock = vi.spyOn(PDFDocument.prototype, "copyPages"); 30 | const saveMock = vi.spyOn(PDFDocument.prototype, "save"); 31 | const addMock = vi.spyOn(PDFDocument.prototype, "addPage"); 32 | 33 | // Call the method 34 | const result = await pdfPagesToBlob(pdf, 4, 8); 35 | 36 | // Verify the expected behavior 37 | expect(copyMock).toHaveBeenCalledWith(pdf, [3, 4, 5, 6, 7]); 38 | expect(saveMock).toHaveBeenCalled(); 39 | expect(addMock).toHaveBeenCalledTimes(5); 40 | expect(result).toBeInstanceOf(Blob); 41 | expect(result.type).toEqual("application/pdf"); 42 | }); 43 | }); 44 | 45 | describe("getOptimalSplitSize", () => { 46 | it("should return the maximum pages per thread when pagesCount is high", async () => { 47 | const result = await getOptimalSplitSize(100, 4); 48 | expect(result).toBe(MAX_PAGES_PER_THREAD); 49 | }); 50 | 51 | it("should return the minimum pages per thread when pagesCount is low", async () => { 52 | const result = await getOptimalSplitSize(2, 4); 53 | expect(result).toBe(MIN_PAGES_PER_THREAD); 54 | }); 55 | 56 | it("should return an appropriate split size for a given pagesCount and concurrencyLevel", async () => { 57 | const result = await getOptimalSplitSize(10, 3); 58 | expect(result).toBe(Math.ceil(10 / 3)); 59 | }); 60 | }); 61 | 62 | describe("splitPdf", () => { 63 | it("should split the PDF into one batch", async () => { 64 | const result = await splitPdf(pdf, 16); 65 | 66 | expect(result).toHaveLength(1); 67 | expect(result[0]?.startPage).toBe(1); 68 | expect(result[0]?.endPage).toBe(16); 69 | }); 70 | 71 | it("should split the PDF into 3 batches", async () => { 72 | const result = await splitPdf(pdf, 6); 73 | 74 | // Verify the expected behavior 75 | expect(result).toHaveLength(3); 76 | expect(result[0]?.startPage).toBe(1); 77 | expect(result[0]?.endPage).toBe(6); 78 | expect(result[1]?.startPage).toBe(7); 79 | expect(result[1]?.endPage).toBe(12); 80 | expect(result[2]?.startPage).toBe(13); 81 | expect(result[2]?.endPage).toBe(16); 82 | }); 83 | 84 | it("should split the PDF into 4 batches", async () => { 85 | const result = await splitPdf(pdf, 4); 86 | 87 | // Verify the expected behavior 88 | expect(result).toHaveLength(4); 89 | expect(result[0]?.startPage).toBe(1); 90 | expect(result[0]?.endPage).toBe(4); 91 | expect(result[1]?.startPage).toBe(5); 92 | expect(result[1]?.endPage).toBe(8); 93 | expect(result[2]?.startPage).toBe(9); 94 | expect(result[2]?.endPage).toBe(12); 95 | expect(result[3]?.startPage).toBe(13); 96 | expect(result[3]?.endPage).toBe(16); 97 | }); 98 | }); 99 | 100 | describe("loadPdf", () => { 101 | it("should return true, null, and 0 if the file is null", async () => { 102 | const result = await loadPdf(null); 103 | 104 | expect(result).toEqual([true, null, 0]); 105 | }); 106 | 107 | it("should return true, null, and 0 if the file is not a PDF", async () => { 108 | const file = { 109 | name: "document.txt", 110 | content: vi.fn().mockResolvedValue(new ArrayBuffer(0)), 111 | }; 112 | 113 | const result = await loadPdf(file as any); 114 | 115 | expect(result).toEqual([true, null, 0]); 116 | expect(file.content).not.toHaveBeenCalled(); 117 | }); 118 | 119 | it("should return true, null, and 0 if the file is not a PDF without basing on file extension", async () => { 120 | const file = { 121 | name: "uuid1234", 122 | content: vi.fn().mockResolvedValue(new ArrayBuffer(0)), 123 | }; 124 | 125 | const result = await loadPdf(file as any); 126 | 127 | expect(result).toEqual([true, null, 0]); 128 | expect(file.content).not.toHaveBeenCalled(); 129 | }); 130 | 131 | 132 | it("should return true, null, and 0 if there is an error while loading the PDF", async () => { 133 | const file = { 134 | name: "document.pdf", 135 | arrayBuffer: vi.fn().mockRejectedValue(new ArrayBuffer(0)), 136 | }; 137 | 138 | const result = await loadPdf(file as any); 139 | 140 | expect(result).toEqual([true, null, 0]); 141 | expect(file.arrayBuffer).toHaveBeenCalled(); 142 | }); 143 | 144 | it("should return false, PDFDocument object, and the number of pages if the PDF is loaded successfully", async () => { 145 | const file = readFileSync("test/data/layout-parser-paper-fast.pdf"); 146 | const f = { 147 | name: "document.pdf", 148 | arrayBuffer: () => file.buffer, 149 | }; 150 | 151 | const loadMock = vi.spyOn(PDFDocument, "load"); 152 | 153 | const [error, _, pages] = await loadPdf(f as any); 154 | 155 | expect(error).toBeFalsy(); 156 | expect(pages).toEqual(2); 157 | expect(loadMock).toHaveBeenCalledTimes(1); 158 | expect(loadMock).toHaveBeenCalledWith(f.arrayBuffer()); 159 | }); 160 | 161 | it("should return false, PDFDocument object, and the number of pages if the PDF is loaded successfully without basing on file extension", async () => { 162 | const file = readFileSync("test/data/layout-parser-paper-fast.pdf"); 163 | const f = { 164 | name: "uuid1234", 165 | arrayBuffer: () => file.buffer, 166 | }; 167 | 168 | vi.clearAllMocks(); // Reset Mocks Between Tests 169 | const loadMock = vi.spyOn(PDFDocument, "load"); 170 | 171 | const [error, _, pages] = await loadPdf(f as any); 172 | 173 | expect(error).toBeFalsy(); 174 | expect(pages).toEqual(2); 175 | expect(loadMock).toHaveBeenCalledTimes(1); 176 | expect(loadMock).toHaveBeenCalledWith(f.arrayBuffer()); 177 | }); 178 | 179 | }); 180 | }); 181 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "tsBuildInfoFile": ".tsbuildinfo", 5 | "target": "ES2020", 6 | "lib": ["ES2022", "DOM", "DOM.Iterable"], 7 | "jsx": "react-jsx", 8 | 9 | "module": "Node16", 10 | "moduleResolution": "Node16", 11 | 12 | "allowJs": true, 13 | 14 | "declaration": true, 15 | "declarationMap": true, 16 | "sourceMap": true, 17 | "outDir": ".", 18 | 19 | 20 | // https://github.com/tsconfig/bases/blob/a1bf7c0fa2e094b068ca3e1448ca2ece4157977e/bases/strictest.json 21 | "strict": true, 22 | "allowUnusedLabels": false, 23 | "allowUnreachableCode": false, 24 | "exactOptionalPropertyTypes": true, 25 | "useUnknownInCatchVariables": true, 26 | "noFallthroughCasesInSwitch": true, 27 | "noImplicitOverride": true, 28 | "noImplicitReturns": true, 29 | "noPropertyAccessFromIndexSignature": true, 30 | "noUncheckedIndexedAccess": true, 31 | "noUnusedLocals": true, 32 | "noUnusedParameters": true, 33 | "isolatedModules": true, 34 | "checkJs": true, 35 | "esModuleInterop": true, 36 | "skipLibCheck": true, 37 | "forceConsistentCasingInFileNames": true 38 | }, 39 | "include": ["src"], 40 | "exclude": ["node_modules"] 41 | } 42 | -------------------------------------------------------------------------------- /vitest.config.mjs: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { defineConfig } from 'vitest/config' 4 | 5 | export default defineConfig({ 6 | esbuild: { 7 | // Transpile all files with ESBuild to remove comments from code coverage. 8 | // Required for `test.coverage.ignoreEmptyLines` to work: 9 | include: ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.ts', '**/*.tsx'], 10 | }, 11 | test: { 12 | coverage: { 13 | provider: 'v8', 14 | ignoreEmptyLines: true, 15 | }, 16 | }, 17 | }) 18 | --------------------------------------------------------------------------------