diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 60f0e7a3..00000000 --- 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 f1ca7188..791fb1bc 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/.release-please-manifest.json b/.release-please-manifest.json index 42cf93a6..aeda91d8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "6.39.0" + ".": "7.0.0" } diff --git a/.stats.yml b/.stats.yml index 6dc87a14..c27288ed 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 +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/CHANGELOG.md b/CHANGELOG.md index 6b5563bb..0c137e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,34 +1,45 @@ # Changelog -## 6.39.0 (2025-11-03) +## 7.0.0 (2025-11-25) -Full Changelog: [v6.38.2...v6.39.0](https://github.com/Finch-API/finch-api-node/compare/v6.38.2...v6.39.0) +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 ([b6892f5](https://github.com/Finch-API/finch-api-node/commit/b6892f5c579a0eeb8fa03863d9a72faa36adab9d)) +* **api:** api update ([7a322ff](https://github.com/Finch-API/finch-api-node/commit/7a322ff5740ab28666e66cfd1c0fc2cde2bb1f9e)) +* **api:** api update ([bd84e9e](https://github.com/Finch-API/finch-api-node/commit/bd84e9e2a16ba20cb6b326649a19d88814f40654)) +* **api:** api update ([d77cdb9](https://github.com/Finch-API/finch-api-node/commit/d77cdb9385c1d1f38be3cdc248328a7c322e6ed0)) +* **api:** manual updates ([27ef0ad](https://github.com/Finch-API/finch-api-node/commit/27ef0ad2f76b9b1d6f1ff90595304e0ab5c6a74b)) +* **api:** move node to typescript generator ([b575b75](https://github.com/Finch-API/finch-api-node/commit/b575b75d10ab94293c6e3e79d11ad8c7fd268dde)) +* **api:** update automated code reviewer selection ([b35dc3d](https://github.com/Finch-API/finch-api-node/commit/b35dc3d2dae4a30ecc8904f387e102be6b13bfea)) +* **mcp:** enable optional code execution tool on http mcp servers ([d1be429](https://github.com/Finch-API/finch-api-node/commit/d1be4290729cbe5c028c5f51196db51b9471484a)) -### 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)) +### Bug Fixes -## 6.38.2 (2025-10-31) +* **api:** migrate custom code to TypeScript ([ff51914](https://github.com/Finch-API/finch-api-node/commit/ff519145c9926660213fcb0e5455fd2a99a23212)) +* **api:** resolve build issues ([0de2504](https://github.com/Finch-API/finch-api-node/commit/0de25044cbba6da6ecefc62ff0459a5f4eac28c8)) +* **java:** Resolve name collisions ([b73e60a](https://github.com/Finch-API/finch-api-node/commit/b73e60ab8f1595c3b5eaf165e1b46e18a64f1f13)) +* **mcp:** return tool execution error on jq failure ([48f0290](https://github.com/Finch-API/finch-api-node/commit/48f0290be983b021e7139b9233d54d0c0cc56f43)) +* **tests:** fix tests ([278e1c6](https://github.com/Finch-API/finch-api-node/commit/278e1c675c50c0c1237c7720d3047b2ec6ec036f)) -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)) +* **internal:** codegen related update ([e3aa0c0](https://github.com/Finch-API/finch-api-node/commit/e3aa0c0c5f8b6321586cf2352178a54b8f41e73b)) +* **internal:** codegen related update ([af00e6d](https://github.com/Finch-API/finch-api-node/commit/af00e6df5750a02bda57e71e09f868f73747702e)) +* mcp code tool explicit error message when missing a run function ([1eccc66](https://github.com/Finch-API/finch-api-node/commit/1eccc660caf22e56268e6a53b829f103b8fb2544)) +* **mcp:** add friendlier MCP code tool errors on incorrect method invocations ([6a8308c](https://github.com/Finch-API/finch-api-node/commit/6a8308c732c7492a1aa66e57d34332c08d8c6955)) +* **mcp:** add line numbers to code tool errors ([14714f0](https://github.com/Finch-API/finch-api-node/commit/14714f0a3bb496bd08f566021743cabcdaccb37c)) +* **mcp:** clarify http auth error ([2aec61d](https://github.com/Finch-API/finch-api-node/commit/2aec61d543405f6fcfd3a7ab8b9f714a48eec6b1)) +* **mcp:** upgrade jq-web ([11f353e](https://github.com/Finch-API/finch-api-node/commit/11f353e8da02e1e071222d5f8bd6fb7b65d73e44)) +* sync repo ([26bfae7](https://github.com/Finch-API/finch-api-node/commit/26bfae7c26a1a09b0f56e513657b0a2aa4f98b54)) -## 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 +### Documentation -* **mcpb:** pin @anthropic-ai/mcpb version ([77d0632](https://github.com/Finch-API/finch-api-node/commit/77d0632d886fee1d2358b723de7b9585dc2db6b6)) +* **mcp:** add a README button for one-click add to Cursor ([0003fb4](https://github.com/Finch-API/finch-api-node/commit/0003fb4138b1166bcb492ae3f9339792ca9c60ec)) +* **mcp:** add a README link to add server to VS Code or Claude Code ([25bbf95](https://github.com/Finch-API/finch-api-node/commit/25bbf952e28a758419ddd1ae54e64071b19f1b2f)) ## 6.38.0 (2025-10-27) diff --git a/CODEOWNERS b/CODEOWNERS index 3b10bb3e..4c449e5e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @Finch-API/team-platform +* @Finch-API/team-data diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 00000000..c90ed0ae --- /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/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/Finch-API/finch-api-node#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 dc8e3c7a..76c0579a 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. @@ -205,8 +205,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 +225,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 +302,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 +323,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: -### Logging and middleware +```ts +import Finch from '@tryfinch/finch-api'; +import fetch from 'my-fetch'; -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: +const client = new Finch({ fetch }); +``` + +### Fetch options + +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 + +To modify proxy behavior, you can provide custom `fetchOptions` that add runtime-specific proxy +options to requests: -### Configuring an HTTP(S) Agent (e.g., for proxies) + **Node** [[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)] -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. +```ts +import Finch from '@tryfinch/finch-api'; +import * as undici from 'undici'; -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: +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: @@ -343,11 +416,11 @@ We are keen for your feedback; please open an [issue](https://www.github.com/Fin ## 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 4395a45a..40b2f274 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/cli b/bin/cli new file mode 100755 index 00000000..38f15a1d --- /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 00000000..058ea340 --- /dev/null +++ b/bin/migration-config.json @@ -0,0 +1,6 @@ +{ + "pkg": "@tryfinch/finch-api", + "githubRepo": "https://github.com/Finch-API/finch-api-node", + "clientClass": "Finch", + "methods": [] +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..8dff6305 --- /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 045e4de7..4f1313f6 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 8652fcbd..b5687858 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tryfinch/finch-api", - "version": "6.39.0", + "version": "7.0.0", "description": "The official TypeScript library for the Finch API", "author": "Finch ", "types": "dist/index.d.ts", @@ -20,104 +20,55 @@ "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" + "@noble/hashes": "^1.5.0" }, "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 647e03e8..5d51973f 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -1,4 +1,4 @@ -# Finch Node MCP Server +# Finch TypeScript MCP Server It is generated with [Stainless](https://www.stainless.com/). @@ -40,12 +40,36 @@ 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) + +### 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 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 @@ -80,6 +104,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. @@ -141,7 +177,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/package.json b/packages/mcp-server/package.json index 4c55b951..f6aef403 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": "7.0.0", "description": "The official MCP Server for the Finch API", "author": "Finch ", "types": "dist/index.d.ts", @@ -36,8 +36,10 @@ "@valtown/deno-http-worker": "^0.0.21", "cors": "^2.8.5", "express": "^5.1.0", - "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", + "fuse.js": "^7.1.0", + "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", "zod": "^3.25.20", "zod-to-json-schema": "^3.24.5", @@ -64,8 +66,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 b82608a4..c2d1dc70 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -1,11 +1,210 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 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 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 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; + 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) { + 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, }); @@ -22,21 +221,17 @@ const fetch = async (req: Request): Promise => { }; try { let run_ = async (client: any) => {}; - eval(` - ${code} - run_ = run; - `); - const result = await run_(client); + eval(`${code}\nrun_ = run;`); + const result = await run_(makeSdkProxy(client, { path: ['client'] })); return Response.json({ result, logLines, 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' }, ); diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index c4c9814a..7babc168 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 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' } } }, }; @@ -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 3e4e9fb9..abd09c0b 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/filtering.ts b/packages/mcp-server/src/filtering.ts index 1aa9a40c..eaae0fcf 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/headers.ts b/packages/mcp-server/src/headers.ts index ad38ca5d..e127d4e8 100644 --- a/packages/mcp-server/src/headers.ts +++ b/packages/mcp-server/src/headers.ts @@ -13,15 +13,17 @@ 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: - throw new Error(`Unsupported authorization scheme`); + throw new Error( + 'Unsupported authorization scheme. Expected the "Authorization" header to be a supported scheme (Bearer, Basic).', + ); } } - 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 +31,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/http.ts b/packages/mcp-server/src/http.ts index ec34ab47..84517003 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; diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts index 4fe3b987..b6ff5976 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/src/server.ts b/packages/mcp-server/src/server.ts index feff6664..f4143399 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: '7.0.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/access-tokens/create-access-tokens.ts b/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts index 78e30059..362ff584 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 7e65c7d3..fbfa74c5 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 f826f4bf..3cb5d9d4 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'; @@ -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: { @@ -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 dcc65e40..7b4d7a54 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 15b21a9a..6bbef87a 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 dd236291..6d2556dc 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 0df297ac..7655faca 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 86a7f2bb..567c2892 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 4613572d..2ae53758 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'; @@ -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: { @@ -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 6efef578..2217a5ea 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 a2524e0f..7e62ae28 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 323e8f5f..eb2692e7 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 3ba7ef8c..9fae4a7e 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 6175fc5b..b3f26f73 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 00fee1e4..55b9b1d8 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 c4e51c45..33df5529 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 1211f96a..021b22be 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 24e345b5..b4b62db3 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 80f2e66d..07fd3a13 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 234a6fc8..5f129a6f 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 0c6ca02b..8b4ea1c9 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 edd287c3..e9063123 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 bfb0d107..6e07907e 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 c42ab6e0..2e2bdeeb 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 4f4163fc..b90e1a48 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 efd870cb..b66dee36 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 dde71026..71544163 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 212feeae..456b9e36 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 39670f06..9557ab63 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 e76086bc..9a2cf6f3 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 be62e8bf..0a270fbf 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 70e47a5d..3a18de80 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 4407f890..cf376249 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'; @@ -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: { @@ -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 e46a0642..8d3645b2 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 e330b866..f13682ad 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 0db70e8e..99847b2e 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 a87e34ff..d13d1a61 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 d0b01c8d..03805678 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,9 +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['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 6e57f5dd..cc9f2af8 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 c91dcfcd..f50cd3aa 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 cc6471dc..93314437 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 897d9b88..15e0f2a7 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 fcfb613d..b8485d97 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'; @@ -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', @@ -209,7 +211,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 d55df0d1..317283d9 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 { 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', () => { diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock index 966d0575..ddf11f38 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" @@ -2494,9 +2747,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" @@ -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/release-please-config.json b/release-please-config.json index 8204c04c..b1909804 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", diff --git a/scripts/bootstrap b/scripts/bootstrap index f68bedac..a8b69ff3 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 58327f89..4a87eb89 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 8a8e9d59..53721ac0 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 a6bb9d03..7a756401 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 6ba75dfb..3ffb78a6 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 00000000..b3477c0e --- /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 72b0b8fd..e5e10b3e 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 457ad07a..deae575e 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 84cc4286..3e728015 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 af3b1118..00000000 --- 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 f28b5d81..00000000 --- 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 e053254b..00000000 --- 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 62b7a39e..00000000 --- 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 0ae2216f..00000000 --- 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 62b7a39e..00000000 --- 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 226fb15a..00000000 --- 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 2625a8b7..00000000 --- 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 d7755070..00000000 --- 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 ddbdb799..00000000 --- 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 ddbdb799..00000000 --- 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 8d5aaab0..00000000 --- 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 d3cdead4..00000000 --- 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 923dd3a6..00000000 --- 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 38cdaf9c..00000000 --- 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 67f444f7..00000000 --- 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 693c6b9a..00000000 --- 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 ddbdb799..00000000 --- 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 ddbdb799..00000000 --- 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 ab9f2ab5..00000000 --- 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 c159e5fa..00000000 --- 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 ddbdb799..00000000 --- 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 ddbdb799..00000000 --- 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 dc06ba11..00000000 --- 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 8261f7f0..00000000 --- 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 4ff35138..00000000 --- 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 ddbdb799..00000000 --- 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 ddbdb799..00000000 --- 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 00000000..8c775ee6 --- /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 00000000..35d59aac --- /dev/null +++ b/src/client.ts @@ -0,0 +1,967 @@ +// 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); + + /** + * 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; +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 4eebc18a..00000000 --- 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 00000000..485fce86 --- /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 00000000..a3d3ebef --- /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 00000000..e969b5f4 --- /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 00000000..475ff532 --- /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 00000000..29e3943d --- /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 00000000..2882ca6d --- /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 44c6bd24..fc55f46c 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 000fafb6..ad0e5450 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,453 +1,11 @@ // 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; - - /** - * 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, @@ -462,6 +20,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 00000000..3ef5a25b --- /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 00000000..c23d3bde --- /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 00000000..e82d95c9 --- /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 00000000..82c7b14d --- /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 00000000..6d9e48dc --- /dev/null +++ b/src/internal/headers.ts @@ -0,0 +1,144 @@ +// 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; +}; + +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/internal/parse.ts b/src/internal/parse.ts new file mode 100644 index 00000000..19d4f9db --- /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 1cf9e2cd..e76a742f 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 67497561..7e71387f 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 113b18fb..4cd56579 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 00000000..2aabf9aa --- /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 00000000..8ddf7b0a --- /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 00000000..5c6104af --- /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 00000000..30eada32 --- /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 00000000..b668dfc0 --- /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 00000000..a85a3e58 --- /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 00000000..3cbfacce --- /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 00000000..1ade99da --- /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 00000000..8da627ab --- /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 00000000..2d848007 --- /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 00000000..55eb21e3 --- /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 00000000..a57c7c34 --- /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 00000000..65e52962 --- /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 00000000..b0e53aaf --- /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 00000000..9f053eb2 --- /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 3aed3e41..90bf015e 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 673f09a7..363e3516 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 586f6ff0..7c4d6884 100644 --- a/src/resources/access-tokens.ts +++ b/src/resources/access-tokens.ts @@ -1,17 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../resource'; -import * as Core from '../core'; +import type { RequestOptions } from '../internal/request-options'; +import type { APIPromise } from '../core/api-promise'; +import { APIResource } from '../core/resource'; 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/account.ts b/src/resources/account.ts index c6ff9124..68ddd577 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); } } @@ -179,11 +180,6 @@ export namespace Introspection { * The source ID of the entity */ source_id: string | null; - - /** - * The type of entity - */ - type: string | null; } } diff --git a/src/resources/connect/connect.ts b/src/resources/connect/connect.ts index d0cb8b42..876dad04 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 845de320..b56c9a0c 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 3024393b..82a088d7 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 eb37c409..b754e6cb 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 b269ddd1..0f631923 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; @@ -177,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; /** @@ -194,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'; } @@ -219,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'; } @@ -239,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'; } @@ -412,15 +383,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 6a40f600..506bf6b0 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 fb849643..94a550df 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 21d1080e..e0aab289 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 af89e364..f2d8248a 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 d8fb1de7..cd842d79 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 237ed3d6..7bdc13f3 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 4251336a..ad028ada 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 420b1ff1..ec84cd58 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 ea5ab6ca..4ecf085c 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 5fddd3d6..391e5a6b 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 664af0b8..e32b95a9 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 b5be191b..40b011c3 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 b7e63d8d..cb68fa1f 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 85c4c42d..ff93bb94 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 9e7a3a0f..bd50765c 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 7cc64d28..4daaed61 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 07f4b6d5..ac5268d7 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 00580f17..23896339 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 c8704bf1..c8153560 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 fbb7c3e4..b7ebad3a 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 175f23aa..0299ebad 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 6bc5fcbf..f2794345 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 }); } } @@ -70,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. @@ -99,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/src/resources/sandbox/company.ts b/src/resources/sandbox/company.ts index 4bc56393..ab493267 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 22beec25..7c22ed00 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 1fc36baa..478b6d76 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 22a0c2ee..78c79d19 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 e2bdfad3..c770ddae 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 9e296970..19a333d0 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 ed5de651..81933301 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 3eea8bcd..9b8ad1a2 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 cf572792..bc49d691 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 }); } } @@ -99,6 +94,9 @@ export namespace PaymentCreateParams { export interface EmployeeDeduction { amount?: number; + /** + * The deduction name. Required when type is specified. + */ name?: string; pre_tax?: boolean; @@ -128,6 +126,9 @@ export namespace PaymentCreateParams { export interface EmployerContribution { amount?: number; + /** + * The contribution name. Required when type is specified. + */ name?: string; type?: diff --git a/src/resources/sandbox/sandbox.ts b/src/resources/sandbox/sandbox.ts index 9e230ca4..4390b7eb 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 fffd2d27..19a4c592 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -1,9 +1,11 @@ // 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'; +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/src/shims/node.ts b/src/shims/node.ts deleted file mode 100644 index 73df5600..00000000 --- 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 f72d7844..00000000 --- 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 8fd2154d..b2ef6471 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 b4492697..e1f023d3 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '6.39.0'; // x-release-please-version +export const VERSION = '7.0.0'; // x-release-please-version diff --git a/tests/api-resources/access-tokens.test.ts b/tests/api-resources/access-tokens.test.ts index fe0433de..e0265ba8 100644 --- a/tests/api-resources/access-tokens.test.ts +++ b/tests/api-resources/access-tokens.test.ts @@ -1,11 +1,10 @@ // 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', - 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/account.test.ts b/tests/api-resources/account.test.ts index cce65e15..e9f1bda2 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 773a7554..38043b91 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 f58ceb11..c9d3a7dc 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 22c98808..b148b105 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 38e056de..491581e1 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 839efc54..3cbc8bad 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 1c171c20..af4708c0 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 045deca1..92edf347 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 b17cf345..dcb27c1f 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 c264064e..9b5d4b89 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 8aa5239b..e0449a67 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 03de3163..8b9cd772 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 f083b49c..f2c5d1cd 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 a199923f..64e3288b 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 c1912928..a12570d2 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 b42df448..0839c4cc 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 a9f88956..3ed1bb1e 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 cdc8b714..35b9e403 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', @@ -25,8 +24,8 @@ describe('resource requestForwarding', () => { method: 'method', route: 'route', data: 'data', - headers: { foo: 'bar' }, params: { foo: 'bar' }, + request_headers: { foo: 'bar' }, }); }); }); diff --git a/tests/api-resources/sandbox/company.test.ts b/tests/api-resources/sandbox/company.test.ts index f24175ac..5c0427ba 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 51fcbe24..8772382d 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 3c4b8809..65ef52fd 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 b84558f9..04af5d12 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 6c5239b1..654bc3e5 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 2aca124f..1ecb821e 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 1ed2c8c6..0ff598d3 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 32b50c63..31aa67c0 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 575dbf28..8a669841 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/api-resources/top-level.test.ts b/tests/api-resources/top-level.test.ts index da5588c8..89d6257e 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 67888e03..688f9268 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/base64.test.ts b/tests/base64.test.ts new file mode 100644 index 00000000..a5cdcf87 --- /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 00000000..56115d91 --- /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/core.test.ts b/tests/core.test.ts index b05127e8..290dbc2e 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; diff --git a/tests/form.test.ts b/tests/form.test.ts index 31d59c7f..81c2f959 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 e765e131..33e714de 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 00000000..b0ca531d --- /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 3be17dd5..6636cee5 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 469e2b3f..00000000 --- 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 6f1a5f43..254e85dd 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 4facad5a..384ddac5 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 b5b9724d..1b087983 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 e9f2d70b..c550e299 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 6f28db23..fca72f91 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 a6966172..ea32d280 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" @@ -618,9 +694,9 @@ "@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== + 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" @@ -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" - -"@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== + undici-types "~5.26.4" -"@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==