From f38fc15fccf7f65422fc9cd62aea41fb143b36ed Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 01:52:33 +0000 Subject: [PATCH 01/40] chore: sync repo --- .eslintrc.js | 10 - .github/workflows/ci.yml | 16 +- .github/workflows/publish-npm.yml | 50 - .github/workflows/release-doctor.yml | 22 - .release-please-manifest.json | 3 - CHANGELOG.md | 30 - CODEOWNERS | 2 +- CONTRIBUTING.md | 20 +- MIGRATION.md | 387 +++++ README.md | 158 +- api.md | 28 +- bin/check-release-environment | 22 - bin/cli | 53 + bin/migration-config.json | 6 + eslint.config.mjs | 42 + jest.config.ts | 2 +- package.json | 97 +- packages/mcp-server/README.md | 29 +- packages/mcp-server/manifest.json | 4 +- packages/mcp-server/package.json | 6 +- packages/mcp-server/src/code-tool.ts | 2 +- packages/mcp-server/src/dynamic-tools.ts | 6 +- packages/mcp-server/src/headers.ts | 6 +- packages/mcp-server/src/server.ts | 45 +- .../enrolled-ids-benefits-hris-individuals.ts | 2 +- .../directory/create-sandbox-directory.ts | 4 +- release-please-config.json | 74 - scripts/bootstrap | 2 +- scripts/build | 19 +- scripts/fast-format | 2 +- scripts/format | 6 +- scripts/lint | 16 +- scripts/utils/attw-report.cjs | 24 + scripts/utils/fix-index-exports.cjs | 7 +- scripts/utils/postprocess-files.cjs | 199 +-- scripts/utils/upload-artifact.sh | 2 +- src/_shims/MultipartBody.ts | 9 - src/_shims/README.md | 46 - src/_shims/auto/runtime-bun.ts | 4 - src/_shims/auto/runtime-deno.ts | 4 - src/_shims/auto/runtime-node.ts | 4 - src/_shims/auto/runtime.ts | 4 - src/_shims/auto/types-deno.ts | 4 - src/_shims/auto/types-node.ts | 4 - src/_shims/auto/types.d.ts | 101 -- src/_shims/auto/types.js | 3 - src/_shims/auto/types.mjs | 3 - src/_shims/bun-runtime.ts | 14 - src/_shims/index-deno.ts | 112 -- src/_shims/index.d.ts | 83 -- src/_shims/index.js | 17 - src/_shims/index.mjs | 11 - src/_shims/manual-types.d.ts | 12 - src/_shims/manual-types.js | 3 - src/_shims/manual-types.mjs | 3 - src/_shims/node-runtime.ts | 81 - src/_shims/node-types.d.ts | 42 - src/_shims/node-types.js | 3 - src/_shims/node-types.mjs | 3 - src/_shims/registry.ts | 67 - src/_shims/web-runtime.ts | 103 -- src/_shims/web-types.d.ts | 83 -- src/_shims/web-types.js | 3 - src/_shims/web-types.mjs | 3 - src/api-promise.ts | 2 + src/client.ts | 932 ++++++++++++ src/core.ts | 1257 ---------------- src/core/README.md | 3 + src/core/api-promise.ts | 92 ++ src/core/error.ts | 130 ++ src/core/pagination.ts | 292 ++++ src/core/resource.ts | 11 + src/core/uploads.ts | 2 + src/error.ts | 132 +- src/index.ts | 51 +- src/internal/README.md | 3 + src/internal/builtin-types.ts | 93 ++ src/internal/detect-platform.ts | 196 +++ src/internal/errors.ts | 33 + src/internal/headers.ts | 97 ++ src/internal/parse.ts | 50 + src/internal/qs/formats.ts | 3 +- src/internal/qs/stringify.ts | 37 +- src/internal/qs/utils.ts | 28 +- src/internal/request-options.ts | 91 ++ src/internal/shim-types.ts | 26 + src/internal/shims.ts | 107 ++ src/internal/to-file.ts | 154 ++ src/internal/types.ts | 95 ++ src/internal/uploads.ts | 187 +++ src/internal/utils.ts | 8 + src/internal/utils/base64.ts | 40 + src/internal/utils/bytes.ts | 32 + src/internal/utils/env.ts | 18 + src/internal/utils/log.ts | 126 ++ src/internal/utils/path.ts | 88 ++ src/internal/utils/sleep.ts | 3 + src/internal/utils/uuid.ts | 17 + src/internal/utils/values.ts | 105 ++ src/pagination.ts | 215 +-- src/resource.ts | 13 +- src/resources/access-tokens.ts | 5 +- src/resources/account.ts | 9 +- src/resources/connect/connect.ts | 2 +- src/resources/connect/sessions.ts | 11 +- src/resources/hris/benefits/benefits.ts | 116 +- src/resources/hris/benefits/index.ts | 6 +- src/resources/hris/benefits/individuals.ts | 111 +- src/resources/hris/company/company.ts | 20 +- src/resources/hris/company/index.ts | 2 +- .../hris/company/pay-statement-item/index.ts | 4 +- .../pay-statement-item/pay-statement-item.ts | 40 +- .../hris/company/pay-statement-item/rules.ts | 86 +- src/resources/hris/directory.ts | 26 +- src/resources/hris/documents.ts | 37 +- src/resources/hris/employments.ts | 18 +- src/resources/hris/hris.ts | 20 +- src/resources/hris/index.ts | 12 +- src/resources/hris/individuals.ts | 31 +- src/resources/hris/pay-statements.ts | 18 +- src/resources/hris/payments.ts | 19 +- src/resources/index.ts | 2 +- src/resources/jobs/automated.ts | 27 +- src/resources/jobs/jobs.ts | 2 +- src/resources/jobs/manual.ts | 10 +- src/resources/payroll/index.ts | 2 +- src/resources/payroll/pay-groups.ts | 52 +- src/resources/payroll/payroll.ts | 5 +- src/resources/providers.ts | 18 +- src/resources/request-forwarding.ts | 9 +- src/resources/sandbox/company.ts | 7 +- src/resources/sandbox/connections/accounts.ts | 19 +- .../sandbox/connections/connections.ts | 10 +- src/resources/sandbox/directory.ts | 35 +- src/resources/sandbox/employment.ts | 26 +- src/resources/sandbox/individual.ts | 26 +- src/resources/sandbox/jobs/configuration.ts | 12 +- src/resources/sandbox/jobs/jobs.ts | 7 +- src/resources/sandbox/payment.ts | 17 +- src/resources/sandbox/sandbox.ts | 2 +- src/resources/webhooks.ts | 2 +- src/shims/node.ts | 50 - src/shims/web.ts | 50 - src/uploads.ts | 257 +--- src/version.ts | 2 +- tests/api-resources/access-tokens.test.ts | 1 - tests/api-resources/account.test.ts | 15 - tests/api-resources/connect/sessions.test.ts | 1 - .../hris/benefits/benefits.test.ts | 36 - .../hris/benefits/individuals.test.ts | 39 +- .../hris/company/company.test.ts | 8 - .../pay-statement-item.test.ts | 8 - .../company/pay-statement-item/rules.test.ts | 29 - tests/api-resources/hris/directory.test.ts | 15 - tests/api-resources/hris/documents.test.ts | 15 - tests/api-resources/hris/employments.test.ts | 1 - tests/api-resources/hris/individuals.test.ts | 8 - .../api-resources/hris/pay-statements.test.ts | 1 - tests/api-resources/hris/payments.test.ts | 1 - tests/api-resources/jobs/automated.test.ts | 15 - tests/api-resources/jobs/manual.test.ts | 8 - .../api-resources/payroll/pay-groups.test.ts | 15 - tests/api-resources/providers.test.ts | 8 - .../api-resources/request-forwarding.test.ts | 1 - tests/api-resources/sandbox/company.test.ts | 1 - .../sandbox/connections/accounts.test.ts | 8 - .../sandbox/connections/connections.test.ts | 1 - tests/api-resources/sandbox/directory.test.ts | 108 +- .../api-resources/sandbox/employment.test.ts | 8 - .../api-resources/sandbox/individual.test.ts | 8 - .../sandbox/jobs/configuration.test.ts | 8 - tests/api-resources/sandbox/jobs/jobs.test.ts | 1 - tests/api-resources/sandbox/payment.test.ts | 8 - tests/base64.test.ts | 80 + tests/buildHeaders.test.ts | 88 ++ tests/form.test.ts | 88 +- tests/index.test.ts | 337 ++++- tests/path.test.ts | 462 ++++++ tests/qs/utils.test.ts | 2 +- tests/responses.test.ts | 25 - tests/uploads.test.ts | 48 +- tsc-multi.json | 12 +- tsconfig.build.json | 2 +- tsconfig.dist-src.json | 4 +- tsconfig.json | 3 +- yarn.lock | 1311 ++++++++--------- 186 files changed, 5974 insertions(+), 5107 deletions(-) delete mode 100644 .eslintrc.js delete mode 100644 .github/workflows/publish-npm.yml delete mode 100644 .github/workflows/release-doctor.yml delete mode 100644 .release-please-manifest.json create mode 100644 MIGRATION.md delete mode 100644 bin/check-release-environment create mode 100755 bin/cli create mode 100644 bin/migration-config.json create mode 100644 eslint.config.mjs delete mode 100644 release-please-config.json create mode 100644 scripts/utils/attw-report.cjs delete mode 100644 src/_shims/MultipartBody.ts delete mode 100644 src/_shims/README.md delete mode 100644 src/_shims/auto/runtime-bun.ts delete mode 100644 src/_shims/auto/runtime-deno.ts delete mode 100644 src/_shims/auto/runtime-node.ts delete mode 100644 src/_shims/auto/runtime.ts delete mode 100644 src/_shims/auto/types-deno.ts delete mode 100644 src/_shims/auto/types-node.ts delete mode 100644 src/_shims/auto/types.d.ts delete mode 100644 src/_shims/auto/types.js delete mode 100644 src/_shims/auto/types.mjs delete mode 100644 src/_shims/bun-runtime.ts delete mode 100644 src/_shims/index-deno.ts delete mode 100644 src/_shims/index.d.ts delete mode 100644 src/_shims/index.js delete mode 100644 src/_shims/index.mjs delete mode 100644 src/_shims/manual-types.d.ts delete mode 100644 src/_shims/manual-types.js delete mode 100644 src/_shims/manual-types.mjs delete mode 100644 src/_shims/node-runtime.ts delete mode 100644 src/_shims/node-types.d.ts delete mode 100644 src/_shims/node-types.js delete mode 100644 src/_shims/node-types.mjs delete mode 100644 src/_shims/registry.ts delete mode 100644 src/_shims/web-runtime.ts delete mode 100644 src/_shims/web-types.d.ts delete mode 100644 src/_shims/web-types.js delete mode 100644 src/_shims/web-types.mjs create mode 100644 src/api-promise.ts create mode 100644 src/client.ts delete mode 100644 src/core.ts create mode 100644 src/core/README.md create mode 100644 src/core/api-promise.ts create mode 100644 src/core/error.ts create mode 100644 src/core/pagination.ts create mode 100644 src/core/resource.ts create mode 100644 src/core/uploads.ts create mode 100644 src/internal/README.md create mode 100644 src/internal/builtin-types.ts create mode 100644 src/internal/detect-platform.ts create mode 100644 src/internal/errors.ts create mode 100644 src/internal/headers.ts create mode 100644 src/internal/parse.ts create mode 100644 src/internal/request-options.ts create mode 100644 src/internal/shim-types.ts create mode 100644 src/internal/shims.ts create mode 100644 src/internal/to-file.ts create mode 100644 src/internal/types.ts create mode 100644 src/internal/uploads.ts create mode 100644 src/internal/utils.ts create mode 100644 src/internal/utils/base64.ts create mode 100644 src/internal/utils/bytes.ts create mode 100644 src/internal/utils/env.ts create mode 100644 src/internal/utils/log.ts create mode 100644 src/internal/utils/path.ts create mode 100644 src/internal/utils/sleep.ts create mode 100644 src/internal/utils/uuid.ts create mode 100644 src/internal/utils/values.ts delete mode 100644 src/shims/node.ts delete mode 100644 src/shims/web.ts create mode 100644 tests/base64.test.ts create mode 100644 tests/buildHeaders.test.ts create mode 100644 tests/path.test.ts delete mode 100644 tests/responses.test.ts diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 60f0e7a35..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], - rules: { - 'no-unused-vars': 'off', - 'prettier/prettier': 'error', - 'unused-imports/no-unused-imports': 'error', - }, - root: true, -}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1ca7188f..791fb1bc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: lint: timeout-minutes: 10 name: lint - runs-on: ${{ github.repository == 'stainless-sdks/finch-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ github.repository == 'stainless-sdks/finch-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '22' - name: Bootstrap run: ./scripts/bootstrap @@ -35,7 +35,7 @@ jobs: build: timeout-minutes: 5 name: build - runs-on: ${{ github.repository == 'stainless-sdks/finch-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ github.repository == 'stainless-sdks/finch-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork permissions: contents: read @@ -46,7 +46,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '22' - name: Bootstrap run: ./scripts/bootstrap @@ -55,14 +55,14 @@ jobs: run: ./scripts/build - name: Get GitHub OIDC Token - if: github.repository == 'stainless-sdks/finch-node' + if: github.repository == 'stainless-sdks/finch-typescript' id: github-oidc uses: actions/github-script@v6 with: script: core.setOutput('github_token', await core.getIDToken()); - name: Upload tarball - if: github.repository == 'stainless-sdks/finch-node' + if: github.repository == 'stainless-sdks/finch-typescript' env: URL: https://pkg.stainless.com/s AUTH: ${{ steps.github-oidc.outputs.github_token }} @@ -70,7 +70,7 @@ jobs: run: ./scripts/utils/upload-artifact.sh - name: Upload MCP Server tarball - if: github.repository == 'stainless-sdks/finch-node' + if: github.repository == 'stainless-sdks/finch-typescript' env: URL: https://pkg.stainless.com/s?subpackage=mcp-server AUTH: ${{ steps.github-oidc.outputs.github_token }} @@ -80,7 +80,7 @@ jobs: test: timeout-minutes: 10 name: test - runs-on: ${{ github.repository == 'stainless-sdks/finch-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ github.repository == 'stainless-sdks/finch-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml deleted file mode 100644 index 459a68590..000000000 --- a/.github/workflows/publish-npm.yml +++ /dev/null @@ -1,50 +0,0 @@ -# This workflow is triggered when a GitHub release is created. -# It can also be run manually to re-publish to NPM in case it failed for some reason. -# You can run this workflow by navigating to https://www.github.com/Finch-API/finch-api-node/actions/workflows/publish-npm.yml -name: Publish NPM -on: - workflow_dispatch: - inputs: - path: - description: The path to run the release in, e.g. '.' or 'packages/mcp-server' - required: true - - release: - types: [published] - -jobs: - publish: - name: publish - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - uses: actions/checkout@v4 - - - name: Set up Node - uses: actions/setup-node@v3 - with: - node-version: '20' - - - name: Install dependencies - run: | - yarn install - - - name: Publish to NPM - run: | - if [ -n "${{ github.event.inputs.path }}" ]; then - PATHS_RELEASED='[\"${{ github.event.inputs.path }}\"]' - else - PATHS_RELEASED='[\".\", \"packages/mcp-server\"]' - fi - yarn tsn scripts/publish-packages.ts "{ \"paths_released\": \"$PATHS_RELEASED\" }" - env: - NPM_TOKEN: ${{ secrets.FINCH_NPM_TOKEN || secrets.NPM_TOKEN }} - - - name: Upload MCP Server DXT GitHub release asset - run: | - gh release upload ${{ github.event.release.tag_name }} \ - packages/mcp-server/tryfinch_finch_api_api.mcpb - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml deleted file mode 100644 index 01f642c5f..000000000 --- a/.github/workflows/release-doctor.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Release Doctor -on: - pull_request: - branches: - - main - workflow_dispatch: - -jobs: - release_doctor: - name: release doctor - runs-on: ubuntu-latest - if: github.repository == 'Finch-API/finch-api-node' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') - - steps: - - uses: actions/checkout@v4 - - - name: Check release environment - run: | - bash ./bin/check-release-environment - env: - NPM_TOKEN: ${{ secrets.FINCH_NPM_TOKEN || secrets.NPM_TOKEN }} - diff --git a/.release-please-manifest.json b/.release-please-manifest.json deleted file mode 100644 index 42cf93a67..000000000 --- a/.release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "6.39.0" -} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b5563bb2..b12249840 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,35 +1,5 @@ # Changelog -## 6.39.0 (2025-11-03) - -Full Changelog: [v6.38.2...v6.39.0](https://github.com/Finch-API/finch-api-node/compare/v6.38.2...v6.39.0) - -### Features - -* **api:** api update ([b6892f5](https://github.com/Finch-API/finch-api-node/commit/b6892f5c579a0eeb8fa03863d9a72faa36adab9d)) - - -### Chores - -* **internal:** grammar fix (it's -> its) ([54ad7ca](https://github.com/Finch-API/finch-api-node/commit/54ad7ca2689051eccdb1de832e9041e3a6fe8d52)) -* use structured error when code execution tool errors ([a85e9d8](https://github.com/Finch-API/finch-api-node/commit/a85e9d8431d5b22e82f34606a813821f5573d197)) - -## 6.38.2 (2025-10-31) - -Full Changelog: [v6.38.1...v6.38.2](https://github.com/Finch-API/finch-api-node/compare/v6.38.1...v6.38.2) - -### Chores - -* **ownership:** move from data to platform team ([#633](https://github.com/Finch-API/finch-api-node/issues/633)) ([1e3cc40](https://github.com/Finch-API/finch-api-node/commit/1e3cc404f3722b90d1b24223d6e08834df227ff6)) - -## 6.38.1 (2025-10-30) - -Full Changelog: [v6.38.0...v6.38.1](https://github.com/Finch-API/finch-api-node/compare/v6.38.0...v6.38.1) - -### Bug Fixes - -* **mcpb:** pin @anthropic-ai/mcpb version ([77d0632](https://github.com/Finch-API/finch-api-node/commit/77d0632d886fee1d2358b723de7b9585dc2db6b6)) - ## 6.38.0 (2025-10-27) Full Changelog: [v6.37.0...v6.38.0](https://github.com/Finch-API/finch-api-node/compare/v6.37.0...v6.38.0) diff --git a/CODEOWNERS b/CODEOWNERS index 3b10bb3e4..4c449e5e2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @Finch-API/team-platform +* @Finch-API/team-data diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index af1d7143b..9bea7aaa6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,15 +42,15 @@ If you’d like to use the repository from source, you can either install from g To install via git: ```sh -$ npm install git+ssh://git@github.com:Finch-API/finch-api-node.git +$ npm install git+ssh://git@github.com:stainless-sdks/finch-typescript.git ``` Alternatively, to link a local copy of the repo: ```sh # Clone -$ git clone https://www.github.com/Finch-API/finch-api-node -$ cd finch-api-node +$ git clone https://www.github.com/stainless-sdks/finch-typescript +$ cd finch-typescript # With yarn $ yarn link @@ -91,17 +91,3 @@ To format and fix all lint issues automatically: ```sh $ yarn fix ``` - -## Publishing and releases - -Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If -the changes aren't made through the automated pipeline, you may want to make releases manually. - -### Publish with a GitHub workflow - -You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/Finch-API/finch-api-node/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up. - -### Publish manually - -If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on -the environment. diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 000000000..7511b7531 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,387 @@ +# Migration guide + +This guide outlines the changes and steps needed to migrate your codebase to the latest version of the Finch TypeScript SDK. + +The main changes are that the SDK now relies on the [builtin Web fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) instead of `node-fetch` and has zero dependencies. + +## Migration CLI + +Most programs will only need minimal changes, but to assist there is a migration tool that will automatically update your code for the new version. +To use it, upgrade the `@tryfinch/finch-api` package, then run `./node_modules/.bin/tryfinch-finch-api migrate ./your/src/folders` to update your code. +To preview the changes without writing them to disk, run the tool with `--dry`. + +## Environment requirements + +The minimum supported runtime and tooling versions are now: + +- Node.js 20 LTS (Most recent non-EOL Node version) +- TypeScript 4.9 +- Jest 28 + +## Breaking changes + +### Web types for `withResponse`, `asResponse`, and `APIError.headers` + +Because we now use the builtin Web fetch API on all platforms, if you wrote code that used `withResponse` or `asResponse` and then accessed `node-fetch`-specific properties on the result, you will need to switch to standardized alternatives. +For example, `body` is now a [Web `ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) rather than a [node `Readable`](https://nodejs.org/api/stream.html#readable-streams). + +```ts +// Before: +const res = await client.example.retrieve('string/with/slash').asResponse(); +res.body.pipe(process.stdout); + +// After: +import { Readable } from 'node:stream'; +const res = await client.example.retrieve('string/with/slash').asResponse(); +Readable.fromWeb(res.body).pipe(process.stdout); +``` + +Additionally, the `headers` property on `APIError` objects is now an instance of the Web [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) class. It was previously defined as `Record`. + +### URI encoded path parameters + +Path params are now properly encoded by default. If you were manually encoding path parameters before giving them to the SDK, you must now stop doing that and pass the +param without any encoding applied. + +For example: + +```diff +- client.example.retrieve(encodeURIComponent('string/with/slash')) ++ client.example.retrieve('string/with/slash') // retrieves /example/string%2Fwith%2Fslash +``` + +Previously without the `encodeURIComponent()` call we would have used the path `/example/string/with/slash`; now we'll use `/example/string%2Fwith%2Fslash`. + +### Method params must be an object + +When making requests to endpoints that expect something other than a JSON object, you must now pass the body as a property instead +of an individual argument. + +For example, an endpoint that takes an array: + +```typescript +// Before +client.example.create([{ name: 'name' }, { name: 'name' }]); + +// After +client.example.create({ items: [{ name: 'name' }, { name: 'name' }] }); +``` + +This affects the following methods: + +- `client.hris.benefits.individuals.enrollMany()` +- `client.sandbox.directory.create()` + +### Removed request options overloads + +When making requests with no required body, query or header parameters, you must now explicitly pass `null`, `undefined` or an empty object `{}` to the params argument in order to customise request options. + +```diff +client.example.list(); +client.example.list({}, { headers: { ... } }); +client.example.list(null, { headers: { ... } }); +client.example.list(undefined, { headers: { ... } }); +- client.example.list({ headers: { ... } }); ++ client.example.list({}, { headers: { ... } }); +``` + +
+ +This affects the following methods + +- `client.hris.company.retrieve()` +- `client.hris.company.payStatementItem.list()` +- `client.hris.company.payStatementItem.rules.create()` +- `client.hris.company.payStatementItem.rules.update()` +- `client.hris.company.payStatementItem.rules.list()` +- `client.hris.company.payStatementItem.rules.delete()` +- `client.hris.directory.list()` +- `client.hris.directory.listIndividuals()` +- `client.hris.individuals.retrieveMany()` +- `client.hris.documents.list()` +- `client.hris.documents.retreive()` +- `client.hris.benefits.create()` +- `client.hris.benefits.retrieve()` +- `client.hris.benefits.update()` +- `client.hris.benefits.list()` +- `client.hris.benefits.listSupportedBenefits()` +- `client.hris.benefits.individuals.enrollMany()` +- `client.hris.benefits.individuals.enrolledIDs()` +- `client.hris.benefits.individuals.retrieveManyBenefits()` +- `client.hris.benefits.individuals.unenrollMany()` +- `client.jobs.automated.list()` +- `client.sandbox.connections.accounts.update()` +- `client.sandbox.directory.create()` +- `client.sandbox.individual.update()` +- `client.sandbox.employment.update()` +- `client.sandbox.payment.create()` +- `client.payroll.payGroups.retrieve()` +- `client.payroll.payGroups.list()` + +
+ +### Removed `httpAgent` in favor of `fetchOptions` + +The `httpAgent` client option has been removed in favor of a [platform-specific `fetchOptions` property](https://github.com/stainless-sdks/finch-typescript#fetch-options). +This change was made as `httpAgent` relied on `node:http` agents which are not supported by any runtime's builtin fetch implementation. + +If you were using `httpAgent` for proxy support, check out the [new proxy documentation](https://github.com/stainless-sdks/finch-typescript#configuring-proxies). + +Before: + +```ts +import Finch from '@tryfinch/finch-api'; +import http from 'http'; +import { HttpsProxyAgent } from 'https-proxy-agent'; + +// Configure the default for all requests: +const client = new Finch({ + httpAgent: new HttpsProxyAgent(process.env.PROXY_URL), +}); +``` + +After: + +```ts +import Finch from '@tryfinch/finch-api'; +import * as undici from 'undici'; + +const proxyAgent = new undici.ProxyAgent(process.env.PROXY_URL); +const client = new Finch({ + fetchOptions: { + dispatcher: proxyAgent, + }, +}); +``` + +### Changed exports + +#### Refactor of `@tryfinch/finch-api/core`, `error`, `pagination`, `resource` and `uploads` + +Much of the `@tryfinch/finch-api/core` file was intended to be internal-only but it was publicly accessible, as such it has been refactored and split up into internal and public files, with public-facing code moved to a new `core` folder and internal code moving to the private `internal` folder. + +At the same time, we moved some public-facing files which were previously at the top level into `core` to make the file structure cleaner and more clear: + +```typescript +// Before +import '@tryfinch/finch-api/error'; +import '@tryfinch/finch-api/pagination'; +import '@tryfinch/finch-api/resource'; +import '@tryfinch/finch-api/uploads'; + +// After +import '@tryfinch/finch-api/core/error'; +import '@tryfinch/finch-api/core/pagination'; +import '@tryfinch/finch-api/core/resource'; +import '@tryfinch/finch-api/core/uploads'; +``` + +If you were relying on anything that was only exported from `@tryfinch/finch-api/core` and is also not accessible anywhere else, please open an issue and we'll consider adding it to the public API. + +#### Resource classes + +Previously under certain circumstances it was possible to import resource classes like `AccessTokens` directly from the root of the package. This was never valid at the type level and only worked in CommonJS files. +Now you must always either reference them as static class properties or import them directly from the files in which they are defined. + +```typescript +// Before +const { AccessTokens } = require('@tryfinch/finch-api'); + +// After +const { Finch } = require('@tryfinch/finch-api'); +Finch.AccessTokens; // or import directly from @tryfinch/finch-api/resources/access-tokens +``` + +#### Cleaned up `uploads` exports + +As part of the `core` refactor, `@tryfinch/finch-api/uploads` was moved to `@tryfinch/finch-api/core/uploads` +and the following exports were removed, as they were not intended to be a part of the public API: + +- `fileFromPath` +- `BlobPart` +- `BlobLike` +- `FileLike` +- `ResponseLike` +- `isResponseLike` +- `isBlobLike` +- `isFileLike` +- `isUploadable` +- `isMultipartBody` +- `maybeMultipartFormRequestOptions` +- `multipartFormRequestOptions` +- `createForm` + +Note that `Uploadable` & `toFile` **are** still exported: + +```typescript +import { type Uploadable, toFile } from '@tryfinch/finch-api/core/uploads'; +``` + +#### `APIClient` + +The `APIClient` base client class has been removed as it is no longer needed. If you were importing this class then you must now import the main client class: + +```typescript +// Before +import { APIClient } from '@tryfinch/finch-api/core'; + +// After +import { Finch } from '@tryfinch/finch-api'; +``` + +### File handling + +The deprecated `fileFromPath` helper has been removed in favor of native Node.js streams: + +```ts +// Before +Finch.fileFromPath('path/to/file'); + +// After +import fs from 'fs'; +fs.createReadStream('path/to/file'); +``` + +Note that this function previously only worked on Node.js. If you're using Bun, you can use [`Bun.file`](https://bun.sh/docs/api/file-io) instead. + +### Shims removal + +Previously you could configure the types that the SDK used like this: + +```ts +// Tell TypeScript and the package to use the global Web fetch instead of node-fetch. +import '@tryfinch/finch-api/shims/web'; +import Finch from '@tryfinch/finch-api'; +``` + +The `@tryfinch/finch-api/shims` imports have been removed. Your global types must now be [correctly configured](#minimum-types-requirements). + +### Pagination changes + +The `for await` syntax **is not affected**. This still works as-is: + +```ts +// Automatically fetches more pages as needed. +for await (const individualInDirectory of client.hris.directory.list()) { + console.log(individualInDirectory); +} +``` + +The interface for manually paginating through list results has been simplified: + +```ts +// Before +page.nextPageParams(); +page.nextPageInfo(); +// Required manually handling { url } | { params } type + +// After +page.nextPageRequestOptions(); +``` + +#### Removed unnecessary classes + +Page classes for individual methods are now type aliases: + +```ts +// Before +export class IndividualsPage extends IndividualsPage {} + +// After +export type IndividualsPage = IndividualsPage; +``` + +If you were importing these classes at runtime, you'll need to switch to importing the base class or only import them at the type-level. + +### `@tryfinch/finch-api/src` directory removed + +Previously IDEs may have auto-completed imports from the `@tryfinch/finch-api/src` directory, however this +directory was only included for an improved go-to-definition experience and should not have been used at runtime. + +If you have any `@tryfinch/finch-api/src/*` imports, you will need to replace them with `@tryfinch/finch-api/*`. + +```ts +// Before +import Finch from '@tryfinch/finch-api/src'; + +// After +import Finch from '@tryfinch/finch-api'; +``` + +## TypeScript troubleshooting + +When referencing the library after updating, you may encounter new type errors related to JS features like private properties or fetch classes like Request, Response, and Headers. +To resolve these issues, configure your tsconfig.json and install the appropriate `@types` packages for your runtime environment using the guidelines below: + +### Browsers + +`tsconfig.json` + +```jsonc +{ + "target": "ES2018", // note: we recommend ES2020 or higher + "lib": ["DOM", "DOM.Iterable", "ES2018"] +} +``` + +### Node.js + +`tsconfig.json` + +```jsonc +{ + "target": "ES2018" // note: we recommend ES2020 or higher +} +``` + +`package.json` + +```json +{ + "devDependencies": { + "@types/node": ">= 20" + } +} +``` + +### Cloudflare Workers + +`tsconfig.json` + +```jsonc +{ + "target": "ES2018", // note: we recommend ES2020 or higher + "lib": ["ES2020"], // <- needed by @cloudflare/workers-types + "types": ["@cloudflare/workers-types"] +} +``` + +`package.json` + +```json +{ + "devDependencies": { + "@cloudflare/workers-types": ">= 0.20221111.0" + } +} +``` + +### Bun + +`tsconfig.json` + +```jsonc +{ + "target": "ES2018" // note: we recommend ES2020 or higher +} +``` + +`package.json` + +```json +{ + "devDependencies": { + "@types/bun": ">= 1.2.0" + } +} +``` diff --git a/README.md b/README.md index dc8e3c7ab..affc95297 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Finch Node API Library +# Finch TypeScript API Library -[![NPM version](https://img.shields.io/npm/v/@tryfinch/finch-api.svg)](https://npmjs.org/package/@tryfinch/finch-api) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@tryfinch/finch-api) +[![NPM version]()](https://npmjs.org/package/@tryfinch/finch-api) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@tryfinch/finch-api) This library provides convenient access to the Finch REST API from server-side TypeScript or JavaScript. @@ -11,9 +11,12 @@ It is generated with [Stainless](https://www.stainless.com/). ## Installation ```sh -npm install @tryfinch/finch-api +npm install git+ssh://git@github.com:stainless-sdks/finch-typescript.git ``` +> [!NOTE] +> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npm install @tryfinch/finch-api` + ## Usage The full API of this library can be found in [api.md](api.md). @@ -205,8 +208,10 @@ export default async function POST(req) { ### Accessing raw Response data (e.g., headers) The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return. +This method returns as soon as the headers for a successful response are received and does not consume the response body, so you are free to write custom parsing or streaming logic. You can also use the `.withResponse()` method to get the raw `Response` along with the parsed data. +Unlike `.asResponse()` this method consumes the body, returning once it is parsed. ```ts @@ -223,6 +228,59 @@ for await (const individualInDirectory of page) { } ``` +### Logging + +> [!IMPORTANT] +> All log messages are intended for debugging only. The format and content of log messages +> may change between releases. + +#### Log levels + +The log level can be configured in two ways: + +1. Via the `FINCH_LOG` environment variable +2. Using the `logLevel` client option (overrides the environment variable if set) + +```ts +import Finch from '@tryfinch/finch-api'; + +const client = new Finch({ + logLevel: 'debug', // Show all log messages +}); +``` + +Available log levels, from most to least verbose: + +- `'debug'` - Show debug messages, info, warnings, and errors +- `'info'` - Show info messages, warnings, and errors +- `'warn'` - Show warnings and errors (default) +- `'error'` - Show only errors +- `'off'` - Disable all logging + +At the `'debug'` level, all HTTP requests and responses are logged, including headers and bodies. +Some authentication-related headers are redacted, but sensitive data in request and response bodies +may still be visible. + +#### Custom logger + +By default, this library logs to `globalThis.console`. You can also provide a custom logger. +Most logging libraries are supported, including [pino](https://www.npmjs.com/package/pino), [winston](https://www.npmjs.com/package/winston), [bunyan](https://www.npmjs.com/package/bunyan), [consola](https://www.npmjs.com/package/consola), [signale](https://www.npmjs.com/package/signale), and [@std/log](https://jsr.io/@std/log). If your logger doesn't work, please open an issue. + +When providing a custom logger, the `logLevel` option still controls which messages are emitted, messages +below the configured level will not be sent to your logger. + +```ts +import Finch from '@tryfinch/finch-api'; +import pino from 'pino'; + +const logger = pino(); + +const client = new Finch({ + logger: logger.child({ name: 'Finch' }), + logLevel: 'debug', // Send all messages to pino, allowing it to filter +}); +``` + ### Making custom/undocumented requests This library is typed for convenient access to the documented API. If you need to access undocumented @@ -247,9 +305,8 @@ parameter. This library doesn't validate at runtime that the request matches the send will be sent as-is. ```ts -client.foo.create({ - foo: 'my_param', - bar: 12, +client.hris.directory.list({ + // ... // @ts-expect-error baz is not yet public baz: 'undocumented option', }); @@ -269,66 +326,85 @@ validate or strip extra properties from the response from the API. ### Customizing the fetch client -By default, this library uses `node-fetch` in Node, and expects a global `fetch` function in other environments. +By default, this library expects a global `fetch` function is defined. -If you would prefer to use a global, web-standards-compliant `fetch` function even in a Node environment, -(for example, if you are running Node with `--experimental-fetch` or using NextJS which polyfills with `undici`), -add the following import before your first import `from "Finch"`: +If you want to use a different `fetch` function, you can either polyfill the global: ```ts -// Tell TypeScript and the package to use the global web fetch instead of node-fetch. -// Note, despite the name, this does not add any polyfills, but expects them to be provided if needed. -import '@tryfinch/finch-api/shims/web'; -import Finch from '@tryfinch/finch-api'; +import fetch from 'my-fetch'; + +globalThis.fetch = fetch; ``` -To do the inverse, add `import "@tryfinch/finch-api/shims/node"` (which does import polyfills). -This can also be useful if you are getting the wrong TypeScript types for `Response` ([more details](https://github.com/Finch-API/finch-api-node/tree/main/src/_shims#readme)). +Or pass it to the client: + +```ts +import Finch from '@tryfinch/finch-api'; +import fetch from 'my-fetch'; + +const client = new Finch({ fetch }); +``` -### Logging and middleware +### Fetch options -You may also provide a custom `fetch` function when instantiating the client, -which can be used to inspect or alter the `Request` or `Response` before/after each request: +If you want to set custom `fetch` options without overriding the `fetch` function, you can provide a `fetchOptions` object when instantiating the client or making a request. (Request-specific options override client options.) ```ts -import { fetch } from 'undici'; // as one example import Finch from '@tryfinch/finch-api'; const client = new Finch({ - fetch: async (url: RequestInfo, init?: RequestInit): Promise => { - console.log('About to make a request', url, init); - const response = await fetch(url, init); - console.log('Got response', response); - return response; + fetchOptions: { + // `RequestInit` options }, }); ``` -Note that if given a `DEBUG=true` environment variable, this library will log all requests and responses automatically. -This is intended for debugging purposes only and may change in the future without notice. +#### Configuring proxies -### Configuring an HTTP(S) Agent (e.g., for proxies) +To modify proxy behavior, you can provide custom `fetchOptions` that add runtime-specific proxy +options to requests: -By default, this library uses a stable agent for all http/https requests to reuse TCP connections, eliminating many TCP & TLS handshakes and shaving around 100ms off most requests. + **Node** [[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)] -If you would like to disable or customize this behavior, for example to use the API behind a proxy, you can pass an `httpAgent` which is used for all requests (be they http or https), for example: +```ts +import Finch from '@tryfinch/finch-api'; +import * as undici from 'undici'; + +const proxyAgent = new undici.ProxyAgent('http://localhost:8888'); +const client = new Finch({ + fetchOptions: { + dispatcher: proxyAgent, + }, +}); +``` + + **Bun** [[docs](https://bun.sh/guides/http/proxy)] - ```ts -import http from 'http'; -import { HttpsProxyAgent } from 'https-proxy-agent'; +import Finch from '@tryfinch/finch-api'; -// Configure the default for all requests: const client = new Finch({ - httpAgent: new HttpsProxyAgent(process.env.PROXY_URL), + fetchOptions: { + proxy: 'http://localhost:8888', + }, }); +``` -// Override per-request: -await client.hris.directory.list({ - httpAgent: new http.Agent({ keepAlive: false }), + **Deno** [[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)] + +```ts +import Finch from 'npm:@tryfinch/finch-api'; + +const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } }); +const client = new Finch({ + fetchOptions: { + client: httpClient, + }, }); ``` +## Frequently Asked Questions + ## Semantic versioning This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: @@ -339,15 +415,15 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. -We are keen for your feedback; please open an [issue](https://www.github.com/Finch-API/finch-api-node/issues) with questions, bugs, or suggestions. +We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/finch-typescript/issues) with questions, bugs, or suggestions. ## Requirements -TypeScript >= 4.5 is supported. +TypeScript >= 4.9 is supported. The following runtimes are supported: -- Node.js 18 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions. +- Node.js 20 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions. - Deno v1.28.0 or higher. - Bun 1.0 or later. - Cloudflare Workers. diff --git a/api.md b/api.md index 4395a45a7..40b2f2745 100644 --- a/api.md +++ b/api.md @@ -64,9 +64,9 @@ Types: Methods: - client.hris.company.payStatementItem.rules.create({ ...params }) -> RuleCreateResponse -- client.hris.company.payStatementItem.rules.update(ruleId, { ...params }) -> RuleUpdateResponse +- client.hris.company.payStatementItem.rules.update(ruleID, { ...params }) -> RuleUpdateResponse - client.hris.company.payStatementItem.rules.list({ ...params }) -> RuleListResponsesPage -- client.hris.company.payStatementItem.rules.delete(ruleId, { ...params }) -> RuleDeleteResponse +- client.hris.company.payStatementItem.rules.delete(ruleID, { ...params }) -> RuleDeleteResponse ## Directory @@ -136,7 +136,7 @@ Types: Methods: - client.hris.documents.list({ ...params }) -> DocumentListResponse -- client.hris.documents.retreive(documentId, { ...params }) -> DocumentRetreiveResponse +- client.hris.documents.retreive(documentID, { ...params }) -> DocumentRetreiveResponse ## Benefits @@ -157,8 +157,8 @@ Types: Methods: - client.hris.benefits.create({ ...params }) -> CreateCompanyBenefitsResponse -- client.hris.benefits.retrieve(benefitId, { ...params }) -> CompanyBenefit -- client.hris.benefits.update(benefitId, { ...params }) -> UpdateCompanyBenefitResponse +- client.hris.benefits.retrieve(benefitID, { ...params }) -> CompanyBenefit +- client.hris.benefits.update(benefitID, { ...params }) -> UpdateCompanyBenefitResponse - client.hris.benefits.list({ ...params }) -> CompanyBenefitsSinglePage - client.hris.benefits.listSupportedBenefits({ ...params }) -> SupportedBenefitsSinglePage @@ -173,10 +173,10 @@ Types: Methods: -- client.hris.benefits.individuals.enrollMany(benefitId, [ ...individuals ]) -> EnrolledIndividualBenefitResponse -- client.hris.benefits.individuals.enrolledIds(benefitId, { ...params }) -> IndividualEnrolledIDsResponse -- client.hris.benefits.individuals.retrieveManyBenefits(benefitId, { ...params }) -> IndividualBenefitsSinglePage -- client.hris.benefits.individuals.unenrollMany(benefitId, { ...params }) -> UnenrolledIndividualBenefitResponse +- client.hris.benefits.individuals.enrollMany(benefitID, [ ...individuals ]) -> EnrolledIndividualBenefitResponse +- client.hris.benefits.individuals.enrolledIDs(benefitID, { ...params }) -> IndividualEnrolledIDsResponse +- client.hris.benefits.individuals.retrieveManyBenefits(benefitID, { ...params }) -> IndividualBenefitsSinglePage +- client.hris.benefits.individuals.unenrollMany(benefitID, { ...params }) -> UnenrolledIndividualBenefitResponse # Providers @@ -244,7 +244,7 @@ Types: Methods: - client.jobs.automated.create({ ...params }) -> AutomatedCreateResponse -- client.jobs.automated.retrieve(jobId) -> AutomatedAsyncJob +- client.jobs.automated.retrieve(jobID) -> AutomatedAsyncJob - client.jobs.automated.list({ ...params }) -> AutomatedListResponse ## Manual @@ -255,7 +255,7 @@ Types: Methods: -- client.jobs.manual.retrieve(jobId) -> ManualAsyncJob +- client.jobs.manual.retrieve(jobID) -> ManualAsyncJob # Sandbox @@ -309,7 +309,7 @@ Types: Methods: -- client.sandbox.individual.update(individualId, { ...params }) -> IndividualUpdateResponse +- client.sandbox.individual.update(individualID, { ...params }) -> IndividualUpdateResponse ## Employment @@ -319,7 +319,7 @@ Types: Methods: -- client.sandbox.employment.update(individualId, { ...params }) -> EmploymentUpdateResponse +- client.sandbox.employment.update(individualID, { ...params }) -> EmploymentUpdateResponse ## Payment @@ -364,7 +364,7 @@ Types: Methods: -- client.payroll.payGroups.retrieve(payGroupId, { ...params }) -> PayGroupRetrieveResponse +- client.payroll.payGroups.retrieve(payGroupID, { ...params }) -> PayGroupRetrieveResponse - client.payroll.payGroups.list({ ...params }) -> PayGroupListResponsesSinglePage # Connect diff --git a/bin/check-release-environment b/bin/check-release-environment deleted file mode 100644 index e4b6d58ea..000000000 --- a/bin/check-release-environment +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -errors=() - -if [ -z "${NPM_TOKEN}" ]; then - errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") -fi - -lenErrors=${#errors[@]} - -if [[ lenErrors -gt 0 ]]; then - echo -e "Found the following errors in the release environment:\n" - - for error in "${errors[@]}"; do - echo -e "- $error\n" - done - - exit 1 -fi - -echo "The environment is ready to push releases!" - diff --git a/bin/cli b/bin/cli new file mode 100755 index 000000000..38f15a1d6 --- /dev/null +++ b/bin/cli @@ -0,0 +1,53 @@ +#!/usr/bin/env node + +const { spawnSync } = require('child_process'); + +const commands = { + migrate: { + description: + 'Run migrations to update your code using @tryfinch/finch-api@6 to be compatible with @tryfinch/finch-api@7', + fn: () => { + const result = spawnSync( + 'npx', + [ + '-y', + 'https://github.com/stainless-api/migrate-ts/releases/download/0.0.2/stainless-api-migrate-0.0.2-6.tgz', + '--migrationConfig', + require.resolve('./migration-config.json'), + ...process.argv.slice(3), + ], + { stdio: 'inherit' }, + ); + if (result.status !== 0) { + process.exit(result.status); + } + }, + }, +}; + +function exitWithHelp() { + console.log(`Usage: tryfinch-finch-api `); + console.log(); + console.log('Subcommands:'); + + for (const [name, info] of Object.entries(commands)) { + console.log(` ${name} ${info.description}`); + } + + console.log(); + process.exit(1); +} + +if (process.argv.length < 3) { + exitWithHelp(); +} + +const commandName = process.argv[2]; + +const command = commands[commandName]; +if (!command) { + console.log(`Unknown subcommand ${commandName}.`); + exitWithHelp(); +} + +command.fn(); diff --git a/bin/migration-config.json b/bin/migration-config.json new file mode 100644 index 000000000..e544cb7c0 --- /dev/null +++ b/bin/migration-config.json @@ -0,0 +1,6 @@ +{ + "pkg": "@tryfinch/finch-api", + "githubRepo": "https://github.com/stainless-sdks/finch-typescript", + "clientClass": "Finch", + "methods": [] +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..8dff63055 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,42 @@ +// @ts-check +import tseslint from 'typescript-eslint'; +import unusedImports from 'eslint-plugin-unused-imports'; +import prettier from 'eslint-plugin-prettier'; + +export default tseslint.config( + { + languageOptions: { + parser: tseslint.parser, + parserOptions: { sourceType: 'module' }, + }, + files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.js', '**/*.mjs', '**/*.cjs'], + ignores: ['dist/'], + plugins: { + '@typescript-eslint': tseslint.plugin, + 'unused-imports': unusedImports, + prettier, + }, + rules: { + 'no-unused-vars': 'off', + 'prettier/prettier': 'error', + 'unused-imports/no-unused-imports': 'error', + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + regex: '^@tryfinch/finch-api(/.*)?', + message: 'Use a relative import, not a package import.', + }, + ], + }, + ], + }, + }, + { + files: ['tests/**', 'examples/**', 'packages/**'], + rules: { + 'no-restricted-imports': 'off', + }, + }, +); diff --git a/jest.config.ts b/jest.config.ts index 045e4de7c..4f1313f6b 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -8,7 +8,6 @@ const config: JestConfigWithTsJest = { }, moduleNameMapper: { '^@tryfinch/finch-api$': '/src/index.ts', - '^@tryfinch/finch-api/_shims/auto/(.*)$': '/src/_shims/auto/$1-node', '^@tryfinch/finch-api/(.*)$': '/src/$1', }, modulePathIgnorePatterns: [ @@ -16,6 +15,7 @@ const config: JestConfigWithTsJest = { '/dist/', '/deno/', '/deno_tests/', + '/packages/', ], testPathIgnorePatterns: ['scripts'], }; diff --git a/package.json b/package.json index 8652fcbd6..871d84589 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "@tryfinch/finch-api", - "version": "6.39.0", + "version": "6.38.0", "description": "The official TypeScript library for the Finch API", "author": "Finch ", "types": "dist/index.d.ts", "main": "dist/index.js", "type": "commonjs", - "repository": "github:Finch-API/finch-api-node", + "repository": "github:stainless-sdks/finch-typescript", "license": "Apache-2.0", "packageManager": "yarn@1.22.22", "files": [ @@ -20,104 +20,53 @@ "test": "./scripts/test", "build": "./scripts/build", "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", - "format": "prettier --write --cache --cache-strategy metadata . !dist", + "format": "./scripts/format", "prepare": "if ./scripts/utils/check-is-in-git-install.sh; then ./scripts/build && ./scripts/utils/git-swap.sh; fi", "tsn": "ts-node -r tsconfig-paths/register", "lint": "./scripts/lint", "fix": "./scripts/format" }, - "dependencies": { - "@noble/hashes": "^1.5.0", - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, + "dependencies": {}, "devDependencies": { + "@arethetypeswrong/cli": "^0.17.0", "@swc/core": "^1.3.102", "@swc/jest": "^0.2.29", "@types/jest": "^29.4.0", - "@typescript-eslint/eslint-plugin": "^6.7.0", - "@typescript-eslint/parser": "^6.7.0", - "eslint": "^8.49.0", - "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-unused-imports": "^3.0.0", + "@types/node": "^20.17.6", + "@typescript-eslint/eslint-plugin": "8.31.1", + "@typescript-eslint/parser": "8.31.1", + "eslint": "^9.20.1", + "eslint-plugin-prettier": "^5.4.1", + "eslint-plugin-unused-imports": "^4.1.4", "iconv-lite": "^0.6.3", "jest": "^29.4.0", "prettier": "^3.0.0", + "publint": "^0.2.12", "ts-jest": "^29.1.0", "ts-node": "^10.5.0", - "tsc-multi": "^1.1.0", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", "tsconfig-paths": "^4.0.0", - "typescript": "^4.8.2" + "tslib": "^2.8.1", + "typescript": "5.8.3", + "typescript-eslint": "8.31.1" + }, + "bin": { + "tryfinch-finch-api": "bin/cli" }, - "sideEffects": [ - "./_shims/index.js", - "./_shims/index.mjs", - "./shims/node.js", - "./shims/node.mjs", - "./shims/web.js", - "./shims/web.mjs" - ], "exports": { - "./_shims/auto/*": { - "deno": { - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*.js", - "default": "./dist/_shims/auto/*.mjs" - }, - "bun": { - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*-bun.js", - "default": "./dist/_shims/auto/*-bun.mjs" - }, - "browser": { - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*.js", - "default": "./dist/_shims/auto/*.mjs" - }, - "worker": { - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*.js", - "default": "./dist/_shims/auto/*.mjs" - }, - "workerd": { - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*.js", - "default": "./dist/_shims/auto/*.mjs" - }, - "node": { - "types": "./dist/_shims/auto/*-node.d.ts", - "require": "./dist/_shims/auto/*-node.js", - "default": "./dist/_shims/auto/*-node.mjs" - }, - "types": "./dist/_shims/auto/*.d.ts", - "require": "./dist/_shims/auto/*.js", - "default": "./dist/_shims/auto/*.mjs" - }, ".": { - "require": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" + "import": "./dist/index.mjs", + "require": "./dist/index.js" }, "./*.mjs": { - "types": "./dist/*.d.ts", "default": "./dist/*.mjs" }, "./*.js": { - "types": "./dist/*.d.ts", "default": "./dist/*.js" }, "./*": { - "types": "./dist/*.d.ts", - "require": "./dist/*.js", - "default": "./dist/*.mjs" + "import": "./dist/*.mjs", + "require": "./dist/*.js" } } } diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 647e03e8d..45b8b9cc6 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -1,23 +1,38 @@ -# Finch Node MCP Server +# Finch TypeScript MCP Server It is generated with [Stainless](https://www.stainless.com/). ## Installation -### Direct invocation +### Building -You can run the MCP Server directly via `npx`: +Because it's not published yet, clone the repo and build it: ```sh +git clone git@github.com:stainless-sdks/finch-typescript.git +cd finch-typescript +./scripts/bootstrap +./scripts/build +``` + +### Running + +```sh +# set env vars as needed export FINCH_ACCESS_TOKEN="My Access Token" export FINCH_CLIENT_ID="4ab15e51-11ad-49f4-acae-f343b7794375" export FINCH_CLIENT_SECRET="My Client Secret" export FINCH_WEBHOOK_SECRET="My Webhook Secret" -npx -y @tryfinch/finch-api-mcp@latest +node ./packages/mcp-server/dist/index.js ``` +> [!NOTE] +> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npx -y @tryfinch/finch-api-mcp` + ### Via MCP Client +[Build the project](#building) as mentioned above. + There is a partial list of existing clients at [modelcontextprotocol.io](https://modelcontextprotocol.io/clients). If you already have a client, consult their documentation to install the MCP server. @@ -27,8 +42,8 @@ For clients with a configuration JSON, it might look something like this: { "mcpServers": { "tryfinch_finch_api_api": { - "command": "npx", - "args": ["-y", "@tryfinch/finch-api-mcp", "--client=claude", "--tools=dynamic"], + "command": "node", + "args": ["/path/to/local/finch-typescript/packages/mcp-server", "--client=claude", "--tools=dynamic"], "env": { "FINCH_ACCESS_TOKEN": "My Access Token", "FINCH_CLIENT_ID": "4ab15e51-11ad-49f4-acae-f343b7794375", @@ -141,7 +156,7 @@ Authorization can be provided via the `Authorization` header using the Bearer or Additionally, authorization can be provided via the following headers: | Header | Equivalent client option | Security scheme | | ----------------------- | ------------------------ | --------------- | -| `x-finch-client-id` | `clientId` | basicAuth | +| `x-finch-client-id` | `clientID` | basicAuth | | `x-finch-client-secret` | `clientSecret` | basicAuth | A configuration JSON for this server might look like this, assuming the server is hosted at `http://localhost:3000`: diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json index 2901767d0..b86c859f5 100644 --- a/packages/mcp-server/manifest.json +++ b/packages/mcp-server/manifest.json @@ -9,9 +9,9 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/Finch-API/finch-api-node.git" + "url": "git+https://github.com/stainless-sdks/finch-typescript.git" }, - "homepage": "https://github.com/Finch-API/finch-api-node/tree/main/packages/mcp-server#readme", + "homepage": "https://github.com/stainless-sdks/finch-typescript/tree/main/packages/mcp-server#readme", "documentation": "https://developer.tryfinch.com/", "server": { "type": "node", diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 4c55b951d..e2551798e 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@tryfinch/finch-api-mcp", - "version": "6.39.0", + "version": "6.38.0", "description": "The official MCP Server for the Finch API", "author": "Finch ", "types": "dist/index.d.ts", @@ -8,10 +8,10 @@ "type": "commonjs", "repository": { "type": "git", - "url": "git+https://github.com/Finch-API/finch-api-node.git", + "url": "git+https://github.com/stainless-sdks/finch-typescript.git", "directory": "packages/mcp-server" }, - "homepage": "https://github.com/Finch-API/finch-api-node/tree/main/packages/mcp-server#readme", + "homepage": "https://github.com/stainless-sdks/finch-typescript/tree/main/packages/mcp-server#readme", "license": "Apache-2.0", "packageManager": "yarn@1.22.22", "private": false, diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index c4c9814aa..2de55444d 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -59,7 +59,7 @@ export async function codeTool(): Promise { const opts: ClientOptions = { baseURL: client.baseURL, accessToken: client.accessToken, - clientId: client.clientId, + clientID: client.clientID, clientSecret: client.clientSecret, webhookSecret: client.webhookSecret, defaultHeaders: { diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts index 3e4e9fb91..abd09c0b2 100644 --- a/packages/mcp-server/src/dynamic-tools.ts +++ b/packages/mcp-server/src/dynamic-tools.ts @@ -38,7 +38,7 @@ export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { }, tool: { name: 'list_api_endpoints', - description: 'List or search for all endpoints in the Finch Node API', + description: 'List or search for all endpoints in the Finch TypeScript API', inputSchema: zodToInputSchema(listEndpointsSchema), }, handler: async (client: Finch, args: Record | undefined): Promise => { @@ -82,7 +82,7 @@ export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { tool: { name: 'get_api_endpoint_schema', description: - 'Get the schema for an endpoint in the Finch Node API. You can use the schema returned by this tool to invoke an endpoint with the `invoke_api_endpoint` tool.', + 'Get the schema for an endpoint in the Finch TypeScript API. You can use the schema returned by this tool to invoke an endpoint with the `invoke_api_endpoint` tool.', inputSchema: zodToInputSchema(getEndpointSchema), }, handler: async (client: Finch, args: Record | undefined) => { @@ -117,7 +117,7 @@ export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { tool: { name: 'invoke_api_endpoint', description: - 'Invoke an endpoint in the Finch Node API. Note: use the `list_api_endpoints` tool to get the list of endpoints and `get_api_endpoint_schema` tool to get the schema for an endpoint.', + 'Invoke an endpoint in the Finch TypeScript API. Note: use the `list_api_endpoints` tool to get the list of endpoints and `get_api_endpoint_schema` tool to get the schema for an endpoint.', inputSchema: zodToInputSchema(invokeEndpointSchema), }, handler: async (client: Finch, args: Record | undefined): Promise => { diff --git a/packages/mcp-server/src/headers.ts b/packages/mcp-server/src/headers.ts index ad38ca5d8..c9b5810d4 100644 --- a/packages/mcp-server/src/headers.ts +++ b/packages/mcp-server/src/headers.ts @@ -13,7 +13,7 @@ export const parseAuthHeaders = (req: IncomingMessage): Partial = case 'Basic': const rawValue = Buffer.from(value, 'base64').toString(); return { - clientId: rawValue.slice(0, rawValue.search(':')), + clientID: rawValue.slice(0, rawValue.search(':')), clientSecret: rawValue.slice(rawValue.search(':') + 1), }; default: @@ -21,7 +21,7 @@ export const parseAuthHeaders = (req: IncomingMessage): Partial = } } - const clientId = + const clientID = Array.isArray(req.headers['x-finch-client-id']) ? req.headers['x-finch-client-id'][0] : req.headers['x-finch-client-id']; @@ -29,5 +29,5 @@ export const parseAuthHeaders = (req: IncomingMessage): Partial = Array.isArray(req.headers['x-finch-client-secret']) ? req.headers['x-finch-client-secret'][0] : req.headers['x-finch-client-secret']; - return { clientId, clientSecret }; + return { clientID, clientSecret }; }; diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index feff66646..5be6df535 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -6,6 +6,7 @@ import { Endpoint, endpoints, HandlerFunction, query } from './tools'; import { CallToolRequestSchema, ListToolsRequestSchema, + SetLevelRequestSchema, Implementation, Tool, } from '@modelcontextprotocol/sdk/types.js'; @@ -33,7 +34,7 @@ export const newMcpServer = () => new McpServer( { name: 'tryfinch_finch_api_api', - version: '6.39.0', + version: '6.38.0', }, { capabilities: { tools: {}, logging: {} } }, ); @@ -71,9 +72,24 @@ export function initMcpServer(params: { endpointMap ??= Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint])); }; - const client = new Finch({ - ...{ accessToken: readEnv('FINCH_ACCESS_TOKEN') }, + const logAtLevel = + (level: 'debug' | 'info' | 'warning' | 'error') => + (message: string, ...rest: unknown[]) => { + void server.sendLoggingMessage({ + level, + data: { message, rest }, + }); + }; + const logger = { + debug: logAtLevel('debug'), + info: logAtLevel('info'), + warn: logAtLevel('warning'), + error: logAtLevel('error'), + }; + let client = new Finch({ + ...{ accessToken: readEnv('FINCH_ACCESS_TOKEN') }, + logger, ...params.clientOptions, defaultHeaders: { ...params.clientOptions?.defaultHeaders, @@ -102,6 +118,29 @@ export function initMcpServer(params: { return executeHandler(endpoint.tool, endpoint.handler, client, args, mcpOptions.capabilities); }); + + server.setRequestHandler(SetLevelRequestSchema, async (request) => { + const { level } = request.params; + switch (level) { + case 'debug': + client = client.withOptions({ logLevel: 'debug' }); + break; + case 'info': + client = client.withOptions({ logLevel: 'info' }); + break; + case 'notice': + case 'warning': + client = client.withOptions({ logLevel: 'warn' }); + break; + case 'error': + client = client.withOptions({ logLevel: 'error' }); + break; + default: + client = client.withOptions({ logLevel: 'off' }); + break; + } + return {}; + }); } /** diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts index 86a7f2bb9..72ab360e8 100644 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts @@ -49,7 +49,7 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { benefit_id, jq_filter, ...body } = args as any; return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.benefits.individuals.enrolledIds(benefit_id, body)), + await maybeFilter(jq_filter, await client.hris.benefits.individuals.enrolledIDs(benefit_id, body)), ); }; diff --git a/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts b/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts index d0b01c8df..3b2b782bb 100644 --- a/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts +++ b/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts @@ -306,9 +306,7 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.sandbox.directory.create(body['body'])), - ); + return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.directory.create(body))); }; export default { metadata, tool, handler }; diff --git a/release-please-config.json b/release-please-config.json deleted file mode 100644 index 8204c04cd..000000000 --- a/release-please-config.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "packages": { - ".": {} - }, - "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", - "include-v-in-tag": true, - "include-component-in-tag": false, - "versioning": "prerelease", - "prerelease": true, - "bump-minor-pre-major": true, - "bump-patch-for-minor-pre-major": false, - "pull-request-header": "Automated Release PR", - "pull-request-title-pattern": "release: ${version}", - "changelog-sections": [ - { - "type": "feat", - "section": "Features" - }, - { - "type": "fix", - "section": "Bug Fixes" - }, - { - "type": "perf", - "section": "Performance Improvements" - }, - { - "type": "revert", - "section": "Reverts" - }, - { - "type": "chore", - "section": "Chores" - }, - { - "type": "docs", - "section": "Documentation" - }, - { - "type": "style", - "section": "Styles" - }, - { - "type": "refactor", - "section": "Refactors" - }, - { - "type": "test", - "section": "Tests", - "hidden": true - }, - { - "type": "build", - "section": "Build System" - }, - { - "type": "ci", - "section": "Continuous Integration", - "hidden": true - } - ], - "reviewers": ["jordanbrauer", "minupalaniappan"], - "release-type": "node", - "extra-files": [ - "src/version.ts", - "README.md", - "packages/mcp-server/yarn.lock", - { - "type": "json", - "path": "packages/mcp-server/package.json", - "jsonpath": "$.version" - } - ] -} diff --git a/scripts/bootstrap b/scripts/bootstrap index f68bedacb..a8b69ff31 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -23,4 +23,4 @@ echo "==> Installing Node dependencies…" PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm") -$PACKAGE_MANAGER install +$PACKAGE_MANAGER install "$@" diff --git a/scripts/build b/scripts/build index 58327f895..4a87eb898 100755 --- a/scripts/build +++ b/scripts/build @@ -15,32 +15,27 @@ rm -rf dist; mkdir dist # Copy src to dist/src and build from dist/src into dist, so that # the source map for index.js.map will refer to ./src/index.ts etc cp -rp src README.md dist -rm dist/src/_shims/*-deno.ts dist/src/_shims/auto/*-deno.ts for file in LICENSE CHANGELOG.md; do if [ -e "${file}" ]; then cp "${file}" dist; fi done if [ -e "bin/cli" ]; then - mkdir dist/bin + mkdir -p dist/bin cp -p "bin/cli" dist/bin/; fi +if [ -e "bin/migration-config.json" ]; then + mkdir -p dist/bin + cp -p "bin/migration-config.json" dist/bin/; +fi # this converts the export map paths for the dist directory # and does a few other minor things node scripts/utils/make-dist-package-json.cjs > dist/package.json # build to .js/.mjs/.d.ts files ./node_modules/.bin/tsc-multi -# copy over handwritten .js/.mjs/.d.ts files -cp src/_shims/*.{d.ts,js,mjs,md} dist/_shims -cp src/_shims/auto/*.{d.ts,js,mjs} dist/_shims/auto -# we need to add exports = module.exports = Finch to index.js; -# No way to get that from index.ts because it would cause compile errors +# we need to patch index.js so that `new module.exports()` works for cjs backwards +# compat. No way to get that from index.ts because it would cause compile errors # when building .mjs node scripts/utils/fix-index-exports.cjs -# with "moduleResolution": "nodenext", if ESM resolves to index.d.ts, -# it'll have TS errors on the default import. But if it resolves to -# index.d.mts the default import will work (even though both files have -# the same export default statement) -cp dist/index.d.ts dist/index.d.mts cp tsconfig.dist-src.json dist/src/tsconfig.json node scripts/utils/postprocess-files.cjs diff --git a/scripts/fast-format b/scripts/fast-format index 8a8e9d590..53721ac07 100755 --- a/scripts/fast-format +++ b/scripts/fast-format @@ -27,7 +27,7 @@ fi echo "==> Running eslint --fix" ESLINT_FILES="$(grep '\.ts$' "$FILE_LIST" || true)" if ! [ -z "$ESLINT_FILES" ]; then - echo "$ESLINT_FILES" | ESLINT_USE_FLAT_CONFIG="false" xargs ./node_modules/.bin/eslint --cache --fix + echo "$ESLINT_FILES" | xargs ./node_modules/.bin/eslint --cache --fix fi echo "==> Running prettier --write" diff --git a/scripts/format b/scripts/format index a6bb9d03a..7a7564019 100755 --- a/scripts/format +++ b/scripts/format @@ -5,4 +5,8 @@ set -e cd "$(dirname "$0")/.." echo "==> Running eslint --fix" -ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --fix --ext ts,js . +./node_modules/.bin/eslint --fix . + +echo "==> Running prettier --write" +# format things eslint didn't +./node_modules/.bin/prettier --write --cache --cache-strategy metadata . '!**/dist' '!**/*.ts' '!**/*.mts' '!**/*.cts' '!**/*.js' '!**/*.mjs' '!**/*.cjs' diff --git a/scripts/lint b/scripts/lint index 6ba75dfb5..3ffb78a64 100755 --- a/scripts/lint +++ b/scripts/lint @@ -5,7 +5,17 @@ set -e cd "$(dirname "$0")/.." echo "==> Running eslint" -ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --ext ts,js . +./node_modules/.bin/eslint . -echo "==> Running tsc" -./node_modules/.bin/tsc --noEmit +echo "==> Building" +./scripts/build + +echo "==> Checking types" +./node_modules/typescript/bin/tsc + +echo "==> Running Are The Types Wrong?" +./node_modules/.bin/attw --pack dist -f json >.attw.json || true +node scripts/utils/attw-report.cjs + +echo "==> Running publint" +./node_modules/.bin/publint dist diff --git a/scripts/utils/attw-report.cjs b/scripts/utils/attw-report.cjs new file mode 100644 index 000000000..b3477c0ef --- /dev/null +++ b/scripts/utils/attw-report.cjs @@ -0,0 +1,24 @@ +const fs = require('fs'); +const problems = Object.values(JSON.parse(fs.readFileSync('.attw.json', 'utf-8')).problems) + .flat() + .filter( + (problem) => + !( + // This is intentional, if the user specifies .mjs they get ESM. + ( + (problem.kind === 'CJSResolvesToESM' && problem.entrypoint.endsWith('.mjs')) || + // This is intentional for backwards compat reasons. + (problem.kind === 'MissingExportEquals' && problem.implementationFileName.endsWith('/index.js')) || + // this is intentional, we deliberately attempt to import types that may not exist from parent node_modules + // folders to better support various runtimes without triggering automatic type acquisition. + (problem.kind === 'InternalResolutionError' && problem.moduleSpecifier.includes('node_modules')) + ) + ), + ); +fs.unlinkSync('.attw.json'); +if (problems.length) { + process.stdout.write('The types are wrong!\n' + JSON.stringify(problems, null, 2) + '\n'); + process.exitCode = 1; +} else { + process.stdout.write('Types ok!\n'); +} diff --git a/scripts/utils/fix-index-exports.cjs b/scripts/utils/fix-index-exports.cjs index 72b0b8fd0..e5e10b3e7 100644 --- a/scripts/utils/fix-index-exports.cjs +++ b/scripts/utils/fix-index-exports.cjs @@ -8,7 +8,10 @@ const indexJs = let before = fs.readFileSync(indexJs, 'utf8'); let after = before.replace( - /^\s*exports\.default\s*=\s*(\w+)/m, - 'exports = module.exports = $1;\nexports.default = $1', + /^(\s*Object\.defineProperty\s*\(exports,\s*["']__esModule["'].+)$/m, + `exports = module.exports = function (...args) { + return new exports.default(...args) + } + $1`.replace(/^ /gm, ''), ); fs.writeFileSync(indexJs, after, 'utf8'); diff --git a/scripts/utils/postprocess-files.cjs b/scripts/utils/postprocess-files.cjs index 457ad07a0..deae575e3 100644 --- a/scripts/utils/postprocess-files.cjs +++ b/scripts/utils/postprocess-files.cjs @@ -1,98 +1,11 @@ +// @ts-check const fs = require('fs'); const path = require('path'); -const { parse } = require('@typescript-eslint/parser'); - -const pkgImportPath = process.env['PKG_IMPORT_PATH'] ?? '@tryfinch/finch-api/'; const distDir = process.env['DIST_PATH'] ? path.resolve(process.env['DIST_PATH']) : path.resolve(__dirname, '..', '..', 'dist'); -const distSrcDir = path.join(distDir, 'src'); - -/** - * Quick and dirty AST traversal - */ -function traverse(node, visitor) { - if (!node || typeof node.type !== 'string') return; - visitor.node?.(node); - visitor[node.type]?.(node); - for (const key in node) { - const value = node[key]; - if (Array.isArray(value)) { - for (const elem of value) traverse(elem, visitor); - } else if (value instanceof Object) { - traverse(value, visitor); - } - } -} - -/** - * Helper method for replacing arbitrary ranges of text in input code. - * - * The `replacer` is a function that will be called with a mini-api. For example: - * - * replaceRanges('foobar', ({ replace }) => replace([0, 3], 'baz')) // 'bazbar' - * - * The replaced ranges must not be overlapping. - */ -function replaceRanges(code, replacer) { - const replacements = []; - replacer({ replace: (range, replacement) => replacements.push({ range, replacement }) }); - - if (!replacements.length) return code; - replacements.sort((a, b) => a.range[0] - b.range[0]); - const overlapIndex = replacements.findIndex( - (r, index) => index > 0 && replacements[index - 1].range[1] > r.range[0], - ); - if (overlapIndex >= 0) { - throw new Error( - `replacements overlap: ${JSON.stringify(replacements[overlapIndex - 1])} and ${JSON.stringify( - replacements[overlapIndex], - )}`, - ); - } - - const parts = []; - let end = 0; - for (const { - range: [from, to], - replacement, - } of replacements) { - if (from > end) parts.push(code.substring(end, from)); - parts.push(replacement); - end = to; - } - if (end < code.length) parts.push(code.substring(end)); - return parts.join(''); -} - -/** - * Like calling .map(), where the iteratee is called on the path in every import or export from statement. - * @returns the transformed code - */ -function mapModulePaths(code, iteratee) { - const ast = parse(code, { range: true }); - return replaceRanges(code, ({ replace }) => - traverse(ast, { - node(node) { - switch (node.type) { - case 'ImportDeclaration': - case 'ExportNamedDeclaration': - case 'ExportAllDeclaration': - case 'ImportExpression': - if (node.source) { - const { range, value } = node.source; - const transformed = iteratee(value); - if (transformed !== value) { - replace(range, JSON.stringify(transformed)); - } - } - } - }, - }), - ); -} async function* walk(dir) { for await (const d of await fs.promises.opendir(dir)) { @@ -103,63 +16,79 @@ async function* walk(dir) { } async function postprocess() { - for await (const file of walk(path.resolve(__dirname, '..', '..', 'dist'))) { - if (!/\.([cm]?js|(\.d)?[cm]?ts)$/.test(file)) continue; + for await (const file of walk(distDir)) { + if (!/(\.d)?[cm]?ts$/.test(file)) continue; const code = await fs.promises.readFile(file, 'utf8'); - let transformed = mapModulePaths(code, (importPath) => { - if (file.startsWith(distSrcDir)) { - if (importPath.startsWith(pkgImportPath)) { - // convert self-references in dist/src to relative paths - let relativePath = path.relative( - path.dirname(file), - path.join(distSrcDir, importPath.substring(pkgImportPath.length)), - ); - if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`; - return relativePath; - } - return importPath; - } - if (importPath.startsWith('.')) { - // add explicit file extensions to relative imports - const { dir, name } = path.parse(importPath); - const ext = /\.mjs$/.test(file) ? '.mjs' : '.js'; - return `${dir}/${name}${ext}`; - } - return importPath; - }); - - if (file.startsWith(distSrcDir) && !file.endsWith('_shims/index.d.ts')) { - // strip out `unknown extends Foo ? never :` shim guards in dist/src - // to prevent errors from appearing in Go To Source - transformed = transformed.replace( - new RegExp('unknown extends (typeof )?\\S+ \\? \\S+ :\\s*'.replace(/\s+/, '\\s+'), 'gm'), - // replace with same number of characters to avoid breaking source maps - (match) => ' '.repeat(match.length), - ); - } - - if (file.endsWith('.d.ts')) { - // work around bad tsc behavior - // if we have `import { type Readable } from '@tryfinch/finch-api/_shims/index'`, - // tsc sometimes replaces `Readable` with `import("stream").Readable` inline - // in the output .d.ts - transformed = transformed.replace(/import\("stream"\).Readable/g, 'Readable'); - } - - // strip out lib="dom" and types="node" references; these are needed at build time, - // but would pollute the user's TS environment - transformed = transformed.replace( - /^ *\/\/\/ * ' '.repeat(match.length - 1) + '\n', ); if (transformed !== code) { - await fs.promises.writeFile(file, transformed, 'utf8'); console.error(`wrote ${path.relative(process.cwd(), file)}`); + await fs.promises.writeFile(file, transformed, 'utf8'); + } + } + + const newExports = { + '.': { + require: { + types: './index.d.ts', + default: './index.js', + }, + types: './index.d.mts', + default: './index.mjs', + }, + }; + + for (const entry of await fs.promises.readdir(distDir, { withFileTypes: true })) { + if (entry.isDirectory() && entry.name !== 'src' && entry.name !== 'internal' && entry.name !== 'bin') { + const subpath = './' + entry.name; + newExports[subpath + '/*.mjs'] = { + default: subpath + '/*.mjs', + }; + newExports[subpath + '/*.js'] = { + default: subpath + '/*.js', + }; + newExports[subpath + '/*'] = { + import: subpath + '/*.mjs', + require: subpath + '/*.js', + }; + } else if (entry.isFile() && /\.[cm]?js$/.test(entry.name)) { + const { name, ext } = path.parse(entry.name); + const subpathWithoutExt = './' + name; + const subpath = './' + entry.name; + newExports[subpathWithoutExt] ||= { import: undefined, require: undefined }; + const isModule = ext[1] === 'm'; + if (isModule) { + newExports[subpathWithoutExt].import = subpath; + } else { + newExports[subpathWithoutExt].require = subpath; + } + newExports[subpath] = { + default: subpath, + }; } } + await fs.promises.writeFile( + 'dist/package.json', + JSON.stringify( + Object.assign( + /** @type {Record} */ ( + JSON.parse(await fs.promises.readFile('dist/package.json', 'utf-8')) + ), + { + exports: newExports, + }, + ), + null, + 2, + ), + ); } postprocess(); diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 84cc42860..3e7280151 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -20,7 +20,7 @@ UPLOAD_RESPONSE=$(curl -v -X PUT \ if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/finch-node/$SHA'\033[0m" + echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/finch-typescript/$SHA'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 diff --git a/src/_shims/MultipartBody.ts b/src/_shims/MultipartBody.ts deleted file mode 100644 index af3b11188..000000000 --- a/src/_shims/MultipartBody.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export class MultipartBody { - constructor(public body: any) {} - get [Symbol.toStringTag](): string { - return 'MultipartBody'; - } -} diff --git a/src/_shims/README.md b/src/_shims/README.md deleted file mode 100644 index f28b5d818..000000000 --- a/src/_shims/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# 👋 Wondering what everything in here does? - -`@tryfinch/finch-api` supports a wide variety of runtime environments like Node.js, Deno, Bun, browsers, and various -edge runtimes, as well as both CommonJS (CJS) and EcmaScript Modules (ESM). - -To do this, `@tryfinch/finch-api` provides shims for either using `node-fetch` when in Node (because `fetch` is still experimental there) or the global `fetch` API built into the environment when not in Node. - -It uses [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) to -automatically select the correct shims for each environment. However, conditional exports are a fairly new -feature and not supported everywhere. For instance, the TypeScript `"moduleResolution": "node"` - -setting doesn't consult the `exports` map, compared to `"moduleResolution": "nodeNext"`, which does. -Unfortunately that's still the default setting, and it can result in errors like -getting the wrong raw `Response` type from `.asResponse()`, for example. - -The user can work around these issues by manually importing one of: - -- `import '@tryfinch/finch-api/shims/node'` -- `import '@tryfinch/finch-api/shims/web'` - -All of the code here in `_shims` handles selecting the automatic default shims or manual overrides. - -### How it works - Runtime - -Runtime shims get installed by calling `setShims` exported by `@tryfinch/finch-api/_shims/registry`. - -Manually importing `@tryfinch/finch-api/shims/node` or `@tryfinch/finch-api/shims/web`, calls `setShims` with the respective runtime shims. - -All client code imports shims from `@tryfinch/finch-api/_shims/index`, which: - -- checks if shims have been set manually -- if not, calls `setShims` with the shims from `@tryfinch/finch-api/_shims/auto/runtime` -- re-exports the installed shims from `@tryfinch/finch-api/_shims/registry`. - -`@tryfinch/finch-api/_shims/auto/runtime` exports web runtime shims. -If the `node` export condition is set, the export map replaces it with `@tryfinch/finch-api/_shims/auto/runtime-node`. - -### How it works - Type time - -All client code imports shim types from `@tryfinch/finch-api/_shims/index`, which selects the manual types from `@tryfinch/finch-api/_shims/manual-types` if they have been declared, otherwise it exports the auto types from `@tryfinch/finch-api/_shims/auto/types`. - -`@tryfinch/finch-api/_shims/manual-types` exports an empty namespace. -Manually importing `@tryfinch/finch-api/shims/node` or `@tryfinch/finch-api/shims/web` merges declarations into this empty namespace, so they get picked up by `@tryfinch/finch-api/_shims/index`. - -`@tryfinch/finch-api/_shims/auto/types` exports web type definitions. -If the `node` export condition is set, the export map replaces it with `@tryfinch/finch-api/_shims/auto/types-node`, though TS only picks this up if `"moduleResolution": "nodenext"` or `"moduleResolution": "bundler"`. diff --git a/src/_shims/auto/runtime-bun.ts b/src/_shims/auto/runtime-bun.ts deleted file mode 100644 index e053254b3..000000000 --- a/src/_shims/auto/runtime-bun.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../bun-runtime'; diff --git a/src/_shims/auto/runtime-deno.ts b/src/_shims/auto/runtime-deno.ts deleted file mode 100644 index 62b7a39ee..000000000 --- a/src/_shims/auto/runtime-deno.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../web-runtime'; diff --git a/src/_shims/auto/runtime-node.ts b/src/_shims/auto/runtime-node.ts deleted file mode 100644 index 0ae2216fe..000000000 --- a/src/_shims/auto/runtime-node.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../node-runtime'; diff --git a/src/_shims/auto/runtime.ts b/src/_shims/auto/runtime.ts deleted file mode 100644 index 62b7a39ee..000000000 --- a/src/_shims/auto/runtime.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../web-runtime'; diff --git a/src/_shims/auto/types-deno.ts b/src/_shims/auto/types-deno.ts deleted file mode 100644 index 226fb15a0..000000000 --- a/src/_shims/auto/types-deno.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../web-types'; diff --git a/src/_shims/auto/types-node.ts b/src/_shims/auto/types-node.ts deleted file mode 100644 index 2625a8b70..000000000 --- a/src/_shims/auto/types-node.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export * from '../node-types'; diff --git a/src/_shims/auto/types.d.ts b/src/_shims/auto/types.d.ts deleted file mode 100644 index d7755070b..000000000 --- a/src/_shims/auto/types.d.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export type Agent = any; - -// @ts-ignore -declare const _fetch: unknown extends typeof fetch ? never : typeof fetch; -export { _fetch as fetch }; - -// @ts-ignore -type _Request = unknown extends Request ? never : Request; -export { _Request as Request }; - -// @ts-ignore -type _RequestInfo = unknown extends RequestInfo ? never : RequestInfo; -export { type _RequestInfo as RequestInfo }; - -// @ts-ignore -type _RequestInit = unknown extends RequestInit ? never : RequestInit; -export { type _RequestInit as RequestInit }; - -// @ts-ignore -type _Response = unknown extends Response ? never : Response; -export { _Response as Response }; - -// @ts-ignore -type _ResponseInit = unknown extends ResponseInit ? never : ResponseInit; -export { type _ResponseInit as ResponseInit }; - -// @ts-ignore -type _ResponseType = unknown extends ResponseType ? never : ResponseType; -export { type _ResponseType as ResponseType }; - -// @ts-ignore -type _BodyInit = unknown extends BodyInit ? never : BodyInit; -export { type _BodyInit as BodyInit }; - -// @ts-ignore -type _Headers = unknown extends Headers ? never : Headers; -export { _Headers as Headers }; - -// @ts-ignore -type _HeadersInit = unknown extends HeadersInit ? never : HeadersInit; -export { type _HeadersInit as HeadersInit }; - -type EndingType = 'native' | 'transparent'; - -export interface BlobPropertyBag { - endings?: EndingType; - type?: string; -} - -export interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} - -export type FileFromPathOptions = Omit; - -// @ts-ignore -type _FormData = unknown extends FormData ? never : FormData; -// @ts-ignore -declare const _FormData: unknown extends typeof FormData ? never : typeof FormData; -export { _FormData as FormData }; - -// @ts-ignore -type _File = unknown extends File ? never : File; -// @ts-ignore -declare const _File: unknown extends typeof File ? never : typeof File; -export { _File as File }; - -// @ts-ignore -type _Blob = unknown extends Blob ? never : Blob; -// @ts-ignore -declare const _Blob: unknown extends typeof Blob ? never : typeof Blob; -export { _Blob as Blob }; - -export declare class Readable { - readable: boolean; - readonly readableEnded: boolean; - readonly readableFlowing: boolean | null; - readonly readableHighWaterMark: number; - readonly readableLength: number; - readonly readableObjectMode: boolean; - destroyed: boolean; - read(size?: number): any; - pause(): this; - resume(): this; - isPaused(): boolean; - destroy(error?: Error): this; - [Symbol.asyncIterator](): AsyncIterableIterator; -} - -export declare class FsReadStream extends Readable { - path: {}; // node type is string | Buffer -} - -// @ts-ignore -type _ReadableStream = unknown extends ReadableStream ? never : ReadableStream; -// @ts-ignore -declare const _ReadableStream: unknown extends typeof ReadableStream ? never : typeof ReadableStream; -export { _ReadableStream as ReadableStream }; diff --git a/src/_shims/auto/types.js b/src/_shims/auto/types.js deleted file mode 100644 index ddbdb799c..000000000 --- a/src/_shims/auto/types.js +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/auto/types.mjs b/src/_shims/auto/types.mjs deleted file mode 100644 index ddbdb799c..000000000 --- a/src/_shims/auto/types.mjs +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/bun-runtime.ts b/src/_shims/bun-runtime.ts deleted file mode 100644 index 8d5aaab0c..000000000 --- a/src/_shims/bun-runtime.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import { type Shims } from './registry'; -import { getRuntime as getWebRuntime } from './web-runtime'; -import { ReadStream as FsReadStream } from 'node:fs'; - -export function getRuntime(): Shims { - const runtime = getWebRuntime(); - function isFsReadStream(value: any): value is FsReadStream { - return value instanceof FsReadStream; - } - return { ...runtime, isFsReadStream }; -} diff --git a/src/_shims/index-deno.ts b/src/_shims/index-deno.ts deleted file mode 100644 index d3cdead47..000000000 --- a/src/_shims/index-deno.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { MultipartBody } from './MultipartBody'; -import { type RequestOptions } from '../core'; - -export const kind: string = 'web'; - -export type Agent = any; - -const _fetch = fetch; -type _fetch = typeof fetch; -export { _fetch as fetch }; - -const _Request = Request; -type _Request = Request; -export { _Request as Request }; - -type _RequestInfo = RequestInfo; -export { type _RequestInfo as RequestInfo }; - -type _RequestInit = RequestInit; -export { type _RequestInit as RequestInit }; - -const _Response = Response; -type _Response = Response; -export { _Response as Response }; - -type _ResponseInit = ResponseInit; -export { type _ResponseInit as ResponseInit }; - -type _ResponseType = ResponseType; -export { type _ResponseType as ResponseType }; - -type _BodyInit = BodyInit; -export { type _BodyInit as BodyInit }; - -const _Headers = Headers; -type _Headers = Headers; -export { _Headers as Headers }; - -type _HeadersInit = HeadersInit; -export { type _HeadersInit as HeadersInit }; - -type EndingType = 'native' | 'transparent'; - -export interface BlobPropertyBag { - endings?: EndingType; - type?: string; -} - -export interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} - -export type FileFromPathOptions = Omit; - -const _FormData = FormData; -type _FormData = FormData; -export { _FormData as FormData }; - -const _File = File; -type _File = File; -export { _File as File }; - -const _Blob = Blob; -type _Blob = Blob; -export { _Blob as Blob }; - -export async function getMultipartRequestOptions>( - form: FormData, - opts: RequestOptions, -): Promise> { - return { - ...opts, - body: new MultipartBody(form) as any, - }; -} - -export function getDefaultAgent(url: string) { - return undefined; -} -export function fileFromPath() { - throw new Error( - 'The `fileFromPath` function is only supported in Node. See the README for more details: https://www.github.com/Finch-API/finch-api-node#file-uploads', - ); -} - -export const isFsReadStream = (value: any) => false; - -export declare class Readable { - readable: boolean; - readonly readableEnded: boolean; - readonly readableFlowing: boolean | null; - readonly readableHighWaterMark: number; - readonly readableLength: number; - readonly readableObjectMode: boolean; - destroyed: boolean; - read(size?: number): any; - pause(): this; - resume(): this; - isPaused(): boolean; - destroy(error?: Error): this; - [Symbol.asyncIterator](): AsyncIterableIterator; -} - -export declare class FsReadStream extends Readable { - path: {}; // node type is string | Buffer -} - -const _ReadableStream = ReadableStream; -type _ReadableStream = ReadableStream; -export { _ReadableStream as ReadableStream }; - -export const init = () => {}; diff --git a/src/_shims/index.d.ts b/src/_shims/index.d.ts deleted file mode 100644 index 923dd3a6a..000000000 --- a/src/_shims/index.d.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import { manual } from './manual-types'; -import * as auto from '@tryfinch/finch-api/_shims/auto/types'; -import { type RequestOptions } from '../core'; - -type SelectType = unknown extends Manual ? Auto : Manual; - -export const kind: string; - -// @ts-ignore -export type Agent = SelectType; - -// @ts-ignore -export const fetch: SelectType; - -// @ts-ignore -export type Request = SelectType; -// @ts-ignore -export type RequestInfo = SelectType; -// @ts-ignore -export type RequestInit = SelectType; - -// @ts-ignore -export type Response = SelectType; -// @ts-ignore -export type ResponseInit = SelectType; -// @ts-ignore -export type ResponseType = SelectType; -// @ts-ignore -export type BodyInit = SelectType; -// @ts-ignore -export type Headers = SelectType; -// @ts-ignore -export const Headers: SelectType; -// @ts-ignore -export type HeadersInit = SelectType; - -// @ts-ignore -export type BlobPropertyBag = SelectType; -// @ts-ignore -export type FilePropertyBag = SelectType; -// @ts-ignore -export type FileFromPathOptions = SelectType; -// @ts-ignore -export type FormData = SelectType; -// @ts-ignore -export const FormData: SelectType; -// @ts-ignore -export type File = SelectType; -// @ts-ignore -export const File: SelectType; -// @ts-ignore -export type Blob = SelectType; -// @ts-ignore -export const Blob: SelectType; - -// @ts-ignore -export type Readable = SelectType; -// @ts-ignore -export type FsReadStream = SelectType; -// @ts-ignore -export type ReadableStream = SelectType; -// @ts-ignore -export const ReadableStream: SelectType; - -export function getMultipartRequestOptions>( - form: FormData, - opts: RequestOptions, -): Promise>; - -export function getDefaultAgent(url: string): any; - -// @ts-ignore -export type FileFromPathOptions = SelectType; - -export function fileFromPath(path: string, options?: FileFromPathOptions): Promise; -export function fileFromPath(path: string, filename?: string, options?: FileFromPathOptions): Promise; - -export function isFsReadStream(value: any): value is FsReadStream; - -export const init: () => void; diff --git a/src/_shims/index.js b/src/_shims/index.js deleted file mode 100644 index 38cdaf9c6..000000000 --- a/src/_shims/index.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -const shims = require('./registry'); -const auto = require('@tryfinch/finch-api/_shims/auto/runtime'); -exports.init = () => { - if (!shims.kind) shims.setShims(auto.getRuntime(), { auto: true }); -}; -for (const property of Object.keys(shims)) { - Object.defineProperty(exports, property, { - get() { - return shims[property]; - }, - }); -} - -exports.init(); diff --git a/src/_shims/index.mjs b/src/_shims/index.mjs deleted file mode 100644 index 67f444f7a..000000000 --- a/src/_shims/index.mjs +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import * as shims from './registry.mjs'; -import * as auto from '@tryfinch/finch-api/_shims/auto/runtime'; -export const init = () => { - if (!shims.kind) shims.setShims(auto.getRuntime(), { auto: true }); -}; -export * from './registry.mjs'; - -init(); diff --git a/src/_shims/manual-types.d.ts b/src/_shims/manual-types.d.ts deleted file mode 100644 index 693c6b9a2..000000000 --- a/src/_shims/manual-types.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -/** - * Types will get added to this namespace when you import one of the following: - * - * import '@tryfinch/finch-api/shims/node' - * import '@tryfinch/finch-api/shims/web' - * - * Importing more than one will cause type and runtime errors. - */ -export namespace manual {} diff --git a/src/_shims/manual-types.js b/src/_shims/manual-types.js deleted file mode 100644 index ddbdb799c..000000000 --- a/src/_shims/manual-types.js +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/manual-types.mjs b/src/_shims/manual-types.mjs deleted file mode 100644 index ddbdb799c..000000000 --- a/src/_shims/manual-types.mjs +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/node-runtime.ts b/src/_shims/node-runtime.ts deleted file mode 100644 index ab9f2ab5c..000000000 --- a/src/_shims/node-runtime.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import * as nf from 'node-fetch'; -import * as fd from 'formdata-node'; -import { type File, type FilePropertyBag } from 'formdata-node'; -import KeepAliveAgent from 'agentkeepalive'; -import { AbortController as AbortControllerPolyfill } from 'abort-controller'; -import { ReadStream as FsReadStream } from 'node:fs'; -import { type Agent } from 'node:http'; -import { FormDataEncoder } from 'form-data-encoder'; -import { Readable } from 'node:stream'; -import { type RequestOptions } from '../core'; -import { MultipartBody } from './MultipartBody'; -import { type Shims } from './registry'; -import { ReadableStream } from 'node:stream/web'; - -type FileFromPathOptions = Omit; - -let fileFromPathWarned = false; - -/** - * @deprecated use fs.createReadStream('./my/file.txt') instead - */ -async function fileFromPath(path: string): Promise; -async function fileFromPath(path: string, filename?: string): Promise; -async function fileFromPath(path: string, options?: FileFromPathOptions): Promise; -async function fileFromPath(path: string, filename?: string, options?: FileFromPathOptions): Promise; -async function fileFromPath(path: string, ...args: any[]): Promise { - // this import fails in environments that don't handle export maps correctly, like old versions of Jest - const { fileFromPath: _fileFromPath } = await import('formdata-node/file-from-path'); - - if (!fileFromPathWarned) { - console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path)}) instead`); - fileFromPathWarned = true; - } - // @ts-ignore - return await _fileFromPath(path, ...args); -} - -const defaultHttpAgent: Agent = new KeepAliveAgent({ keepAlive: true, timeout: 5 * 60 * 1000 }); -const defaultHttpsAgent: Agent = new KeepAliveAgent.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1000 }); - -async function getMultipartRequestOptions>( - form: fd.FormData, - opts: RequestOptions, -): Promise> { - const encoder = new FormDataEncoder(form); - const readable = Readable.from(encoder); - const body = new MultipartBody(readable); - const headers = { - ...opts.headers, - ...encoder.headers, - 'Content-Length': encoder.contentLength, - }; - - return { ...opts, body: body as any, headers }; -} - -export function getRuntime(): Shims { - // Polyfill global object if needed. - if (typeof AbortController === 'undefined') { - // @ts-expect-error (the types are subtly different, but compatible in practice) - globalThis.AbortController = AbortControllerPolyfill; - } - return { - kind: 'node', - fetch: nf.default, - Request: nf.Request, - Response: nf.Response, - Headers: nf.Headers, - FormData: fd.FormData, - Blob: fd.Blob, - File: fd.File, - ReadableStream, - getMultipartRequestOptions, - getDefaultAgent: (url: string): Agent => (url.startsWith('https') ? defaultHttpsAgent : defaultHttpAgent), - fileFromPath, - isFsReadStream: (value: any): value is FsReadStream => value instanceof FsReadStream, - }; -} diff --git a/src/_shims/node-types.d.ts b/src/_shims/node-types.d.ts deleted file mode 100644 index c159e5fa7..000000000 --- a/src/_shims/node-types.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import * as nf from 'node-fetch'; -import * as fd from 'formdata-node'; - -export { type Agent } from 'node:http'; -export { type Readable } from 'node:stream'; -export { type ReadStream as FsReadStream } from 'node:fs'; -export { ReadableStream } from 'node:stream/web'; - -export const fetch: typeof nf.default; - -export type Request = nf.Request; -export type RequestInfo = nf.RequestInfo; -export type RequestInit = nf.RequestInit; - -export type Response = nf.Response; -export type ResponseInit = nf.ResponseInit; -export type ResponseType = nf.ResponseType; -export type BodyInit = nf.BodyInit; -export type Headers = nf.Headers; -export type HeadersInit = nf.HeadersInit; - -type EndingType = 'native' | 'transparent'; -export interface BlobPropertyBag { - endings?: EndingType; - type?: string; -} - -export interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} - -export type FileFromPathOptions = Omit; - -export type FormData = fd.FormData; -export const FormData: typeof fd.FormData; -export type File = fd.File; -export const File: typeof fd.File; -export type Blob = fd.Blob; -export const Blob: typeof fd.Blob; diff --git a/src/_shims/node-types.js b/src/_shims/node-types.js deleted file mode 100644 index ddbdb799c..000000000 --- a/src/_shims/node-types.js +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/node-types.mjs b/src/_shims/node-types.mjs deleted file mode 100644 index ddbdb799c..000000000 --- a/src/_shims/node-types.mjs +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/registry.ts b/src/_shims/registry.ts deleted file mode 100644 index dc06ba117..000000000 --- a/src/_shims/registry.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import { type RequestOptions } from '../core'; - -export interface Shims { - kind: string; - fetch: any; - Request: any; - Response: any; - Headers: any; - FormData: any; - Blob: any; - File: any; - ReadableStream: any; - getMultipartRequestOptions: >( - form: Shims['FormData'], - opts: RequestOptions, - ) => Promise>; - getDefaultAgent: (url: string) => any; - fileFromPath: - | ((path: string, filename?: string, options?: {}) => Promise) - | ((path: string, options?: {}) => Promise); - isFsReadStream: (value: any) => boolean; -} - -export let auto = false; -export let kind: Shims['kind'] | undefined = undefined; -export let fetch: Shims['fetch'] | undefined = undefined; -export let Request: Shims['Request'] | undefined = undefined; -export let Response: Shims['Response'] | undefined = undefined; -export let Headers: Shims['Headers'] | undefined = undefined; -export let FormData: Shims['FormData'] | undefined = undefined; -export let Blob: Shims['Blob'] | undefined = undefined; -export let File: Shims['File'] | undefined = undefined; -export let ReadableStream: Shims['ReadableStream'] | undefined = undefined; -export let getMultipartRequestOptions: Shims['getMultipartRequestOptions'] | undefined = undefined; -export let getDefaultAgent: Shims['getDefaultAgent'] | undefined = undefined; -export let fileFromPath: Shims['fileFromPath'] | undefined = undefined; -export let isFsReadStream: Shims['isFsReadStream'] | undefined = undefined; - -export function setShims(shims: Shims, options: { auto: boolean } = { auto: false }) { - if (auto) { - throw new Error( - `you must \`import '@tryfinch/finch-api/shims/${shims.kind}'\` before importing anything else from @tryfinch/finch-api`, - ); - } - if (kind) { - throw new Error( - `can't \`import '@tryfinch/finch-api/shims/${shims.kind}'\` after \`import '@tryfinch/finch-api/shims/${kind}'\``, - ); - } - auto = options.auto; - kind = shims.kind; - fetch = shims.fetch; - Request = shims.Request; - Response = shims.Response; - Headers = shims.Headers; - FormData = shims.FormData; - Blob = shims.Blob; - File = shims.File; - ReadableStream = shims.ReadableStream; - getMultipartRequestOptions = shims.getMultipartRequestOptions; - getDefaultAgent = shims.getDefaultAgent; - fileFromPath = shims.fileFromPath; - isFsReadStream = shims.isFsReadStream; -} diff --git a/src/_shims/web-runtime.ts b/src/_shims/web-runtime.ts deleted file mode 100644 index 8261f7f0d..000000000 --- a/src/_shims/web-runtime.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -import { MultipartBody } from './MultipartBody'; -import { type RequestOptions } from '../core'; -import { type Shims } from './registry'; - -export function getRuntime({ manuallyImported }: { manuallyImported?: boolean } = {}): Shims { - const recommendation = - manuallyImported ? - `You may need to use polyfills` - : `Add one of these imports before your first \`import … from '@tryfinch/finch-api'\`: -- \`import '@tryfinch/finch-api/shims/node'\` (if you're running on Node) -- \`import '@tryfinch/finch-api/shims/web'\` (otherwise) -`; - - let _fetch, _Request, _Response, _Headers; - try { - // @ts-ignore - _fetch = fetch; - // @ts-ignore - _Request = Request; - // @ts-ignore - _Response = Response; - // @ts-ignore - _Headers = Headers; - } catch (error) { - throw new Error( - `this environment is missing the following Web Fetch API type: ${ - (error as any).message - }. ${recommendation}`, - ); - } - - return { - kind: 'web', - fetch: _fetch, - Request: _Request, - Response: _Response, - Headers: _Headers, - FormData: - // @ts-ignore - typeof FormData !== 'undefined' ? FormData : ( - class FormData { - // @ts-ignore - constructor() { - throw new Error( - `file uploads aren't supported in this environment yet as 'FormData' is undefined. ${recommendation}`, - ); - } - } - ), - Blob: - typeof Blob !== 'undefined' ? Blob : ( - class Blob { - constructor() { - throw new Error( - `file uploads aren't supported in this environment yet as 'Blob' is undefined. ${recommendation}`, - ); - } - } - ), - File: - // @ts-ignore - typeof File !== 'undefined' ? File : ( - class File { - // @ts-ignore - constructor() { - throw new Error( - `file uploads aren't supported in this environment yet as 'File' is undefined. ${recommendation}`, - ); - } - } - ), - ReadableStream: - // @ts-ignore - typeof ReadableStream !== 'undefined' ? ReadableStream : ( - class ReadableStream { - // @ts-ignore - constructor() { - throw new Error( - `streaming isn't supported in this environment yet as 'ReadableStream' is undefined. ${recommendation}`, - ); - } - } - ), - getMultipartRequestOptions: async >( - // @ts-ignore - form: FormData, - opts: RequestOptions, - ): Promise> => ({ - ...opts, - body: new MultipartBody(form) as any, - }), - getDefaultAgent: (url: string) => undefined, - fileFromPath: () => { - throw new Error( - 'The `fileFromPath` function is only supported in Node. See the README for more details: https://www.github.com/Finch-API/finch-api-node#file-uploads', - ); - }, - isFsReadStream: (value: any) => false, - }; -} diff --git a/src/_shims/web-types.d.ts b/src/_shims/web-types.d.ts deleted file mode 100644 index 4ff351383..000000000 --- a/src/_shims/web-types.d.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ -export type Agent = any; - -declare const _fetch: typeof fetch; -export { _fetch as fetch }; - -type _Request = Request; -export { _Request as Request }; - -type _RequestInfo = RequestInfo; -export { type _RequestInfo as RequestInfo }; - -type _RequestInit = RequestInit; -export { type _RequestInit as RequestInit }; - -type _Response = Response; -export { _Response as Response }; - -type _ResponseInit = ResponseInit; -export { type _ResponseInit as ResponseInit }; - -type _ResponseType = ResponseType; -export { type _ResponseType as ResponseType }; - -type _BodyInit = BodyInit; -export { type _BodyInit as BodyInit }; - -type _Headers = Headers; -export { _Headers as Headers }; - -type _HeadersInit = HeadersInit; -export { type _HeadersInit as HeadersInit }; - -type EndingType = 'native' | 'transparent'; - -export interface BlobPropertyBag { - endings?: EndingType; - type?: string; -} - -export interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} - -export type FileFromPathOptions = Omit; - -type _FormData = FormData; -declare const _FormData: typeof FormData; -export { _FormData as FormData }; - -type _File = File; -declare const _File: typeof File; -export { _File as File }; - -type _Blob = Blob; -declare const _Blob: typeof Blob; -export { _Blob as Blob }; - -export declare class Readable { - readable: boolean; - readonly readableEnded: boolean; - readonly readableFlowing: boolean | null; - readonly readableHighWaterMark: number; - readonly readableLength: number; - readonly readableObjectMode: boolean; - destroyed: boolean; - read(size?: number): any; - pause(): this; - resume(): this; - isPaused(): boolean; - destroy(error?: Error): this; - [Symbol.asyncIterator](): AsyncIterableIterator; -} - -export declare class FsReadStream extends Readable { - path: {}; // node type is string | Buffer -} - -type _ReadableStream = ReadableStream; -declare const _ReadableStream: typeof ReadableStream; -export { _ReadableStream as ReadableStream }; diff --git a/src/_shims/web-types.js b/src/_shims/web-types.js deleted file mode 100644 index ddbdb799c..000000000 --- a/src/_shims/web-types.js +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/_shims/web-types.mjs b/src/_shims/web-types.mjs deleted file mode 100644 index ddbdb799c..000000000 --- a/src/_shims/web-types.mjs +++ /dev/null @@ -1,3 +0,0 @@ -/** - * Disclaimer: modules in _shims aren't intended to be imported by SDK users. - */ diff --git a/src/api-promise.ts b/src/api-promise.ts new file mode 100644 index 000000000..8c775ee69 --- /dev/null +++ b/src/api-promise.ts @@ -0,0 +1,2 @@ +/** @deprecated Import from ./core/api-promise instead */ +export * from './core/api-promise'; diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 000000000..4ea39f253 --- /dev/null +++ b/src/client.ts @@ -0,0 +1,932 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import type { RequestInit, RequestInfo, BodyInit } from './internal/builtin-types'; +import type { HTTPMethod, PromiseOrValue, MergedRequestInit, FinalizedRequestInit } from './internal/types'; +import { uuid4 } from './internal/utils/uuid'; +import { validatePositiveInteger, isAbsoluteURL, safeJSON } from './internal/utils/values'; +import { sleep } from './internal/utils/sleep'; +export type { Logger, LogLevel } from './internal/utils/log'; +import { castToError, isAbortError } from './internal/errors'; +import type { APIResponseProps } from './internal/parse'; +import { getPlatformHeaders } from './internal/detect-platform'; +import * as Shims from './internal/shims'; +import * as Opts from './internal/request-options'; +import * as qs from './internal/qs'; +import { VERSION } from './version'; +import * as Errors from './core/error'; +import * as Pagination from './core/pagination'; +import { + AbstractPage, + type IndividualsPageParams, + IndividualsPageResponse, + type PageParams, + PageResponse, + ResponsesPageResponse, + SinglePageResponse, +} from './core/pagination'; +import * as Uploads from './core/uploads'; +import * as API from './resources/index'; +import { APIPromise } from './core/api-promise'; +import { AccessTokenCreateParams, AccessTokens, CreateAccessTokenResponse } from './resources/access-tokens'; +import { Account, DisconnectResponse, Introspection } from './resources/account'; +import { + Provider, + ProviderListResponse, + ProviderListResponsesSinglePage, + Providers, +} from './resources/providers'; +import { + RequestForwarding, + RequestForwardingForwardParams, + RequestForwardingForwardResponse, +} from './resources/request-forwarding'; +import { + AccountUpdateEvent, + BaseWebhookEvent, + CompanyEvent, + DirectoryEvent, + EmploymentEvent, + IndividualEvent, + JobCompletionEvent, + PayStatementEvent, + PaymentEvent, + WebhookEvent, + Webhooks, +} from './resources/webhooks'; +import { Connect } from './resources/connect/connect'; +import { HRIS, Income, Location, Money } from './resources/hris/hris'; +import { Jobs } from './resources/jobs/jobs'; +import { Payroll } from './resources/payroll/payroll'; +import { Sandbox } from './resources/sandbox/sandbox'; +import { type Fetch } from './internal/builtin-types'; +import { isRunningInBrowser } from './internal/detect-platform'; +import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; +import { FinalRequestOptions, RequestOptions } from './internal/request-options'; +import { toBase64 } from './internal/utils/base64'; +import { readEnv } from './internal/utils/env'; +import { + type LogLevel, + type Logger, + formatRequestDetails, + loggerFor, + parseLogLevel, +} from './internal/utils/log'; +import { isEmptyObj } from './internal/utils/values'; + +export interface ClientOptions { + accessToken?: string | null | undefined; + + /** + * Defaults to process.env['FINCH_CLIENT_ID']. + */ + clientID?: string | null | undefined; + + /** + * Defaults to process.env['FINCH_CLIENT_SECRET']. + */ + clientSecret?: string | null | undefined; + + /** + * Defaults to process.env['FINCH_WEBHOOK_SECRET']. + */ + webhookSecret?: string | null | undefined; + + /** + * Override the default base URL for the API, e.g., "https://api.example.com/v2/" + * + * Defaults to process.env['FINCH_BASE_URL']. + */ + baseURL?: string | null | undefined; + + /** + * The maximum amount of time (in milliseconds) that the client should wait for a response + * from the server before timing out a single request. + * + * Note that request timeouts are retried by default, so in a worst-case scenario you may wait + * much longer than this timeout before the promise succeeds or fails. + * + * @unit milliseconds + */ + timeout?: number | undefined; + /** + * Additional `RequestInit` options to be passed to `fetch` calls. + * Properties will be overridden by per-request `fetchOptions`. + */ + fetchOptions?: MergedRequestInit | undefined; + + /** + * Specify a custom `fetch` function implementation. + * + * If not provided, we expect that `fetch` is defined globally. + */ + fetch?: Fetch | undefined; + + /** + * The maximum number of times that the client will retry a request in case of a + * temporary failure, like a network error or a 5XX error from the server. + * + * @default 2 + */ + maxRetries?: number | undefined; + + /** + * Default headers to include with every request to the API. + * + * These can be removed in individual requests by explicitly setting the + * header to `null` in request options. + */ + defaultHeaders?: HeadersLike | undefined; + + /** + * Default query parameters to include with every request to the API. + * + * These can be removed in individual requests by explicitly setting the + * param to `undefined` in request options. + */ + defaultQuery?: Record | undefined; + + /** + * By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers. + * Only set this option to `true` if you understand the risks and have appropriate mitigations in place. + */ + dangerouslyAllowBrowser?: boolean | undefined; + + /** + * Set the log level. + * + * Defaults to process.env['FINCH_LOG'] or 'warn' if it isn't set. + */ + logLevel?: LogLevel | undefined; + + /** + * Set the logger. + * + * Defaults to globalThis.console. + */ + logger?: Logger | undefined; +} + +/** + * API Client for interfacing with the Finch API. + */ +export class Finch { + accessToken: string | null; + clientID: string | null; + clientSecret: string | null; + webhookSecret: string | null; + + baseURL: string; + maxRetries: number; + timeout: number; + logger: Logger | undefined; + logLevel: LogLevel | undefined; + fetchOptions: MergedRequestInit | undefined; + + private fetch: Fetch; + #encoder: Opts.RequestEncoder; + protected idempotencyHeader?: string; + private _options: ClientOptions; + + /** + * API Client for interfacing with the Finch API. + * + * @param {string | null | undefined} [opts.accessToken] + * @param {string | null | undefined} [opts.clientID=process.env['FINCH_CLIENT_ID'] ?? null] + * @param {string | null | undefined} [opts.clientSecret=process.env['FINCH_CLIENT_SECRET'] ?? null] + * @param {string | null | undefined} [opts.webhookSecret=process.env['FINCH_WEBHOOK_SECRET'] ?? null] + * @param {string} [opts.baseURL=process.env['FINCH_BASE_URL'] ?? https://api.tryfinch.com] - Override the default base URL for the API. + * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. + * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. + * @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. + * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. + * @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API. + * @param {Record} opts.defaultQuery - Default query parameters to include with every request to the API. + * @param {boolean} [opts.dangerouslyAllowBrowser=false] - By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers. + */ + constructor({ + baseURL = readEnv('FINCH_BASE_URL'), + accessToken = null, + clientID = readEnv('FINCH_CLIENT_ID') ?? null, + clientSecret = readEnv('FINCH_CLIENT_SECRET') ?? null, + webhookSecret = readEnv('FINCH_WEBHOOK_SECRET') ?? null, + ...opts + }: ClientOptions = {}) { + const options: ClientOptions = { + accessToken, + clientID, + clientSecret, + webhookSecret, + ...opts, + baseURL: baseURL || `https://api.tryfinch.com`, + }; + + if (!options.dangerouslyAllowBrowser && isRunningInBrowser()) { + throw new Errors.FinchError( + 'This is disabled by default, as it risks exposing your secret API credentials to attackers.\nIf you understand the risks and have appropriate mitigations in place,\nyou can set the `dangerouslyAllowBrowser` option to `true`, e.g.,\n\nnew Finch({ dangerouslyAllowBrowser: true })', + ); + } + + this.baseURL = options.baseURL!; + this.timeout = options.timeout ?? Finch.DEFAULT_TIMEOUT /* 1 minute */; + this.logger = options.logger ?? console; + const defaultLogLevel = 'warn'; + // Set default logLevel early so that we can log a warning in parseLogLevel. + this.logLevel = defaultLogLevel; + this.logLevel = + parseLogLevel(options.logLevel, 'ClientOptions.logLevel', this) ?? + parseLogLevel(readEnv('FINCH_LOG'), "process.env['FINCH_LOG']", this) ?? + defaultLogLevel; + this.fetchOptions = options.fetchOptions; + this.maxRetries = options.maxRetries ?? 2; + this.fetch = options.fetch ?? Shims.getDefaultFetch(); + this.#encoder = Opts.FallbackEncoder; + + this._options = options; + + this.accessToken = accessToken; + this.clientID = clientID; + this.clientSecret = clientSecret; + this.webhookSecret = webhookSecret; + } + + /** + * Create a new client instance re-using the same options given to the current client with optional overriding. + */ + withOptions(options: Partial): this { + const client = new (this.constructor as any as new (props: ClientOptions) => typeof this)({ + ...this._options, + baseURL: this.baseURL, + maxRetries: this.maxRetries, + timeout: this.timeout, + logger: this.logger, + logLevel: this.logLevel, + fetch: this.fetch, + fetchOptions: this.fetchOptions, + accessToken: this.accessToken, + clientID: this.clientID, + clientSecret: this.clientSecret, + webhookSecret: this.webhookSecret, + ...options, + }); + return client; + } + + /** + * Check whether the base URL is set to its default. + */ + #baseURLOverridden(): boolean { + return this.baseURL !== 'https://api.tryfinch.com'; + } + + protected defaultQuery(): Record | undefined { + return this._options.defaultQuery; + } + + protected validateHeaders({ values, nulls }: NullableHeaders) { + if (this.accessToken && values.get('authorization')) { + return; + } + if (nulls.has('authorization')) { + return; + } + + if (this.clientID && this.clientSecret && values.get('authorization')) { + return; + } + if (nulls.has('authorization')) { + return; + } + + throw new Error( + 'Could not resolve authentication method. Expected either accessToken, clientID or clientSecret to be set. Or for one of the "Authorization" or "Authorization" headers to be explicitly omitted', + ); + } + + protected async authHeaders(opts: FinalRequestOptions): Promise { + return buildHeaders([await this.bearerAuth(opts), await this.basicAuth(opts)]); + } + + protected async bearerAuth(opts: FinalRequestOptions): Promise { + if (this.accessToken == null) { + return undefined; + } + return buildHeaders([{ Authorization: `Bearer ${this.accessToken}` }]); + } + + protected async basicAuth(opts: FinalRequestOptions): Promise { + if (!this.clientID) { + return undefined; + } + + if (!this.clientSecret) { + return undefined; + } + + const credentials = `${this.clientID}:${this.clientSecret}`; + const Authorization = `Basic ${toBase64(credentials)}`; + return buildHeaders([{ Authorization }]); + } + + protected stringifyQuery(query: Record): string { + return qs.stringify(query, { arrayFormat: 'brackets' }); + } + + private getUserAgent(): string { + return `${this.constructor.name}/JS ${VERSION}`; + } + + protected defaultIdempotencyKey(): string { + return `stainless-node-retry-${uuid4()}`; + } + + protected makeStatusError( + status: number, + error: Object, + message: string | undefined, + headers: Headers, + ): Errors.APIError { + return Errors.APIError.generate(status, error, message, headers); + } + + buildURL( + path: string, + query: Record | null | undefined, + defaultBaseURL?: string | undefined, + ): string { + const baseURL = (!this.#baseURLOverridden() && defaultBaseURL) || this.baseURL; + const url = + isAbsoluteURL(path) ? + new URL(path) + : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); + + const defaultQuery = this.defaultQuery(); + if (!isEmptyObj(defaultQuery)) { + query = { ...defaultQuery, ...query }; + } + + if (typeof query === 'object' && query && !Array.isArray(query)) { + url.search = this.stringifyQuery(query as Record); + } + + return url.toString(); + } + + /** + * Used as a callback for mutating the given `FinalRequestOptions` object. + */ + protected async prepareOptions(options: FinalRequestOptions): Promise {} + + /** + * Used as a callback for mutating the given `RequestInit` object. + * + * This is useful for cases where you want to add certain headers based off of + * the request properties, e.g. `method` or `url`. + */ + protected async prepareRequest( + request: RequestInit, + { url, options }: { url: string; options: FinalRequestOptions }, + ): Promise {} + + get(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('get', path, opts); + } + + post(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('post', path, opts); + } + + patch(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('patch', path, opts); + } + + put(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('put', path, opts); + } + + delete(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('delete', path, opts); + } + + private methodRequest( + method: HTTPMethod, + path: string, + opts?: PromiseOrValue, + ): APIPromise { + return this.request( + Promise.resolve(opts).then((opts) => { + return { method, path, ...opts }; + }), + ); + } + + request( + options: PromiseOrValue, + remainingRetries: number | null = null, + ): APIPromise { + return new APIPromise(this, this.makeRequest(options, remainingRetries, undefined)); + } + + private async makeRequest( + optionsInput: PromiseOrValue, + retriesRemaining: number | null, + retryOfRequestLogID: string | undefined, + ): Promise { + const options = await optionsInput; + const maxRetries = options.maxRetries ?? this.maxRetries; + if (retriesRemaining == null) { + retriesRemaining = maxRetries; + } + + await this.prepareOptions(options); + + const { req, url, timeout } = await this.buildRequest(options, { + retryCount: maxRetries - retriesRemaining, + }); + + await this.prepareRequest(req, { url, options }); + + /** Not an API request ID, just for correlating local log entries. */ + const requestLogID = 'log_' + ((Math.random() * (1 << 24)) | 0).toString(16).padStart(6, '0'); + const retryLogStr = retryOfRequestLogID === undefined ? '' : `, retryOf: ${retryOfRequestLogID}`; + const startTime = Date.now(); + + loggerFor(this).debug( + `[${requestLogID}] sending request`, + formatRequestDetails({ + retryOfRequestLogID, + method: options.method, + url, + options, + headers: req.headers, + }), + ); + + if (options.signal?.aborted) { + throw new Errors.APIUserAbortError(); + } + + const controller = new AbortController(); + const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError); + const headersTime = Date.now(); + + if (response instanceof globalThis.Error) { + const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; + if (options.signal?.aborted) { + throw new Errors.APIUserAbortError(); + } + // detect native connection timeout errors + // deno throws "TypeError: error sending request for url (https://example/): client error (Connect): tcp connect error: Operation timed out (os error 60): Operation timed out (os error 60)" + // undici throws "TypeError: fetch failed" with cause "ConnectTimeoutError: Connect Timeout Error (attempted address: example:443, timeout: 1ms)" + // others do not provide enough information to distinguish timeouts from other connection errors + const isTimeout = + isAbortError(response) || + /timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : '')); + if (retriesRemaining) { + loggerFor(this).info( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`, + ); + loggerFor(this).debug( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url, + durationMs: headersTime - startTime, + message: response.message, + }), + ); + return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID); + } + loggerFor(this).info( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - error; no more retries left`, + ); + loggerFor(this).debug( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (error; no more retries left)`, + formatRequestDetails({ + retryOfRequestLogID, + url, + durationMs: headersTime - startTime, + message: response.message, + }), + ); + if (isTimeout) { + throw new Errors.APIConnectionTimeoutError(); + } + throw new Errors.APIConnectionError({ cause: response }); + } + + const responseInfo = `[${requestLogID}${retryLogStr}] ${req.method} ${url} ${ + response.ok ? 'succeeded' : 'failed' + } with status ${response.status} in ${headersTime - startTime}ms`; + + if (!response.ok) { + const shouldRetry = await this.shouldRetry(response); + if (retriesRemaining && shouldRetry) { + const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; + + // We don't need the body of this response. + await Shims.CancelReadableStream(response.body); + loggerFor(this).info(`${responseInfo} - ${retryMessage}`); + loggerFor(this).debug( + `[${requestLogID}] response error (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + durationMs: headersTime - startTime, + }), + ); + return this.retryRequest( + options, + retriesRemaining, + retryOfRequestLogID ?? requestLogID, + response.headers, + ); + } + + const retryMessage = shouldRetry ? `error; no more retries left` : `error; not retryable`; + + loggerFor(this).info(`${responseInfo} - ${retryMessage}`); + + const errText = await response.text().catch((err: any) => castToError(err).message); + const errJSON = safeJSON(errText); + const errMessage = errJSON ? undefined : errText; + + loggerFor(this).debug( + `[${requestLogID}] response error (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + message: errMessage, + durationMs: Date.now() - startTime, + }), + ); + + const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers); + throw err; + } + + loggerFor(this).info(responseInfo); + loggerFor(this).debug( + `[${requestLogID}] response start`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + durationMs: headersTime - startTime, + }), + ); + + return { response, options, controller, requestLogID, retryOfRequestLogID, startTime }; + } + + getAPIList = Pagination.AbstractPage>( + path: string, + Page: new (...args: any[]) => PageClass, + opts?: RequestOptions, + ): Pagination.PagePromise { + return this.requestAPIList(Page, { method: 'get', path, ...opts }); + } + + requestAPIList< + Item = unknown, + PageClass extends Pagination.AbstractPage = Pagination.AbstractPage, + >( + Page: new (...args: ConstructorParameters) => PageClass, + options: FinalRequestOptions, + ): Pagination.PagePromise { + const request = this.makeRequest(options, null, undefined); + return new Pagination.PagePromise(this as any as Finch, request, Page); + } + + async fetchWithTimeout( + url: RequestInfo, + init: RequestInit | undefined, + ms: number, + controller: AbortController, + ): Promise { + const { signal, method, ...options } = init || {}; + if (signal) signal.addEventListener('abort', () => controller.abort()); + + const timeout = setTimeout(() => controller.abort(), ms); + + const isReadableBody = + ((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) || + (typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body); + + const fetchOptions: RequestInit = { + signal: controller.signal as any, + ...(isReadableBody ? { duplex: 'half' } : {}), + method: 'GET', + ...options, + }; + if (method) { + // Custom methods like 'patch' need to be uppercased + // See https://github.com/nodejs/undici/issues/2294 + fetchOptions.method = method.toUpperCase(); + } + + try { + // use undefined this binding; fetch errors if bound to something else in browser/cloudflare + return await this.fetch.call(undefined, url, fetchOptions); + } finally { + clearTimeout(timeout); + } + } + + private async shouldRetry(response: Response): Promise { + // Note this is not a standard header. + const shouldRetryHeader = response.headers.get('x-should-retry'); + + // If the server explicitly says whether or not to retry, obey. + if (shouldRetryHeader === 'true') return true; + if (shouldRetryHeader === 'false') return false; + + // Retry on request timeouts. + if (response.status === 408) return true; + + // Retry on lock timeouts. + if (response.status === 409) return true; + + // Retry on rate limits. + if (response.status === 429) return true; + + // Retry internal errors. + if (response.status >= 500) return true; + + return false; + } + + private async retryRequest( + options: FinalRequestOptions, + retriesRemaining: number, + requestLogID: string, + responseHeaders?: Headers | undefined, + ): Promise { + let timeoutMillis: number | undefined; + + // Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it. + const retryAfterMillisHeader = responseHeaders?.get('retry-after-ms'); + if (retryAfterMillisHeader) { + const timeoutMs = parseFloat(retryAfterMillisHeader); + if (!Number.isNaN(timeoutMs)) { + timeoutMillis = timeoutMs; + } + } + + // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + const retryAfterHeader = responseHeaders?.get('retry-after'); + if (retryAfterHeader && !timeoutMillis) { + const timeoutSeconds = parseFloat(retryAfterHeader); + if (!Number.isNaN(timeoutSeconds)) { + timeoutMillis = timeoutSeconds * 1000; + } else { + timeoutMillis = Date.parse(retryAfterHeader) - Date.now(); + } + } + + // If the API asks us to wait a certain amount of time (and it's a reasonable amount), + // just do what it says, but otherwise calculate a default + if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) { + const maxRetries = options.maxRetries ?? this.maxRetries; + timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); + } + await sleep(timeoutMillis); + + return this.makeRequest(options, retriesRemaining - 1, requestLogID); + } + + private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { + const initialRetryDelay = 0.5; + const maxRetryDelay = 8.0; + + const numRetries = maxRetries - retriesRemaining; + + // Apply exponential backoff, but not more than the max. + const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay); + + // Apply some jitter, take up to at most 25 percent of the retry time. + const jitter = 1 - Math.random() * 0.25; + + return sleepSeconds * jitter * 1000; + } + + async buildRequest( + inputOptions: FinalRequestOptions, + { retryCount = 0 }: { retryCount?: number } = {}, + ): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> { + const options = { ...inputOptions }; + const { method, path, query, defaultBaseURL } = options; + + const url = this.buildURL(path!, query as Record, defaultBaseURL); + if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); + options.timeout = options.timeout ?? this.timeout; + const { bodyHeaders, body } = this.buildBody({ options }); + const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount }); + + const req: FinalizedRequestInit = { + method, + headers: reqHeaders, + ...(options.signal && { signal: options.signal }), + ...((globalThis as any).ReadableStream && + body instanceof (globalThis as any).ReadableStream && { duplex: 'half' }), + ...(body && { body }), + ...((this.fetchOptions as any) ?? {}), + ...((options.fetchOptions as any) ?? {}), + }; + + return { req, url, timeout: options.timeout }; + } + + private async buildHeaders({ + options, + method, + bodyHeaders, + retryCount, + }: { + options: FinalRequestOptions; + method: HTTPMethod; + bodyHeaders: HeadersLike; + retryCount: number; + }): Promise { + let idempotencyHeaders: HeadersLike = {}; + if (this.idempotencyHeader && method !== 'get') { + if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); + idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey; + } + + const headers = buildHeaders([ + idempotencyHeaders, + { + Accept: 'application/json', + 'User-Agent': this.getUserAgent(), + 'X-Stainless-Retry-Count': String(retryCount), + ...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}), + ...getPlatformHeaders(), + 'Finch-API-Version': '2020-09-17', + }, + await this.authHeaders(options), + this._options.defaultHeaders, + bodyHeaders, + options.headers, + ]); + + this.validateHeaders(headers); + + return headers.values; + } + + private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): { + bodyHeaders: HeadersLike; + body: BodyInit | undefined; + } { + if (!body) { + return { bodyHeaders: undefined, body: undefined }; + } + const headers = buildHeaders([rawHeaders]); + if ( + // Pass raw type verbatim + ArrayBuffer.isView(body) || + body instanceof ArrayBuffer || + body instanceof DataView || + (typeof body === 'string' && + // Preserve legacy string encoding behavior for now + headers.values.has('content-type')) || + // `Blob` is superset of `File` + ((globalThis as any).Blob && body instanceof (globalThis as any).Blob) || + // `FormData` -> `multipart/form-data` + body instanceof FormData || + // `URLSearchParams` -> `application/x-www-form-urlencoded` + body instanceof URLSearchParams || + // Send chunked stream (each chunk has own `length`) + ((globalThis as any).ReadableStream && body instanceof (globalThis as any).ReadableStream) + ) { + return { bodyHeaders: undefined, body: body as BodyInit }; + } else if ( + typeof body === 'object' && + (Symbol.asyncIterator in body || + (Symbol.iterator in body && 'next' in body && typeof body.next === 'function')) + ) { + return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body as AsyncIterable) }; + } else { + return this.#encoder({ body, headers }); + } + } + + static Finch = this; + static DEFAULT_TIMEOUT = 60000; // 1 minute + + static FinchError = Errors.FinchError; + static APIError = Errors.APIError; + static APIConnectionError = Errors.APIConnectionError; + static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; + static APIUserAbortError = Errors.APIUserAbortError; + static NotFoundError = Errors.NotFoundError; + static ConflictError = Errors.ConflictError; + static RateLimitError = Errors.RateLimitError; + static BadRequestError = Errors.BadRequestError; + static AuthenticationError = Errors.AuthenticationError; + static InternalServerError = Errors.InternalServerError; + static PermissionDeniedError = Errors.PermissionDeniedError; + static UnprocessableEntityError = Errors.UnprocessableEntityError; + + static toFile = Uploads.toFile; + + accessTokens: API.AccessTokens = new API.AccessTokens(this); + hris: API.HRIS = new API.HRIS(this); + providers: API.Providers = new API.Providers(this); + account: API.Account = new API.Account(this); + webhooks: API.Webhooks = new API.Webhooks(this); + requestForwarding: API.RequestForwarding = new API.RequestForwarding(this); + jobs: API.Jobs = new API.Jobs(this); + sandbox: API.Sandbox = new API.Sandbox(this); + payroll: API.Payroll = new API.Payroll(this); + connect: API.Connect = new API.Connect(this); +} + +Finch.AccessTokens = AccessTokens; +Finch.HRIS = HRIS; +Finch.Providers = Providers; +Finch.Account = Account; +Finch.Webhooks = Webhooks; +Finch.RequestForwarding = RequestForwarding; +Finch.Jobs = Jobs; +Finch.Sandbox = Sandbox; +Finch.Payroll = Payroll; +Finch.Connect = Connect; + +export declare namespace Finch { + export type RequestOptions = Opts.RequestOptions; + + export import SinglePage = Pagination.SinglePage; + export { type SinglePageResponse as SinglePageResponse }; + + export import ResponsesPage = Pagination.ResponsesPage; + export { type ResponsesPageResponse as ResponsesPageResponse }; + + export import IndividualsPage = Pagination.IndividualsPage; + export { + type IndividualsPageParams as IndividualsPageParams, + type IndividualsPageResponse as IndividualsPageResponse, + }; + + export import Page = Pagination.Page; + export { type PageParams as PageParams, type PageResponse as PageResponse }; + + export { + AccessTokens as AccessTokens, + type CreateAccessTokenResponse as CreateAccessTokenResponse, + type AccessTokenCreateParams as AccessTokenCreateParams, + }; + + export { HRIS as HRIS, type Income as Income, type Location as Location, type Money as Money }; + + export { + Providers as Providers, + type Provider as Provider, + type ProviderListResponse as ProviderListResponse, + type ProviderListResponsesSinglePage as ProviderListResponsesSinglePage, + }; + + export { + Account as Account, + type DisconnectResponse as DisconnectResponse, + type Introspection as Introspection, + }; + + export { + Webhooks as Webhooks, + type AccountUpdateEvent as AccountUpdateEvent, + type BaseWebhookEvent as BaseWebhookEvent, + type CompanyEvent as CompanyEvent, + type DirectoryEvent as DirectoryEvent, + type EmploymentEvent as EmploymentEvent, + type IndividualEvent as IndividualEvent, + type JobCompletionEvent as JobCompletionEvent, + type PayStatementEvent as PayStatementEvent, + type PaymentEvent as PaymentEvent, + type WebhookEvent as WebhookEvent, + }; + + export { + RequestForwarding as RequestForwarding, + type RequestForwardingForwardResponse as RequestForwardingForwardResponse, + type RequestForwardingForwardParams as RequestForwardingForwardParams, + }; + + export { Jobs as Jobs }; + + export { Sandbox as Sandbox }; + + export { Payroll as Payroll }; + + export { Connect as Connect }; + + export type ConnectionStatusType = API.ConnectionStatusType; + export type OperationSupport = API.OperationSupport; + export type OperationSupportMatrix = API.OperationSupportMatrix; + export type Paging = API.Paging; +} diff --git a/src/core.ts b/src/core.ts deleted file mode 100644 index 4eebc18a5..000000000 --- a/src/core.ts +++ /dev/null @@ -1,1257 +0,0 @@ -import { VERSION } from './version'; -import { - FinchError, - APIError, - APIConnectionError, - APIConnectionTimeoutError, - APIUserAbortError, -} from './error'; -import { - kind as shimsKind, - type Readable, - getDefaultAgent, - type Agent, - fetch, - type RequestInfo, - type RequestInit, - type Response, - type HeadersInit, - init, -} from './_shims/index'; - -// try running side effects outside of _shims/index to workaround https://github.com/vercel/next.js/issues/76881 -init(); - -export { type Response }; -import { BlobLike, isBlobLike, isMultipartBody } from './uploads'; -export { - maybeMultipartFormRequestOptions, - multipartFormRequestOptions, - createForm, - type Uploadable, -} from './uploads'; - -export type Fetch = (url: RequestInfo, init?: RequestInit) => Promise; - -/** - * An alias to the builtin `Array` type so we can - * easily alias it in import statements if there are name clashes. - */ -type _Array = Array; - -/** - * An alias to the builtin `Record` type so we can - * easily alias it in import statements if there are name clashes. - */ -type _Record = Record; - -export type { _Array as Array, _Record as Record }; - -type PromiseOrValue = T | Promise; - -type APIResponseProps = { - response: Response; - options: FinalRequestOptions; - controller: AbortController; -}; - -async function defaultParseResponse(props: APIResponseProps): Promise { - const { response } = props; - // fetch refuses to read the body when the status code is 204. - if (response.status === 204) { - return null as T; - } - - if (props.options.__binaryResponse) { - return response as unknown as T; - } - - const contentType = response.headers.get('content-type'); - const mediaType = contentType?.split(';')[0]?.trim(); - const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json'); - if (isJSON) { - const json = await response.json(); - - debug('response', response.status, response.url, response.headers, json); - - return json as T; - } - - const text = await response.text(); - debug('response', response.status, response.url, response.headers, text); - - // TODO handle blob, arraybuffer, other content types, etc. - return text as unknown as T; -} - -/** - * A subclass of `Promise` providing additional helper methods - * for interacting with the SDK. - */ -export class APIPromise extends Promise { - private parsedPromise: Promise | undefined; - - constructor( - private responsePromise: Promise, - private parseResponse: (props: APIResponseProps) => PromiseOrValue = defaultParseResponse, - ) { - super((resolve) => { - // this is maybe a bit weird but this has to be a no-op to not implicitly - // parse the response body; instead .then, .catch, .finally are overridden - // to parse the response - resolve(null as any); - }); - } - - _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { - return new APIPromise(this.responsePromise, async (props) => - transform(await this.parseResponse(props), props), - ); - } - - /** - * Gets the raw `Response` instance instead of parsing the response - * data. - * - * If you want to parse the response body but still get the `Response` - * instance, you can use {@link withResponse()}. - * - * 👋 Getting the wrong TypeScript type for `Response`? - * Try setting `"moduleResolution": "NodeNext"` if you can, - * or add one of these imports before your first `import … from '@tryfinch/finch-api'`: - * - `import '@tryfinch/finch-api/shims/node'` (if you're running on Node) - * - `import '@tryfinch/finch-api/shims/web'` (otherwise) - */ - asResponse(): Promise { - return this.responsePromise.then((p) => p.response); - } - /** - * Gets the parsed response data and the raw `Response` instance. - * - * If you just want to get the raw `Response` instance without parsing it, - * you can use {@link asResponse()}. - * - * - * 👋 Getting the wrong TypeScript type for `Response`? - * Try setting `"moduleResolution": "NodeNext"` if you can, - * or add one of these imports before your first `import … from '@tryfinch/finch-api'`: - * - `import '@tryfinch/finch-api/shims/node'` (if you're running on Node) - * - `import '@tryfinch/finch-api/shims/web'` (otherwise) - */ - async withResponse(): Promise<{ data: T; response: Response }> { - const [data, response] = await Promise.all([this.parse(), this.asResponse()]); - return { data, response }; - } - - private parse(): Promise { - if (!this.parsedPromise) { - this.parsedPromise = this.responsePromise.then(this.parseResponse); - } - return this.parsedPromise; - } - - override then( - onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, - onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, - ): Promise { - return this.parse().then(onfulfilled, onrejected); - } - - override catch( - onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null, - ): Promise { - return this.parse().catch(onrejected); - } - - override finally(onfinally?: (() => void) | undefined | null): Promise { - return this.parse().finally(onfinally); - } -} - -export abstract class APIClient { - baseURL: string; - #baseURLOverridden: boolean; - maxRetries: number; - timeout: number; - httpAgent: Agent | undefined; - - private fetch: Fetch; - protected idempotencyHeader?: string; - - constructor({ - baseURL, - baseURLOverridden, - maxRetries = 2, - timeout = 60000, // 1 minute - httpAgent, - fetch: overriddenFetch, - }: { - baseURL: string; - baseURLOverridden: boolean; - maxRetries?: number | undefined; - timeout: number | undefined; - httpAgent: Agent | undefined; - fetch: Fetch | undefined; - }) { - this.baseURL = baseURL; - this.#baseURLOverridden = baseURLOverridden; - this.maxRetries = validatePositiveInteger('maxRetries', maxRetries); - this.timeout = validatePositiveInteger('timeout', timeout); - this.httpAgent = httpAgent; - - this.fetch = overriddenFetch ?? fetch; - } - - protected authHeaders(opts: FinalRequestOptions): Headers { - return {}; - } - - /** - * Override this to add your own default headers, for example: - * - * { - * ...super.defaultHeaders(), - * Authorization: 'Bearer 123', - * } - */ - protected defaultHeaders(opts: FinalRequestOptions): Headers { - return { - Accept: 'application/json', - ...(['head', 'get'].includes(opts.method) ? {} : { 'Content-Type': 'application/json' }), - 'User-Agent': this.getUserAgent(), - ...getPlatformHeaders(), - ...this.authHeaders(opts), - }; - } - - protected abstract defaultQuery(): DefaultQuery | undefined; - - /** - * Override this to add your own headers validation: - */ - protected validateHeaders(headers: Headers, customHeaders: Headers) {} - - protected defaultIdempotencyKey(): string { - return `stainless-node-retry-${uuid4()}`; - } - - get(path: string, opts?: PromiseOrValue>): APIPromise { - return this.methodRequest('get', path, opts); - } - - post(path: string, opts?: PromiseOrValue>): APIPromise { - return this.methodRequest('post', path, opts); - } - - patch(path: string, opts?: PromiseOrValue>): APIPromise { - return this.methodRequest('patch', path, opts); - } - - put(path: string, opts?: PromiseOrValue>): APIPromise { - return this.methodRequest('put', path, opts); - } - - delete(path: string, opts?: PromiseOrValue>): APIPromise { - return this.methodRequest('delete', path, opts); - } - - private methodRequest( - method: HTTPMethod, - path: string, - opts?: PromiseOrValue>, - ): APIPromise { - return this.request( - Promise.resolve(opts).then(async (opts) => { - const body = - opts && isBlobLike(opts?.body) ? new DataView(await opts.body.arrayBuffer()) - : opts?.body instanceof DataView ? opts.body - : opts?.body instanceof ArrayBuffer ? new DataView(opts.body) - : opts && ArrayBuffer.isView(opts?.body) ? new DataView(opts.body.buffer) - : opts?.body; - return { method, path, ...opts, body }; - }), - ); - } - - getAPIList = AbstractPage>( - path: string, - Page: new (...args: any[]) => PageClass, - opts?: RequestOptions, - ): PagePromise { - return this.requestAPIList(Page, { method: 'get', path, ...opts }); - } - - private calculateContentLength(body: unknown): string | null { - if (typeof body === 'string') { - if (typeof Buffer !== 'undefined') { - return Buffer.byteLength(body, 'utf8').toString(); - } - - if (typeof TextEncoder !== 'undefined') { - const encoder = new TextEncoder(); - const encoded = encoder.encode(body); - return encoded.length.toString(); - } - } else if (ArrayBuffer.isView(body)) { - return body.byteLength.toString(); - } - - return null; - } - - async buildRequest( - inputOptions: FinalRequestOptions, - { retryCount = 0 }: { retryCount?: number } = {}, - ): Promise<{ req: RequestInit; url: string; timeout: number }> { - const options = { ...inputOptions }; - const { method, path, query, defaultBaseURL, headers: headers = {} } = options; - - const body = - ArrayBuffer.isView(options.body) || (options.__binaryRequest && typeof options.body === 'string') ? - options.body - : isMultipartBody(options.body) ? options.body.body - : options.body ? JSON.stringify(options.body, null, 2) - : null; - const contentLength = this.calculateContentLength(body); - - const url = this.buildURL(path!, query, defaultBaseURL); - if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); - options.timeout = options.timeout ?? this.timeout; - const httpAgent = options.httpAgent ?? this.httpAgent ?? getDefaultAgent(url); - const minAgentTimeout = options.timeout + 1000; - if ( - typeof (httpAgent as any)?.options?.timeout === 'number' && - minAgentTimeout > ((httpAgent as any).options.timeout ?? 0) - ) { - // Allow any given request to bump our agent active socket timeout. - // This may seem strange, but leaking active sockets should be rare and not particularly problematic, - // and without mutating agent we would need to create more of them. - // This tradeoff optimizes for performance. - (httpAgent as any).options.timeout = minAgentTimeout; - } - - if (this.idempotencyHeader && method !== 'get') { - if (!inputOptions.idempotencyKey) inputOptions.idempotencyKey = this.defaultIdempotencyKey(); - headers[this.idempotencyHeader] = inputOptions.idempotencyKey; - } - - const reqHeaders = this.buildHeaders({ options, headers, contentLength, retryCount }); - - const req: RequestInit = { - method, - ...(body && { body: body as any }), - headers: reqHeaders, - ...(httpAgent && { agent: httpAgent }), - // @ts-ignore node-fetch uses a custom AbortSignal type that is - // not compatible with standard web types - signal: options.signal ?? null, - }; - - return { req, url, timeout: options.timeout }; - } - - private buildHeaders({ - options, - headers, - contentLength, - retryCount, - }: { - options: FinalRequestOptions; - headers: Record; - contentLength: string | null | undefined; - retryCount: number; - }): Record { - const reqHeaders: Record = {}; - if (contentLength) { - reqHeaders['content-length'] = contentLength; - } - - const defaultHeaders = this.defaultHeaders(options); - applyHeadersMut(reqHeaders, defaultHeaders); - applyHeadersMut(reqHeaders, headers); - - // let builtin fetch set the Content-Type for multipart bodies - if (isMultipartBody(options.body) && shimsKind !== 'node') { - delete reqHeaders['content-type']; - } - - // Don't set theses headers if they were already set or removed through default headers or by the caller. - // We check `defaultHeaders` and `headers`, which can contain nulls, instead of `reqHeaders` to account - // for the removal case. - if ( - getHeader(defaultHeaders, 'x-stainless-retry-count') === undefined && - getHeader(headers, 'x-stainless-retry-count') === undefined - ) { - reqHeaders['x-stainless-retry-count'] = String(retryCount); - } - if ( - getHeader(defaultHeaders, 'x-stainless-timeout') === undefined && - getHeader(headers, 'x-stainless-timeout') === undefined && - options.timeout - ) { - reqHeaders['x-stainless-timeout'] = String(Math.trunc(options.timeout / 1000)); - } - - this.validateHeaders(reqHeaders, headers); - - return reqHeaders; - } - - /** - * Used as a callback for mutating the given `FinalRequestOptions` object. - */ - protected async prepareOptions(options: FinalRequestOptions): Promise {} - - /** - * Used as a callback for mutating the given `RequestInit` object. - * - * This is useful for cases where you want to add certain headers based off of - * the request properties, e.g. `method` or `url`. - */ - protected async prepareRequest( - request: RequestInit, - { url, options }: { url: string; options: FinalRequestOptions }, - ): Promise {} - - protected parseHeaders(headers: HeadersInit | null | undefined): Record { - return ( - !headers ? {} - : Symbol.iterator in headers ? - Object.fromEntries(Array.from(headers as Iterable).map((header) => [...header])) - : { ...(headers as any as Record) } - ); - } - - protected makeStatusError( - status: number | undefined, - error: Object | undefined, - message: string | undefined, - headers: Headers | undefined, - ): APIError { - return APIError.generate(status, error, message, headers); - } - - request( - options: PromiseOrValue>, - remainingRetries: number | null = null, - ): APIPromise { - return new APIPromise(this.makeRequest(options, remainingRetries)); - } - - private async makeRequest( - optionsInput: PromiseOrValue>, - retriesRemaining: number | null, - ): Promise { - const options = await optionsInput; - const maxRetries = options.maxRetries ?? this.maxRetries; - if (retriesRemaining == null) { - retriesRemaining = maxRetries; - } - - await this.prepareOptions(options); - - const { req, url, timeout } = await this.buildRequest(options, { - retryCount: maxRetries - retriesRemaining, - }); - - await this.prepareRequest(req, { url, options }); - - debug('request', url, options, req.headers); - - if (options.signal?.aborted) { - throw new APIUserAbortError(); - } - - const controller = new AbortController(); - const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError); - - if (response instanceof Error) { - if (options.signal?.aborted) { - throw new APIUserAbortError(); - } - if (retriesRemaining) { - return this.retryRequest(options, retriesRemaining); - } - if (response.name === 'AbortError') { - throw new APIConnectionTimeoutError(); - } - throw new APIConnectionError({ cause: response }); - } - - const responseHeaders = createResponseHeaders(response.headers); - - if (!response.ok) { - if (retriesRemaining && this.shouldRetry(response)) { - const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; - debug(`response (error; ${retryMessage})`, response.status, url, responseHeaders); - return this.retryRequest(options, retriesRemaining, responseHeaders); - } - - const errText = await response.text().catch((e) => castToError(e).message); - const errJSON = safeJSON(errText); - const errMessage = errJSON ? undefined : errText; - const retryMessage = retriesRemaining ? `(error; no more retries left)` : `(error; not retryable)`; - - debug(`response (error; ${retryMessage})`, response.status, url, responseHeaders, errMessage); - - const err = this.makeStatusError(response.status, errJSON, errMessage, responseHeaders); - throw err; - } - - return { response, options, controller }; - } - - requestAPIList = AbstractPage>( - Page: new (...args: ConstructorParameters) => PageClass, - options: FinalRequestOptions, - ): PagePromise { - const request = this.makeRequest(options, null); - return new PagePromise(this, request, Page); - } - - buildURL(path: string, query: Req | null | undefined, defaultBaseURL?: string | undefined): string { - const baseURL = (!this.#baseURLOverridden && defaultBaseURL) || this.baseURL; - const url = - isAbsoluteURL(path) ? - new URL(path) - : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); - - const defaultQuery = this.defaultQuery(); - if (!isEmptyObj(defaultQuery)) { - query = { ...defaultQuery, ...query } as Req; - } - - if (typeof query === 'object' && query && !Array.isArray(query)) { - url.search = this.stringifyQuery(query as Record); - } - - return url.toString(); - } - - protected stringifyQuery(query: Record): string { - return Object.entries(query) - .filter(([_, value]) => typeof value !== 'undefined') - .map(([key, value]) => { - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - } - if (value === null) { - return `${encodeURIComponent(key)}=`; - } - throw new FinchError( - `Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`, - ); - }) - .join('&'); - } - - async fetchWithTimeout( - url: RequestInfo, - init: RequestInit | undefined, - ms: number, - controller: AbortController, - ): Promise { - const { signal, ...options } = init || {}; - if (signal) signal.addEventListener('abort', () => controller.abort()); - - const timeout = setTimeout(() => controller.abort(), ms); - - const fetchOptions = { - signal: controller.signal as any, - ...options, - }; - if (fetchOptions.method) { - // Custom methods like 'patch' need to be uppercased - // See https://github.com/nodejs/undici/issues/2294 - fetchOptions.method = fetchOptions.method.toUpperCase(); - } - - return ( - // use undefined this binding; fetch errors if bound to something else in browser/cloudflare - this.fetch.call(undefined, url, fetchOptions).finally(() => { - clearTimeout(timeout); - }) - ); - } - - private shouldRetry(response: Response): boolean { - // Note this is not a standard header. - const shouldRetryHeader = response.headers.get('x-should-retry'); - - // If the server explicitly says whether or not to retry, obey. - if (shouldRetryHeader === 'true') return true; - if (shouldRetryHeader === 'false') return false; - - // Retry on request timeouts. - if (response.status === 408) return true; - - // Retry on lock timeouts. - if (response.status === 409) return true; - - // Retry on rate limits. - if (response.status === 429) return true; - - // Retry internal errors. - if (response.status >= 500) return true; - - return false; - } - - private async retryRequest( - options: FinalRequestOptions, - retriesRemaining: number, - responseHeaders?: Headers | undefined, - ): Promise { - let timeoutMillis: number | undefined; - - // Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it. - const retryAfterMillisHeader = responseHeaders?.['retry-after-ms']; - if (retryAfterMillisHeader) { - const timeoutMs = parseFloat(retryAfterMillisHeader); - if (!Number.isNaN(timeoutMs)) { - timeoutMillis = timeoutMs; - } - } - - // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After - const retryAfterHeader = responseHeaders?.['retry-after']; - if (retryAfterHeader && !timeoutMillis) { - const timeoutSeconds = parseFloat(retryAfterHeader); - if (!Number.isNaN(timeoutSeconds)) { - timeoutMillis = timeoutSeconds * 1000; - } else { - timeoutMillis = Date.parse(retryAfterHeader) - Date.now(); - } - } - - // If the API asks us to wait a certain amount of time (and it's a reasonable amount), - // just do what it says, but otherwise calculate a default - if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) { - const maxRetries = options.maxRetries ?? this.maxRetries; - timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); - } - await sleep(timeoutMillis); - - return this.makeRequest(options, retriesRemaining - 1); - } - - private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { - const initialRetryDelay = 0.5; - const maxRetryDelay = 8.0; - - const numRetries = maxRetries - retriesRemaining; - - // Apply exponential backoff, but not more than the max. - const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay); - - // Apply some jitter, take up to at most 25 percent of the retry time. - const jitter = 1 - Math.random() * 0.25; - - return sleepSeconds * jitter * 1000; - } - - private getUserAgent(): string { - return `${this.constructor.name}/JS ${VERSION}`; - } -} - -export type PageInfo = { url: URL } | { params: Record | null }; - -export abstract class AbstractPage implements AsyncIterable { - #client: APIClient; - protected options: FinalRequestOptions; - - protected response: Response; - protected body: unknown; - - constructor(client: APIClient, response: Response, body: unknown, options: FinalRequestOptions) { - this.#client = client; - this.options = options; - this.response = response; - this.body = body; - } - - /** - * @deprecated Use nextPageInfo instead - */ - abstract nextPageParams(): Partial> | null; - abstract nextPageInfo(): PageInfo | null; - - abstract getPaginatedItems(): Item[]; - - hasNextPage(): boolean { - const items = this.getPaginatedItems(); - if (!items.length) return false; - return this.nextPageInfo() != null; - } - - async getNextPage(): Promise { - const nextInfo = this.nextPageInfo(); - if (!nextInfo) { - throw new FinchError( - 'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.', - ); - } - const nextOptions = { ...this.options }; - if ('params' in nextInfo && typeof nextOptions.query === 'object') { - nextOptions.query = { ...nextOptions.query, ...nextInfo.params }; - } else if ('url' in nextInfo) { - const params = [...Object.entries(nextOptions.query || {}), ...nextInfo.url.searchParams.entries()]; - for (const [key, value] of params) { - nextInfo.url.searchParams.set(key, value as any); - } - nextOptions.query = undefined; - nextOptions.path = nextInfo.url.toString(); - } - return await this.#client.requestAPIList(this.constructor as any, nextOptions); - } - - async *iterPages(): AsyncGenerator { - // eslint-disable-next-line @typescript-eslint/no-this-alias - let page: this = this; - yield page; - while (page.hasNextPage()) { - page = await page.getNextPage(); - yield page; - } - } - - async *[Symbol.asyncIterator](): AsyncGenerator { - for await (const page of this.iterPages()) { - for (const item of page.getPaginatedItems()) { - yield item; - } - } - } -} - -/** - * This subclass of Promise will resolve to an instantiated Page once the request completes. - * - * It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg: - * - * for await (const item of client.items.list()) { - * console.log(item) - * } - */ -export class PagePromise< - PageClass extends AbstractPage, - Item = ReturnType[number], - > - extends APIPromise - implements AsyncIterable -{ - constructor( - client: APIClient, - request: Promise, - Page: new (...args: ConstructorParameters) => PageClass, - ) { - super( - request, - async (props) => new Page(client, props.response, await defaultParseResponse(props), props.options), - ); - } - - /** - * Allow auto-paginating iteration on an unawaited list call, eg: - * - * for await (const item of client.items.list()) { - * console.log(item) - * } - */ - async *[Symbol.asyncIterator](): AsyncGenerator { - const page = await this; - for await (const item of page) { - yield item; - } - } -} - -export const createResponseHeaders = ( - headers: Awaited>['headers'], -): Record => { - return new Proxy( - Object.fromEntries( - // @ts-ignore - headers.entries(), - ), - { - get(target, name) { - const key = name.toString(); - return target[key.toLowerCase()] || target[key]; - }, - }, - ); -}; - -type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'; - -export type RequestClient = { fetch: Fetch }; -export type Headers = Record; -export type DefaultQuery = Record; -export type KeysEnum = { [P in keyof Required]: true }; - -export type RequestOptions< - Req = unknown | Record | Readable | BlobLike | ArrayBufferView | ArrayBuffer, -> = { - method?: HTTPMethod; - path?: string; - query?: Req | undefined; - body?: Req | null | undefined; - headers?: Headers | undefined; - defaultBaseURL?: string | undefined; - - maxRetries?: number; - stream?: boolean | undefined; - timeout?: number; - httpAgent?: Agent; - signal?: AbortSignal | undefined | null; - idempotencyKey?: string; - - __binaryRequest?: boolean | undefined; - __binaryResponse?: boolean | undefined; -}; - -// This is required so that we can determine if a given object matches the RequestOptions -// type at runtime. While this requires duplication, it is enforced by the TypeScript -// compiler such that any missing / extraneous keys will cause an error. -const requestOptionsKeys: KeysEnum = { - method: true, - path: true, - query: true, - body: true, - headers: true, - defaultBaseURL: true, - - maxRetries: true, - stream: true, - timeout: true, - httpAgent: true, - signal: true, - idempotencyKey: true, - - __binaryRequest: true, - __binaryResponse: true, -}; - -export const isRequestOptions = (obj: unknown): obj is RequestOptions => { - return ( - typeof obj === 'object' && - obj !== null && - !isEmptyObj(obj) && - Object.keys(obj).every((k) => hasOwn(requestOptionsKeys, k)) - ); -}; - -export type FinalRequestOptions | Readable | DataView> = - RequestOptions & { - method: HTTPMethod; - path: string; - }; - -declare const Deno: any; -declare const EdgeRuntime: any; -type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown'; -type PlatformName = - | 'MacOS' - | 'Linux' - | 'Windows' - | 'FreeBSD' - | 'OpenBSD' - | 'iOS' - | 'Android' - | `Other:${string}` - | 'Unknown'; -type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari'; -type PlatformProperties = { - 'X-Stainless-Lang': 'js'; - 'X-Stainless-Package-Version': string; - 'X-Stainless-OS': PlatformName; - 'X-Stainless-Arch': Arch; - 'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown'; - 'X-Stainless-Runtime-Version': string; -}; -const getPlatformProperties = (): PlatformProperties => { - if (typeof Deno !== 'undefined' && Deno.build != null) { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': normalizePlatform(Deno.build.os), - 'X-Stainless-Arch': normalizeArch(Deno.build.arch), - 'X-Stainless-Runtime': 'deno', - 'X-Stainless-Runtime-Version': - typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown', - }; - } - if (typeof EdgeRuntime !== 'undefined') { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': `other:${EdgeRuntime}`, - 'X-Stainless-Runtime': 'edge', - 'X-Stainless-Runtime-Version': process.version, - }; - } - // Check if Node.js - if (Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]') { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': normalizePlatform(process.platform), - 'X-Stainless-Arch': normalizeArch(process.arch), - 'X-Stainless-Runtime': 'node', - 'X-Stainless-Runtime-Version': process.version, - }; - } - - const browserInfo = getBrowserInfo(); - if (browserInfo) { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': 'unknown', - 'X-Stainless-Runtime': `browser:${browserInfo.browser}`, - 'X-Stainless-Runtime-Version': browserInfo.version, - }; - } - - // TODO add support for Cloudflare workers, etc. - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': 'unknown', - 'X-Stainless-Runtime': 'unknown', - 'X-Stainless-Runtime-Version': 'unknown', - }; -}; - -type BrowserInfo = { - browser: Browser; - version: string; -}; - -declare const navigator: { userAgent: string } | undefined; - -// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts -function getBrowserInfo(): BrowserInfo | null { - if (typeof navigator === 'undefined' || !navigator) { - return null; - } - - // NOTE: The order matters here! - const browserPatterns = [ - { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ }, - ]; - - // Find the FIRST matching browser - for (const { key, pattern } of browserPatterns) { - const match = pattern.exec(navigator.userAgent); - if (match) { - const major = match[1] || 0; - const minor = match[2] || 0; - const patch = match[3] || 0; - - return { browser: key, version: `${major}.${minor}.${patch}` }; - } - } - - return null; -} - -const normalizeArch = (arch: string): Arch => { - // Node docs: - // - https://nodejs.org/api/process.html#processarch - // Deno docs: - // - https://doc.deno.land/deno/stable/~/Deno.build - if (arch === 'x32') return 'x32'; - if (arch === 'x86_64' || arch === 'x64') return 'x64'; - if (arch === 'arm') return 'arm'; - if (arch === 'aarch64' || arch === 'arm64') return 'arm64'; - if (arch) return `other:${arch}`; - return 'unknown'; -}; - -const normalizePlatform = (platform: string): PlatformName => { - // Node platforms: - // - https://nodejs.org/api/process.html#processplatform - // Deno platforms: - // - https://doc.deno.land/deno/stable/~/Deno.build - // - https://github.com/denoland/deno/issues/14799 - - platform = platform.toLowerCase(); - - // NOTE: this iOS check is untested and may not work - // Node does not work natively on IOS, there is a fork at - // https://github.com/nodejs-mobile/nodejs-mobile - // however it is unknown at the time of writing how to detect if it is running - if (platform.includes('ios')) return 'iOS'; - if (platform === 'android') return 'Android'; - if (platform === 'darwin') return 'MacOS'; - if (platform === 'win32') return 'Windows'; - if (platform === 'freebsd') return 'FreeBSD'; - if (platform === 'openbsd') return 'OpenBSD'; - if (platform === 'linux') return 'Linux'; - if (platform) return `Other:${platform}`; - return 'Unknown'; -}; - -let _platformHeaders: PlatformProperties; -const getPlatformHeaders = () => { - return (_platformHeaders ??= getPlatformProperties()); -}; - -export const safeJSON = (text: string) => { - try { - return JSON.parse(text); - } catch (err) { - return undefined; - } -}; - -// https://url.spec.whatwg.org/#url-scheme-string -const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i; -const isAbsoluteURL = (url: string): boolean => { - return startsWithSchemeRegexp.test(url); -}; - -export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -const validatePositiveInteger = (name: string, n: unknown): number => { - if (typeof n !== 'number' || !Number.isInteger(n)) { - throw new FinchError(`${name} must be an integer`); - } - if (n < 0) { - throw new FinchError(`${name} must be a positive integer`); - } - return n; -}; - -export const castToError = (err: any): Error => { - if (err instanceof Error) return err; - if (typeof err === 'object' && err !== null) { - try { - return new Error(JSON.stringify(err)); - } catch {} - } - return new Error(err); -}; - -export const ensurePresent = (value: T | null | undefined): T => { - if (value == null) throw new FinchError(`Expected a value to be given but received ${value} instead.`); - return value; -}; - -/** - * Read an environment variable. - * - * Trims beginning and trailing whitespace. - * - * Will return undefined if the environment variable doesn't exist or cannot be accessed. - */ -export const readEnv = (env: string): string | undefined => { - if (typeof process !== 'undefined') { - return process.env?.[env]?.trim() ?? undefined; - } - if (typeof Deno !== 'undefined') { - return Deno.env?.get?.(env)?.trim(); - } - return undefined; -}; - -export const coerceInteger = (value: unknown): number => { - if (typeof value === 'number') return Math.round(value); - if (typeof value === 'string') return parseInt(value, 10); - - throw new FinchError(`Could not coerce ${value} (type: ${typeof value}) into a number`); -}; - -export const coerceFloat = (value: unknown): number => { - if (typeof value === 'number') return value; - if (typeof value === 'string') return parseFloat(value); - - throw new FinchError(`Could not coerce ${value} (type: ${typeof value}) into a number`); -}; - -export const coerceBoolean = (value: unknown): boolean => { - if (typeof value === 'boolean') return value; - if (typeof value === 'string') return value === 'true'; - return Boolean(value); -}; - -export const maybeCoerceInteger = (value: unknown): number | undefined => { - if (value == null) { - return undefined; - } - return coerceInteger(value); -}; - -export const maybeCoerceFloat = (value: unknown): number | undefined => { - if (value == null) { - return undefined; - } - return coerceFloat(value); -}; - -export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { - if (value == null) { - return undefined; - } - return coerceBoolean(value); -}; - -// https://stackoverflow.com/a/34491287 -export function isEmptyObj(obj: Object | null | undefined): boolean { - if (!obj) return true; - for (const _k in obj) return false; - return true; -} - -// https://eslint.org/docs/latest/rules/no-prototype-builtins -export function hasOwn(obj: Object, key: string): boolean { - return Object.prototype.hasOwnProperty.call(obj, key); -} - -/** - * Copies headers from "newHeaders" onto "targetHeaders", - * using lower-case for all properties, - * ignoring any keys with undefined values, - * and deleting any keys with null values. - */ -function applyHeadersMut(targetHeaders: Headers, newHeaders: Headers): void { - for (const k in newHeaders) { - if (!hasOwn(newHeaders, k)) continue; - const lowerKey = k.toLowerCase(); - if (!lowerKey) continue; - - const val = newHeaders[k]; - - if (val === null) { - delete targetHeaders[lowerKey]; - } else if (val !== undefined) { - targetHeaders[lowerKey] = val; - } - } -} - -export function debug(action: string, ...args: any[]) { - if (typeof process !== 'undefined' && process?.env?.['DEBUG'] === 'true') { - console.log(`Finch:DEBUG:${action}`, ...args); - } -} - -/** - * https://stackoverflow.com/a/2117523 - */ -const uuid4 = () => { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { - const r = (Math.random() * 16) | 0; - const v = c === 'x' ? r : (r & 0x3) | 0x8; - return v.toString(16); - }); -}; - -export const isRunningInBrowser = () => { - return ( - // @ts-ignore - typeof window !== 'undefined' && - // @ts-ignore - typeof window.document !== 'undefined' && - // @ts-ignore - typeof navigator !== 'undefined' - ); -}; - -export interface HeadersProtocol { - get: (header: string) => string | null | undefined; -} -export type HeadersLike = Record | HeadersProtocol; - -export const isHeadersProtocol = (headers: any): headers is HeadersProtocol => { - return typeof headers?.get === 'function'; -}; - -export const getRequiredHeader = (headers: HeadersLike | Headers, header: string): string => { - const foundHeader = getHeader(headers, header); - if (foundHeader === undefined) { - throw new Error(`Could not find ${header} header`); - } - return foundHeader; -}; - -export const getHeader = (headers: HeadersLike | Headers, header: string): string | undefined => { - const lowerCasedHeader = header.toLowerCase(); - if (isHeadersProtocol(headers)) { - // to deal with the case where the header looks like Stainless-Event-Id - const intercapsHeader = - header[0]?.toUpperCase() + - header.substring(1).replace(/([^\w])(\w)/g, (_m, g1, g2) => g1 + g2.toUpperCase()); - for (const key of [header, lowerCasedHeader, header.toUpperCase(), intercapsHeader]) { - const value = headers.get(key); - if (value) { - return value; - } - } - } - - for (const [key, value] of Object.entries(headers)) { - if (key.toLowerCase() === lowerCasedHeader) { - if (Array.isArray(value)) { - if (value.length <= 1) return value[0]; - console.warn(`Received ${value.length} entries for the ${header} header, using the first entry.`); - return value[0]; - } - return value; - } - } - - return undefined; -}; - -/** - * Encodes a string to Base64 format. - */ -export const toBase64 = (data: string | Uint8Array | null | undefined): string => { - if (!data) return ''; - - if (typeof data === 'string') { - data = new TextEncoder().encode(data); - } - - if (typeof Buffer !== 'undefined') { - return Buffer.from(data).toString('base64'); - } - - if (typeof btoa !== 'undefined') { - return btoa(String.fromCharCode.apply(null, data as any)); - } - - throw new FinchError('Cannot generate b64 string; Expected `Buffer` or `btoa` to be defined'); -}; - -export const fromBase64 = (str: string): Uint8Array => { - if (typeof Buffer !== 'undefined') { - return new Uint8Array(Buffer.from(str, 'base64')); - } - - if (typeof atob !== 'undefined') { - return new Uint8Array( - atob(str) - .split('') - .map((c) => c.charCodeAt(0)), - ); - } - - throw new FinchError('Cannot decode b64 string; Expected `Buffer` or `atob` to be defined'); -}; - -export function isObj(obj: unknown): obj is Record { - return obj != null && typeof obj === 'object' && !Array.isArray(obj); -} diff --git a/src/core/README.md b/src/core/README.md new file mode 100644 index 000000000..485fce861 --- /dev/null +++ b/src/core/README.md @@ -0,0 +1,3 @@ +# `core` + +This directory holds public modules implementing non-resource-specific SDK functionality. diff --git a/src/core/api-promise.ts b/src/core/api-promise.ts new file mode 100644 index 000000000..a3d3ebef5 --- /dev/null +++ b/src/core/api-promise.ts @@ -0,0 +1,92 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { type Finch } from '../client'; + +import { type PromiseOrValue } from '../internal/types'; +import { APIResponseProps, defaultParseResponse } from '../internal/parse'; + +/** + * A subclass of `Promise` providing additional helper methods + * for interacting with the SDK. + */ +export class APIPromise extends Promise { + private parsedPromise: Promise | undefined; + #client: Finch; + + constructor( + client: Finch, + private responsePromise: Promise, + private parseResponse: ( + client: Finch, + props: APIResponseProps, + ) => PromiseOrValue = defaultParseResponse, + ) { + super((resolve) => { + // this is maybe a bit weird but this has to be a no-op to not implicitly + // parse the response body; instead .then, .catch, .finally are overridden + // to parse the response + resolve(null as any); + }); + this.#client = client; + } + + _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { + return new APIPromise(this.#client, this.responsePromise, async (client, props) => + transform(await this.parseResponse(client, props), props), + ); + } + + /** + * Gets the raw `Response` instance instead of parsing the response + * data. + * + * If you want to parse the response body but still get the `Response` + * instance, you can use {@link withResponse()}. + * + * 👋 Getting the wrong TypeScript type for `Response`? + * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` + * to your `tsconfig.json`. + */ + asResponse(): Promise { + return this.responsePromise.then((p) => p.response); + } + + /** + * Gets the parsed response data and the raw `Response` instance. + * + * If you just want to get the raw `Response` instance without parsing it, + * you can use {@link asResponse()}. + * + * 👋 Getting the wrong TypeScript type for `Response`? + * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` + * to your `tsconfig.json`. + */ + async withResponse(): Promise<{ data: T; response: Response }> { + const [data, response] = await Promise.all([this.parse(), this.asResponse()]); + return { data, response }; + } + + private parse(): Promise { + if (!this.parsedPromise) { + this.parsedPromise = this.responsePromise.then((data) => this.parseResponse(this.#client, data)); + } + return this.parsedPromise; + } + + override then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, + ): Promise { + return this.parse().then(onfulfilled, onrejected); + } + + override catch( + onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null, + ): Promise { + return this.parse().catch(onrejected); + } + + override finally(onfinally?: (() => void) | undefined | null): Promise { + return this.parse().finally(onfinally); + } +} diff --git a/src/core/error.ts b/src/core/error.ts new file mode 100644 index 000000000..e969b5f42 --- /dev/null +++ b/src/core/error.ts @@ -0,0 +1,130 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { castToError } from '../internal/errors'; + +export class FinchError extends Error {} + +export class APIError< + TStatus extends number | undefined = number | undefined, + THeaders extends Headers | undefined = Headers | undefined, + TError extends Object | undefined = Object | undefined, +> extends FinchError { + /** HTTP status for the response that caused the error */ + readonly status: TStatus; + /** HTTP headers for the response that caused the error */ + readonly headers: THeaders; + /** JSON body of the response that caused the error */ + readonly error: TError; + + constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) { + super(`${APIError.makeMessage(status, error, message)}`); + this.status = status; + this.headers = headers; + this.error = error; + } + + private static makeMessage(status: number | undefined, error: any, message: string | undefined) { + const msg = + error?.message ? + typeof error.message === 'string' ? + error.message + : JSON.stringify(error.message) + : error ? JSON.stringify(error) + : message; + + if (status && msg) { + return `${status} ${msg}`; + } + if (status) { + return `${status} status code (no body)`; + } + if (msg) { + return msg; + } + return '(no status code or body)'; + } + + static generate( + status: number | undefined, + errorResponse: Object | undefined, + message: string | undefined, + headers: Headers | undefined, + ): APIError { + if (!status || !headers) { + return new APIConnectionError({ message, cause: castToError(errorResponse) }); + } + + const error = errorResponse as Record; + + if (status === 400) { + return new BadRequestError(status, error, message, headers); + } + + if (status === 401) { + return new AuthenticationError(status, error, message, headers); + } + + if (status === 403) { + return new PermissionDeniedError(status, error, message, headers); + } + + if (status === 404) { + return new NotFoundError(status, error, message, headers); + } + + if (status === 409) { + return new ConflictError(status, error, message, headers); + } + + if (status === 422) { + return new UnprocessableEntityError(status, error, message, headers); + } + + if (status === 429) { + return new RateLimitError(status, error, message, headers); + } + + if (status >= 500) { + return new InternalServerError(status, error, message, headers); + } + + return new APIError(status, error, message, headers); + } +} + +export class APIUserAbortError extends APIError { + constructor({ message }: { message?: string } = {}) { + super(undefined, undefined, message || 'Request was aborted.', undefined); + } +} + +export class APIConnectionError extends APIError { + constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) { + super(undefined, undefined, message || 'Connection error.', undefined); + // in some environments the 'cause' property is already declared + // @ts-ignore + if (cause) this.cause = cause; + } +} + +export class APIConnectionTimeoutError extends APIConnectionError { + constructor({ message }: { message?: string } = {}) { + super({ message: message ?? 'Request timed out.' }); + } +} + +export class BadRequestError extends APIError<400, Headers> {} + +export class AuthenticationError extends APIError<401, Headers> {} + +export class PermissionDeniedError extends APIError<403, Headers> {} + +export class NotFoundError extends APIError<404, Headers> {} + +export class ConflictError extends APIError<409, Headers> {} + +export class UnprocessableEntityError extends APIError<422, Headers> {} + +export class RateLimitError extends APIError<429, Headers> {} + +export class InternalServerError extends APIError {} diff --git a/src/core/pagination.ts b/src/core/pagination.ts new file mode 100644 index 000000000..475ff5323 --- /dev/null +++ b/src/core/pagination.ts @@ -0,0 +1,292 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { FinchError } from './error'; +import { FinalRequestOptions } from '../internal/request-options'; +import { defaultParseResponse } from '../internal/parse'; +import * as Shared from '../resources/shared'; +import * as DirectoryAPI from '../resources/hris/directory'; +import { type Finch } from '../client'; +import { APIPromise } from './api-promise'; +import { type APIResponseProps } from '../internal/parse'; +import { maybeObj } from '../internal/utils/values'; + +export type PageRequestOptions = Pick; + +export abstract class AbstractPage implements AsyncIterable { + #client: Finch; + protected options: FinalRequestOptions; + + protected response: Response; + protected body: unknown; + + constructor(client: Finch, response: Response, body: unknown, options: FinalRequestOptions) { + this.#client = client; + this.options = options; + this.response = response; + this.body = body; + } + + abstract nextPageRequestOptions(): PageRequestOptions | null; + + abstract getPaginatedItems(): Item[]; + + hasNextPage(): boolean { + const items = this.getPaginatedItems(); + if (!items.length) return false; + return this.nextPageRequestOptions() != null; + } + + async getNextPage(): Promise { + const nextOptions = this.nextPageRequestOptions(); + if (!nextOptions) { + throw new FinchError( + 'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.', + ); + } + + return await this.#client.requestAPIList(this.constructor as any, nextOptions); + } + + async *iterPages(): AsyncGenerator { + let page: this = this; + yield page; + while (page.hasNextPage()) { + page = await page.getNextPage(); + yield page; + } + } + + async *[Symbol.asyncIterator](): AsyncGenerator { + for await (const page of this.iterPages()) { + for (const item of page.getPaginatedItems()) { + yield item; + } + } + } +} + +/** + * This subclass of Promise will resolve to an instantiated Page once the request completes. + * + * It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg: + * + * for await (const item of client.items.list()) { + * console.log(item) + * } + */ +export class PagePromise< + PageClass extends AbstractPage, + Item = ReturnType[number], + > + extends APIPromise + implements AsyncIterable +{ + constructor( + client: Finch, + request: Promise, + Page: new (...args: ConstructorParameters) => PageClass, + ) { + super( + client, + request, + async (client, props) => + new Page(client, props.response, await defaultParseResponse(client, props), props.options), + ); + } + + /** + * Allow auto-paginating iteration on an unawaited list call, eg: + * + * for await (const item of client.items.list()) { + * console.log(item) + * } + */ + async *[Symbol.asyncIterator](): AsyncGenerator { + const page = await this; + for await (const item of page) { + yield item; + } + } +} + +export type SinglePageResponse = Item[]; + +export class SinglePage extends AbstractPage { + items: Array; + + constructor( + client: Finch, + response: Response, + body: SinglePageResponse, + options: FinalRequestOptions, + ) { + super(client, response, body, options); + + this.items = body || []; + } + + getPaginatedItems(): Item[] { + return this.items ?? []; + } + + nextPageRequestOptions(): PageRequestOptions | null { + return null; + } +} + +export interface ResponsesPageResponse { + responses: Array; +} + +export class ResponsesPage extends AbstractPage implements ResponsesPageResponse { + responses: Array; + + constructor( + client: Finch, + response: Response, + body: ResponsesPageResponse, + options: FinalRequestOptions, + ) { + super(client, response, body, options); + + this.responses = body.responses || []; + } + + getPaginatedItems(): Item[] { + return this.responses ?? []; + } + + nextPageRequestOptions(): PageRequestOptions | null { + return null; + } +} + +export interface IndividualsPageResponse { + /** + * The array of employees. + */ + individuals: Array; + + paging: Shared.Paging; +} + +export interface IndividualsPageParams { + /** + * Number of employees to return (defaults to all) + */ + limit?: number; + + /** + * Index to start from (defaults to 0) + */ + offset?: number; +} + +export class IndividualsPage + extends AbstractPage + implements IndividualsPageResponse +{ + /** + * The array of employees. + */ + individuals: Array; + + paging: Shared.Paging; + + constructor( + client: Finch, + response: Response, + body: IndividualsPageResponse, + options: FinalRequestOptions, + ) { + super(client, response, body, options); + + this.individuals = body.individuals || []; + this.paging = body.paging; + } + + getPaginatedItems(): DirectoryAPI.IndividualInDirectory[] { + return this.individuals ?? []; + } + + nextPageRequestOptions(): PageRequestOptions | null { + const offset = this.paging.offset ?? 0; + const length = this.getPaginatedItems().length; + const currentCount = offset + length; + + const totalCount = this.paging.count; + if (!totalCount) { + return null; + } + + if (currentCount < totalCount) { + return { + ...this.options, + query: { + ...maybeObj(this.options.query), + offset: currentCount, + }, + }; + } + + return null; + } +} + +export interface PageResponse { + data: Array; + + paging: Shared.Paging; +} + +export interface PageParams { + /** + * Number of entries to return (defaults to all) + */ + limit?: number; + + /** + * Index to start from (defaults to 0) + */ + offset?: number; +} + +export class Page extends AbstractPage implements PageResponse { + data: Array; + + paging: Shared.Paging; + + constructor(client: Finch, response: Response, body: PageResponse, options: FinalRequestOptions) { + super(client, response, body, options); + + this.data = body.data || []; + this.paging = body.paging; + } + + getPaginatedItems(): Item[] { + return this.data ?? []; + } + + nextPageRequestOptions(): PageRequestOptions | null { + const offset = this.paging.offset ?? 0; + const length = this.getPaginatedItems().length; + const currentCount = offset + length; + + const totalCount = this.paging.count; + if (!totalCount) { + return null; + } + + if (currentCount < totalCount) { + return { + ...this.options, + query: { + ...maybeObj(this.options.query), + offset: currentCount, + }, + }; + } + + return null; + } +} diff --git a/src/core/resource.ts b/src/core/resource.ts new file mode 100644 index 000000000..29e3943db --- /dev/null +++ b/src/core/resource.ts @@ -0,0 +1,11 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import type { Finch } from '../client'; + +export abstract class APIResource { + protected _client: Finch; + + constructor(client: Finch) { + this._client = client; + } +} diff --git a/src/core/uploads.ts b/src/core/uploads.ts new file mode 100644 index 000000000..2882ca6d1 --- /dev/null +++ b/src/core/uploads.ts @@ -0,0 +1,2 @@ +export { type Uploadable } from '../internal/uploads'; +export { toFile, type ToFileInput } from '../internal/to-file'; diff --git a/src/error.ts b/src/error.ts index 44c6bd24e..fc55f46c0 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,130 +1,2 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { castToError, Headers } from './core'; - -export class FinchError extends Error {} - -export class APIError< - TStatus extends number | undefined = number | undefined, - THeaders extends Headers | undefined = Headers | undefined, - TError extends Object | undefined = Object | undefined, -> extends FinchError { - /** HTTP status for the response that caused the error */ - readonly status: TStatus; - /** HTTP headers for the response that caused the error */ - readonly headers: THeaders; - /** JSON body of the response that caused the error */ - readonly error: TError; - - constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) { - super(`${APIError.makeMessage(status, error, message)}`); - this.status = status; - this.headers = headers; - this.error = error; - } - - private static makeMessage(status: number | undefined, error: any, message: string | undefined) { - const msg = - error?.message ? - typeof error.message === 'string' ? - error.message - : JSON.stringify(error.message) - : error ? JSON.stringify(error) - : message; - - if (status && msg) { - return `${status} ${msg}`; - } - if (status) { - return `${status} status code (no body)`; - } - if (msg) { - return msg; - } - return '(no status code or body)'; - } - - static generate( - status: number | undefined, - errorResponse: Object | undefined, - message: string | undefined, - headers: Headers | undefined, - ): APIError { - if (!status || !headers) { - return new APIConnectionError({ message, cause: castToError(errorResponse) }); - } - - const error = errorResponse as Record; - - if (status === 400) { - return new BadRequestError(status, error, message, headers); - } - - if (status === 401) { - return new AuthenticationError(status, error, message, headers); - } - - if (status === 403) { - return new PermissionDeniedError(status, error, message, headers); - } - - if (status === 404) { - return new NotFoundError(status, error, message, headers); - } - - if (status === 409) { - return new ConflictError(status, error, message, headers); - } - - if (status === 422) { - return new UnprocessableEntityError(status, error, message, headers); - } - - if (status === 429) { - return new RateLimitError(status, error, message, headers); - } - - if (status >= 500) { - return new InternalServerError(status, error, message, headers); - } - - return new APIError(status, error, message, headers); - } -} - -export class APIUserAbortError extends APIError { - constructor({ message }: { message?: string } = {}) { - super(undefined, undefined, message || 'Request was aborted.', undefined); - } -} - -export class APIConnectionError extends APIError { - constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) { - super(undefined, undefined, message || 'Connection error.', undefined); - // in some environments the 'cause' property is already declared - // @ts-ignore - if (cause) this.cause = cause; - } -} - -export class APIConnectionTimeoutError extends APIConnectionError { - constructor({ message }: { message?: string } = {}) { - super({ message: message ?? 'Request timed out.' }); - } -} - -export class BadRequestError extends APIError<400, Headers> {} - -export class AuthenticationError extends APIError<401, Headers> {} - -export class PermissionDeniedError extends APIError<403, Headers> {} - -export class NotFoundError extends APIError<404, Headers> {} - -export class ConflictError extends APIError<409, Headers> {} - -export class UnprocessableEntityError extends APIError<422, Headers> {} - -export class RateLimitError extends APIError<429, Headers> {} - -export class InternalServerError extends APIError {} +/** @deprecated Import from ./core/error instead */ +export * from './core/error'; diff --git a/src/index.ts b/src/index.ts index 000fafb69..14ae8b5f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,51 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { type Agent } from './_shims/index'; -import * as qs from './internal/qs'; -import * as Core from './core'; -import * as Errors from './error'; -import * as Pagination from './pagination'; -import { - type IndividualsPageParams, - IndividualsPageResponse, - type PageParams, - PageResponse, - ResponsesPageResponse, - SinglePageResponse, -} from './pagination'; -import * as Uploads from './uploads'; -import * as API from './resources/index'; -import { AccessTokenCreateParams, AccessTokens, CreateAccessTokenResponse } from './resources/access-tokens'; -import { Account, DisconnectResponse, Introspection } from './resources/account'; -import { - Provider, - ProviderListResponse, - ProviderListResponsesSinglePage, - Providers, -} from './resources/providers'; -import { - RequestForwarding, - RequestForwardingForwardParams, - RequestForwardingForwardResponse, -} from './resources/request-forwarding'; -import { - AccountUpdateEvent, - BaseWebhookEvent, - CompanyEvent, - DirectoryEvent, - EmploymentEvent, - IndividualEvent, - JobCompletionEvent, - PayStatementEvent, - PaymentEvent, - WebhookEvent, - Webhooks, -} from './resources/webhooks'; -import { Connect } from './resources/connect/connect'; -import { HRIS, Income, Location, Money } from './resources/hris/hris'; -import { Jobs } from './resources/jobs/jobs'; -import { Payroll } from './resources/payroll/payroll'; -import { Sandbox } from './resources/sandbox/sandbox'; +export { Finch as default } from './client'; export interface ClientOptions { accessToken?: string | null | undefined; @@ -462,6 +417,4 @@ export { InternalServerError, PermissionDeniedError, UnprocessableEntityError, -} from './error'; - -export default Finch; +} from './core/error'; diff --git a/src/internal/README.md b/src/internal/README.md new file mode 100644 index 000000000..3ef5a25ba --- /dev/null +++ b/src/internal/README.md @@ -0,0 +1,3 @@ +# `internal` + +The modules in this directory are not importable outside this package and will change between releases. diff --git a/src/internal/builtin-types.ts b/src/internal/builtin-types.ts new file mode 100644 index 000000000..c23d3bded --- /dev/null +++ b/src/internal/builtin-types.ts @@ -0,0 +1,93 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise; + +/** + * An alias to the builtin `RequestInit` type so we can + * easily alias it in import statements if there are name clashes. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit + */ +type _RequestInit = RequestInit; + +/** + * An alias to the builtin `Response` type so we can + * easily alias it in import statements if there are name clashes. + * + * https://developer.mozilla.org/docs/Web/API/Response + */ +type _Response = Response; + +/** + * The type for the first argument to `fetch`. + * + * https://developer.mozilla.org/docs/Web/API/Window/fetch#resource + */ +type _RequestInfo = Request | URL | string; + +/** + * The type for constructing `RequestInit` Headers. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit#setting_headers + */ +type _HeadersInit = RequestInit['headers']; + +/** + * The type for constructing `RequestInit` body. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit#body + */ +type _BodyInit = RequestInit['body']; + +/** + * An alias to the builtin `Array` type so we can + * easily alias it in import statements if there are name clashes. + */ +type _Array = Array; + +/** + * An alias to the builtin `Record` type so we can + * easily alias it in import statements if there are name clashes. + */ +type _Record = Record; + +export type { + _Array as Array, + _BodyInit as BodyInit, + _HeadersInit as HeadersInit, + _Record as Record, + _RequestInfo as RequestInfo, + _RequestInit as RequestInit, + _Response as Response, +}; + +/** + * A copy of the builtin `EndingType` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L27941 + */ +type EndingType = 'native' | 'transparent'; + +/** + * A copy of the builtin `BlobPropertyBag` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L154 + * https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#options + */ +export interface BlobPropertyBag { + endings?: EndingType; + type?: string; +} + +/** + * A copy of the builtin `FilePropertyBag` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L503 + * https://developer.mozilla.org/en-US/docs/Web/API/File/File#options + */ +export interface FilePropertyBag extends BlobPropertyBag { + lastModified?: number; +} diff --git a/src/internal/detect-platform.ts b/src/internal/detect-platform.ts new file mode 100644 index 000000000..e82d95c92 --- /dev/null +++ b/src/internal/detect-platform.ts @@ -0,0 +1,196 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { VERSION } from '../version'; + +export const isRunningInBrowser = () => { + return ( + // @ts-ignore + typeof window !== 'undefined' && + // @ts-ignore + typeof window.document !== 'undefined' && + // @ts-ignore + typeof navigator !== 'undefined' + ); +}; + +type DetectedPlatform = 'deno' | 'node' | 'edge' | 'unknown'; + +/** + * Note this does not detect 'browser'; for that, use getBrowserInfo(). + */ +function getDetectedPlatform(): DetectedPlatform { + if (typeof Deno !== 'undefined' && Deno.build != null) { + return 'deno'; + } + if (typeof EdgeRuntime !== 'undefined') { + return 'edge'; + } + if ( + Object.prototype.toString.call( + typeof (globalThis as any).process !== 'undefined' ? (globalThis as any).process : 0, + ) === '[object process]' + ) { + return 'node'; + } + return 'unknown'; +} + +declare const Deno: any; +declare const EdgeRuntime: any; +type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown'; +type PlatformName = + | 'MacOS' + | 'Linux' + | 'Windows' + | 'FreeBSD' + | 'OpenBSD' + | 'iOS' + | 'Android' + | `Other:${string}` + | 'Unknown'; +type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari'; +type PlatformProperties = { + 'X-Stainless-Lang': 'js'; + 'X-Stainless-Package-Version': string; + 'X-Stainless-OS': PlatformName; + 'X-Stainless-Arch': Arch; + 'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown'; + 'X-Stainless-Runtime-Version': string; +}; +const getPlatformProperties = (): PlatformProperties => { + const detectedPlatform = getDetectedPlatform(); + if (detectedPlatform === 'deno') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': normalizePlatform(Deno.build.os), + 'X-Stainless-Arch': normalizeArch(Deno.build.arch), + 'X-Stainless-Runtime': 'deno', + 'X-Stainless-Runtime-Version': + typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown', + }; + } + if (typeof EdgeRuntime !== 'undefined') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': `other:${EdgeRuntime}`, + 'X-Stainless-Runtime': 'edge', + 'X-Stainless-Runtime-Version': (globalThis as any).process.version, + }; + } + // Check if Node.js + if (detectedPlatform === 'node') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': normalizePlatform((globalThis as any).process.platform ?? 'unknown'), + 'X-Stainless-Arch': normalizeArch((globalThis as any).process.arch ?? 'unknown'), + 'X-Stainless-Runtime': 'node', + 'X-Stainless-Runtime-Version': (globalThis as any).process.version ?? 'unknown', + }; + } + + const browserInfo = getBrowserInfo(); + if (browserInfo) { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': 'unknown', + 'X-Stainless-Runtime': `browser:${browserInfo.browser}`, + 'X-Stainless-Runtime-Version': browserInfo.version, + }; + } + + // TODO add support for Cloudflare workers, etc. + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': 'unknown', + 'X-Stainless-Runtime': 'unknown', + 'X-Stainless-Runtime-Version': 'unknown', + }; +}; + +type BrowserInfo = { + browser: Browser; + version: string; +}; + +declare const navigator: { userAgent: string } | undefined; + +// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts +function getBrowserInfo(): BrowserInfo | null { + if (typeof navigator === 'undefined' || !navigator) { + return null; + } + + // NOTE: The order matters here! + const browserPatterns = [ + { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ }, + ]; + + // Find the FIRST matching browser + for (const { key, pattern } of browserPatterns) { + const match = pattern.exec(navigator.userAgent); + if (match) { + const major = match[1] || 0; + const minor = match[2] || 0; + const patch = match[3] || 0; + + return { browser: key, version: `${major}.${minor}.${patch}` }; + } + } + + return null; +} + +const normalizeArch = (arch: string): Arch => { + // Node docs: + // - https://nodejs.org/api/process.html#processarch + // Deno docs: + // - https://doc.deno.land/deno/stable/~/Deno.build + if (arch === 'x32') return 'x32'; + if (arch === 'x86_64' || arch === 'x64') return 'x64'; + if (arch === 'arm') return 'arm'; + if (arch === 'aarch64' || arch === 'arm64') return 'arm64'; + if (arch) return `other:${arch}`; + return 'unknown'; +}; + +const normalizePlatform = (platform: string): PlatformName => { + // Node platforms: + // - https://nodejs.org/api/process.html#processplatform + // Deno platforms: + // - https://doc.deno.land/deno/stable/~/Deno.build + // - https://github.com/denoland/deno/issues/14799 + + platform = platform.toLowerCase(); + + // NOTE: this iOS check is untested and may not work + // Node does not work natively on IOS, there is a fork at + // https://github.com/nodejs-mobile/nodejs-mobile + // however it is unknown at the time of writing how to detect if it is running + if (platform.includes('ios')) return 'iOS'; + if (platform === 'android') return 'Android'; + if (platform === 'darwin') return 'MacOS'; + if (platform === 'win32') return 'Windows'; + if (platform === 'freebsd') return 'FreeBSD'; + if (platform === 'openbsd') return 'OpenBSD'; + if (platform === 'linux') return 'Linux'; + if (platform) return `Other:${platform}`; + return 'Unknown'; +}; + +let _platformHeaders: PlatformProperties; +export const getPlatformHeaders = () => { + return (_platformHeaders ??= getPlatformProperties()); +}; diff --git a/src/internal/errors.ts b/src/internal/errors.ts new file mode 100644 index 000000000..82c7b14d5 --- /dev/null +++ b/src/internal/errors.ts @@ -0,0 +1,33 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export function isAbortError(err: unknown) { + return ( + typeof err === 'object' && + err !== null && + // Spec-compliant fetch implementations + (('name' in err && (err as any).name === 'AbortError') || + // Expo fetch + ('message' in err && String((err as any).message).includes('FetchRequestCanceledException'))) + ); +} + +export const castToError = (err: any): Error => { + if (err instanceof Error) return err; + if (typeof err === 'object' && err !== null) { + try { + if (Object.prototype.toString.call(err) === '[object Error]') { + // @ts-ignore - not all envs have native support for cause yet + const error = new Error(err.message, err.cause ? { cause: err.cause } : {}); + if (err.stack) error.stack = err.stack; + // @ts-ignore - not all envs have native support for cause yet + if (err.cause && !error.cause) error.cause = err.cause; + if (err.name) error.name = err.name; + return error; + } + } catch {} + try { + return new Error(JSON.stringify(err)); + } catch {} + } + return new Error(err); +}; diff --git a/src/internal/headers.ts b/src/internal/headers.ts new file mode 100644 index 000000000..c724a9d22 --- /dev/null +++ b/src/internal/headers.ts @@ -0,0 +1,97 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isReadonlyArray } from './utils/values'; + +type HeaderValue = string | undefined | null; +export type HeadersLike = + | Headers + | readonly HeaderValue[][] + | Record + | undefined + | null + | NullableHeaders; + +const brand_privateNullableHeaders = /* @__PURE__ */ Symbol('brand.privateNullableHeaders'); + +/** + * @internal + * Users can pass explicit nulls to unset default headers. When we parse them + * into a standard headers type we need to preserve that information. + */ +export type NullableHeaders = { + /** Brand check, prevent users from creating a NullableHeaders. */ + [brand_privateNullableHeaders]: true; + /** Parsed headers. */ + values: Headers; + /** Set of lowercase header names explicitly set to null. */ + nulls: Set; +}; + +function* iterateHeaders(headers: HeadersLike): IterableIterator { + if (!headers) return; + + if (brand_privateNullableHeaders in headers) { + const { values, nulls } = headers; + yield* values.entries(); + for (const name of nulls) { + yield [name, null]; + } + return; + } + + let shouldClear = false; + let iter: Iterable; + if (headers instanceof Headers) { + iter = headers.entries(); + } else if (isReadonlyArray(headers)) { + iter = headers; + } else { + shouldClear = true; + iter = Object.entries(headers ?? {}); + } + for (let row of iter) { + const name = row[0]; + if (typeof name !== 'string') throw new TypeError('expected header name to be a string'); + const values = isReadonlyArray(row[1]) ? row[1] : [row[1]]; + let didClear = false; + for (const value of values) { + if (value === undefined) continue; + + // Objects keys always overwrite older headers, they never append. + // Yield a null to clear the header before adding the new values. + if (shouldClear && !didClear) { + didClear = true; + yield [name, null]; + } + yield [name, value]; + } + } +} + +export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => { + const targetHeaders = new Headers(); + const nullHeaders = new Set(); + for (const headers of newHeaders) { + const seenHeaders = new Set(); + for (const [name, value] of iterateHeaders(headers)) { + const lowerName = name.toLowerCase(); + if (!seenHeaders.has(lowerName)) { + targetHeaders.delete(name); + seenHeaders.add(lowerName); + } + if (value === null) { + targetHeaders.delete(name); + nullHeaders.add(lowerName); + } else { + targetHeaders.append(name, value); + nullHeaders.delete(lowerName); + } + } + } + return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders }; +}; + +export const isEmptyHeaders = (headers: HeadersLike) => { + for (const _ of iterateHeaders(headers)) return false; + return true; +}; diff --git a/src/internal/parse.ts b/src/internal/parse.ts new file mode 100644 index 000000000..19d4f9dbc --- /dev/null +++ b/src/internal/parse.ts @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import type { FinalRequestOptions } from './request-options'; +import { type Finch } from '../client'; +import { formatRequestDetails, loggerFor } from './utils/log'; + +export type APIResponseProps = { + response: Response; + options: FinalRequestOptions; + controller: AbortController; + requestLogID: string; + retryOfRequestLogID: string | undefined; + startTime: number; +}; + +export async function defaultParseResponse(client: Finch, props: APIResponseProps): Promise { + const { response, requestLogID, retryOfRequestLogID, startTime } = props; + const body = await (async () => { + // fetch refuses to read the body when the status code is 204. + if (response.status === 204) { + return null as T; + } + + if (props.options.__binaryResponse) { + return response as unknown as T; + } + + const contentType = response.headers.get('content-type'); + const mediaType = contentType?.split(';')[0]?.trim(); + const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json'); + if (isJSON) { + const json = await response.json(); + return json as T; + } + + const text = await response.text(); + return text as unknown as T; + })(); + loggerFor(client).debug( + `[${requestLogID}] response parsed`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + body, + durationMs: Date.now() - startTime, + }), + ); + return body; +} diff --git a/src/internal/qs/formats.ts b/src/internal/qs/formats.ts index 1cf9e2cde..e76a742f3 100644 --- a/src/internal/qs/formats.ts +++ b/src/internal/qs/formats.ts @@ -1,9 +1,10 @@ import type { Format } from './types'; export const default_format: Format = 'RFC3986'; +export const default_formatter = (v: PropertyKey) => String(v); export const formatters: Record string> = { RFC1738: (v: PropertyKey) => String(v).replace(/%20/g, '+'), - RFC3986: (v: PropertyKey) => String(v), + RFC3986: default_formatter, }; export const RFC1738 = 'RFC1738'; export const RFC3986 = 'RFC3986'; diff --git a/src/internal/qs/stringify.ts b/src/internal/qs/stringify.ts index 67497561a..7e71387f5 100644 --- a/src/internal/qs/stringify.ts +++ b/src/internal/qs/stringify.ts @@ -1,8 +1,7 @@ -import { encode, is_buffer, maybe_map } from './utils'; -import { default_format, formatters } from './formats'; +import { encode, is_buffer, maybe_map, has } from './utils'; +import { default_format, default_formatter, formatters } from './formats'; import type { NonNullableProperties, StringifyOptions } from './types'; - -const has = Object.prototype.hasOwnProperty; +import { isArray } from '../utils/values'; const array_prefix_generators = { brackets(prefix: PropertyKey) { @@ -17,13 +16,11 @@ const array_prefix_generators = { }, }; -const is_array = Array.isArray; -const push = Array.prototype.push; const push_to_array = function (arr: any[], value_or_array: any) { - push.apply(arr, is_array(value_or_array) ? value_or_array : [value_or_array]); + Array.prototype.push.apply(arr, isArray(value_or_array) ? value_or_array : [value_or_array]); }; -const to_ISO = Date.prototype.toISOString; +let toISOString; const defaults = { addQueryPrefix: false, @@ -38,11 +35,11 @@ const defaults = { encoder: encode, encodeValuesOnly: false, format: default_format, - formatter: formatters[default_format], + formatter: default_formatter, /** @deprecated */ indices: false, serializeDate(date) { - return to_ISO.call(date); + return (toISOString ??= Function.prototype.call.bind(Date.prototype.toISOString))(date); }, skipNulls: false, strictNullHandling: false, @@ -105,7 +102,7 @@ function inner_stringify( obj = filter(prefix, obj); } else if (obj instanceof Date) { obj = serializeDate?.(obj); - } else if (generateArrayPrefix === 'comma' && is_array(obj)) { + } else if (generateArrayPrefix === 'comma' && isArray(obj)) { obj = maybe_map(obj, function (value) { if (value instanceof Date) { return serializeDate?.(value); @@ -148,14 +145,14 @@ function inner_stringify( } let obj_keys; - if (generateArrayPrefix === 'comma' && is_array(obj)) { + if (generateArrayPrefix === 'comma' && isArray(obj)) { // we need to join elements in if (encodeValuesOnly && encoder) { // @ts-expect-error values only obj = maybe_map(obj, encoder); } obj_keys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }]; - } else if (is_array(filter)) { + } else if (isArray(filter)) { obj_keys = filter; } else { const keys = Object.keys(obj); @@ -165,9 +162,9 @@ function inner_stringify( const encoded_prefix = encodeDotInKeys ? String(prefix).replace(/\./g, '%2E') : String(prefix); const adjusted_prefix = - commaRoundTrip && is_array(obj) && obj.length === 1 ? encoded_prefix + '[]' : encoded_prefix; + commaRoundTrip && isArray(obj) && obj.length === 1 ? encoded_prefix + '[]' : encoded_prefix; - if (allowEmptyArrays && is_array(obj) && obj.length === 0) { + if (allowEmptyArrays && isArray(obj) && obj.length === 0) { return adjusted_prefix + '[]'; } @@ -184,7 +181,7 @@ function inner_stringify( // @ts-ignore const encoded_key = allowDots && encodeDotInKeys ? (key as any).replace(/\./g, '%2E') : key; const key_prefix = - is_array(obj) ? + isArray(obj) ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(adjusted_prefix, encoded_key) : adjusted_prefix @@ -205,7 +202,7 @@ function inner_stringify( skipNulls, encodeDotInKeys, // @ts-ignore - generateArrayPrefix === 'comma' && encodeValuesOnly && is_array(obj) ? null : encoder, + generateArrayPrefix === 'comma' && encodeValuesOnly && isArray(obj) ? null : encoder, filter, sort, allowDots, @@ -244,7 +241,7 @@ function normalize_stringify_options( let format = default_format; if (typeof opts.format !== 'undefined') { - if (!has.call(formatters, opts.format)) { + if (!has(formatters, opts.format)) { throw new TypeError('Unknown format option provided.'); } format = opts.format; @@ -252,7 +249,7 @@ function normalize_stringify_options( const formatter = formatters[format]; let filter = defaults.filter; - if (typeof opts.filter === 'function' || is_array(opts.filter)) { + if (typeof opts.filter === 'function' || isArray(opts.filter)) { filter = opts.filter; } @@ -316,7 +313,7 @@ export function stringify(object: any, opts: StringifyOptions = {}) { if (typeof options.filter === 'function') { filter = options.filter; obj = filter('', obj); - } else if (is_array(options.filter)) { + } else if (isArray(options.filter)) { filter = options.filter; obj_keys = filter; } diff --git a/src/internal/qs/utils.ts b/src/internal/qs/utils.ts index 113b18fb9..4cd56579c 100644 --- a/src/internal/qs/utils.ts +++ b/src/internal/qs/utils.ts @@ -1,10 +1,13 @@ import { RFC1738 } from './formats'; import type { DefaultEncoder, Format } from './types'; +import { isArray } from '../utils/values'; -const has = Object.prototype.hasOwnProperty; -const is_array = Array.isArray; +export let has = (obj: object, key: PropertyKey): boolean => ( + (has = (Object as any).hasOwn ?? Function.prototype.call.bind(Object.prototype.hasOwnProperty)), + has(obj, key) +); -const hex_table = (() => { +const hex_table = /* @__PURE__ */ (() => { const array = []; for (let i = 0; i < 256; ++i) { array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase()); @@ -20,7 +23,7 @@ function compact_queue>(queue: Array<{ obj: T; pro const obj = item.obj[item.prop]; - if (is_array(obj)) { + if (isArray(obj)) { const compacted: unknown[] = []; for (let j = 0; j < obj.length; ++j) { @@ -56,13 +59,10 @@ export function merge( } if (typeof source !== 'object') { - if (is_array(target)) { + if (isArray(target)) { target.push(source); } else if (target && typeof target === 'object') { - if ( - (options && (options.plainObjects || options.allowPrototypes)) || - !has.call(Object.prototype, source) - ) { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has(Object.prototype, source)) { target[source] = true; } } else { @@ -77,14 +77,14 @@ export function merge( } let mergeTarget = target; - if (is_array(target) && !is_array(source)) { + if (isArray(target) && !isArray(source)) { // @ts-ignore mergeTarget = array_to_object(target, options); } - if (is_array(target) && is_array(source)) { + if (isArray(target) && isArray(source)) { source.forEach(function (item, i) { - if (has.call(target, i)) { + if (has(target, i)) { const targetItem = target[i]; if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { target[i] = merge(targetItem, item, options); @@ -101,7 +101,7 @@ export function merge( return Object.keys(source).reduce(function (acc, key) { const value = source[key]; - if (has.call(acc, key)) { + if (has(acc, key)) { acc[key] = merge(acc[key], value, options); } else { acc[key] = value; @@ -254,7 +254,7 @@ export function combine(a: any, b: any) { } export function maybe_map(val: T[], fn: (v: T) => T) { - if (is_array(val)) { + if (isArray(val)) { const mapped = []; for (let i = 0; i < val.length; i += 1) { mapped.push(fn(val[i]!)); diff --git a/src/internal/request-options.ts b/src/internal/request-options.ts new file mode 100644 index 000000000..2aabf9aa3 --- /dev/null +++ b/src/internal/request-options.ts @@ -0,0 +1,91 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { NullableHeaders } from './headers'; + +import type { BodyInit } from './builtin-types'; +import type { HTTPMethod, MergedRequestInit } from './types'; +import { type HeadersLike } from './headers'; + +export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string }; + +export type RequestOptions = { + /** + * The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete'). + */ + method?: HTTPMethod; + + /** + * The URL path for the request. + * + * @example "/v1/foo" + */ + path?: string; + + /** + * Query parameters to include in the request URL. + */ + query?: object | undefined | null; + + /** + * The request body. Can be a string, JSON object, FormData, or other supported types. + */ + body?: unknown; + + /** + * HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples. + */ + headers?: HeadersLike; + + /** + * The maximum number of times that the client will retry a request in case of a + * temporary failure, like a network error or a 5XX error from the server. + * + * @default 2 + */ + maxRetries?: number; + + stream?: boolean | undefined; + + /** + * The maximum amount of time (in milliseconds) that the client should wait for a response + * from the server before timing out a single request. + * + * @unit milliseconds + */ + timeout?: number; + + /** + * Additional `RequestInit` options to be passed to the underlying `fetch` call. + * These options will be merged with the client's default fetch options. + */ + fetchOptions?: MergedRequestInit; + + /** + * An AbortSignal that can be used to cancel the request. + */ + signal?: AbortSignal | undefined | null; + + /** + * A unique key for this request to enable idempotency. + */ + idempotencyKey?: string; + + /** + * Override the default base URL for this specific request. + */ + defaultBaseURL?: string | undefined; + + __binaryResponse?: boolean | undefined; +}; + +export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit }; +export type RequestEncoder = (request: { headers: NullableHeaders; body: unknown }) => EncodedContent; + +export const FallbackEncoder: RequestEncoder = ({ headers, body }) => { + return { + bodyHeaders: { + 'content-type': 'application/json', + }, + body: JSON.stringify(body), + }; +}; diff --git a/src/internal/shim-types.ts b/src/internal/shim-types.ts new file mode 100644 index 000000000..8ddf7b0ad --- /dev/null +++ b/src/internal/shim-types.ts @@ -0,0 +1,26 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * Shims for types that we can't always rely on being available globally. + * + * Note: these only exist at the type-level, there is no corresponding runtime + * version for any of these symbols. + */ + +type NeverToAny = T extends never ? any : T; + +/** @ts-ignore */ +type _DOMReadableStream = globalThis.ReadableStream; + +/** @ts-ignore */ +type _NodeReadableStream = import('stream/web').ReadableStream; + +type _ConditionalNodeReadableStream = + typeof globalThis extends { ReadableStream: any } ? never : _NodeReadableStream; + +type _ReadableStream = NeverToAny< + | ([0] extends [1 & _DOMReadableStream] ? never : _DOMReadableStream) + | ([0] extends [1 & _ConditionalNodeReadableStream] ? never : _ConditionalNodeReadableStream) +>; + +export type { _ReadableStream as ReadableStream }; diff --git a/src/internal/shims.ts b/src/internal/shims.ts new file mode 100644 index 000000000..5c6104afb --- /dev/null +++ b/src/internal/shims.ts @@ -0,0 +1,107 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * This module provides internal shims and utility functions for environments where certain Node.js or global types may not be available. + * + * These are used to ensure we can provide a consistent behaviour between different JavaScript environments and good error + * messages in cases where an environment isn't fully supported. + */ + +import type { Fetch } from './builtin-types'; +import type { ReadableStream } from './shim-types'; + +export function getDefaultFetch(): Fetch { + if (typeof fetch !== 'undefined') { + return fetch as any; + } + + throw new Error( + '`fetch` is not defined as a global; Either pass `fetch` to the client, `new Finch({ fetch })` or polyfill the global, `globalThis.fetch = fetch`', + ); +} + +type ReadableStreamArgs = ConstructorParameters; + +export function makeReadableStream(...args: ReadableStreamArgs): ReadableStream { + const ReadableStream = (globalThis as any).ReadableStream; + if (typeof ReadableStream === 'undefined') { + // Note: All of the platforms / runtimes we officially support already define + // `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes. + throw new Error( + '`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`', + ); + } + + return new ReadableStream(...args); +} + +export function ReadableStreamFrom(iterable: Iterable | AsyncIterable): ReadableStream { + let iter: AsyncIterator | Iterator = + Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator](); + + return makeReadableStream({ + start() {}, + async pull(controller: any) { + const { done, value } = await iter.next(); + if (done) { + controller.close(); + } else { + controller.enqueue(value); + } + }, + async cancel() { + await iter.return?.(); + }, + }); +} + +/** + * Most browsers don't yet have async iterable support for ReadableStream, + * and Node has a very different way of reading bytes from its "ReadableStream". + * + * This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490 + */ +export function ReadableStreamToAsyncIterable(stream: any): AsyncIterableIterator { + if (stream[Symbol.asyncIterator]) return stream; + + const reader = stream.getReader(); + return { + async next() { + try { + const result = await reader.read(); + if (result?.done) reader.releaseLock(); // release lock when stream becomes closed + return result; + } catch (e) { + reader.releaseLock(); // release lock when stream becomes errored + throw e; + } + }, + async return() { + const cancelPromise = reader.cancel(); + reader.releaseLock(); + await cancelPromise; + return { done: true, value: undefined }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; +} + +/** + * Cancels a ReadableStream we don't need to consume. + * See https://undici.nodejs.org/#/?id=garbage-collection + */ +export async function CancelReadableStream(stream: any): Promise { + if (stream === null || typeof stream !== 'object') return; + + if (stream[Symbol.asyncIterator]) { + await stream[Symbol.asyncIterator]().return?.(); + return; + } + + const reader = stream.getReader(); + const cancelPromise = reader.cancel(); + reader.releaseLock(); + await cancelPromise; +} diff --git a/src/internal/to-file.ts b/src/internal/to-file.ts new file mode 100644 index 000000000..30eada32c --- /dev/null +++ b/src/internal/to-file.ts @@ -0,0 +1,154 @@ +import { BlobPart, getName, makeFile, isAsyncIterable } from './uploads'; +import type { FilePropertyBag } from './builtin-types'; +import { checkFileSupport } from './uploads'; + +type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | DataView; + +/** + * Intended to match DOM Blob, node-fetch Blob, node:buffer Blob, etc. + * Don't add arrayBuffer here, node-fetch doesn't have it + */ +interface BlobLike { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ + readonly size: number; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ + readonly type: string; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ + text(): Promise; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ + slice(start?: number, end?: number): BlobLike; +} + +/** + * This check adds the arrayBuffer() method type because it is available and used at runtime + */ +const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => + value != null && + typeof value === 'object' && + typeof value.size === 'number' && + typeof value.type === 'string' && + typeof value.text === 'function' && + typeof value.slice === 'function' && + typeof value.arrayBuffer === 'function'; + +/** + * Intended to match DOM File, node:buffer File, undici File, etc. + */ +interface FileLike extends BlobLike { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ + readonly lastModified: number; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ + readonly name?: string | undefined; +} + +/** + * This check adds the arrayBuffer() method type because it is available and used at runtime + */ +const isFileLike = (value: any): value is FileLike & { arrayBuffer(): Promise } => + value != null && + typeof value === 'object' && + typeof value.name === 'string' && + typeof value.lastModified === 'number' && + isBlobLike(value); + +/** + * Intended to match DOM Response, node-fetch Response, undici Response, etc. + */ +export interface ResponseLike { + url: string; + blob(): Promise; +} + +const isResponseLike = (value: any): value is ResponseLike => + value != null && + typeof value === 'object' && + typeof value.url === 'string' && + typeof value.blob === 'function'; + +export type ToFileInput = + | FileLike + | ResponseLike + | Exclude + | AsyncIterable; + +/** + * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats + * @param value the raw content of the file. Can be an {@link Uploadable}, BlobLikePart, or AsyncIterable of BlobLikeParts + * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible + * @param {Object=} options additional properties + * @param {string=} options.type the MIME type of the content + * @param {number=} options.lastModified the last modified timestamp + * @returns a {@link File} with the given properties + */ +export async function toFile( + value: ToFileInput | PromiseLike, + name?: string | null | undefined, + options?: FilePropertyBag | undefined, +): Promise { + checkFileSupport(); + + // If it's a promise, resolve it. + value = await value; + + // If we've been given a `File` we don't need to do anything + if (isFileLike(value)) { + if (value instanceof File) { + return value; + } + return makeFile([await value.arrayBuffer()], value.name); + } + + if (isResponseLike(value)) { + const blob = await value.blob(); + name ||= new URL(value.url).pathname.split(/[\\/]/).pop(); + + return makeFile(await getBytes(blob), name, options); + } + + const parts = await getBytes(value); + + name ||= getName(value); + + if (!options?.type) { + const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type); + if (typeof type === 'string') { + options = { ...options, type }; + } + } + + return makeFile(parts, name, options); +} + +async function getBytes(value: BlobLikePart | AsyncIterable): Promise> { + let parts: Array = []; + if ( + typeof value === 'string' || + ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc. + value instanceof ArrayBuffer + ) { + parts.push(value); + } else if (isBlobLike(value)) { + parts.push(value instanceof Blob ? value : await value.arrayBuffer()); + } else if ( + isAsyncIterable(value) // includes Readable, ReadableStream, etc. + ) { + for await (const chunk of value) { + parts.push(...(await getBytes(chunk as BlobLikePart))); // TODO, consider validating? + } + } else { + const constructor = value?.constructor?.name; + throw new Error( + `Unexpected data type: ${typeof value}${ + constructor ? `; constructor: ${constructor}` : '' + }${propsForError(value)}`, + ); + } + + return parts; +} + +function propsForError(value: unknown): string { + if (typeof value !== 'object' || value === null) return ''; + const props = Object.getOwnPropertyNames(value); + return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`; +} diff --git a/src/internal/types.ts b/src/internal/types.ts new file mode 100644 index 000000000..b668dfc0f --- /dev/null +++ b/src/internal/types.ts @@ -0,0 +1,95 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export type PromiseOrValue = T | Promise; +export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'; + +export type KeysEnum = { [P in keyof Required]: true }; + +export type FinalizedRequestInit = RequestInit & { headers: Headers }; + +type NotAny = [0] extends [1 & T] ? never : T; + +/** + * Some environments overload the global fetch function, and Parameters only gets the last signature. + */ +type OverloadedParameters = + T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + (...args: infer C): unknown; + (...args: infer D): unknown; + } + ) ? + A | B | C | D + : T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + (...args: infer C): unknown; + } + ) ? + A | B | C + : T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + } + ) ? + A | B + : T extends (...args: infer A) => unknown ? A + : never; + +/* eslint-disable */ +/** + * These imports attempt to get types from a parent package's dependencies. + * Unresolved bare specifiers can trigger [automatic type acquisition][1] in some projects, which + * would cause typescript to show types not present at runtime. To avoid this, we import + * directly from parent node_modules folders. + * + * We need to check multiple levels because we don't know what directory structure we'll be in. + * For example, pnpm generates directories like this: + * ``` + * node_modules + * ├── .pnpm + * │ └── pkg@1.0.0 + * │ └── node_modules + * │ └── pkg + * │ └── internal + * │ └── types.d.ts + * ├── pkg -> .pnpm/pkg@1.0.0/node_modules/pkg + * └── undici + * ``` + * + * [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition + */ +/** @ts-ignore For users with \@types/node */ +type UndiciTypesRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with undici */ +type UndiciRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with \@types/bun */ +type BunRequestInit = globalThis.FetchRequestInit; +/** @ts-ignore For users with node-fetch@2 */ +type NodeFetch2RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with node-fetch@3, doesn't need file extension because types are at ./@types/index.d.ts */ +type NodeFetch3RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users who use Deno */ +type FetchRequestInit = NonNullable[1]>; +/* eslint-enable */ + +type RequestInits = + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny; + +/** + * This type contains `RequestInit` options that may be available on the current runtime, + * including per-platform extensions like `dispatcher`, `agent`, `client`, etc. + */ +export type MergedRequestInit = RequestInits & + /** We don't include these in the types as they'll be overridden for every request. */ + Partial>; diff --git a/src/internal/uploads.ts b/src/internal/uploads.ts new file mode 100644 index 000000000..a85a3e589 --- /dev/null +++ b/src/internal/uploads.ts @@ -0,0 +1,187 @@ +import { type RequestOptions } from './request-options'; +import type { FilePropertyBag, Fetch } from './builtin-types'; +import type { Finch } from '../client'; +import { ReadableStreamFrom } from './shims'; + +export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | DataView; +type FsReadStream = AsyncIterable & { path: string | { toString(): string } }; + +// https://github.com/oven-sh/bun/issues/5980 +interface BunFile extends Blob { + readonly name?: string | undefined; +} + +export const checkFileSupport = () => { + if (typeof File === 'undefined') { + const { process } = globalThis as any; + const isOldNode = + typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20; + throw new Error( + '`File` is not defined as a global, which is required for file uploads.' + + (isOldNode ? + " Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`." + : ''), + ); + } +}; + +/** + * Typically, this is a native "File" class. + * + * We provide the {@link toFile} utility to convert a variety of objects + * into the File class. + * + * For convenience, you can also pass a fetch Response, or in Node, + * the result of fs.createReadStream(). + */ +export type Uploadable = File | Response | FsReadStream | BunFile; + +/** + * Construct a `File` instance. This is used to ensure a helpful error is thrown + * for environments that don't define a global `File` yet. + */ +export function makeFile( + fileBits: BlobPart[], + fileName: string | undefined, + options?: FilePropertyBag, +): File { + checkFileSupport(); + return new File(fileBits as any, fileName ?? 'unknown_file', options); +} + +export function getName(value: any): string | undefined { + return ( + ( + (typeof value === 'object' && + value !== null && + (('name' in value && value.name && String(value.name)) || + ('url' in value && value.url && String(value.url)) || + ('filename' in value && value.filename && String(value.filename)) || + ('path' in value && value.path && String(value.path)))) || + '' + ) + .split(/[\\/]/) + .pop() || undefined + ); +} + +export const isAsyncIterable = (value: any): value is AsyncIterable => + value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; + +/** + * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value. + * Otherwise returns the request as is. + */ +export const maybeMultipartFormRequestOptions = async ( + opts: RequestOptions, + fetch: Finch | Fetch, +): Promise => { + if (!hasUploadableValue(opts.body)) return opts; + + return { ...opts, body: await createForm(opts.body, fetch) }; +}; + +type MultipartFormRequestOptions = Omit & { body: unknown }; + +export const multipartFormRequestOptions = async ( + opts: MultipartFormRequestOptions, + fetch: Finch | Fetch, +): Promise => { + return { ...opts, body: await createForm(opts.body, fetch) }; +}; + +const supportsFormDataMap = /* @__PURE__ */ new WeakMap>(); + +/** + * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending + * properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]". + * This function detects if the fetch function provided supports the global FormData object to avoid + * confusing error messages later on. + */ +function supportsFormData(fetchObject: Finch | Fetch): Promise { + const fetch: Fetch = typeof fetchObject === 'function' ? fetchObject : (fetchObject as any).fetch; + const cached = supportsFormDataMap.get(fetch); + if (cached) return cached; + const promise = (async () => { + try { + const FetchResponse = ( + 'Response' in fetch ? + fetch.Response + : (await fetch('data:,')).constructor) as typeof Response; + const data = new FormData(); + if (data.toString() === (await new FetchResponse(data).text())) { + return false; + } + return true; + } catch { + // avoid false negatives + return true; + } + })(); + supportsFormDataMap.set(fetch, promise); + return promise; +} + +export const createForm = async >( + body: T | undefined, + fetch: Finch | Fetch, +): Promise => { + if (!(await supportsFormData(fetch))) { + throw new TypeError( + 'The provided fetch function does not support file uploads with the current global FormData class.', + ); + } + const form = new FormData(); + await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))); + return form; +}; + +// We check for Blob not File because Bun.File doesn't inherit from File, +// but they both inherit from Blob and have a `name` property at runtime. +const isNamedBlob = (value: unknown) => value instanceof Blob && 'name' in value; + +const isUploadable = (value: unknown) => + typeof value === 'object' && + value !== null && + (value instanceof Response || isAsyncIterable(value) || isNamedBlob(value)); + +const hasUploadableValue = (value: unknown): boolean => { + if (isUploadable(value)) return true; + if (Array.isArray(value)) return value.some(hasUploadableValue); + if (value && typeof value === 'object') { + for (const k in value) { + if (hasUploadableValue((value as any)[k])) return true; + } + } + return false; +}; + +const addFormValue = async (form: FormData, key: string, value: unknown): Promise => { + if (value === undefined) return; + if (value == null) { + throw new TypeError( + `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`, + ); + } + + // TODO: make nested formats configurable + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + form.append(key, String(value)); + } else if (value instanceof Response) { + form.append(key, makeFile([await value.blob()], getName(value))); + } else if (isAsyncIterable(value)) { + form.append(key, makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value))); + } else if (isNamedBlob(value)) { + form.append(key, value, getName(value)); + } else if (Array.isArray(value)) { + await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); + } else if (typeof value === 'object') { + await Promise.all( + Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), + ); + } else { + throw new TypeError( + `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`, + ); + } +}; diff --git a/src/internal/utils.ts b/src/internal/utils.ts new file mode 100644 index 000000000..3cbfacce2 --- /dev/null +++ b/src/internal/utils.ts @@ -0,0 +1,8 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './utils/values'; +export * from './utils/base64'; +export * from './utils/env'; +export * from './utils/log'; +export * from './utils/uuid'; +export * from './utils/sleep'; diff --git a/src/internal/utils/base64.ts b/src/internal/utils/base64.ts new file mode 100644 index 000000000..1ade99daf --- /dev/null +++ b/src/internal/utils/base64.ts @@ -0,0 +1,40 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { FinchError } from '../../core/error'; +import { encodeUTF8 } from './bytes'; + +export const toBase64 = (data: string | Uint8Array | null | undefined): string => { + if (!data) return ''; + + if (typeof (globalThis as any).Buffer !== 'undefined') { + return (globalThis as any).Buffer.from(data).toString('base64'); + } + + if (typeof data === 'string') { + data = encodeUTF8(data); + } + + if (typeof btoa !== 'undefined') { + return btoa(String.fromCharCode.apply(null, data as any)); + } + + throw new FinchError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined'); +}; + +export const fromBase64 = (str: string): Uint8Array => { + if (typeof (globalThis as any).Buffer !== 'undefined') { + const buf = (globalThis as any).Buffer.from(str, 'base64'); + return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); + } + + if (typeof atob !== 'undefined') { + const bstr = atob(str); + const buf = new Uint8Array(bstr.length); + for (let i = 0; i < bstr.length; i++) { + buf[i] = bstr.charCodeAt(i); + } + return buf; + } + + throw new FinchError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined'); +}; diff --git a/src/internal/utils/bytes.ts b/src/internal/utils/bytes.ts new file mode 100644 index 000000000..8da627abe --- /dev/null +++ b/src/internal/utils/bytes.ts @@ -0,0 +1,32 @@ +export function concatBytes(buffers: Uint8Array[]): Uint8Array { + let length = 0; + for (const buffer of buffers) { + length += buffer.length; + } + const output = new Uint8Array(length); + let index = 0; + for (const buffer of buffers) { + output.set(buffer, index); + index += buffer.length; + } + + return output; +} + +let encodeUTF8_: (str: string) => Uint8Array; +export function encodeUTF8(str: string) { + let encoder; + return ( + encodeUTF8_ ?? + ((encoder = new (globalThis as any).TextEncoder()), (encodeUTF8_ = encoder.encode.bind(encoder))) + )(str); +} + +let decodeUTF8_: (bytes: Uint8Array) => string; +export function decodeUTF8(bytes: Uint8Array) { + let decoder; + return ( + decodeUTF8_ ?? + ((decoder = new (globalThis as any).TextDecoder()), (decodeUTF8_ = decoder.decode.bind(decoder))) + )(bytes); +} diff --git a/src/internal/utils/env.ts b/src/internal/utils/env.ts new file mode 100644 index 000000000..2d8480077 --- /dev/null +++ b/src/internal/utils/env.ts @@ -0,0 +1,18 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * Read an environment variable. + * + * Trims beginning and trailing whitespace. + * + * Will return undefined if the environment variable doesn't exist or cannot be accessed. + */ +export const readEnv = (env: string): string | undefined => { + if (typeof (globalThis as any).process !== 'undefined') { + return (globalThis as any).process.env?.[env]?.trim() ?? undefined; + } + if (typeof (globalThis as any).Deno !== 'undefined') { + return (globalThis as any).Deno.env?.get?.(env)?.trim(); + } + return undefined; +}; diff --git a/src/internal/utils/log.ts b/src/internal/utils/log.ts new file mode 100644 index 000000000..55eb21e30 --- /dev/null +++ b/src/internal/utils/log.ts @@ -0,0 +1,126 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { hasOwn } from './values'; +import { type Finch } from '../../client'; +import { RequestOptions } from '../request-options'; + +type LogFn = (message: string, ...rest: unknown[]) => void; +export type Logger = { + error: LogFn; + warn: LogFn; + info: LogFn; + debug: LogFn; +}; +export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug'; + +const levelNumbers = { + off: 0, + error: 200, + warn: 300, + info: 400, + debug: 500, +}; + +export const parseLogLevel = ( + maybeLevel: string | undefined, + sourceName: string, + client: Finch, +): LogLevel | undefined => { + if (!maybeLevel) { + return undefined; + } + if (hasOwn(levelNumbers, maybeLevel)) { + return maybeLevel; + } + loggerFor(client).warn( + `${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify( + Object.keys(levelNumbers), + )}`, + ); + return undefined; +}; + +function noop() {} + +function makeLogFn(fnLevel: keyof Logger, logger: Logger | undefined, logLevel: LogLevel) { + if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) { + return noop; + } else { + // Don't wrap logger functions, we want the stacktrace intact! + return logger[fnLevel].bind(logger); + } +} + +const noopLogger = { + error: noop, + warn: noop, + info: noop, + debug: noop, +}; + +let cachedLoggers = /* @__PURE__ */ new WeakMap(); + +export function loggerFor(client: Finch): Logger { + const logger = client.logger; + const logLevel = client.logLevel ?? 'off'; + if (!logger) { + return noopLogger; + } + + const cachedLogger = cachedLoggers.get(logger); + if (cachedLogger && cachedLogger[0] === logLevel) { + return cachedLogger[1]; + } + + const levelLogger = { + error: makeLogFn('error', logger, logLevel), + warn: makeLogFn('warn', logger, logLevel), + info: makeLogFn('info', logger, logLevel), + debug: makeLogFn('debug', logger, logLevel), + }; + + cachedLoggers.set(logger, [logLevel, levelLogger]); + + return levelLogger; +} + +export const formatRequestDetails = (details: { + options?: RequestOptions | undefined; + headers?: Headers | Record | undefined; + retryOfRequestLogID?: string | undefined; + retryOf?: string | undefined; + url?: string | undefined; + status?: number | undefined; + method?: string | undefined; + durationMs?: number | undefined; + message?: unknown; + body?: unknown; +}) => { + if (details.options) { + details.options = { ...details.options }; + delete details.options['headers']; // redundant + leaks internals + } + if (details.headers) { + details.headers = Object.fromEntries( + (details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map( + ([name, value]) => [ + name, + ( + name.toLowerCase() === 'authorization' || + name.toLowerCase() === 'cookie' || + name.toLowerCase() === 'set-cookie' + ) ? + '***' + : value, + ], + ), + ); + } + if ('retryOfRequestLogID' in details) { + if (details.retryOfRequestLogID) { + details.retryOf = details.retryOfRequestLogID; + } + delete details.retryOfRequestLogID; + } + return details; +}; diff --git a/src/internal/utils/path.ts b/src/internal/utils/path.ts new file mode 100644 index 000000000..a57c7c346 --- /dev/null +++ b/src/internal/utils/path.ts @@ -0,0 +1,88 @@ +import { FinchError } from '../../core/error'; + +/** + * Percent-encode everything that isn't safe to have in a path without encoding safe chars. + * + * Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3: + * > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * > sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + * > pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + */ +export function encodeURIPath(str: string) { + return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent); +} + +const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null)); + +export const createPathTagFunction = (pathEncoder = encodeURIPath) => + function path(statics: readonly string[], ...params: readonly unknown[]): string { + // If there are no params, no processing is needed. + if (statics.length === 1) return statics[0]!; + + let postPath = false; + const invalidSegments = []; + const path = statics.reduce((previousValue, currentValue, index) => { + if (/[?#]/.test(currentValue)) { + postPath = true; + } + const value = params[index]; + let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value); + if ( + index !== params.length && + (value == null || + (typeof value === 'object' && + // handle values from other realms + value.toString === + Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY) + ?.toString)) + ) { + encoded = value + ''; + invalidSegments.push({ + start: previousValue.length + currentValue.length, + length: encoded.length, + error: `Value of type ${Object.prototype.toString + .call(value) + .slice(8, -1)} is not a valid path parameter`, + }); + } + return previousValue + currentValue + (index === params.length ? '' : encoded); + }, ''); + + const pathOnly = path.split(/[?#]/, 1)[0]!; + const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi; + let match; + + // Find all invalid segments + while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) { + invalidSegments.push({ + start: match.index, + length: match[0].length, + error: `Value "${match[0]}" can\'t be safely passed as a path parameter`, + }); + } + + invalidSegments.sort((a, b) => a.start - b.start); + + if (invalidSegments.length > 0) { + let lastEnd = 0; + const underline = invalidSegments.reduce((acc, segment) => { + const spaces = ' '.repeat(segment.start - lastEnd); + const arrows = '^'.repeat(segment.length); + lastEnd = segment.start + segment.length; + return acc + spaces + arrows; + }, ''); + + throw new FinchError( + `Path parameters result in path with invalid segments:\n${invalidSegments + .map((e) => e.error) + .join('\n')}\n${path}\n${underline}`, + ); + } + + return path; + }; + +/** + * URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced. + */ +export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath); diff --git a/src/internal/utils/sleep.ts b/src/internal/utils/sleep.ts new file mode 100644 index 000000000..65e52962b --- /dev/null +++ b/src/internal/utils/sleep.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/internal/utils/uuid.ts b/src/internal/utils/uuid.ts new file mode 100644 index 000000000..b0e53aaf7 --- /dev/null +++ b/src/internal/utils/uuid.ts @@ -0,0 +1,17 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * https://stackoverflow.com/a/2117523 + */ +export let uuid4 = function () { + const { crypto } = globalThis as any; + if (crypto?.randomUUID) { + uuid4 = crypto.randomUUID.bind(crypto); + return crypto.randomUUID(); + } + const u8 = new Uint8Array(1); + const randomByte = crypto ? () => crypto.getRandomValues(u8)[0]! : () => (Math.random() * 0xff) & 0xff; + return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => + (+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16), + ); +}; diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts new file mode 100644 index 000000000..9f053eb23 --- /dev/null +++ b/src/internal/utils/values.ts @@ -0,0 +1,105 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { FinchError } from '../../core/error'; + +// https://url.spec.whatwg.org/#url-scheme-string +const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i; + +export const isAbsoluteURL = (url: string): boolean => { + return startsWithSchemeRegexp.test(url); +}; + +export let isArray = (val: unknown): val is unknown[] => ((isArray = Array.isArray), isArray(val)); +export let isReadonlyArray = isArray as (val: unknown) => val is readonly unknown[]; + +/** Returns an object if the given value isn't an object, otherwise returns as-is */ +export function maybeObj(x: unknown): object { + if (typeof x !== 'object') { + return {}; + } + + return x ?? {}; +} + +// https://stackoverflow.com/a/34491287 +export function isEmptyObj(obj: Object | null | undefined): boolean { + if (!obj) return true; + for (const _k in obj) return false; + return true; +} + +// https://eslint.org/docs/latest/rules/no-prototype-builtins +export function hasOwn(obj: T, key: PropertyKey): key is keyof T { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +export function isObj(obj: unknown): obj is Record { + return obj != null && typeof obj === 'object' && !Array.isArray(obj); +} + +export const ensurePresent = (value: T | null | undefined): T => { + if (value == null) { + throw new FinchError(`Expected a value to be given but received ${value} instead.`); + } + + return value; +}; + +export const validatePositiveInteger = (name: string, n: unknown): number => { + if (typeof n !== 'number' || !Number.isInteger(n)) { + throw new FinchError(`${name} must be an integer`); + } + if (n < 0) { + throw new FinchError(`${name} must be a positive integer`); + } + return n; +}; + +export const coerceInteger = (value: unknown): number => { + if (typeof value === 'number') return Math.round(value); + if (typeof value === 'string') return parseInt(value, 10); + + throw new FinchError(`Could not coerce ${value} (type: ${typeof value}) into a number`); +}; + +export const coerceFloat = (value: unknown): number => { + if (typeof value === 'number') return value; + if (typeof value === 'string') return parseFloat(value); + + throw new FinchError(`Could not coerce ${value} (type: ${typeof value}) into a number`); +}; + +export const coerceBoolean = (value: unknown): boolean => { + if (typeof value === 'boolean') return value; + if (typeof value === 'string') return value === 'true'; + return Boolean(value); +}; + +export const maybeCoerceInteger = (value: unknown): number | undefined => { + if (value == null) { + return undefined; + } + return coerceInteger(value); +}; + +export const maybeCoerceFloat = (value: unknown): number | undefined => { + if (value == null) { + return undefined; + } + return coerceFloat(value); +}; + +export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { + if (value == null) { + return undefined; + } + return coerceBoolean(value); +}; + +export const safeJSON = (text: string) => { + try { + return JSON.parse(text); + } catch (err) { + return undefined; + } +}; diff --git a/src/pagination.ts b/src/pagination.ts index 3aed3e413..90bf015e1 100644 --- a/src/pagination.ts +++ b/src/pagination.ts @@ -1,213 +1,2 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { AbstractPage, Response, APIClient, FinalRequestOptions, PageInfo } from './core'; -import * as Shared from './resources/shared'; -import * as DirectoryAPI from './resources/hris/directory'; - -export type SinglePageResponse = Item[]; - -export class SinglePage extends AbstractPage { - items: Array; - - constructor( - client: APIClient, - response: Response, - body: SinglePageResponse, - options: FinalRequestOptions, - ) { - super(client, response, body, options); - - this.items = body || []; - } - - getPaginatedItems(): Item[] { - return this.items ?? []; - } - - // @deprecated Please use `nextPageInfo()` instead - /** - * This page represents a response that isn't actually paginated at the API level - * so there will never be any next page params. - */ - nextPageParams(): null { - return null; - } - - nextPageInfo(): null { - return null; - } -} - -export interface ResponsesPageResponse { - responses: Array; -} - -export class ResponsesPage extends AbstractPage implements ResponsesPageResponse { - responses: Array; - - constructor( - client: APIClient, - response: Response, - body: ResponsesPageResponse, - options: FinalRequestOptions, - ) { - super(client, response, body, options); - - this.responses = body.responses || []; - } - - getPaginatedItems(): Item[] { - return this.responses ?? []; - } - - // @deprecated Please use `nextPageInfo()` instead - /** - * This page represents a response that isn't actually paginated at the API level - * so there will never be any next page params. - */ - nextPageParams(): null { - return null; - } - - nextPageInfo(): null { - return null; - } -} - -export interface IndividualsPageResponse { - /** - * The array of employees. - */ - individuals: Array; - - paging: Shared.Paging; -} - -export interface IndividualsPageParams { - /** - * Number of employees to return (defaults to all) - */ - limit?: number; - - /** - * Index to start from (defaults to 0) - */ - offset?: number; -} - -export class IndividualsPage - extends AbstractPage - implements IndividualsPageResponse -{ - /** - * The array of employees. - */ - individuals: Array; - - paging: Shared.Paging; - - constructor( - client: APIClient, - response: Response, - body: IndividualsPageResponse, - options: FinalRequestOptions, - ) { - super(client, response, body, options); - - this.individuals = body.individuals || []; - this.paging = body.paging; - } - - getPaginatedItems(): DirectoryAPI.IndividualInDirectory[] { - return this.individuals ?? []; - } - - // @deprecated Please use `nextPageInfo()` instead - nextPageParams(): Partial | null { - const info = this.nextPageInfo(); - if (!info) return null; - if ('params' in info) return info.params; - const params = Object.fromEntries(info.url.searchParams); - if (!Object.keys(params).length) return null; - return params; - } - - nextPageInfo(): PageInfo | null { - const offset = this.paging.offset ?? 0; - const length = this.getPaginatedItems().length; - const currentCount = offset + length; - - const totalCount = this.paging.count; - if (!totalCount) { - return null; - } - - if (currentCount < totalCount) { - return { params: { offset: currentCount } }; - } - - return null; - } -} - -export interface PageResponse { - data: Array; - - paging: Shared.Paging; -} - -export interface PageParams { - /** - * Number of entries to return (defaults to all) - */ - limit?: number; - - /** - * Index to start from (defaults to 0) - */ - offset?: number; -} - -export class Page extends AbstractPage implements PageResponse { - data: Array; - - paging: Shared.Paging; - - constructor(client: APIClient, response: Response, body: PageResponse, options: FinalRequestOptions) { - super(client, response, body, options); - - this.data = body.data || []; - this.paging = body.paging; - } - - getPaginatedItems(): Item[] { - return this.data ?? []; - } - - // @deprecated Please use `nextPageInfo()` instead - nextPageParams(): Partial | null { - const info = this.nextPageInfo(); - if (!info) return null; - if ('params' in info) return info.params; - const params = Object.fromEntries(info.url.searchParams); - if (!Object.keys(params).length) return null; - return params; - } - - nextPageInfo(): PageInfo | null { - const offset = this.paging.offset ?? 0; - const length = this.getPaginatedItems().length; - const currentCount = offset + length; - - const totalCount = this.paging.count; - if (!totalCount) { - return null; - } - - if (currentCount < totalCount) { - return { params: { offset: currentCount } }; - } - - return null; - } -} +/** @deprecated Import from ./core/pagination instead */ +export * from './core/pagination'; diff --git a/src/resource.ts b/src/resource.ts index 673f09a71..363e3516b 100644 --- a/src/resource.ts +++ b/src/resource.ts @@ -1,11 +1,2 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import type { Finch } from './index'; - -export abstract class APIResource { - protected _client: Finch; - - constructor(client: Finch) { - this._client = client; - } -} +/** @deprecated Import from ./core/resource instead */ +export * from './core/resource'; diff --git a/src/resources/access-tokens.ts b/src/resources/access-tokens.ts index 586f6ff0b..651b61b2e 100644 --- a/src/resources/access-tokens.ts +++ b/src/resources/access-tokens.ts @@ -1,7 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../resource'; -import * as Core from '../core'; +import { APIResource } from '../core/resource'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; export class AccessTokens extends APIResource { /** diff --git a/src/resources/account.ts b/src/resources/account.ts index c6ff91247..91517e8fa 100644 --- a/src/resources/account.ts +++ b/src/resources/account.ts @@ -1,21 +1,22 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../resource'; -import * as Core from '../core'; +import { APIResource } from '../core/resource'; import * as Shared from './shared'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; export class Account extends APIResource { /** * Disconnect one or more `access_token`s from your application. */ - disconnect(options?: Core.RequestOptions): Core.APIPromise { + disconnect(options?: RequestOptions): APIPromise { return this._client.post('/disconnect', options); } /** * Read account information associated with an `access_token` */ - introspect(options?: Core.RequestOptions): Core.APIPromise { + introspect(options?: RequestOptions): APIPromise { return this._client.get('/introspect', options); } } diff --git a/src/resources/connect/connect.ts b/src/resources/connect/connect.ts index d0cb8b421..876dad049 100644 --- a/src/resources/connect/connect.ts +++ b/src/resources/connect/connect.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; +import { APIResource } from '../../core/resource'; import * as SessionsAPI from './sessions'; import { SessionNewParams, diff --git a/src/resources/connect/sessions.ts b/src/resources/connect/sessions.ts index 845de320d..b56c9a0c5 100644 --- a/src/resources/connect/sessions.ts +++ b/src/resources/connect/sessions.ts @@ -1,13 +1,14 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; export class Sessions extends APIResource { /** * Create a new connect session for an employer */ - new(body: SessionNewParams, options?: Core.RequestOptions): Core.APIPromise { + new(body: SessionNewParams, options?: RequestOptions): APIPromise { return this._client.post('/connect/sessions', { body, ...options }); } @@ -16,8 +17,8 @@ export class Sessions extends APIResource { */ reauthenticate( body: SessionReauthenticateParams, - options?: Core.RequestOptions, - ): Core.APIPromise { + options?: RequestOptions, + ): APIPromise { return this._client.post('/connect/sessions/reauthenticate', { body, ...options }); } } diff --git a/src/resources/hris/benefits/benefits.ts b/src/resources/hris/benefits/benefits.ts index 3024393ba..82a088d77 100644 --- a/src/resources/hris/benefits/benefits.ts +++ b/src/resources/hris/benefits/benefits.ts @@ -1,8 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../../resource'; -import { isRequestOptions } from '../../../core'; -import * as Core from '../../../core'; +import { APIResource } from '../../../core/resource'; import * as Shared from '../../shared'; import * as IndividualsAPI from './individuals'; import { @@ -17,7 +15,10 @@ import { Individuals, UnenrolledIndividualBenefitResponse, } from './individuals'; -import { SinglePage } from '../../../pagination'; +import { APIPromise } from '../../../core/api-promise'; +import { PagePromise, SinglePage } from '../../../core/pagination'; +import { RequestOptions } from '../../../internal/request-options'; +import { path } from '../../../internal/utils/path'; export class Benefits extends APIResource { individuals: IndividualsAPI.Individuals = new IndividualsAPI.Individuals(this._client); @@ -33,18 +34,10 @@ export class Benefits extends APIResource { * ``` */ create( - params?: BenefitCreateParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - create(options?: Core.RequestOptions): Core.APIPromise; - create( - params: BenefitCreateParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(params)) { - return this.create({}, params); - } - const { entity_ids, ...body } = params; + params: BenefitCreateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + const { entity_ids, ...body } = params ?? {}; return this._client.post('/employer/benefits', { query: { entity_ids }, body, ...options }); } @@ -59,20 +52,11 @@ export class Benefits extends APIResource { * ``` */ retrieve( - benefitId: string, - query?: BenefitRetrieveParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - retrieve(benefitId: string, options?: Core.RequestOptions): Core.APIPromise; - retrieve( - benefitId: string, - query: BenefitRetrieveParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(query)) { - return this.retrieve(benefitId, {}, query); - } - return this._client.get(`/employer/benefits/${benefitId}`, { query, ...options }); + benefitID: string, + query: BenefitRetrieveParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/employer/benefits/${benefitID}`, { query, ...options }); } /** @@ -85,21 +69,16 @@ export class Benefits extends APIResource { * ``` */ update( - benefitId: string, - params?: BenefitUpdateParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - update(benefitId: string, options?: Core.RequestOptions): Core.APIPromise; - update( - benefitId: string, - params: BenefitUpdateParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(params)) { - return this.update(benefitId, {}, params); - } - const { entity_ids, ...body } = params; - return this._client.post(`/employer/benefits/${benefitId}`, { query: { entity_ids }, body, ...options }); + benefitID: string, + params: BenefitUpdateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + const { entity_ids, ...body } = params ?? {}; + return this._client.post(path`/employer/benefits/${benefitID}`, { + query: { entity_ids }, + body, + ...options, + }); } /** @@ -114,18 +93,10 @@ export class Benefits extends APIResource { * ``` */ list( - query?: BenefitListParams, - options?: Core.RequestOptions, - ): Core.PagePromise; - list(options?: Core.RequestOptions): Core.PagePromise; - list( - query: BenefitListParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(query)) { - return this.list({}, query); - } - return this._client.getAPIList('/employer/benefits', CompanyBenefitsSinglePage, { query, ...options }); + query: BenefitListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/employer/benefits', SinglePage, { query, ...options }); } /** @@ -140,29 +111,19 @@ export class Benefits extends APIResource { * ``` */ listSupportedBenefits( - query?: BenefitListSupportedBenefitsParams, - options?: Core.RequestOptions, - ): Core.PagePromise; - listSupportedBenefits( - options?: Core.RequestOptions, - ): Core.PagePromise; - listSupportedBenefits( - query: BenefitListSupportedBenefitsParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(query)) { - return this.listSupportedBenefits({}, query); - } - return this._client.getAPIList('/employer/benefits/meta', SupportedBenefitsSinglePage, { + query: BenefitListSupportedBenefitsParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/employer/benefits/meta', SinglePage, { query, ...options, }); } } -export class CompanyBenefitsSinglePage extends SinglePage {} +export type CompanyBenefitsSinglePage = SinglePage; -export class SupportedBenefitsSinglePage extends SinglePage {} +export type SupportedBenefitsSinglePage = SinglePage; export type BenefitContribution = | BenefitContribution.UnionMember0 @@ -475,10 +436,7 @@ export interface BenefitListSupportedBenefitsParams { entity_ids?: Array; } -Benefits.CompanyBenefitsSinglePage = CompanyBenefitsSinglePage; -Benefits.SupportedBenefitsSinglePage = SupportedBenefitsSinglePage; Benefits.Individuals = Individuals; -Benefits.IndividualBenefitsSinglePage = IndividualBenefitsSinglePage; export declare namespace Benefits { export { @@ -493,8 +451,8 @@ export declare namespace Benefits { type SupportedBenefit as SupportedBenefit, type UpdateCompanyBenefitResponse as UpdateCompanyBenefitResponse, type BenfitContribution as BenfitContribution, - CompanyBenefitsSinglePage as CompanyBenefitsSinglePage, - SupportedBenefitsSinglePage as SupportedBenefitsSinglePage, + type CompanyBenefitsSinglePage as CompanyBenefitsSinglePage, + type SupportedBenefitsSinglePage as SupportedBenefitsSinglePage, type BenefitCreateParams as BenefitCreateParams, type BenefitRetrieveParams as BenefitRetrieveParams, type BenefitUpdateParams as BenefitUpdateParams, @@ -508,7 +466,7 @@ export declare namespace Benefits { type IndividualBenefit as IndividualBenefit, type UnenrolledIndividualBenefitResponse as UnenrolledIndividualBenefitResponse, type IndividualEnrolledIDsResponse as IndividualEnrolledIDsResponse, - IndividualBenefitsSinglePage as IndividualBenefitsSinglePage, + type IndividualBenefitsSinglePage as IndividualBenefitsSinglePage, type IndividualEnrollManyParams as IndividualEnrollManyParams, type IndividualEnrolledIDsParams as IndividualEnrolledIDsParams, type IndividualRetrieveManyBenefitsParams as IndividualRetrieveManyBenefitsParams, diff --git a/src/resources/hris/benefits/index.ts b/src/resources/hris/benefits/index.ts index eb37c4099..b754e6cb0 100644 --- a/src/resources/hris/benefits/index.ts +++ b/src/resources/hris/benefits/index.ts @@ -1,8 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. export { - CompanyBenefitsSinglePage, - SupportedBenefitsSinglePage, Benefits, type BenefitContribution, type BenefitFeaturesAndOperations, @@ -20,9 +18,10 @@ export { type BenefitUpdateParams, type BenefitListParams, type BenefitListSupportedBenefitsParams, + type CompanyBenefitsSinglePage, + type SupportedBenefitsSinglePage, } from './benefits'; export { - IndividualBenefitsSinglePage, Individuals, type EnrolledIndividualBenefitResponse, type IndividualBenefit, @@ -32,4 +31,5 @@ export { type IndividualEnrolledIDsParams, type IndividualRetrieveManyBenefitsParams, type IndividualUnenrollManyParams, + type IndividualBenefitsSinglePage, } from './individuals'; diff --git a/src/resources/hris/benefits/individuals.ts b/src/resources/hris/benefits/individuals.ts index b269ddd11..58862f4e5 100644 --- a/src/resources/hris/benefits/individuals.ts +++ b/src/resources/hris/benefits/individuals.ts @@ -1,9 +1,10 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../../resource'; -import { isRequestOptions } from '../../../core'; -import * as Core from '../../../core'; -import { SinglePage } from '../../../pagination'; +import { APIResource } from '../../../core/resource'; +import { APIPromise } from '../../../core/api-promise'; +import { PagePromise, SinglePage } from '../../../core/pagination'; +import { RequestOptions } from '../../../internal/request-options'; +import { path } from '../../../internal/utils/path'; export class Individuals extends APIResource { /** @@ -21,24 +22,12 @@ export class Individuals extends APIResource { * ``` */ enrollMany( - benefitId: string, - params?: IndividualEnrollManyParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - enrollMany( - benefitId: string, - options?: Core.RequestOptions, - ): Core.APIPromise; - enrollMany( - benefitId: string, - params?: IndividualEnrollManyParams | Core.RequestOptions, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(params)) { - return this.enrollMany(benefitId, undefined, params); - } + benefitID: string, + params: IndividualEnrollManyParams | null | undefined = undefined, + options?: RequestOptions, + ): APIPromise { const { entity_ids, individuals } = params ?? {}; - return this._client.post(`/employer/benefits/${benefitId}/individuals`, { + return this._client.post(path`/employer/benefits/${benefitID}/individuals`, { query: { entity_ids }, body: individuals, ...options, @@ -51,29 +40,17 @@ export class Individuals extends APIResource { * @example * ```ts * const response = - * await client.hris.benefits.individuals.enrolledIds( + * await client.hris.benefits.individuals.enrolledIDs( * 'benefit_id', * ); * ``` */ - enrolledIds( - benefitId: string, - query?: IndividualEnrolledIDsParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - enrolledIds( - benefitId: string, - options?: Core.RequestOptions, - ): Core.APIPromise; - enrolledIds( - benefitId: string, - query: IndividualEnrolledIDsParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(query)) { - return this.enrolledIds(benefitId, {}, query); - } - return this._client.get(`/employer/benefits/${benefitId}/enrolled`, { query, ...options }); + enrolledIDs( + benefitID: string, + query: IndividualEnrolledIDsParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/employer/benefits/${benefitID}/enrolled`, { query, ...options }); } /** @@ -90,25 +67,13 @@ export class Individuals extends APIResource { * ``` */ retrieveManyBenefits( - benefitId: string, - query?: IndividualRetrieveManyBenefitsParams, - options?: Core.RequestOptions, - ): Core.PagePromise; - retrieveManyBenefits( - benefitId: string, - options?: Core.RequestOptions, - ): Core.PagePromise; - retrieveManyBenefits( - benefitId: string, - query: IndividualRetrieveManyBenefitsParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(query)) { - return this.retrieveManyBenefits(benefitId, {}, query); - } + benefitID: string, + query: IndividualRetrieveManyBenefitsParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { return this._client.getAPIList( - `/employer/benefits/${benefitId}/individuals`, - IndividualBenefitsSinglePage, + path`/employer/benefits/${benefitID}/individuals`, + SinglePage, { query, ...options }, ); } @@ -125,24 +90,12 @@ export class Individuals extends APIResource { * ``` */ unenrollMany( - benefitId: string, - params?: IndividualUnenrollManyParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - unenrollMany( - benefitId: string, - options?: Core.RequestOptions, - ): Core.APIPromise; - unenrollMany( - benefitId: string, - params: IndividualUnenrollManyParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(params)) { - return this.unenrollMany(benefitId, {}, params); - } - const { entity_ids, ...body } = params; - return this._client.delete(`/employer/benefits/${benefitId}/individuals`, { + benefitID: string, + params: IndividualUnenrollManyParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + const { entity_ids, ...body } = params ?? {}; + return this._client.delete(path`/employer/benefits/${benefitID}/individuals`, { query: { entity_ids }, body, ...options, @@ -150,7 +103,7 @@ export class Individuals extends APIResource { } } -export class IndividualBenefitsSinglePage extends SinglePage {} +export type IndividualBenefitsSinglePage = SinglePage; export interface EnrolledIndividualBenefitResponse { job_id: string; @@ -412,15 +365,13 @@ export interface IndividualUnenrollManyParams { individual_ids?: Array; } -Individuals.IndividualBenefitsSinglePage = IndividualBenefitsSinglePage; - export declare namespace Individuals { export { type EnrolledIndividualBenefitResponse as EnrolledIndividualBenefitResponse, type IndividualBenefit as IndividualBenefit, type UnenrolledIndividualBenefitResponse as UnenrolledIndividualBenefitResponse, type IndividualEnrolledIDsResponse as IndividualEnrolledIDsResponse, - IndividualBenefitsSinglePage as IndividualBenefitsSinglePage, + type IndividualBenefitsSinglePage as IndividualBenefitsSinglePage, type IndividualEnrollManyParams as IndividualEnrollManyParams, type IndividualEnrolledIDsParams as IndividualEnrolledIDsParams, type IndividualRetrieveManyBenefitsParams as IndividualRetrieveManyBenefitsParams, diff --git a/src/resources/hris/company/company.ts b/src/resources/hris/company/company.ts index 6a40f6002..506bf6b0e 100644 --- a/src/resources/hris/company/company.ts +++ b/src/resources/hris/company/company.ts @@ -1,8 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../../resource'; -import { isRequestOptions } from '../../../core'; -import * as Core from '../../../core'; +import { APIResource } from '../../../core/resource'; import * as HRISAPI from '../hris'; import * as PayStatementItemAPI from './pay-statement-item/pay-statement-item'; import { @@ -11,6 +9,8 @@ import { PayStatementItemListResponse, PayStatementItemListResponsesPage, } from './pay-statement-item/pay-statement-item'; +import { APIPromise } from '../../../core/api-promise'; +import { RequestOptions } from '../../../internal/request-options'; export class CompanyResource extends APIResource { payStatementItem: PayStatementItemAPI.PayStatementItem = new PayStatementItemAPI.PayStatementItem( @@ -25,15 +25,10 @@ export class CompanyResource extends APIResource { * const company = await client.hris.company.retrieve(); * ``` */ - retrieve(query?: CompanyRetrieveParams, options?: Core.RequestOptions): Core.APIPromise; - retrieve(options?: Core.RequestOptions): Core.APIPromise; retrieve( - query: CompanyRetrieveParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(query)) { - return this.retrieve({}, query); - } + query: CompanyRetrieveParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { return this._client.get('/employer/company', { query, ...options }); } } @@ -168,7 +163,6 @@ export interface CompanyRetrieveParams { } CompanyResource.PayStatementItem = PayStatementItem; -CompanyResource.PayStatementItemListResponsesPage = PayStatementItemListResponsesPage; export declare namespace CompanyResource { export { type Company as Company, type CompanyRetrieveParams as CompanyRetrieveParams }; @@ -176,7 +170,7 @@ export declare namespace CompanyResource { export { PayStatementItem as PayStatementItem, type PayStatementItemListResponse as PayStatementItemListResponse, - PayStatementItemListResponsesPage as PayStatementItemListResponsesPage, + type PayStatementItemListResponsesPage as PayStatementItemListResponsesPage, type PayStatementItemListParams as PayStatementItemListParams, }; } diff --git a/src/resources/hris/company/index.ts b/src/resources/hris/company/index.ts index fb8496431..94a550df7 100644 --- a/src/resources/hris/company/index.ts +++ b/src/resources/hris/company/index.ts @@ -2,8 +2,8 @@ export { CompanyResource, type Company, type CompanyRetrieveParams } from './company'; export { - PayStatementItemListResponsesPage, PayStatementItem, type PayStatementItemListResponse, type PayStatementItemListParams, + type PayStatementItemListResponsesPage, } from './pay-statement-item/index'; diff --git a/src/resources/hris/company/pay-statement-item/index.ts b/src/resources/hris/company/pay-statement-item/index.ts index 21d1080e6..e0aab2893 100644 --- a/src/resources/hris/company/pay-statement-item/index.ts +++ b/src/resources/hris/company/pay-statement-item/index.ts @@ -1,13 +1,12 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. export { - PayStatementItemListResponsesPage, PayStatementItem, type PayStatementItemListResponse, type PayStatementItemListParams, + type PayStatementItemListResponsesPage, } from './pay-statement-item'; export { - RuleListResponsesPage, Rules, type RuleCreateResponse, type RuleUpdateResponse, @@ -17,4 +16,5 @@ export { type RuleUpdateParams, type RuleListParams, type RuleDeleteParams, + type RuleListResponsesPage, } from './rules'; diff --git a/src/resources/hris/company/pay-statement-item/pay-statement-item.ts b/src/resources/hris/company/pay-statement-item/pay-statement-item.ts index af89e364d..f2d8248ab 100644 --- a/src/resources/hris/company/pay-statement-item/pay-statement-item.ts +++ b/src/resources/hris/company/pay-statement-item/pay-statement-item.ts @@ -1,8 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../../../resource'; -import { isRequestOptions } from '../../../../core'; -import * as Core from '../../../../core'; +import { APIResource } from '../../../../core/resource'; import * as RulesAPI from './rules'; import { RuleCreateParams, @@ -16,7 +14,8 @@ import { RuleUpdateResponse, Rules, } from './rules'; -import { ResponsesPage } from '../../../../pagination'; +import { PagePromise, ResponsesPage } from '../../../../core/pagination'; +import { RequestOptions } from '../../../../internal/request-options'; export class PayStatementItem extends APIResource { rules: RulesAPI.Rules = new RulesAPI.Rules(this._client); @@ -35,27 +34,18 @@ export class PayStatementItem extends APIResource { * ``` */ list( - query?: PayStatementItemListParams, - options?: Core.RequestOptions, - ): Core.PagePromise; - list( - options?: Core.RequestOptions, - ): Core.PagePromise; - list( - query: PayStatementItemListParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(query)) { - return this.list({}, query); - } - return this._client.getAPIList('/employer/pay-statement-item', PayStatementItemListResponsesPage, { - query, - ...options, - }); + query: PayStatementItemListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList( + '/employer/pay-statement-item', + ResponsesPage, + { query, ...options }, + ); } } -export class PayStatementItemListResponsesPage extends ResponsesPage {} +export type PayStatementItemListResponsesPage = ResponsesPage; export interface PayStatementItemListResponse { /** @@ -139,14 +129,12 @@ export interface PayStatementItemListParams { type?: string; } -PayStatementItem.PayStatementItemListResponsesPage = PayStatementItemListResponsesPage; PayStatementItem.Rules = Rules; -PayStatementItem.RuleListResponsesPage = RuleListResponsesPage; export declare namespace PayStatementItem { export { type PayStatementItemListResponse as PayStatementItemListResponse, - PayStatementItemListResponsesPage as PayStatementItemListResponsesPage, + type PayStatementItemListResponsesPage as PayStatementItemListResponsesPage, type PayStatementItemListParams as PayStatementItemListParams, }; @@ -156,7 +144,7 @@ export declare namespace PayStatementItem { type RuleUpdateResponse as RuleUpdateResponse, type RuleListResponse as RuleListResponse, type RuleDeleteResponse as RuleDeleteResponse, - RuleListResponsesPage as RuleListResponsesPage, + type RuleListResponsesPage as RuleListResponsesPage, type RuleCreateParams as RuleCreateParams, type RuleUpdateParams as RuleUpdateParams, type RuleListParams as RuleListParams, diff --git a/src/resources/hris/company/pay-statement-item/rules.ts b/src/resources/hris/company/pay-statement-item/rules.ts index d8fb1de72..cd842d793 100644 --- a/src/resources/hris/company/pay-statement-item/rules.ts +++ b/src/resources/hris/company/pay-statement-item/rules.ts @@ -1,9 +1,10 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../../../resource'; -import { isRequestOptions } from '../../../../core'; -import * as Core from '../../../../core'; -import { ResponsesPage } from '../../../../pagination'; +import { APIResource } from '../../../../core/resource'; +import { APIPromise } from '../../../../core/api-promise'; +import { PagePromise, ResponsesPage } from '../../../../core/pagination'; +import { RequestOptions } from '../../../../internal/request-options'; +import { path } from '../../../../internal/utils/path'; export class Rules extends APIResource { /** @@ -20,16 +21,11 @@ export class Rules extends APIResource { * await client.hris.company.payStatementItem.rules.create(); * ``` */ - create(params?: RuleCreateParams, options?: Core.RequestOptions): Core.APIPromise; - create(options?: Core.RequestOptions): Core.APIPromise; create( - params: RuleCreateParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(params)) { - return this.create({}, params); - } - const { entity_ids, ...body } = params; + params: RuleCreateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + const { entity_ids, ...body } = params ?? {}; return this._client.post('/employer/pay-statement-item/rule', { query: { entity_ids }, body, @@ -50,21 +46,12 @@ export class Rules extends APIResource { * ``` */ update( - ruleId: string, - params?: RuleUpdateParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - update(ruleId: string, options?: Core.RequestOptions): Core.APIPromise; - update( - ruleId: string, - params: RuleUpdateParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(params)) { - return this.update(ruleId, {}, params); - } - const { entity_ids, ...body } = params; - return this._client.put(`/employer/pay-statement-item/rule/${ruleId}`, { + ruleID: string, + params: RuleUpdateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + const { entity_ids, ...body } = params ?? {}; + return this._client.put(path`/employer/pay-statement-item/rule/${ruleID}`, { query: { entity_ids }, body, ...options, @@ -84,18 +71,10 @@ export class Rules extends APIResource { * ``` */ list( - query?: RuleListParams, - options?: Core.RequestOptions, - ): Core.PagePromise; - list(options?: Core.RequestOptions): Core.PagePromise; - list( - query: RuleListParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(query)) { - return this.list({}, query); - } - return this._client.getAPIList('/employer/pay-statement-item/rule', RuleListResponsesPage, { + query: RuleListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/employer/pay-statement-item/rule', ResponsesPage, { query, ...options, }); @@ -114,28 +93,19 @@ export class Rules extends APIResource { * ``` */ delete( - ruleId: string, - params?: RuleDeleteParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - delete(ruleId: string, options?: Core.RequestOptions): Core.APIPromise; - delete( - ruleId: string, - params: RuleDeleteParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(params)) { - return this.delete(ruleId, {}, params); - } - const { entity_ids } = params; - return this._client.delete(`/employer/pay-statement-item/rule/${ruleId}`, { + ruleID: string, + params: RuleDeleteParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + const { entity_ids } = params ?? {}; + return this._client.delete(path`/employer/pay-statement-item/rule/${ruleID}`, { query: { entity_ids }, ...options, }); } } -export class RuleListResponsesPage extends ResponsesPage {} +export type RuleListResponsesPage = ResponsesPage; export interface RuleCreateResponse { /** @@ -527,15 +497,13 @@ export interface RuleDeleteParams { entity_ids?: Array; } -Rules.RuleListResponsesPage = RuleListResponsesPage; - export declare namespace Rules { export { type RuleCreateResponse as RuleCreateResponse, type RuleUpdateResponse as RuleUpdateResponse, type RuleListResponse as RuleListResponse, type RuleDeleteResponse as RuleDeleteResponse, - RuleListResponsesPage as RuleListResponsesPage, + type RuleListResponsesPage as RuleListResponsesPage, type RuleCreateParams as RuleCreateParams, type RuleUpdateParams as RuleUpdateParams, type RuleListParams as RuleListParams, diff --git a/src/resources/hris/directory.ts b/src/resources/hris/directory.ts index 237ed3d68..110aa8468 100644 --- a/src/resources/hris/directory.ts +++ b/src/resources/hris/directory.ts @@ -1,9 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import { isRequestOptions } from '../../core'; -import * as Core from '../../core'; -import { IndividualsPage, type IndividualsPageParams } from '../../pagination'; +import { APIResource } from '../../core/resource'; +import { IndividualsPage, type IndividualsPageParams, PagePromise } from '../../core/pagination'; +import { RequestOptions } from '../../internal/request-options'; export class Directory extends APIResource { /** @@ -18,18 +17,13 @@ export class Directory extends APIResource { * ``` */ list( - query?: DirectoryListParams, - options?: Core.RequestOptions, - ): Core.PagePromise; - list(options?: Core.RequestOptions): Core.PagePromise; - list( - query: DirectoryListParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(query)) { - return this.list({}, query); - } - return this._client.getAPIList('/employer/directory', IndividualsPage, { query, ...options }); + query: DirectoryListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/employer/directory', IndividualsPage, { + query, + ...options, + }); } /** diff --git a/src/resources/hris/documents.ts b/src/resources/hris/documents.ts index 4251336ae..ad028adae 100644 --- a/src/resources/hris/documents.ts +++ b/src/resources/hris/documents.ts @@ -1,9 +1,10 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import { isRequestOptions } from '../../core'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; import * as Shared from '../shared'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; export class Documents extends APIResource { /** @@ -15,15 +16,10 @@ export class Documents extends APIResource { * const documents = await client.hris.documents.list(); * ``` */ - list(query?: DocumentListParams, options?: Core.RequestOptions): Core.APIPromise; - list(options?: Core.RequestOptions): Core.APIPromise; list( - query: DocumentListParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(query)) { - return this.list({}, query); - } + query: DocumentListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { return this._client.get('/employer/documents', { query, ...options }); } @@ -39,20 +35,11 @@ export class Documents extends APIResource { * ``` */ retreive( - documentId: string, - query?: DocumentRetreiveParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - retreive(documentId: string, options?: Core.RequestOptions): Core.APIPromise; - retreive( - documentId: string, - query: DocumentRetreiveParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(query)) { - return this.retreive(documentId, {}, query); - } - return this._client.get(`/employer/documents/${documentId}`, { query, ...options }); + documentID: string, + query: DocumentRetreiveParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/employer/documents/${documentID}`, { query, ...options }); } } diff --git a/src/resources/hris/employments.ts b/src/resources/hris/employments.ts index 420b1ff1e..ec84cd584 100644 --- a/src/resources/hris/employments.ts +++ b/src/resources/hris/employments.ts @@ -1,9 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; import * as HRISAPI from './hris'; -import { ResponsesPage } from '../../pagination'; +import { PagePromise, ResponsesPage } from '../../core/pagination'; +import { RequestOptions } from '../../internal/request-options'; export class Employments extends APIResource { /** @@ -21,10 +21,10 @@ export class Employments extends APIResource { */ retrieveMany( params: EmploymentRetrieveManyParams, - options?: Core.RequestOptions, - ): Core.PagePromise { + options?: RequestOptions, + ): PagePromise { const { entity_ids, ...body } = params; - return this._client.getAPIList('/employer/employment', EmploymentDataResponsesPage, { + return this._client.getAPIList('/employer/employment', ResponsesPage, { query: { entity_ids }, body, method: 'post', @@ -33,7 +33,7 @@ export class Employments extends APIResource { } } -export class EmploymentDataResponsesPage extends ResponsesPage {} +export type EmploymentDataResponsesPage = ResponsesPage; export type EmploymentData = EmploymentData.UnionMember0 | EmploymentData.BatchError; @@ -230,13 +230,11 @@ export namespace EmploymentRetrieveManyParams { } } -Employments.EmploymentDataResponsesPage = EmploymentDataResponsesPage; - export declare namespace Employments { export { type EmploymentData as EmploymentData, type EmploymentDataResponse as EmploymentDataResponse, - EmploymentDataResponsesPage as EmploymentDataResponsesPage, + type EmploymentDataResponsesPage as EmploymentDataResponsesPage, type EmploymentRetrieveManyParams as EmploymentRetrieveManyParams, }; } diff --git a/src/resources/hris/hris.ts b/src/resources/hris/hris.ts index ea5ab6ca7..4ecf085c4 100644 --- a/src/resources/hris/hris.ts +++ b/src/resources/hris/hris.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; +import { APIResource } from '../../core/resource'; import * as DirectoryAPI from './directory'; import { Directory, @@ -169,17 +169,11 @@ export interface Money { HRIS.CompanyResource = CompanyResource; HRIS.Directory = Directory; HRIS.Individuals = Individuals; -HRIS.IndividualResponsesPage = IndividualResponsesPage; HRIS.Employments = Employments; -HRIS.EmploymentDataResponsesPage = EmploymentDataResponsesPage; HRIS.Payments = Payments; -HRIS.PaymentsSinglePage = PaymentsSinglePage; HRIS.PayStatements = PayStatements; -HRIS.PayStatementResponsesPage = PayStatementResponsesPage; HRIS.Documents = Documents; HRIS.Benefits = Benefits; -HRIS.CompanyBenefitsSinglePage = CompanyBenefitsSinglePage; -HRIS.SupportedBenefitsSinglePage = SupportedBenefitsSinglePage; export declare namespace HRIS { export { type Income as Income, type Location as Location, type Money as Money }; @@ -201,7 +195,7 @@ export declare namespace HRIS { Individuals as Individuals, type Individual as Individual, type IndividualResponse as IndividualResponse, - IndividualResponsesPage as IndividualResponsesPage, + type IndividualResponsesPage as IndividualResponsesPage, type IndividualRetrieveManyParams as IndividualRetrieveManyParams, }; @@ -209,14 +203,14 @@ export declare namespace HRIS { Employments as Employments, type EmploymentData as EmploymentData, type EmploymentDataResponse as EmploymentDataResponse, - EmploymentDataResponsesPage as EmploymentDataResponsesPage, + type EmploymentDataResponsesPage as EmploymentDataResponsesPage, type EmploymentRetrieveManyParams as EmploymentRetrieveManyParams, }; export { Payments as Payments, type Payment as Payment, - PaymentsSinglePage as PaymentsSinglePage, + type PaymentsSinglePage as PaymentsSinglePage, type PaymentListParams as PaymentListParams, }; @@ -226,7 +220,7 @@ export declare namespace HRIS { type PayStatementDataSyncInProgress as PayStatementDataSyncInProgress, type PayStatementResponse as PayStatementResponse, type PayStatementResponseBody as PayStatementResponseBody, - PayStatementResponsesPage as PayStatementResponsesPage, + type PayStatementResponsesPage as PayStatementResponsesPage, type PayStatementRetrieveManyParams as PayStatementRetrieveManyParams, }; @@ -254,8 +248,8 @@ export declare namespace HRIS { type SupportedBenefit as SupportedBenefit, type UpdateCompanyBenefitResponse as UpdateCompanyBenefitResponse, type BenfitContribution as BenfitContribution, - CompanyBenefitsSinglePage as CompanyBenefitsSinglePage, - SupportedBenefitsSinglePage as SupportedBenefitsSinglePage, + type CompanyBenefitsSinglePage as CompanyBenefitsSinglePage, + type SupportedBenefitsSinglePage as SupportedBenefitsSinglePage, type BenefitCreateParams as BenefitCreateParams, type BenefitRetrieveParams as BenefitRetrieveParams, type BenefitUpdateParams as BenefitUpdateParams, diff --git a/src/resources/hris/index.ts b/src/resources/hris/index.ts index 5fddd3d6e..391e5a6bd 100644 --- a/src/resources/hris/index.ts +++ b/src/resources/hris/index.ts @@ -1,8 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. export { - CompanyBenefitsSinglePage, - SupportedBenefitsSinglePage, Benefits, type BenefitContribution, type BenefitFeaturesAndOperations, @@ -20,6 +18,8 @@ export { type BenefitUpdateParams, type BenefitListParams, type BenefitListSupportedBenefitsParams, + type CompanyBenefitsSinglePage, + type SupportedBenefitsSinglePage, } from './benefits/index'; export { CompanyResource, type Company, type CompanyRetrieveParams } from './company/index'; export { @@ -39,27 +39,27 @@ export { type DocumentRetreiveParams, } from './documents'; export { - EmploymentDataResponsesPage, Employments, type EmploymentData, type EmploymentDataResponse, type EmploymentRetrieveManyParams, + type EmploymentDataResponsesPage, } from './employments'; export { HRIS, type Income, type Location, type Money } from './hris'; export { - IndividualResponsesPage, Individuals, type Individual, type IndividualResponse, type IndividualRetrieveManyParams, + type IndividualResponsesPage, } from './individuals'; export { - PayStatementResponsesPage, PayStatements, type PayStatement, type PayStatementDataSyncInProgress, type PayStatementResponse, type PayStatementResponseBody, type PayStatementRetrieveManyParams, + type PayStatementResponsesPage, } from './pay-statements'; -export { PaymentsSinglePage, Payments, type Payment, type PaymentListParams } from './payments'; +export { Payments, type Payment, type PaymentListParams, type PaymentsSinglePage } from './payments'; diff --git a/src/resources/hris/individuals.ts b/src/resources/hris/individuals.ts index 664af0b85..e32b95a90 100644 --- a/src/resources/hris/individuals.ts +++ b/src/resources/hris/individuals.ts @@ -1,10 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import { isRequestOptions } from '../../core'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; import * as HRISAPI from './hris'; -import { ResponsesPage } from '../../pagination'; +import { PagePromise, ResponsesPage } from '../../core/pagination'; +import { RequestOptions } from '../../internal/request-options'; export class Individuals extends APIResource { /** @@ -19,19 +18,11 @@ export class Individuals extends APIResource { * ``` */ retrieveMany( - params?: IndividualRetrieveManyParams, - options?: Core.RequestOptions, - ): Core.PagePromise; - retrieveMany(options?: Core.RequestOptions): Core.PagePromise; - retrieveMany( - params: IndividualRetrieveManyParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(params)) { - return this.retrieveMany({}, params); - } - const { entity_ids, ...body } = params; - return this._client.getAPIList('/employer/individual', IndividualResponsesPage, { + params: IndividualRetrieveManyParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + const { entity_ids, ...body } = params ?? {}; + return this._client.getAPIList('/employer/individual', ResponsesPage, { query: { entity_ids }, body, method: 'post', @@ -40,7 +31,7 @@ export class Individuals extends APIResource { } } -export class IndividualResponsesPage extends ResponsesPage {} +export type IndividualResponsesPage = ResponsesPage; export type Individual = Individual.UnionMember0 | Individual.BatchError; @@ -174,13 +165,11 @@ export namespace IndividualRetrieveManyParams { } } -Individuals.IndividualResponsesPage = IndividualResponsesPage; - export declare namespace Individuals { export { type Individual as Individual, type IndividualResponse as IndividualResponse, - IndividualResponsesPage as IndividualResponsesPage, + type IndividualResponsesPage as IndividualResponsesPage, type IndividualRetrieveManyParams as IndividualRetrieveManyParams, }; } diff --git a/src/resources/hris/pay-statements.ts b/src/resources/hris/pay-statements.ts index b5be191be..40b011c3f 100644 --- a/src/resources/hris/pay-statements.ts +++ b/src/resources/hris/pay-statements.ts @@ -1,10 +1,10 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; import * as HRISAPI from './hris'; import * as BenefitsAPI from './benefits/benefits'; -import { ResponsesPage } from '../../pagination'; +import { PagePromise, ResponsesPage } from '../../core/pagination'; +import { RequestOptions } from '../../internal/request-options'; export class PayStatements extends APIResource { /** @@ -31,10 +31,10 @@ export class PayStatements extends APIResource { */ retrieveMany( params: PayStatementRetrieveManyParams, - options?: Core.RequestOptions, - ): Core.PagePromise { + options?: RequestOptions, + ): PagePromise { const { entity_ids, ...body } = params; - return this._client.getAPIList('/employer/pay-statement', PayStatementResponsesPage, { + return this._client.getAPIList('/employer/pay-statement', ResponsesPage, { query: { entity_ids }, body, method: 'post', @@ -43,7 +43,7 @@ export class PayStatements extends APIResource { } } -export class PayStatementResponsesPage extends ResponsesPage {} +export type PayStatementResponsesPage = ResponsesPage; export interface PayStatement { /** @@ -341,15 +341,13 @@ export namespace PayStatementRetrieveManyParams { } } -PayStatements.PayStatementResponsesPage = PayStatementResponsesPage; - export declare namespace PayStatements { export { type PayStatement as PayStatement, type PayStatementDataSyncInProgress as PayStatementDataSyncInProgress, type PayStatementResponse as PayStatementResponse, type PayStatementResponseBody as PayStatementResponseBody, - PayStatementResponsesPage as PayStatementResponsesPage, + type PayStatementResponsesPage as PayStatementResponsesPage, type PayStatementRetrieveManyParams as PayStatementRetrieveManyParams, }; } diff --git a/src/resources/hris/payments.ts b/src/resources/hris/payments.ts index b7e63d8d3..cb68fa1ff 100644 --- a/src/resources/hris/payments.ts +++ b/src/resources/hris/payments.ts @@ -1,9 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; import * as HRISAPI from './hris'; -import { SinglePage } from '../../pagination'; +import { PagePromise, SinglePage } from '../../core/pagination'; +import { RequestOptions } from '../../internal/request-options'; export class Payments extends APIResource { /** @@ -20,15 +20,12 @@ export class Payments extends APIResource { * } * ``` */ - list( - query: PaymentListParams, - options?: Core.RequestOptions, - ): Core.PagePromise { - return this._client.getAPIList('/employer/payment', PaymentsSinglePage, { query, ...options }); + list(query: PaymentListParams, options?: RequestOptions): PagePromise { + return this._client.getAPIList('/employer/payment', SinglePage, { query, ...options }); } } -export class PaymentsSinglePage extends SinglePage {} +export type PaymentsSinglePage = SinglePage; export interface Payment { /** @@ -111,12 +108,10 @@ export interface PaymentListParams { entity_ids?: Array; } -Payments.PaymentsSinglePage = PaymentsSinglePage; - export declare namespace Payments { export { type Payment as Payment, - PaymentsSinglePage as PaymentsSinglePage, + type PaymentsSinglePage as PaymentsSinglePage, type PaymentListParams as PaymentListParams, }; } diff --git a/src/resources/index.ts b/src/resources/index.ts index 85c4c42db..ff93bb948 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -8,10 +8,10 @@ export { HRIS, type Income, type Location, type Money } from './hris/hris'; export { Jobs } from './jobs/jobs'; export { Payroll } from './payroll/payroll'; export { - ProviderListResponsesSinglePage, Providers, type Provider, type ProviderListResponse, + type ProviderListResponsesSinglePage, } from './providers'; export { RequestForwarding, diff --git a/src/resources/jobs/automated.ts b/src/resources/jobs/automated.ts index 9e7a3a0f4..bd50765c7 100644 --- a/src/resources/jobs/automated.ts +++ b/src/resources/jobs/automated.ts @@ -1,8 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import { isRequestOptions } from '../../core'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; export class Automated extends APIResource { /** @@ -21,18 +22,15 @@ export class Automated extends APIResource { * This endpoint is available for _Scale_ tier customers as an add-on. To request * access to this endpoint, please contact your Finch account manager. */ - create( - body: AutomatedCreateParams, - options?: Core.RequestOptions, - ): Core.APIPromise { + create(body: AutomatedCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/jobs/automated', { body, ...options }); } /** * Get an automated job by `job_id`. */ - retrieve(jobId: string, options?: Core.RequestOptions): Core.APIPromise { - return this._client.get(`/jobs/automated/${jobId}`, options); + retrieve(jobID: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/jobs/automated/${jobID}`, options); } /** @@ -40,15 +38,10 @@ export class Automated extends APIResource { * jobs are sorted in descending order by submission time. For scheduled jobs such * as data syncs, only the next scheduled job is shown. */ - list(query?: AutomatedListParams, options?: Core.RequestOptions): Core.APIPromise; - list(options?: Core.RequestOptions): Core.APIPromise; list( - query: AutomatedListParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(query)) { - return this.list({}, query); - } + query: AutomatedListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { return this._client.get('/jobs/automated', { query, ...options }); } } diff --git a/src/resources/jobs/jobs.ts b/src/resources/jobs/jobs.ts index 7cc64d285..4daaed611 100644 --- a/src/resources/jobs/jobs.ts +++ b/src/resources/jobs/jobs.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; +import { APIResource } from '../../core/resource'; import * as AutomatedAPI from './automated'; import { Automated, diff --git a/src/resources/jobs/manual.ts b/src/resources/jobs/manual.ts index 07f4b6d5c..ac5268d79 100644 --- a/src/resources/jobs/manual.ts +++ b/src/resources/jobs/manual.ts @@ -1,15 +1,17 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; export class Manual extends APIResource { /** * Get a manual job by `job_id`. Manual jobs are completed by a human and include * Assisted Benefits jobs. */ - retrieve(jobId: string, options?: Core.RequestOptions): Core.APIPromise { - return this._client.get(`/jobs/manual/${jobId}`, options); + retrieve(jobID: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/jobs/manual/${jobID}`, options); } } diff --git a/src/resources/payroll/index.ts b/src/resources/payroll/index.ts index 00580f179..238963398 100644 --- a/src/resources/payroll/index.ts +++ b/src/resources/payroll/index.ts @@ -1,11 +1,11 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. export { - PayGroupListResponsesSinglePage, PayGroups, type PayGroupRetrieveResponse, type PayGroupListResponse, type PayGroupRetrieveParams, type PayGroupListParams, + type PayGroupListResponsesSinglePage, } from './pay-groups'; export { Payroll } from './payroll'; diff --git a/src/resources/payroll/pay-groups.ts b/src/resources/payroll/pay-groups.ts index c8704bf10..c81535600 100644 --- a/src/resources/payroll/pay-groups.ts +++ b/src/resources/payroll/pay-groups.ts @@ -1,56 +1,38 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import { isRequestOptions } from '../../core'; -import * as Core from '../../core'; -import { SinglePage } from '../../pagination'; +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { PagePromise, SinglePage } from '../../core/pagination'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; export class PayGroups extends APIResource { /** * Read information from a single pay group */ retrieve( - payGroupId: string, - query?: PayGroupRetrieveParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - retrieve(payGroupId: string, options?: Core.RequestOptions): Core.APIPromise; - retrieve( - payGroupId: string, - query: PayGroupRetrieveParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(query)) { - return this.retrieve(payGroupId, {}, query); - } - return this._client.get(`/employer/pay-groups/${payGroupId}`, { query, ...options }); + payGroupID: string, + query: PayGroupRetrieveParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get(path`/employer/pay-groups/${payGroupID}`, { query, ...options }); } /** * Read company pay groups and frequencies */ list( - query?: PayGroupListParams, - options?: Core.RequestOptions, - ): Core.PagePromise; - list( - options?: Core.RequestOptions, - ): Core.PagePromise; - list( - query: PayGroupListParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.PagePromise { - if (isRequestOptions(query)) { - return this.list({}, query); - } - return this._client.getAPIList('/employer/pay-groups', PayGroupListResponsesSinglePage, { + query: PayGroupListParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/employer/pay-groups', SinglePage, { query, ...options, }); } } -export class PayGroupListResponsesSinglePage extends SinglePage {} +export type PayGroupListResponsesSinglePage = SinglePage; export interface PayGroupRetrieveResponse { /** @@ -126,13 +108,11 @@ export interface PayGroupListParams { pay_frequencies?: Array; } -PayGroups.PayGroupListResponsesSinglePage = PayGroupListResponsesSinglePage; - export declare namespace PayGroups { export { type PayGroupRetrieveResponse as PayGroupRetrieveResponse, type PayGroupListResponse as PayGroupListResponse, - PayGroupListResponsesSinglePage as PayGroupListResponsesSinglePage, + type PayGroupListResponsesSinglePage as PayGroupListResponsesSinglePage, type PayGroupRetrieveParams as PayGroupRetrieveParams, type PayGroupListParams as PayGroupListParams, }; diff --git a/src/resources/payroll/payroll.ts b/src/resources/payroll/payroll.ts index fbb7c3e46..b7ebad3a0 100644 --- a/src/resources/payroll/payroll.ts +++ b/src/resources/payroll/payroll.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; +import { APIResource } from '../../core/resource'; import * as PayGroupsAPI from './pay-groups'; import { PayGroupListParams, @@ -16,14 +16,13 @@ export class Payroll extends APIResource { } Payroll.PayGroups = PayGroups; -Payroll.PayGroupListResponsesSinglePage = PayGroupListResponsesSinglePage; export declare namespace Payroll { export { PayGroups as PayGroups, type PayGroupRetrieveResponse as PayGroupRetrieveResponse, type PayGroupListResponse as PayGroupListResponse, - PayGroupListResponsesSinglePage as PayGroupListResponsesSinglePage, + type PayGroupListResponsesSinglePage as PayGroupListResponsesSinglePage, type PayGroupRetrieveParams as PayGroupRetrieveParams, type PayGroupListParams as PayGroupListParams, }; diff --git a/src/resources/providers.ts b/src/resources/providers.ts index 175f23aa5..0299ebadf 100644 --- a/src/resources/providers.ts +++ b/src/resources/providers.ts @@ -1,21 +1,19 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../resource'; -import * as Core from '../core'; -import { SinglePage } from '../pagination'; +import { APIResource } from '../core/resource'; +import { PagePromise, SinglePage } from '../core/pagination'; +import { RequestOptions } from '../internal/request-options'; export class Providers extends APIResource { /** * Return details on all available payroll and HR systems. */ - list( - options?: Core.RequestOptions, - ): Core.PagePromise { - return this._client.getAPIList('/providers', ProviderListResponsesSinglePage, options); + list(options?: RequestOptions): PagePromise { + return this._client.getAPIList('/providers', SinglePage, options); } } -export class ProviderListResponsesSinglePage extends SinglePage {} +export type ProviderListResponsesSinglePage = SinglePage; export interface Provider { /** @@ -163,12 +161,10 @@ export namespace ProviderListResponse { } } -Providers.ProviderListResponsesSinglePage = ProviderListResponsesSinglePage; - export declare namespace Providers { export { type Provider as Provider, type ProviderListResponse as ProviderListResponse, - ProviderListResponsesSinglePage as ProviderListResponsesSinglePage, + type ProviderListResponsesSinglePage as ProviderListResponsesSinglePage, }; } diff --git a/src/resources/request-forwarding.ts b/src/resources/request-forwarding.ts index 6bc5fcbf5..4544f37d5 100644 --- a/src/resources/request-forwarding.ts +++ b/src/resources/request-forwarding.ts @@ -1,7 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../resource'; -import * as Core from '../core'; +import { APIResource } from '../core/resource'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; export class RequestForwarding extends APIResource { /** @@ -12,8 +13,8 @@ export class RequestForwarding extends APIResource { */ forward( body: RequestForwardingForwardParams, - options?: Core.RequestOptions, - ): Core.APIPromise { + options?: RequestOptions, + ): APIPromise { return this._client.post('/forward', { body, ...options }); } } diff --git a/src/resources/sandbox/company.ts b/src/resources/sandbox/company.ts index 4bc563932..ab4932679 100644 --- a/src/resources/sandbox/company.ts +++ b/src/resources/sandbox/company.ts @@ -1,8 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; import * as HRISAPI from '../hris/hris'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; export class Company extends APIResource { /** @@ -31,7 +32,7 @@ export class Company extends APIResource { * }); * ``` */ - update(body: CompanyUpdateParams, options?: Core.RequestOptions): Core.APIPromise { + update(body: CompanyUpdateParams, options?: RequestOptions): APIPromise { return this._client.put('/sandbox/company', { body, ...options }); } } diff --git a/src/resources/sandbox/connections/accounts.ts b/src/resources/sandbox/connections/accounts.ts index 22beec253..7c22ed006 100644 --- a/src/resources/sandbox/connections/accounts.ts +++ b/src/resources/sandbox/connections/accounts.ts @@ -1,9 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../../resource'; -import { isRequestOptions } from '../../../core'; -import * as Core from '../../../core'; +import { APIResource } from '../../../core/resource'; import * as Shared from '../../shared'; +import { APIPromise } from '../../../core/api-promise'; +import { RequestOptions } from '../../../internal/request-options'; export class Accounts extends APIResource { /** @@ -18,7 +18,7 @@ export class Accounts extends APIResource { * }); * ``` */ - create(body: AccountCreateParams, options?: Core.RequestOptions): Core.APIPromise { + create(body: AccountCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/sandbox/connections/accounts', { body, ...options }); } @@ -34,15 +34,10 @@ export class Accounts extends APIResource { * }); * ``` */ - update(body?: AccountUpdateParams, options?: Core.RequestOptions): Core.APIPromise; - update(options?: Core.RequestOptions): Core.APIPromise; update( - body: AccountUpdateParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(body)) { - return this.update({}, body); - } + body: AccountUpdateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { return this._client.put('/sandbox/connections/accounts', { body, ...options }); } } diff --git a/src/resources/sandbox/connections/connections.ts b/src/resources/sandbox/connections/connections.ts index 1fc36baa7..478b6d76c 100644 --- a/src/resources/sandbox/connections/connections.ts +++ b/src/resources/sandbox/connections/connections.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../../resource'; -import * as Core from '../../../core'; +import { APIResource } from '../../../core/resource'; import * as AccountsAPI from './accounts'; import { AccountCreateParams, @@ -10,6 +9,8 @@ import { AccountUpdateResponse, Accounts, } from './accounts'; +import { APIPromise } from '../../../core/api-promise'; +import { RequestOptions } from '../../../internal/request-options'; export class Connections extends APIResource { accounts: AccountsAPI.Accounts = new AccountsAPI.Accounts(this._client); @@ -24,10 +25,7 @@ export class Connections extends APIResource { * }); * ``` */ - create( - body: ConnectionCreateParams, - options?: Core.RequestOptions, - ): Core.APIPromise { + create(body: ConnectionCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/sandbox/connections', { body, ...options }); } } diff --git a/src/resources/sandbox/directory.ts b/src/resources/sandbox/directory.ts index 22a0c2eee..78c79d191 100644 --- a/src/resources/sandbox/directory.ts +++ b/src/resources/sandbox/directory.ts @@ -1,9 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import { isRequestOptions } from '../../core'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; import * as HRISAPI from '../hris/hris'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; export class Directory extends APIResource { /** @@ -11,24 +11,15 @@ export class Directory extends APIResource { * * @example * ```ts - * const directories = await client.sandbox.directory.create([ - * {}, - * ]); + * const directories = await client.sandbox.directory.create(); * ``` */ create( - body?: DirectoryCreateParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - create(options?: Core.RequestOptions): Core.APIPromise; - create( - body?: DirectoryCreateParams | Core.RequestOptions, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(body)) { - return this.create(undefined, body); - } - return this._client.post('/sandbox/directory', { body, ...options }); + params: DirectoryCreateParams | null | undefined = undefined, + options?: RequestOptions, + ): APIPromise { + const { body } = params ?? {}; + return this._client.post('/sandbox/directory', { body: body, ...options }); } } @@ -37,7 +28,13 @@ export class Directory extends APIResource { */ export type DirectoryCreateResponse = Array; -export type DirectoryCreateParams = Array; +export interface DirectoryCreateParams { + /** + * Array of individuals to create. Takes all combined fields from `/individual` and + * `/employment` endpoints. All fields are optional. + */ + body?: Array; +} export namespace DirectoryCreateParams { export interface Body { diff --git a/src/resources/sandbox/employment.ts b/src/resources/sandbox/employment.ts index e2bdfad31..c770ddaee 100644 --- a/src/resources/sandbox/employment.ts +++ b/src/resources/sandbox/employment.ts @@ -1,9 +1,10 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import { isRequestOptions } from '../../core'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; import * as HRISAPI from '../hris/hris'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; export class Employment extends APIResource { /** @@ -17,20 +18,11 @@ export class Employment extends APIResource { * ``` */ update( - individualId: string, - body?: EmploymentUpdateParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - update(individualId: string, options?: Core.RequestOptions): Core.APIPromise; - update( - individualId: string, - body: EmploymentUpdateParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(body)) { - return this.update(individualId, {}, body); - } - return this._client.put(`/sandbox/employment/${individualId}`, { body, ...options }); + individualID: string, + body: EmploymentUpdateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.put(path`/sandbox/employment/${individualID}`, { body, ...options }); } } diff --git a/src/resources/sandbox/individual.ts b/src/resources/sandbox/individual.ts index 9e2969703..19a333d0b 100644 --- a/src/resources/sandbox/individual.ts +++ b/src/resources/sandbox/individual.ts @@ -1,9 +1,10 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import { isRequestOptions } from '../../core'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; import * as HRISAPI from '../hris/hris'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; export class Individual extends APIResource { /** @@ -17,20 +18,11 @@ export class Individual extends APIResource { * ``` */ update( - individualId: string, - body?: IndividualUpdateParams, - options?: Core.RequestOptions, - ): Core.APIPromise; - update(individualId: string, options?: Core.RequestOptions): Core.APIPromise; - update( - individualId: string, - body: IndividualUpdateParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(body)) { - return this.update(individualId, {}, body); - } - return this._client.put(`/sandbox/individual/${individualId}`, { body, ...options }); + individualID: string, + body: IndividualUpdateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.put(path`/sandbox/individual/${individualID}`, { body, ...options }); } } diff --git a/src/resources/sandbox/jobs/configuration.ts b/src/resources/sandbox/jobs/configuration.ts index ed5de6514..819333014 100644 --- a/src/resources/sandbox/jobs/configuration.ts +++ b/src/resources/sandbox/jobs/configuration.ts @@ -1,7 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../../resource'; -import * as Core from '../../../core'; +import { APIResource } from '../../../core/resource'; +import { APIPromise } from '../../../core/api-promise'; +import { RequestOptions } from '../../../internal/request-options'; export class Configuration extends APIResource { /** @@ -13,7 +14,7 @@ export class Configuration extends APIResource { * await client.sandbox.jobs.configuration.retrieve(); * ``` */ - retrieve(options?: Core.RequestOptions): Core.APIPromise { + retrieve(options?: RequestOptions): APIPromise { return this._client.get('/sandbox/jobs/configuration', options); } @@ -29,10 +30,7 @@ export class Configuration extends APIResource { * }); * ``` */ - update( - body: ConfigurationUpdateParams, - options?: Core.RequestOptions, - ): Core.APIPromise { + update(body: ConfigurationUpdateParams, options?: RequestOptions): APIPromise { return this._client.put('/sandbox/jobs/configuration', { body, ...options }); } } diff --git a/src/resources/sandbox/jobs/jobs.ts b/src/resources/sandbox/jobs/jobs.ts index 3eea8bcdb..9b8ad1a20 100644 --- a/src/resources/sandbox/jobs/jobs.ts +++ b/src/resources/sandbox/jobs/jobs.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../../resource'; -import * as Core from '../../../core'; +import { APIResource } from '../../../core/resource'; import * as ConfigurationAPI from './configuration'; import { Configuration, @@ -9,6 +8,8 @@ import { ConfigurationUpdateParams, SandboxJobConfiguration, } from './configuration'; +import { APIPromise } from '../../../core/api-promise'; +import { RequestOptions } from '../../../internal/request-options'; export class Jobs extends APIResource { configuration: ConfigurationAPI.Configuration = new ConfigurationAPI.Configuration(this._client); @@ -23,7 +24,7 @@ export class Jobs extends APIResource { * }); * ``` */ - create(body: JobCreateParams, options?: Core.RequestOptions): Core.APIPromise { + create(body: JobCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/sandbox/jobs', { body, ...options }); } } diff --git a/src/resources/sandbox/payment.ts b/src/resources/sandbox/payment.ts index cf5727920..5bfb9e8c1 100644 --- a/src/resources/sandbox/payment.ts +++ b/src/resources/sandbox/payment.ts @@ -1,8 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; -import { isRequestOptions } from '../../core'; -import * as Core from '../../core'; +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; export class Payment extends APIResource { /** @@ -13,15 +13,10 @@ export class Payment extends APIResource { * const payment = await client.sandbox.payment.create(); * ``` */ - create(body?: PaymentCreateParams, options?: Core.RequestOptions): Core.APIPromise; - create(options?: Core.RequestOptions): Core.APIPromise; create( - body: PaymentCreateParams | Core.RequestOptions = {}, - options?: Core.RequestOptions, - ): Core.APIPromise { - if (isRequestOptions(body)) { - return this.create({}, body); - } + body: PaymentCreateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { return this._client.post('/sandbox/payment', { body, ...options }); } } diff --git a/src/resources/sandbox/sandbox.ts b/src/resources/sandbox/sandbox.ts index 9e230ca4c..4390b7eb7 100644 --- a/src/resources/sandbox/sandbox.ts +++ b/src/resources/sandbox/sandbox.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../../resource'; +import { APIResource } from '../../core/resource'; import * as CompanyAPI from './company'; import { Company, CompanyUpdateParams, CompanyUpdateResponse } from './company'; import * as DirectoryAPI from './directory'; diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index fffd2d274..a83387f99 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../resource'; +import { APIResource } from '../core/resource'; import * as Shared from './shared'; import * as BenefitsAPI from './hris/benefits/benefits'; import { fromBase64, getRequiredHeader, HeadersLike, toBase64 } from '../core'; diff --git a/src/shims/node.ts b/src/shims/node.ts deleted file mode 100644 index 73df5600c..000000000 --- a/src/shims/node.ts +++ /dev/null @@ -1,50 +0,0 @@ -// @ts-ignore -import * as types from '../_shims/node-types'; -import { setShims } from '../_shims/registry'; -import { getRuntime } from '../_shims/node-runtime'; -setShims(getRuntime()); - -declare module '../_shims/manual-types' { - export namespace manual { - // @ts-ignore - export type Agent = types.Agent; - // @ts-ignore - export import fetch = types.fetch; - // @ts-ignore - export type Request = types.Request; - // @ts-ignore - export type RequestInfo = types.RequestInfo; - // @ts-ignore - export type RequestInit = types.RequestInit; - // @ts-ignore - export type Response = types.Response; - // @ts-ignore - export type ResponseInit = types.ResponseInit; - // @ts-ignore - export type ResponseType = types.ResponseType; - // @ts-ignore - export type BodyInit = types.BodyInit; - // @ts-ignore - export type Headers = types.Headers; - // @ts-ignore - export type HeadersInit = types.HeadersInit; - // @ts-ignore - export type BlobPropertyBag = types.BlobPropertyBag; - // @ts-ignore - export type FilePropertyBag = types.FilePropertyBag; - // @ts-ignore - export type FileFromPathOptions = types.FileFromPathOptions; - // @ts-ignore - export import FormData = types.FormData; - // @ts-ignore - export import File = types.File; - // @ts-ignore - export import Blob = types.Blob; - // @ts-ignore - export type Readable = types.Readable; - // @ts-ignore - export type FsReadStream = types.FsReadStream; - // @ts-ignore - export import ReadableStream = types.ReadableStream; - } -} diff --git a/src/shims/web.ts b/src/shims/web.ts deleted file mode 100644 index f72d78444..000000000 --- a/src/shims/web.ts +++ /dev/null @@ -1,50 +0,0 @@ -// @ts-ignore -import * as types from '../_shims/web-types'; -import { setShims } from '../_shims/registry'; -import { getRuntime } from '../_shims/web-runtime'; -setShims(getRuntime({ manuallyImported: true })); - -declare module '../_shims/manual-types' { - export namespace manual { - // @ts-ignore - export type Agent = types.Agent; - // @ts-ignore - export import fetch = types.fetch; - // @ts-ignore - export type Request = types.Request; - // @ts-ignore - export type RequestInfo = types.RequestInfo; - // @ts-ignore - export type RequestInit = types.RequestInit; - // @ts-ignore - export type Response = types.Response; - // @ts-ignore - export type ResponseInit = types.ResponseInit; - // @ts-ignore - export type ResponseType = types.ResponseType; - // @ts-ignore - export type BodyInit = types.BodyInit; - // @ts-ignore - export type Headers = types.Headers; - // @ts-ignore - export type HeadersInit = types.HeadersInit; - // @ts-ignore - export type BlobPropertyBag = types.BlobPropertyBag; - // @ts-ignore - export type FilePropertyBag = types.FilePropertyBag; - // @ts-ignore - export type FileFromPathOptions = types.FileFromPathOptions; - // @ts-ignore - export import FormData = types.FormData; - // @ts-ignore - export import File = types.File; - // @ts-ignore - export import Blob = types.Blob; - // @ts-ignore - export type Readable = types.Readable; - // @ts-ignore - export type FsReadStream = types.FsReadStream; - // @ts-ignore - export import ReadableStream = types.ReadableStream; - } -} diff --git a/src/uploads.ts b/src/uploads.ts index 8fd2154d4..b2ef64710 100644 --- a/src/uploads.ts +++ b/src/uploads.ts @@ -1,255 +1,2 @@ -import { type RequestOptions } from './core'; -import { - FormData, - File, - type Blob, - type FilePropertyBag, - getMultipartRequestOptions, - type FsReadStream, - isFsReadStream, -} from './_shims/index'; -import { MultipartBody } from './_shims/MultipartBody'; -export { fileFromPath } from './_shims/index'; - -type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | Uint8Array | DataView; -export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | Uint8Array | DataView; - -/** - * Typically, this is a native "File" class. - * - * We provide the {@link toFile} utility to convert a variety of objects - * into the File class. - * - * For convenience, you can also pass a fetch Response, or in Node, - * the result of fs.createReadStream(). - */ -export type Uploadable = FileLike | ResponseLike | FsReadStream; - -/** - * Intended to match web.Blob, node.Blob, node-fetch.Blob, etc. - */ -export interface BlobLike { - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ - readonly size: number; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ - readonly type: string; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ - text(): Promise; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ - slice(start?: number, end?: number): BlobLike; - // unfortunately @types/node-fetch@^2.6.4 doesn't type the arrayBuffer method -} - -/** - * Intended to match web.File, node.File, node-fetch.File, etc. - */ -export interface FileLike extends BlobLike { - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ - readonly lastModified: number; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ - readonly name: string; -} - -/** - * Intended to match web.Response, node.Response, node-fetch.Response, etc. - */ -export interface ResponseLike { - url: string; - blob(): Promise; -} - -export const isResponseLike = (value: any): value is ResponseLike => - value != null && - typeof value === 'object' && - typeof value.url === 'string' && - typeof value.blob === 'function'; - -export const isFileLike = (value: any): value is FileLike => - value != null && - typeof value === 'object' && - typeof value.name === 'string' && - typeof value.lastModified === 'number' && - isBlobLike(value); - -/** - * The BlobLike type omits arrayBuffer() because @types/node-fetch@^2.6.4 lacks it; but this check - * adds the arrayBuffer() method type because it is available and used at runtime - */ -export const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => - value != null && - typeof value === 'object' && - typeof value.size === 'number' && - typeof value.type === 'string' && - typeof value.text === 'function' && - typeof value.slice === 'function' && - typeof value.arrayBuffer === 'function'; - -export const isUploadable = (value: any): value is Uploadable => { - return isFileLike(value) || isResponseLike(value) || isFsReadStream(value); -}; - -export type ToFileInput = Uploadable | Exclude | AsyncIterable; - -/** - * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats - * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s - * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible - * @param {Object=} options additional properties - * @param {string=} options.type the MIME type of the content - * @param {number=} options.lastModified the last modified timestamp - * @returns a {@link File} with the given properties - */ -export async function toFile( - value: ToFileInput | PromiseLike, - name?: string | null | undefined, - options?: FilePropertyBag | undefined, -): Promise { - // If it's a promise, resolve it. - value = await value; - - // If we've been given a `File` we don't need to do anything - if (isFileLike(value)) { - return value; - } - - if (isResponseLike(value)) { - const blob = await value.blob(); - name ||= new URL(value.url).pathname.split(/[\\/]/).pop() ?? 'unknown_file'; - - // we need to convert the `Blob` into an array buffer because the `Blob` class - // that `node-fetch` defines is incompatible with the web standard which results - // in `new File` interpreting it as a string instead of binary data. - const data = isBlobLike(blob) ? [(await blob.arrayBuffer()) as any] : [blob]; - - return new File(data, name, options); - } - - const bits = await getBytes(value); - - name ||= getName(value) ?? 'unknown_file'; - - if (!options?.type) { - const type = (bits[0] as any)?.type; - if (typeof type === 'string') { - options = { ...options, type }; - } - } - - return new File(bits, name, options); -} - -async function getBytes(value: ToFileInput): Promise> { - let parts: Array = []; - if ( - typeof value === 'string' || - ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc. - value instanceof ArrayBuffer - ) { - parts.push(value); - } else if (isBlobLike(value)) { - parts.push(await value.arrayBuffer()); - } else if ( - isAsyncIterableIterator(value) // includes Readable, ReadableStream, etc. - ) { - for await (const chunk of value) { - parts.push(chunk as BlobPart); // TODO, consider validating? - } - } else { - throw new Error( - `Unexpected data type: ${typeof value}; constructor: ${value?.constructor - ?.name}; props: ${propsForError(value)}`, - ); - } - - return parts; -} - -function propsForError(value: any): string { - const props = Object.getOwnPropertyNames(value); - return `[${props.map((p) => `"${p}"`).join(', ')}]`; -} - -function getName(value: any): string | undefined { - return ( - getStringFromMaybeBuffer(value.name) || - getStringFromMaybeBuffer(value.filename) || - // For fs.ReadStream - getStringFromMaybeBuffer(value.path)?.split(/[\\/]/).pop() - ); -} - -const getStringFromMaybeBuffer = (x: string | Buffer | unknown): string | undefined => { - if (typeof x === 'string') return x; - if (typeof Buffer !== 'undefined' && x instanceof Buffer) return String(x); - return undefined; -}; - -const isAsyncIterableIterator = (value: any): value is AsyncIterableIterator => - value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; - -export const isMultipartBody = (body: any): body is MultipartBody => - body && typeof body === 'object' && body.body && body[Symbol.toStringTag] === 'MultipartBody'; - -/** - * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value. - * Otherwise returns the request as is. - */ -export const maybeMultipartFormRequestOptions = async >( - opts: RequestOptions, -): Promise> => { - if (!hasUploadableValue(opts.body)) return opts; - - const form = await createForm(opts.body); - return getMultipartRequestOptions(form, opts); -}; - -export const multipartFormRequestOptions = async >( - opts: RequestOptions, -): Promise> => { - const form = await createForm(opts.body); - return getMultipartRequestOptions(form, opts); -}; - -export const createForm = async >(body: T | undefined): Promise => { - const form = new FormData(); - await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))); - return form; -}; - -const hasUploadableValue = (value: unknown): boolean => { - if (isUploadable(value)) return true; - if (Array.isArray(value)) return value.some(hasUploadableValue); - if (value && typeof value === 'object') { - for (const k in value) { - if (hasUploadableValue((value as any)[k])) return true; - } - } - return false; -}; - -const addFormValue = async (form: FormData, key: string, value: unknown): Promise => { - if (value === undefined) return; - if (value == null) { - throw new TypeError( - `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`, - ); - } - - // TODO: make nested formats configurable - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - form.append(key, String(value)); - } else if (isUploadable(value)) { - const file = await toFile(value); - form.append(key, file as File); - } else if (Array.isArray(value)) { - await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); - } else if (typeof value === 'object') { - await Promise.all( - Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), - ); - } else { - throw new TypeError( - `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`, - ); - } -}; +/** @deprecated Import from ./core/uploads instead */ +export * from './core/uploads'; diff --git a/src/version.ts b/src/version.ts index b44926972..2622794a2 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '6.39.0'; // x-release-please-version +export const VERSION = '6.38.0'; diff --git a/tests/api-resources/access-tokens.test.ts b/tests/api-resources/access-tokens.test.ts index fe0433de7..eaf5bc2ce 100644 --- a/tests/api-resources/access-tokens.test.ts +++ b/tests/api-resources/access-tokens.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', diff --git a/tests/api-resources/account.test.ts b/tests/api-resources/account.test.ts index cce65e150..e9f1bda2d 100644 --- a/tests/api-resources/account.test.ts +++ b/tests/api-resources/account.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource account', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('disconnect: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.account.disconnect({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('introspect', async () => { const responsePromise = client.account.introspect(); const rawResponse = await responsePromise.asResponse(); @@ -37,11 +29,4 @@ describe('resource account', () => { expect(dataAndResponse.data).toBe(response); expect(dataAndResponse.response).toBe(rawResponse); }); - - test('introspect: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.account.introspect({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); }); diff --git a/tests/api-resources/connect/sessions.test.ts b/tests/api-resources/connect/sessions.test.ts index 773a75544..38043b915 100644 --- a/tests/api-resources/connect/sessions.test.ts +++ b/tests/api-resources/connect/sessions.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', diff --git a/tests/api-resources/hris/benefits/benefits.test.ts b/tests/api-resources/hris/benefits/benefits.test.ts index f58ceb111..c9d3a7dc6 100644 --- a/tests/api-resources/hris/benefits/benefits.test.ts +++ b/tests/api-resources/hris/benefits/benefits.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource benefits', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('create: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.hris.benefits.create({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('create: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -54,13 +46,6 @@ describe('resource benefits', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retrieve: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.benefits.retrieve('benefit_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('retrieve: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -83,13 +68,6 @@ describe('resource benefits', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('update: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.benefits.update('benefit_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('update: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -112,13 +90,6 @@ describe('resource benefits', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.hris.benefits.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -140,13 +111,6 @@ describe('resource benefits', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('listSupportedBenefits: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.benefits.listSupportedBenefits({ path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('listSupportedBenefits: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/hris/benefits/individuals.test.ts b/tests/api-resources/hris/benefits/individuals.test.ts index 22c98808c..b148b1056 100644 --- a/tests/api-resources/hris/benefits/individuals.test.ts +++ b/tests/api-resources/hris/benefits/individuals.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource individuals', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('enrollMany: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.benefits.individuals.enrollMany('benefit_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('enrollMany: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -53,8 +45,8 @@ describe('resource individuals', () => { ).rejects.toThrow(Finch.NotFoundError); }); - test('enrolledIds', async () => { - const responsePromise = client.hris.benefits.individuals.enrolledIds('benefit_id'); + test('enrolledIDs', async () => { + const responsePromise = client.hris.benefits.individuals.enrolledIDs('benefit_id'); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -64,17 +56,10 @@ describe('resource individuals', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('enrolledIds: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.benefits.individuals.enrolledIds('benefit_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - - test('enrolledIds: request options and params are passed correctly', async () => { + test('enrolledIDs: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( - client.hris.benefits.individuals.enrolledIds( + client.hris.benefits.individuals.enrolledIDs( 'benefit_id', { entity_ids: ['550e8400-e29b-41d4-a716-446655440000'] }, { path: '/_stainless_unknown_path' }, @@ -93,15 +78,6 @@ describe('resource individuals', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retrieveManyBenefits: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.benefits.individuals.retrieveManyBenefits('benefit_id', { - path: '/_stainless_unknown_path', - }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('retrieveManyBenefits: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -127,13 +103,6 @@ describe('resource individuals', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('unenrollMany: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.benefits.individuals.unenrollMany('benefit_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('unenrollMany: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/hris/company/company.test.ts b/tests/api-resources/hris/company/company.test.ts index 38e056de3..491581e1c 100644 --- a/tests/api-resources/hris/company/company.test.ts +++ b/tests/api-resources/hris/company/company.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource company', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retrieve: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.hris.company.retrieve({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('retrieve: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/hris/company/pay-statement-item/pay-statement-item.test.ts b/tests/api-resources/hris/company/pay-statement-item/pay-statement-item.test.ts index 839efc54f..3cbc8bada 100644 --- a/tests/api-resources/hris/company/pay-statement-item/pay-statement-item.test.ts +++ b/tests/api-resources/hris/company/pay-statement-item/pay-statement-item.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource payStatementItem', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.company.payStatementItem.list({ path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/hris/company/pay-statement-item/rules.test.ts b/tests/api-resources/hris/company/pay-statement-item/rules.test.ts index 1c171c20d..af4708c05 100644 --- a/tests/api-resources/hris/company/pay-statement-item/rules.test.ts +++ b/tests/api-resources/hris/company/pay-statement-item/rules.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource rules', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('create: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.company.payStatementItem.rules.create({ path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('create: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -55,13 +47,6 @@ describe('resource rules', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('update: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.company.payStatementItem.rules.update('rule_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('update: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -84,13 +69,6 @@ describe('resource rules', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.company.payStatementItem.rules.list({ path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -112,13 +90,6 @@ describe('resource rules', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('delete: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.company.payStatementItem.rules.delete('rule_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('delete: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/hris/directory.test.ts b/tests/api-resources/hris/directory.test.ts index 045deca15..92edf3472 100644 --- a/tests/api-resources/hris/directory.test.ts +++ b/tests/api-resources/hris/directory.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource directory', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.hris.directory.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -48,13 +40,6 @@ describe('resource directory', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('listIndividuals: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.hris.directory.listIndividuals({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('listIndividuals: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/hris/documents.test.ts b/tests/api-resources/hris/documents.test.ts index b17cf3457..dcb27c1fb 100644 --- a/tests/api-resources/hris/documents.test.ts +++ b/tests/api-resources/hris/documents.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource documents', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.hris.documents.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -54,13 +46,6 @@ describe('resource documents', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retreive: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.hris.documents.retreive('document_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('retreive: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/hris/employments.test.ts b/tests/api-resources/hris/employments.test.ts index c264064ee..9b5d4b89b 100644 --- a/tests/api-resources/hris/employments.test.ts +++ b/tests/api-resources/hris/employments.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', diff --git a/tests/api-resources/hris/individuals.test.ts b/tests/api-resources/hris/individuals.test.ts index 8aa5239bd..e0449a67c 100644 --- a/tests/api-resources/hris/individuals.test.ts +++ b/tests/api-resources/hris/individuals.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource individuals', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retrieveMany: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.hris.individuals.retrieveMany({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('retrieveMany: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/hris/pay-statements.test.ts b/tests/api-resources/hris/pay-statements.test.ts index 03de31636..8b9cd7720 100644 --- a/tests/api-resources/hris/pay-statements.test.ts +++ b/tests/api-resources/hris/pay-statements.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', diff --git a/tests/api-resources/hris/payments.test.ts b/tests/api-resources/hris/payments.test.ts index f083b49c7..f2c5d1cd4 100644 --- a/tests/api-resources/hris/payments.test.ts +++ b/tests/api-resources/hris/payments.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', diff --git a/tests/api-resources/jobs/automated.test.ts b/tests/api-resources/jobs/automated.test.ts index a199923fd..64e3288bd 100644 --- a/tests/api-resources/jobs/automated.test.ts +++ b/tests/api-resources/jobs/automated.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -35,13 +34,6 @@ describe('resource automated', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retrieve: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.jobs.automated.retrieve('job_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('list', async () => { const responsePromise = client.jobs.automated.list(); const rawResponse = await responsePromise.asResponse(); @@ -53,13 +45,6 @@ describe('resource automated', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.jobs.automated.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/jobs/manual.test.ts b/tests/api-resources/jobs/manual.test.ts index c19129280..a12570d2d 100644 --- a/tests/api-resources/jobs/manual.test.ts +++ b/tests/api-resources/jobs/manual.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -19,11 +18,4 @@ describe('resource manual', () => { expect(dataAndResponse.data).toBe(response); expect(dataAndResponse.response).toBe(rawResponse); }); - - test('retrieve: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.jobs.manual.retrieve('job_id', { path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); }); diff --git a/tests/api-resources/payroll/pay-groups.test.ts b/tests/api-resources/payroll/pay-groups.test.ts index b42df448b..0839c4cc5 100644 --- a/tests/api-resources/payroll/pay-groups.test.ts +++ b/tests/api-resources/payroll/pay-groups.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource payGroups', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retrieve: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.payroll.payGroups.retrieve('pay_group_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('retrieve: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( @@ -49,13 +41,6 @@ describe('resource payGroups', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.payroll.payGroups.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/providers.test.ts b/tests/api-resources/providers.test.ts index a9f889560..3ed1bb1e9 100644 --- a/tests/api-resources/providers.test.ts +++ b/tests/api-resources/providers.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -19,11 +18,4 @@ describe('resource providers', () => { expect(dataAndResponse.data).toBe(response); expect(dataAndResponse.response).toBe(rawResponse); }); - - test('list: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.providers.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); }); diff --git a/tests/api-resources/request-forwarding.test.ts b/tests/api-resources/request-forwarding.test.ts index cdc8b7143..e035b1b8f 100644 --- a/tests/api-resources/request-forwarding.test.ts +++ b/tests/api-resources/request-forwarding.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', diff --git a/tests/api-resources/sandbox/company.test.ts b/tests/api-resources/sandbox/company.test.ts index f24175ac3..5c0427bae 100644 --- a/tests/api-resources/sandbox/company.test.ts +++ b/tests/api-resources/sandbox/company.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', diff --git a/tests/api-resources/sandbox/connections/accounts.test.ts b/tests/api-resources/sandbox/connections/accounts.test.ts index 51fcbe248..8772382dd 100644 --- a/tests/api-resources/sandbox/connections/accounts.test.ts +++ b/tests/api-resources/sandbox/connections/accounts.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -45,13 +44,6 @@ describe('resource accounts', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('update: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.sandbox.connections.accounts.update({ path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('update: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/sandbox/connections/connections.test.ts b/tests/api-resources/sandbox/connections/connections.test.ts index 3c4b88093..65ef52fd2 100644 --- a/tests/api-resources/sandbox/connections/connections.test.ts +++ b/tests/api-resources/sandbox/connections/connections.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', diff --git a/tests/api-resources/sandbox/directory.test.ts b/tests/api-resources/sandbox/directory.test.ts index b84558f90..04af5d12b 100644 --- a/tests/api-resources/sandbox/directory.test.ts +++ b/tests/api-resources/sandbox/directory.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,68 +19,63 @@ describe('resource directory', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('create: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.sandbox.directory.create({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('create: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( client.sandbox.directory.create( - [ - { - class_code: 'class_code', - custom_fields: [{ name: 'name', value: {} }], - department: { name: 'name' }, - dob: 'dob', - emails: [{ data: 'data', type: 'work' }], - employment: { subtype: 'full_time', type: 'employee' }, - employment_status: 'active', - encrypted_ssn: 'encrypted_ssn', - end_date: 'end_date', - ethnicity: 'asian', - first_name: 'first_name', - gender: 'female', - income: { amount: 0, currency: 'currency', effective_date: '2019-12-27', unit: 'yearly' }, - income_history: [ - { amount: 0, currency: 'currency', effective_date: '2019-12-27', unit: 'yearly' }, - ], - is_active: true, - last_name: 'last_name', - latest_rehire_date: 'latest_rehire_date', - location: { - city: 'city', - country: 'country', - line1: 'line1', - line2: 'line2', - postal_code: 'postal_code', - state: 'state', - name: 'name', - source_id: 'source_id', - }, - manager: { id: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e' }, - middle_name: 'middle_name', - phone_numbers: [{ data: 'data', type: 'work' }], - preferred_name: 'preferred_name', - residence: { - city: 'city', - country: 'country', - line1: 'line1', - line2: 'line2', - postal_code: 'postal_code', - state: 'state', - name: 'name', + { + body: [ + { + class_code: 'class_code', + custom_fields: [{ name: 'name', value: {} }], + department: { name: 'name' }, + dob: 'dob', + emails: [{ data: 'data', type: 'work' }], + employment: { subtype: 'full_time', type: 'employee' }, + employment_status: 'active', + encrypted_ssn: 'encrypted_ssn', + end_date: 'end_date', + ethnicity: 'asian', + first_name: 'first_name', + gender: 'female', + income: { amount: 0, currency: 'currency', effective_date: '2019-12-27', unit: 'yearly' }, + income_history: [ + { amount: 0, currency: 'currency', effective_date: '2019-12-27', unit: 'yearly' }, + ], + is_active: true, + last_name: 'last_name', + latest_rehire_date: 'latest_rehire_date', + location: { + city: 'city', + country: 'country', + line1: 'line1', + line2: 'line2', + postal_code: 'postal_code', + state: 'state', + name: 'name', + source_id: 'source_id', + }, + manager: { id: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e' }, + middle_name: 'middle_name', + phone_numbers: [{ data: 'data', type: 'work' }], + preferred_name: 'preferred_name', + residence: { + city: 'city', + country: 'country', + line1: 'line1', + line2: 'line2', + postal_code: 'postal_code', + state: 'state', + name: 'name', + source_id: 'source_id', + }, source_id: 'source_id', + ssn: 'ssn', + start_date: 'start_date', + title: 'title', }, - source_id: 'source_id', - ssn: 'ssn', - start_date: 'start_date', - title: 'title', - }, - ], + ], + }, { path: '/_stainless_unknown_path' }, ), ).rejects.toThrow(Finch.NotFoundError); diff --git a/tests/api-resources/sandbox/employment.test.ts b/tests/api-resources/sandbox/employment.test.ts index 6c5239b1b..654bc3e5c 100644 --- a/tests/api-resources/sandbox/employment.test.ts +++ b/tests/api-resources/sandbox/employment.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource employment', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('update: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.sandbox.employment.update('individual_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('update: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/sandbox/individual.test.ts b/tests/api-resources/sandbox/individual.test.ts index 2aca124f4..1ecb821e5 100644 --- a/tests/api-resources/sandbox/individual.test.ts +++ b/tests/api-resources/sandbox/individual.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource individual', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('update: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.sandbox.individual.update('individual_id', { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('update: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/api-resources/sandbox/jobs/configuration.test.ts b/tests/api-resources/sandbox/jobs/configuration.test.ts index 1ed2c8c6e..0ff598d3c 100644 --- a/tests/api-resources/sandbox/jobs/configuration.test.ts +++ b/tests/api-resources/sandbox/jobs/configuration.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource configuration', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('retrieve: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.sandbox.jobs.configuration.retrieve({ path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Finch.NotFoundError); - }); - test('update: only required params', async () => { const responsePromise = client.sandbox.jobs.configuration.update({ completion_status: 'complete', diff --git a/tests/api-resources/sandbox/jobs/jobs.test.ts b/tests/api-resources/sandbox/jobs/jobs.test.ts index 32b50c63a..31aa67c00 100644 --- a/tests/api-resources/sandbox/jobs/jobs.test.ts +++ b/tests/api-resources/sandbox/jobs/jobs.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', diff --git a/tests/api-resources/sandbox/payment.test.ts b/tests/api-resources/sandbox/payment.test.ts index 575dbf28c..8a669841b 100644 --- a/tests/api-resources/sandbox/payment.test.ts +++ b/tests/api-resources/sandbox/payment.test.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { Response } from 'node-fetch'; const client = new Finch({ accessToken: 'My Access Token', @@ -20,13 +19,6 @@ describe('resource payment', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('create: request options instead of params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(client.sandbox.payment.create({ path: '/_stainless_unknown_path' })).rejects.toThrow( - Finch.NotFoundError, - ); - }); - test('create: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( diff --git a/tests/base64.test.ts b/tests/base64.test.ts new file mode 100644 index 000000000..a5cdcf871 --- /dev/null +++ b/tests/base64.test.ts @@ -0,0 +1,80 @@ +import { fromBase64, toBase64 } from '@tryfinch/finch-api/internal/utils/base64'; + +describe.each(['Buffer', 'atob'])('with %s', (mode) => { + let originalBuffer: BufferConstructor; + beforeAll(() => { + if (mode === 'atob') { + originalBuffer = globalThis.Buffer; + // @ts-expect-error Can't assign undefined to BufferConstructor + delete globalThis.Buffer; + } + }); + afterAll(() => { + if (mode === 'atob') { + globalThis.Buffer = originalBuffer; + } + }); + test('toBase64', () => { + const testCases = [ + { + input: 'hello world', + expected: 'aGVsbG8gd29ybGQ=', + }, + { + input: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), + expected: 'aGVsbG8gd29ybGQ=', + }, + { + input: undefined, + expected: '', + }, + { + input: new Uint8Array([ + 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, + 193, 71, + ]), + expected: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', + }, + { + input: '✓', + expected: '4pyT', + }, + { + input: new Uint8Array([226, 156, 147]), + expected: '4pyT', + }, + ]; + + testCases.forEach(({ input, expected }) => { + expect(toBase64(input)).toBe(expected); + }); + }); + + test('fromBase64', () => { + const testCases = [ + { + input: 'aGVsbG8gd29ybGQ=', + expected: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), + }, + { + input: '', + expected: new Uint8Array([]), + }, + { + input: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', + expected: new Uint8Array([ + 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, + 193, 71, + ]), + }, + { + input: '4pyT', + expected: new Uint8Array([226, 156, 147]), + }, + ]; + + testCases.forEach(({ input, expected }) => { + expect(fromBase64(input)).toEqual(expected); + }); + }); +}); diff --git a/tests/buildHeaders.test.ts b/tests/buildHeaders.test.ts new file mode 100644 index 000000000..56115d91f --- /dev/null +++ b/tests/buildHeaders.test.ts @@ -0,0 +1,88 @@ +import { inspect } from 'node:util'; +import { buildHeaders, type HeadersLike, type NullableHeaders } from '@tryfinch/finch-api/internal/headers'; + +function inspectNullableHeaders(headers: NullableHeaders) { + return `NullableHeaders {${[ + ...[...headers.values.entries()].map(([name, value]) => ` ${inspect(name)}: ${inspect(value)}`), + ...[...headers.nulls].map((name) => ` ${inspect(name)}: null`), + ].join(', ')} }`; +} + +describe('buildHeaders', () => { + const cases: [HeadersLike[], string][] = [ + [[new Headers({ 'content-type': 'text/plain' })], `NullableHeaders { 'content-type': 'text/plain' }`], + [ + [ + { + 'content-type': 'text/plain', + }, + { + 'Content-Type': undefined, + }, + ], + `NullableHeaders { 'content-type': 'text/plain' }`, + ], + [ + [ + { + 'content-type': 'text/plain', + }, + { + 'Content-Type': null, + }, + ], + `NullableHeaders { 'content-type': null }`, + ], + [ + [ + { + cookie: 'name1=value1', + Cookie: 'name2=value2', + }, + ], + `NullableHeaders { 'cookie': 'name2=value2' }`, + ], + [ + [ + { + cookie: 'name1=value1', + Cookie: undefined, + }, + ], + `NullableHeaders { 'cookie': 'name1=value1' }`, + ], + [ + [ + { + cookie: ['name1=value1', 'name2=value2'], + }, + ], + `NullableHeaders { 'cookie': 'name1=value1; name2=value2' }`, + ], + [ + [ + { + 'x-foo': ['name1=value1', 'name2=value2'], + }, + ], + `NullableHeaders { 'x-foo': 'name1=value1, name2=value2' }`, + ], + [ + [ + [ + ['cookie', 'name1=value1'], + ['cookie', 'name2=value2'], + ['Cookie', 'name3=value3'], + ], + ], + `NullableHeaders { 'cookie': 'name1=value1; name2=value2; name3=value3' }`, + ], + [[undefined], `NullableHeaders { }`], + [[null], `NullableHeaders { }`], + ]; + for (const [input, expected] of cases) { + test(expected, () => { + expect(inspectNullableHeaders(buildHeaders(input))).toEqual(expected); + }); + } +}); diff --git a/tests/form.test.ts b/tests/form.test.ts index 31d59c7f1..81c2f9595 100644 --- a/tests/form.test.ts +++ b/tests/form.test.ts @@ -1,65 +1,85 @@ -import { multipartFormRequestOptions, createForm } from '@tryfinch/finch-api/core'; -import { Blob } from '@tryfinch/finch-api/_shims/index'; -import { toFile } from '@tryfinch/finch-api'; +import { multipartFormRequestOptions, createForm } from '@tryfinch/finch-api/internal/uploads'; +import { toFile } from '@tryfinch/finch-api/core/uploads'; describe('form data validation', () => { test('valid values do not error', async () => { - await multipartFormRequestOptions({ - body: { - foo: 'foo', - string: 1, - bool: true, - file: await toFile(Buffer.from('some-content')), - blob: new Blob(['Some content'], { type: 'text/plain' }), + await multipartFormRequestOptions( + { + body: { + foo: 'foo', + string: 1, + bool: true, + file: await toFile(Buffer.from('some-content')), + blob: new Blob(['Some content'], { type: 'text/plain' }), + }, }, - }); + fetch, + ); }); test('null', async () => { await expect(() => - multipartFormRequestOptions({ - body: { - null: null, + multipartFormRequestOptions( + { + body: { + null: null, + }, }, - }), + fetch, + ), ).rejects.toThrow(TypeError); }); test('undefined is stripped', async () => { - const form = await createForm({ - foo: undefined, - bar: 'baz', - }); + const form = await createForm( + { + foo: undefined, + bar: 'baz', + }, + fetch, + ); expect(form.has('foo')).toBe(false); expect(form.get('bar')).toBe('baz'); }); test('nested undefined property is stripped', async () => { - const form = await createForm({ - bar: { - baz: undefined, + const form = await createForm( + { + bar: { + baz: undefined, + }, }, - }); + fetch, + ); expect(Array.from(form.entries())).toEqual([]); - const form2 = await createForm({ - bar: { - foo: 'string', - baz: undefined, + const form2 = await createForm( + { + bar: { + foo: 'string', + baz: undefined, + }, }, - }); + fetch, + ); expect(Array.from(form2.entries())).toEqual([['bar[foo]', 'string']]); }); test('nested undefined array item is stripped', async () => { - const form = await createForm({ - bar: [undefined, undefined], - }); + const form = await createForm( + { + bar: [undefined, undefined], + }, + fetch, + ); expect(Array.from(form.entries())).toEqual([]); - const form2 = await createForm({ - bar: [undefined, 'foo'], - }); + const form2 = await createForm( + { + bar: [undefined, 'foo'], + }, + fetch, + ); expect(Array.from(form2.entries())).toEqual([['bar[]', 'foo']]); }); }); diff --git a/tests/index.test.ts b/tests/index.test.ts index e765e131b..33e714de9 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,9 +1,11 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import { APIPromise } from '@tryfinch/finch-api/core/api-promise'; + +import util from 'node:util'; import Finch from '@tryfinch/finch-api'; import { APIUserAbortError } from '@tryfinch/finch-api'; -import { Headers } from '@tryfinch/finch-api/core'; -import defaultFetch, { Response, type RequestInit, type RequestInfo } from 'node-fetch'; +const defaultFetch = fetch; describe('instantiate client', () => { const env = process.env; @@ -11,8 +13,6 @@ describe('instantiate client', () => { beforeEach(() => { jest.resetModules(); process.env = { ...env }; - - console.warn = jest.fn(); }); afterEach(() => { @@ -28,7 +28,7 @@ describe('instantiate client', () => { test('they are used in the request', async () => { const { req } = await client.buildRequest({ path: '/foo', method: 'post' }); - expect((req.headers as Headers)['x-my-default-header']).toEqual('2'); + expect(req.headers.get('x-my-default-header')).toEqual('2'); }); test('can ignore `undefined` and leave the default', async () => { @@ -37,7 +37,7 @@ describe('instantiate client', () => { method: 'post', headers: { 'X-My-Default-Header': undefined }, }); - expect((req.headers as Headers)['x-my-default-header']).toEqual('2'); + expect(req.headers.get('x-my-default-header')).toEqual('2'); }); test('can be removed with `null`', async () => { @@ -46,7 +46,136 @@ describe('instantiate client', () => { method: 'post', headers: { 'X-My-Default-Header': null }, }); - expect(req.headers as Headers).not.toHaveProperty('x-my-default-header'); + expect(req.headers.has('x-my-default-header')).toBe(false); + }); + }); + describe('logging', () => { + const env = process.env; + + beforeEach(() => { + process.env = { ...env }; + process.env['FINCH_LOG'] = undefined; + }); + + afterEach(() => { + process.env = env; + }); + + const forceAPIResponseForClient = async (client: Finch) => { + await new APIPromise( + client, + Promise.resolve({ + response: new Response(), + controller: new AbortController(), + requestLogID: 'log_000000', + retryOfRequestLogID: undefined, + startTime: Date.now(), + options: { + method: 'get', + path: '/', + }, + }), + ); + }; + + test('debug logs when log level is debug', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + const client = new Finch({ logger: logger, logLevel: 'debug', accessToken: 'My Access Token' }); + + await forceAPIResponseForClient(client); + expect(debugMock).toHaveBeenCalled(); + }); + + test('default logLevel is warn', async () => { + const client = new Finch({ accessToken: 'My Access Token' }); + expect(client.logLevel).toBe('warn'); + }); + + test('debug logs are skipped when log level is info', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + const client = new Finch({ logger: logger, logLevel: 'info', accessToken: 'My Access Token' }); + + await forceAPIResponseForClient(client); + expect(debugMock).not.toHaveBeenCalled(); + }); + + test('debug logs happen with debug env var', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + process.env['FINCH_LOG'] = 'debug'; + const client = new Finch({ logger: logger, accessToken: 'My Access Token' }); + expect(client.logLevel).toBe('debug'); + + await forceAPIResponseForClient(client); + expect(debugMock).toHaveBeenCalled(); + }); + + test('warn when env var level is invalid', async () => { + const warnMock = jest.fn(); + const logger = { + debug: jest.fn(), + info: jest.fn(), + warn: warnMock, + error: jest.fn(), + }; + + process.env['FINCH_LOG'] = 'not a log level'; + const client = new Finch({ logger: logger, accessToken: 'My Access Token' }); + expect(client.logLevel).toBe('warn'); + expect(warnMock).toHaveBeenCalledWith( + 'process.env[\'FINCH_LOG\'] was set to "not a log level", expected one of ["off","error","warn","info","debug"]', + ); + }); + + test('client log level overrides env var', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + process.env['FINCH_LOG'] = 'debug'; + const client = new Finch({ logger: logger, logLevel: 'off', accessToken: 'My Access Token' }); + + await forceAPIResponseForClient(client); + expect(debugMock).not.toHaveBeenCalled(); + }); + + test('no warning logged for invalid env var level + valid client level', async () => { + const warnMock = jest.fn(); + const logger = { + debug: jest.fn(), + info: jest.fn(), + warn: warnMock, + error: jest.fn(), + }; + + process.env['FINCH_LOG'] = 'not a log level'; + const client = new Finch({ logger: logger, logLevel: 'debug', accessToken: 'My Access Token' }); + expect(client.logLevel).toBe('debug'); + expect(warnMock).not.toHaveBeenCalled(); }); }); @@ -133,7 +262,7 @@ describe('instantiate client', () => { test('normalized method', async () => { let capturedRequest: RequestInit | undefined; - const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { capturedRequest = init; return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); }; @@ -223,22 +352,86 @@ describe('instantiate client', () => { const client2 = new Finch({ accessToken: 'My Access Token' }); expect(client2.maxRetries).toEqual(2); }); -}); -describe('request building', () => { - const client = new Finch({ accessToken: 'My Access Token' }); + describe('withOptions', () => { + test('creates a new client with overridden options', async () => { + const client = new Finch({ + baseURL: 'http://localhost:5000/', + maxRetries: 3, + accessToken: 'My Access Token', + }); + + const newClient = client.withOptions({ + maxRetries: 5, + baseURL: 'http://localhost:5001/', + }); - describe('Content-Length', () => { - test('handles multi-byte characters', async () => { - const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: { value: '—' } }); - expect((req.headers as Record)['content-length']).toEqual('20'); + // Verify the new client has updated options + expect(newClient.maxRetries).toEqual(5); + expect(newClient.baseURL).toEqual('http://localhost:5001/'); + + // Verify the original client is unchanged + expect(client.maxRetries).toEqual(3); + expect(client.baseURL).toEqual('http://localhost:5000/'); + + // Verify it's a different instance + expect(newClient).not.toBe(client); + expect(newClient.constructor).toBe(client.constructor); + }); + + test('inherits options from the parent client', async () => { + const client = new Finch({ + baseURL: 'http://localhost:5000/', + defaultHeaders: { 'X-Test-Header': 'test-value' }, + defaultQuery: { 'test-param': 'test-value' }, + accessToken: 'My Access Token', + }); + + const newClient = client.withOptions({ + baseURL: 'http://localhost:5001/', + }); + + // Test inherited options remain the same + expect(newClient.buildURL('/foo', null)).toEqual('http://localhost:5001/foo?test-param=test-value'); + + const { req } = await newClient.buildRequest({ path: '/foo', method: 'get' }); + expect(req.headers.get('x-test-header')).toEqual('test-value'); }); - test('handles standard characters', async () => { - const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: { value: 'hello' } }); - expect((req.headers as Record)['content-length']).toEqual('22'); + test('respects runtime property changes when creating new client', () => { + const client = new Finch({ + baseURL: 'http://localhost:5000/', + timeout: 1000, + accessToken: 'My Access Token', + }); + + // Modify the client properties directly after creation + client.baseURL = 'http://localhost:6000/'; + client.timeout = 2000; + + // Create a new client with withOptions + const newClient = client.withOptions({ + maxRetries: 10, + }); + + // Verify the new client uses the updated properties, not the original ones + expect(newClient.baseURL).toEqual('http://localhost:6000/'); + expect(newClient.timeout).toEqual(2000); + expect(newClient.maxRetries).toEqual(10); + + // Original client should still have its modified properties + expect(client.baseURL).toEqual('http://localhost:6000/'); + expect(client.timeout).toEqual(2000); + expect(client.maxRetries).not.toEqual(10); + + // Verify URL building uses the updated baseURL + expect(newClient.buildURL('/bar', null)).toEqual('http://localhost:6000/bar'); }); }); +}); + +describe('request building', () => { + const client = new Finch({ accessToken: 'My Access Token' }); describe('custom headers', () => { test('handles undefined', async () => { @@ -248,18 +441,92 @@ describe('request building', () => { body: { value: 'hello' }, headers: { 'X-Foo': 'baz', 'x-foo': 'bar', 'x-Foo': undefined, 'x-baz': 'bam', 'X-Baz': null }, }); - expect((req.headers as Record)['x-foo']).toEqual('bar'); - expect((req.headers as Record)['x-Foo']).toEqual(undefined); - expect((req.headers as Record)['X-Foo']).toEqual(undefined); - expect((req.headers as Record)['x-baz']).toEqual(undefined); + expect(req.headers.get('x-foo')).toEqual('bar'); + expect(req.headers.get('x-Foo')).toEqual('bar'); + expect(req.headers.get('X-Foo')).toEqual('bar'); + expect(req.headers.get('x-baz')).toEqual(null); }); }); }); +describe('default encoder', () => { + const client = new Finch({ accessToken: 'My Access Token' }); + + class Serializable { + toJSON() { + return { $type: 'Serializable' }; + } + } + class Collection { + #things: T[]; + constructor(things: T[]) { + this.#things = Array.from(things); + } + toJSON() { + return Array.from(this.#things); + } + [Symbol.iterator]() { + return this.#things[Symbol.iterator]; + } + } + for (const jsonValue of [{}, [], { __proto__: null }, new Serializable(), new Collection(['item'])]) { + test(`serializes ${util.inspect(jsonValue)} as json`, async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + body: jsonValue, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual('application/json'); + expect(req.body).toBe(JSON.stringify(jsonValue)); + }); + } + + const encoder = new TextEncoder(); + const asyncIterable = (async function* () { + yield encoder.encode('a\n'); + yield encoder.encode('b\n'); + yield encoder.encode('c\n'); + })(); + for (const streamValue of [ + [encoder.encode('a\nb\nc\n')][Symbol.iterator](), + new Response('a\nb\nc\n').body, + asyncIterable, + ]) { + test(`converts ${util.inspect(streamValue)} to ReadableStream`, async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + body: streamValue, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual(null); + expect(req.body).toBeInstanceOf(ReadableStream); + expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); + }); + } + + test(`can set content-type for ReadableStream`, async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + body: new Response('a\nb\nc\n').body, + headers: { 'Content-Type': 'text/plain' }, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual('text/plain'); + expect(req.body).toBeInstanceOf(ReadableStream); + expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); + }); +}); + describe('retries', () => { test('retry on timeout', async () => { let count = 0; - const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise => { + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { if (count++ === 0) { return new Promise( (resolve, reject) => signal?.addEventListener('abort', () => reject(new Error('timed out'))), @@ -284,7 +551,7 @@ describe('retries', () => { test('retry count header', async () => { let count = 0; let capturedRequest: RequestInit | undefined; - const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { count++; if (count <= 2) { return new Response(undefined, { @@ -302,14 +569,14 @@ describe('retries', () => { expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - expect((capturedRequest!.headers as Headers)['x-stainless-retry-count']).toEqual('2'); + expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('2'); expect(count).toEqual(3); }); test('omit retry count header', async () => { let count = 0; let capturedRequest: RequestInit | undefined; - const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { count++; if (count <= 2) { return new Response(undefined, { @@ -332,13 +599,13 @@ describe('retries', () => { }), ).toEqual({ a: 1 }); - expect(capturedRequest!.headers as Headers).not.toHaveProperty('x-stainless-retry-count'); + expect((capturedRequest!.headers as Headers).has('x-stainless-retry-count')).toBe(false); }); test('omit retry count header by default', async () => { let count = 0; let capturedRequest: RequestInit | undefined; - const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { count++; if (count <= 2) { return new Response(undefined, { @@ -371,7 +638,7 @@ describe('retries', () => { test('overwrite retry count header', async () => { let count = 0; let capturedRequest: RequestInit | undefined; - const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { count++; if (count <= 2) { return new Response(undefined, { @@ -394,12 +661,15 @@ describe('retries', () => { }), ).toEqual({ a: 1 }); - expect((capturedRequest!.headers as Headers)['x-stainless-retry-count']).toBe('42'); + expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('42'); }); test('retry on 429 with retry-after', async () => { let count = 0; - const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise => { + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { if (count++ === 0) { return new Response(undefined, { status: 429, @@ -426,7 +696,10 @@ describe('retries', () => { test('retry on 429 with retry-after-ms', async () => { let count = 0; - const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise => { + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { if (count++ === 0) { return new Response(undefined, { status: 429, diff --git a/tests/path.test.ts b/tests/path.test.ts new file mode 100644 index 000000000..b0ca531d9 --- /dev/null +++ b/tests/path.test.ts @@ -0,0 +1,462 @@ +import { createPathTagFunction, encodeURIPath } from '@tryfinch/finch-api/internal/utils/path'; +import { inspect } from 'node:util'; +import { runInNewContext } from 'node:vm'; + +describe('path template tag function', () => { + test('validates input', () => { + const testParams = ['', '.', '..', 'x', '%2e', '%2E', '%2e%2e', '%2E%2e', '%2e%2E', '%2E%2E']; + const testCases = [ + ['/path_params/', '/a'], + ['/path_params/', '/'], + ['/path_params/', ''], + ['', '/a'], + ['', '/'], + ['', ''], + ['a'], + [''], + ['/path_params/', ':initiate'], + ['/path_params/', '.json'], + ['/path_params/', '?beta=true'], + ['/path_params/', '.?beta=true'], + ['/path_params/', '/', '/download'], + ['/path_params/', '-', '/download'], + ['/path_params/', '', '/download'], + ['/path_params/', '.', '/download'], + ['/path_params/', '..', '/download'], + ['/plain/path'], + ]; + + function paramPermutations(len: number): string[][] { + if (len === 0) return []; + if (len === 1) return testParams.map((e) => [e]); + const rest = paramPermutations(len - 1); + return testParams.flatMap((e) => rest.map((r) => [e, ...r])); + } + + // We need to test how %2E is handled, so we use a custom encoder that does no escaping. + const rawPath = createPathTagFunction((s) => s); + + const emptyObject = {}; + const mathObject = Math; + const numberObject = new Number(); + const stringObject = new String(); + const basicClass = new (class {})(); + const classWithToString = new (class { + toString() { + return 'ok'; + } + })(); + + // Invalid values + expect(() => rawPath`/a/${null}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Null is not a valid path parameter\n' + + '/a/null/b\n' + + ' ^^^^', + ); + expect(() => rawPath`/a/${undefined}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Undefined is not a valid path parameter\n' + + '/a/undefined/b\n' + + ' ^^^^^^^^^', + ); + expect(() => rawPath`/a/${emptyObject}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/a/[object Object]/b\n' + + ' ^^^^^^^^^^^^^^^', + ); + expect(() => rawPath`?${mathObject}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Math is not a valid path parameter\n' + + '?[object Math]\n' + + ' ^^^^^^^^^^^^^', + ); + expect(() => rawPath`/${basicClass}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/[object Object]\n' + + ' ^^^^^^^^^^^^^^', + ); + expect(() => rawPath`/../${''}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '/../\n' + + ' ^^', + ); + expect(() => rawPath`/../${{}}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + 'Value of type Object is not a valid path parameter\n' + + '/../[object Object]\n' + + ' ^^ ^^^^^^^^^^^^^^', + ); + + // Valid values + expect(rawPath`/${0}`).toBe('/0'); + expect(rawPath`/${''}`).toBe('/'); + expect(rawPath`/${numberObject}`).toBe('/0'); + expect(rawPath`${stringObject}/`).toBe('/'); + expect(rawPath`/${classWithToString}`).toBe('/ok'); + + // We need to check what happens with cross-realm values, which we might get from + // Jest or other frames in a browser. + + const newRealm = runInNewContext('globalThis'); + expect(newRealm.Object).not.toBe(Object); + + const crossRealmObject = newRealm.Object(); + const crossRealmMathObject = newRealm.Math; + const crossRealmNumber = new newRealm.Number(); + const crossRealmString = new newRealm.String(); + const crossRealmClass = new (class extends newRealm.Object {})(); + const crossRealmClassWithToString = new (class extends newRealm.Object { + toString() { + return 'ok'; + } + })(); + + // Invalid cross-realm values + expect(() => rawPath`/a/${crossRealmObject}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/a/[object Object]/b\n' + + ' ^^^^^^^^^^^^^^^', + ); + expect(() => rawPath`?${crossRealmMathObject}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Math is not a valid path parameter\n' + + '?[object Math]\n' + + ' ^^^^^^^^^^^^^', + ); + expect(() => rawPath`/${crossRealmClass}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/[object Object]\n' + + ' ^^^^^^^^^^^^^^^', + ); + + // Valid cross-realm values + expect(rawPath`/${crossRealmNumber}`).toBe('/0'); + expect(rawPath`${crossRealmString}/`).toBe('/'); + expect(rawPath`/${crossRealmClassWithToString}`).toBe('/ok'); + + const results: { + [pathParts: string]: { + [params: string]: { valid: boolean; result?: string; error?: string }; + }; + } = {}; + + for (const pathParts of testCases) { + const pathResults: Record = {}; + results[JSON.stringify(pathParts)] = pathResults; + for (const params of paramPermutations(pathParts.length - 1)) { + const stringRaw = String.raw({ raw: pathParts }, ...params); + const plainString = String.raw( + { raw: pathParts.map((e) => e.replace(/\./g, 'x')) }, + ...params.map((e) => 'X'.repeat(e.length)), + ); + const normalizedStringRaw = new URL(stringRaw, 'https://example.com').href; + const normalizedPlainString = new URL(plainString, 'https://example.com').href; + const pathResultsKey = JSON.stringify(params); + try { + const result = rawPath(pathParts, ...params); + expect(result).toBe(stringRaw); + // there are no special segments, so the length of the normalized path is + // equal to the length of the normalized plain path. + expect(normalizedStringRaw.length).toBe(normalizedPlainString.length); + pathResults[pathResultsKey] = { + valid: true, + result, + }; + } catch (e) { + const error = String(e); + expect(error).toMatch(/Path parameters result in path with invalid segment/); + // there are special segments, so the length of the normalized path is + // different than the length of the normalized plain path. + expect(normalizedStringRaw.length).not.toBe(normalizedPlainString.length); + pathResults[pathResultsKey] = { + valid: false, + error, + }; + } + } + } + + expect(results).toMatchObject({ + '["/path_params/","/a"]': { + '["x"]': { valid: true, result: '/path_params/x/a' }, + '[""]': { valid: true, result: '/path_params//a' }, + '["%2E%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2e/a\n' + + ' ^^^^^^', + }, + '["%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E/a\n' + + ' ^^^', + }, + }, + '["/path_params/","/"]': { + '["x"]': { valid: true, result: '/path_params/x/' }, + '[""]': { valid: true, result: '/path_params//' }, + '["%2e%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2e%2E/\n' + + ' ^^^^^^', + }, + '["%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2e/\n' + + ' ^^^', + }, + }, + '["/path_params/",""]': { + '[""]': { valid: true, result: '/path_params/' }, + '["x"]': { valid: true, result: '/path_params/x' }, + '["%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E\n' + + ' ^^^', + }, + '["%2E%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2e\n' + + ' ^^^^^^', + }, + }, + '["","/a"]': { + '[""]': { valid: true, result: '/a' }, + '["x"]': { valid: true, result: 'x/a' }, + '["%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n%2E/a\n^^^', + }, + '["%2e%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + + '%2e%2E/a\n' + + '^^^^^^', + }, + }, + '["","/"]': { + '["x"]': { valid: true, result: 'x/' }, + '[""]': { valid: true, result: '/' }, + '["%2E%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + '%2E%2e/\n' + + '^^^^^^', + }, + '["."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + './\n^', + }, + }, + '["",""]': { + '[""]': { valid: true, result: '' }, + '["x"]': { valid: true, result: 'x' }, + '[".."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '..\n^^', + }, + '["."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + '.\n^', + }, + }, + '["a"]': {}, + '[""]': {}, + '["/path_params/",":initiate"]': { + '[""]': { valid: true, result: '/path_params/:initiate' }, + '["."]': { valid: true, result: '/path_params/.:initiate' }, + }, + '["/path_params/",".json"]': { + '["x"]': { valid: true, result: '/path_params/x.json' }, + '["."]': { valid: true, result: '/path_params/..json' }, + }, + '["/path_params/","?beta=true"]': { + '["x"]': { valid: true, result: '/path_params/x?beta=true' }, + '[""]': { valid: true, result: '/path_params/?beta=true' }, + '["%2E%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2E?beta=true\n' + + ' ^^^^^^', + }, + '["%2e%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2e%2E?beta=true\n' + + ' ^^^^^^', + }, + }, + '["/path_params/",".?beta=true"]': { + '[".."]': { valid: true, result: '/path_params/...?beta=true' }, + '["x"]': { valid: true, result: '/path_params/x.?beta=true' }, + '[""]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + '/path_params/.?beta=true\n' + + ' ^', + }, + '["%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e." can\'t be safely passed as a path parameter\n' + + '/path_params/%2e.?beta=true\n' + + ' ^^^^', + }, + }, + '["/path_params/","/","/download"]': { + '["",""]': { valid: true, result: '/path_params///download' }, + '["","x"]': { valid: true, result: '/path_params//x/download' }, + '[".","%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/./%2e/download\n' + + ' ^ ^^^', + }, + '["%2E%2e","%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2e/%2e/download\n' + + ' ^^^^^^ ^^^', + }, + }, + '["/path_params/","-","/download"]': { + '["","%2e"]': { valid: true, result: '/path_params/-%2e/download' }, + '["%2E",".."]': { valid: true, result: '/path_params/%2E-../download' }, + }, + '["/path_params/","","/download"]': { + '["%2E%2e","%2e%2E"]': { valid: true, result: '/path_params/%2E%2e%2e%2E/download' }, + '["%2E",".."]': { valid: true, result: '/path_params/%2E../download' }, + '["","%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E/download\n' + + ' ^^^', + }, + '["%2E","."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E." can\'t be safely passed as a path parameter\n' + + '/path_params/%2E./download\n' + + ' ^^^^', + }, + }, + '["/path_params/",".","/download"]': { + '["%2e%2e",""]': { valid: true, result: '/path_params/%2e%2e./download' }, + '["","%2e%2e"]': { valid: true, result: '/path_params/.%2e%2e/download' }, + '["",""]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + '/path_params/./download\n' + + ' ^', + }, + '["","."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '/path_params/../download\n' + + ' ^^', + }, + }, + '["/path_params/","..","/download"]': { + '["","%2E"]': { valid: true, result: '/path_params/..%2E/download' }, + '["","x"]': { valid: true, result: '/path_params/..x/download' }, + '["",""]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '/path_params/../download\n' + + ' ^^', + }, + }, + }); + }); +}); + +describe('encodeURIPath', () => { + const testCases: string[] = [ + '', + // Every ASCII character + ...Array.from({ length: 0x7f }, (_, i) => String.fromCharCode(i)), + // Unicode BMP codepoint + 'å', + // Unicode supplementary codepoint + '😃', + ]; + + for (const param of testCases) { + test('properly encodes ' + inspect(param), () => { + const encoded = encodeURIPath(param); + const naiveEncoded = encodeURIComponent(param); + // we should never encode more characters than encodeURIComponent + expect(naiveEncoded.length).toBeGreaterThanOrEqual(encoded.length); + expect(decodeURIComponent(encoded)).toBe(param); + }); + } + + test("leaves ':' intact", () => { + expect(encodeURIPath(':')).toBe(':'); + }); + + test("leaves '@' intact", () => { + expect(encodeURIPath('@')).toBe('@'); + }); +}); diff --git a/tests/qs/utils.test.ts b/tests/qs/utils.test.ts index 3be17dd5c..6636cee5f 100644 --- a/tests/qs/utils.test.ts +++ b/tests/qs/utils.test.ts @@ -66,7 +66,7 @@ describe('merge()', function () { // st.equal(getCount, 1); expect(setCount).toEqual(0); expect(getCount).toEqual(1); - observed[0] = observed[0]; // eslint-disable-line no-self-assign + observed[0] = observed[0]; // st.equal(setCount, 1); // st.equal(getCount, 2); expect(setCount).toEqual(1); diff --git a/tests/responses.test.ts b/tests/responses.test.ts deleted file mode 100644 index 469e2b3fe..000000000 --- a/tests/responses.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createResponseHeaders } from '@tryfinch/finch-api/core'; -import { Headers } from '@tryfinch/finch-api/_shims/index'; - -describe('response parsing', () => { - // TODO: test unicode characters - test('headers are case agnostic', async () => { - const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); - expect(headers['content-type']).toEqual('foo'); - expect(headers['Content-type']).toEqual('foo'); - expect(headers['Content-Type']).toEqual('foo'); - expect(headers['accept']).toEqual('text/plain'); - expect(headers['Accept']).toEqual('text/plain'); - expect(headers['Hello-World']).toBeUndefined(); - }); - - test('duplicate headers are concatenated', () => { - const headers = createResponseHeaders( - new Headers([ - ['Content-Type', 'text/xml'], - ['Content-Type', 'application/json'], - ]), - ); - expect(headers['content-type']).toBe('text/xml, application/json'); - }); -}); diff --git a/tests/uploads.test.ts b/tests/uploads.test.ts index 6f1a5f430..254e85ddd 100644 --- a/tests/uploads.test.ts +++ b/tests/uploads.test.ts @@ -1,6 +1,7 @@ import fs from 'fs'; -import { toFile, type ResponseLike } from '@tryfinch/finch-api/uploads'; -import { File } from '@tryfinch/finch-api/_shims/index'; +import type { ResponseLike } from '@tryfinch/finch-api/internal/to-file'; +import { toFile } from '@tryfinch/finch-api/core/uploads'; +import { File } from 'node:buffer'; class MyClass { name: string = 'foo'; @@ -9,7 +10,7 @@ class MyClass { function mockResponse({ url, content }: { url: string; content?: Blob }): ResponseLike { return { url, - blob: async () => content as any, + blob: async () => content || new Blob([]), }; } @@ -62,4 +63,45 @@ describe('toFile', () => { expect(file.name).toEqual('input.jsonl'); expect(file.type).toBe('jsonl'); }); + + it('is assignable to File and Blob', async () => { + const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); + const result = await toFile(input); + const file: File = result; + const blob: Blob = result; + void file, blob; + }); +}); + +describe('missing File error message', () => { + let prevGlobalFile: unknown; + let prevNodeFile: unknown; + beforeEach(() => { + // The file shim captures the global File object when it's first imported. + // Reset modules before each test so we can test the error thrown when it's undefined. + jest.resetModules(); + const buffer = require('node:buffer'); + // @ts-ignore + prevGlobalFile = globalThis.File; + prevNodeFile = buffer.File; + // @ts-ignore + globalThis.File = undefined; + buffer.File = undefined; + }); + afterEach(() => { + // Clean up + // @ts-ignore + globalThis.File = prevGlobalFile; + require('node:buffer').File = prevNodeFile; + jest.resetModules(); + }); + + test('is thrown', async () => { + const uploads = await import('@tryfinch/finch-api/core/uploads'); + await expect( + uploads.toFile(mockResponse({ url: 'https://example.com/my/audio.mp3' })), + ).rejects.toMatchInlineSnapshot( + `[Error: \`File\` is not defined as a global, which is required for file uploads.]`, + ); + }); }); diff --git a/tsc-multi.json b/tsc-multi.json index 4facad5ad..384ddac5b 100644 --- a/tsc-multi.json +++ b/tsc-multi.json @@ -1,7 +1,15 @@ { "targets": [ - { "extname": ".js", "module": "commonjs" }, - { "extname": ".mjs", "module": "esnext" } + { + "extname": ".js", + "module": "commonjs", + "shareHelpers": "internal/tslib.js" + }, + { + "extname": ".mjs", + "module": "esnext", + "shareHelpers": "internal/tslib.mjs" + } ], "projects": ["tsconfig.build.json"] } diff --git a/tsconfig.build.json b/tsconfig.build.json index b5b9724dc..1b087983e 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", "include": ["dist/src"], - "exclude": ["dist/src/_shims/*-deno.ts"], + "exclude": [], "compilerOptions": { "rootDir": "./dist/src", "paths": { diff --git a/tsconfig.dist-src.json b/tsconfig.dist-src.json index e9f2d70b0..c550e2996 100644 --- a/tsconfig.dist-src.json +++ b/tsconfig.dist-src.json @@ -4,8 +4,8 @@ // via declaration maps "include": ["index.ts"], "compilerOptions": { - "target": "es2015", - "lib": ["DOM"], + "target": "ES2015", + "lib": ["DOM", "DOM.Iterable", "ES2018"], "moduleResolution": "node" } } diff --git a/tsconfig.json b/tsconfig.json index 6f28db23d..fca72f913 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "include": ["src", "tests", "examples"], - "exclude": ["src/_shims/**/*-deno.ts"], + "exclude": [], "compilerOptions": { "target": "es2020", "lib": ["es2020"], @@ -8,7 +8,6 @@ "moduleResolution": "node", "esModuleInterop": true, "paths": { - "@tryfinch/finch-api/_shims/auto/*": ["./src/_shims/auto/*-node"], "@tryfinch/finch-api/*": ["./src/*"], "@tryfinch/finch-api": ["./src/index.ts"] }, diff --git a/yarn.lock b/yarn.lock index a69661728..9fd07e737 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,37 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" +"@andrewbranch/untar.js@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz#ba9494f85eb83017c5c855763969caf1d0adea00" + integrity sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw== + +"@arethetypeswrong/cli@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@arethetypeswrong/cli/-/cli-0.17.0.tgz#f97f10926b3f9f9eb5117550242d2e06c25cadac" + integrity sha512-xSMW7bfzVWpYw5JFgZqBXqr6PdR0/REmn3DkxCES5N0JTcB0CVgbIynJCvKBFmXaPc3hzmmTrb7+yPDRoOSZdA== + dependencies: + "@arethetypeswrong/core" "0.17.0" + chalk "^4.1.2" + cli-table3 "^0.6.3" + commander "^10.0.1" + marked "^9.1.2" + marked-terminal "^7.1.0" + semver "^7.5.4" + +"@arethetypeswrong/core@0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@arethetypeswrong/core/-/core-0.17.0.tgz#abb3b5f425056d37193644c2a2de4aecf866b76b" + integrity sha512-FHyhFizXNetigTVsIhqXKGYLpazPS5YNojEPpZEUcBPt9wVvoEbNIvG+hybuBR+pjlRcbyuqhukHZm1fr+bDgA== + dependencies: + "@andrewbranch/untar.js" "^1.0.3" + cjs-module-lexer "^1.2.3" + fflate "^0.8.2" + lru-cache "^10.4.3" + semver "^7.5.4" + typescript "5.6.1-rc" + validate-npm-package-name "^5.0.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" @@ -302,6 +333,11 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + "@cspotcode/source-map-consumer@0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" @@ -321,54 +357,94 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.5.1": - version "4.11.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.1.tgz#a547badfc719eb3e5f4b556325e542fbe9d7a18f" - integrity sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q== +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint-community/regexpp@^4.6.1": - version "4.6.2" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" - integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== +"@eslint/config-array@^0.19.0": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" + integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== + dependencies: + "@eslint/object-schema" "^2.1.6" + debug "^4.3.1" + minimatch "^3.1.2" -"@eslint/eslintrc@^2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" - integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== +"@eslint/core@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.10.0.tgz#23727063c21b335f752dbb3a16450f6f9cbc9091" + integrity sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/core@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12" + integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" + integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" + espree "^10.0.1" + globals "^14.0.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.50.0": - version "8.50.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.50.0.tgz#9e93b850f0f3fa35f5fa59adfd03adae8488e484" - integrity sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ== +"@eslint/js@9.20.0": + version "9.20.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4" + integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ== -"@humanwhocodes/config-array@^0.11.11": - version "0.11.11" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" - integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA== +"@eslint/object-schema@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" + integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== + +"@eslint/plugin-kit@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz#ee07372035539e7847ef834e3f5e7b79f09e3a81" + integrity sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A== dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.5" + "@eslint/core" "^0.10.0" + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.6" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.0" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" + integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -635,7 +711,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": +"@nodelib/fs.walk@^1.2.3": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -643,23 +719,21 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@pkgr/utils@^2.4.2": - version "2.4.2" - resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" - integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== - dependencies: - cross-spawn "^7.0.3" - fast-glob "^3.3.0" - is-glob "^4.0.3" - open "^9.1.0" - picocolors "^1.0.0" - tslib "^2.6.0" +"@pkgr/core@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.4.tgz#d897170a2b0ba51f78a099edccd968f7b103387c" + integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw== "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sindresorhus/is@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + "@sinonjs/commons@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" @@ -817,6 +891,11 @@ dependencies: "@babel/types" "^7.20.7" +"@types/estree@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -851,35 +930,24 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@^7.0.12": +"@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/node-fetch@^2.6.4": - version "2.6.13" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.13.tgz#e0c9b7b5edbdb1b50ce32c127e85e880872d56ee" - integrity sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw== - dependencies: - "@types/node" "*" - form-data "^4.0.4" - "@types/node@*": - version "24.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.3.0.tgz#89b09f45cb9a8ee69466f18ee5864e4c3eb84dec" - integrity sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow== + version "20.10.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2" + integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw== dependencies: - undici-types "~7.10.0" + undici-types "~5.26.4" -"@types/node@^18.11.18": - version "18.11.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" - integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== - -"@types/semver@^7.5.0": - version "7.5.8" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" - integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== +"@types/node@^20.17.6": + version "20.19.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.11.tgz#728cab53092bd5f143beed7fbba7ba99de3c16c4" + integrity sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow== + dependencies: + undici-types "~6.21.0" "@types/stack-utils@^2.0.0": version "2.0.3" @@ -898,98 +966,86 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^6.7.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" - integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== - dependencies: - "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/type-utils" "6.21.0" - "@typescript-eslint/utils" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" - debug "^4.3.4" +"@typescript-eslint/eslint-plugin@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz#62f1befe59647524994e89de4516d8dcba7a850a" + integrity sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/type-utils" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" graphemer "^1.4.0" - ignore "^5.2.4" + ignore "^5.3.1" natural-compare "^1.4.0" - semver "^7.5.4" - ts-api-utils "^1.0.1" + ts-api-utils "^2.0.1" -"@typescript-eslint/parser@^6.7.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" - integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== +"@typescript-eslint/parser@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.1.tgz#e9b0ccf30d37dde724ee4d15f4dbc195995cce1b" + integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q== dependencies: - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/typescript-estree" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" - integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== +"@typescript-eslint/scope-manager@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz#1eb52e76878f545e4add142e0d8e3e97e7aa443b" + integrity sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw== dependencies: - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" -"@typescript-eslint/type-utils@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" - integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== +"@typescript-eslint/type-utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz#be0f438fb24b03568e282a0aed85f776409f970c" + integrity sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA== dependencies: - "@typescript-eslint/typescript-estree" "6.21.0" - "@typescript-eslint/utils" "6.21.0" + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/utils" "8.31.1" debug "^4.3.4" - ts-api-utils "^1.0.1" + ts-api-utils "^2.0.1" -"@typescript-eslint/types@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" - integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== +"@typescript-eslint/types@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.1.tgz#478ed6f7e8aee1be7b63a60212b6bffe1423b5d4" + integrity sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ== -"@typescript-eslint/typescript-estree@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" - integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== +"@typescript-eslint/typescript-estree@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz#37792fe7ef4d3021c7580067c8f1ae66daabacdf" + integrity sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag== dependencies: - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" debug "^4.3.4" - globby "^11.1.0" + fast-glob "^3.3.2" is-glob "^4.0.3" - minimatch "9.0.3" - semver "^7.5.4" - ts-api-utils "^1.0.1" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" -"@typescript-eslint/utils@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" - integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== +"@typescript-eslint/utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.1.tgz#5628ea0393598a0b2f143d0fc6d019f0dee9dd14" + integrity sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@types/json-schema" "^7.0.12" - "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/typescript-estree" "6.21.0" - semver "^7.5.4" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" -"@typescript-eslint/visitor-keys@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" - integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== +"@typescript-eslint/visitor-keys@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz#6742b0e3ba1e0c1e35bdaf78c03e759eb8dd8e75" + integrity sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw== dependencies: - "@typescript-eslint/types" "6.21.0" - eslint-visitor-keys "^3.4.1" - -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" + "@typescript-eslint/types" "8.31.1" + eslint-visitor-keys "^4.2.0" acorn-jsx@^5.3.2: version "5.3.2" @@ -1001,25 +1057,16 @@ acorn-walk@^8.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== +acorn@^8.14.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + acorn@^8.4.1: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== -acorn@^8.9.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== - -agentkeepalive@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" - integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== - dependencies: - debug "^4.1.0" - depd "^1.1.2" - humanize-ms "^1.2.1" - aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -1045,11 +1092,23 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" +ansi-escapes@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7" + integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== + dependencies: + environment "^1.0.0" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -1069,6 +1128,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@^3.0.3: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1094,16 +1158,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" @@ -1169,18 +1223,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -big-integer@^1.6.44: - version "1.6.52" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" - integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== - -bplist-parser@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" - integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== - dependencies: - big-integer "^1.6.44" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1232,21 +1274,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -bundle-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" - integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== - dependencies: - run-applescript "^5.0.0" - -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1276,7 +1303,7 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1284,6 +1311,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -1299,11 +1331,46 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== +cjs-module-lexer@^1.2.3: + version "1.4.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" + integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-highlight@^2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + +cli-table3@^0.6.3, cli-table3@^0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -1347,12 +1414,10 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== concat-map@0.0.1: version "0.0.1" @@ -1382,7 +1447,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -1398,7 +1463,7 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: dependencies: ms "2.1.2" -debug@^4.3.4: +debug@^4.3.4, debug@^4.3.7: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -1420,39 +1485,6 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -default-browser-id@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" - integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== - dependencies: - bplist-parser "^0.2.0" - untildify "^4.0.0" - -default-browser@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" - integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== - dependencies: - bundle-name "^3.0.0" - default-browser-id "^3.0.0" - execa "^7.1.1" - titleize "^3.0.0" - -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -depd@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -1468,29 +1500,6 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - electron-to-chromium@^1.4.601: version "1.4.614" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.614.tgz#2fe789d61fa09cb875569f37c309d0c2701f91c0" @@ -1506,6 +1515,16 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emojilib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" + integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== + +environment@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" + integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -1513,33 +1532,6 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -1560,100 +1552,95 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-plugin-prettier@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz#a3b399f04378f79f066379f544e42d6b73f11515" - integrity sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg== +eslint-plugin-prettier@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz#99b55d7dd70047886b2222fdd853665f180b36af" + integrity sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg== dependencies: prettier-linter-helpers "^1.0.0" - synckit "^0.8.5" - -eslint-plugin-unused-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.0.0.tgz#d25175b0072ff16a91892c3aa72a09ca3a9e69e7" - integrity sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw== - dependencies: - eslint-rule-composer "^0.3.0" + synckit "^0.11.7" -eslint-rule-composer@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" - integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== +eslint-plugin-unused-imports@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz#62ddc7446ccbf9aa7b6f1f0b00a980423cda2738" + integrity sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ== -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== +eslint-scope@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" + integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.3.0: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.49.0: - version "8.50.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.50.0.tgz#2ae6015fee0240fcd3f83e1e25df0287f487d6b2" - integrity sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg== +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^9.20.1: + version "9.20.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6" + integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g== dependencies: "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.2" - "@eslint/js" "8.50.0" - "@humanwhocodes/config-array" "^0.11.11" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.19.0" + "@eslint/core" "^0.11.0" + "@eslint/eslintrc" "^3.2.0" + "@eslint/js" "9.20.0" + "@eslint/plugin-kit" "^0.2.5" + "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" + "@humanwhocodes/retry" "^0.4.1" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" ajv "^6.12.4" chalk "^4.0.0" - cross-spawn "^7.0.2" + cross-spawn "^7.0.6" debug "^4.3.2" - doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" + eslint-scope "^8.2.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" + file-entry-cache "^8.0.0" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" ignore "^5.2.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" lodash.merge "^4.6.2" minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== +espree@^10.0.1, espree@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" + integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== dependencies: - acorn "^8.9.0" + acorn "^8.14.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" + eslint-visitor-keys "^4.2.0" esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== +esquery@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== dependencies: estraverse "^5.1.0" @@ -1674,11 +1661,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -1694,21 +1676,6 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" - integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.1" - human-signals "^4.3.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^3.0.7" - strip-final-newline "^3.0.0" - exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -1735,18 +1702,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-glob@^3.2.12: - version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-glob@^3.2.9, fast-glob@^3.3.0: +fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -1781,12 +1737,17 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== dependencies: - flat-cache "^3.0.4" + flat-cache "^4.0.0" fill-range@^7.1.1: version "7.1.1" @@ -1811,42 +1772,18 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== - -form-data-encoder@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" - integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== - -form-data@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" - integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - hasown "^2.0.2" - mime-types "^2.1.12" + flatted "^3.2.9" + keyv "^4.5.4" -formdata-node@^4.3.2: - version "4.3.3" - resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.3.3.tgz#21415225be66e2c87a917bfc0fedab30a119c23c" - integrity sha512-coTew7WODO2vF+XhpUdmYz4UBvlsiTMSNaFYZlrXIqYbFd4W7bMwnoALNLE6uvNgzTg2j1JDF0ZImEfF06VPAA== - dependencies: - node-domexception "1.0.0" - web-streams-polyfill "4.0.0-beta.1" +flatted@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== fs.realpath@^1.0.0: version "1.0.0" @@ -1873,41 +1810,17 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.6: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - get-stdin@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== -get-stream@^6.0.0, get-stream@^6.0.1: +get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -1938,34 +1851,26 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== - dependencies: - type-fest "^0.20.2" - -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== graceful-fs@^4.2.9: version "4.2.11" @@ -1987,18 +1892,6 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.3, has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - hasown@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" @@ -2006,12 +1899,10 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== html-escaper@^2.0.0: version "2.0.2" @@ -2023,18 +1914,6 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -human-signals@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" - integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== - -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= - dependencies: - ms "^2.0.0" - iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -2042,7 +1921,14 @@ iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ignore@^5.2.0, ignore@^5.2.4: +ignore-walk@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" + integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== + dependencies: + minimatch "^5.0.1" + +ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -2098,16 +1984,6 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.0" -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -2130,40 +2006,16 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -2605,6 +2457,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -2630,6 +2487,13 @@ jsonc-parser@^3.2.0: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -2677,6 +2541,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lru-cache@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2710,17 +2579,30 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== +marked-terminal@^7.1.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-7.2.1.tgz#9c1ae073a245a03c6a13e3eeac6f586f29856068" + integrity sha512-rQ1MoMFXZICWNsKMiiHwP/Z+92PLKskTPXj+e7uwXmuMPkNn7iTqC+IvDekVm1MPeC9wYQeLxeFaOvudRR/XbQ== + dependencies: + ansi-escapes "^7.0.0" + ansi-regex "^6.1.0" + chalk "^5.3.0" + cli-highlight "^2.1.11" + cli-table3 "^0.6.5" + node-emoji "^2.1.3" + supports-hyperlinks "^3.1.0" + +marked@^9.1.2: + version "9.1.6" + resolved "https://registry.yarnpkg.com/marked/-/marked-9.1.6.tgz#5d2a3f8180abfbc5d62e3258a38a1c19c0381695" + integrity sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q== merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0, merge2@^1.4.1: +merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -2733,73 +2615,75 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== - -minimatch@9.0.3: - version "9.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +mri@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.3: +ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -node-domexception@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - -node-fetch@^2.6.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== +node-emoji@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-2.1.3.tgz#93cfabb5cc7c3653aa52f29d6ffb7927d8047c06" + integrity sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA== dependencies: - whatwg-url "^5.0.0" + "@sindresorhus/is" "^4.6.0" + char-regex "^1.0.2" + emojilib "^2.4.0" + skin-tone "^2.0.0" node-int64@^0.4.0: version "0.4.0" @@ -2816,6 +2700,28 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +npm-bundled@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4" + integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== + dependencies: + npm-normalize-package-bin "^2.0.0" + +npm-normalize-package-bin@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" + integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== + +npm-packlist@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b" + integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== + dependencies: + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^2.0.0" + npm-normalize-package-bin "^2.0.0" + npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -2823,12 +2729,10 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -npm-run-path@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" - integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== - dependencies: - path-key "^4.0.0" +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== once@^1.3.0: version "1.4.0" @@ -2844,23 +2748,6 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== - dependencies: - mimic-fn "^4.0.0" - -open@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" - integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== - dependencies: - default-browser "^4.0.0" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^2.2.0" - optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" @@ -2937,6 +2824,23 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -2952,26 +2856,21 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -3023,6 +2922,15 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +publint@^0.2.12: + version "0.2.12" + resolved "https://registry.yarnpkg.com/publint/-/publint-0.2.12.tgz#d25cd6bd243d5bdd640344ecdddb3eeafdcc4059" + integrity sha512-YNeUtCVeM4j9nDiTT2OPczmlyzOkIXNtdDZnSuajAxS/nZ6j3t7Vs9SUB4euQNddiltIwu7Tdd3s+hr08fAsMw== + dependencies: + npm-packlist "^5.1.3" + picocolors "^1.1.1" + sade "^1.8.1" + punycode@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" @@ -3093,20 +3001,6 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -run-applescript@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" - integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== - dependencies: - execa "^5.0.0" - run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -3114,6 +3008,13 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +sade@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" + integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== + dependencies: + mri "^1.1.0" + safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -3141,6 +3042,11 @@ semver@^7.5.4: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +semver@^7.6.0: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -3163,6 +3069,13 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +skin-tone@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/skin-tone/-/skin-tone-2.0.0.tgz#4e3933ab45c0d4f4f781745d64b9f4c208e41237" + integrity sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA== + dependencies: + unicode-emoji-modifier-base "^1.0.0" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -3246,20 +3159,15 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -superstruct@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046" - integrity sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg== +superstruct@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" + integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== supports-color@^5.3.0: version "5.5.0" @@ -3268,7 +3176,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -3282,18 +3190,25 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" +supports-hyperlinks@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz#b56150ff0173baacc15f21956450b61f2b18d3ac" + integrity sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -synckit@^0.8.5: - version "0.8.6" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.6.tgz#b69b7fbce3917c2673cbdc0d87fb324db4a5b409" - integrity sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA== +synckit@^0.11.7: + version "0.11.8" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.8.tgz#b2aaae998a4ef47ded60773ad06e7cb821f55457" + integrity sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A== dependencies: - "@pkgr/utils" "^2.4.2" - tslib "^2.6.2" + "@pkgr/core" "^0.2.4" test-exclude@^6.0.0: version "6.0.0" @@ -3304,15 +3219,19 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" -titleize@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" - integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" tmpl@1.0.5: version "1.0.5" @@ -3331,15 +3250,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -ts-api-utils@^1.0.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" - integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== +ts-api-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.1.tgz#660729385b625b939aaa58054f45c058f33f10cd" + integrity sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w== ts-jest@^29.1.0: version "29.1.1" @@ -3374,21 +3288,20 @@ ts-node@^10.5.0: v8-compile-cache-lib "^3.0.0" yn "3.1.1" -tsc-multi@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/tsc-multi/-/tsc-multi-1.1.0.tgz#0e2b03c0ed0ac58ecb556f11709441102d202680" - integrity sha512-THE6X+sse7EZ2qMhqXvBhd2HMTvXyWwYnx+2T/ijqdp/6Rf7rUc2uPRzPdrrljZCNcYDeL0qP2P7tqm2IwayTg== +"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz": + version "1.1.9" + resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz#777f6f5d9e26bf0e94e5170990dd3a841d6707cd" dependencies: - debug "^4.3.4" - fast-glob "^3.2.12" + debug "^4.3.7" + fast-glob "^3.3.2" get-stdin "^8.0.0" p-all "^3.0.0" - picocolors "^1.0.0" + picocolors "^1.1.1" signal-exit "^3.0.7" string-to-stream "^3.0.1" - superstruct "^1.0.3" - tslib "^2.5.0" - yargs "^17.7.1" + superstruct "^1.0.4" + tslib "^2.8.1" + yargs "^17.7.2" tsconfig-paths@^4.0.0: version "4.2.0" @@ -3399,15 +3312,10 @@ tsconfig-paths@^4.0.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" - integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== - -tslib@^2.6.0, tslib@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" @@ -3421,30 +3329,44 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -typescript@^4.8.2: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript-eslint@8.31.1: + version "8.31.1" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.31.1.tgz#b77ab1e48ced2daab9225ff94bab54391a4af69b" + integrity sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA== + dependencies: + "@typescript-eslint/eslint-plugin" "8.31.1" + "@typescript-eslint/parser" "8.31.1" + "@typescript-eslint/utils" "8.31.1" -undici-types@~7.10.0: - version "7.10.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.10.0.tgz#4ac2e058ce56b462b056e629cc6a02393d3ff350" - integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag== +typescript@5.6.1-rc: + version "5.6.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.1-rc.tgz#d5e4d7d8170174fed607b74cc32aba3d77018e02" + integrity sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ== -untildify@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" - integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== +typescript@5.8.3: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +unicode-emoji-modifier-base@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459" + integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g== update-browserslist-db@^1.0.13: version "1.0.13" @@ -3480,6 +3402,11 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +validate-npm-package-name@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" + integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -3487,24 +3414,6 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -web-streams-polyfill@4.0.0-beta.1: - version "4.0.0-beta.1" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz#3b19b9817374b7cee06d374ba7eeb3aeb80e8c95" - integrity sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -3549,12 +3458,30 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.3.1, yargs@^17.7.1: +yargs@^16.0.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.3.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From baa02378d7b36940a4ac806462b6180d77e3ceac Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 01:52:54 +0000 Subject: [PATCH 02/40] feat(api): move node to typescript generator --- .github/workflows/publish-npm.yml | 50 +++++++++++++++++++ .github/workflows/release-doctor.yml | 22 +++++++++ .release-please-manifest.json | 3 ++ .stats.yml | 2 +- CONTRIBUTING.md | 20 ++++++-- MIGRATION.md | 4 +- README.md | 7 +-- bin/check-release-environment | 22 +++++++++ bin/migration-config.json | 2 +- package.json | 2 +- packages/mcp-server/README.md | 25 ++-------- packages/mcp-server/manifest.json | 4 +- packages/mcp-server/package.json | 4 +- release-please-config.json | 74 ++++++++++++++++++++++++++++ src/version.ts | 2 +- 15 files changed, 205 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/publish-npm.yml create mode 100644 .github/workflows/release-doctor.yml create mode 100644 .release-please-manifest.json create mode 100644 bin/check-release-environment create mode 100644 release-please-config.json diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 000000000..459a68590 --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,50 @@ +# This workflow is triggered when a GitHub release is created. +# It can also be run manually to re-publish to NPM in case it failed for some reason. +# You can run this workflow by navigating to https://www.github.com/Finch-API/finch-api-node/actions/workflows/publish-npm.yml +name: Publish NPM +on: + workflow_dispatch: + inputs: + path: + description: The path to run the release in, e.g. '.' or 'packages/mcp-server' + required: true + + release: + types: [published] + +jobs: + publish: + name: publish + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Install dependencies + run: | + yarn install + + - name: Publish to NPM + run: | + if [ -n "${{ github.event.inputs.path }}" ]; then + PATHS_RELEASED='[\"${{ github.event.inputs.path }}\"]' + else + PATHS_RELEASED='[\".\", \"packages/mcp-server\"]' + fi + yarn tsn scripts/publish-packages.ts "{ \"paths_released\": \"$PATHS_RELEASED\" }" + env: + NPM_TOKEN: ${{ secrets.FINCH_NPM_TOKEN || secrets.NPM_TOKEN }} + + - name: Upload MCP Server DXT GitHub release asset + run: | + gh release upload ${{ github.event.release.tag_name }} \ + packages/mcp-server/tryfinch_finch_api_api.mcpb + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml new file mode 100644 index 000000000..01f642c5f --- /dev/null +++ b/.github/workflows/release-doctor.yml @@ -0,0 +1,22 @@ +name: Release Doctor +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + release_doctor: + name: release doctor + runs-on: ubuntu-latest + if: github.repository == 'Finch-API/finch-api-node' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + + steps: + - uses: actions/checkout@v4 + + - name: Check release environment + run: | + bash ./bin/check-release-environment + env: + NPM_TOKEN: ${{ secrets.FINCH_NPM_TOKEN || secrets.NPM_TOKEN }} + diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 000000000..a25f0a9b1 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "6.38.0" +} diff --git a/.stats.yml b/.stats.yml index 6dc87a141..89fe3e91f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-b817e7a30a6366c063a8c9a334d5be281eb8d93e21acc8c8219d3bdc95043deb.yml openapi_spec_hash: d4cc4a5cba9f13986e38d148d330aa00 -config_hash: 5c64f384746e7570c10f19fe241062a7 +config_hash: 87775d204161dc65d5231647162ea0e8 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9bea7aaa6..af1d7143b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,15 +42,15 @@ If you’d like to use the repository from source, you can either install from g To install via git: ```sh -$ npm install git+ssh://git@github.com:stainless-sdks/finch-typescript.git +$ npm install git+ssh://git@github.com:Finch-API/finch-api-node.git ``` Alternatively, to link a local copy of the repo: ```sh # Clone -$ git clone https://www.github.com/stainless-sdks/finch-typescript -$ cd finch-typescript +$ git clone https://www.github.com/Finch-API/finch-api-node +$ cd finch-api-node # With yarn $ yarn link @@ -91,3 +91,17 @@ To format and fix all lint issues automatically: ```sh $ yarn fix ``` + +## Publishing and releases + +Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If +the changes aren't made through the automated pipeline, you may want to make releases manually. + +### Publish with a GitHub workflow + +You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/Finch-API/finch-api-node/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up. + +### Publish manually + +If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on +the environment. diff --git a/MIGRATION.md b/MIGRATION.md index 7511b7531..c90ed0aea 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -122,10 +122,10 @@ client.example.list(undefined, { headers: { ... } }); ### Removed `httpAgent` in favor of `fetchOptions` -The `httpAgent` client option has been removed in favor of a [platform-specific `fetchOptions` property](https://github.com/stainless-sdks/finch-typescript#fetch-options). +The `httpAgent` client option has been removed in favor of a [platform-specific `fetchOptions` property](https://github.com/Finch-API/finch-api-node#fetch-options). This change was made as `httpAgent` relied on `node:http` agents which are not supported by any runtime's builtin fetch implementation. -If you were using `httpAgent` for proxy support, check out the [new proxy documentation](https://github.com/stainless-sdks/finch-typescript#configuring-proxies). +If you were using `httpAgent` for proxy support, check out the [new proxy documentation](https://github.com/Finch-API/finch-api-node#configuring-proxies). Before: diff --git a/README.md b/README.md index affc95297..76c0579aa 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,9 @@ It is generated with [Stainless](https://www.stainless.com/). ## Installation ```sh -npm install git+ssh://git@github.com:stainless-sdks/finch-typescript.git +npm install @tryfinch/finch-api ``` -> [!NOTE] -> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npm install @tryfinch/finch-api` - ## Usage The full API of this library can be found in [api.md](api.md). @@ -415,7 +412,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. -We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/finch-typescript/issues) with questions, bugs, or suggestions. +We are keen for your feedback; please open an [issue](https://www.github.com/Finch-API/finch-api-node/issues) with questions, bugs, or suggestions. ## Requirements diff --git a/bin/check-release-environment b/bin/check-release-environment new file mode 100644 index 000000000..e4b6d58ea --- /dev/null +++ b/bin/check-release-environment @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +errors=() + +if [ -z "${NPM_TOKEN}" ]; then + errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") +fi + +lenErrors=${#errors[@]} + +if [[ lenErrors -gt 0 ]]; then + echo -e "Found the following errors in the release environment:\n" + + for error in "${errors[@]}"; do + echo -e "- $error\n" + done + + exit 1 +fi + +echo "The environment is ready to push releases!" + diff --git a/bin/migration-config.json b/bin/migration-config.json index e544cb7c0..058ea3406 100644 --- a/bin/migration-config.json +++ b/bin/migration-config.json @@ -1,6 +1,6 @@ { "pkg": "@tryfinch/finch-api", - "githubRepo": "https://github.com/stainless-sdks/finch-typescript", + "githubRepo": "https://github.com/Finch-API/finch-api-node", "clientClass": "Finch", "methods": [] } diff --git a/package.json b/package.json index 871d84589..d7ccaa8be 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "types": "dist/index.d.ts", "main": "dist/index.js", "type": "commonjs", - "repository": "github:stainless-sdks/finch-typescript", + "repository": "github:Finch-API/finch-api-node", "license": "Apache-2.0", "packageManager": "yarn@1.22.22", "files": [ diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 45b8b9cc6..1e21a15aa 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -4,35 +4,20 @@ It is generated with [Stainless](https://www.stainless.com/). ## Installation -### Building +### Direct invocation -Because it's not published yet, clone the repo and build it: +You can run the MCP Server directly via `npx`: ```sh -git clone git@github.com:stainless-sdks/finch-typescript.git -cd finch-typescript -./scripts/bootstrap -./scripts/build -``` - -### Running - -```sh -# set env vars as needed export FINCH_ACCESS_TOKEN="My Access Token" export FINCH_CLIENT_ID="4ab15e51-11ad-49f4-acae-f343b7794375" export FINCH_CLIENT_SECRET="My Client Secret" export FINCH_WEBHOOK_SECRET="My Webhook Secret" -node ./packages/mcp-server/dist/index.js +npx -y @tryfinch/finch-api-mcp@latest ``` -> [!NOTE] -> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npx -y @tryfinch/finch-api-mcp` - ### Via MCP Client -[Build the project](#building) as mentioned above. - There is a partial list of existing clients at [modelcontextprotocol.io](https://modelcontextprotocol.io/clients). If you already have a client, consult their documentation to install the MCP server. @@ -42,8 +27,8 @@ For clients with a configuration JSON, it might look something like this: { "mcpServers": { "tryfinch_finch_api_api": { - "command": "node", - "args": ["/path/to/local/finch-typescript/packages/mcp-server", "--client=claude", "--tools=dynamic"], + "command": "npx", + "args": ["-y", "@tryfinch/finch-api-mcp", "--client=claude", "--tools=dynamic"], "env": { "FINCH_ACCESS_TOKEN": "My Access Token", "FINCH_CLIENT_ID": "4ab15e51-11ad-49f4-acae-f343b7794375", diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json index b86c859f5..2901767d0 100644 --- a/packages/mcp-server/manifest.json +++ b/packages/mcp-server/manifest.json @@ -9,9 +9,9 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/stainless-sdks/finch-typescript.git" + "url": "git+https://github.com/Finch-API/finch-api-node.git" }, - "homepage": "https://github.com/stainless-sdks/finch-typescript/tree/main/packages/mcp-server#readme", + "homepage": "https://github.com/Finch-API/finch-api-node/tree/main/packages/mcp-server#readme", "documentation": "https://developer.tryfinch.com/", "server": { "type": "node", diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index e2551798e..955d84703 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -8,10 +8,10 @@ "type": "commonjs", "repository": { "type": "git", - "url": "git+https://github.com/stainless-sdks/finch-typescript.git", + "url": "git+https://github.com/Finch-API/finch-api-node.git", "directory": "packages/mcp-server" }, - "homepage": "https://github.com/stainless-sdks/finch-typescript/tree/main/packages/mcp-server#readme", + "homepage": "https://github.com/Finch-API/finch-api-node/tree/main/packages/mcp-server#readme", "license": "Apache-2.0", "packageManager": "yarn@1.22.22", "private": false, diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 000000000..8204c04cd --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,74 @@ +{ + "packages": { + ".": {} + }, + "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", + "include-v-in-tag": true, + "include-component-in-tag": false, + "versioning": "prerelease", + "prerelease": true, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "pull-request-header": "Automated Release PR", + "pull-request-title-pattern": "release: ${version}", + "changelog-sections": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "perf", + "section": "Performance Improvements" + }, + { + "type": "revert", + "section": "Reverts" + }, + { + "type": "chore", + "section": "Chores" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "style", + "section": "Styles" + }, + { + "type": "refactor", + "section": "Refactors" + }, + { + "type": "test", + "section": "Tests", + "hidden": true + }, + { + "type": "build", + "section": "Build System" + }, + { + "type": "ci", + "section": "Continuous Integration", + "hidden": true + } + ], + "reviewers": ["jordanbrauer", "minupalaniappan"], + "release-type": "node", + "extra-files": [ + "src/version.ts", + "README.md", + "packages/mcp-server/yarn.lock", + { + "type": "json", + "path": "packages/mcp-server/package.json", + "jsonpath": "$.version" + } + ] +} diff --git a/src/version.ts b/src/version.ts index 2622794a2..1422fd54f 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '6.38.0'; +export const VERSION = '6.38.0'; // x-release-please-version From 8639c2e81ba805d4e7df858302d0efbe725c2feb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 02:01:19 +0000 Subject: [PATCH 03/40] feat(api): update automated code reviewer selection --- .stats.yml | 2 +- release-please-config.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 89fe3e91f..14e5d14cb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-b817e7a30a6366c063a8c9a334d5be281eb8d93e21acc8c8219d3bdc95043deb.yml openapi_spec_hash: d4cc4a5cba9f13986e38d148d330aa00 -config_hash: 87775d204161dc65d5231647162ea0e8 +config_hash: f2846563903bf75ab0858872154df0f7 diff --git a/release-please-config.json b/release-please-config.json index 8204c04cd..b1909804e 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -59,7 +59,6 @@ "hidden": true } ], - "reviewers": ["jordanbrauer", "minupalaniappan"], "release-type": "node", "extra-files": [ "src/version.ts", From 3fb650b9ac0084d7a6e4d9c026c0355b2b74af0b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:48:00 +0000 Subject: [PATCH 04/40] feat(api): api update --- .stats.yml | 4 ++-- packages/mcp-server/src/tools/account/introspect-account.ts | 2 +- src/resources/account.ts | 5 ----- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index 14e5d14cb..51e32ae9f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-b817e7a30a6366c063a8c9a334d5be281eb8d93e21acc8c8219d3bdc95043deb.yml -openapi_spec_hash: d4cc4a5cba9f13986e38d148d330aa00 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-6190639c909b62f2e182a32dc56a80a87ca470e8970efd6ce8d3d7bd659d237c.yml +openapi_spec_hash: c4078fa61a4ab0b480a2c7b40e495104 config_hash: f2846563903bf75ab0858872154df0f7 diff --git a/packages/mcp-server/src/tools/account/introspect-account.ts b/packages/mcp-server/src/tools/account/introspect-account.ts index f826f4bfc..a1ceb6a10 100644 --- a/packages/mcp-server/src/tools/account/introspect-account.ts +++ b/packages/mcp-server/src/tools/account/introspect-account.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'introspect_account', description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRead account information associated with an `access_token`\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/introspection',\n $defs: {\n introspection: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The Finch UUID of the token being introspected'\n },\n client_id: {\n type: 'string',\n description: 'The client ID of the application associated with the `access_token`'\n },\n client_type: {\n type: 'string',\n title: 'ClientType',\n description: 'The type of application associated with a token.',\n enum: [ 'development',\n 'production',\n 'sandbox'\n ]\n },\n connection_id: {\n type: 'string',\n description: 'The Finch UUID of the connection associated with the `access_token`'\n },\n connection_status: {\n type: 'object',\n properties: {\n status: {\n $ref: '#/$defs/connection_status_type'\n },\n last_successful_sync: {\n anyOf: [ {\n type: 'string',\n format: 'date-time'\n },\n {\n type: 'string'\n }\n ],\n description: 'The datetime when the connection was last successfully synced'\n },\n message: {\n type: 'string'\n }\n },\n required: [ 'status'\n ]\n },\n connection_type: {\n type: 'string',\n title: 'ConnectionType',\n description: 'The type of the connection associated with the token.\\n- `provider` - connection to an external provider\\n- `finch` - finch-generated data.',\n enum: [ 'finch',\n 'provider'\n ]\n },\n products: {\n type: 'array',\n description: 'An array of the authorized products associated with the `access_token`.',\n items: {\n type: 'string'\n }\n },\n provider_id: {\n type: 'string',\n description: 'The ID of the provider associated with the `access_token`.'\n },\n account_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate tokens with a Finch connection instead of this account ID'\n },\n authentication_methods: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n description: 'The type of authentication method',\n enum: [ 'assisted',\n 'credential',\n 'api_token',\n 'api_credential',\n 'oauth'\n ]\n },\n connection_status: {\n type: 'object',\n properties: {\n status: {\n $ref: '#/$defs/connection_status_type'\n },\n last_successful_sync: {\n anyOf: [ {\n type: 'string',\n format: 'date-time'\n },\n {\n type: 'string'\n }\n ],\n description: 'The datetime when the connection was last successfully synced'\n },\n message: {\n type: 'string'\n }\n },\n required: [ 'status'\n ]\n },\n products: {\n type: 'array',\n description: 'An array of the authorized products associated with the `access_token`',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n company_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate tokens with a Finch connection instead of this company ID'\n },\n customer_email: {\n type: 'string',\n description: 'The email of your customer you provided to Finch when a connect session was created for this connection'\n },\n customer_id: {\n type: 'string',\n description: 'The ID of your customer you provided to Finch when a connect session was created for this connection'\n },\n customer_name: {\n type: 'string',\n description: 'The name of your customer you provided to Finch when a connect session was created for this connection'\n },\n entities: {\n type: 'array',\n description: 'Array of detailed entity information for each connected account in multi-account mode',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The connection account ID for this entity'\n },\n name: {\n type: 'string',\n description: 'The name of the entity (payroll provider company name)'\n },\n source_id: {\n type: 'string',\n description: 'The source ID of the entity'\n },\n type: {\n type: 'string',\n description: 'The type of entity'\n }\n },\n required: [ 'id',\n 'name',\n 'source_id',\n 'type'\n ]\n }\n },\n manual: {\n type: 'boolean',\n description: 'Whether the connection associated with the `access_token` uses the Assisted Connect Flow. (`true` if using Assisted Connect, `false` if connection is automated)'\n },\n payroll_provider_id: {\n type: 'string',\n description: '[DEPRECATED] Use `provider_id` to identify the provider instead of this payroll provider ID.'\n },\n username: {\n type: 'string',\n description: 'The account username used for login associated with the `access_token`.'\n }\n },\n required: [ 'id',\n 'client_id',\n 'client_type',\n 'connection_id',\n 'connection_status',\n 'connection_type',\n 'products',\n 'provider_id'\n ]\n },\n connection_status_type: {\n type: 'string',\n enum: [ 'pending',\n 'processing',\n 'connected',\n 'error_no_account_setup',\n 'error_permissions',\n 'reauth'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRead account information associated with an `access_token`\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/introspection',\n $defs: {\n introspection: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The Finch UUID of the token being introspected'\n },\n client_id: {\n type: 'string',\n description: 'The client ID of the application associated with the `access_token`'\n },\n client_type: {\n type: 'string',\n title: 'ClientType',\n description: 'The type of application associated with a token.',\n enum: [ 'development',\n 'production',\n 'sandbox'\n ]\n },\n connection_id: {\n type: 'string',\n description: 'The Finch UUID of the connection associated with the `access_token`'\n },\n connection_status: {\n type: 'object',\n properties: {\n status: {\n $ref: '#/$defs/connection_status_type'\n },\n last_successful_sync: {\n anyOf: [ {\n type: 'string',\n format: 'date-time'\n },\n {\n type: 'string'\n }\n ],\n description: 'The datetime when the connection was last successfully synced'\n },\n message: {\n type: 'string'\n }\n },\n required: [ 'status'\n ]\n },\n connection_type: {\n type: 'string',\n title: 'ConnectionType',\n description: 'The type of the connection associated with the token.\\n- `provider` - connection to an external provider\\n- `finch` - finch-generated data.',\n enum: [ 'finch',\n 'provider'\n ]\n },\n products: {\n type: 'array',\n description: 'An array of the authorized products associated with the `access_token`.',\n items: {\n type: 'string'\n }\n },\n provider_id: {\n type: 'string',\n description: 'The ID of the provider associated with the `access_token`.'\n },\n account_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate tokens with a Finch connection instead of this account ID'\n },\n authentication_methods: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n description: 'The type of authentication method',\n enum: [ 'assisted',\n 'credential',\n 'api_token',\n 'api_credential',\n 'oauth'\n ]\n },\n connection_status: {\n type: 'object',\n properties: {\n status: {\n $ref: '#/$defs/connection_status_type'\n },\n last_successful_sync: {\n anyOf: [ {\n type: 'string',\n format: 'date-time'\n },\n {\n type: 'string'\n }\n ],\n description: 'The datetime when the connection was last successfully synced'\n },\n message: {\n type: 'string'\n }\n },\n required: [ 'status'\n ]\n },\n products: {\n type: 'array',\n description: 'An array of the authorized products associated with the `access_token`',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n company_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate tokens with a Finch connection instead of this company ID'\n },\n customer_email: {\n type: 'string',\n description: 'The email of your customer you provided to Finch when a connect session was created for this connection'\n },\n customer_id: {\n type: 'string',\n description: 'The ID of your customer you provided to Finch when a connect session was created for this connection'\n },\n customer_name: {\n type: 'string',\n description: 'The name of your customer you provided to Finch when a connect session was created for this connection'\n },\n entities: {\n type: 'array',\n description: 'Array of detailed entity information for each connected account in multi-account mode',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The connection account ID for this entity'\n },\n name: {\n type: 'string',\n description: 'The name of the entity (payroll provider company name)'\n },\n source_id: {\n type: 'string',\n description: 'The source ID of the entity'\n }\n },\n required: [ 'id',\n 'name',\n 'source_id'\n ]\n }\n },\n manual: {\n type: 'boolean',\n description: 'Whether the connection associated with the `access_token` uses the Assisted Connect Flow. (`true` if using Assisted Connect, `false` if connection is automated)'\n },\n payroll_provider_id: {\n type: 'string',\n description: '[DEPRECATED] Use `provider_id` to identify the provider instead of this payroll provider ID.'\n },\n username: {\n type: 'string',\n description: 'The account username used for login associated with the `access_token`.'\n }\n },\n required: [ 'id',\n 'client_id',\n 'client_type',\n 'connection_id',\n 'connection_status',\n 'connection_type',\n 'products',\n 'provider_id'\n ]\n },\n connection_status_type: {\n type: 'string',\n enum: [ 'pending',\n 'processing',\n 'connected',\n 'error_no_account_setup',\n 'error_permissions',\n 'reauth'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/src/resources/account.ts b/src/resources/account.ts index 91517e8fa..68ddd577c 100644 --- a/src/resources/account.ts +++ b/src/resources/account.ts @@ -180,11 +180,6 @@ export namespace Introspection { * The source ID of the entity */ source_id: string | null; - - /** - * The type of entity - */ - type: string | null; } } From c43cbb234a5044f21866998cacf9f658cc55de49 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:52:22 +0000 Subject: [PATCH 05/40] chore: mcp code tool explicit error message when missing a run function --- packages/mcp-server/package.json | 4 +- packages/mcp-server/src/code-tool-worker.ts | 47 +++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 955d84703..ae3a94278 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -38,6 +38,7 @@ "express": "^5.1.0", "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", "qs": "^6.14.0", + "typescript": "5.8.3", "yargs": "^17.7.2", "zod": "^3.25.20", "zod-to-json-schema": "^3.24.5", @@ -64,8 +65,7 @@ "ts-morph": "^19.0.0", "ts-node": "^10.5.0", "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", - "tsconfig-paths": "^4.0.0", - "typescript": "5.8.3" + "tsconfig-paths": "^4.0.0" }, "imports": { "@tryfinch/finch-api-mcp": ".", diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index b82608a49..bfd42fede 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -1,11 +1,58 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import util from 'node:util'; + +import ts from 'typescript'; + import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; import { Finch } from '@tryfinch/finch-api'; +function getRunFunctionNode( + code: string, +): ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction | null { + const sourceFile = ts.createSourceFile('code.ts', code, ts.ScriptTarget.Latest, true); + + for (const statement of sourceFile.statements) { + // Check for top-level function declarations + if (ts.isFunctionDeclaration(statement)) { + if (statement.name?.text === 'run') { + return statement; + } + } + + // Check for variable declarations: const run = () => {} or const run = function() {} + if (ts.isVariableStatement(statement)) { + for (const declaration of statement.declarationList.declarations) { + if (ts.isIdentifier(declaration.name) && declaration.name.text === 'run') { + // Check if it's initialized with a function + if ( + declaration.initializer && + (ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer)) + ) { + return declaration.initializer; + } + } + } + } + } + + return null; +} + const fetch = async (req: Request): Promise => { const { opts, code } = (await req.json()) as WorkerInput; + + const runFunctionNode = getRunFunctionNode(code); + if (!runFunctionNode) { + return Response.json( + { + message: + 'The code is missing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + } satisfies WorkerError, + { status: 400, statusText: 'Code execution error' }, + ); + } + const client = new Finch({ ...opts, }); From ace64daf797895abdf52bada50f0cfff2b44c146 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 22:22:39 +0000 Subject: [PATCH 06/40] feat(mcp): enable optional code execution tool on http mcp servers --- packages/mcp-server/src/options.ts | 13 ++++++++++--- packages/mcp-server/tests/options.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts index 4fe3b9875..b6ff59763 100644 --- a/packages/mcp-server/src/options.ts +++ b/packages/mcp-server/src/options.ts @@ -284,8 +284,10 @@ const coerceArray = (zodType: T) => ); const QueryOptions = z.object({ - tools: coerceArray(z.enum(['dynamic', 'all', 'docs'])).describe('Use dynamic tools or all tools'), - no_tools: coerceArray(z.enum(['dynamic', 'all', 'docs'])).describe('Do not use dynamic tools or all tools'), + tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe('Specify which MCP tools to use'), + no_tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe( + 'Specify which MCP tools to not use.', + ), tool: coerceArray(z.string()).describe('Include tools matching the specified names'), resource: coerceArray(z.string()).describe('Include tools matching the specified resources'), operation: coerceArray(z.enum(['read', 'write'])).describe( @@ -385,11 +387,16 @@ export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): M : queryOptions.tools?.includes('docs') ? true : defaultOptions.includeDocsTools; + let codeTools: boolean | undefined = + queryOptions.no_tools && queryOptions.no_tools?.includes('code') ? false + : queryOptions.tools?.includes('code') && defaultOptions.includeCodeTools ? true + : defaultOptions.includeCodeTools; + return { client: queryOptions.client ?? defaultOptions.client, includeDynamicTools: dynamicTools, includeAllTools: allTools, - includeCodeTools: undefined, + includeCodeTools: codeTools, includeDocsTools: docsTools, filters, capabilities: clientCapabilities, diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts index a8a5b81a9..4d9b60ca3 100644 --- a/packages/mcp-server/tests/options.test.ts +++ b/packages/mcp-server/tests/options.test.ts @@ -171,6 +171,7 @@ describe('parseQueryOptions', () => { const defaultOptions = { client: undefined, includeDynamicTools: undefined, + includeCodeTools: undefined, includeAllTools: undefined, filters: [], capabilities: { @@ -383,6 +384,27 @@ describe('parseQueryOptions', () => { { type: 'tool', op: 'exclude', value: 'exclude-tool' }, ]); }); + + it('code tools are enabled on http servers with default option set', () => { + const query = 'tools=code'; + const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: true }, query); + + expect(result.includeCodeTools).toBe(true); + }); + + it('code tools are prevented on http servers when no default option set', () => { + const query = 'tools=code'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.includeCodeTools).toBe(undefined); + }); + + it('code tools are prevented on http servers when default option is explicitly false', () => { + const query = 'tools=code'; + const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: false }, query); + + expect(result.includeCodeTools).toBe(false); + }); }); describe('parseEmbeddedJSON', () => { From 6b20464cdcf9d407bb22ac7a65a0a40732130321 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:20:36 +0000 Subject: [PATCH 07/40] chore(mcp): add friendlier MCP code tool errors on incorrect method invocations --- packages/mcp-server/package.json | 1 + packages/mcp-server/src/code-tool-worker.ts | 135 +++++++++++++++++++- packages/mcp-server/src/code-tool.ts | 2 +- 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index ae3a94278..8e3796296 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -36,6 +36,7 @@ "@valtown/deno-http-worker": "^0.0.21", "cors": "^2.8.5", "express": "^5.1.0", + "fuse.js": "^7.1.0", "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", "qs": "^6.14.0", "typescript": "5.8.3", diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index bfd42fede..57b3cb66a 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -2,6 +2,7 @@ import util from 'node:util'; +import Fuse from 'fuse.js'; import ts from 'typescript'; import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; @@ -39,8 +40,140 @@ function getRunFunctionNode( return null; } +const fuse = new Fuse( + [ + 'client.accessTokens.create', + 'client.hris.company.retrieve', + 'client.hris.company.payStatementItem.list', + 'client.hris.company.payStatementItem.rules.create', + 'client.hris.company.payStatementItem.rules.delete', + 'client.hris.company.payStatementItem.rules.list', + 'client.hris.company.payStatementItem.rules.update', + 'client.hris.directory.list', + 'client.hris.directory.listIndividuals', + 'client.hris.individuals.retrieveMany', + 'client.hris.employments.retrieveMany', + 'client.hris.payments.list', + 'client.hris.payStatements.retrieveMany', + 'client.hris.documents.list', + 'client.hris.documents.retreive', + 'client.hris.benefits.create', + 'client.hris.benefits.list', + 'client.hris.benefits.listSupportedBenefits', + 'client.hris.benefits.retrieve', + 'client.hris.benefits.update', + 'client.hris.benefits.individuals.enrollMany', + 'client.hris.benefits.individuals.enrolledIDs', + 'client.hris.benefits.individuals.retrieveManyBenefits', + 'client.hris.benefits.individuals.unenrollMany', + 'client.providers.list', + 'client.account.disconnect', + 'client.account.introspect', + 'client.requestForwarding.forward', + 'client.jobs.automated.create', + 'client.jobs.automated.list', + 'client.jobs.automated.retrieve', + 'client.jobs.manual.retrieve', + 'client.sandbox.connections.create', + 'client.sandbox.connections.accounts.create', + 'client.sandbox.connections.accounts.update', + 'client.sandbox.company.update', + 'client.sandbox.directory.create', + 'client.sandbox.individual.update', + 'client.sandbox.employment.update', + 'client.sandbox.payment.create', + 'client.sandbox.jobs.create', + 'client.sandbox.jobs.configuration.retrieve', + 'client.sandbox.jobs.configuration.update', + 'client.payroll.payGroups.list', + 'client.payroll.payGroups.retrieve', + 'client.connect.sessions.new', + 'client.connect.sessions.reauthenticate', + ], + { threshold: 1, shouldSort: true }, +); + +function getMethodSuggestions(fullyQualifiedMethodName: string): string[] { + return fuse + .search(fullyQualifiedMethodName) + .map(({ item }) => item) + .slice(0, 5); +} + +const proxyToObj = new WeakMap(); +const objToProxy = new WeakMap(); + +type ClientProxyConfig = { + path: string[]; + isBelievedBad?: boolean; +}; + +function makeSdkProxy(obj: T, { path, isBelievedBad = false }: ClientProxyConfig): T { + let proxy: T = objToProxy.get(obj); + + if (!proxy) { + proxy = new Proxy(obj, { + get(target, prop, receiver) { + const propPath = [...path, String(prop)]; + const value = Reflect.get(target, prop, receiver); + + if (isBelievedBad || (!(prop in target) && value === undefined)) { + // If we're accessing a path that doesn't exist, it will probably eventually error. + // Let's proxy it and mark it bad so that we can control the error message. + // We proxy an empty class so that an invocation or construction attempt is possible. + return makeSdkProxy(class {}, { path: propPath, isBelievedBad: true }); + } + + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + return makeSdkProxy(value, { path: propPath, isBelievedBad }); + } + + return value; + }, + + apply(target, thisArg, args) { + if (isBelievedBad || typeof target !== 'function') { + const fullyQualifiedMethodName = path.join('.'); + const suggestions = getMethodSuggestions(fullyQualifiedMethodName); + throw new Error( + `${fullyQualifiedMethodName} is not a function. Did you mean: ${suggestions.join(', ')}`, + ); + } + + return Reflect.apply(target, proxyToObj.get(thisArg) ?? thisArg, args); + }, + + construct(target, args, newTarget) { + if (isBelievedBad || typeof target !== 'function') { + const fullyQualifiedMethodName = path.join('.'); + const suggestions = getMethodSuggestions(fullyQualifiedMethodName); + throw new Error( + `${fullyQualifiedMethodName} is not a constructor. Did you mean: ${suggestions.join(', ')}`, + ); + } + + return Reflect.construct(target, args, newTarget); + }, + }); + + objToProxy.set(obj, proxy); + proxyToObj.set(proxy, obj); + } + + return proxy; +} + const fetch = async (req: Request): Promise => { const { opts, code } = (await req.json()) as WorkerInput; + if (code == null) { + return Response.json( + { + message: + 'The code param is missing. Provide one containing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + } satisfies WorkerError, + { status: 400, statusText: 'Code execution error' }, + ); + } const runFunctionNode = getRunFunctionNode(code); if (!runFunctionNode) { @@ -73,7 +206,7 @@ const fetch = async (req: Request): Promise => { ${code} run_ = run; `); - const result = await run_(client); + const result = await run_(makeSdkProxy(client, { path: ['client'] })); return Response.json({ result, logLines, diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 2de55444d..163ac6eaf 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -23,7 +23,7 @@ export async function codeTool(): Promise { const tool: Tool = { name: 'execute', description: - 'Runs TypeScript code to interact with the API.\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client, and it will be run.\nDo not initialize a client, but instead use the client that you are given as a parameter.\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', + 'Runs TypeScript code to interact with the API.\n\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client named "client", and it will be run.\nWrite code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```\n\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, }; From 3289a2b9e29e2d7298f096fcbae9237e28bce198 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 18:27:18 +0000 Subject: [PATCH 08/40] chore(mcp): add line numbers to code tool errors --- packages/mcp-server/src/code-tool-worker.ts | 27 ++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index 57b3cb66a..c2d1dc70c 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -163,6 +163,25 @@ function makeSdkProxy(obj: T, { path, isBelievedBad = false }: return proxy; } +function parseError(code: string, error: unknown): string | undefined { + if (!(error instanceof Error)) return; + const message = error.name ? `${error.name}: ${error.message}` : error.message; + try { + // Deno uses V8; the first ":LINE:COLUMN" is the top of stack. + const lineNumber = error.stack?.match(/:([0-9]+):[0-9]+/)?.[1]; + // -1 for the zero-based indexing + const line = + lineNumber && + code + .split('\n') + .at(parseInt(lineNumber, 10) - 1) + ?.trim(); + return line ? `${message}\n at line ${lineNumber}\n ${line}` : message; + } catch { + return message; + } +} + const fetch = async (req: Request): Promise => { const { opts, code } = (await req.json()) as WorkerInput; if (code == null) { @@ -202,10 +221,7 @@ const fetch = async (req: Request): Promise => { }; try { let run_ = async (client: any) => {}; - eval(` - ${code} - run_ = run; - `); + eval(`${code}\nrun_ = run;`); const result = await run_(makeSdkProxy(client, { path: ['client'] })); return Response.json({ result, @@ -213,10 +229,9 @@ const fetch = async (req: Request): Promise => { errLines, } satisfies WorkerSuccess); } catch (e) { - const message = e instanceof Error ? e.message : undefined; return Response.json( { - message, + message: parseError(code, e), } satisfies WorkerError, { status: 400, statusText: 'Code execution error' }, ); From 99e438ca85071e011f30e93f7b5f67628bd0695c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 21:20:10 +0000 Subject: [PATCH 09/40] docs(mcp): add a README button for one-click add to Cursor --- packages/mcp-server/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 1e21a15aa..3968ff131 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -40,6 +40,13 @@ For clients with a configuration JSON, it might look something like this: } ``` +### Cursor + + If you use Cursor, you can install the MCP server by using the button below. You will need to set your environment variables + in Cursor's `mcp.json`, which can be found in Cursor Settings > Tools & MCP > New MCP Server. + + [![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=@tryfinch/finch-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0cnlmaW5jaC9maW5jaC1hcGktbWNwIl0sImVudiI6eyJGSU5DSF9BQ0NFU1NfVE9LRU4iOiJTZXQgeW91ciBGSU5DSF9BQ0NFU1NfVE9LRU4gaGVyZS4iLCJGSU5DSF9DTElFTlRfSUQiOiJTZXQgeW91ciBGSU5DSF9DTElFTlRfSUQgaGVyZS4iLCJGSU5DSF9DTElFTlRfU0VDUkVUIjoiU2V0IHlvdXIgRklOQ0hfQ0xJRU5UX1NFQ1JFVCBoZXJlLiIsIkZJTkNIX1dFQkhPT0tfU0VDUkVUIjoiU2V0IHlvdXIgRklOQ0hfV0VCSE9PS19TRUNSRVQgaGVyZS4ifX0) + ## Exposing endpoints to your MCP Client There are two ways to expose endpoints as tools in the MCP server: From 60aa66a135d99299976fb2c59ac8d83f98d6841c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 00:43:51 +0000 Subject: [PATCH 10/40] chore(internal): codegen related update --- packages/mcp-server/README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 3968ff131..3af96b06d 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -42,17 +42,18 @@ For clients with a configuration JSON, it might look something like this: ### Cursor - If you use Cursor, you can install the MCP server by using the button below. You will need to set your environment variables - in Cursor's `mcp.json`, which can be found in Cursor Settings > Tools & MCP > New MCP Server. +If you use Cursor, you can install the MCP server by using the button below. You will need to set your environment variables +in Cursor's `mcp.json`, which can be found in Cursor Settings > Tools & MCP > New MCP Server. - [![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=@tryfinch/finch-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0cnlmaW5jaC9maW5jaC1hcGktbWNwIl0sImVudiI6eyJGSU5DSF9BQ0NFU1NfVE9LRU4iOiJTZXQgeW91ciBGSU5DSF9BQ0NFU1NfVE9LRU4gaGVyZS4iLCJGSU5DSF9DTElFTlRfSUQiOiJTZXQgeW91ciBGSU5DSF9DTElFTlRfSUQgaGVyZS4iLCJGSU5DSF9DTElFTlRfU0VDUkVUIjoiU2V0IHlvdXIgRklOQ0hfQ0xJRU5UX1NFQ1JFVCBoZXJlLiIsIkZJTkNIX1dFQkhPT0tfU0VDUkVUIjoiU2V0IHlvdXIgRklOQ0hfV0VCSE9PS19TRUNSRVQgaGVyZS4ifX0) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=@tryfinch/finch-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0cnlmaW5jaC9maW5jaC1hcGktbWNwIl0sImVudiI6eyJGSU5DSF9BQ0NFU1NfVE9LRU4iOiJTZXQgeW91ciBGSU5DSF9BQ0NFU1NfVE9LRU4gaGVyZS4iLCJGSU5DSF9DTElFTlRfSUQiOiJTZXQgeW91ciBGSU5DSF9DTElFTlRfSUQgaGVyZS4iLCJGSU5DSF9DTElFTlRfU0VDUkVUIjoiU2V0IHlvdXIgRklOQ0hfQ0xJRU5UX1NFQ1JFVCBoZXJlLiIsIkZJTkNIX1dFQkhPT0tfU0VDUkVUIjoiU2V0IHlvdXIgRklOQ0hfV0VCSE9PS19TRUNSRVQgaGVyZS4ifX0) ## Exposing endpoints to your MCP Client -There are two ways to expose endpoints as tools in the MCP server: +There are three ways to expose endpoints as tools in the MCP server: 1. Exposing one tool per endpoint, and filtering as necessary 2. Exposing a set of tools to dynamically discover and invoke endpoints from the API +3. Exposing a docs search tool and a code execution tool, allowing the client to write code to be executed against the TypeScript client ### Filtering endpoints and tools @@ -87,6 +88,18 @@ All of these command-line options can be repeated, combined together, and have c Use `--list` to see the list of available tools, or see below. +### Code execution + +If you specify `--tools=code` to the MCP server, it will expose just two tools: + +- `search_docs` - Searches the API documentation and returns a list of markdown results +- `execute` - Runs code against the TypeScript client + +This allows the LLM to implement more complex logic by chaining together many API calls without loading +intermediary results into its context window. + +The code execution itself happens in a Deno sandbox that has network access only to the base URL for the API. + ### Specifying the MCP Client Different clients have varying abilities to handle arbitrary tools and schemas. From 7a5ecb92bc7575e51fd053e3d6796f0b1b06dc6f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 20:46:13 +0000 Subject: [PATCH 11/40] docs(mcp): add a README link to add server to VS Code or Claude Code --- packages/mcp-server/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 3af96b06d..5d51973fc 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -47,6 +47,22 @@ in Cursor's `mcp.json`, which can be found in Cursor Settings > Tools & MCP > Ne [![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=@tryfinch/finch-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0cnlmaW5jaC9maW5jaC1hcGktbWNwIl0sImVudiI6eyJGSU5DSF9BQ0NFU1NfVE9LRU4iOiJTZXQgeW91ciBGSU5DSF9BQ0NFU1NfVE9LRU4gaGVyZS4iLCJGSU5DSF9DTElFTlRfSUQiOiJTZXQgeW91ciBGSU5DSF9DTElFTlRfSUQgaGVyZS4iLCJGSU5DSF9DTElFTlRfU0VDUkVUIjoiU2V0IHlvdXIgRklOQ0hfQ0xJRU5UX1NFQ1JFVCBoZXJlLiIsIkZJTkNIX1dFQkhPT0tfU0VDUkVUIjoiU2V0IHlvdXIgRklOQ0hfV0VCSE9PS19TRUNSRVQgaGVyZS4ifX0) +### VS Code + +If you use MCP, you can install the MCP server by clicking the link below. You will need to set your environment variables +in VS Code's `mcp.json`, which can be found via Command Palette > MCP: Open User Configuration. + +[Open VS Code](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40tryfinch%2Ffinch-api-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40tryfinch%2Ffinch-api-mcp%22%5D%2C%22env%22%3A%7B%22FINCH_ACCESS_TOKEN%22%3A%22Set%20your%20FINCH_ACCESS_TOKEN%20here.%22%2C%22FINCH_CLIENT_ID%22%3A%22Set%20your%20FINCH_CLIENT_ID%20here.%22%2C%22FINCH_CLIENT_SECRET%22%3A%22Set%20your%20FINCH_CLIENT_SECRET%20here.%22%2C%22FINCH_WEBHOOK_SECRET%22%3A%22Set%20your%20FINCH_WEBHOOK_SECRET%20here.%22%7D%7D) + +### Claude Code + +If you use Claude Code, you can install the MCP server by running the command below in your terminal. You will need to set your +environment variables in Claude Code's `.claude.json`, which can be found in your home directory. + +``` +claude mcp add --transport stdio tryfinch_finch_api_api --env FINCH_ACCESS_TOKEN="Your FINCH_ACCESS_TOKEN here." FINCH_CLIENT_ID="Your FINCH_CLIENT_ID here." FINCH_CLIENT_SECRET="Your FINCH_CLIENT_SECRET here." FINCH_WEBHOOK_SECRET="Your FINCH_WEBHOOK_SECRET here." -- npx -y @tryfinch/finch-api-mcp +``` + ## Exposing endpoints to your MCP Client There are three ways to expose endpoints as tools in the MCP server: From a39b0402570ec2e64a4950efb55726c33cbf6ce3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 21:09:00 +0000 Subject: [PATCH 12/40] chore(internal): codegen related update --- packages/mcp-server/src/code-tool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 163ac6eaf..7babc168e 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -23,7 +23,7 @@ export async function codeTool(): Promise { const tool: Tool = { name: 'execute', description: - 'Runs TypeScript code to interact with the API.\n\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client named "client", and it will be run.\nWrite code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```\n\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', + 'Runs JavaScript code to interact with the API.\n\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client named "client", and it will be run.\nWrite code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```\n\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, }; From b1be6b0108c5ae0896e7379d5342fc9629ec8c30 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 9 Nov 2025 18:22:46 +0000 Subject: [PATCH 13/40] feat(api): api update --- .stats.yml | 4 +- ...many-benefits-benefits-hris-individuals.ts | 2 +- src/resources/hris/benefits/individuals.ts | 38 ++++++++++++++----- src/resources/request-forwarding.ts | 2 +- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/.stats.yml b/.stats.yml index 51e32ae9f..8d224370b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-6190639c909b62f2e182a32dc56a80a87ca470e8970efd6ce8d3d7bd659d237c.yml -openapi_spec_hash: c4078fa61a4ab0b480a2c7b40e495104 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-970ab04a97244c68824c0c52e06925cba14fb7dbfc36c03167c1afe74cd1b150.yml +openapi_spec_hash: 315e7859c3f77311261fb824b74a8247 config_hash: f2846563903bf75ab0858872154df0f7 diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts index 4613572da..ce7e05adb 100644 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'retrieve_many_benefits_benefits_hris_individuals', description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet enrollment information for the given individuals.\n\n# Response Schema\n```json\n{\n type: 'array',\n title: 'IndividualBenefits',\n items: {\n $ref: '#/$defs/individual_benefit'\n },\n $defs: {\n individual_benefit: {\n type: 'object',\n properties: {\n body: {\n anyOf: [ {\n type: 'object',\n properties: {\n annual_maximum: {\n type: 'integer',\n description: 'If the benefit supports annual maximum, the amount in cents for this individual.'\n },\n catch_up: {\n type: 'boolean',\n description: 'If the benefit supports catch up (401k, 403b, etc.), whether catch up is enabled for this individual.'\n },\n company_contribution: {\n anyOf: [ {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in cents.'\n },\n type: {\n type: 'string',\n description: 'Fixed contribution type.',\n enum: [ 'fixed'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n },\n {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in basis points (1/100th of a percent).'\n },\n type: {\n type: 'string',\n description: 'Percentage contribution type.',\n enum: [ 'percent'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n },\n {\n type: 'object',\n properties: {\n tiers: {\n type: 'array',\n description: 'Array of tier objects defining employer match tiers based on employee contribution thresholds.',\n items: {\n type: 'object',\n properties: {\n match: {\n type: 'integer'\n },\n threshold: {\n type: 'integer'\n }\n },\n required: [ 'match',\n 'threshold'\n ]\n }\n },\n type: {\n type: 'string',\n description: 'Tiered contribution type (only valid for company_contribution).',\n enum: [ 'tiered'\n ]\n }\n },\n required: [ 'tiers',\n 'type'\n ]\n }\n ],\n title: 'CompanyContribution'\n },\n employee_deduction: {\n anyOf: [ {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in cents.'\n },\n type: {\n type: 'string',\n description: 'Fixed contribution type.',\n enum: [ 'fixed'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n },\n {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in basis points (1/100th of a percent).'\n },\n type: {\n type: 'string',\n description: 'Percentage contribution type.',\n enum: [ 'percent'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n }\n ],\n title: 'EmployeeDeductionContribution'\n },\n hsa_contribution_limit: {\n type: 'string',\n description: 'Type for HSA contribution limit if the benefit is a HSA.',\n enum: [ 'individual',\n 'family'\n ]\n }\n },\n required: [ 'annual_maximum',\n 'catch_up',\n 'company_contribution',\n 'employee_deduction'\n ]\n },\n {\n type: 'object',\n properties: {\n code: {\n type: 'number'\n },\n message: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n finch_code: {\n type: 'string'\n }\n },\n required: [ 'code',\n 'message',\n 'name'\n ]\n }\n ]\n },\n code: {\n type: 'integer'\n },\n individual_id: {\n type: 'string'\n }\n },\n required: [ 'body',\n 'code',\n 'individual_id'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet enrollment information for the given individuals.\n\n# Response Schema\n```json\n{\n type: 'array',\n title: 'IndividualBenefits',\n items: {\n $ref: '#/$defs/individual_benefit'\n },\n $defs: {\n individual_benefit: {\n type: 'object',\n properties: {\n body: {\n anyOf: [ {\n type: 'object',\n properties: {\n annual_maximum: {\n type: 'integer',\n description: 'If the benefit supports annual maximum, the amount in cents for this individual.'\n },\n catch_up: {\n type: 'boolean',\n description: 'If the benefit supports catch up (401k, 403b, etc.), whether catch up is enabled for this individual.'\n },\n company_contribution: {\n anyOf: [ {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in cents (for type=fixed) or basis points (for type=percent, where 100 = 1%). Not used for type=tiered.'\n },\n type: {\n type: 'string',\n description: 'Contribution type. Supported values: \"fixed\" (amount in cents), \"percent\" (amount in basis points), or \"tiered\" (multi-tier matching).',\n enum: [ 'fixed'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n },\n {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in cents (for type=fixed) or basis points (for type=percent, where 100 = 1%). Not used for type=tiered.'\n },\n type: {\n type: 'string',\n description: 'Contribution type. Supported values: \"fixed\" (amount in cents), \"percent\" (amount in basis points), or \"tiered\" (multi-tier matching).',\n enum: [ 'percent'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n },\n {\n type: 'object',\n properties: {\n tiers: {\n type: 'array',\n description: 'Array of tier objects defining employer match tiers based on employee contribution thresholds. Required when type=tiered.',\n items: {\n type: 'object',\n properties: {\n match: {\n type: 'integer'\n },\n threshold: {\n type: 'integer'\n }\n },\n required: [ 'match',\n 'threshold'\n ]\n }\n },\n type: {\n type: 'string',\n description: 'Contribution type. Supported values: \"fixed\" (amount in cents), \"percent\" (amount in basis points), or \"tiered\" (multi-tier matching).',\n enum: [ 'tiered'\n ]\n }\n },\n required: [ 'tiers',\n 'type'\n ]\n }\n ],\n title: 'CompanyContribution',\n description: 'Company contribution configuration. Supports fixed amounts (in cents), percentage-based contributions (in basis points where 100 = 1%), or tiered matching structures.'\n },\n employee_deduction: {\n anyOf: [ {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in cents (for type=fixed) or basis points (for type=percent, where 100 = 1%).'\n },\n type: {\n type: 'string',\n description: 'Contribution type. Supported values: \"fixed\" (amount in cents) or \"percent\" (amount in basis points).',\n enum: [ 'fixed'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n },\n {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in cents (for type=fixed) or basis points (for type=percent, where 100 = 1%).'\n },\n type: {\n type: 'string',\n description: 'Contribution type. Supported values: \"fixed\" (amount in cents) or \"percent\" (amount in basis points).',\n enum: [ 'percent'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n }\n ],\n title: 'EmployeeDeductionContribution',\n description: 'Employee deduction configuration. Supports both fixed amounts (in cents) and percentage-based contributions (in basis points where 100 = 1%).'\n },\n hsa_contribution_limit: {\n type: 'string',\n description: 'Type for HSA contribution limit if the benefit is a HSA.',\n enum: [ 'individual',\n 'family'\n ]\n }\n },\n required: [ 'annual_maximum',\n 'catch_up',\n 'company_contribution',\n 'employee_deduction'\n ]\n },\n {\n type: 'object',\n properties: {\n code: {\n type: 'number'\n },\n message: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n finch_code: {\n type: 'string'\n }\n },\n required: [ 'code',\n 'message',\n 'name'\n ]\n }\n ]\n },\n code: {\n type: 'integer'\n },\n individual_id: {\n type: 'string'\n }\n },\n required: [ 'body',\n 'code',\n 'individual_id'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/src/resources/hris/benefits/individuals.ts b/src/resources/hris/benefits/individuals.ts index 58862f4e5..0f631923d 100644 --- a/src/resources/hris/benefits/individuals.ts +++ b/src/resources/hris/benefits/individuals.ts @@ -130,12 +130,21 @@ export namespace IndividualBenefit { */ catch_up: boolean | null; + /** + * Company contribution configuration. Supports fixed amounts (in cents), + * percentage-based contributions (in basis points where 100 = 1%), or tiered + * matching structures. + */ company_contribution: | UnionMember0.UnionMember0 | UnionMember0.UnionMember1 | UnionMember0.UnionMember2 | null; + /** + * Employee deduction configuration. Supports both fixed amounts (in cents) and + * percentage-based contributions (in basis points where 100 = 1%). + */ employee_deduction: UnionMember0.UnionMember0 | UnionMember0.UnionMember1 | null; /** @@ -147,24 +156,28 @@ export namespace IndividualBenefit { export namespace UnionMember0 { export interface UnionMember0 { /** - * Contribution amount in cents. + * Contribution amount in cents (for type=fixed) or basis points (for type=percent, + * where 100 = 1%). Not used for type=tiered. */ amount: number; /** - * Fixed contribution type. + * Contribution type. Supported values: "fixed" (amount in cents), "percent" + * (amount in basis points), or "tiered" (multi-tier matching). */ type: 'fixed'; } export interface UnionMember1 { /** - * Contribution amount in basis points (1/100th of a percent). + * Contribution amount in cents (for type=fixed) or basis points (for type=percent, + * where 100 = 1%). Not used for type=tiered. */ amount: number; /** - * Percentage contribution type. + * Contribution type. Supported values: "fixed" (amount in cents), "percent" + * (amount in basis points), or "tiered" (multi-tier matching). */ type: 'percent'; } @@ -172,12 +185,13 @@ export namespace IndividualBenefit { export interface UnionMember2 { /** * Array of tier objects defining employer match tiers based on employee - * contribution thresholds. + * contribution thresholds. Required when type=tiered. */ tiers: Array; /** - * Tiered contribution type (only valid for company_contribution). + * Contribution type. Supported values: "fixed" (amount in cents), "percent" + * (amount in basis points), or "tiered" (multi-tier matching). */ type: 'tiered'; } @@ -192,24 +206,28 @@ export namespace IndividualBenefit { export interface UnionMember0 { /** - * Contribution amount in cents. + * Contribution amount in cents (for type=fixed) or basis points (for type=percent, + * where 100 = 1%). */ amount: number; /** - * Fixed contribution type. + * Contribution type. Supported values: "fixed" (amount in cents) or "percent" + * (amount in basis points). */ type: 'fixed'; } export interface UnionMember1 { /** - * Contribution amount in basis points (1/100th of a percent). + * Contribution amount in cents (for type=fixed) or basis points (for type=percent, + * where 100 = 1%). */ amount: number; /** - * Percentage contribution type. + * Contribution type. Supported values: "fixed" (amount in cents) or "percent" + * (amount in basis points). */ type: 'percent'; } diff --git a/src/resources/request-forwarding.ts b/src/resources/request-forwarding.ts index 4544f37d5..9832aa6b5 100644 --- a/src/resources/request-forwarding.ts +++ b/src/resources/request-forwarding.ts @@ -71,7 +71,7 @@ export namespace RequestForwardingForwardResponse { /** * The HTTP headers that were specified for the forwarded request. */ - headers?: { [key: string]: unknown } | null; + headers?: { [key: string]: string } | null; /** * The query parameters that were specified for the forwarded request. From b02890809d93b7496cc2deeb9f0547d5b180cd06 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:20:32 +0000 Subject: [PATCH 14/40] chore(mcp): clarify http auth error --- packages/mcp-server/src/headers.ts | 4 +++- packages/mcp-server/src/http.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/mcp-server/src/headers.ts b/packages/mcp-server/src/headers.ts index c9b5810d4..e127d4e82 100644 --- a/packages/mcp-server/src/headers.ts +++ b/packages/mcp-server/src/headers.ts @@ -17,7 +17,9 @@ export const parseAuthHeaders = (req: IncomingMessage): Partial = clientSecret: rawValue.slice(rawValue.search(':') + 1), }; default: - throw new Error(`Unsupported authorization scheme`); + throw new Error( + 'Unsupported authorization scheme. Expected the "Authorization" header to be a supported scheme (Bearer, Basic).', + ); } } diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts index ec34ab47d..84517003c 100644 --- a/packages/mcp-server/src/http.ts +++ b/packages/mcp-server/src/http.ts @@ -46,12 +46,12 @@ const newServer = ({ }, mcpOptions, }); - } catch { + } catch (error) { res.status(401).json({ jsonrpc: '2.0', error: { code: -32000, - message: 'Unauthorized', + message: `Unauthorized: ${error instanceof Error ? error.message : error}`, }, }); return null; From 738e75d5e4be2a4a2f4819941fc3af918094e9e5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 19:10:06 +0000 Subject: [PATCH 15/40] fix(mcp): return tool execution error on jq failure --- packages/mcp-server/src/filtering.ts | 4 ++++ .../tools/access-tokens/create-access-tokens.ts | 13 ++++++++++--- .../src/tools/account/disconnect-account.ts | 13 ++++++++++--- .../src/tools/account/introspect-account.ts | 13 ++++++++++--- .../connect/sessions/new-connect-sessions.ts | 13 ++++++++++--- .../sessions/reauthenticate-connect-sessions.ts | 17 ++++++++++++----- .../tools/hris/benefits/create-hris-benefits.ts | 13 ++++++++++--- .../enroll-many-benefits-hris-individuals.ts | 17 ++++++++++++----- .../enrolled-ids-benefits-hris-individuals.ts | 17 ++++++++++++----- ...e-many-benefits-benefits-hris-individuals.ts | 13 ++++++++++--- .../unenroll-many-benefits-hris-individuals.ts | 17 ++++++++++++----- .../tools/hris/benefits/list-hris-benefits.ts | 13 ++++++++++--- .../list-supported-benefits-hris-benefits.ts | 13 ++++++++++--- .../hris/benefits/retrieve-hris-benefits.ts | 17 ++++++++++++----- .../tools/hris/benefits/update-hris-benefits.ts | 17 ++++++++++++----- .../list-company-hris-pay-statement-item.ts | 13 ++++++++++--- ...ate-pay-statement-item-company-hris-rules.ts | 17 ++++++++++++----- ...ete-pay-statement-item-company-hris-rules.ts | 17 ++++++++++++----- ...ist-pay-statement-item-company-hris-rules.ts | 13 ++++++++++--- ...ate-pay-statement-item-company-hris-rules.ts | 17 ++++++++++++----- .../tools/hris/company/retrieve-hris-company.ts | 13 ++++++++++--- .../tools/hris/directory/list-hris-directory.ts | 13 ++++++++++--- .../tools/hris/documents/list-hris-documents.ts | 13 ++++++++++--- .../hris/documents/retreive-hris-documents.ts | 17 ++++++++++++----- .../retrieve-many-hris-individuals.ts | 13 ++++++++++--- .../tools/hris/payments/list-hris-payments.ts | 13 ++++++++++--- .../jobs/automated/create-jobs-automated.ts | 13 ++++++++++--- .../tools/jobs/automated/list-jobs-automated.ts | 13 ++++++++++--- .../jobs/automated/retrieve-jobs-automated.ts | 13 ++++++++++--- .../tools/jobs/manual/retrieve-jobs-manual.ts | 13 ++++++++++--- .../pay-groups/list-payroll-pay-groups.ts | 13 ++++++++++--- .../pay-groups/retrieve-payroll-pay-groups.ts | 17 ++++++++++++----- .../src/tools/providers/list-providers.ts | 13 ++++++++++--- .../forward-request-forwarding.ts | 13 ++++++++++--- .../sandbox/company/update-sandbox-company.ts | 13 ++++++++++--- .../create-connections-sandbox-accounts.ts | 17 ++++++++++++----- .../update-connections-sandbox-accounts.ts | 17 ++++++++++++----- .../connections/create-sandbox-connections.ts | 13 ++++++++++--- .../directory/create-sandbox-directory.ts | 13 ++++++++++--- .../individual/update-sandbox-individual.ts | 17 ++++++++++++----- .../retrieve-jobs-sandbox-configuration.ts | 17 ++++++++++++----- .../update-jobs-sandbox-configuration.ts | 17 ++++++++++++----- .../tools/sandbox/jobs/create-sandbox-jobs.ts | 13 ++++++++++--- .../sandbox/payment/create-sandbox-payment.ts | 13 ++++++++++--- packages/mcp-server/src/tools/types.ts | 12 ++++++++++++ 45 files changed, 478 insertions(+), 161 deletions(-) diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts index 1aa9a40cb..eaae0fcfb 100644 --- a/packages/mcp-server/src/filtering.ts +++ b/packages/mcp-server/src/filtering.ts @@ -12,3 +12,7 @@ export async function maybeFilter(jqFilter: unknown | undefined, response: any): async function jq(json: any, jqFilter: string) { return (await initJq).json(json, jqFilter); } + +export function isJqError(error: any): error is Error { + return error instanceof Error && 'stderr' in error; +} diff --git a/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts b/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts index 78e30059b..362ff5846 100644 --- a/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts +++ b/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -52,7 +52,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.accessTokens.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.accessTokens.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/account/disconnect-account.ts b/packages/mcp-server/src/tools/account/disconnect-account.ts index 7e65c7d37..fbfa74c52 100644 --- a/packages/mcp-server/src/tools/account/disconnect-account.ts +++ b/packages/mcp-server/src/tools/account/disconnect-account.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -36,7 +36,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.account.disconnect())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.account.disconnect())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/account/introspect-account.ts b/packages/mcp-server/src/tools/account/introspect-account.ts index a1ceb6a10..3cb5d9d4f 100644 --- a/packages/mcp-server/src/tools/account/introspect-account.ts +++ b/packages/mcp-server/src/tools/account/introspect-account.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -38,7 +38,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.account.introspect())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.account.introspect())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts b/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts index dcc65e401..7b4d7a545 100644 --- a/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts +++ b/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -102,7 +102,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.connect.sessions.new(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.connect.sessions.new(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/connect/sessions/reauthenticate-connect-sessions.ts b/packages/mcp-server/src/tools/connect/sessions/reauthenticate-connect-sessions.ts index 15b21a9a3..6bbef87a1 100644 --- a/packages/mcp-server/src/tools/connect/sessions/reauthenticate-connect-sessions.ts +++ b/packages/mcp-server/src/tools/connect/sessions/reauthenticate-connect-sessions.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -68,9 +68,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.connect.sessions.reauthenticate(body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.connect.sessions.reauthenticate(body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/create-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/create-hris-benefits.ts index dd236291a..6d2556dc8 100644 --- a/packages/mcp-server/src/tools/hris/benefits/create-hris-benefits.ts +++ b/packages/mcp-server/src/tools/hris/benefits/create-hris-benefits.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -116,7 +116,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.hris.benefits.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.hris.benefits.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/enroll-many-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/enroll-many-benefits-hris-individuals.ts index 0df297acd..7655faca6 100644 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/enroll-many-benefits-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/benefits/individuals/enroll-many-benefits-hris-individuals.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -131,9 +131,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { benefit_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.benefits.individuals.enrollMany(benefit_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.hris.benefits.individuals.enrollMany(benefit_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts index 72ab360e8..567c28920 100644 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -48,9 +48,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { benefit_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.benefits.individuals.enrolledIDs(benefit_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.hris.benefits.individuals.enrolledIDs(benefit_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts index ce7e05adb..2ae53758b 100644 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -54,7 +54,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { benefit_id, jq_filter, ...body } = args as any; const response = await client.hris.benefits.individuals.retrieveManyBenefits(benefit_id, body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/unenroll-many-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/unenroll-many-benefits-hris-individuals.ts index 6efef5787..2217a5ea0 100644 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/unenroll-many-benefits-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/benefits/individuals/unenroll-many-benefits-hris-individuals.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -55,9 +55,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { benefit_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.benefits.individuals.unenrollMany(benefit_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.hris.benefits.individuals.unenrollMany(benefit_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/list-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/list-hris-benefits.ts index a2524e0f1..7e62ae283 100644 --- a/packages/mcp-server/src/tools/hris/benefits/list-hris-benefits.ts +++ b/packages/mcp-server/src/tools/hris/benefits/list-hris-benefits.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -46,7 +46,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; const response = await client.hris.benefits.list(body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/list-supported-benefits-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/list-supported-benefits-hris-benefits.ts index 323e8f5fc..eb2692e70 100644 --- a/packages/mcp-server/src/tools/hris/benefits/list-supported-benefits-hris-benefits.ts +++ b/packages/mcp-server/src/tools/hris/benefits/list-supported-benefits-hris-benefits.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -46,7 +46,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; const response = await client.hris.benefits.listSupportedBenefits(body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/retrieve-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/retrieve-hris-benefits.ts index 3ba7ef8c8..9fae4a7e4 100644 --- a/packages/mcp-server/src/tools/hris/benefits/retrieve-hris-benefits.ts +++ b/packages/mcp-server/src/tools/hris/benefits/retrieve-hris-benefits.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -48,9 +48,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { benefit_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.benefits.retrieve(benefit_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.hris.benefits.retrieve(benefit_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/update-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/update-hris-benefits.ts index 6175fc5bf..b3f26f73d 100644 --- a/packages/mcp-server/src/tools/hris/benefits/update-hris-benefits.ts +++ b/packages/mcp-server/src/tools/hris/benefits/update-hris-benefits.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -50,9 +50,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { benefit_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.benefits.update(benefit_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.hris.benefits.update(benefit_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/list-company-hris-pay-statement-item.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/list-company-hris-pay-statement-item.ts index 00fee1e46..55b9b1d80 100644 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/list-company-hris-pay-statement-item.ts +++ b/packages/mcp-server/src/tools/hris/company/pay-statement-item/list-company-hris-pay-statement-item.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -75,7 +75,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; const response = await client.hris.company.payStatementItem.list(body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules.ts index c4e51c454..33df55291 100644 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules.ts +++ b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -91,9 +91,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.create(body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.create(body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules.ts index 1211f96a1..021b22be3 100644 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules.ts +++ b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -48,9 +48,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { rule_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.delete(rule_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.delete(rule_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules.ts index 24e345b5f..b4b62db3c 100644 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules.ts +++ b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -46,7 +46,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; const response = await client.hris.company.payStatementItem.rules.list(body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules.ts index 80f2e66d4..07fd3a136 100644 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules.ts +++ b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -52,9 +52,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { rule_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.update(rule_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.update(rule_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/retrieve-hris-company.ts b/packages/mcp-server/src/tools/hris/company/retrieve-hris-company.ts index 234a6fc8a..5f129a6fe 100644 --- a/packages/mcp-server/src/tools/hris/company/retrieve-hris-company.ts +++ b/packages/mcp-server/src/tools/hris/company/retrieve-hris-company.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -45,7 +45,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.hris.company.retrieve(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.hris.company.retrieve(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/directory/list-hris-directory.ts b/packages/mcp-server/src/tools/hris/directory/list-hris-directory.ts index 0c6ca02b3..8b4ea1c94 100644 --- a/packages/mcp-server/src/tools/hris/directory/list-hris-directory.ts +++ b/packages/mcp-server/src/tools/hris/directory/list-hris-directory.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -54,7 +54,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; const response = await client.hris.directory.list(body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/documents/list-hris-documents.ts b/packages/mcp-server/src/tools/hris/documents/list-hris-documents.ts index edd287c36..e9063123e 100644 --- a/packages/mcp-server/src/tools/hris/documents/list-hris-documents.ts +++ b/packages/mcp-server/src/tools/hris/documents/list-hris-documents.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -69,7 +69,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.hris.documents.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.hris.documents.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/documents/retreive-hris-documents.ts b/packages/mcp-server/src/tools/hris/documents/retreive-hris-documents.ts index bfb0d1072..6e07907e5 100644 --- a/packages/mcp-server/src/tools/hris/documents/retreive-hris-documents.ts +++ b/packages/mcp-server/src/tools/hris/documents/retreive-hris-documents.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -48,9 +48,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { document_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.documents.retreive(document_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.hris.documents.retreive(document_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/individuals/retrieve-many-hris-individuals.ts b/packages/mcp-server/src/tools/hris/individuals/retrieve-many-hris-individuals.ts index c42ab6e0e..2e2bdeebf 100644 --- a/packages/mcp-server/src/tools/hris/individuals/retrieve-many-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/individuals/retrieve-many-hris-individuals.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -66,7 +66,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; const response = await client.hris.individuals.retrieveMany(body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/payments/list-hris-payments.ts b/packages/mcp-server/src/tools/hris/payments/list-hris-payments.ts index 4f4163fce..b90e1a485 100644 --- a/packages/mcp-server/src/tools/hris/payments/list-hris-payments.ts +++ b/packages/mcp-server/src/tools/hris/payments/list-hris-payments.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -56,7 +56,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; const response = await client.hris.payments.list(body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/jobs/automated/create-jobs-automated.ts b/packages/mcp-server/src/tools/jobs/automated/create-jobs-automated.ts index efd870cbc..b66dee36b 100644 --- a/packages/mcp-server/src/tools/jobs/automated/create-jobs-automated.ts +++ b/packages/mcp-server/src/tools/jobs/automated/create-jobs-automated.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -69,7 +69,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/jobs/automated/list-jobs-automated.ts b/packages/mcp-server/src/tools/jobs/automated/list-jobs-automated.ts index dde710268..715441639 100644 --- a/packages/mcp-server/src/tools/jobs/automated/list-jobs-automated.ts +++ b/packages/mcp-server/src/tools/jobs/automated/list-jobs-automated.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -46,7 +46,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/jobs/automated/retrieve-jobs-automated.ts b/packages/mcp-server/src/tools/jobs/automated/retrieve-jobs-automated.ts index 212feeaed..456b9e36b 100644 --- a/packages/mcp-server/src/tools/jobs/automated/retrieve-jobs-automated.ts +++ b/packages/mcp-server/src/tools/jobs/automated/retrieve-jobs-automated.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { job_id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.retrieve(job_id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.retrieve(job_id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/jobs/manual/retrieve-jobs-manual.ts b/packages/mcp-server/src/tools/jobs/manual/retrieve-jobs-manual.ts index 39670f067..9557ab639 100644 --- a/packages/mcp-server/src/tools/jobs/manual/retrieve-jobs-manual.ts +++ b/packages/mcp-server/src/tools/jobs/manual/retrieve-jobs-manual.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { job_id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.manual.retrieve(job_id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.manual.retrieve(job_id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/payroll/pay-groups/list-payroll-pay-groups.ts b/packages/mcp-server/src/tools/payroll/pay-groups/list-payroll-pay-groups.ts index e76086bc6..9a2cf6f30 100644 --- a/packages/mcp-server/src/tools/payroll/pay-groups/list-payroll-pay-groups.ts +++ b/packages/mcp-server/src/tools/payroll/pay-groups/list-payroll-pay-groups.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -55,7 +55,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; const response = await client.payroll.payGroups.list(body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/payroll/pay-groups/retrieve-payroll-pay-groups.ts b/packages/mcp-server/src/tools/payroll/pay-groups/retrieve-payroll-pay-groups.ts index be62e8bff..0a270fbff 100644 --- a/packages/mcp-server/src/tools/payroll/pay-groups/retrieve-payroll-pay-groups.ts +++ b/packages/mcp-server/src/tools/payroll/pay-groups/retrieve-payroll-pay-groups.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -48,9 +48,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { pay_group_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.payroll.payGroups.retrieve(pay_group_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.payroll.payGroups.retrieve(pay_group_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/providers/list-providers.ts b/packages/mcp-server/src/tools/providers/list-providers.ts index 70e47a5df..3a18de80d 100644 --- a/packages/mcp-server/src/tools/providers/list-providers.ts +++ b/packages/mcp-server/src/tools/providers/list-providers.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -39,7 +39,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter } = args as any; const response = await client.providers.list().asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts b/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts index 4407f890f..2296cf130 100644 --- a/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts +++ b/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -63,7 +63,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.requestForwarding.forward(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.requestForwarding.forward(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/company/update-sandbox-company.ts b/packages/mcp-server/src/tools/sandbox/company/update-sandbox-company.ts index e46a0642d..8d3645b2f 100644 --- a/packages/mcp-server/src/tools/sandbox/company/update-sandbox-company.ts +++ b/packages/mcp-server/src/tools/sandbox/company/update-sandbox-company.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -179,7 +179,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.company.update(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.company.update(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/connections/accounts/create-connections-sandbox-accounts.ts b/packages/mcp-server/src/tools/sandbox/connections/accounts/create-connections-sandbox-accounts.ts index e330b866e..f13682ad3 100644 --- a/packages/mcp-server/src/tools/sandbox/connections/accounts/create-connections-sandbox-accounts.ts +++ b/packages/mcp-server/src/tools/sandbox/connections/accounts/create-connections-sandbox-accounts.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -56,9 +56,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.sandbox.connections.accounts.create(body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.sandbox.connections.accounts.create(body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/connections/accounts/update-connections-sandbox-accounts.ts b/packages/mcp-server/src/tools/sandbox/connections/accounts/update-connections-sandbox-accounts.ts index 0db70e8e7..99847b2e3 100644 --- a/packages/mcp-server/src/tools/sandbox/connections/accounts/update-connections-sandbox-accounts.ts +++ b/packages/mcp-server/src/tools/sandbox/connections/accounts/update-connections-sandbox-accounts.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -47,9 +47,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.sandbox.connections.accounts.update(body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.sandbox.connections.accounts.update(body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/connections/create-sandbox-connections.ts b/packages/mcp-server/src/tools/sandbox/connections/create-sandbox-connections.ts index a87e34ff2..d13d1a61a 100644 --- a/packages/mcp-server/src/tools/sandbox/connections/create-sandbox-connections.ts +++ b/packages/mcp-server/src/tools/sandbox/connections/create-sandbox-connections.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -56,7 +56,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.connections.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.connections.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts b/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts index 3b2b782bb..03805678b 100644 --- a/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts +++ b/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -306,7 +306,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.directory.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.directory.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/individual/update-sandbox-individual.ts b/packages/mcp-server/src/tools/sandbox/individual/update-sandbox-individual.ts index 6e57f5dd0..cc9f2af89 100644 --- a/packages/mcp-server/src/tools/sandbox/individual/update-sandbox-individual.ts +++ b/packages/mcp-server/src/tools/sandbox/individual/update-sandbox-individual.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -162,9 +162,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { individual_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.sandbox.individual.update(individual_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.sandbox.individual.update(individual_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration.ts b/packages/mcp-server/src/tools/sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration.ts index c91dcfcd3..f50cd3aaa 100644 --- a/packages/mcp-server/src/tools/sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration.ts +++ b/packages/mcp-server/src/tools/sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -38,9 +38,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.sandbox.jobs.configuration.retrieve()), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.sandbox.jobs.configuration.retrieve()), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/jobs/configuration/update-jobs-sandbox-configuration.ts b/packages/mcp-server/src/tools/sandbox/jobs/configuration/update-jobs-sandbox-configuration.ts index cc6471dc5..93314437f 100644 --- a/packages/mcp-server/src/tools/sandbox/jobs/configuration/update-jobs-sandbox-configuration.ts +++ b/packages/mcp-server/src/tools/sandbox/jobs/configuration/update-jobs-sandbox-configuration.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -46,9 +46,16 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.sandbox.jobs.configuration.update(body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.sandbox.jobs.configuration.update(body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/jobs/create-sandbox-jobs.ts b/packages/mcp-server/src/tools/sandbox/jobs/create-sandbox-jobs.ts index 897d9b886..15e0f2a7e 100644 --- a/packages/mcp-server/src/tools/sandbox/jobs/create-sandbox-jobs.ts +++ b/packages/mcp-server/src/tools/sandbox/jobs/create-sandbox-jobs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.jobs.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.jobs.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts b/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts index fcfb613d3..8d86f1bc0 100644 --- a/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts +++ b/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -209,7 +209,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.payment.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.payment.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/tools/types.ts index d55df0d15..317283d93 100644 --- a/packages/mcp-server/src/tools/types.ts +++ b/packages/mcp-server/src/tools/types.ts @@ -87,6 +87,18 @@ export async function asBinaryContentResult(response: Response): Promise Date: Wed, 12 Nov 2025 20:51:39 +0000 Subject: [PATCH 16/40] chore(mcp): upgrade jq-web --- packages/mcp-server/package.json | 2 +- packages/mcp-server/yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 8e3796296..b970c5e48 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -37,7 +37,7 @@ "cors": "^2.8.5", "express": "^5.1.0", "fuse.js": "^7.1.0", - "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", + "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz", "qs": "^6.14.0", "typescript": "5.8.3", "yargs": "^17.7.2", diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock index 966d05755..2bb21c66f 100644 --- a/packages/mcp-server/yarn.lock +++ b/packages/mcp-server/yarn.lock @@ -2494,9 +2494,9 @@ jest@^29.4.0: import-local "^3.0.2" jest-cli "^29.7.0" -"jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz": - version "0.8.6" - resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz#14d0e126987736e82e964d675c3838b5944faa6f" +"jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz": + version "0.8.8" + resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz#7849ef64bdfc28f70cbfc9888f886860e96da10d" js-tokens@^4.0.0: version "4.0.0" From ec5d7fd9792c1a95be18f099764996796c949bb9 Mon Sep 17 00:00:00 2001 From: Jon Harrell <4829245+jharrell@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:19:25 -0600 Subject: [PATCH 17/40] fix(api): migrate custom code to TypeScript --- package.json | 4 +- src/client.ts | 35 +++ src/index.ts | 405 +-------------------------------- src/internal/headers.ts | 47 ++++ src/resources/access-tokens.ts | 11 +- src/resources/webhooks.ts | 4 +- yarn.lock | 8 +- 7 files changed, 100 insertions(+), 414 deletions(-) diff --git a/package.json b/package.json index d7ccaa8be..9b76cc58b 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "lint": "./scripts/lint", "fix": "./scripts/format" }, - "dependencies": {}, + "dependencies": { + "@noble/hashes": "^2.0.1" + }, "devDependencies": { "@arethetypeswrong/cli": "^0.17.0", "@swc/core": "^1.3.102", diff --git a/src/client.ts b/src/client.ts index 4ea39f253..35d59aac3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -845,6 +845,41 @@ export class Finch { sandbox: API.Sandbox = new API.Sandbox(this); payroll: API.Payroll = new API.Payroll(this); connect: API.Connect = new API.Connect(this); + + /** + * Returns the authorization url which can be visited in order to obtain an + * authorization code from Finch. The authorization code can then be exchanged for + * an access token for the Finch api by calling get_access_token(). + */ + getAuthURL({ + products, + redirectUri, + sandbox, + }: { + products: string; + redirectUri: string; + sandbox: boolean; + }): string { + if (!this.clientID) { + throw new Error('Expected `clientID` to be set in order to call getAuthUrl'); + } + const url = new URL('/authorize', 'https://connect.tryfinch.com/authorize'); + url.search = this.stringifyQuery({ + client_id: this.clientID, + products, + redirect_uri: redirectUri, + sandbox, + }); + return url.toString(); + } + + /** + * Returns a copy of the current Finch client with the given access token for + * authentication. + */ + withAccessToken(accessToken: string): Finch { + return new Finch({ ...this._options, accessToken }); + } } Finch.AccessTokens = AccessTokens; diff --git a/src/index.ts b/src/index.ts index 14ae8b5f7..ad0e54500 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,407 +2,10 @@ export { Finch as default } from './client'; -export interface ClientOptions { - accessToken?: string | null | undefined; - - /** - * Defaults to process.env['FINCH_CLIENT_ID']. - */ - clientId?: string | null | undefined; - - /** - * Defaults to process.env['FINCH_CLIENT_SECRET']. - */ - clientSecret?: string | null | undefined; - - /** - * Defaults to process.env['FINCH_WEBHOOK_SECRET']. - */ - webhookSecret?: string | null | undefined; - - /** - * Override the default base URL for the API, e.g., "https://api.example.com/v2/" - * - * Defaults to process.env['FINCH_BASE_URL']. - */ - baseURL?: string | null | undefined; - - /** - * The maximum amount of time (in milliseconds) that the client should wait for a response - * from the server before timing out a single request. - * - * Note that request timeouts are retried by default, so in a worst-case scenario you may wait - * much longer than this timeout before the promise succeeds or fails. - * - * @unit milliseconds - */ - timeout?: number | undefined; - - /** - * An HTTP agent used to manage HTTP(S) connections. - * - * If not provided, an agent will be constructed by default in the Node.js environment, - * otherwise no agent is used. - */ - httpAgent?: Agent | undefined; - - /** - * Specify a custom `fetch` function implementation. - * - * If not provided, we use `node-fetch` on Node.js and otherwise expect that `fetch` is - * defined globally. - */ - fetch?: Core.Fetch | undefined; - - /** - * The maximum number of times that the client will retry a request in case of a - * temporary failure, like a network error or a 5XX error from the server. - * - * @default 2 - */ - maxRetries?: number | undefined; - - /** - * Default headers to include with every request to the API. - * - * These can be removed in individual requests by explicitly setting the - * header to `undefined` or `null` in request options. - */ - defaultHeaders?: Core.Headers | undefined; - - /** - * Default query parameters to include with every request to the API. - * - * These can be removed in individual requests by explicitly setting the - * param to `undefined` in request options. - */ - defaultQuery?: Core.DefaultQuery | undefined; - - /** - * By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers. - * Only set this option to `true` if you understand the risks and have appropriate mitigations in place. - */ - dangerouslyAllowBrowser?: boolean | undefined; -} - -/** - * API Client for interfacing with the Finch API. - */ -export class Finch extends Core.APIClient { - accessToken: string | null; - clientId: string | null; - clientSecret: string | null; - webhookSecret: string | null; - - private _options: ClientOptions; - - /** - * API Client for interfacing with the Finch API. - * - * @param {string | null | undefined} [opts.accessToken] - * @param {string | null | undefined} [opts.clientId=process.env['FINCH_CLIENT_ID'] ?? null] - * @param {string | null | undefined} [opts.clientSecret=process.env['FINCH_CLIENT_SECRET'] ?? null] - * @param {string | null | undefined} [opts.webhookSecret=process.env['FINCH_WEBHOOK_SECRET'] ?? null] - * @param {string} [opts.baseURL=process.env['FINCH_BASE_URL'] ?? https://api.tryfinch.com] - Override the default base URL for the API. - * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. - * @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections. - * @param {Core.Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. - * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. - * @param {Core.Headers} opts.defaultHeaders - Default headers to include with every request to the API. - * @param {Core.DefaultQuery} opts.defaultQuery - Default query parameters to include with every request to the API. - * @param {boolean} [opts.dangerouslyAllowBrowser=false] - By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers. - */ - constructor({ - baseURL = Core.readEnv('FINCH_BASE_URL'), - accessToken = null, - clientId = Core.readEnv('FINCH_CLIENT_ID') ?? null, - clientSecret = Core.readEnv('FINCH_CLIENT_SECRET') ?? null, - webhookSecret = Core.readEnv('FINCH_WEBHOOK_SECRET') ?? null, - ...opts - }: ClientOptions = {}) { - const options: ClientOptions = { - accessToken, - clientId, - clientSecret, - webhookSecret, - ...opts, - baseURL: baseURL || `https://api.tryfinch.com`, - }; - - if (!options.dangerouslyAllowBrowser && Core.isRunningInBrowser()) { - throw new Errors.FinchError( - 'This is disabled by default, as it risks exposing your secret API credentials to attackers.\nIf you understand the risks and have appropriate mitigations in place,\nyou can set the `dangerouslyAllowBrowser` option to `true`, e.g.,\n\nnew Finch({ dangerouslyAllowBrowser: true })', - ); - } - - super({ - baseURL: options.baseURL!, - baseURLOverridden: baseURL ? baseURL !== 'https://api.tryfinch.com' : false, - timeout: options.timeout ?? 60000 /* 1 minute */, - httpAgent: options.httpAgent, - maxRetries: options.maxRetries, - fetch: options.fetch, - }); - - this._options = options; - - this.accessToken = accessToken; - this.clientId = clientId; - this.clientSecret = clientSecret; - this.webhookSecret = webhookSecret; - } - - accessTokens: API.AccessTokens = new API.AccessTokens(this); - hris: API.HRIS = new API.HRIS(this); - providers: API.Providers = new API.Providers(this); - account: API.Account = new API.Account(this); - webhooks: API.Webhooks = new API.Webhooks(this); - requestForwarding: API.RequestForwarding = new API.RequestForwarding(this); - jobs: API.Jobs = new API.Jobs(this); - sandbox: API.Sandbox = new API.Sandbox(this); - payroll: API.Payroll = new API.Payroll(this); - connect: API.Connect = new API.Connect(this); - - /** - * DEPRECATED: use client.accessTokens.create instead. - */ - getAccessToken(code: string, options?: { redirectUri: string }): Promise { - if (!this.clientId) { - throw new Error('Expected the clientId to be set in order to call getAccessToken'); - } - if (!this.clientSecret) { - throw new Error('Expected the clientSecret to be set in order to call getAccessToken'); - } - return this.post, { access_token: string }>('/auth/token', { - body: { - client_id: this.clientId, - client_secret: this.clientSecret, - code: code, - redirect_uri: options?.redirectUri, - }, - headers: { - authorization: null, - }, - }).then((response) => response.access_token); - } - /** - * Returns the authorization url which can be visited in order to obtain an - * authorization code from Finch. The authorization code can then be exchanged for - * an access token for the Finch api by calling get_access_token(). - */ - getAuthURL({ - products, - redirectUri, - sandbox, - }: { - products: string; - redirectUri: string; - sandbox: boolean; - }): string { - if (!this.clientId) { - throw new Error('Expected the clientId to be set in order to call getAuthUrl'); - } - const url = new URL('/authorize', 'https://connect.tryfinch.com/authorize'); - url.search = this.stringifyQuery({ - client_id: this.clientId, - products: products, - redirect_uri: redirectUri, - sandbox: sandbox, - }); - return url.toString(); - } - /** - * Returns a copy of the current Finch client with the given access token for - * authentication. - */ - withAccessToken(accessToken: string): Finch { - return new Finch({ ...this._options, accessToken }); - } - /** - * Check whether the base URL is set to its default. - */ - #baseURLOverridden(): boolean { - return this.baseURL !== 'https://api.tryfinch.com'; - } - - protected override defaultQuery(): Core.DefaultQuery | undefined { - return this._options.defaultQuery; - } - - protected override defaultHeaders(opts: Core.FinalRequestOptions): Core.Headers { - return { - ...super.defaultHeaders(opts), - 'Finch-API-Version': '2020-09-17', - ...this._options.defaultHeaders, - }; - } - - protected override validateHeaders(headers: Core.Headers, customHeaders: Core.Headers) { - if (this.accessToken && headers['authorization']) { - return; - } - if (customHeaders['authorization'] === null) { - return; - } - - if (this.clientId && this.clientSecret && headers['authorization']) { - return; - } - if (customHeaders['authorization'] === null) { - return; - } - - throw new Error( - 'Could not resolve authentication method. Expected either accessToken, clientId or clientSecret to be set. Or for one of the "Authorization" or "Authorization" headers to be explicitly omitted', - ); - } - - protected override authHeaders(opts: Core.FinalRequestOptions): Core.Headers { - const bearerAuth = this.bearerAuth(opts); - const basicAuth = this.basicAuth(opts); - - if (bearerAuth != null && !Core.isEmptyObj(bearerAuth)) { - return bearerAuth; - } - - if (basicAuth != null && !Core.isEmptyObj(basicAuth)) { - return basicAuth; - } - return {}; - } - - protected bearerAuth(opts: Core.FinalRequestOptions): Core.Headers { - if (this.accessToken == null) { - return {}; - } - return { Authorization: `Bearer ${this.accessToken}` }; - } - - protected basicAuth(opts: Core.FinalRequestOptions): Core.Headers { - if (!this.clientId) { - return {}; - } - - if (!this.clientSecret) { - return {}; - } - - const credentials = `${this.clientId}:${this.clientSecret}`; - const Authorization = `Basic ${Core.toBase64(credentials)}`; - return { Authorization }; - } - - protected override stringifyQuery(query: Record): string { - return qs.stringify(query, { arrayFormat: 'brackets' }); - } - - static Finch = this; - static DEFAULT_TIMEOUT = 60000; // 1 minute - - static FinchError = Errors.FinchError; - static APIError = Errors.APIError; - static APIConnectionError = Errors.APIConnectionError; - static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; - static APIUserAbortError = Errors.APIUserAbortError; - static NotFoundError = Errors.NotFoundError; - static ConflictError = Errors.ConflictError; - static RateLimitError = Errors.RateLimitError; - static BadRequestError = Errors.BadRequestError; - static AuthenticationError = Errors.AuthenticationError; - static InternalServerError = Errors.InternalServerError; - static PermissionDeniedError = Errors.PermissionDeniedError; - static UnprocessableEntityError = Errors.UnprocessableEntityError; - - static toFile = Uploads.toFile; - static fileFromPath = Uploads.fileFromPath; -} - -Finch.AccessTokens = AccessTokens; -Finch.HRIS = HRIS; -Finch.Providers = Providers; -Finch.ProviderListResponsesSinglePage = ProviderListResponsesSinglePage; -Finch.Account = Account; -Finch.Webhooks = Webhooks; -Finch.RequestForwarding = RequestForwarding; -Finch.Jobs = Jobs; -Finch.Sandbox = Sandbox; -Finch.Payroll = Payroll; -Finch.Connect = Connect; - -export declare namespace Finch { - export type RequestOptions = Core.RequestOptions; - - export import SinglePage = Pagination.SinglePage; - export { type SinglePageResponse as SinglePageResponse }; - - export import ResponsesPage = Pagination.ResponsesPage; - export { type ResponsesPageResponse as ResponsesPageResponse }; - - export import IndividualsPage = Pagination.IndividualsPage; - export { - type IndividualsPageParams as IndividualsPageParams, - type IndividualsPageResponse as IndividualsPageResponse, - }; - - export import Page = Pagination.Page; - export { type PageParams as PageParams, type PageResponse as PageResponse }; - - export { - AccessTokens as AccessTokens, - type CreateAccessTokenResponse as CreateAccessTokenResponse, - type AccessTokenCreateParams as AccessTokenCreateParams, - }; - - export { HRIS as HRIS, type Income as Income, type Location as Location, type Money as Money }; - - export { - Providers as Providers, - type Provider as Provider, - type ProviderListResponse as ProviderListResponse, - ProviderListResponsesSinglePage as ProviderListResponsesSinglePage, - }; - - export { - Account as Account, - type DisconnectResponse as DisconnectResponse, - type Introspection as Introspection, - }; - - export { - Webhooks as Webhooks, - type AccountUpdateEvent as AccountUpdateEvent, - type BaseWebhookEvent as BaseWebhookEvent, - type CompanyEvent as CompanyEvent, - type DirectoryEvent as DirectoryEvent, - type EmploymentEvent as EmploymentEvent, - type IndividualEvent as IndividualEvent, - type JobCompletionEvent as JobCompletionEvent, - type PayStatementEvent as PayStatementEvent, - type PaymentEvent as PaymentEvent, - type WebhookEvent as WebhookEvent, - }; - - export { - RequestForwarding as RequestForwarding, - type RequestForwardingForwardResponse as RequestForwardingForwardResponse, - type RequestForwardingForwardParams as RequestForwardingForwardParams, - }; - - export { Jobs as Jobs }; - - export { Sandbox as Sandbox }; - - export { Payroll as Payroll }; - - export { Connect as Connect }; - - export type ConnectionStatusType = API.ConnectionStatusType; - export type OperationSupport = API.OperationSupport; - export type OperationSupportMatrix = API.OperationSupportMatrix; - export type Paging = API.Paging; -} - -export { toFile, fileFromPath } from './uploads'; +export { type Uploadable, toFile } from './core/uploads'; +export { APIPromise } from './core/api-promise'; +export { Finch, type ClientOptions } from './client'; +export { PagePromise } from './core/pagination'; export { FinchError, APIError, diff --git a/src/internal/headers.ts b/src/internal/headers.ts index c724a9d22..6d9e48dc5 100644 --- a/src/internal/headers.ts +++ b/src/internal/headers.ts @@ -95,3 +95,50 @@ export const isEmptyHeaders = (headers: HeadersLike) => { for (const _ of iterateHeaders(headers)) return false; return true; }; + +export const getRequiredHeader = (headers: HeadersLike | Headers, header: string): string => { + const foundHeader = getHeader(headers, header); + if (foundHeader === undefined) { + throw new Error(`Could not find ${header} header`); + } + return foundHeader; +}; + +export const isHeadersProtocol = (headers: any): headers is HeadersProtocol => { + return typeof headers?.get === 'function'; +}; + +export interface HeadersProtocol { + get: (header: string) => string | null | undefined; +} + +export const getHeader = (headers: HeadersLike | Headers, header: string): string | undefined => { + if (!headers) return undefined; + + const lowerCasedHeader = header.toLowerCase(); + if (isHeadersProtocol(headers)) { + // to deal with the case where the header looks like Stainless-Event-Id + const intercapsHeader = + header[0]?.toUpperCase() + + header.substring(1).replace(/([^\w])(\w)/g, (_m, g1, g2) => g1 + g2.toUpperCase()); + for (const key of [header, lowerCasedHeader, header.toUpperCase(), intercapsHeader]) { + const value = headers.get(key); + if (value) { + return value; + } + } + } + + for (const [key, value] of Object.entries(headers)) { + if (key.toLowerCase() === lowerCasedHeader) { + if (Array.isArray(value)) { + if (value.length <= 1) return value[0]; + console.warn(`Received ${value.length} entries for the ${header} header, using the first entry.`); + return value[0]; + } + return value; + } + } + + return undefined; +}; diff --git a/src/resources/access-tokens.ts b/src/resources/access-tokens.ts index 651b61b2e..7c4d6884b 100644 --- a/src/resources/access-tokens.ts +++ b/src/resources/access-tokens.ts @@ -1,18 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import type { RequestOptions } from '../internal/request-options'; +import type { APIPromise } from '../core/api-promise'; import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; export class AccessTokens extends APIResource { /** * Exchange the authorization code for an access token */ - create( - body: AccessTokenCreateParams, - options?: Core.RequestOptions, - ): Core.APIPromise { - const clientID = body.client_id || this._client.clientId; + create(body: AccessTokenCreateParams, options?: RequestOptions): APIPromise { + const clientID = body.client_id || this._client.clientID; if (!clientID) throw new Error( 'client_id must be provided as an argument or with the FINCH_CLIENT_ID environment variable', diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index a83387f99..19a4c5923 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -3,7 +3,9 @@ import { APIResource } from '../core/resource'; import * as Shared from './shared'; import * as BenefitsAPI from './hris/benefits/benefits'; -import { fromBase64, getRequiredHeader, HeadersLike, toBase64 } from '../core'; +import type { HeadersLike } from '../internal/headers'; +import { getRequiredHeader } from '../internal/headers'; +import { fromBase64, toBase64 } from '../internal/utils'; import { hmac } from '@noble/hashes/hmac'; import { sha256 } from '@noble/hashes/sha2'; diff --git a/yarn.lock b/yarn.lock index 9fd07e737..12684cc43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -693,10 +693,10 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@noble/hashes@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" - integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== +"@noble/hashes@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.0.1.tgz#fc1a928061d1232b0a52bb754393c37a5216c89e" + integrity sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw== "@nodelib/fs.scandir@2.1.5": version "2.1.5" From f6be917b1e7116cf84b575166d59e332390eb904 Mon Sep 17 00:00:00 2001 From: Jon Harrell <4829245+jharrell@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:46:33 -0600 Subject: [PATCH 18/40] fix(api): resolve build issues --- package.json | 2 +- packages/mcp-server/yarn.lock | 321 +++++++++++++++++++++++++++++++- src/resources/hris/directory.ts | 2 +- yarn.lock | 8 +- 4 files changed, 323 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 9b76cc58b..aa1db64be 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "fix": "./scripts/format" }, "dependencies": { - "@noble/hashes": "^2.0.1" + "@noble/hashes": "^1.5.0" }, "devDependencies": { "@arethetypeswrong/cli": "^0.17.0", diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock index 2bb21c66f..ddf11f383 100644 --- a/packages/mcp-server/yarn.lock +++ b/packages/mcp-server/yarn.lock @@ -10,6 +10,20 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@anthropic-ai/mcpb@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@anthropic-ai/mcpb/-/mcpb-1.1.0.tgz#1af18de2ab9499d321d6310d0be095f5fef5161b" + integrity sha512-nOnhG1eNpGKSIDv6lt3xsI3w2p2k0D/rPTMGXXugLovCEaJ7svh8XMfCe145vs8qo384t8wKbokWAvx9PkQMDA== + dependencies: + "@inquirer/prompts" "^6.0.1" + commander "^13.1.0" + fflate "^0.8.2" + galactus "^1.0.0" + ignore "^7.0.5" + node-forge "^1.3.1" + pretty-bytes "^5.6.0" + zod "^3.25.67" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" @@ -336,6 +350,144 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@inquirer/checkbox@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-3.0.1.tgz#0a57f704265f78c36e17f07e421b98efb4b9867b" + integrity sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/confirm@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-4.0.1.tgz#9106d6bffa0b2fdd0e4f60319b6f04f2e06e6e25" + integrity sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/core@^9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-9.2.1.tgz#677c49dee399c9063f31e0c93f0f37bddc67add1" + integrity sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg== + dependencies: + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + "@types/mute-stream" "^0.0.4" + "@types/node" "^22.5.5" + "@types/wrap-ansi" "^3.0.0" + ansi-escapes "^4.3.2" + cli-width "^4.1.0" + mute-stream "^1.0.0" + signal-exit "^4.1.0" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/editor@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-3.0.1.tgz#d109f21e050af6b960725388cb1c04214ed7c7bc" + integrity sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + external-editor "^3.1.0" + +"@inquirer/expand@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-3.0.1.tgz#aed9183cac4d12811be47a4a895ea8e82a17e22c" + integrity sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/figures@^1.0.6": + version "1.0.15" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.15.tgz#dbb49ed80df11df74268023b496ac5d9acd22b3a" + integrity sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g== + +"@inquirer/input@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-3.0.1.tgz#de63d49e516487388508d42049deb70f2cb5f28e" + integrity sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/number@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/number/-/number-2.0.1.tgz#b9863080d02ab7dc2e56e16433d83abea0f2a980" + integrity sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/password@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-3.0.1.tgz#2a9a9143591088336bbd573bcb05d5bf080dbf87" + integrity sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + +"@inquirer/prompts@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-6.0.1.tgz#43f5c0ed35c5ebfe52f1d43d46da2d363d950071" + integrity sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A== + dependencies: + "@inquirer/checkbox" "^3.0.1" + "@inquirer/confirm" "^4.0.1" + "@inquirer/editor" "^3.0.1" + "@inquirer/expand" "^3.0.1" + "@inquirer/input" "^3.0.1" + "@inquirer/number" "^2.0.1" + "@inquirer/password" "^3.0.1" + "@inquirer/rawlist" "^3.0.1" + "@inquirer/search" "^2.0.1" + "@inquirer/select" "^3.0.1" + +"@inquirer/rawlist@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-3.0.1.tgz#729def358419cc929045f264131878ed379e0af3" + integrity sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/search@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/search/-/search-2.0.1.tgz#69b774a0a826de2e27b48981d01bc5ad81e73721" + integrity sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/select@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-3.0.1.tgz#1df9ed27fb85a5f526d559ac5ce7cc4e9dc4e7ec" + integrity sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/type@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-2.0.0.tgz#08fa513dca2cb6264fe1b0a2fabade051444e3f6" + integrity sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag== + dependencies: + mute-stream "^1.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -602,6 +754,11 @@ zod "^3.23.8" zod-to-json-schema "^3.24.1" +"@noble/hashes@1.5": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" + integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -647,6 +804,11 @@ dependencies: "@sinonjs/commons" "^3.0.0" +"@tryfinch/finch-api@file:../../dist": + version "6.38.0" + dependencies: + "@noble/hashes" "1.5" + "@ts-morph/common@~0.20.0": version "0.20.0" resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.20.0.tgz#3f161996b085ba4519731e4d24c35f6cba5b80af" @@ -795,6 +957,13 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== +"@types/mute-stream@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.4.tgz#77208e56a08767af6c5e1237be8888e2f255c478" + integrity sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow== + dependencies: + "@types/node" "*" + "@types/node@*": version "22.15.17" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055" @@ -802,6 +971,13 @@ dependencies: undici-types "~6.21.0" +"@types/node@^22.5.5": + version "22.19.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.1.tgz#1188f1ddc9f46b4cc3aec76749050b4e1f459b7b" + integrity sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ== + dependencies: + undici-types "~6.21.0" + "@types/qs@*", "@types/qs@^6.14.0": version "6.14.0" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" @@ -834,6 +1010,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/wrap-ansi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -980,7 +1161,7 @@ ajv@^6.12.4, ajv@^6.12.6: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-escapes@^4.2.1: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -1222,6 +1403,11 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + ci-info@^3.2.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" @@ -1237,6 +1423,11 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -1273,6 +1464,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +commander@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46" + integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1685,6 +1881,15 @@ express@^5.0.1, express@^5.1.0: type-is "^2.0.1" vary "^1.1.2" +external-editor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1730,6 +1935,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -1793,6 +2003,14 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== +flora-colossus@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/flora-colossus/-/flora-colossus-2.0.0.tgz#af1e85db0a8256ef05f3fb531c1235236c97220a" + integrity sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA== + dependencies: + debug "^4.3.4" + fs-extra "^10.1.0" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -1803,6 +2021,15 @@ fresh@^2.0.0: resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== +fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1818,6 +2045,20 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +fuse.js@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-7.1.0.tgz#306228b4befeee11e05b027087c2744158527d09" + integrity sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ== + +galactus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/galactus/-/galactus-1.0.0.tgz#c2615182afa0c6d0859b92e56ae36d052827db7e" + integrity sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ== + dependencies: + debug "^4.3.4" + flora-colossus "^2.0.0" + fs-extra "^10.1.0" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -1910,7 +2151,7 @@ gopd@^1.2.0: resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.2.9: +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -1965,11 +2206,23 @@ iconv-lite@0.6.3, iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +ignore@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + import-fresh@^3.2.1: version "3.3.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" @@ -2548,6 +2801,15 @@ json5@^2.2.2, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonfile@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -2721,6 +2983,11 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mute-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" + integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -2731,6 +2998,11 @@ negotiator@^1.0.0: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== +node-forge@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -2796,6 +3068,11 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + p-all@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" @@ -2939,6 +3216,11 @@ prettier@^3.0.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== +pretty-bytes@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -3086,7 +3368,7 @@ safe-buffer@5.2.1, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -"safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -3190,6 +3472,11 @@ signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -3334,6 +3621,13 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -3474,6 +3768,11 @@ undici-types@~6.21.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -3537,6 +3836,15 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -3597,6 +3905,11 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yoctocolors-cjs@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz#7e4964ea8ec422b7a40ac917d3a344cfd2304baa" + integrity sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw== + zod-to-json-schema@^3.24.1, zod-to-json-schema@^3.24.5: version "3.24.5" resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3" @@ -3612,7 +3925,7 @@ zod@^3.23.8: resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f" integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg== -zod@^3.25.20: +zod@^3.25.20, zod@^3.25.67: version "3.25.76" resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/src/resources/hris/directory.ts b/src/resources/hris/directory.ts index 110aa8468..7bdc13f3d 100644 --- a/src/resources/hris/directory.ts +++ b/src/resources/hris/directory.ts @@ -20,7 +20,7 @@ export class Directory extends APIResource { query: DirectoryListParams | null | undefined = {}, options?: RequestOptions, ): PagePromise { - return this._client.getAPIList('/employer/directory', IndividualsPage, { + return this._client.getAPIList('/employer/directory', IndividualsPage, { query, ...options, }); diff --git a/yarn.lock b/yarn.lock index 12684cc43..ea32d280c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -693,10 +693,10 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@noble/hashes@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.0.1.tgz#fc1a928061d1232b0a52bb754393c37a5216c89e" - integrity sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw== +"@noble/hashes@^1.5.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== "@nodelib/fs.scandir@2.1.5": version "2.1.5" From eee03b0f219aead8d6c7ab4e743ed3cf66f8e9e5 Mon Sep 17 00:00:00 2001 From: Jon Harrell <4829245+jharrell@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:06:11 -0600 Subject: [PATCH 19/40] fix(tests): fix tests --- tests/api-resources/access-tokens.test.ts | 2 +- tests/api-resources/top-level.test.ts | 10 +--------- tests/api-resources/webhooks.test.ts | 4 ++-- tests/core.test.ts | 2 +- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/api-resources/access-tokens.test.ts b/tests/api-resources/access-tokens.test.ts index eaf5bc2ce..e0265ba80 100644 --- a/tests/api-resources/access-tokens.test.ts +++ b/tests/api-resources/access-tokens.test.ts @@ -4,7 +4,7 @@ import Finch from '@tryfinch/finch-api'; const client = new Finch({ accessToken: 'My Access Token', - clientId: '6d28c315-5eaa-4071-8ea5-f030eb45edbc', + clientID: '6d28c315-5eaa-4071-8ea5-f030eb45edbc', clientSecret: 'My Client Secret', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/top-level.test.ts b/tests/api-resources/top-level.test.ts index da5588c87..89d6257e5 100644 --- a/tests/api-resources/top-level.test.ts +++ b/tests/api-resources/top-level.test.ts @@ -4,20 +4,12 @@ import Finch from '@tryfinch/finch-api'; const finch = new Finch({ accessToken: 'My Access Token', - clientId: '4ab15e51-11ad-49f4-acae-f343b7794375', + clientID: '4ab15e51-11ad-49f4-acae-f343b7794375', clientSecret: 'My Client Secret', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); describe('top level methods', () => { - test('getAccessToken with redirect', async () => { - const response = await finch.getAccessToken('my-authorization-code', { redirectUri: '/my-app/redirect' }); - }); - - test('getAccessToken without redirect', async () => { - const response = await finch.getAccessToken('my-authorization-code'); - }); - test('getAuthUrl', async () => { // TODO }); diff --git a/tests/api-resources/webhooks.test.ts b/tests/api-resources/webhooks.test.ts index 67888e03d..688f92686 100644 --- a/tests/api-resources/webhooks.test.ts +++ b/tests/api-resources/webhooks.test.ts @@ -1,11 +1,11 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Finch from '@tryfinch/finch-api'; -import { toBase64 } from '@tryfinch/finch-api/core'; +import { toBase64 } from '@tryfinch/finch-api/internal/utils'; const finch = new Finch({ accessToken: 'My Access Token', - clientId: '4ab15e51-11ad-49f4-acae-f343b7794375', + clientID: '4ab15e51-11ad-49f4-acae-f343b7794375', clientSecret: 'My Client Secret', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/core.test.ts b/tests/core.test.ts index b05127e8b..290dbc2e4 100644 --- a/tests/core.test.ts +++ b/tests/core.test.ts @@ -1,4 +1,4 @@ -import { fromBase64, toBase64 } from '@tryfinch/finch-api/core'; +import { fromBase64, toBase64 } from '@tryfinch/finch-api/internal/utils'; describe.each([true, false])('with Buffer (Buffer is %s)', (buffer) => { let originalBuffer: BufferConstructor; From 7866332384999fb7dd718cf04e1e767946b400db Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:39:19 +0000 Subject: [PATCH 20/40] fix(java): Resolve name collisions --- .stats.yml | 4 ++-- api.md | 4 ++-- packages/mcp-server/README.md | 2 +- packages/mcp-server/src/code-tool-worker.ts | 2 +- ...ect-sessions.ts => connect-connect-sessions.ts} | 6 +++--- packages/mcp-server/src/tools/index.ts | 4 ++-- .../forward-request-forwarding.ts | 8 ++++---- src/resources/connect/connect.ts | 8 ++++---- src/resources/connect/index.ts | 4 ++-- src/resources/connect/sessions.ts | 14 +++++++------- src/resources/request-forwarding.ts | 14 +++++++------- tests/api-resources/connect/sessions.test.ts | 8 ++++---- tests/api-resources/request-forwarding.test.ts | 2 +- 13 files changed, 40 insertions(+), 40 deletions(-) rename packages/mcp-server/src/tools/connect/sessions/{new-connect-sessions.ts => connect-connect-sessions.ts} (86%) diff --git a/.stats.yml b/.stats.yml index 8d224370b..f316e0628 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-970ab04a97244c68824c0c52e06925cba14fb7dbfc36c03167c1afe74cd1b150.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-640ec74bfc2f335f7277863ee0cbd0af98bb6018cb1ec2f116ce4145148f4477.yml openapi_spec_hash: 315e7859c3f77311261fb824b74a8247 -config_hash: f2846563903bf75ab0858872154df0f7 +config_hash: f921f92cc97449dcc1334a594b435c73 diff --git a/api.md b/api.md index 40b2f2745..c70ca7959 100644 --- a/api.md +++ b/api.md @@ -373,10 +373,10 @@ Methods: Types: -- SessionNewResponse +- SessionConnectResponse - SessionReauthenticateResponse Methods: -- client.connect.sessions.new({ ...params }) -> SessionNewResponse +- client.connect.sessions.connect({ ...params }) -> SessionConnectResponse - client.connect.sessions.reauthenticate({ ...params }) -> SessionReauthenticateResponse diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 5d51973fc..a6438fb79 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -392,5 +392,5 @@ The following tools are available in this MCP server. ### Resource `connect.sessions`: -- `new_connect_sessions` (`write`): Create a new connect session for an employer +- `connect_connect_sessions` (`write`): Create a new connect session for an employer - `reauthenticate_connect_sessions` (`write`): Create a new Connect session for reauthenticating an existing connection diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index c2d1dc70c..5a3d04553 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -87,7 +87,7 @@ const fuse = new Fuse( 'client.sandbox.jobs.configuration.update', 'client.payroll.payGroups.list', 'client.payroll.payGroups.retrieve', - 'client.connect.sessions.new', + 'client.connect.sessions.connect', 'client.connect.sessions.reauthenticate', ], { threshold: 1, shouldSort: true }, diff --git a/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts b/packages/mcp-server/src/tools/connect/sessions/connect-connect-sessions.ts similarity index 86% rename from packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts rename to packages/mcp-server/src/tools/connect/sessions/connect-connect-sessions.ts index 7b4d7a545..21642b523 100644 --- a/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts +++ b/packages/mcp-server/src/tools/connect/sessions/connect-connect-sessions.ts @@ -16,9 +16,9 @@ export const metadata: Metadata = { }; export const tool: Tool = { - name: 'new_connect_sessions', + name: 'connect_connect_sessions', description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nCreate a new connect session for an employer\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/session_new_response',\n $defs: {\n session_new_response: {\n type: 'object',\n properties: {\n connect_url: {\n type: 'string',\n description: 'The Connect URL to redirect the user to for authentication'\n },\n session_id: {\n type: 'string',\n description: 'The unique identifier for the created connect session'\n }\n },\n required: [ 'connect_url',\n 'session_id'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nCreate a new connect session for an employer\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/session_connect_response',\n $defs: {\n session_connect_response: {\n type: 'object',\n properties: {\n connect_url: {\n type: 'string',\n description: 'The Connect URL to redirect the user to for authentication'\n },\n session_id: {\n type: 'string',\n description: 'The unique identifier for the created connect session'\n }\n },\n required: [ 'connect_url',\n 'session_id'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -103,7 +103,7 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; try { - return asTextContentResult(await maybeFilter(jq_filter, await client.connect.sessions.new(body))); + return asTextContentResult(await maybeFilter(jq_filter, await client.connect.sessions.connect(body))); } catch (error) { if (isJqError(error)) { return asErrorResult(error.message); diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts index 438763953..15911eec6 100644 --- a/packages/mcp-server/src/tools/index.ts +++ b/packages/mcp-server/src/tools/index.ts @@ -48,7 +48,7 @@ import retrieve_jobs_sandbox_configuration from './sandbox/jobs/configuration/re import update_jobs_sandbox_configuration from './sandbox/jobs/configuration/update-jobs-sandbox-configuration'; import retrieve_payroll_pay_groups from './payroll/pay-groups/retrieve-payroll-pay-groups'; import list_payroll_pay_groups from './payroll/pay-groups/list-payroll-pay-groups'; -import new_connect_sessions from './connect/sessions/new-connect-sessions'; +import connect_connect_sessions from './connect/sessions/connect-connect-sessions'; import reauthenticate_connect_sessions from './connect/sessions/reauthenticate-connect-sessions'; export const endpoints: Endpoint[] = []; @@ -101,7 +101,7 @@ addEndpoint(retrieve_jobs_sandbox_configuration); addEndpoint(update_jobs_sandbox_configuration); addEndpoint(retrieve_payroll_pay_groups); addEndpoint(list_payroll_pay_groups); -addEndpoint(new_connect_sessions); +addEndpoint(connect_connect_sessions); addEndpoint(reauthenticate_connect_sessions); export type Filter = { diff --git a/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts b/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts index 2296cf130..cf3762490 100644 --- a/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts +++ b/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts @@ -37,16 +37,16 @@ export const tool: Tool = { description: 'The body for the forwarded request. This value must be specified as either a string or a valid JSON object.', }, - headers: { + params: { type: 'object', description: - 'The HTTP headers to include on the forwarded request. This value must be specified as an object of key-value pairs. Example: `{"Content-Type": "application/xml", "X-API-Version": "v1" }`', + 'The query parameters for the forwarded request. This value must be specified as a valid JSON object rather than a query string.', additionalProperties: true, }, - params: { + request_headers: { type: 'object', description: - 'The query parameters for the forwarded request. This value must be specified as a valid JSON object rather than a query string.', + 'The HTTP headers to include on the forwarded request. This value must be specified as an object of key-value pairs. Example: `{"Content-Type": "application/xml", "X-API-Version": "v1" }`', additionalProperties: true, }, jq_filter: { diff --git a/src/resources/connect/connect.ts b/src/resources/connect/connect.ts index 876dad049..31deaa2f9 100644 --- a/src/resources/connect/connect.ts +++ b/src/resources/connect/connect.ts @@ -3,8 +3,8 @@ import { APIResource } from '../../core/resource'; import * as SessionsAPI from './sessions'; import { - SessionNewParams, - SessionNewResponse, + SessionConnectParams, + SessionConnectResponse, SessionReauthenticateParams, SessionReauthenticateResponse, Sessions, @@ -19,9 +19,9 @@ Connect.Sessions = Sessions; export declare namespace Connect { export { Sessions as Sessions, - type SessionNewResponse as SessionNewResponse, + type SessionConnectResponse as SessionConnectResponse, type SessionReauthenticateResponse as SessionReauthenticateResponse, - type SessionNewParams as SessionNewParams, + type SessionConnectParams as SessionConnectParams, type SessionReauthenticateParams as SessionReauthenticateParams, }; } diff --git a/src/resources/connect/index.ts b/src/resources/connect/index.ts index 6e29c158f..79192b28a 100644 --- a/src/resources/connect/index.ts +++ b/src/resources/connect/index.ts @@ -3,8 +3,8 @@ export { Connect } from './connect'; export { Sessions, - type SessionNewResponse, + type SessionConnectResponse, type SessionReauthenticateResponse, - type SessionNewParams, + type SessionConnectParams, type SessionReauthenticateParams, } from './sessions'; diff --git a/src/resources/connect/sessions.ts b/src/resources/connect/sessions.ts index b56c9a0c5..fbd7201da 100644 --- a/src/resources/connect/sessions.ts +++ b/src/resources/connect/sessions.ts @@ -8,7 +8,7 @@ export class Sessions extends APIResource { /** * Create a new connect session for an employer */ - new(body: SessionNewParams, options?: RequestOptions): APIPromise { + connect(body: SessionConnectParams, options?: RequestOptions): APIPromise { return this._client.post('/connect/sessions', { body, ...options }); } @@ -23,7 +23,7 @@ export class Sessions extends APIResource { } } -export interface SessionNewResponse { +export interface SessionConnectResponse { /** * The Connect URL to redirect the user to for authentication */ @@ -47,7 +47,7 @@ export interface SessionReauthenticateResponse { session_id: string; } -export interface SessionNewParams { +export interface SessionConnectParams { /** * Unique identifier for the customer */ @@ -82,7 +82,7 @@ export interface SessionNewParams { /** * Integration configuration for the connect session */ - integration?: SessionNewParams.Integration | null; + integration?: SessionConnectParams.Integration | null; /** * Enable manual authentication mode @@ -106,7 +106,7 @@ export interface SessionNewParams { sandbox?: 'finch' | 'provider' | null; } -export namespace SessionNewParams { +export namespace SessionConnectParams { /** * Integration configuration for the connect session */ @@ -159,9 +159,9 @@ export interface SessionReauthenticateParams { export declare namespace Sessions { export { - type SessionNewResponse as SessionNewResponse, + type SessionConnectResponse as SessionConnectResponse, type SessionReauthenticateResponse as SessionReauthenticateResponse, - type SessionNewParams as SessionNewParams, + type SessionConnectParams as SessionConnectParams, type SessionReauthenticateParams as SessionReauthenticateParams, }; } diff --git a/src/resources/request-forwarding.ts b/src/resources/request-forwarding.ts index 9832aa6b5..f27943456 100644 --- a/src/resources/request-forwarding.ts +++ b/src/resources/request-forwarding.ts @@ -100,18 +100,18 @@ export interface RequestForwardingForwardParams { */ data?: string | null; - /** - * The HTTP headers to include on the forwarded request. This value must be - * specified as an object of key-value pairs. Example: - * `{"Content-Type": "application/xml", "X-API-Version": "v1" }` - */ - headers?: { [key: string]: unknown } | null; - /** * The query parameters for the forwarded request. This value must be specified as * a valid JSON object rather than a query string. */ params?: { [key: string]: unknown } | null; + + /** + * The HTTP headers to include on the forwarded request. This value must be + * specified as an object of key-value pairs. Example: + * `{"Content-Type": "application/xml", "X-API-Version": "v1" }` + */ + request_headers?: { [key: string]: unknown } | null; } export declare namespace RequestForwarding { diff --git a/tests/api-resources/connect/sessions.test.ts b/tests/api-resources/connect/sessions.test.ts index 38043b915..3df440c37 100644 --- a/tests/api-resources/connect/sessions.test.ts +++ b/tests/api-resources/connect/sessions.test.ts @@ -9,8 +9,8 @@ const client = new Finch({ describe('resource sessions', () => { // prism tests are broken - test.skip('new: only required params', async () => { - const responsePromise = client.connect.sessions.new({ + test.skip('connect: only required params', async () => { + const responsePromise = client.connect.sessions.connect({ customer_id: 'x', customer_name: 'x', products: ['benefits'], @@ -25,8 +25,8 @@ describe('resource sessions', () => { }); // prism tests are broken - test.skip('new: required and optional params', async () => { - const response = await client.connect.sessions.new({ + test.skip('connect: required and optional params', async () => { + const response = await client.connect.sessions.connect({ customer_id: 'x', customer_name: 'x', products: ['benefits'], diff --git a/tests/api-resources/request-forwarding.test.ts b/tests/api-resources/request-forwarding.test.ts index e035b1b8f..35b9e4039 100644 --- a/tests/api-resources/request-forwarding.test.ts +++ b/tests/api-resources/request-forwarding.test.ts @@ -24,8 +24,8 @@ describe('resource requestForwarding', () => { method: 'method', route: 'route', data: 'data', - headers: { foo: 'bar' }, params: { foo: 'bar' }, + request_headers: { foo: 'bar' }, }); }); }); From adf8013674c0b3c7fb532ad8b98ec23863f7200b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 20:22:44 +0000 Subject: [PATCH 21/40] feat(api): manual updates --- .stats.yml | 2 +- api.md | 4 ++-- packages/mcp-server/README.md | 2 +- packages/mcp-server/src/code-tool-worker.ts | 2 +- ...connect-sessions.ts => new-connect-sessions.ts} | 6 +++--- packages/mcp-server/src/tools/index.ts | 4 ++-- src/resources/connect/connect.ts | 8 ++++---- src/resources/connect/index.ts | 4 ++-- src/resources/connect/sessions.ts | 14 +++++++------- tests/api-resources/connect/sessions.test.ts | 8 ++++---- 10 files changed, 27 insertions(+), 27 deletions(-) rename packages/mcp-server/src/tools/connect/sessions/{connect-connect-sessions.ts => new-connect-sessions.ts} (86%) diff --git a/.stats.yml b/.stats.yml index f316e0628..40836a3fd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-640ec74bfc2f335f7277863ee0cbd0af98bb6018cb1ec2f116ce4145148f4477.yml openapi_spec_hash: 315e7859c3f77311261fb824b74a8247 -config_hash: f921f92cc97449dcc1334a594b435c73 +config_hash: 0892e2e0eeb0343a022afa62e9080dd1 diff --git a/api.md b/api.md index c70ca7959..40b2f2745 100644 --- a/api.md +++ b/api.md @@ -373,10 +373,10 @@ Methods: Types: -- SessionConnectResponse +- SessionNewResponse - SessionReauthenticateResponse Methods: -- client.connect.sessions.connect({ ...params }) -> SessionConnectResponse +- client.connect.sessions.new({ ...params }) -> SessionNewResponse - client.connect.sessions.reauthenticate({ ...params }) -> SessionReauthenticateResponse diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index a6438fb79..5d51973fc 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -392,5 +392,5 @@ The following tools are available in this MCP server. ### Resource `connect.sessions`: -- `connect_connect_sessions` (`write`): Create a new connect session for an employer +- `new_connect_sessions` (`write`): Create a new connect session for an employer - `reauthenticate_connect_sessions` (`write`): Create a new Connect session for reauthenticating an existing connection diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index 5a3d04553..c2d1dc70c 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -87,7 +87,7 @@ const fuse = new Fuse( 'client.sandbox.jobs.configuration.update', 'client.payroll.payGroups.list', 'client.payroll.payGroups.retrieve', - 'client.connect.sessions.connect', + 'client.connect.sessions.new', 'client.connect.sessions.reauthenticate', ], { threshold: 1, shouldSort: true }, diff --git a/packages/mcp-server/src/tools/connect/sessions/connect-connect-sessions.ts b/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts similarity index 86% rename from packages/mcp-server/src/tools/connect/sessions/connect-connect-sessions.ts rename to packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts index 21642b523..7b4d7a545 100644 --- a/packages/mcp-server/src/tools/connect/sessions/connect-connect-sessions.ts +++ b/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts @@ -16,9 +16,9 @@ export const metadata: Metadata = { }; export const tool: Tool = { - name: 'connect_connect_sessions', + name: 'new_connect_sessions', description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nCreate a new connect session for an employer\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/session_connect_response',\n $defs: {\n session_connect_response: {\n type: 'object',\n properties: {\n connect_url: {\n type: 'string',\n description: 'The Connect URL to redirect the user to for authentication'\n },\n session_id: {\n type: 'string',\n description: 'The unique identifier for the created connect session'\n }\n },\n required: [ 'connect_url',\n 'session_id'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nCreate a new connect session for an employer\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/session_new_response',\n $defs: {\n session_new_response: {\n type: 'object',\n properties: {\n connect_url: {\n type: 'string',\n description: 'The Connect URL to redirect the user to for authentication'\n },\n session_id: {\n type: 'string',\n description: 'The unique identifier for the created connect session'\n }\n },\n required: [ 'connect_url',\n 'session_id'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -103,7 +103,7 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { jq_filter, ...body } = args as any; try { - return asTextContentResult(await maybeFilter(jq_filter, await client.connect.sessions.connect(body))); + return asTextContentResult(await maybeFilter(jq_filter, await client.connect.sessions.new(body))); } catch (error) { if (isJqError(error)) { return asErrorResult(error.message); diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts index 15911eec6..438763953 100644 --- a/packages/mcp-server/src/tools/index.ts +++ b/packages/mcp-server/src/tools/index.ts @@ -48,7 +48,7 @@ import retrieve_jobs_sandbox_configuration from './sandbox/jobs/configuration/re import update_jobs_sandbox_configuration from './sandbox/jobs/configuration/update-jobs-sandbox-configuration'; import retrieve_payroll_pay_groups from './payroll/pay-groups/retrieve-payroll-pay-groups'; import list_payroll_pay_groups from './payroll/pay-groups/list-payroll-pay-groups'; -import connect_connect_sessions from './connect/sessions/connect-connect-sessions'; +import new_connect_sessions from './connect/sessions/new-connect-sessions'; import reauthenticate_connect_sessions from './connect/sessions/reauthenticate-connect-sessions'; export const endpoints: Endpoint[] = []; @@ -101,7 +101,7 @@ addEndpoint(retrieve_jobs_sandbox_configuration); addEndpoint(update_jobs_sandbox_configuration); addEndpoint(retrieve_payroll_pay_groups); addEndpoint(list_payroll_pay_groups); -addEndpoint(connect_connect_sessions); +addEndpoint(new_connect_sessions); addEndpoint(reauthenticate_connect_sessions); export type Filter = { diff --git a/src/resources/connect/connect.ts b/src/resources/connect/connect.ts index 31deaa2f9..876dad049 100644 --- a/src/resources/connect/connect.ts +++ b/src/resources/connect/connect.ts @@ -3,8 +3,8 @@ import { APIResource } from '../../core/resource'; import * as SessionsAPI from './sessions'; import { - SessionConnectParams, - SessionConnectResponse, + SessionNewParams, + SessionNewResponse, SessionReauthenticateParams, SessionReauthenticateResponse, Sessions, @@ -19,9 +19,9 @@ Connect.Sessions = Sessions; export declare namespace Connect { export { Sessions as Sessions, - type SessionConnectResponse as SessionConnectResponse, + type SessionNewResponse as SessionNewResponse, type SessionReauthenticateResponse as SessionReauthenticateResponse, - type SessionConnectParams as SessionConnectParams, + type SessionNewParams as SessionNewParams, type SessionReauthenticateParams as SessionReauthenticateParams, }; } diff --git a/src/resources/connect/index.ts b/src/resources/connect/index.ts index 79192b28a..6e29c158f 100644 --- a/src/resources/connect/index.ts +++ b/src/resources/connect/index.ts @@ -3,8 +3,8 @@ export { Connect } from './connect'; export { Sessions, - type SessionConnectResponse, + type SessionNewResponse, type SessionReauthenticateResponse, - type SessionConnectParams, + type SessionNewParams, type SessionReauthenticateParams, } from './sessions'; diff --git a/src/resources/connect/sessions.ts b/src/resources/connect/sessions.ts index fbd7201da..b56c9a0c5 100644 --- a/src/resources/connect/sessions.ts +++ b/src/resources/connect/sessions.ts @@ -8,7 +8,7 @@ export class Sessions extends APIResource { /** * Create a new connect session for an employer */ - connect(body: SessionConnectParams, options?: RequestOptions): APIPromise { + new(body: SessionNewParams, options?: RequestOptions): APIPromise { return this._client.post('/connect/sessions', { body, ...options }); } @@ -23,7 +23,7 @@ export class Sessions extends APIResource { } } -export interface SessionConnectResponse { +export interface SessionNewResponse { /** * The Connect URL to redirect the user to for authentication */ @@ -47,7 +47,7 @@ export interface SessionReauthenticateResponse { session_id: string; } -export interface SessionConnectParams { +export interface SessionNewParams { /** * Unique identifier for the customer */ @@ -82,7 +82,7 @@ export interface SessionConnectParams { /** * Integration configuration for the connect session */ - integration?: SessionConnectParams.Integration | null; + integration?: SessionNewParams.Integration | null; /** * Enable manual authentication mode @@ -106,7 +106,7 @@ export interface SessionConnectParams { sandbox?: 'finch' | 'provider' | null; } -export namespace SessionConnectParams { +export namespace SessionNewParams { /** * Integration configuration for the connect session */ @@ -159,9 +159,9 @@ export interface SessionReauthenticateParams { export declare namespace Sessions { export { - type SessionConnectResponse as SessionConnectResponse, + type SessionNewResponse as SessionNewResponse, type SessionReauthenticateResponse as SessionReauthenticateResponse, - type SessionConnectParams as SessionConnectParams, + type SessionNewParams as SessionNewParams, type SessionReauthenticateParams as SessionReauthenticateParams, }; } diff --git a/tests/api-resources/connect/sessions.test.ts b/tests/api-resources/connect/sessions.test.ts index 3df440c37..38043b915 100644 --- a/tests/api-resources/connect/sessions.test.ts +++ b/tests/api-resources/connect/sessions.test.ts @@ -9,8 +9,8 @@ const client = new Finch({ describe('resource sessions', () => { // prism tests are broken - test.skip('connect: only required params', async () => { - const responsePromise = client.connect.sessions.connect({ + test.skip('new: only required params', async () => { + const responsePromise = client.connect.sessions.new({ customer_id: 'x', customer_name: 'x', products: ['benefits'], @@ -25,8 +25,8 @@ describe('resource sessions', () => { }); // prism tests are broken - test.skip('connect: required and optional params', async () => { - const response = await client.connect.sessions.connect({ + test.skip('new: required and optional params', async () => { + const response = await client.connect.sessions.new({ customer_id: 'x', customer_name: 'x', products: ['benefits'], From b2d5fcca38564c4b562263813bc945bae0a06b39 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:18:45 +0000 Subject: [PATCH 22/40] feat(api): api update --- .stats.yml | 4 ++-- .../src/tools/sandbox/payment/create-sandbox-payment.ts | 2 ++ src/resources/sandbox/payment.ts | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 40836a3fd..c27288ed9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-640ec74bfc2f335f7277863ee0cbd0af98bb6018cb1ec2f116ce4145148f4477.yml -openapi_spec_hash: 315e7859c3f77311261fb824b74a8247 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-d9cb320c8313cd122b4851d726c6cea39a14a5317880c6d063671ad3f412e632.yml +openapi_spec_hash: 58c2cf578f0736b8c5df957f6a61190b config_hash: 0892e2e0eeb0343a022afa62e9080dd1 diff --git a/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts b/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts index 8d86f1bc0..b8485d978 100644 --- a/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts +++ b/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts @@ -80,6 +80,7 @@ export const tool: Tool = { }, name: { type: 'string', + description: 'The deduction name. Required when type is specified.', }, pre_tax: { type: 'boolean', @@ -121,6 +122,7 @@ export const tool: Tool = { }, name: { type: 'string', + description: 'The contribution name. Required when type is specified.', }, type: { type: 'string', diff --git a/src/resources/sandbox/payment.ts b/src/resources/sandbox/payment.ts index 5bfb9e8c1..bc49d6919 100644 --- a/src/resources/sandbox/payment.ts +++ b/src/resources/sandbox/payment.ts @@ -94,6 +94,9 @@ export namespace PaymentCreateParams { export interface EmployeeDeduction { amount?: number; + /** + * The deduction name. Required when type is specified. + */ name?: string; pre_tax?: boolean; @@ -123,6 +126,9 @@ export namespace PaymentCreateParams { export interface EmployerContribution { amount?: number; + /** + * The contribution name. Required when type is specified. + */ name?: string; type?: From 7eb8b44caeeea6202f06c192759554a3883df345 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 18:12:04 +0000 Subject: [PATCH 23/40] feat(mcp): add detail field to docs search tool --- packages/mcp-server/src/docs-search-tool.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts index 0ef8ccb90..8c81f9c5e 100644 --- a/packages/mcp-server/src/docs-search-tool.ts +++ b/packages/mcp-server/src/docs-search-tool.ts @@ -13,8 +13,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'search_docs', - description: - 'Search for documentation for how to use the client to interact with the API.\nThe tool will return an array of Markdown-formatted documentation pages.', + description: 'Search for documentation for how to use the client to interact with the API.', inputSchema: { type: 'object', properties: { @@ -25,7 +24,12 @@ export const tool: Tool = { language: { type: 'string', description: 'The language for the SDK to search for.', - enum: ['http', 'python', 'go', 'typescript', 'terraform', 'ruby', 'java', 'kotlin'], + enum: ['http', 'python', 'go', 'typescript', 'javascript', 'terraform', 'ruby', 'java', 'kotlin'], + }, + detail: { + type: 'string', + description: 'The amount of detail to return.', + enum: ['default', 'verbose'], }, }, required: ['query', 'language'], From c3f828db47683dac2852c85f710c145f0efc95cf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:40:55 +0000 Subject: [PATCH 24/40] chore(client): fix logger property type --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 35d59aac3..d6eedc3e0 100644 --- a/src/client.ts +++ b/src/client.ts @@ -178,7 +178,7 @@ export class Finch { baseURL: string; maxRetries: number; timeout: number; - logger: Logger | undefined; + logger: Logger; logLevel: LogLevel | undefined; fetchOptions: MergedRequestInit | undefined; From f3e0bdb21695ad01b77f44dc39af2a09d48e7a8e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:41:19 +0000 Subject: [PATCH 25/40] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index c27288ed9..459fe7372 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-d9cb320c8313cd122b4851d726c6cea39a14a5317880c6d063671ad3f412e632.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-39e0191e43a9db93c8f35e91d10013f05352a2bedcf7ead6bac437957f6e922e.yml openapi_spec_hash: 58c2cf578f0736b8c5df957f6a61190b config_hash: 0892e2e0eeb0343a022afa62e9080dd1 From 6059d9c53093f4da1c8bede00dffd4e27209c691 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:04:12 +0000 Subject: [PATCH 26/40] fix(mcp): return tool execution error on api error --- .../src/tools/access-tokens/create-access-tokens.ts | 2 +- .../src/tools/account/disconnect-account.ts | 2 +- .../src/tools/account/introspect-account.ts | 2 +- .../tools/connect/sessions/new-connect-sessions.ts | 2 +- .../sessions/reauthenticate-connect-sessions.ts | 2 +- .../src/tools/hris/benefits/create-hris-benefits.ts | 2 +- .../enroll-many-benefits-hris-individuals.ts | 2 +- .../enrolled-ids-benefits-hris-individuals.ts | 2 +- ...etrieve-many-benefits-benefits-hris-individuals.ts | 2 +- .../unenroll-many-benefits-hris-individuals.ts | 2 +- .../src/tools/hris/benefits/list-hris-benefits.ts | 2 +- .../benefits/list-supported-benefits-hris-benefits.ts | 2 +- .../src/tools/hris/benefits/retrieve-hris-benefits.ts | 2 +- .../src/tools/hris/benefits/update-hris-benefits.ts | 2 +- .../list-company-hris-pay-statement-item.ts | 2 +- .../create-pay-statement-item-company-hris-rules.ts | 2 +- .../delete-pay-statement-item-company-hris-rules.ts | 2 +- .../list-pay-statement-item-company-hris-rules.ts | 2 +- .../update-pay-statement-item-company-hris-rules.ts | 2 +- .../src/tools/hris/company/retrieve-hris-company.ts | 2 +- .../src/tools/hris/directory/list-hris-directory.ts | 2 +- .../src/tools/hris/documents/list-hris-documents.ts | 2 +- .../tools/hris/documents/retreive-hris-documents.ts | 2 +- .../employments/retrieve-many-hris-employments.ts | 11 +++++++++-- .../individuals/retrieve-many-hris-individuals.ts | 2 +- .../retrieve-many-hris-pay-statements.ts | 11 +++++++++-- .../src/tools/hris/payments/list-hris-payments.ts | 2 +- .../src/tools/jobs/automated/create-jobs-automated.ts | 2 +- .../src/tools/jobs/automated/list-jobs-automated.ts | 2 +- .../tools/jobs/automated/retrieve-jobs-automated.ts | 2 +- .../src/tools/jobs/manual/retrieve-jobs-manual.ts | 2 +- .../payroll/pay-groups/list-payroll-pay-groups.ts | 2 +- .../payroll/pay-groups/retrieve-payroll-pay-groups.ts | 2 +- .../mcp-server/src/tools/providers/list-providers.ts | 2 +- .../request-forwarding/forward-request-forwarding.ts | 2 +- .../tools/sandbox/company/update-sandbox-company.ts | 2 +- .../accounts/create-connections-sandbox-accounts.ts | 2 +- .../accounts/update-connections-sandbox-accounts.ts | 2 +- .../sandbox/connections/create-sandbox-connections.ts | 2 +- .../sandbox/directory/create-sandbox-directory.ts | 2 +- .../sandbox/employment/update-sandbox-employment.ts | 11 +++++++++-- .../sandbox/individual/update-sandbox-individual.ts | 2 +- .../retrieve-jobs-sandbox-configuration.ts | 2 +- .../update-jobs-sandbox-configuration.ts | 2 +- .../src/tools/sandbox/jobs/create-sandbox-jobs.ts | 2 +- .../tools/sandbox/payment/create-sandbox-payment.ts | 2 +- 46 files changed, 70 insertions(+), 49 deletions(-) diff --git a/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts b/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts index 362ff5846..c890c2255 100644 --- a/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts +++ b/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts @@ -55,7 +55,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.accessTokens.create(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/account/disconnect-account.ts b/packages/mcp-server/src/tools/account/disconnect-account.ts index fbfa74c52..29bd39a61 100644 --- a/packages/mcp-server/src/tools/account/disconnect-account.ts +++ b/packages/mcp-server/src/tools/account/disconnect-account.ts @@ -39,7 +39,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.account.disconnect())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/account/introspect-account.ts b/packages/mcp-server/src/tools/account/introspect-account.ts index 3cb5d9d4f..2ef68e175 100644 --- a/packages/mcp-server/src/tools/account/introspect-account.ts +++ b/packages/mcp-server/src/tools/account/introspect-account.ts @@ -41,7 +41,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.account.introspect())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts b/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts index 7b4d7a545..2b2aceb06 100644 --- a/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts +++ b/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts @@ -105,7 +105,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.connect.sessions.new(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/connect/sessions/reauthenticate-connect-sessions.ts b/packages/mcp-server/src/tools/connect/sessions/reauthenticate-connect-sessions.ts index 6bbef87a1..1c9b8a92f 100644 --- a/packages/mcp-server/src/tools/connect/sessions/reauthenticate-connect-sessions.ts +++ b/packages/mcp-server/src/tools/connect/sessions/reauthenticate-connect-sessions.ts @@ -73,7 +73,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.connect.sessions.reauthenticate(body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/benefits/create-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/create-hris-benefits.ts index 6d2556dc8..633564654 100644 --- a/packages/mcp-server/src/tools/hris/benefits/create-hris-benefits.ts +++ b/packages/mcp-server/src/tools/hris/benefits/create-hris-benefits.ts @@ -119,7 +119,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.hris.benefits.create(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/enroll-many-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/enroll-many-benefits-hris-individuals.ts index 7655faca6..42a9bf106 100644 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/enroll-many-benefits-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/benefits/individuals/enroll-many-benefits-hris-individuals.ts @@ -136,7 +136,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.hris.benefits.individuals.enrollMany(benefit_id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts index 567c28920..1c5372751 100644 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts @@ -53,7 +53,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.hris.benefits.individuals.enrolledIDs(benefit_id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts index 2ae53758b..19f106162 100644 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts @@ -57,7 +57,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await response.json())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/unenroll-many-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/unenroll-many-benefits-hris-individuals.ts index 2217a5ea0..26d739bb5 100644 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/unenroll-many-benefits-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/benefits/individuals/unenroll-many-benefits-hris-individuals.ts @@ -60,7 +60,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.hris.benefits.individuals.unenrollMany(benefit_id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/benefits/list-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/list-hris-benefits.ts index 7e62ae283..dc696b8fb 100644 --- a/packages/mcp-server/src/tools/hris/benefits/list-hris-benefits.ts +++ b/packages/mcp-server/src/tools/hris/benefits/list-hris-benefits.ts @@ -49,7 +49,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await response.json())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/benefits/list-supported-benefits-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/list-supported-benefits-hris-benefits.ts index eb2692e70..4e8404dda 100644 --- a/packages/mcp-server/src/tools/hris/benefits/list-supported-benefits-hris-benefits.ts +++ b/packages/mcp-server/src/tools/hris/benefits/list-supported-benefits-hris-benefits.ts @@ -49,7 +49,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await response.json())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/benefits/retrieve-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/retrieve-hris-benefits.ts index 9fae4a7e4..bd8c9a25e 100644 --- a/packages/mcp-server/src/tools/hris/benefits/retrieve-hris-benefits.ts +++ b/packages/mcp-server/src/tools/hris/benefits/retrieve-hris-benefits.ts @@ -53,7 +53,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.hris.benefits.retrieve(benefit_id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/benefits/update-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/update-hris-benefits.ts index b3f26f73d..db8e04d1d 100644 --- a/packages/mcp-server/src/tools/hris/benefits/update-hris-benefits.ts +++ b/packages/mcp-server/src/tools/hris/benefits/update-hris-benefits.ts @@ -55,7 +55,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.hris.benefits.update(benefit_id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/list-company-hris-pay-statement-item.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/list-company-hris-pay-statement-item.ts index 55b9b1d80..1d6b40212 100644 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/list-company-hris-pay-statement-item.ts +++ b/packages/mcp-server/src/tools/hris/company/pay-statement-item/list-company-hris-pay-statement-item.ts @@ -78,7 +78,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await response.json())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules.ts index 33df55291..2c6558b65 100644 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules.ts +++ b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules.ts @@ -96,7 +96,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.create(body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules.ts index 021b22be3..f20a273f3 100644 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules.ts +++ b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules.ts @@ -53,7 +53,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.delete(rule_id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules.ts index b4b62db3c..e4b8742e3 100644 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules.ts +++ b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules.ts @@ -49,7 +49,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await response.json())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules.ts index 07fd3a136..5dd2ecf90 100644 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules.ts +++ b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules.ts @@ -57,7 +57,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.update(rule_id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/company/retrieve-hris-company.ts b/packages/mcp-server/src/tools/hris/company/retrieve-hris-company.ts index 5f129a6fe..6f56dc9e1 100644 --- a/packages/mcp-server/src/tools/hris/company/retrieve-hris-company.ts +++ b/packages/mcp-server/src/tools/hris/company/retrieve-hris-company.ts @@ -48,7 +48,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.hris.company.retrieve(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/directory/list-hris-directory.ts b/packages/mcp-server/src/tools/hris/directory/list-hris-directory.ts index 8b4ea1c94..23c677438 100644 --- a/packages/mcp-server/src/tools/hris/directory/list-hris-directory.ts +++ b/packages/mcp-server/src/tools/hris/directory/list-hris-directory.ts @@ -57,7 +57,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await response.json())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/documents/list-hris-documents.ts b/packages/mcp-server/src/tools/hris/documents/list-hris-documents.ts index e9063123e..182c20844 100644 --- a/packages/mcp-server/src/tools/hris/documents/list-hris-documents.ts +++ b/packages/mcp-server/src/tools/hris/documents/list-hris-documents.ts @@ -72,7 +72,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.hris.documents.list(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/documents/retreive-hris-documents.ts b/packages/mcp-server/src/tools/hris/documents/retreive-hris-documents.ts index 6e07907e5..ff8e1613e 100644 --- a/packages/mcp-server/src/tools/hris/documents/retreive-hris-documents.ts +++ b/packages/mcp-server/src/tools/hris/documents/retreive-hris-documents.ts @@ -53,7 +53,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.hris.documents.retreive(document_id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/employments/retrieve-many-hris-employments.ts b/packages/mcp-server/src/tools/hris/employments/retrieve-many-hris-employments.ts index e562d8f7c..a01047251 100644 --- a/packages/mcp-server/src/tools/hris/employments/retrieve-many-hris-employments.ts +++ b/packages/mcp-server/src/tools/hris/employments/retrieve-many-hris-employments.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -51,7 +51,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const body = args as any; const response = await client.hris.employments.retrieveMany(body).asResponse(); - return asTextContentResult(await response.json()); + try { + return asTextContentResult(await response.json()); + } catch (error) { + if (error instanceof Finch.APIError) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/individuals/retrieve-many-hris-individuals.ts b/packages/mcp-server/src/tools/hris/individuals/retrieve-many-hris-individuals.ts index 2e2bdeebf..ec636ac35 100644 --- a/packages/mcp-server/src/tools/hris/individuals/retrieve-many-hris-individuals.ts +++ b/packages/mcp-server/src/tools/hris/individuals/retrieve-many-hris-individuals.ts @@ -69,7 +69,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await response.json())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/hris/pay-statements/retrieve-many-hris-pay-statements.ts b/packages/mcp-server/src/tools/hris/pay-statements/retrieve-many-hris-pay-statements.ts index d638c697a..70f7bb9ca 100644 --- a/packages/mcp-server/src/tools/hris/pay-statements/retrieve-many-hris-pay-statements.ts +++ b/packages/mcp-server/src/tools/hris/pay-statements/retrieve-many-hris-pay-statements.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -59,7 +59,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const body = args as any; const response = await client.hris.payStatements.retrieveMany(body).asResponse(); - return asTextContentResult(await response.json()); + try { + return asTextContentResult(await response.json()); + } catch (error) { + if (error instanceof Finch.APIError) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/payments/list-hris-payments.ts b/packages/mcp-server/src/tools/hris/payments/list-hris-payments.ts index b90e1a485..89f768608 100644 --- a/packages/mcp-server/src/tools/hris/payments/list-hris-payments.ts +++ b/packages/mcp-server/src/tools/hris/payments/list-hris-payments.ts @@ -59,7 +59,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await response.json())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/jobs/automated/create-jobs-automated.ts b/packages/mcp-server/src/tools/jobs/automated/create-jobs-automated.ts index b66dee36b..472edaff7 100644 --- a/packages/mcp-server/src/tools/jobs/automated/create-jobs-automated.ts +++ b/packages/mcp-server/src/tools/jobs/automated/create-jobs-automated.ts @@ -72,7 +72,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.create(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/jobs/automated/list-jobs-automated.ts b/packages/mcp-server/src/tools/jobs/automated/list-jobs-automated.ts index 715441639..7bcb3d797 100644 --- a/packages/mcp-server/src/tools/jobs/automated/list-jobs-automated.ts +++ b/packages/mcp-server/src/tools/jobs/automated/list-jobs-automated.ts @@ -49,7 +49,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.list(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/jobs/automated/retrieve-jobs-automated.ts b/packages/mcp-server/src/tools/jobs/automated/retrieve-jobs-automated.ts index 456b9e36b..96a9d97ff 100644 --- a/packages/mcp-server/src/tools/jobs/automated/retrieve-jobs-automated.ts +++ b/packages/mcp-server/src/tools/jobs/automated/retrieve-jobs-automated.ts @@ -44,7 +44,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.retrieve(job_id))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/jobs/manual/retrieve-jobs-manual.ts b/packages/mcp-server/src/tools/jobs/manual/retrieve-jobs-manual.ts index 9557ab639..6f5b671d9 100644 --- a/packages/mcp-server/src/tools/jobs/manual/retrieve-jobs-manual.ts +++ b/packages/mcp-server/src/tools/jobs/manual/retrieve-jobs-manual.ts @@ -44,7 +44,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.manual.retrieve(job_id))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/payroll/pay-groups/list-payroll-pay-groups.ts b/packages/mcp-server/src/tools/payroll/pay-groups/list-payroll-pay-groups.ts index 9a2cf6f30..3f5c70e75 100644 --- a/packages/mcp-server/src/tools/payroll/pay-groups/list-payroll-pay-groups.ts +++ b/packages/mcp-server/src/tools/payroll/pay-groups/list-payroll-pay-groups.ts @@ -58,7 +58,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await response.json())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/payroll/pay-groups/retrieve-payroll-pay-groups.ts b/packages/mcp-server/src/tools/payroll/pay-groups/retrieve-payroll-pay-groups.ts index 0a270fbff..4cb29e1b3 100644 --- a/packages/mcp-server/src/tools/payroll/pay-groups/retrieve-payroll-pay-groups.ts +++ b/packages/mcp-server/src/tools/payroll/pay-groups/retrieve-payroll-pay-groups.ts @@ -53,7 +53,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.payroll.payGroups.retrieve(pay_group_id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/providers/list-providers.ts b/packages/mcp-server/src/tools/providers/list-providers.ts index 3a18de80d..f317254c2 100644 --- a/packages/mcp-server/src/tools/providers/list-providers.ts +++ b/packages/mcp-server/src/tools/providers/list-providers.ts @@ -42,7 +42,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await response.json())); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts b/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts index cf3762490..9055907ad 100644 --- a/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts +++ b/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts @@ -66,7 +66,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.requestForwarding.forward(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/sandbox/company/update-sandbox-company.ts b/packages/mcp-server/src/tools/sandbox/company/update-sandbox-company.ts index 8d3645b2f..4f6036205 100644 --- a/packages/mcp-server/src/tools/sandbox/company/update-sandbox-company.ts +++ b/packages/mcp-server/src/tools/sandbox/company/update-sandbox-company.ts @@ -182,7 +182,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.company.update(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/sandbox/connections/accounts/create-connections-sandbox-accounts.ts b/packages/mcp-server/src/tools/sandbox/connections/accounts/create-connections-sandbox-accounts.ts index f13682ad3..310eb2f4f 100644 --- a/packages/mcp-server/src/tools/sandbox/connections/accounts/create-connections-sandbox-accounts.ts +++ b/packages/mcp-server/src/tools/sandbox/connections/accounts/create-connections-sandbox-accounts.ts @@ -61,7 +61,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.sandbox.connections.accounts.create(body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/sandbox/connections/accounts/update-connections-sandbox-accounts.ts b/packages/mcp-server/src/tools/sandbox/connections/accounts/update-connections-sandbox-accounts.ts index 99847b2e3..c1f34d38e 100644 --- a/packages/mcp-server/src/tools/sandbox/connections/accounts/update-connections-sandbox-accounts.ts +++ b/packages/mcp-server/src/tools/sandbox/connections/accounts/update-connections-sandbox-accounts.ts @@ -52,7 +52,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.sandbox.connections.accounts.update(body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/sandbox/connections/create-sandbox-connections.ts b/packages/mcp-server/src/tools/sandbox/connections/create-sandbox-connections.ts index d13d1a61a..aaaceb5c9 100644 --- a/packages/mcp-server/src/tools/sandbox/connections/create-sandbox-connections.ts +++ b/packages/mcp-server/src/tools/sandbox/connections/create-sandbox-connections.ts @@ -59,7 +59,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.connections.create(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts b/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts index 03805678b..69aa60342 100644 --- a/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts +++ b/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts @@ -309,7 +309,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.directory.create(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/sandbox/employment/update-sandbox-employment.ts b/packages/mcp-server/src/tools/sandbox/employment/update-sandbox-employment.ts index 6cf7c300b..17cf07156 100644 --- a/packages/mcp-server/src/tools/sandbox/employment/update-sandbox-employment.ts +++ b/packages/mcp-server/src/tools/sandbox/employment/update-sandbox-employment.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; +import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Finch from '@tryfinch/finch-api'; @@ -223,7 +223,14 @@ export const tool: Tool = { export const handler = async (client: Finch, args: Record | undefined) => { const { individual_id, ...body } = args as any; - return asTextContentResult(await client.sandbox.employment.update(individual_id, body)); + try { + return asTextContentResult(await client.sandbox.employment.update(individual_id, body)); + } catch (error) { + if (error instanceof Finch.APIError) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/individual/update-sandbox-individual.ts b/packages/mcp-server/src/tools/sandbox/individual/update-sandbox-individual.ts index cc9f2af89..7da561133 100644 --- a/packages/mcp-server/src/tools/sandbox/individual/update-sandbox-individual.ts +++ b/packages/mcp-server/src/tools/sandbox/individual/update-sandbox-individual.ts @@ -167,7 +167,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.sandbox.individual.update(individual_id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration.ts b/packages/mcp-server/src/tools/sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration.ts index f50cd3aaa..61aa1a249 100644 --- a/packages/mcp-server/src/tools/sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration.ts +++ b/packages/mcp-server/src/tools/sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration.ts @@ -43,7 +43,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.sandbox.jobs.configuration.retrieve()), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/sandbox/jobs/configuration/update-jobs-sandbox-configuration.ts b/packages/mcp-server/src/tools/sandbox/jobs/configuration/update-jobs-sandbox-configuration.ts index 93314437f..336b97af5 100644 --- a/packages/mcp-server/src/tools/sandbox/jobs/configuration/update-jobs-sandbox-configuration.ts +++ b/packages/mcp-server/src/tools/sandbox/jobs/configuration/update-jobs-sandbox-configuration.ts @@ -51,7 +51,7 @@ export const handler = async (client: Finch, args: Record | und await maybeFilter(jq_filter, await client.sandbox.jobs.configuration.update(body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/sandbox/jobs/create-sandbox-jobs.ts b/packages/mcp-server/src/tools/sandbox/jobs/create-sandbox-jobs.ts index 15e0f2a7e..562841dcc 100644 --- a/packages/mcp-server/src/tools/sandbox/jobs/create-sandbox-jobs.ts +++ b/packages/mcp-server/src/tools/sandbox/jobs/create-sandbox-jobs.ts @@ -44,7 +44,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.jobs.create(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts b/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts index b8485d978..6b8d3ed12 100644 --- a/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts +++ b/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts @@ -214,7 +214,7 @@ export const handler = async (client: Finch, args: Record | und try { return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.payment.create(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof Finch.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; From 7b37d8f9cbca8d13dc09f894da08d6cb76487e74 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:17:07 +0000 Subject: [PATCH 27/40] feat(mcp): return logs on code tool errors --- packages/mcp-server/src/code-tool-types.ts | 6 ++++- packages/mcp-server/src/code-tool-worker.ts | 6 +++++ packages/mcp-server/src/code-tool.ts | 25 +++++++++++++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/mcp-server/src/code-tool-types.ts b/packages/mcp-server/src/code-tool-types.ts index 7ea856320..195f71434 100644 --- a/packages/mcp-server/src/code-tool-types.ts +++ b/packages/mcp-server/src/code-tool-types.ts @@ -11,4 +11,8 @@ export type WorkerSuccess = { logLines: string[]; errLines: string[]; }; -export type WorkerError = { message: string | undefined }; +export type WorkerError = { + message: string | undefined; + logLines: string[]; + errLines: string[]; +}; diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index c2d1dc70c..cf2fee957 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -189,6 +189,8 @@ const fetch = async (req: Request): Promise => { { message: 'The code param is missing. Provide one containing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + logLines: [], + errLines: [], } satisfies WorkerError, { status: 400, statusText: 'Code execution error' }, ); @@ -200,6 +202,8 @@ const fetch = async (req: Request): Promise => { { message: 'The code is missing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + logLines: [], + errLines: [], } satisfies WorkerError, { status: 400, statusText: 'Code execution error' }, ); @@ -232,6 +236,8 @@ const fetch = async (req: Request): Promise => { return Response.json( { message: parseError(code, e), + logLines, + errLines, } satisfies WorkerError, { status: 400, statusText: 'Code execution error' }, ); diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 7babc168e..c71847a2a 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -135,9 +135,30 @@ export async function codeTool(): Promise { content: [returnOutput, logOutput, errOutput].filter((block) => block !== null), }; } else { - const { message } = (await resp.json()) as WorkerError; + const { message, logLines, errLines } = (await resp.json()) as WorkerError; + const messageOutput: ContentBlock | null = + message == null ? null : ( + { + type: 'text', + text: message, + } + ); + const logOutput: ContentBlock | null = + logLines.length === 0 ? + null + : { + type: 'text', + text: logLines.join('\n'), + }; + const errOutput: ContentBlock | null = + errLines.length === 0 ? + null + : { + type: 'text', + text: 'Error output:\n' + errLines.join('\n'), + }; return { - content: message == null ? [] : [{ type: 'text', text: message }], + content: [messageOutput, logOutput, errOutput].filter((block) => block !== null), isError: true, }; } From 1707b51aab5ad912d7210f6dc3b142eea1b2d749 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:06:21 +0000 Subject: [PATCH 28/40] chore(internal): upgrade eslint --- package.json | 2 +- yarn.lock | 150 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 89 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index aa1db64be..6fc4a4e53 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@types/node": "^20.17.6", "@typescript-eslint/eslint-plugin": "8.31.1", "@typescript-eslint/parser": "8.31.1", - "eslint": "^9.20.1", + "eslint": "^9.39.1", "eslint-plugin-prettier": "^5.4.1", "eslint-plugin-unused-imports": "^4.1.4", "iconv-lite": "^0.6.3", diff --git a/yarn.lock b/yarn.lock index ea32d280c..4349fc1f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -350,45 +350,52 @@ dependencies: "@cspotcode/source-map-consumer" "0.8.0" -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": +"@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" +"@eslint-community/eslint-utils@^4.8.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" + integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== + dependencies: + eslint-visitor-keys "^3.4.3" + "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": version "4.12.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint/config-array@^0.19.0": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" - integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== +"@eslint/config-array@^0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" + integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== dependencies: - "@eslint/object-schema" "^2.1.6" + "@eslint/object-schema" "^2.1.7" debug "^4.3.1" minimatch "^3.1.2" -"@eslint/core@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.10.0.tgz#23727063c21b335f752dbb3a16450f6f9cbc9091" - integrity sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw== +"@eslint/config-helpers@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" + integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== dependencies: - "@types/json-schema" "^7.0.15" + "@eslint/core" "^0.17.0" -"@eslint/core@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12" - integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA== +"@eslint/core@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" + integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== dependencies: "@types/json-schema" "^7.0.15" -"@eslint/eslintrc@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" - integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== +"@eslint/eslintrc@^3.3.1": + version "3.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz#26393a0806501b5e2b6a43aa588a4d8df67880ac" + integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -396,26 +403,26 @@ globals "^14.0.0" ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^4.1.0" + js-yaml "^4.1.1" minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.20.0": - version "9.20.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4" - integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ== +"@eslint/js@9.39.1": + version "9.39.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.1.tgz#0dd59c3a9f40e3f1882975c321470969243e0164" + integrity sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw== -"@eslint/object-schema@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" - integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== +"@eslint/object-schema@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" + integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== -"@eslint/plugin-kit@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz#ee07372035539e7847ef834e3f5e7b79f09e3a81" - integrity sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A== +"@eslint/plugin-kit@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" + integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== dependencies: - "@eslint/core" "^0.10.0" + "@eslint/core" "^0.17.0" levn "^0.4.1" "@humanfs/core@^0.19.1": @@ -441,10 +448,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== -"@humanwhocodes/retry@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" - integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== +"@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -1062,6 +1069,11 @@ acorn@^8.14.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + acorn@^8.4.1: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" @@ -1565,15 +1577,15 @@ eslint-plugin-unused-imports@^4.1.4: resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz#62ddc7446ccbf9aa7b6f1f0b00a980423cda2738" integrity sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ== -eslint-scope@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" - integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0: +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== @@ -1583,31 +1595,36 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@^9.20.1: - version "9.20.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6" - integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g== +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint@^9.39.1: + version "9.39.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.1.tgz#be8bf7c6de77dcc4252b5a8dcb31c2efff74a6e5" + integrity sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g== dependencies: - "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/eslint-utils" "^4.8.0" "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.19.0" - "@eslint/core" "^0.11.0" - "@eslint/eslintrc" "^3.2.0" - "@eslint/js" "9.20.0" - "@eslint/plugin-kit" "^0.2.5" + "@eslint/config-array" "^0.21.1" + "@eslint/config-helpers" "^0.4.2" + "@eslint/core" "^0.17.0" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.39.1" + "@eslint/plugin-kit" "^0.4.1" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.1" + "@humanwhocodes/retry" "^0.4.2" "@types/estree" "^1.0.6" - "@types/json-schema" "^7.0.15" ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.6" debug "^4.3.2" escape-string-regexp "^4.0.0" - eslint-scope "^8.2.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -1623,7 +1640,7 @@ eslint@^9.20.1: natural-compare "^1.4.0" optionator "^0.9.3" -espree@^10.0.1, espree@^10.3.0: +espree@^10.0.1: version "10.3.0" resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== @@ -1632,6 +1649,15 @@ espree@^10.0.1, espree@^10.3.0: acorn-jsx "^5.3.2" eslint-visitor-keys "^4.2.0" +espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + dependencies: + acorn "^8.15.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.1" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -2445,10 +2471,10 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== +js-yaml@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" From b29ebd1cc574733a9443ad9937e8e1b7e0f5d0a1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:32:36 +0000 Subject: [PATCH 29/40] chore: use latest @modelcontextprotocol/sdk --- packages/mcp-server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index b970c5e48..e828be244 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -32,7 +32,7 @@ "dependencies": { "@tryfinch/finch-api": "file:../../dist/", "@cloudflare/cabidela": "^0.2.4", - "@modelcontextprotocol/sdk": "^1.11.5", + "@modelcontextprotocol/sdk": "^1.24.0", "@valtown/deno-http-worker": "^0.0.21", "cors": "^2.8.5", "express": "^5.1.0", From b2b1ac16388edf736837f722fb700c3079bda946 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 21:25:28 +0000 Subject: [PATCH 30/40] feat(mcp): add typescript check to code execution tool --- packages/mcp-server/src/code-tool-worker.ts | 107 ++++++++++++++++---- packages/mcp-server/src/code-tool.ts | 16 ++- 2 files changed, 100 insertions(+), 23 deletions(-) diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index cf2fee957..e96885c42 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -1,5 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import path from 'node:path'; import util from 'node:util'; import Fuse from 'fuse.js'; @@ -8,30 +9,41 @@ import ts from 'typescript'; import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; import { Finch } from '@tryfinch/finch-api'; -function getRunFunctionNode( - code: string, -): ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction | null { +function getRunFunctionSource(code: string): { + type: 'declaration' | 'expression'; + client: string | undefined; + code: string; +} | null { const sourceFile = ts.createSourceFile('code.ts', code, ts.ScriptTarget.Latest, true); + const printer = ts.createPrinter(); for (const statement of sourceFile.statements) { // Check for top-level function declarations if (ts.isFunctionDeclaration(statement)) { if (statement.name?.text === 'run') { - return statement; + return { + type: 'declaration', + client: statement.parameters[0]?.name.getText(), + code: printer.printNode(ts.EmitHint.Unspecified, statement.body!, sourceFile), + }; } } // Check for variable declarations: const run = () => {} or const run = function() {} if (ts.isVariableStatement(statement)) { for (const declaration of statement.declarationList.declarations) { - if (ts.isIdentifier(declaration.name) && declaration.name.text === 'run') { + if ( + ts.isIdentifier(declaration.name) && + declaration.name.text === 'run' && // Check if it's initialized with a function - if ( - declaration.initializer && - (ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer)) - ) { - return declaration.initializer; - } + declaration.initializer && + (ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer)) + ) { + return { + type: 'expression', + client: declaration.initializer.parameters[0]?.name.getText(), + code: printer.printNode(ts.EmitHint.Unspecified, declaration.initializer, sourceFile), + }; } } } @@ -40,6 +52,61 @@ function getRunFunctionNode( return null; } +function getTSDiagnostics(code: string): string[] { + const functionSource = getRunFunctionSource(code)!; + const codeWithImport = [ + 'import { Finch } from "@tryfinch/finch-api";', + functionSource.type === 'declaration' ? + `async function run(${functionSource.client}: Finch)` + : `const run: (${functionSource.client}: Finch) => Promise =`, + functionSource.code, + ].join('\n'); + const sourcePath = path.resolve('code.ts'); + const ast = ts.createSourceFile(sourcePath, codeWithImport, ts.ScriptTarget.Latest, true); + const options = ts.getDefaultCompilerOptions(); + options.target = ts.ScriptTarget.Latest; + options.module = ts.ModuleKind.NodeNext; + options.moduleResolution = ts.ModuleResolutionKind.NodeNext; + const host = ts.createCompilerHost(options, true); + const newHost: typeof host = { + ...host, + getSourceFile: (...args) => { + if (path.resolve(args[0]) === sourcePath) { + return ast; + } + return host.getSourceFile(...args); + }, + readFile: (...args) => { + if (path.resolve(args[0]) === sourcePath) { + return codeWithImport; + } + return host.readFile(...args); + }, + fileExists: (...args) => { + if (path.resolve(args[0]) === sourcePath) { + return true; + } + return host.fileExists(...args); + }, + }; + const program = ts.createProgram({ + options, + rootNames: [sourcePath], + host: newHost, + }); + const diagnostics = ts.getPreEmitDiagnostics(program, ast); + return diagnostics.map((d) => { + const message = ts.flattenDiagnosticMessageText(d.messageText, '\n'); + if (!d.file || !d.start) return `- ${message}`; + const { line: tsLine } = ts.getLineAndCharacterOfPosition(d.file, d.start); + // We add two lines in the beginning, for the client import and the function declaration. + // So the actual (zero-based) line number is tsLine - 2. + const lineNumber = tsLine - 2; + const line = code.split('\n').at(lineNumber)?.trim(); + return line ? `- ${message}\n at line ${lineNumber + 1}\n ${line}` : `- ${message}`; + }); +} + const fuse = new Fuse( [ 'client.accessTokens.create', @@ -184,11 +251,16 @@ function parseError(code: string, error: unknown): string | undefined { const fetch = async (req: Request): Promise => { const { opts, code } = (await req.json()) as WorkerInput; - if (code == null) { + + const runFunctionSource = code ? getRunFunctionSource(code) : null; + if (!runFunctionSource) { + const message = + code ? + 'The code is missing a top-level `run` function.' + : 'The code argument is missing. Provide one containing a top-level `run` function.'; return Response.json( { - message: - 'The code param is missing. Provide one containing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + message: `${message} Write code within this template:\n\n\`\`\`\nasync function run(client) {\n // Fill this out\n}\n\`\`\``, logLines: [], errLines: [], } satisfies WorkerError, @@ -196,12 +268,11 @@ const fetch = async (req: Request): Promise => { ); } - const runFunctionNode = getRunFunctionNode(code); - if (!runFunctionNode) { + const diagnostics = getTSDiagnostics(code); + if (diagnostics.length > 0) { return Response.json( { - message: - 'The code is missing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + message: `The code contains TypeScript diagnostics:\n${diagnostics.join('\n')}`, logLines: [], errLines: [], } satisfies WorkerError, diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index c71847a2a..0d18a1110 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { dirname } from 'node:path'; -import { pathToFileURL } from 'node:url'; +import path from 'node:path'; +import url from 'node:url'; import Finch, { ClientOptions } from '@tryfinch/finch-api'; import { ContentBlock, Endpoint, Metadata, ToolCallResult } from './tools/types'; @@ -35,10 +35,16 @@ export async function codeTool(): Promise { const baseURLHostname = new URL(client.baseURL).hostname; const { code } = args as { code: string }; - const worker = await newDenoHTTPWorker(pathToFileURL(workerPath), { + const allowRead = [ + 'code-tool-worker.mjs', + `${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, + path.resolve(path.dirname(workerPath), '..'), + ].join(','); + + const worker = await newDenoHTTPWorker(url.pathToFileURL(workerPath), { runFlags: [ `--node-modules-dir=manual`, - `--allow-read=code-tool-worker.mjs,${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, + `--allow-read=${allowRead}`, `--allow-net=${baseURLHostname}`, // Allow environment variables because instantiating the client will try to read from them, // even though they are not set. @@ -46,7 +52,7 @@ export async function codeTool(): Promise { ], printOutput: true, spawnOptions: { - cwd: dirname(workerPath), + cwd: path.dirname(workerPath), }, }); From e3777b8a92ed7971bf7fcbb2f4754638457a6494 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:04:55 +0000 Subject: [PATCH 31/40] feat(mcp): handle code mode calls in the Stainless API Moves the code-mode execution to an endpoint in the Stainless API. --- packages/mcp-server/src/code-tool-paths.cts | 3 - packages/mcp-server/src/code-tool-worker.ts | 318 -------------------- packages/mcp-server/src/code-tool.ts | 188 +++--------- packages/mcp-server/src/docs-search-tool.ts | 7 + 4 files changed, 44 insertions(+), 472 deletions(-) delete mode 100644 packages/mcp-server/src/code-tool-paths.cts delete mode 100644 packages/mcp-server/src/code-tool-worker.ts diff --git a/packages/mcp-server/src/code-tool-paths.cts b/packages/mcp-server/src/code-tool-paths.cts deleted file mode 100644 index 15ce7f555..000000000 --- a/packages/mcp-server/src/code-tool-paths.cts +++ /dev/null @@ -1,3 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export const workerPath = require.resolve('./code-tool-worker.mjs'); diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts deleted file mode 100644 index e96885c42..000000000 --- a/packages/mcp-server/src/code-tool-worker.ts +++ /dev/null @@ -1,318 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import path from 'node:path'; -import util from 'node:util'; - -import Fuse from 'fuse.js'; -import ts from 'typescript'; - -import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; -import { Finch } from '@tryfinch/finch-api'; - -function getRunFunctionSource(code: string): { - type: 'declaration' | 'expression'; - client: string | undefined; - code: string; -} | null { - const sourceFile = ts.createSourceFile('code.ts', code, ts.ScriptTarget.Latest, true); - const printer = ts.createPrinter(); - - for (const statement of sourceFile.statements) { - // Check for top-level function declarations - if (ts.isFunctionDeclaration(statement)) { - if (statement.name?.text === 'run') { - return { - type: 'declaration', - client: statement.parameters[0]?.name.getText(), - code: printer.printNode(ts.EmitHint.Unspecified, statement.body!, sourceFile), - }; - } - } - - // Check for variable declarations: const run = () => {} or const run = function() {} - if (ts.isVariableStatement(statement)) { - for (const declaration of statement.declarationList.declarations) { - if ( - ts.isIdentifier(declaration.name) && - declaration.name.text === 'run' && - // Check if it's initialized with a function - declaration.initializer && - (ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer)) - ) { - return { - type: 'expression', - client: declaration.initializer.parameters[0]?.name.getText(), - code: printer.printNode(ts.EmitHint.Unspecified, declaration.initializer, sourceFile), - }; - } - } - } - } - - return null; -} - -function getTSDiagnostics(code: string): string[] { - const functionSource = getRunFunctionSource(code)!; - const codeWithImport = [ - 'import { Finch } from "@tryfinch/finch-api";', - functionSource.type === 'declaration' ? - `async function run(${functionSource.client}: Finch)` - : `const run: (${functionSource.client}: Finch) => Promise =`, - functionSource.code, - ].join('\n'); - const sourcePath = path.resolve('code.ts'); - const ast = ts.createSourceFile(sourcePath, codeWithImport, ts.ScriptTarget.Latest, true); - const options = ts.getDefaultCompilerOptions(); - options.target = ts.ScriptTarget.Latest; - options.module = ts.ModuleKind.NodeNext; - options.moduleResolution = ts.ModuleResolutionKind.NodeNext; - const host = ts.createCompilerHost(options, true); - const newHost: typeof host = { - ...host, - getSourceFile: (...args) => { - if (path.resolve(args[0]) === sourcePath) { - return ast; - } - return host.getSourceFile(...args); - }, - readFile: (...args) => { - if (path.resolve(args[0]) === sourcePath) { - return codeWithImport; - } - return host.readFile(...args); - }, - fileExists: (...args) => { - if (path.resolve(args[0]) === sourcePath) { - return true; - } - return host.fileExists(...args); - }, - }; - const program = ts.createProgram({ - options, - rootNames: [sourcePath], - host: newHost, - }); - const diagnostics = ts.getPreEmitDiagnostics(program, ast); - return diagnostics.map((d) => { - const message = ts.flattenDiagnosticMessageText(d.messageText, '\n'); - if (!d.file || !d.start) return `- ${message}`; - const { line: tsLine } = ts.getLineAndCharacterOfPosition(d.file, d.start); - // We add two lines in the beginning, for the client import and the function declaration. - // So the actual (zero-based) line number is tsLine - 2. - const lineNumber = tsLine - 2; - const line = code.split('\n').at(lineNumber)?.trim(); - return line ? `- ${message}\n at line ${lineNumber + 1}\n ${line}` : `- ${message}`; - }); -} - -const fuse = new Fuse( - [ - 'client.accessTokens.create', - 'client.hris.company.retrieve', - 'client.hris.company.payStatementItem.list', - 'client.hris.company.payStatementItem.rules.create', - 'client.hris.company.payStatementItem.rules.delete', - 'client.hris.company.payStatementItem.rules.list', - 'client.hris.company.payStatementItem.rules.update', - 'client.hris.directory.list', - 'client.hris.directory.listIndividuals', - 'client.hris.individuals.retrieveMany', - 'client.hris.employments.retrieveMany', - 'client.hris.payments.list', - 'client.hris.payStatements.retrieveMany', - 'client.hris.documents.list', - 'client.hris.documents.retreive', - 'client.hris.benefits.create', - 'client.hris.benefits.list', - 'client.hris.benefits.listSupportedBenefits', - 'client.hris.benefits.retrieve', - 'client.hris.benefits.update', - 'client.hris.benefits.individuals.enrollMany', - 'client.hris.benefits.individuals.enrolledIDs', - 'client.hris.benefits.individuals.retrieveManyBenefits', - 'client.hris.benefits.individuals.unenrollMany', - 'client.providers.list', - 'client.account.disconnect', - 'client.account.introspect', - 'client.requestForwarding.forward', - 'client.jobs.automated.create', - 'client.jobs.automated.list', - 'client.jobs.automated.retrieve', - 'client.jobs.manual.retrieve', - 'client.sandbox.connections.create', - 'client.sandbox.connections.accounts.create', - 'client.sandbox.connections.accounts.update', - 'client.sandbox.company.update', - 'client.sandbox.directory.create', - 'client.sandbox.individual.update', - 'client.sandbox.employment.update', - 'client.sandbox.payment.create', - 'client.sandbox.jobs.create', - 'client.sandbox.jobs.configuration.retrieve', - 'client.sandbox.jobs.configuration.update', - 'client.payroll.payGroups.list', - 'client.payroll.payGroups.retrieve', - 'client.connect.sessions.new', - 'client.connect.sessions.reauthenticate', - ], - { threshold: 1, shouldSort: true }, -); - -function getMethodSuggestions(fullyQualifiedMethodName: string): string[] { - return fuse - .search(fullyQualifiedMethodName) - .map(({ item }) => item) - .slice(0, 5); -} - -const proxyToObj = new WeakMap(); -const objToProxy = new WeakMap(); - -type ClientProxyConfig = { - path: string[]; - isBelievedBad?: boolean; -}; - -function makeSdkProxy(obj: T, { path, isBelievedBad = false }: ClientProxyConfig): T { - let proxy: T = objToProxy.get(obj); - - if (!proxy) { - proxy = new Proxy(obj, { - get(target, prop, receiver) { - const propPath = [...path, String(prop)]; - const value = Reflect.get(target, prop, receiver); - - if (isBelievedBad || (!(prop in target) && value === undefined)) { - // If we're accessing a path that doesn't exist, it will probably eventually error. - // Let's proxy it and mark it bad so that we can control the error message. - // We proxy an empty class so that an invocation or construction attempt is possible. - return makeSdkProxy(class {}, { path: propPath, isBelievedBad: true }); - } - - if (value !== null && (typeof value === 'object' || typeof value === 'function')) { - return makeSdkProxy(value, { path: propPath, isBelievedBad }); - } - - return value; - }, - - apply(target, thisArg, args) { - if (isBelievedBad || typeof target !== 'function') { - const fullyQualifiedMethodName = path.join('.'); - const suggestions = getMethodSuggestions(fullyQualifiedMethodName); - throw new Error( - `${fullyQualifiedMethodName} is not a function. Did you mean: ${suggestions.join(', ')}`, - ); - } - - return Reflect.apply(target, proxyToObj.get(thisArg) ?? thisArg, args); - }, - - construct(target, args, newTarget) { - if (isBelievedBad || typeof target !== 'function') { - const fullyQualifiedMethodName = path.join('.'); - const suggestions = getMethodSuggestions(fullyQualifiedMethodName); - throw new Error( - `${fullyQualifiedMethodName} is not a constructor. Did you mean: ${suggestions.join(', ')}`, - ); - } - - return Reflect.construct(target, args, newTarget); - }, - }); - - objToProxy.set(obj, proxy); - proxyToObj.set(proxy, obj); - } - - return proxy; -} - -function parseError(code: string, error: unknown): string | undefined { - if (!(error instanceof Error)) return; - const message = error.name ? `${error.name}: ${error.message}` : error.message; - try { - // Deno uses V8; the first ":LINE:COLUMN" is the top of stack. - const lineNumber = error.stack?.match(/:([0-9]+):[0-9]+/)?.[1]; - // -1 for the zero-based indexing - const line = - lineNumber && - code - .split('\n') - .at(parseInt(lineNumber, 10) - 1) - ?.trim(); - return line ? `${message}\n at line ${lineNumber}\n ${line}` : message; - } catch { - return message; - } -} - -const fetch = async (req: Request): Promise => { - const { opts, code } = (await req.json()) as WorkerInput; - - const runFunctionSource = code ? getRunFunctionSource(code) : null; - if (!runFunctionSource) { - const message = - code ? - 'The code is missing a top-level `run` function.' - : 'The code argument is missing. Provide one containing a top-level `run` function.'; - return Response.json( - { - message: `${message} Write code within this template:\n\n\`\`\`\nasync function run(client) {\n // Fill this out\n}\n\`\`\``, - logLines: [], - errLines: [], - } satisfies WorkerError, - { status: 400, statusText: 'Code execution error' }, - ); - } - - const diagnostics = getTSDiagnostics(code); - if (diagnostics.length > 0) { - return Response.json( - { - message: `The code contains TypeScript diagnostics:\n${diagnostics.join('\n')}`, - logLines: [], - errLines: [], - } satisfies WorkerError, - { status: 400, statusText: 'Code execution error' }, - ); - } - - const client = new Finch({ - ...opts, - }); - - const logLines: string[] = []; - const errLines: string[] = []; - const console = { - log: (...args: unknown[]) => { - logLines.push(util.format(...args)); - }, - error: (...args: unknown[]) => { - errLines.push(util.format(...args)); - }, - }; - try { - let run_ = async (client: any) => {}; - eval(`${code}\nrun_ = run;`); - const result = await run_(makeSdkProxy(client, { path: ['client'] })); - return Response.json({ - result, - logLines, - errLines, - } satisfies WorkerSuccess); - } catch (e) { - return Response.json( - { - message: parseError(code, e), - logLines, - errLines, - } satisfies WorkerError, - { status: 400, statusText: 'Code execution error' }, - ); - } -}; - -export default { fetch }; diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 0d18a1110..afd86fed3 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -1,14 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import path from 'node:path'; -import url from 'node:url'; -import Finch, { ClientOptions } from '@tryfinch/finch-api'; -import { ContentBlock, Endpoint, Metadata, ToolCallResult } from './tools/types'; - +import { Metadata, ToolCallResult, asTextContentResult } from './tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; - -import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; - +import { readEnv } from './server'; +import { WorkerSuccess } from './code-tool-types'; /** * A tool that runs code against a copy of the SDK. * @@ -18,159 +13,50 @@ import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; * * @param endpoints - The endpoints to include in the list. */ -export async function codeTool(): Promise { +export async function codeTool() { const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] }; const tool: Tool = { name: 'execute', description: - 'Runs JavaScript code to interact with the API.\n\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client named "client", and it will be run.\nWrite code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```\n\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', + 'Runs JavaScript code to interact with the API.\n\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized SDK client and it will be run.\nWrite code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```\n\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, }; - - // Import dynamically to avoid failing at import time in cases where the environment is not well-supported. - const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker'); - const { workerPath } = await import('./code-tool-paths.cjs'); - - const handler = async (client: Finch, args: unknown): Promise => { - const baseURLHostname = new URL(client.baseURL).hostname; - const { code } = args as { code: string }; - - const allowRead = [ - 'code-tool-worker.mjs', - `${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, - path.resolve(path.dirname(workerPath), '..'), - ].join(','); - - const worker = await newDenoHTTPWorker(url.pathToFileURL(workerPath), { - runFlags: [ - `--node-modules-dir=manual`, - `--allow-read=${allowRead}`, - `--allow-net=${baseURLHostname}`, - // Allow environment variables because instantiating the client will try to read from them, - // even though they are not set. - '--allow-env', - ], - printOutput: true, - spawnOptions: { - cwd: path.dirname(workerPath), + const handler = async (_: unknown, args: any): Promise => { + const code = args.code as string; + + // this is not required, but passing a Stainless API key for the matching project_name + // will allow you to run code-mode queries against non-published versions of your SDK. + const stainlessAPIKey = readEnv('STAINLESS_API_KEY'); + const codeModeEndpoint = + readEnv('CODE_MODE_ENDPOINT_URL') ?? 'https://api.stainless.com/api/ai/code-tool/'; + + const res = await fetch(codeModeEndpoint, { + method: 'POST', + headers: { + ...(stainlessAPIKey && { Authorization: stainlessAPIKey }), + 'Content-Type': 'application/json', + client_envs: JSON.stringify({ + FINCH_ACCESS_TOKEN: readEnv('FINCH_ACCESS_TOKEN'), + FINCH_CLIENT_ID: readEnv('FINCH_CLIENT_ID'), + FINCH_CLIENT_SECRET: readEnv('FINCH_CLIENT_SECRET'), + FINCH_WEBHOOK_SECRET: readEnv('FINCH_WEBHOOK_SECRET'), + }), }, + body: JSON.stringify({ + project_name: 'finch', + code, + }), }); - try { - const resp = await new Promise((resolve, reject) => { - worker.addEventListener('exit', (exitCode) => { - reject(new Error(`Worker exited with code ${exitCode}`)); - }); - - const opts: ClientOptions = { - baseURL: client.baseURL, - accessToken: client.accessToken, - clientID: client.clientID, - clientSecret: client.clientSecret, - webhookSecret: client.webhookSecret, - defaultHeaders: { - 'X-Stainless-MCP': 'true', - }, - }; - - const req = worker.request( - 'http://localhost', - { - headers: { - 'content-type': 'application/json', - }, - method: 'POST', - }, - (resp) => { - const body: Uint8Array[] = []; - resp.on('error', (err) => { - reject(err); - }); - resp.on('data', (chunk) => { - body.push(chunk); - }); - resp.on('end', () => { - resolve( - new Response(Buffer.concat(body).toString(), { - status: resp.statusCode ?? 200, - headers: resp.headers as any, - }), - ); - }); - }, - ); - - const body = JSON.stringify({ - opts, - code, - } satisfies WorkerInput); - - req.write(body, (err) => { - if (err != null) { - reject(err); - } - }); - - req.end(); - }); - - if (resp.status === 200) { - const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess; - const returnOutput: ContentBlock | null = - result == null ? null : ( - { - type: 'text', - text: typeof result === 'string' ? result : JSON.stringify(result), - } - ); - const logOutput: ContentBlock | null = - logLines.length === 0 ? - null - : { - type: 'text', - text: logLines.join('\n'), - }; - const errOutput: ContentBlock | null = - errLines.length === 0 ? - null - : { - type: 'text', - text: 'Error output:\n' + errLines.join('\n'), - }; - return { - content: [returnOutput, logOutput, errOutput].filter((block) => block !== null), - }; - } else { - const { message, logLines, errLines } = (await resp.json()) as WorkerError; - const messageOutput: ContentBlock | null = - message == null ? null : ( - { - type: 'text', - text: message, - } - ); - const logOutput: ContentBlock | null = - logLines.length === 0 ? - null - : { - type: 'text', - text: logLines.join('\n'), - }; - const errOutput: ContentBlock | null = - errLines.length === 0 ? - null - : { - type: 'text', - text: 'Error output:\n' + errLines.join('\n'), - }; - return { - content: [messageOutput, logOutput, errOutput].filter((block) => block !== null), - isError: true, - }; - } - } finally { - worker.terminate(); + if (!res.ok) { + throw new Error( + `${res.status}: ${ + res.statusText + } error when trying to contact Code Tool server. Details: ${await res.text()}`, + ); } + + return asTextContentResult((await res.json()) as WorkerSuccess); }; return { metadata, tool, handler }; diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts index 8c81f9c5e..d2f4174d3 100644 --- a/packages/mcp-server/src/docs-search-tool.ts +++ b/packages/mcp-server/src/docs-search-tool.ts @@ -46,6 +46,13 @@ export const handler = async (_: unknown, args: Record | undefi const body = args as any; const query = new URLSearchParams(body).toString(); const result = await fetch(`${docsSearchURL}?${query}`); + + if (!result.ok) { + throw new Error( + `${result.status}: ${result.statusText} when using doc search tool. Details: ${await result.text()}`, + ); + } + return asTextContentResult(await result.json()); }; From 41e296a4d57b19346d88236a530b908f9c6f4f02 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 20:42:07 +0000 Subject: [PATCH 32/40] fix(mcp): return correct lines on typescript errors --- .devcontainer/Dockerfile | 23 ++ .eslintrc.js | 10 + jest.setup.ts | 0 packages/mcp-server/src/did-you-mean-proxy.ts | 356 ++++++++++++++++++ src/internal/polyfill/file.node.d.ts | 14 + src/internal/polyfill/file.node.mjs | 9 + tests/responses.test.ts | 24 ++ 7 files changed, 436 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .eslintrc.js create mode 100644 jest.setup.ts create mode 100644 packages/mcp-server/src/did-you-mean-proxy.ts create mode 100644 src/internal/polyfill/file.node.d.ts create mode 100644 src/internal/polyfill/file.node.mjs create mode 100644 tests/responses.test.ts diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..8ea34be96 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,23 @@ +# syntax=docker/dockerfile:1 +FROM debian:bookworm-slim AS stainless + +RUN apt-get update && apt-get install -y \ + nodejs \ + npm \ + yarnpkg \ + && apt-get clean autoclean + +# Ensure UTF-8 encoding +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +# Yarn +RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn + +WORKDIR /workspace + +COPY package.json yarn.lock /workspace/ + +RUN yarn install + +COPY . /workspace diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..60f0e7a35 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], + rules: { + 'no-unused-vars': 'off', + 'prettier/prettier': 'error', + 'unused-imports/no-unused-imports': 'error', + }, + root: true, +}; diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/mcp-server/src/did-you-mean-proxy.ts b/packages/mcp-server/src/did-you-mean-proxy.ts new file mode 100644 index 000000000..8c1380d72 --- /dev/null +++ b/packages/mcp-server/src/did-you-mean-proxy.ts @@ -0,0 +1,356 @@ +import Fuse from 'fuse.js'; + +const allKinds = ['string', 'number', 'boolean', 'object', 'array', 'method', 'constructor'] as const; +type Kind = (typeof allKinds)[number]; + +const nodeInspect = Symbol.for('nodejs.util.inspect.custom'); +const denoInspect = Symbol.for('Deno.customInspect'); + +const stringMethods = new Set(Reflect.ownKeys(String.prototype).filter((k) => typeof k === 'string')); +const numberMethods = new Set(Reflect.ownKeys(Number.prototype).filter((k) => typeof k === 'string')); +const arrayMethods = new Set(Reflect.ownKeys(Array.prototype).filter((k) => typeof k === 'string')); + +function getApplyKinds(name?: string | symbol): readonly Kind[] { + if (!name || name === nodeInspect || name === denoInspect) { + return allKinds; + } + + if (name === Symbol.toPrimitive || name === 'toString' || name === 'valueOf') { + return ['string', 'number', 'boolean']; + } + + if (name === Symbol.iterator) { + return ['array']; + } + + if (typeof name !== 'string') { + return ['method']; + } + + const kinds: Kind[] = []; + if (stringMethods.has(name)) { + kinds.push('string'); + } else if (numberMethods.has(name)) { + kinds.push('number'); + } else if (arrayMethods.has(name)) { + kinds.push('array'); + } + + return kinds.length > 0 ? kinds : ['method']; +} + +type KindPaths = Record; + +function traverseKinds( + obj: object, + path: string = '', + result: KindPaths = { + string: [], + number: [], + boolean: [], + object: [], + array: [], + method: [], + constructor: [], + }, +): KindPaths { + while (obj !== null) { + for (const key of Reflect.ownKeys(obj)) { + if (typeof key !== 'string') { + continue; + } + + if (key === 'constructor') { + continue; + } + + if (!/^[a-zA-Z]/.test(key)) { + continue; + } + + const value = Reflect.get(obj, key); + let kind: Kind; + + switch (typeof value) { + case 'string': { + kind = 'string'; + break; + } + case 'number': + case 'bigint': { + kind = 'number'; + break; + } + case 'boolean': { + kind = 'boolean'; + break; + } + case 'object': { + if (value === null) { + continue; + } + kind = Array.isArray(value) ? 'array' : 'object'; + break; + } + case 'function': { + kind = + key === value.name && value.name === value.prototype?.constructor?.name ? + 'constructor' + : 'method'; + break; + } + default: { + continue; + } + } + + const fullKey = path ? `${path}.${key}` : key; + result[kind].push(fullKey); + + if (kind === 'object') { + traverseKinds(value, fullKey, result); + } else if (kind === 'array' && value.length > 0) { + traverseKinds(value[0], `${fullKey}[]`, result); + } + } + + obj = Object.getPrototypeOf(obj); + if (obj === Object.prototype || obj === Array.prototype) { + break; + } + } + + return result; +} + +export type MakeError = (props: { + expected: readonly Kind[]; + rootPath: (string | symbol)[]; + path: (string | symbol)[]; + suggestions: { item: string; score: number }[]; +}) => string; + +export type ProxyConfig = { + /** + * Whether to also proxy the return values. They will be proxied with + * the same config, except the root path will be blank. Defaults to true. + */ + proxyReturn?: boolean; + /** + * The path to the root object, prepended to path suggestions. For example, + * if this is set to ['client'], then suggestions will be 'client.repos.list', + * 'client.users.list', etc. + */ + rootPath?: (string | symbol)[]; + /** + * Customize the error message to be thrown. The root path will not be + * prepended to either the path or the suggestions. + */ + makeSuggestionError?: MakeError; +}; + +function shouldProxy(value: unknown): value is NonNullable { + return value !== null && (typeof value === 'object' || typeof value === 'function'); +} + +const emptyTargetSymbol = Symbol.for('did-you-mean-proxy.emptyTargetPath'); + +type EmptyTarget = { + [emptyTargetSymbol]: { + getError: () => string; + }; +}; +type EmptyTargetInfo = EmptyTarget[typeof emptyTargetSymbol]; + +/** + * We use a special empty target so we can catch calls and constructions. + * Also useful for de-proxying in the end; if we get an empty target, we know + * we can throw an error. + */ +function createEmptyTarget(info: EmptyTargetInfo): EmptyTarget { + const emptyTarget = function () {} as any; + emptyTarget[nodeInspect] = () => { + throw info.getError(); + }; + emptyTarget[denoInspect] = () => { + throw info.getError(); + }; + emptyTarget[emptyTargetSymbol] = info; + return emptyTarget; +} + +function isEmptyTarget(value: unknown): value is EmptyTarget { + return typeof value === 'function' && (value as any)[emptyTargetSymbol] !== undefined; +} + +export const defaultMakeError: MakeError = function ({ expected, rootPath, path, suggestions }) { + const rootPathString = + rootPath.length > 0 ? `${rootPath.filter((p) => typeof p === 'string').join('.')}.` : ''; + const pathString = `'${rootPathString}${path.filter((p) => typeof p === 'string').join('.')}'`; + + let header = `${pathString} does not exist.`; + if (expected.length === 1) { + const expectedType = + expected[0] === 'array' ? 'an array' + : expected[0] === 'object' ? 'an object' + : expected[0] === 'method' ? 'a function' + : `a ${expected[0]}`; + header = `${pathString} is not ${expectedType}.`; + } + + const suggestionStrings = suggestions + // TODO(sometime): thresholding? + .filter((suggestion) => suggestion.score < 1) + .slice(0, 5) + .map((suggestion) => `'${rootPathString}${suggestion.item}'`); + + let body = ''; + if (suggestionStrings.length === 1) { + body = `Did you mean ${suggestionStrings[0]}?`; + } else if (suggestionStrings.length > 1) { + const commas = suggestionStrings.slice(0, suggestionStrings.length - 1).join(', '); + body = `Did you mean ${commas}, or ${suggestionStrings[suggestionStrings.length - 1]}?`; + } + + return body ? `${header} ${body}` : header; +}; + +export const debugMakeError: MakeError = function ({ expected, path, suggestions }) { + return `path ${path.filter((p) => typeof p === 'string').join('.')}; expected ${expected.join(', ')} +${suggestions + .slice(0, 10) + .map((suggestion) => ` - [${suggestion.score.toFixed(2)}] ${suggestion.item}`) + .join('\n')} +`; +}; + +const proxyToObj = new WeakMap(); + +export function makeProxy(root: Root, config: ProxyConfig = {}): Root { + let kindPaths: KindPaths | null = null; + + config.proxyReturn ??= true; + config.rootPath ??= ['']; + config.makeSuggestionError ??= defaultMakeError; + + const { proxyReturn, rootPath, makeSuggestionError } = config; + const { rootPath: _, ...subconfig } = config; + + function makeError(pathWithRoot: (string | symbol)[], expected: readonly Kind[]) { + if (!kindPaths) { + kindPaths = traverseKinds(root); + } + + const fuse = new Fuse( + expected.flatMap((kind) => kindPaths![kind]), + { includeScore: true }, + ); + + const path = pathWithRoot.slice(rootPath.length); + const searchKey: string[] = []; + for (const key of path) { + // Convert array keys to []: + if (/^\d+$/.test(key.toString())) { + searchKey.push('[]'); + } else if (typeof key === 'string') { + searchKey.push('.'); + searchKey.push(key); + } + } + + const key = searchKey.join(''); + const suggestions = fuse.search(key.slice(1)) as { item: string; score: number }[]; + + return makeSuggestionError({ expected, rootPath, path, suggestions }); + } + + function subproxy(obj: T, path: (string | symbol)[]): T { + const handlers: ProxyHandler = { + get(target, prop, receiver) { + const newPath = [...path, prop]; + const value = Reflect.get(target, prop, receiver); + + if (value === undefined && !Reflect.has(target, prop)) { + // Some common special cases: + // - 'then' is called on a non-thenable. + // - 'toJSON' is called when it's not defined. + // In these cases, we actually want to return undefined, so we + // resolve to the top-level thing. + if (prop === 'then' || prop === 'toJSON') { + return undefined; + } + + return subproxy( + createEmptyTarget({ + getError: () => makeError(newPath, allKinds), + }), + newPath, + ); + } + + return shouldProxy(value) ? subproxy(value, newPath) : value; + }, + construct(target, args, newTarget) { + if (isEmptyTarget(target) || typeof target !== 'function') { + throw new Error(makeError(path, ['constructor'])); + } + + const result = Reflect.construct(target, args, newTarget); + + return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; + }, + apply(target, thisArg, args) { + if (isEmptyTarget(target) || typeof target !== 'function') { + throw new Error(makeError(path, getApplyKinds(path[path.length - 1]))); + } + + const correctThisArg = proxyToObj.get(thisArg) ?? thisArg; + const proxiedArgs = + proxyReturn ? args.map((arg) => (shouldProxy(arg) ? makeProxy(arg, subconfig) : arg)) : args; + const result = Reflect.apply(target, correctThisArg, proxiedArgs); + + return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; + }, + }; + + // All other traps demand a non-empty target: + for (const trap of [ + 'defineProperty', + 'has', + 'set', + 'deleteProperty', + 'ownKeys', + 'getPrototypeOf', + 'setPrototypeOf', + 'isExtensible', + 'preventExtensions', + 'getOwnPropertyDescriptor', + ] as const) { + handlers[trap] = function (target: any, ...args: any[]) { + if (isEmptyTarget(target)) { + throw new Error(makeError(path, allKinds)); + } + + return (Reflect[trap] as any)(target, ...args); + }; + } + + const proxy = new Proxy(obj, handlers); + proxyToObj.set(proxy, obj); + + return proxy; + } + + return subproxy(root, rootPath); +} + +export function deproxy(value: T): T { + // Primitives never get proxied, so these are safe: + if (typeof value !== 'object' && typeof value !== 'function') { + return value; + } + if (isEmptyTarget(value)) { + throw new Error(value[emptyTargetSymbol].getError()); + } + return proxyToObj.get(value) ?? value; +} diff --git a/src/internal/polyfill/file.node.d.ts b/src/internal/polyfill/file.node.d.ts new file mode 100644 index 000000000..c95276d80 --- /dev/null +++ b/src/internal/polyfill/file.node.d.ts @@ -0,0 +1,14 @@ +/** + * This file polyfills the global `File` object for you if it's not already defined + * when running on Node.js + * + * This is only needed on Node.js v18 & v19. Newer versions already define `File` + * as a global. + */ + +// @ts-ignore +type nodeBuffer = typeof import('node:buffer'); +declare const File: typeof globalThis extends { File: unknown } ? (typeof globalThis)['File'] +: nodeBuffer extends { File: unknown } ? nodeBuffer['File'] +: any; +export {}; diff --git a/src/internal/polyfill/file.node.mjs b/src/internal/polyfill/file.node.mjs new file mode 100644 index 000000000..520dcb84c --- /dev/null +++ b/src/internal/polyfill/file.node.mjs @@ -0,0 +1,9 @@ +/** + * This file polyfills the global `File` object for you if it's not already defined + * when running on Node.js + * + * This is only needed on Node.js v18 & v19. Newer versions already define `File` + * as a global. + */ + +import './file.node.js'; diff --git a/tests/responses.test.ts b/tests/responses.test.ts new file mode 100644 index 000000000..ac34b299f --- /dev/null +++ b/tests/responses.test.ts @@ -0,0 +1,24 @@ +import { createResponseHeaders } from '@tryfinch/finch-api/internal/headers'; + +describe('response parsing', () => { + // TODO: test unicode characters + test('headers are case agnostic', async () => { + const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); + expect(headers['content-type']).toEqual('foo'); + expect(headers['Content-type']).toEqual('foo'); + expect(headers['Content-Type']).toEqual('foo'); + expect(headers['accept']).toEqual('text/plain'); + expect(headers['Accept']).toEqual('text/plain'); + expect(headers['Hello-World']).toBeUndefined(); + }); + + test('duplicate headers are concatenated', () => { + const headers = createResponseHeaders( + new Headers([ + ['Content-Type', 'text/xml'], + ['Content-Type', 'application/json'], + ]), + ); + expect(headers['content-type']).toBe('text/xml, application/json'); + }); +}); From 0f70da66ed3b1d7b2be256c25298a788baed4847 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 21:20:21 +0000 Subject: [PATCH 33/40] chore(internal): codegen related update --- .devcontainer/Dockerfile | 23 -- .eslintrc.js | 10 - jest.setup.ts | 0 packages/mcp-server/src/did-you-mean-proxy.ts | 356 ------------------ src/internal/polyfill/file.node.d.ts | 14 - src/internal/polyfill/file.node.mjs | 9 - tests/responses.test.ts | 24 -- 7 files changed, 436 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .eslintrc.js delete mode 100644 jest.setup.ts delete mode 100644 packages/mcp-server/src/did-you-mean-proxy.ts delete mode 100644 src/internal/polyfill/file.node.d.ts delete mode 100644 src/internal/polyfill/file.node.mjs delete mode 100644 tests/responses.test.ts diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 8ea34be96..000000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM debian:bookworm-slim AS stainless - -RUN apt-get update && apt-get install -y \ - nodejs \ - npm \ - yarnpkg \ - && apt-get clean autoclean - -# Ensure UTF-8 encoding -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 - -# Yarn -RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn - -WORKDIR /workspace - -COPY package.json yarn.lock /workspace/ - -RUN yarn install - -COPY . /workspace diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 60f0e7a35..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], - rules: { - 'no-unused-vars': 'off', - 'prettier/prettier': 'error', - 'unused-imports/no-unused-imports': 'error', - }, - root: true, -}; diff --git a/jest.setup.ts b/jest.setup.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/mcp-server/src/did-you-mean-proxy.ts b/packages/mcp-server/src/did-you-mean-proxy.ts deleted file mode 100644 index 8c1380d72..000000000 --- a/packages/mcp-server/src/did-you-mean-proxy.ts +++ /dev/null @@ -1,356 +0,0 @@ -import Fuse from 'fuse.js'; - -const allKinds = ['string', 'number', 'boolean', 'object', 'array', 'method', 'constructor'] as const; -type Kind = (typeof allKinds)[number]; - -const nodeInspect = Symbol.for('nodejs.util.inspect.custom'); -const denoInspect = Symbol.for('Deno.customInspect'); - -const stringMethods = new Set(Reflect.ownKeys(String.prototype).filter((k) => typeof k === 'string')); -const numberMethods = new Set(Reflect.ownKeys(Number.prototype).filter((k) => typeof k === 'string')); -const arrayMethods = new Set(Reflect.ownKeys(Array.prototype).filter((k) => typeof k === 'string')); - -function getApplyKinds(name?: string | symbol): readonly Kind[] { - if (!name || name === nodeInspect || name === denoInspect) { - return allKinds; - } - - if (name === Symbol.toPrimitive || name === 'toString' || name === 'valueOf') { - return ['string', 'number', 'boolean']; - } - - if (name === Symbol.iterator) { - return ['array']; - } - - if (typeof name !== 'string') { - return ['method']; - } - - const kinds: Kind[] = []; - if (stringMethods.has(name)) { - kinds.push('string'); - } else if (numberMethods.has(name)) { - kinds.push('number'); - } else if (arrayMethods.has(name)) { - kinds.push('array'); - } - - return kinds.length > 0 ? kinds : ['method']; -} - -type KindPaths = Record; - -function traverseKinds( - obj: object, - path: string = '', - result: KindPaths = { - string: [], - number: [], - boolean: [], - object: [], - array: [], - method: [], - constructor: [], - }, -): KindPaths { - while (obj !== null) { - for (const key of Reflect.ownKeys(obj)) { - if (typeof key !== 'string') { - continue; - } - - if (key === 'constructor') { - continue; - } - - if (!/^[a-zA-Z]/.test(key)) { - continue; - } - - const value = Reflect.get(obj, key); - let kind: Kind; - - switch (typeof value) { - case 'string': { - kind = 'string'; - break; - } - case 'number': - case 'bigint': { - kind = 'number'; - break; - } - case 'boolean': { - kind = 'boolean'; - break; - } - case 'object': { - if (value === null) { - continue; - } - kind = Array.isArray(value) ? 'array' : 'object'; - break; - } - case 'function': { - kind = - key === value.name && value.name === value.prototype?.constructor?.name ? - 'constructor' - : 'method'; - break; - } - default: { - continue; - } - } - - const fullKey = path ? `${path}.${key}` : key; - result[kind].push(fullKey); - - if (kind === 'object') { - traverseKinds(value, fullKey, result); - } else if (kind === 'array' && value.length > 0) { - traverseKinds(value[0], `${fullKey}[]`, result); - } - } - - obj = Object.getPrototypeOf(obj); - if (obj === Object.prototype || obj === Array.prototype) { - break; - } - } - - return result; -} - -export type MakeError = (props: { - expected: readonly Kind[]; - rootPath: (string | symbol)[]; - path: (string | symbol)[]; - suggestions: { item: string; score: number }[]; -}) => string; - -export type ProxyConfig = { - /** - * Whether to also proxy the return values. They will be proxied with - * the same config, except the root path will be blank. Defaults to true. - */ - proxyReturn?: boolean; - /** - * The path to the root object, prepended to path suggestions. For example, - * if this is set to ['client'], then suggestions will be 'client.repos.list', - * 'client.users.list', etc. - */ - rootPath?: (string | symbol)[]; - /** - * Customize the error message to be thrown. The root path will not be - * prepended to either the path or the suggestions. - */ - makeSuggestionError?: MakeError; -}; - -function shouldProxy(value: unknown): value is NonNullable { - return value !== null && (typeof value === 'object' || typeof value === 'function'); -} - -const emptyTargetSymbol = Symbol.for('did-you-mean-proxy.emptyTargetPath'); - -type EmptyTarget = { - [emptyTargetSymbol]: { - getError: () => string; - }; -}; -type EmptyTargetInfo = EmptyTarget[typeof emptyTargetSymbol]; - -/** - * We use a special empty target so we can catch calls and constructions. - * Also useful for de-proxying in the end; if we get an empty target, we know - * we can throw an error. - */ -function createEmptyTarget(info: EmptyTargetInfo): EmptyTarget { - const emptyTarget = function () {} as any; - emptyTarget[nodeInspect] = () => { - throw info.getError(); - }; - emptyTarget[denoInspect] = () => { - throw info.getError(); - }; - emptyTarget[emptyTargetSymbol] = info; - return emptyTarget; -} - -function isEmptyTarget(value: unknown): value is EmptyTarget { - return typeof value === 'function' && (value as any)[emptyTargetSymbol] !== undefined; -} - -export const defaultMakeError: MakeError = function ({ expected, rootPath, path, suggestions }) { - const rootPathString = - rootPath.length > 0 ? `${rootPath.filter((p) => typeof p === 'string').join('.')}.` : ''; - const pathString = `'${rootPathString}${path.filter((p) => typeof p === 'string').join('.')}'`; - - let header = `${pathString} does not exist.`; - if (expected.length === 1) { - const expectedType = - expected[0] === 'array' ? 'an array' - : expected[0] === 'object' ? 'an object' - : expected[0] === 'method' ? 'a function' - : `a ${expected[0]}`; - header = `${pathString} is not ${expectedType}.`; - } - - const suggestionStrings = suggestions - // TODO(sometime): thresholding? - .filter((suggestion) => suggestion.score < 1) - .slice(0, 5) - .map((suggestion) => `'${rootPathString}${suggestion.item}'`); - - let body = ''; - if (suggestionStrings.length === 1) { - body = `Did you mean ${suggestionStrings[0]}?`; - } else if (suggestionStrings.length > 1) { - const commas = suggestionStrings.slice(0, suggestionStrings.length - 1).join(', '); - body = `Did you mean ${commas}, or ${suggestionStrings[suggestionStrings.length - 1]}?`; - } - - return body ? `${header} ${body}` : header; -}; - -export const debugMakeError: MakeError = function ({ expected, path, suggestions }) { - return `path ${path.filter((p) => typeof p === 'string').join('.')}; expected ${expected.join(', ')} -${suggestions - .slice(0, 10) - .map((suggestion) => ` - [${suggestion.score.toFixed(2)}] ${suggestion.item}`) - .join('\n')} -`; -}; - -const proxyToObj = new WeakMap(); - -export function makeProxy(root: Root, config: ProxyConfig = {}): Root { - let kindPaths: KindPaths | null = null; - - config.proxyReturn ??= true; - config.rootPath ??= ['']; - config.makeSuggestionError ??= defaultMakeError; - - const { proxyReturn, rootPath, makeSuggestionError } = config; - const { rootPath: _, ...subconfig } = config; - - function makeError(pathWithRoot: (string | symbol)[], expected: readonly Kind[]) { - if (!kindPaths) { - kindPaths = traverseKinds(root); - } - - const fuse = new Fuse( - expected.flatMap((kind) => kindPaths![kind]), - { includeScore: true }, - ); - - const path = pathWithRoot.slice(rootPath.length); - const searchKey: string[] = []; - for (const key of path) { - // Convert array keys to []: - if (/^\d+$/.test(key.toString())) { - searchKey.push('[]'); - } else if (typeof key === 'string') { - searchKey.push('.'); - searchKey.push(key); - } - } - - const key = searchKey.join(''); - const suggestions = fuse.search(key.slice(1)) as { item: string; score: number }[]; - - return makeSuggestionError({ expected, rootPath, path, suggestions }); - } - - function subproxy(obj: T, path: (string | symbol)[]): T { - const handlers: ProxyHandler = { - get(target, prop, receiver) { - const newPath = [...path, prop]; - const value = Reflect.get(target, prop, receiver); - - if (value === undefined && !Reflect.has(target, prop)) { - // Some common special cases: - // - 'then' is called on a non-thenable. - // - 'toJSON' is called when it's not defined. - // In these cases, we actually want to return undefined, so we - // resolve to the top-level thing. - if (prop === 'then' || prop === 'toJSON') { - return undefined; - } - - return subproxy( - createEmptyTarget({ - getError: () => makeError(newPath, allKinds), - }), - newPath, - ); - } - - return shouldProxy(value) ? subproxy(value, newPath) : value; - }, - construct(target, args, newTarget) { - if (isEmptyTarget(target) || typeof target !== 'function') { - throw new Error(makeError(path, ['constructor'])); - } - - const result = Reflect.construct(target, args, newTarget); - - return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; - }, - apply(target, thisArg, args) { - if (isEmptyTarget(target) || typeof target !== 'function') { - throw new Error(makeError(path, getApplyKinds(path[path.length - 1]))); - } - - const correctThisArg = proxyToObj.get(thisArg) ?? thisArg; - const proxiedArgs = - proxyReturn ? args.map((arg) => (shouldProxy(arg) ? makeProxy(arg, subconfig) : arg)) : args; - const result = Reflect.apply(target, correctThisArg, proxiedArgs); - - return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; - }, - }; - - // All other traps demand a non-empty target: - for (const trap of [ - 'defineProperty', - 'has', - 'set', - 'deleteProperty', - 'ownKeys', - 'getPrototypeOf', - 'setPrototypeOf', - 'isExtensible', - 'preventExtensions', - 'getOwnPropertyDescriptor', - ] as const) { - handlers[trap] = function (target: any, ...args: any[]) { - if (isEmptyTarget(target)) { - throw new Error(makeError(path, allKinds)); - } - - return (Reflect[trap] as any)(target, ...args); - }; - } - - const proxy = new Proxy(obj, handlers); - proxyToObj.set(proxy, obj); - - return proxy; - } - - return subproxy(root, rootPath); -} - -export function deproxy(value: T): T { - // Primitives never get proxied, so these are safe: - if (typeof value !== 'object' && typeof value !== 'function') { - return value; - } - if (isEmptyTarget(value)) { - throw new Error(value[emptyTargetSymbol].getError()); - } - return proxyToObj.get(value) ?? value; -} diff --git a/src/internal/polyfill/file.node.d.ts b/src/internal/polyfill/file.node.d.ts deleted file mode 100644 index c95276d80..000000000 --- a/src/internal/polyfill/file.node.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This file polyfills the global `File` object for you if it's not already defined - * when running on Node.js - * - * This is only needed on Node.js v18 & v19. Newer versions already define `File` - * as a global. - */ - -// @ts-ignore -type nodeBuffer = typeof import('node:buffer'); -declare const File: typeof globalThis extends { File: unknown } ? (typeof globalThis)['File'] -: nodeBuffer extends { File: unknown } ? nodeBuffer['File'] -: any; -export {}; diff --git a/src/internal/polyfill/file.node.mjs b/src/internal/polyfill/file.node.mjs deleted file mode 100644 index 520dcb84c..000000000 --- a/src/internal/polyfill/file.node.mjs +++ /dev/null @@ -1,9 +0,0 @@ -/** - * This file polyfills the global `File` object for you if it's not already defined - * when running on Node.js - * - * This is only needed on Node.js v18 & v19. Newer versions already define `File` - * as a global. - */ - -import './file.node.js'; diff --git a/tests/responses.test.ts b/tests/responses.test.ts deleted file mode 100644 index ac34b299f..000000000 --- a/tests/responses.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createResponseHeaders } from '@tryfinch/finch-api/internal/headers'; - -describe('response parsing', () => { - // TODO: test unicode characters - test('headers are case agnostic', async () => { - const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); - expect(headers['content-type']).toEqual('foo'); - expect(headers['Content-type']).toEqual('foo'); - expect(headers['Content-Type']).toEqual('foo'); - expect(headers['accept']).toEqual('text/plain'); - expect(headers['Accept']).toEqual('text/plain'); - expect(headers['Hello-World']).toBeUndefined(); - }); - - test('duplicate headers are concatenated', () => { - const headers = createResponseHeaders( - new Headers([ - ['Content-Type', 'text/xml'], - ['Content-Type', 'application/json'], - ]), - ); - expect(headers['content-type']).toBe('text/xml, application/json'); - }); -}); From 6d91f5522082891016109942b7501946d01153fd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 23:40:10 +0000 Subject: [PATCH 34/40] fix(mcp): correct code tool API endpoint --- .devcontainer/Dockerfile | 23 ++ .eslintrc.js | 10 + jest.setup.ts | 0 packages/mcp-server/src/code-tool.ts | 2 +- packages/mcp-server/src/did-you-mean-proxy.ts | 356 ++++++++++++++++++ src/internal/polyfill/file.node.d.ts | 14 + src/internal/polyfill/file.node.mjs | 9 + tests/responses.test.ts | 24 ++ 8 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .eslintrc.js create mode 100644 jest.setup.ts create mode 100644 packages/mcp-server/src/did-you-mean-proxy.ts create mode 100644 src/internal/polyfill/file.node.d.ts create mode 100644 src/internal/polyfill/file.node.mjs create mode 100644 tests/responses.test.ts diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..8ea34be96 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,23 @@ +# syntax=docker/dockerfile:1 +FROM debian:bookworm-slim AS stainless + +RUN apt-get update && apt-get install -y \ + nodejs \ + npm \ + yarnpkg \ + && apt-get clean autoclean + +# Ensure UTF-8 encoding +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +# Yarn +RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn + +WORKDIR /workspace + +COPY package.json yarn.lock /workspace/ + +RUN yarn install + +COPY . /workspace diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..60f0e7a35 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], + rules: { + 'no-unused-vars': 'off', + 'prettier/prettier': 'error', + 'unused-imports/no-unused-imports': 'error', + }, + root: true, +}; diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index afd86fed3..f81a3e237 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -28,7 +28,7 @@ export async function codeTool() { // will allow you to run code-mode queries against non-published versions of your SDK. const stainlessAPIKey = readEnv('STAINLESS_API_KEY'); const codeModeEndpoint = - readEnv('CODE_MODE_ENDPOINT_URL') ?? 'https://api.stainless.com/api/ai/code-tool/'; + readEnv('CODE_MODE_ENDPOINT_URL') ?? 'https://api.stainless.com/api/ai/code-tool'; const res = await fetch(codeModeEndpoint, { method: 'POST', diff --git a/packages/mcp-server/src/did-you-mean-proxy.ts b/packages/mcp-server/src/did-you-mean-proxy.ts new file mode 100644 index 000000000..8c1380d72 --- /dev/null +++ b/packages/mcp-server/src/did-you-mean-proxy.ts @@ -0,0 +1,356 @@ +import Fuse from 'fuse.js'; + +const allKinds = ['string', 'number', 'boolean', 'object', 'array', 'method', 'constructor'] as const; +type Kind = (typeof allKinds)[number]; + +const nodeInspect = Symbol.for('nodejs.util.inspect.custom'); +const denoInspect = Symbol.for('Deno.customInspect'); + +const stringMethods = new Set(Reflect.ownKeys(String.prototype).filter((k) => typeof k === 'string')); +const numberMethods = new Set(Reflect.ownKeys(Number.prototype).filter((k) => typeof k === 'string')); +const arrayMethods = new Set(Reflect.ownKeys(Array.prototype).filter((k) => typeof k === 'string')); + +function getApplyKinds(name?: string | symbol): readonly Kind[] { + if (!name || name === nodeInspect || name === denoInspect) { + return allKinds; + } + + if (name === Symbol.toPrimitive || name === 'toString' || name === 'valueOf') { + return ['string', 'number', 'boolean']; + } + + if (name === Symbol.iterator) { + return ['array']; + } + + if (typeof name !== 'string') { + return ['method']; + } + + const kinds: Kind[] = []; + if (stringMethods.has(name)) { + kinds.push('string'); + } else if (numberMethods.has(name)) { + kinds.push('number'); + } else if (arrayMethods.has(name)) { + kinds.push('array'); + } + + return kinds.length > 0 ? kinds : ['method']; +} + +type KindPaths = Record; + +function traverseKinds( + obj: object, + path: string = '', + result: KindPaths = { + string: [], + number: [], + boolean: [], + object: [], + array: [], + method: [], + constructor: [], + }, +): KindPaths { + while (obj !== null) { + for (const key of Reflect.ownKeys(obj)) { + if (typeof key !== 'string') { + continue; + } + + if (key === 'constructor') { + continue; + } + + if (!/^[a-zA-Z]/.test(key)) { + continue; + } + + const value = Reflect.get(obj, key); + let kind: Kind; + + switch (typeof value) { + case 'string': { + kind = 'string'; + break; + } + case 'number': + case 'bigint': { + kind = 'number'; + break; + } + case 'boolean': { + kind = 'boolean'; + break; + } + case 'object': { + if (value === null) { + continue; + } + kind = Array.isArray(value) ? 'array' : 'object'; + break; + } + case 'function': { + kind = + key === value.name && value.name === value.prototype?.constructor?.name ? + 'constructor' + : 'method'; + break; + } + default: { + continue; + } + } + + const fullKey = path ? `${path}.${key}` : key; + result[kind].push(fullKey); + + if (kind === 'object') { + traverseKinds(value, fullKey, result); + } else if (kind === 'array' && value.length > 0) { + traverseKinds(value[0], `${fullKey}[]`, result); + } + } + + obj = Object.getPrototypeOf(obj); + if (obj === Object.prototype || obj === Array.prototype) { + break; + } + } + + return result; +} + +export type MakeError = (props: { + expected: readonly Kind[]; + rootPath: (string | symbol)[]; + path: (string | symbol)[]; + suggestions: { item: string; score: number }[]; +}) => string; + +export type ProxyConfig = { + /** + * Whether to also proxy the return values. They will be proxied with + * the same config, except the root path will be blank. Defaults to true. + */ + proxyReturn?: boolean; + /** + * The path to the root object, prepended to path suggestions. For example, + * if this is set to ['client'], then suggestions will be 'client.repos.list', + * 'client.users.list', etc. + */ + rootPath?: (string | symbol)[]; + /** + * Customize the error message to be thrown. The root path will not be + * prepended to either the path or the suggestions. + */ + makeSuggestionError?: MakeError; +}; + +function shouldProxy(value: unknown): value is NonNullable { + return value !== null && (typeof value === 'object' || typeof value === 'function'); +} + +const emptyTargetSymbol = Symbol.for('did-you-mean-proxy.emptyTargetPath'); + +type EmptyTarget = { + [emptyTargetSymbol]: { + getError: () => string; + }; +}; +type EmptyTargetInfo = EmptyTarget[typeof emptyTargetSymbol]; + +/** + * We use a special empty target so we can catch calls and constructions. + * Also useful for de-proxying in the end; if we get an empty target, we know + * we can throw an error. + */ +function createEmptyTarget(info: EmptyTargetInfo): EmptyTarget { + const emptyTarget = function () {} as any; + emptyTarget[nodeInspect] = () => { + throw info.getError(); + }; + emptyTarget[denoInspect] = () => { + throw info.getError(); + }; + emptyTarget[emptyTargetSymbol] = info; + return emptyTarget; +} + +function isEmptyTarget(value: unknown): value is EmptyTarget { + return typeof value === 'function' && (value as any)[emptyTargetSymbol] !== undefined; +} + +export const defaultMakeError: MakeError = function ({ expected, rootPath, path, suggestions }) { + const rootPathString = + rootPath.length > 0 ? `${rootPath.filter((p) => typeof p === 'string').join('.')}.` : ''; + const pathString = `'${rootPathString}${path.filter((p) => typeof p === 'string').join('.')}'`; + + let header = `${pathString} does not exist.`; + if (expected.length === 1) { + const expectedType = + expected[0] === 'array' ? 'an array' + : expected[0] === 'object' ? 'an object' + : expected[0] === 'method' ? 'a function' + : `a ${expected[0]}`; + header = `${pathString} is not ${expectedType}.`; + } + + const suggestionStrings = suggestions + // TODO(sometime): thresholding? + .filter((suggestion) => suggestion.score < 1) + .slice(0, 5) + .map((suggestion) => `'${rootPathString}${suggestion.item}'`); + + let body = ''; + if (suggestionStrings.length === 1) { + body = `Did you mean ${suggestionStrings[0]}?`; + } else if (suggestionStrings.length > 1) { + const commas = suggestionStrings.slice(0, suggestionStrings.length - 1).join(', '); + body = `Did you mean ${commas}, or ${suggestionStrings[suggestionStrings.length - 1]}?`; + } + + return body ? `${header} ${body}` : header; +}; + +export const debugMakeError: MakeError = function ({ expected, path, suggestions }) { + return `path ${path.filter((p) => typeof p === 'string').join('.')}; expected ${expected.join(', ')} +${suggestions + .slice(0, 10) + .map((suggestion) => ` - [${suggestion.score.toFixed(2)}] ${suggestion.item}`) + .join('\n')} +`; +}; + +const proxyToObj = new WeakMap(); + +export function makeProxy(root: Root, config: ProxyConfig = {}): Root { + let kindPaths: KindPaths | null = null; + + config.proxyReturn ??= true; + config.rootPath ??= ['']; + config.makeSuggestionError ??= defaultMakeError; + + const { proxyReturn, rootPath, makeSuggestionError } = config; + const { rootPath: _, ...subconfig } = config; + + function makeError(pathWithRoot: (string | symbol)[], expected: readonly Kind[]) { + if (!kindPaths) { + kindPaths = traverseKinds(root); + } + + const fuse = new Fuse( + expected.flatMap((kind) => kindPaths![kind]), + { includeScore: true }, + ); + + const path = pathWithRoot.slice(rootPath.length); + const searchKey: string[] = []; + for (const key of path) { + // Convert array keys to []: + if (/^\d+$/.test(key.toString())) { + searchKey.push('[]'); + } else if (typeof key === 'string') { + searchKey.push('.'); + searchKey.push(key); + } + } + + const key = searchKey.join(''); + const suggestions = fuse.search(key.slice(1)) as { item: string; score: number }[]; + + return makeSuggestionError({ expected, rootPath, path, suggestions }); + } + + function subproxy(obj: T, path: (string | symbol)[]): T { + const handlers: ProxyHandler = { + get(target, prop, receiver) { + const newPath = [...path, prop]; + const value = Reflect.get(target, prop, receiver); + + if (value === undefined && !Reflect.has(target, prop)) { + // Some common special cases: + // - 'then' is called on a non-thenable. + // - 'toJSON' is called when it's not defined. + // In these cases, we actually want to return undefined, so we + // resolve to the top-level thing. + if (prop === 'then' || prop === 'toJSON') { + return undefined; + } + + return subproxy( + createEmptyTarget({ + getError: () => makeError(newPath, allKinds), + }), + newPath, + ); + } + + return shouldProxy(value) ? subproxy(value, newPath) : value; + }, + construct(target, args, newTarget) { + if (isEmptyTarget(target) || typeof target !== 'function') { + throw new Error(makeError(path, ['constructor'])); + } + + const result = Reflect.construct(target, args, newTarget); + + return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; + }, + apply(target, thisArg, args) { + if (isEmptyTarget(target) || typeof target !== 'function') { + throw new Error(makeError(path, getApplyKinds(path[path.length - 1]))); + } + + const correctThisArg = proxyToObj.get(thisArg) ?? thisArg; + const proxiedArgs = + proxyReturn ? args.map((arg) => (shouldProxy(arg) ? makeProxy(arg, subconfig) : arg)) : args; + const result = Reflect.apply(target, correctThisArg, proxiedArgs); + + return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; + }, + }; + + // All other traps demand a non-empty target: + for (const trap of [ + 'defineProperty', + 'has', + 'set', + 'deleteProperty', + 'ownKeys', + 'getPrototypeOf', + 'setPrototypeOf', + 'isExtensible', + 'preventExtensions', + 'getOwnPropertyDescriptor', + ] as const) { + handlers[trap] = function (target: any, ...args: any[]) { + if (isEmptyTarget(target)) { + throw new Error(makeError(path, allKinds)); + } + + return (Reflect[trap] as any)(target, ...args); + }; + } + + const proxy = new Proxy(obj, handlers); + proxyToObj.set(proxy, obj); + + return proxy; + } + + return subproxy(root, rootPath); +} + +export function deproxy(value: T): T { + // Primitives never get proxied, so these are safe: + if (typeof value !== 'object' && typeof value !== 'function') { + return value; + } + if (isEmptyTarget(value)) { + throw new Error(value[emptyTargetSymbol].getError()); + } + return proxyToObj.get(value) ?? value; +} diff --git a/src/internal/polyfill/file.node.d.ts b/src/internal/polyfill/file.node.d.ts new file mode 100644 index 000000000..c95276d80 --- /dev/null +++ b/src/internal/polyfill/file.node.d.ts @@ -0,0 +1,14 @@ +/** + * This file polyfills the global `File` object for you if it's not already defined + * when running on Node.js + * + * This is only needed on Node.js v18 & v19. Newer versions already define `File` + * as a global. + */ + +// @ts-ignore +type nodeBuffer = typeof import('node:buffer'); +declare const File: typeof globalThis extends { File: unknown } ? (typeof globalThis)['File'] +: nodeBuffer extends { File: unknown } ? nodeBuffer['File'] +: any; +export {}; diff --git a/src/internal/polyfill/file.node.mjs b/src/internal/polyfill/file.node.mjs new file mode 100644 index 000000000..520dcb84c --- /dev/null +++ b/src/internal/polyfill/file.node.mjs @@ -0,0 +1,9 @@ +/** + * This file polyfills the global `File` object for you if it's not already defined + * when running on Node.js + * + * This is only needed on Node.js v18 & v19. Newer versions already define `File` + * as a global. + */ + +import './file.node.js'; diff --git a/tests/responses.test.ts b/tests/responses.test.ts new file mode 100644 index 000000000..ac34b299f --- /dev/null +++ b/tests/responses.test.ts @@ -0,0 +1,24 @@ +import { createResponseHeaders } from '@tryfinch/finch-api/internal/headers'; + +describe('response parsing', () => { + // TODO: test unicode characters + test('headers are case agnostic', async () => { + const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); + expect(headers['content-type']).toEqual('foo'); + expect(headers['Content-type']).toEqual('foo'); + expect(headers['Content-Type']).toEqual('foo'); + expect(headers['accept']).toEqual('text/plain'); + expect(headers['Accept']).toEqual('text/plain'); + expect(headers['Hello-World']).toBeUndefined(); + }); + + test('duplicate headers are concatenated', () => { + const headers = createResponseHeaders( + new Headers([ + ['Content-Type', 'text/xml'], + ['Content-Type', 'application/json'], + ]), + ); + expect(headers['content-type']).toBe('text/xml, application/json'); + }); +}); From a361eb9698f2f3f76f2f02c76d8d44b132a1787e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 23:54:44 +0000 Subject: [PATCH 35/40] chore(internal): codegen related update --- .devcontainer/Dockerfile | 23 -- .eslintrc.js | 10 - jest.setup.ts | 0 packages/mcp-server/src/did-you-mean-proxy.ts | 356 ------------------ src/internal/polyfill/file.node.d.ts | 14 - src/internal/polyfill/file.node.mjs | 9 - tests/responses.test.ts | 24 -- 7 files changed, 436 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .eslintrc.js delete mode 100644 jest.setup.ts delete mode 100644 packages/mcp-server/src/did-you-mean-proxy.ts delete mode 100644 src/internal/polyfill/file.node.d.ts delete mode 100644 src/internal/polyfill/file.node.mjs delete mode 100644 tests/responses.test.ts diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 8ea34be96..000000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM debian:bookworm-slim AS stainless - -RUN apt-get update && apt-get install -y \ - nodejs \ - npm \ - yarnpkg \ - && apt-get clean autoclean - -# Ensure UTF-8 encoding -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 - -# Yarn -RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn - -WORKDIR /workspace - -COPY package.json yarn.lock /workspace/ - -RUN yarn install - -COPY . /workspace diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 60f0e7a35..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], - rules: { - 'no-unused-vars': 'off', - 'prettier/prettier': 'error', - 'unused-imports/no-unused-imports': 'error', - }, - root: true, -}; diff --git a/jest.setup.ts b/jest.setup.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/mcp-server/src/did-you-mean-proxy.ts b/packages/mcp-server/src/did-you-mean-proxy.ts deleted file mode 100644 index 8c1380d72..000000000 --- a/packages/mcp-server/src/did-you-mean-proxy.ts +++ /dev/null @@ -1,356 +0,0 @@ -import Fuse from 'fuse.js'; - -const allKinds = ['string', 'number', 'boolean', 'object', 'array', 'method', 'constructor'] as const; -type Kind = (typeof allKinds)[number]; - -const nodeInspect = Symbol.for('nodejs.util.inspect.custom'); -const denoInspect = Symbol.for('Deno.customInspect'); - -const stringMethods = new Set(Reflect.ownKeys(String.prototype).filter((k) => typeof k === 'string')); -const numberMethods = new Set(Reflect.ownKeys(Number.prototype).filter((k) => typeof k === 'string')); -const arrayMethods = new Set(Reflect.ownKeys(Array.prototype).filter((k) => typeof k === 'string')); - -function getApplyKinds(name?: string | symbol): readonly Kind[] { - if (!name || name === nodeInspect || name === denoInspect) { - return allKinds; - } - - if (name === Symbol.toPrimitive || name === 'toString' || name === 'valueOf') { - return ['string', 'number', 'boolean']; - } - - if (name === Symbol.iterator) { - return ['array']; - } - - if (typeof name !== 'string') { - return ['method']; - } - - const kinds: Kind[] = []; - if (stringMethods.has(name)) { - kinds.push('string'); - } else if (numberMethods.has(name)) { - kinds.push('number'); - } else if (arrayMethods.has(name)) { - kinds.push('array'); - } - - return kinds.length > 0 ? kinds : ['method']; -} - -type KindPaths = Record; - -function traverseKinds( - obj: object, - path: string = '', - result: KindPaths = { - string: [], - number: [], - boolean: [], - object: [], - array: [], - method: [], - constructor: [], - }, -): KindPaths { - while (obj !== null) { - for (const key of Reflect.ownKeys(obj)) { - if (typeof key !== 'string') { - continue; - } - - if (key === 'constructor') { - continue; - } - - if (!/^[a-zA-Z]/.test(key)) { - continue; - } - - const value = Reflect.get(obj, key); - let kind: Kind; - - switch (typeof value) { - case 'string': { - kind = 'string'; - break; - } - case 'number': - case 'bigint': { - kind = 'number'; - break; - } - case 'boolean': { - kind = 'boolean'; - break; - } - case 'object': { - if (value === null) { - continue; - } - kind = Array.isArray(value) ? 'array' : 'object'; - break; - } - case 'function': { - kind = - key === value.name && value.name === value.prototype?.constructor?.name ? - 'constructor' - : 'method'; - break; - } - default: { - continue; - } - } - - const fullKey = path ? `${path}.${key}` : key; - result[kind].push(fullKey); - - if (kind === 'object') { - traverseKinds(value, fullKey, result); - } else if (kind === 'array' && value.length > 0) { - traverseKinds(value[0], `${fullKey}[]`, result); - } - } - - obj = Object.getPrototypeOf(obj); - if (obj === Object.prototype || obj === Array.prototype) { - break; - } - } - - return result; -} - -export type MakeError = (props: { - expected: readonly Kind[]; - rootPath: (string | symbol)[]; - path: (string | symbol)[]; - suggestions: { item: string; score: number }[]; -}) => string; - -export type ProxyConfig = { - /** - * Whether to also proxy the return values. They will be proxied with - * the same config, except the root path will be blank. Defaults to true. - */ - proxyReturn?: boolean; - /** - * The path to the root object, prepended to path suggestions. For example, - * if this is set to ['client'], then suggestions will be 'client.repos.list', - * 'client.users.list', etc. - */ - rootPath?: (string | symbol)[]; - /** - * Customize the error message to be thrown. The root path will not be - * prepended to either the path or the suggestions. - */ - makeSuggestionError?: MakeError; -}; - -function shouldProxy(value: unknown): value is NonNullable { - return value !== null && (typeof value === 'object' || typeof value === 'function'); -} - -const emptyTargetSymbol = Symbol.for('did-you-mean-proxy.emptyTargetPath'); - -type EmptyTarget = { - [emptyTargetSymbol]: { - getError: () => string; - }; -}; -type EmptyTargetInfo = EmptyTarget[typeof emptyTargetSymbol]; - -/** - * We use a special empty target so we can catch calls and constructions. - * Also useful for de-proxying in the end; if we get an empty target, we know - * we can throw an error. - */ -function createEmptyTarget(info: EmptyTargetInfo): EmptyTarget { - const emptyTarget = function () {} as any; - emptyTarget[nodeInspect] = () => { - throw info.getError(); - }; - emptyTarget[denoInspect] = () => { - throw info.getError(); - }; - emptyTarget[emptyTargetSymbol] = info; - return emptyTarget; -} - -function isEmptyTarget(value: unknown): value is EmptyTarget { - return typeof value === 'function' && (value as any)[emptyTargetSymbol] !== undefined; -} - -export const defaultMakeError: MakeError = function ({ expected, rootPath, path, suggestions }) { - const rootPathString = - rootPath.length > 0 ? `${rootPath.filter((p) => typeof p === 'string').join('.')}.` : ''; - const pathString = `'${rootPathString}${path.filter((p) => typeof p === 'string').join('.')}'`; - - let header = `${pathString} does not exist.`; - if (expected.length === 1) { - const expectedType = - expected[0] === 'array' ? 'an array' - : expected[0] === 'object' ? 'an object' - : expected[0] === 'method' ? 'a function' - : `a ${expected[0]}`; - header = `${pathString} is not ${expectedType}.`; - } - - const suggestionStrings = suggestions - // TODO(sometime): thresholding? - .filter((suggestion) => suggestion.score < 1) - .slice(0, 5) - .map((suggestion) => `'${rootPathString}${suggestion.item}'`); - - let body = ''; - if (suggestionStrings.length === 1) { - body = `Did you mean ${suggestionStrings[0]}?`; - } else if (suggestionStrings.length > 1) { - const commas = suggestionStrings.slice(0, suggestionStrings.length - 1).join(', '); - body = `Did you mean ${commas}, or ${suggestionStrings[suggestionStrings.length - 1]}?`; - } - - return body ? `${header} ${body}` : header; -}; - -export const debugMakeError: MakeError = function ({ expected, path, suggestions }) { - return `path ${path.filter((p) => typeof p === 'string').join('.')}; expected ${expected.join(', ')} -${suggestions - .slice(0, 10) - .map((suggestion) => ` - [${suggestion.score.toFixed(2)}] ${suggestion.item}`) - .join('\n')} -`; -}; - -const proxyToObj = new WeakMap(); - -export function makeProxy(root: Root, config: ProxyConfig = {}): Root { - let kindPaths: KindPaths | null = null; - - config.proxyReturn ??= true; - config.rootPath ??= ['']; - config.makeSuggestionError ??= defaultMakeError; - - const { proxyReturn, rootPath, makeSuggestionError } = config; - const { rootPath: _, ...subconfig } = config; - - function makeError(pathWithRoot: (string | symbol)[], expected: readonly Kind[]) { - if (!kindPaths) { - kindPaths = traverseKinds(root); - } - - const fuse = new Fuse( - expected.flatMap((kind) => kindPaths![kind]), - { includeScore: true }, - ); - - const path = pathWithRoot.slice(rootPath.length); - const searchKey: string[] = []; - for (const key of path) { - // Convert array keys to []: - if (/^\d+$/.test(key.toString())) { - searchKey.push('[]'); - } else if (typeof key === 'string') { - searchKey.push('.'); - searchKey.push(key); - } - } - - const key = searchKey.join(''); - const suggestions = fuse.search(key.slice(1)) as { item: string; score: number }[]; - - return makeSuggestionError({ expected, rootPath, path, suggestions }); - } - - function subproxy(obj: T, path: (string | symbol)[]): T { - const handlers: ProxyHandler = { - get(target, prop, receiver) { - const newPath = [...path, prop]; - const value = Reflect.get(target, prop, receiver); - - if (value === undefined && !Reflect.has(target, prop)) { - // Some common special cases: - // - 'then' is called on a non-thenable. - // - 'toJSON' is called when it's not defined. - // In these cases, we actually want to return undefined, so we - // resolve to the top-level thing. - if (prop === 'then' || prop === 'toJSON') { - return undefined; - } - - return subproxy( - createEmptyTarget({ - getError: () => makeError(newPath, allKinds), - }), - newPath, - ); - } - - return shouldProxy(value) ? subproxy(value, newPath) : value; - }, - construct(target, args, newTarget) { - if (isEmptyTarget(target) || typeof target !== 'function') { - throw new Error(makeError(path, ['constructor'])); - } - - const result = Reflect.construct(target, args, newTarget); - - return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; - }, - apply(target, thisArg, args) { - if (isEmptyTarget(target) || typeof target !== 'function') { - throw new Error(makeError(path, getApplyKinds(path[path.length - 1]))); - } - - const correctThisArg = proxyToObj.get(thisArg) ?? thisArg; - const proxiedArgs = - proxyReturn ? args.map((arg) => (shouldProxy(arg) ? makeProxy(arg, subconfig) : arg)) : args; - const result = Reflect.apply(target, correctThisArg, proxiedArgs); - - return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; - }, - }; - - // All other traps demand a non-empty target: - for (const trap of [ - 'defineProperty', - 'has', - 'set', - 'deleteProperty', - 'ownKeys', - 'getPrototypeOf', - 'setPrototypeOf', - 'isExtensible', - 'preventExtensions', - 'getOwnPropertyDescriptor', - ] as const) { - handlers[trap] = function (target: any, ...args: any[]) { - if (isEmptyTarget(target)) { - throw new Error(makeError(path, allKinds)); - } - - return (Reflect[trap] as any)(target, ...args); - }; - } - - const proxy = new Proxy(obj, handlers); - proxyToObj.set(proxy, obj); - - return proxy; - } - - return subproxy(root, rootPath); -} - -export function deproxy(value: T): T { - // Primitives never get proxied, so these are safe: - if (typeof value !== 'object' && typeof value !== 'function') { - return value; - } - if (isEmptyTarget(value)) { - throw new Error(value[emptyTargetSymbol].getError()); - } - return proxyToObj.get(value) ?? value; -} diff --git a/src/internal/polyfill/file.node.d.ts b/src/internal/polyfill/file.node.d.ts deleted file mode 100644 index c95276d80..000000000 --- a/src/internal/polyfill/file.node.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This file polyfills the global `File` object for you if it's not already defined - * when running on Node.js - * - * This is only needed on Node.js v18 & v19. Newer versions already define `File` - * as a global. - */ - -// @ts-ignore -type nodeBuffer = typeof import('node:buffer'); -declare const File: typeof globalThis extends { File: unknown } ? (typeof globalThis)['File'] -: nodeBuffer extends { File: unknown } ? nodeBuffer['File'] -: any; -export {}; diff --git a/src/internal/polyfill/file.node.mjs b/src/internal/polyfill/file.node.mjs deleted file mode 100644 index 520dcb84c..000000000 --- a/src/internal/polyfill/file.node.mjs +++ /dev/null @@ -1,9 +0,0 @@ -/** - * This file polyfills the global `File` object for you if it's not already defined - * when running on Node.js - * - * This is only needed on Node.js v18 & v19. Newer versions already define `File` - * as a global. - */ - -import './file.node.js'; diff --git a/tests/responses.test.ts b/tests/responses.test.ts deleted file mode 100644 index ac34b299f..000000000 --- a/tests/responses.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createResponseHeaders } from '@tryfinch/finch-api/internal/headers'; - -describe('response parsing', () => { - // TODO: test unicode characters - test('headers are case agnostic', async () => { - const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); - expect(headers['content-type']).toEqual('foo'); - expect(headers['Content-type']).toEqual('foo'); - expect(headers['Content-Type']).toEqual('foo'); - expect(headers['accept']).toEqual('text/plain'); - expect(headers['Accept']).toEqual('text/plain'); - expect(headers['Hello-World']).toBeUndefined(); - }); - - test('duplicate headers are concatenated', () => { - const headers = createResponseHeaders( - new Headers([ - ['Content-Type', 'text/xml'], - ['Content-Type', 'application/json'], - ]), - ); - expect(headers['content-type']).toBe('text/xml, application/json'); - }); -}); From 9cdaea8d601afff676330e8eddc8fb4abb02cee3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 18:03:44 +0000 Subject: [PATCH 36/40] fix(mcp): add client instantiation options to code tool --- packages/mcp-server/src/code-tool.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index f81a3e237..929723420 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -44,6 +44,7 @@ export async function codeTool() { }, body: JSON.stringify({ project_name: 'finch', + client_opts: { accessToken: readEnv('FINCH_ACCESS_TOKEN') }, code, }), }); From 88a4eacf9b9276a7b3db8a8dc9f12a0053251829 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:19:21 +0000 Subject: [PATCH 37/40] chore(mcp): update lockfile --- packages/mcp-server/yarn.lock | 92 ++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock index ddf11f383..38be884f4 100644 --- a/packages/mcp-server/yarn.lock +++ b/packages/mcp-server/yarn.lock @@ -736,12 +736,13 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@modelcontextprotocol/sdk@^1.11.5": - version "1.17.3" - resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz#cf92354220f0183d28179e96a9bf3a8f6d3211ae" - integrity sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg== +"@modelcontextprotocol/sdk@^1.24.0": + version "1.24.3" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.24.3.tgz#81a3fcc919cb4ce8630e2bcecf59759176eb331a" + integrity sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw== dependencies: - ajv "^6.12.6" + ajv "^8.17.1" + ajv-formats "^3.0.1" content-type "^1.0.5" cors "^2.8.5" cross-spawn "^7.0.5" @@ -749,15 +750,11 @@ eventsource-parser "^3.0.0" express "^5.0.1" express-rate-limit "^7.5.0" + jose "^6.1.1" pkce-challenge "^5.0.0" raw-body "^3.0.0" - zod "^3.23.8" - zod-to-json-schema "^3.24.1" - -"@noble/hashes@1.5": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" - integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== + zod "^3.25 || ^4.0" + zod-to-json-schema "^3.25.0" "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -804,11 +801,6 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@tryfinch/finch-api@file:../../dist": - version "6.38.0" - dependencies: - "@noble/hashes" "1.5" - "@ts-morph/common@~0.20.0": version "0.20.0" resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.20.0.tgz#3f161996b085ba4519731e4d24c35f6cba5b80af" @@ -972,9 +964,9 @@ undici-types "~6.21.0" "@types/node@^22.5.5": - version "22.19.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.1.tgz#1188f1ddc9f46b4cc3aec76749050b4e1f459b7b" - integrity sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ== + version "22.19.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.2.tgz#2f0956fba46518aaf7578c84e37bddab55f85d01" + integrity sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw== dependencies: undici-types "~6.21.0" @@ -1151,7 +1143,14 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.12.4, ajv@^6.12.6: +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + +ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1161,6 +1160,16 @@ ajv@^6.12.4, ajv@^6.12.6: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.0, ajv@^8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -1921,6 +1930,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + fastq@^1.6.0: version "1.19.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" @@ -2747,6 +2761,11 @@ jest@^29.4.0: import-local "^3.0.2" jest-cli "^29.7.0" +jose@^6.1.1: + version "6.1.3" + resolved "https://registry.yarnpkg.com/jose/-/jose-6.1.3.tgz#8453d7be88af7bb7d64a0481d6a35a0145ba3ea5" + integrity sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ== + "jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz": version "0.8.8" resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz#7849ef64bdfc28f70cbfc9888f886860e96da10d" @@ -2791,6 +2810,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -2999,9 +3023,9 @@ negotiator@^1.0.0: integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== node-forge@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + version "1.3.3" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.3.tgz#0ad80f6333b3a0045e827ac20b7f735f93716751" + integrity sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg== node-int64@^0.4.0: version "0.4.0" @@ -3302,6 +3326,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -3910,20 +3939,25 @@ yoctocolors-cjs@^2.1.2: resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz#7e4964ea8ec422b7a40ac917d3a344cfd2304baa" integrity sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw== -zod-to-json-schema@^3.24.1, zod-to-json-schema@^3.24.5: +zod-to-json-schema@^3.24.5: version "3.24.5" resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3" integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== +zod-to-json-schema@^3.25.0: + version "3.25.0" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz#df504c957c4fb0feff467c74d03e6aab0b013e1c" + integrity sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ== + zod-validation-error@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.1.tgz#a105723eb40299578a6a38cb86647068f6d005b1" integrity sha512-F3rdaCOHs5ViJ5YTz5zzRtfkQdMdIeKudJAoxy7yB/2ZMEHw73lmCAcQw11r7++20MyGl4WV59EVh7A9rNAyog== -zod@^3.23.8: - version "3.24.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f" - integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg== +"zod@^3.25 || ^4.0": + version "4.1.13" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.13.tgz#93699a8afe937ba96badbb0ce8be6033c0a4b6b1" + integrity sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig== zod@^3.25.20, zod@^3.25.67: version "3.25.76" From fc8f7b88cdd0929c91263fcbee85ca005433cff1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:57:28 +0000 Subject: [PATCH 38/40] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 459fe7372..ceb8bf2d5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-39e0191e43a9db93c8f35e91d10013f05352a2bedcf7ead6bac437957f6e922e.yml -openapi_spec_hash: 58c2cf578f0736b8c5df957f6a61190b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-f81c5824a9002c980fc0d66c4d52e6cbd8baf7678f5e0f2215909357cff6f82c.yml +openapi_spec_hash: 7714062cac3bb5597b8571172775bc92 config_hash: 0892e2e0eeb0343a022afa62e9080dd1 From 03cb57e6aecd4ed8b2c86de4f849acc3ccc78f73 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 19:49:54 +0000 Subject: [PATCH 39/40] codegen metadata --- .stats.yml | 4 ++-- tests/api-resources/hris/benefits/individuals.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index ceb8bf2d5..459fe7372 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-f81c5824a9002c980fc0d66c4d52e6cbd8baf7678f5e0f2215909357cff6f82c.yml -openapi_spec_hash: 7714062cac3bb5597b8571172775bc92 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-39e0191e43a9db93c8f35e91d10013f05352a2bedcf7ead6bac437957f6e922e.yml +openapi_spec_hash: 58c2cf578f0736b8c5df957f6a61190b config_hash: 0892e2e0eeb0343a022afa62e9080dd1 diff --git a/tests/api-resources/hris/benefits/individuals.test.ts b/tests/api-resources/hris/benefits/individuals.test.ts index b148b1056..85f555db0 100644 --- a/tests/api-resources/hris/benefits/individuals.test.ts +++ b/tests/api-resources/hris/benefits/individuals.test.ts @@ -8,7 +8,7 @@ const client = new Finch({ }); describe('resource individuals', () => { - test('enrollMany', async () => { + test.skip('enrollMany', async () => { const responsePromise = client.hris.benefits.individuals.enrollMany('benefit_id'); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); From 2831f4264a1b5846f60e585919189734cf84b35d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 19:50:32 +0000 Subject: [PATCH 40/40] release: 7.0.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 55 +++++++++++++++++++++++++++++++ package.json | 2 +- packages/mcp-server/package.json | 2 +- packages/mcp-server/src/server.ts | 2 +- src/version.ts | 2 +- 6 files changed, 60 insertions(+), 5 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a25f0a9b1..aeda91d84 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "6.38.0" + ".": "7.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b12249840..d4bcf4300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,60 @@ # Changelog +## 7.0.0 (2025-12-17) + +Full Changelog: [v6.38.0...v7.0.0](https://github.com/Finch-API/finch-api-node/compare/v6.38.0...v7.0.0) + +### Features + +* **api:** api update ([b2d5fcc](https://github.com/Finch-API/finch-api-node/commit/b2d5fcca38564c4b562263813bc945bae0a06b39)) +* **api:** api update ([b1be6b0](https://github.com/Finch-API/finch-api-node/commit/b1be6b0108c5ae0896e7379d5342fc9629ec8c30)) +* **api:** api update ([3fb650b](https://github.com/Finch-API/finch-api-node/commit/3fb650b9ac0084d7a6e4d9c026c0355b2b74af0b)) +* **api:** manual updates ([adf8013](https://github.com/Finch-API/finch-api-node/commit/adf8013674c0b3c7fb532ad8b98ec23863f7200b)) +* **api:** move node to typescript generator ([baa0237](https://github.com/Finch-API/finch-api-node/commit/baa02378d7b36940a4ac806462b6180d77e3ceac)) +* **api:** update automated code reviewer selection ([8639c2e](https://github.com/Finch-API/finch-api-node/commit/8639c2e81ba805d4e7df858302d0efbe725c2feb)) +* **mcp:** add detail field to docs search tool ([7eb8b44](https://github.com/Finch-API/finch-api-node/commit/7eb8b44caeeea6202f06c192759554a3883df345)) +* **mcp:** add typescript check to code execution tool ([b2b1ac1](https://github.com/Finch-API/finch-api-node/commit/b2b1ac16388edf736837f722fb700c3079bda946)) +* **mcp:** enable optional code execution tool on http mcp servers ([ace64da](https://github.com/Finch-API/finch-api-node/commit/ace64daf797895abdf52bada50f0cfff2b44c146)) +* **mcp:** handle code mode calls in the Stainless API ([e3777b8](https://github.com/Finch-API/finch-api-node/commit/e3777b8a92ed7971bf7fcbb2f4754638457a6494)) +* **mcp:** return logs on code tool errors ([7b37d8f](https://github.com/Finch-API/finch-api-node/commit/7b37d8f9cbca8d13dc09f894da08d6cb76487e74)) + + +### Bug Fixes + +* **api:** migrate custom code to TypeScript ([ec5d7fd](https://github.com/Finch-API/finch-api-node/commit/ec5d7fd9792c1a95be18f099764996796c949bb9)) +* **api:** resolve build issues ([f6be917](https://github.com/Finch-API/finch-api-node/commit/f6be917b1e7116cf84b575166d59e332390eb904)) +* **java:** Resolve name collisions ([7866332](https://github.com/Finch-API/finch-api-node/commit/7866332384999fb7dd718cf04e1e767946b400db)) +* **mcp:** add client instantiation options to code tool ([9cdaea8](https://github.com/Finch-API/finch-api-node/commit/9cdaea8d601afff676330e8eddc8fb4abb02cee3)) +* **mcp:** correct code tool API endpoint ([6d91f55](https://github.com/Finch-API/finch-api-node/commit/6d91f5522082891016109942b7501946d01153fd)) +* **mcp:** return correct lines on typescript errors ([41e296a](https://github.com/Finch-API/finch-api-node/commit/41e296a4d57b19346d88236a530b908f9c6f4f02)) +* **mcp:** return tool execution error on api error ([6059d9c](https://github.com/Finch-API/finch-api-node/commit/6059d9c53093f4da1c8bede00dffd4e27209c691)) +* **mcp:** return tool execution error on jq failure ([738e75d](https://github.com/Finch-API/finch-api-node/commit/738e75d5e4be2a4a2f4819941fc3af918094e9e5)) +* **tests:** fix tests ([eee03b0](https://github.com/Finch-API/finch-api-node/commit/eee03b0f219aead8d6c7ab4e743ed3cf66f8e9e5)) + + +### Chores + +* **client:** fix logger property type ([c3f828d](https://github.com/Finch-API/finch-api-node/commit/c3f828db47683dac2852c85f710c145f0efc95cf)) +* **internal:** codegen related update ([a361eb9](https://github.com/Finch-API/finch-api-node/commit/a361eb9698f2f3f76f2f02c76d8d44b132a1787e)) +* **internal:** codegen related update ([0f70da6](https://github.com/Finch-API/finch-api-node/commit/0f70da66ed3b1d7b2be256c25298a788baed4847)) +* **internal:** codegen related update ([a39b040](https://github.com/Finch-API/finch-api-node/commit/a39b0402570ec2e64a4950efb55726c33cbf6ce3)) +* **internal:** codegen related update ([60aa66a](https://github.com/Finch-API/finch-api-node/commit/60aa66a135d99299976fb2c59ac8d83f98d6841c)) +* **internal:** upgrade eslint ([1707b51](https://github.com/Finch-API/finch-api-node/commit/1707b51aab5ad912d7210f6dc3b142eea1b2d749)) +* mcp code tool explicit error message when missing a run function ([c43cbb2](https://github.com/Finch-API/finch-api-node/commit/c43cbb234a5044f21866998cacf9f658cc55de49)) +* **mcp:** add friendlier MCP code tool errors on incorrect method invocations ([6b20464](https://github.com/Finch-API/finch-api-node/commit/6b20464cdcf9d407bb22ac7a65a0a40732130321)) +* **mcp:** add line numbers to code tool errors ([3289a2b](https://github.com/Finch-API/finch-api-node/commit/3289a2b9e29e2d7298f096fcbae9237e28bce198)) +* **mcp:** clarify http auth error ([b028908](https://github.com/Finch-API/finch-api-node/commit/b02890809d93b7496cc2deeb9f0547d5b180cd06)) +* **mcp:** update lockfile ([88a4eac](https://github.com/Finch-API/finch-api-node/commit/88a4eacf9b9276a7b3db8a8dc9f12a0053251829)) +* **mcp:** upgrade jq-web ([8531130](https://github.com/Finch-API/finch-api-node/commit/8531130ea2dfcd42b747357d9c0f13dea4e933c5)) +* sync repo ([f38fc15](https://github.com/Finch-API/finch-api-node/commit/f38fc15fccf7f65422fc9cd62aea41fb143b36ed)) +* use latest @modelcontextprotocol/sdk ([b29ebd1](https://github.com/Finch-API/finch-api-node/commit/b29ebd1cc574733a9443ad9937e8e1b7e0f5d0a1)) + + +### Documentation + +* **mcp:** add a README button for one-click add to Cursor ([99e438c](https://github.com/Finch-API/finch-api-node/commit/99e438ca85071e011f30e93f7b5f67628bd0695c)) +* **mcp:** add a README link to add server to VS Code or Claude Code ([7a5ecb9](https://github.com/Finch-API/finch-api-node/commit/7a5ecb92bc7575e51fd053e3d6796f0b1b06dc6f)) + ## 6.38.0 (2025-10-27) Full Changelog: [v6.37.0...v6.38.0](https://github.com/Finch-API/finch-api-node/compare/v6.37.0...v6.38.0) diff --git a/package.json b/package.json index 6fc4a4e53..2cd579786 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tryfinch/finch-api", - "version": "6.38.0", + "version": "7.0.0", "description": "The official TypeScript library for the Finch API", "author": "Finch ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index e828be244..b326ec838 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@tryfinch/finch-api-mcp", - "version": "6.38.0", + "version": "7.0.0", "description": "The official MCP Server for the Finch API", "author": "Finch ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 5be6df535..f41433991 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -34,7 +34,7 @@ export const newMcpServer = () => new McpServer( { name: 'tryfinch_finch_api_api', - version: '6.38.0', + version: '7.0.0', }, { capabilities: { tools: {}, logging: {} } }, ); diff --git a/src/version.ts b/src/version.ts index 1422fd54f..e1f023d32 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '6.38.0'; // x-release-please-version +export const VERSION = '7.0.0'; // x-release-please-version