diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 43fd5a7..b52ad04 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "features": { "ghcr.io/devcontainers/features/node:1": {} }, - "postCreateCommand": "yarn install", + "postCreateCommand": "corepack pnpm install --frozen-lockfile", "customizations": { "vscode": { "extensions": ["esbenp.prettier-vscode"] diff --git a/.github/actions/verify-sdk/action.yml b/.github/actions/verify-sdk/action.yml new file mode 100644 index 0000000..b5e2d75 --- /dev/null +++ b/.github/actions/verify-sdk/action.yml @@ -0,0 +1,19 @@ +name: Verify and pack SDK +description: Verify the SDK package and create the registry tarball + +runs: + using: composite + steps: + - name: Bootstrap + shell: bash + run: ./scripts/bootstrap --frozen-lockfile + + - name: Verify SDK package + shell: bash + run: pnpm test + + - name: Pack verified SDK + shell: bash + run: | + mkdir sdk-package + npm pack --silent --pack-destination sdk-package ./dist diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 746dabb..be3f388 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,88 +1,64 @@ name: CI on: push: - branches-ignore: - - 'generated' - - 'codegen/**' - - 'integrated/**' - - 'stl-preview-head/**' - - 'stl-preview-base/**' + branches: + - main pull_request: - branches-ignore: - - 'stl-preview-head/**' - - 'stl-preview-base/**' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - lint: + verify: timeout-minutes: 10 - name: lint - runs-on: ${{ github.repository == 'stainless-sdks/unlayer-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + name: verify (Node 24) + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Bootstrap - run: ./scripts/bootstrap - - - name: Check types - run: ./scripts/lint - - build: - timeout-minutes: 5 - name: build - runs-on: ${{ github.repository == 'stainless-sdks/unlayer-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork - permissions: - contents: read - id-token: write - steps: - - uses: actions/checkout@v6 + - name: Set up pnpm + uses: pnpm/action-setup@v6 - name: Set up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '24' + cache: 'pnpm' - - name: Bootstrap - run: ./scripts/bootstrap + - name: Verify and pack SDK + uses: ./.github/actions/verify-sdk - - name: Check build - run: ./scripts/build - - - name: Get GitHub OIDC Token - if: github.repository == 'stainless-sdks/unlayer-typescript' - id: github-oidc - uses: actions/github-script@v8 + - name: Upload verified SDK + uses: actions/upload-artifact@v4 with: - script: core.setOutput('github_token', await core.getIDToken()); + name: sdk-package + path: sdk-package/*.tgz + retention-days: 1 - - name: Upload tarball - if: github.repository == 'stainless-sdks/unlayer-typescript' - env: - URL: https://pkg.stainless.com/s - AUTH: ${{ steps.github-oidc.outputs.github_token }} - SHA: ${{ github.sha }} - run: ./scripts/utils/upload-artifact.sh - test: - timeout-minutes: 10 - name: test - runs-on: ${{ github.repository == 'stainless-sdks/unlayer-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + runtime: + timeout-minutes: 5 + name: runtime (Node 20) + needs: verify + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: '20' + package-manager-cache: false - - name: Bootstrap - run: ./scripts/bootstrap + - name: Download verified SDK + uses: actions/download-artifact@v4 + with: + name: sdk-package + path: sdk-package - - name: Run tests - run: ./scripts/test + - name: Verify packed SDK runtime + run: ./scripts/test-package-archive sdk-package/*.tgz tests/packed-sdk-smoke.mjs diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 8d3b32c..a6a900b 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -1,32 +1,88 @@ -# This workflow is triggered when a GitHub release is created. -# It can also be run manually to re-publish to NPM in case it failed for some reason. -# You can run this workflow by navigating to https://www.github.com/unlayer/unlayer-typescript/actions/workflows/publish-npm.yml +# Dispatched at the release tag after Release Please creates a GitHub release. +# It can also be run manually at that tag to retry a failed publish. name: Publish NPM on: workflow_dispatch: - release: - types: [published] +permissions: + contents: read jobs: - publish: - name: publish + verify: + timeout-minutes: 10 + name: verify (Node 24) runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Set up pnpm + uses: pnpm/action-setup@v6 + + - name: Set up Node + uses: actions/setup-node@v6 + with: + node-version: '24' + cache: 'pnpm' + + - name: Verify release tag + run: bash ./bin/check-release-ref + + - name: Verify and pack SDK + uses: ./.github/actions/verify-sdk + + - name: Upload verified SDK + uses: actions/upload-artifact@v4 + with: + name: sdk-package + path: sdk-package/*.tgz + retention-days: 1 + + runtime: + timeout-minutes: 5 + name: runtime (Node 20) + needs: verify + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v6 with: node-version: '20' + package-manager-cache: false - - name: Install dependencies - run: | - yarn install + - name: Download verified SDK + uses: actions/download-artifact@v4 + with: + name: sdk-package + path: sdk-package + + - name: Verify packed SDK runtime + run: ./scripts/test-package-archive sdk-package/*.tgz tests/packed-sdk-smoke.mjs + + publish: + timeout-minutes: 5 + name: publish + needs: runtime + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v6 + + - name: Set up Node + uses: actions/setup-node@v6 + with: + node-version: '24' + package-manager-cache: false + registry-url: 'https://registry.npmjs.org' + + - name: Download verified SDK + uses: actions/download-artifact@v4 + with: + name: sdk-package + path: sdk-package - name: Publish to NPM - run: | - bash ./bin/publish-npm - env: - NPM_TOKEN: ${{ secrets.UNLAYER_NPM_TOKEN || secrets.NPM_TOKEN }} + run: bash ./bin/publish-npm sdk-package/*.tgz diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 71339c4..173bae5 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -5,18 +5,23 @@ on: - main workflow_dispatch: +permissions: + contents: read + jobs: release_doctor: name: release doctor runs-on: ubuntu-latest - if: github.repository == 'unlayer/unlayer-typescript' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + if: github.repository == 'unlayer/unlayer-typescript' && (github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - uses: actions/checkout@v6 - - name: Check release environment - run: | - bash ./bin/check-release-environment - env: - NPM_TOKEN: ${{ secrets.UNLAYER_NPM_TOKEN || secrets.NPM_TOKEN }} + - name: Set up Node + uses: actions/setup-node@v6 + with: + node-version: '24' + package-manager-cache: false + - name: Check release environment + run: bash ./bin/check-release-environment diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fe1f284 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,43 @@ +name: Release +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + actions: write + contents: write + issues: write + pull-requests: write + +jobs: + release-please: + name: release please + runs-on: ubuntu-latest + + steps: + - name: Create or update release + id: release + uses: googleapis/release-please-action@v5 + with: + token: ${{ github.token }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + - name: Run CI for the release PR + if: steps.release.outputs.prs_created == 'true' + env: + GH_TOKEN: ${{ github.token }} + RELEASE_PR: ${{ steps.release.outputs.pr }} + run: | + branch=$(node -e "process.stdout.write(JSON.parse(process.env.RELEASE_PR).headBranchName)") + test -n "$branch" + gh workflow run ci.yml --ref "$branch" + + - name: Publish the created release + if: steps.release.outputs.release_created == 'true' + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ steps.release.outputs.tag_name }} + run: gh workflow run publish-npm.yml --ref "$RELEASE_TAG" diff --git a/.github/workflows/sync-openapi.yml b/.github/workflows/sync-openapi.yml new file mode 100644 index 0000000..4175952 --- /dev/null +++ b/.github/workflows/sync-openapi.yml @@ -0,0 +1,106 @@ +name: Sync OpenAPI + +on: + schedule: + - cron: '17 5 * * *' + workflow_dispatch: + +permissions: + actions: write + contents: write + pull-requests: write + +concurrency: + group: sync-openapi + cancel-in-progress: false + +jobs: + sync: + name: sync production API + if: github.repository == 'unlayer/unlayer-typescript' + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v6 + + - name: Set up Node + uses: actions/setup-node@v6 + with: + node-version: '24' + package-manager-cache: false + + - name: Download production OpenAPI document + run: node scripts/sync-openapi.cjs + + - name: Check for API changes + id: changes + shell: bash + run: | + if git diff --quiet -- openapi.json; then + echo 'changed=false' >> "$GITHUB_OUTPUT" + echo 'Production OpenAPI document is unchanged.' >> "$GITHUB_STEP_SUMMARY" + else + echo 'changed=true' >> "$GITHUB_OUTPUT" + fi + + - name: Set up pnpm + if: steps.changes.outputs.changed == 'true' + uses: pnpm/action-setup@v6 + + - name: Install dependencies + if: steps.changes.outputs.changed == 'true' + run: pnpm install --frozen-lockfile + + - name: Generate SDK + if: steps.changes.outputs.changed == 'true' + run: pnpm generate + + - name: Verify SDK + if: steps.changes.outputs.changed == 'true' + run: pnpm test + + - name: Reject unexpected changes + if: steps.changes.outputs.changed == 'true' + shell: bash + run: | + unexpected=$(git diff --name-only | grep -Ev '^(openapi\.json|src/)' || true) + if [ -n "$unexpected" ]; then + echo 'Generation changed files outside openapi.json and src/:' >&2 + echo "$unexpected" >&2 + exit 1 + fi + + - name: Open or update SDK PR + if: steps.changes.outputs.changed == 'true' + id: pull-request + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + token: ${{ github.token }} + add-paths: | + openapi.json + src + branch: automation/native-sdk-update + delete-branch: true + sign-commits: true + commit-message: 'feat(repo): regenerate SDK from the production OpenAPI document' + title: 'feat(repo): regenerate SDK from the production OpenAPI document' + body: | + This PR updates the committed production OpenAPI snapshot and its + matching generated SDK source. + + Generation uses the pinned Hey API toolchain and configuration from + this repository. The source under `src/` is generated and must not + be edited by hand. + + - name: Run CI for the generated branch + if: >- + steps.pull-request.outputs.pull-request-operation == 'created' || + steps.pull-request.outputs.pull-request-operation == 'updated' + env: + GH_TOKEN: ${{ github.token }} + run: gh workflow run ci.yml --ref automation/native-sdk-update + + - name: Write update summary + if: steps.pull-request.outputs.pull-request-url != '' + run: echo 'SDK update PR ${{ steps.pull-request.outputs.pull-request-url }}' >> "$GITHUB_STEP_SUMMARY" diff --git a/.gitignore b/.gitignore index 2412bb7..0976779 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .prism.log node_modules -yarn-error.log +pnpm-debug.log* codegen.log Brewfile.lock.json dist @@ -8,4 +8,3 @@ dist-deno /*.tgz .idea/ .eslintcache - diff --git a/.stats.yml b/.stats.yml deleted file mode 100644 index 2702d73..0000000 --- a/.stats.yml +++ /dev/null @@ -1,4 +0,0 @@ -configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/unlayer%2Funlayer-48f00d1c04c23fb4d1cb7cf4af4f56b0c920d758c1f06e06e5373e5b15e9c27d.yml -openapi_spec_hash: 6ee2a94bb9840aceb4a6161c724ce46c -config_hash: 249869757b6eb98ae3d58f2a47ce21e2 diff --git a/Brewfile b/Brewfile deleted file mode 100644 index e4feee6..0000000 --- a/Brewfile +++ /dev/null @@ -1 +0,0 @@ -brew "node" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 051f357..6ad2382 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,79 +1,94 @@ ## Setting up the environment -This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install). -Other package managers may work but are not officially supported for development. +This repository uses the pnpm version pinned in `package.json`. Development and +SDK generation require Node.js 22.18 or newer; the published package supports +Node.js 20 and newer. Enable Corepack before setup so the correct pnpm version +is selected automatically. To set up the repository, run: ```sh -$ yarn -$ yarn build +$ corepack enable +$ pnpm install +$ pnpm build ``` This will install all the required dependencies and build output files to `dist/`. -## Modifying/Adding code - -Most of the SDK is generated code. Modifications to code will be persisted between generations, but may -result in merge conflicts between manual patches and changes from the generator. The generator will never -modify the contents of the `src/lib/` and `examples/` directories. +## Why Hey API -## Adding and running examples +The SDK uses Hey API because it produces an idiomatic TypeScript Fetch client, +can run entirely from this repository, and adds no runtime dependency to the +published package. The generator, OpenAPI snapshot, configuration, and +postprocessing are all pinned and reviewable, so regeneration does not depend +on a hosted SDK-generation service. -All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. +OpenAPI Generator was considered for its broad language support, but Hey API's +TypeScript output and native Fetch surface require less package-specific +adaptation here. Hosted generators would retain an external control plane, and +custom or LLM-generated runtime code would create more maintenance than a +pinned open-source generator. This repository intentionally scopes generation +to the TypeScript package. -```ts -// add an example to examples/.ts +## Modifying/Adding code -#!/usr/bin/env -S npm run tsn -T -… -``` +The SDK source is generated with the pinned Hey API version and +`openapi-ts.config.ts` from the committed `openapi.json` snapshot. Run: ```sh -$ chmod +x examples/.ts -# run the example against your api -$ yarn tsn -T examples/.ts +$ pnpm generate ``` -## Using the repository from source +Generation replaces `src/` and then applies the small, fail-closed SDK contract +postprocessor. Do not edit generated source by hand. If Hey API changes its +generated request layout, the postprocessor stops instead of producing an SDK +with mismatched runtime and TypeScript behavior. -If you’d like to use the repository from source, you can either install from git or link to a cloned repository: - -To install via git: +To intentionally update the snapshot from the public production API document, +run both commands and review the specification and generated-source diffs: ```sh -$ npm install git+ssh://git@github.com:unlayer/unlayer-typescript.git +$ pnpm sync-spec +$ pnpm generate ``` -Alternatively, to link a local copy of the repo: +The sync command records the canonical API server in the local snapshot. Hey +previously inferred that origin from the remote document URL, while local-file +generation requires it explicitly. -```sh -# Clone -$ git clone https://www.github.com/unlayer/unlayer-typescript -$ cd unlayer-typescript +The `Sync OpenAPI` workflow performs this check once a day and can also be run +manually. When the production document changes, it regenerates and verifies the +SDK before opening or updating one automation PR. The workflow exits early when +the committed snapshot is already current. -# With yarn -$ yarn link -$ cd ../my-package -$ yarn link @unlayer/sdk +`pnpm test` regenerates the SDK from the committed snapshot and fails if the +checked-in source has drifted. -# With pnpm -$ pnpm link --global +## Testing an unreleased package + +Git URL dependencies are not supported because generated build output is not +committed to the repository. To test an unreleased revision, build and pack the +same package layout that the release workflow publishes: + +```sh +$ pnpm build +$ archive=$(npm pack --silent ./dist) $ cd ../my-package -$ pnpm link --global @unlayer/sdk +$ npm install "../unlayer-typescript/$archive" ``` -## Running tests +This exercises the package manifest and files that consumers receive from npm +instead of linking directly to repository internals. -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. +## Running tests ```sh -$ ./scripts/mock +$ pnpm test ``` -```sh -$ yarn run test -``` +This verifies source types and formatting, CommonJS and ESM builds, public +package exports, and the packed type surface. It also installs the package in an +isolated consumer and exercises its HTTP behavior. ## Linting and formatting @@ -83,25 +98,35 @@ This repository uses [prettier](https://www.npmjs.com/package/prettier) and To lint: ```sh -$ yarn lint +$ pnpm lint ``` To format and fix all lint issues automatically: ```sh -$ yarn fix +$ pnpm fix ``` ## Publishing and releases -Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If -the changes aren't made through the automated pipeline, you may want to make releases manually. +Release Please maintains the release PR. Merging that PR creates a GitHub +release and dispatches the `Publish NPM` workflow at the matching tag. Publishing +uses npm trusted publishing. The workflow runs full verification on Node.js 24, +tests the exact packed tarball on Node.js 20, and publishes that same artifact +only after both jobs pass. -### Publish with a GitHub workflow +The repository requires two one-time settings: -You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/unlayer/unlayer-typescript/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up. +- GitHub Actions must be allowed to create pull requests. The built-in, + short-lived `GITHUB_TOKEN` creates SDK update PRs, release PRs, tags, and + releases only within this repository. +- An npm trusted publisher for organization `unlayer`, repository + `unlayer-typescript`, workflow `publish-npm.yml`, with `npm publish` allowed. -### Publish manually +No GitHub App, personal access token, or long-lived npm token is required. +GitHub's OIDC token provides short-lived npm credentials and npm automatically +records package provenance. -If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on -the environment. +To retry a failed package release, manually run the +[`Publish NPM` workflow](https://github.com/unlayer/unlayer-typescript/actions/workflows/publish-npm.yml) +and select the matching `v` release tag. Branches cannot publish. diff --git a/README.md b/README.md index 4d6a2dd..b901555 100644 --- a/README.md +++ b/README.md @@ -1,402 +1,154 @@ -# Unlayer TypeScript API Library +# Unlayer TypeScript SDK -[![NPM version]()](https://npmjs.org/package/@unlayer/sdk) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@unlayer/sdk) +The official TypeScript SDK for the Unlayer API. It is generated directly from +Unlayer's API v3 OpenAPI document with [Hey API](https://heyapi.dev/) and uses +the native Fetch API. -This library provides convenient access to the Unlayer REST API from server-side TypeScript or JavaScript. - -The full API of this library can be found in [api.md](api.md). - -It is generated with [Stainless](https://www.stainless.com/). +> [!IMPORTANT] +> Use this SDK only from trusted server-side code. Never expose an API key or +> Personal Access Token in browser, mobile, or other client-side code. ## Installation -```sh +```bash npm install @unlayer/sdk ``` -## Usage - -The full API of this library can be found in [api.md](api.md). - - -```js -import Unlayer from '@unlayer/sdk'; - -const client = new Unlayer({ - apiKey: process.env['UNLAYER_API_KEY'], // This is the default and can be omitted -}); - -const page = await client.templates.list({ limit: 10, projectId: 'your-project-id' }); -const templateListResponse = page.data[0]; - -console.log(templateListResponse.id); -``` - -### Request & Response types - -This library includes TypeScript definitions for all request params and response fields. You may import and use them like so: - - -```ts -import Unlayer from '@unlayer/sdk'; - -const client = new Unlayer({ - apiKey: process.env['UNLAYER_API_KEY'], // This is the default and can be omitted -}); - -const params: Unlayer.TemplateListParams = { limit: 10, projectId: 'your-project-id' }; -const [templateListResponse]: [Unlayer.TemplateListResponse] = await client.templates.list(params); -``` - -Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors. +## Quick start -## Handling errors - -When the library is unable to connect to the API, -or if the API returns a non-success status code (i.e., 4xx or 5xx response), -a subclass of `APIError` will be thrown: - - ```ts -const page = await client.templates - .list({ limit: 10, projectId: 'your-project-id' }) - .catch(async (err) => { - if (err instanceof Unlayer.APIError) { - console.log(err.status); // 400 - console.log(err.name); // BadRequestError - console.log(err.headers); // {server: 'nginx', ...} - } else { - throw err; - } - }); -``` - -Error codes are as follows: - -| Status Code | Error Type | -| ----------- | -------------------------- | -| 400 | `BadRequestError` | -| 401 | `AuthenticationError` | -| 403 | `PermissionDeniedError` | -| 404 | `NotFoundError` | -| 422 | `UnprocessableEntityError` | -| 429 | `RateLimitError` | -| >=500 | `InternalServerError` | -| N/A | `APIConnectionError` | - -### Retries - -Certain errors will be automatically retried 2 times by default, with a short exponential backoff. -Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, -429 Rate Limit, and >=500 Internal errors will all be retried by default. +import { Unlayer } from '@unlayer/sdk'; +import { createClient } from '@unlayer/sdk/client'; -You can use the `maxRetries` option to configure or disable this: - - -```js -// Configure the default for all requests: -const client = new Unlayer({ - maxRetries: 0, // default is 2 -}); - -// Or, configure per-request: -await client.templates.list({ limit: 10, projectId: 'your-project-id' }, { - maxRetries: 5, +const unlayer = new Unlayer({ + client: createClient({ + auth: process.env['UNLAYER_API_KEY'], + }), }); -``` -### Timeouts - -Requests time out after 1 minute by default. You can configure this with a `timeout` option: - - -```ts -// Configure the default for all requests: -const client = new Unlayer({ - timeout: 20 * 1000, // 20 seconds (default is 1 minute) +const templates = await unlayer.templates.listTemplates({ + query: { limit: 20, projectId: 'your-project-id' }, }); -// Override per-request: -await client.templates.list({ limit: 10, projectId: 'your-project-id' }, { - timeout: 5 * 1000, +const template = await unlayer.templates.getTemplate({ + path: { id: 'template-id' }, }); ``` -On timeout, an `APIConnectionTimeoutError` is thrown. - -Note that requests which time out will be [retried twice by default](#retries). - -## Auto-pagination - -List methods in the Unlayer API are paginated. -You can use the `for await … of` syntax to iterate through items across all pages: - -```ts -async function fetchAllTemplateListResponses(params) { - const allTemplateListResponses = []; - // Automatically fetches more pages as needed. - for await (const templateListResponse of client.templates.list({ - limit: 10, - projectId: 'your-project-id', - })) { - allTemplateListResponses.push(templateListResponse); - } - return allTemplateListResponses; -} -``` - -Alternatively, you can request a single page at a time: +`Unlayer` requires an explicitly configured client. SDK instances do not use a +shared registry or shared credentials. -```ts -let page = await client.templates.list({ limit: 10, projectId: 'your-project-id' }); -for (const templateListResponse of page.data) { - console.log(templateListResponse); -} - -// Convenience methods are provided for manually paginating: -while (page.hasNextPage()) { - page = await page.getNextPage(); - // ... -} -``` - -## Advanced Usage - -### 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 -const client = new Unlayer(); - -const response = await client.templates - .list({ limit: 10, projectId: 'your-project-id' }) - .asResponse(); -console.log(response.headers.get('X-My-Header')); -console.log(response.statusText); // access the underlying Response object - -const { data: page, response: raw } = await client.templates - .list({ limit: 10, projectId: 'your-project-id' }) - .withResponse(); -console.log(raw.headers.get('X-My-Header')); -for await (const templateListResponse of page) { - console.log(templateListResponse.id); -} -``` +SDK operations throw the parsed API error body by default. Generated error-body +types are exported, but caught values should still be narrowed at runtime. +API failures throw the parsed body rather than an `Error` instance; network +failures retain the native Fetch error behavior. -### Logging - -> [!IMPORTANT] -> All log messages are intended for debugging only. The format and content of log messages -> may change between releases. +## Native API shape -#### Log levels - -The log level can be configured in two ways: - -1. Via the `UNLAYER_LOG` environment variable -2. Using the `logLevel` client option (overrides the environment variable if set) +Operations are grouped by their API resource. Parameters use Hey API's native +`path`, `query`, and `body` groups: ```ts -import Unlayer from '@unlayer/sdk'; - -const client = new Unlayer({ - 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 Unlayer from '@unlayer/sdk'; -import pino from 'pino'; - -const logger = pino(); - -const client = new Unlayer({ - logger: logger.child({ name: 'Unlayer' }), - logLevel: 'debug', // Send all messages to pino, allowing it to filter +await unlayer.templates.convertFullToSimple({ + body: { design }, }); ``` -### Making custom/undocumented requests +Types for request parameters, successful responses, and error responses are +exported from `@unlayer/sdk`. Resource methods always return the data-only +response shape and always throw on failures so HTTP, network, abort, URL, and +response parsing errors cannot be mistaken for missing data. The lower-level +client remains available from `@unlayer/sdk/client` for callers that need Hey +API's native field response or non-throwing behavior. -This library is typed for convenient access to the documented API. If you need to access undocumented -endpoints, params, or response properties, the library can still be used. - -#### Undocumented endpoints - -To make requests to undocumented endpoints, you can use `client.get`, `client.post`, and other HTTP verbs. -Options on the client, such as retries, will be respected when making these requests. +To override the API URL, Fetch implementation, headers, or other native client +options, pass them to `createClient()`: ```ts -await client.post('/some/path', { - body: { some_prop: 'foo' }, - query: { some_query_arg: 'bar' }, +const client = createClient({ + auth: process.env['UNLAYER_API_KEY'], + fetch: customFetch, + headers: { 'X-Request-ID': requestId }, }); -``` - -#### Undocumented request params -To make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented -parameter. This library doesn't validate at runtime that the request matches the type, so any extra values you -send will be sent as-is. - -```ts -client.templates.list({ - // ... - // @ts-expect-error baz is not yet public - baz: 'undocumented option', -}); +const unlayer = new Unlayer({ client }); ``` -For requests with the `GET` verb, any extra params will be in the query, all other requests will send the -extra param in the body. +`createClient()` defaults to `https://api.unlayer.com` and throwing on errors. +Pass `baseUrl` or `throwOnError` only when intentionally overriding those +defaults. -If you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request -options. +Each `Unlayer` instance can receive its own client, so credentials and runtime +configuration remain isolated. -#### Undocumented response properties +## Migrating from 0.1 -To access undocumented response properties, you may access the response object with `// @ts-expect-error` on -the response object, or cast the response object to the requisite type. Like the request params, we do not -validate or strip extra properties from the response from the API. - -### Customizing the fetch client - -By default, this library expects a global `fetch` function is defined. - -If you want to use a different `fetch` function, you can either polyfill the global: - -```ts -import fetch from 'my-fetch'; - -globalThis.fetch = fetch; -``` - -Or pass it to the client: +Version 0.2 replaces the generated wrapper runtime with the native Hey API +client. Construct a client explicitly and pass it to `Unlayer`: ```ts +// 0.1 import Unlayer from '@unlayer/sdk'; -import fetch from 'my-fetch'; -const client = new Unlayer({ fetch }); +const unlayer = new Unlayer({ apiKey: process.env['UNLAYER_API_KEY'] }); +await unlayer.templates.list({ limit: 20, projectId: 'your-project-id' }); ``` -### 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 Unlayer from '@unlayer/sdk'; +// 0.2+ +import { Unlayer } from '@unlayer/sdk'; +import { createClient } from '@unlayer/sdk/client'; -const client = new Unlayer({ - fetchOptions: { - // `RequestInit` options - }, +const unlayer = new Unlayer({ + client: createClient({ + auth: process.env['UNLAYER_API_KEY'], + }), }); -``` - -#### Configuring proxies - -To modify proxy behavior, you can provide custom `fetchOptions` that add runtime-specific proxy -options to requests: - **Node** [[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)] - -```ts -import Unlayer from '@unlayer/sdk'; -import * as undici from 'undici'; - -const proxyAgent = new undici.ProxyAgent('http://localhost:8888'); -const client = new Unlayer({ - fetchOptions: { - dispatcher: proxyAgent, - }, +await unlayer.templates.listTemplates({ + query: { limit: 20, projectId: 'your-project-id' }, }); ``` - **Bun** [[docs](https://bun.sh/guides/http/proxy)] +Operation names now match the OpenAPI operation IDs, and parameters are grouped +under `path`, `query`, and `body`. The default export, implicit environment +configuration, custom error hierarchy, retries, timeouts, and pagination +helpers from 0.1 are no longer part of the SDK. API errors are parsed bodies, +not `Error` subclasses, and do not carry response status or headers. Configure +retry behavior with a custom Fetch implementation and timeouts with a request +signal when needed: ```ts -import Unlayer from '@unlayer/sdk'; - -const client = new Unlayer({ - fetchOptions: { - proxy: 'http://localhost:8888', - }, +await unlayer.templates.listTemplates({ + query: { projectId: 'your-project-id' }, + signal: AbortSignal.timeout(60_000), }); ``` - **Deno** [[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)] +Git URL dependencies are no longer supported because generated build output is +not committed. To test an unreleased revision, clone it and install the packed +`dist` archive as described in `CONTRIBUTING.md`. -```ts -import Unlayer from 'npm:@unlayer/sdk'; +## Development -const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } }); -const client = new Unlayer({ - 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: - -1. Changes that only affect static types, without breaking runtime behavior. -2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ -3. Changes that we do not expect to impact the vast majority of users in practice. - -We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. +The generated files under `src/` come from the committed public OpenAPI snapshot +and the pinned configuration in `openapi-ts.config.ts`. Do not edit generated +files directly. Regenerate them with Node.js 22.18 or newer: -We are keen for your feedback; please open an [issue](https://www.github.com/unlayer/unlayer-typescript/issues) with questions, bugs, or suggestions. - -## Requirements - -TypeScript >= 4.9 is supported. - -The following runtimes are supported: - -- Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more) -- 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. -- Vercel Edge Runtime. -- Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time). -- Nitro v2.6 or greater. - -Note that React Native is not supported at this time. +```bash +pnpm install +pnpm generate +pnpm test +``` -If you are interested in other runtime environments, please open or upvote an issue on GitHub. +Run `pnpm sync-spec` before generation only when intentionally updating the +snapshot from the production API document. This keeps unrelated API changes out +of generator and configuration updates. -## Contributing +The published SDK supports Node.js 20 and newer. The newer Node.js requirement +applies only to the development and generation toolchain. -See [the contributing documentation](./CONTRIBUTING.md). +`pnpm test` checks repository formatting and generated source types, builds +CommonJS and ESM output, verifies the package export map, and validates the +packed type surface. It also installs the tarball in an isolated consumer and +checks real HTTP request and response behavior against a local server. diff --git a/SECURITY.md b/SECURITY.md index d4bb777..f1402dd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,14 +2,14 @@ ## Reporting Security Issues -This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. - -To report a security issue, please contact the Stainless team at security@stainless.com. +Please report SDK vulnerabilities privately through this repository's +[GitHub security advisory form](https://github.com/unlayer/unlayer-typescript/security/advisories/new). +Do not include vulnerability details in a public issue. ## Responsible Disclosure We appreciate the efforts of security researchers and individuals who help us maintain the security of -SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible +the SDK. If you believe you have found a security vulnerability, please adhere to responsible disclosure practices by allowing us a reasonable amount of time to investigate and address the issue before making any information public. diff --git a/api.md b/api.md deleted file mode 100644 index 6cee992..0000000 --- a/api.md +++ /dev/null @@ -1,55 +0,0 @@ -# Convert - -## FullToSimple - -Types: - -- FullToSimpleCreateResponse - -Methods: - -- client.convert.fullToSimple.create({ ...params }) -> FullToSimpleCreateResponse - -## SimpleToFull - -Types: - -- SimpleToFullCreateResponse - -Methods: - -- client.convert.simpleToFull.create({ ...params }) -> SimpleToFullCreateResponse - -# Projects - -Types: - -- ProjectRetrieveResponse - -Methods: - -- client.projects.retrieve(id) -> ProjectRetrieveResponse - -# Templates - -Types: - -- TemplateRetrieveResponse -- TemplateListResponse - -Methods: - -- client.templates.retrieve(id, { ...params }) -> TemplateRetrieveResponse -- client.templates.list({ ...params }) -> TemplateListResponsesCursorPage - -# Workspaces - -Types: - -- WorkspaceRetrieveResponse -- WorkspaceListResponse - -Methods: - -- client.workspaces.retrieve(workspaceID) -> WorkspaceRetrieveResponse -- client.workspaces.list() -> WorkspaceListResponse diff --git a/bin/check-release-environment b/bin/check-release-environment index e4b6d58..891e1ce 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -1,22 +1,31 @@ #!/usr/bin/env bash -errors=() - -if [ -z "${NPM_TOKEN}" ]; then - errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") -fi - -lenErrors=${#errors[@]} - -if [[ lenErrors -gt 0 ]]; then - echo -e "Found the following errors in the release environment:\n" - - for error in "${errors[@]}"; do - echo -e "- $error\n" - done - - exit 1 -fi - -echo "The environment is ready to push releases!" - +set -eu + +cd "$(dirname "$0")/.." + +node <<'NODE' +const packageJson = require('./package.json'); +const [npmMajor = 0, npmMinor = 0, npmPatch = 0] = require('child_process') + .execFileSync('npm', ['--version'], { encoding: 'utf8' }) + .trim() + .split('.') + .map(Number); + +if ( + npmMajor < 11 || + (npmMajor === 11 && (npmMinor < 5 || (npmMinor === 5 && npmPatch < 1))) +) { + throw new Error('npm 11.5.1 or newer is required for trusted publishing'); +} + +if (packageJson.publishConfig?.access !== 'public') { + throw new Error('publishConfig.access must be public'); +} + +if (packageJson.repository?.url !== 'git+https://github.com/unlayer/unlayer-typescript.git') { + throw new Error('repository URL must match the trusted publisher repository'); +} +NODE + +echo "The repository release configuration is valid." diff --git a/bin/check-release-ref b/bin/check-release-ref new file mode 100755 index 0000000..c69437f --- /dev/null +++ b/bin/check-release-ref @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -eu + +cd "$(dirname "$0")/.." + +if [ "${GITHUB_ACTIONS:-}" != "true" ]; then + echo "Package releases must run in GitHub Actions." >&2 + exit 1 +fi + +if [ "${GITHUB_REF_TYPE:-}" != "tag" ]; then + echo "Package releases must run from a Git tag." >&2 + exit 1 +fi + +VERSION="$(jq -r -e '.version' package.json)" +EXPECTED_TAG="v${VERSION}" + +if [ "${GITHUB_REF_NAME:-}" != "$EXPECTED_TAG" ]; then + echo "Release tag ${GITHUB_REF_NAME:-} does not match package version $VERSION." >&2 + exit 1 +fi + +echo "Release tag $EXPECTED_TAG matches package version $VERSION." diff --git a/bin/publish-npm b/bin/publish-npm index 45e8aa8..c6f6d07 100644 --- a/bin/publish-npm +++ b/bin/publish-npm @@ -1,40 +1,57 @@ #!/usr/bin/env bash -set -eux - -npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" - -yarn build -cd dist - -# Get package name and version from package.json -PACKAGE_NAME="$(jq -r -e '.name' ./package.json)" -VERSION="$(jq -r -e '.version' ./package.json)" - -# Get latest version from npm -# -# If the package doesn't exist, npm will return: -# { -# "error": { -# "code": "E404", -# "summary": "Unpublished on 2025-06-05T09:54:53.528Z", -# "detail": "'the_package' is not in this registry..." -# } -# } -NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>/dev/null || true)" - -# Check if we got an E404 error -if echo "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then - # Package doesn't exist yet, no last version - LAST_VERSION="" -elif echo "$NPM_INFO" | jq -e '.error' > /dev/null 2>&1; then - # Report other errors - echo "ERROR: npm returned unexpected data:" - echo "$NPM_INFO" +set -euo pipefail + +cd "$(dirname "$0")/.." + +bash ./bin/check-release-ref +bash ./bin/check-release-environment + +if [ "$#" -ne 1 ]; then + echo "usage: $0 " >&2 exit 1 +fi + +ARCHIVE="$1" +if [ ! -f "$ARCHIVE" ]; then + echo "verified package archive not found: $ARCHIVE" >&2 + exit 1 +fi + +# Read release identity from the exact archive that passed verification. +PACKAGE_JSON="$(tar -xOf "$ARCHIVE" package/package.json)" +PACKAGE_NAME="$(echo "$PACKAGE_JSON" | jq -r -e '.name')" +VERSION="$(echo "$PACKAGE_JSON" | jq -r -e '.version')" +EXPECTED_NAME="$(jq -r -e '.name' package.json)" +EXPECTED_VERSION="$(jq -r -e '.version' package.json)" + +if [ "$PACKAGE_NAME" != "$EXPECTED_NAME" ] || [ "$VERSION" != "$EXPECTED_VERSION" ]; then + echo "verified archive identity does not match the release checkout" >&2 + exit 1 +fi + +NPM_ERROR_FILE="$(mktemp "${TMPDIR:-/tmp}/unlayer-sdk-npm-view.XXXXXX")" +trap 'rm -f "$NPM_ERROR_FILE"' EXIT + +NPM_STATUS=0 +NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>"$NPM_ERROR_FILE")" || NPM_STATUS=$? + +if [ "$NPM_STATUS" -eq 0 ]; then + if ! LAST_VERSION="$( + printf '%s\n' "$NPM_INFO" | + jq -er 'if type == "string" and length > 0 then . else error("expected a version string") end' + )"; then + echo "npm returned an invalid version response" >&2 + exit 1 + fi +elif printf '%s\n' "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then + # An explicit E404 means the package has not been published before. + LAST_VERSION="" else - # Success - get the version - LAST_VERSION=$(echo "$NPM_INFO" | jq -r '.') # strip quotes + echo "npm view failed with status $NPM_STATUS" >&2 + if [ -n "$NPM_INFO" ]; then printf '%s\n' "$NPM_INFO" >&2; fi + if [ -s "$NPM_ERROR_FILE" ]; then cat "$NPM_ERROR_FILE" >&2; fi + exit 1 fi # Check if current version is pre-release (e.g. alpha / beta / rc) @@ -57,5 +74,6 @@ else TAG="latest" fi -# Publish with the appropriate tag -yarn publish --tag "$TAG" +# npm trusted publishing exchanges GitHub's OIDC token for short-lived publish +# credentials and automatically attaches provenance to public packages. +npm publish "$ARCHIVE" --tag "$TAG" --access public diff --git a/eslint.config.mjs b/eslint.config.mjs index e0dbbf8..a8677f4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,7 +10,7 @@ export default tseslint.config( parserOptions: { sourceType: 'module' }, }, files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.js', '**/*.mjs', '**/*.cjs'], - ignores: ['dist/'], + ignores: ['dist/**', 'src/**'], plugins: { '@typescript-eslint': tseslint.plugin, 'unused-imports': unusedImports, diff --git a/examples/.keep b/examples/.keep deleted file mode 100644 index 0651c89..0000000 --- a/examples/.keep +++ /dev/null @@ -1,4 +0,0 @@ -File generated from our OpenAPI spec by Stainless. - -This directory can be used to store example files demonstrating usage of this SDK. -It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. diff --git a/jest.config.ts b/jest.config.ts deleted file mode 100644 index da92a62..0000000 --- a/jest.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { JestConfigWithTsJest } from 'ts-jest'; - -const config: JestConfigWithTsJest = { - preset: 'ts-jest/presets/default-esm', - testEnvironment: 'node', - transform: { - '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], - }, - moduleNameMapper: { - '^@unlayer/sdk$': '/src/index.ts', - '^@unlayer/sdk/(.*)$': '/src/$1', - }, - modulePathIgnorePatterns: [ - '/ecosystem-tests/', - '/dist/', - '/deno/', - '/deno_tests/', - '/packages/', - ], - testPathIgnorePatterns: ['scripts'], -}; - -export default config; diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts new file mode 100644 index 0000000..3346e7f --- /dev/null +++ b/openapi-ts.config.ts @@ -0,0 +1,36 @@ +import { defineConfig } from '@hey-api/openapi-ts'; + +const ignoredOperations = new Set(['GET /v3/templates/generate']); + +export default defineConfig({ + input: './openapi.json', + output: { + clean: true, + path: 'src', + }, + plugins: [ + { + name: '@hey-api/client-fetch', + throwOnError: true, + }, + { + name: '@hey-api/sdk', + operations: { + methods: 'instance', + strategy: (operation) => { + const resource = operation.tags?.[0]; + if (!resource || !operation.operationId) { + const operationKey = `${operation.method.toUpperCase()} ${operation.path}`; + if (!ignoredOperations.has(operationKey)) { + throw new Error(`${operationKey} must define a tag and operationId`); + } + console.warn(`Skipping known OpenAPI stub: ${operationKey}`); + return []; + } + return [['Unlayer', resource, operation.operationId]]; + }, + }, + responseStyle: 'data', + }, + ], +}); diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..78e2e57 --- /dev/null +++ b/openapi.json @@ -0,0 +1,10341 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Unlayer API", + "version": "3.0.0", + "description": "Unlayer API\n\n## Authentication\n\nAll endpoints require a Bearer token. Pass it in the `Authorization` header:\n\n```\nAuthorization: Bearer \n```\n\nTwo token types are supported:\n\n| Token | Prefix | Scope | Use case |\n|-------|--------|-------|----------|\n| **API Key** | `unlayer_sk_` | Project | Server-to-server integration. The project ID is embedded in the key — no `projectId` parameter needed. |\n| **Personal Access Token** | `unlayer_pat_` | User | Admin operations. Required for workspace endpoints. Must provide `projectId` via query param or `X-Project-Id` header for project endpoints. |\n\nMost integrations should use an **API Key**. Use a PAT only for workspace management or admin tasks.\n\n> ⚠️ **Server-side use only.** This API does not send CORS headers and cannot be called from a browser. API keys (`unlayer_sk_*`) must never be shipped to client-side code — treat them as secrets." + }, + "components": { + "securitySchemes": { + "apiKeyAuth": { + "type": "http", + "scheme": "bearer", + "description": "API Key (unlayer_sk_). Project-scoped — the project ID is embedded in the key, so no projectId parameter is needed. Recommended for most integrations." + }, + "personalAccessTokenAuth": { + "type": "http", + "scheme": "bearer", + "description": "Personal Access Token (unlayer_pat_). User-scoped — required for workspace endpoints and admin operations. Must provide projectId for project endpoints." + } + }, + "schemas": {} + }, + "paths": { + "/v3/blocks": { + "get": { + "operationId": "listBlocks", + "summary": "List blocks", + "tags": [ + "blocks" + ], + "description": "List blocks with cursor-based pagination. Returns both shared project blocks and blocks saved by end-users; each user-saved block carries the userId it was saved under (null for shared blocks), so usage can be aggregated per end-user without enumerating user IDs. Returns blocks in descending order by creation.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "The project ID to list blocks for" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "in": "query", + "name": "limit", + "required": false, + "description": "Number of blocks to return (1-100)" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false, + "description": "Pagination cursor from previous response" + }, + { + "schema": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ] + }, + "in": "query", + "name": "displayMode", + "required": false, + "description": "Filter by display mode" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "userId", + "required": false, + "description": "Only blocks saved by this end-user (exact match on the user id your app passes to the editor)" + }, + { + "schema": { + "type": "string", + "enum": [ + "all", + "shared", + "user" + ], + "default": "all" + }, + "in": "query", + "name": "scope", + "required": false, + "description": "Filter by block ownership: shared project blocks, end-user saved blocks, or both" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "category", + "required": false, + "description": "Filter by category (case-insensitive search)" + }, + { + "schema": { + "type": "boolean", + "default": true + }, + "in": "query", + "name": "includeData", + "required": false, + "description": "Include the block design JSON in each item. Pass false for lightweight sweeps (e.g. usage reports)." + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data", + "has_more" + ], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Block ID" + }, + "userId": { + "type": "string", + "nullable": true, + "description": "End-user ID the block was saved under (the user id your app passes to the editor). Null for shared project blocks." + }, + "displayMode": { + "type": "string", + "description": "Display mode the block was saved for: email, web, popup, or document" + }, + "category": { + "type": "string", + "description": "Block category" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Block tags" + }, + "data": { + "type": "object", + "additionalProperties": true, + "description": "The block design JSON. Omitted when includeData=false is passed." + }, + "thumbnailUrl": { + "type": "string", + "nullable": true, + "description": "URL of the auto-generated block thumbnail, if available" + }, + "syncId": { + "type": "string", + "nullable": true, + "description": "Synced-block ID referenced by designs using this block. Null when the block has never been synced." + }, + "isSyncEnabled": { + "type": "boolean", + "description": "Whether the block is currently a synced block" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } + } + }, + "next_cursor": { + "type": "string", + "nullable": true, + "description": "Cursor for the next page. Null if no more results." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more results after this page" + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/domains": { + "get": { + "operationId": "listDomains", + "summary": "List sender domains", + "tags": [ + "domains" + ], + "description": "List sender domains shared by every Developer Email API project in the workspace. Requires a personal access token belonging to a workspace owner or admin; project API keys cannot manage domains.", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "domain": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "verified", + "failed" + ] + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "createDomain", + "summary": "Add a sender domain", + "tags": [ + "domains" + ], + "description": "Register a sender domain shared by every Developer Email API project in the workspace. Requires a personal access token belonging to a workspace owner or admin. Verification requires the workspace-specific TXT record and the returned SES DKIM records.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "domain" + ], + "properties": { + "domain": { + "type": "string", + "description": "Domain name to register, such as example.com." + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "domain": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "verified", + "failed" + ] + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "dkimTokens": { + "type": "array", + "items": { + "type": "string" + } + }, + "dnsRecords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "purpose": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/domains/{id}": { + "get": { + "operationId": "getDomain", + "summary": "Get domain details", + "tags": [ + "domains" + ], + "description": "Get the ownership TXT challenge and SES DKIM records for a sender domain shared by every Developer Email API project in the workspace. Requires a personal access token belonging to a workspace owner or admin.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "Domain ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "domain": { + "type": "string" + }, + "status": { + "type": "string" + }, + "dkimTokens": { + "type": "array", + "items": { + "type": "string" + } + }, + "dnsRecords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "purpose": { + "type": "string" + } + } + } + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "deleteDomain", + "summary": "Delete a sender domain", + "tags": [ + "domains" + ], + "description": "Delete a sender domain shared by every Developer Email API project in the workspace. Requires a personal access token belonging to a workspace owner or admin. The SES identity remains so a later reconciler can clean it up safely.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "Domain ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/domains/{id}/verify": { + "post": { + "operationId": "verifyDomain", + "summary": "Verify domain status", + "tags": [ + "domains" + ], + "description": "Verify the ownership TXT challenge and SES DKIM identity for a sender domain shared by every Developer Email API project in the workspace. Requires a personal access token belonging to a workspace owner or admin.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "Domain ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "domain": { + "type": "string" + }, + "status": { + "type": "string" + }, + "ownership": { + "type": "object", + "properties": { + "verified": { + "type": "boolean" + } + } + }, + "dkim": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "tokens": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/editor-sessions": { + "post": { + "operationId": "createEditorSession", + "summary": "Create editor session", + "tags": [ + "editor-sessions" + ], + "description": "Create an ephemeral, no-DB editor session for a design and return a hosted editor URL the user can open to edit it in the real Unlayer editor.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "design" + ], + "properties": { + "design": { + "type": "object", + "additionalProperties": true, + "description": "Design JSON to load into the editor." + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ], + "description": "Editor display mode. Defaults to email." + } + } + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "The project ID (required for PAT auth, auto-resolved for API key auth)" + } + ], + "responses": { + "201": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "editorUrl": { + "type": "string" + }, + "expiresAt": { + "type": "string" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/emails": { + "get": { + "operationId": "listEmails", + "summary": "List sent emails", + "tags": [ + "emails" + ], + "description": "List emails sent from this project within the rolling 90-day history window. Without a status filter, results and date bounds use acceptance time. With a status filter, results and date bounds use the time each email entered that status.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "Project ID (auto-resolved for API key auth)" + }, + { + "schema": { + "type": "string", + "enum": [ + "queued", + "sending", + "sent", + "delivered", + "bounced", + "complained", + "failed" + ] + }, + "in": "query", + "name": "status", + "required": false, + "description": "Filter by email delivery status" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "search", + "required": false, + "description": "Search recipient addresses and subjects by case-sensitive substring" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "tag", + "required": false, + "description": "Filter by tag in \"key=value\" format (e.g. \"campaign=welcome\")" + }, + { + "schema": { + "type": "string", + "format": "date" + }, + "in": "query", + "name": "from", + "required": false, + "description": "Start date (ISO date). Bounds acceptance time normally, or status transition time when status is supplied." + }, + { + "schema": { + "type": "string", + "format": "date" + }, + "in": "query", + "name": "to", + "required": false, + "description": "End date (ISO date). Bounds acceptance time normally, or status transition time when status is supplied." + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "in": "query", + "name": "limit", + "required": false, + "description": "Number of emails to return (1-100)" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false, + "description": "Pagination cursor from previous response" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data", + "has_more" + ], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "from": { + "type": "string" + }, + "to": {}, + "subject": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "statusUpdatedAt": { + "type": "string", + "format": "date-time", + "description": "When the email entered its current status. For a newly queued email, this equals createdAt." + } + } + } + }, + "next_cursor": { + "type": "string", + "nullable": true, + "description": "Cursor for the next page. Null if no more results." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more results after this page" + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "sendEmail", + "summary": "Send an email", + "tags": [ + "emails" + ], + "description": "Send a transactional email with raw HTML content. The sender domain must be verified in the project workspace; verified sender domains are shared by every Developer Email API project in that workspace.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "from", + "to", + "subject", + "html" + ], + "properties": { + "from": { + "type": "string", + "description": "Sender email address or \"Name \" format. Domain must be verified." + }, + "to": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "minItems": 1, + "maxItems": 1, + "description": "Exactly one recipient. Each request creates one independently tracked delivery." + }, + "cc": { + "type": "array", + "maxItems": 0, + "description": "CC is not supported by this endpoint." + }, + "bcc": { + "type": "array", + "maxItems": 0, + "description": "BCC is not supported by this endpoint." + }, + "subject": { + "type": "string", + "maxLength": 998, + "description": "Email subject line" + }, + "html": { + "type": "string", + "description": "HTML content of the email" + }, + "text": { + "type": "string", + "description": "Plain text version of the email. If provided, a multipart/alternative message is sent." + }, + "replyTo": { + "type": "string", + "format": "email", + "description": "Reply-To email address" + }, + "tags": { + "type": "object", + "maxProperties": 10, + "propertyNames": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9_-]{1,64}$" + }, + "additionalProperties": { + "type": "string", + "maxLength": 256, + "pattern": "^[A-Za-z0-9_-]{0,256}$" + }, + "description": "Key-value tags for categorizing the email (e.g. {\"campaign\": \"welcome\"}). Max 10 tags. Keys (1-64 chars) and values (up to 256 chars) may only contain letters, numbers, underscores, and hyphens (the Amazon SES message-tag character set)." + }, + "headers": { + "type": "object", + "maxProperties": 9, + "propertyNames": { + "maxLength": 126, + "pattern": "^[Xx]-[A-Za-z0-9][A-Za-z0-9-]{0,123}$" + }, + "additionalProperties": { + "type": "string", + "minLength": 1, + "maxLength": 995, + "pattern": "^[ -~]+$" + }, + "description": "Custom email headers. Up to 9 printable-ASCII X-* headers are allowed (e.g. {\"X-Entity-Ref-ID\": \"abc123\"}). Header names may contain up to 126 characters and each name plus value may contain up to 996 characters." + }, + "attachments": { + "type": "array", + "maxItems": 10, + "items": { + "type": "object", + "required": [ + "filename", + "content", + "contentType" + ], + "properties": { + "filename": { + "type": "string", + "description": "The filename as it will appear to the recipient. Line breaks are rejected; quotes are stripped before it is written into the message." + }, + "content": { + "type": "string", + "description": "Base64-encoded file content. Whitespace and MIME line wrapping are removed before validation; invalid base64 is rejected with a 400 error." + }, + "contentType": { + "type": "string", + "enum": [ + "application/pdf", + "application/zip", + "application/json", + "application/xml", + "application/csv", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.ms-excel", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "text/plain", + "text/html", + "text/csv", + "text/xml", + "text/calendar", + "image/png", + "image/jpeg", + "image/gif", + "image/webp", + "image/svg+xml", + "audio/mpeg", + "audio/wav", + "video/mp4" + ], + "description": "MIME type of the attachment. Required; must be one of the allowed types." + } + } + }, + "description": "File attachments. Max 10 files per email, max 5 MB total payload size (including headers and base64 overhead)." + } + } + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string", + "maxLength": 255 + }, + "in": "header", + "name": "idempotency-key", + "required": false, + "description": "Unique key for idempotent sends (max 255 characters). If provided, duplicate requests within 24 hours return the cached response." + } + ], + "responses": { + "202": { + "description": "Email accepted and queued for delivery", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "description": "Email accepted and queued for delivery", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique email ID (UUID). Use this with GET /v3/emails/:id to retrieve delivery status and events." + }, + "from": { + "type": "string", + "description": "The sender address the email was sent from, either a plain email or \"Name \" format." + }, + "to": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The single accepted recipient address." + }, + "subject": { + "type": "string", + "description": "The subject line of the email that was sent." + }, + "status": { + "type": "string", + "enum": [ + "queued", + "sending", + "sent", + "delivered", + "bounced", + "complained", + "failed" + ], + "description": "Usually \"queued\" for a fresh send. An idempotent replay of a previously accepted request returns that email's current status instead. Use webhooks or GET /v3/emails/:id for live delivery status." + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "When the email was accepted and queued for delivery (ISO-8601)." + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "409": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "429": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "503": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/emails/{id}": { + "get": { + "operationId": "getEmail", + "summary": "Get email details", + "tags": [ + "emails" + ], + "description": "Retrieve details of a sent email, including its current delivery status, during the rolling 90-day history window. Expired emails return 404.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "Email ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "from": { + "type": "string" + }, + "to": {}, + "cc": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "bcc": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "subject": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "failureReason": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/emails/{id}/events": { + "get": { + "operationId": "getEmailEvents", + "summary": "Get email event timeline", + "tags": [ + "emails" + ], + "description": "Retrieve the operational event timeline for a sent email, showing send, delivery, bounce, and complaint events in chronological order during the rolling 90-day history window. Expired emails return 404.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "Email ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Event type (send, delivery, bounce, complaint)" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "nullable": true + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/emails/render": { + "post": { + "operationId": "renderEmail", + "summary": "Render an email template", + "tags": [ + "emails" + ], + "description": "Render a saved email template with optional merge variables. Returns the final HTML without sending. Useful for previewing emails before sending.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "templateId" + ], + "properties": { + "templateId": { + "type": "string", + "pattern": "^[1-9][0-9]*$", + "description": "Template ID to render" + }, + "variables": { + "type": "object", + "maxProperties": 100, + "propertyNames": { + "maxLength": 64 + }, + "additionalProperties": { + "type": "string", + "maxLength": 100000 + }, + "description": "Merge variables to substitute. Use {{key}} syntax in your template." + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "html": { + "type": "string", + "description": "Rendered HTML content" + }, + "subject": { + "type": "string", + "nullable": true, + "description": "Template name (can be used as default subject)" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/emails/settings": { + "get": { + "operationId": "getEmailSettings", + "summary": "Get email settings", + "tags": [ + "emails" + ], + "description": "Get the email sender settings for this project.", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "defaultFromName": { + "type": "string", + "maxLength": 255, + "description": "Default sender display name" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "When the settings row was first created." + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "When the settings were last updated." + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + }, + "patch": { + "operationId": "updateEmailSettings", + "summary": "Update email settings", + "tags": [ + "emails" + ], + "description": "Update the email sending configuration for this project. Only include the fields you want to change.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "defaultFromName": { + "type": "string", + "maxLength": 255, + "description": "Default sender display name" + } + }, + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "defaultFromName": { + "type": "string", + "maxLength": 255, + "description": "Default sender display name" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "When the settings row was first created." + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "When the settings were last updated." + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/emails/stats": { + "get": { + "operationId": "getEmailStats", + "summary": "Get email statistics", + "tags": [ + "emails" + ], + "description": "Get aggregated email delivery statistics for a project. Returns totals or daily breakdown for the specified period. Statistics are asynchronous and may lag by about one hour.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "Project ID (auto-resolved for API key auth)" + }, + { + "schema": { + "type": "string", + "enum": [ + "7d", + "30d", + "90d" + ], + "default": "30d" + }, + "in": "query", + "name": "period", + "required": false, + "description": "Time period for stats" + }, + { + "schema": { + "type": "string", + "enum": [ + "day" + ] + }, + "in": "query", + "name": "groupBy", + "required": false, + "description": "Group results by day for chart data" + } + ], + "responses": { + "200": { + "description": "Email statistics. Shape depends on the `groupBy` query parameter: an aggregated totals object by default, or a daily breakdown array when groupBy=day.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "description": "Email statistics. Shape depends on the `groupBy` query parameter: an aggregated totals object by default, or a daily breakdown array when groupBy=day.", + "properties": { + "data": { + "oneOf": [ + { + "type": "object", + "description": "Aggregated totals for the requested period (default response).", + "properties": { + "period": { + "type": "string", + "enum": [ + "7d", + "30d", + "90d" + ], + "description": "The period these stats cover." + }, + "sent": { + "type": "number", + "description": "Total emails sent (one per recipient)." + }, + "delivered": { + "type": "number", + "description": "Number of successfully delivered emails." + }, + "bounced": { + "type": "number", + "description": "Number of emails that were bounced by the recipient mail server." + }, + "complained": { + "type": "number", + "description": "Number of spam complaint events received." + }, + "deliveryRate": { + "type": "number", + "description": "Delivered / sent as a percentage (0-100, 2 decimal places)." + }, + "bounceRate": { + "type": "number", + "description": "Bounced / sent as a percentage (0-100, 2 decimal places)." + } + } + }, + { + "type": "array", + "description": "Daily breakdown (returned when groupBy=day). Ordered chronologically.", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date", + "description": "The email send-cohort day in YYYY-MM-DD format." + }, + "sent": { + "type": "number", + "description": "Emails sent on this day." + }, + "delivered": { + "type": "number", + "description": "Emails from this send cohort that were delivered." + }, + "bounced": { + "type": "number", + "description": "Emails bounced on this day." + }, + "complained": { + "type": "number", + "description": "Spam complaints received for this send cohort." + } + } + } + } + ] + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/emails/suppressions": { + "get": { + "operationId": "listSuppressions", + "summary": "List suppressed email addresses", + "tags": [ + "emails" + ], + "description": "List all email addresses suppressed for this project due to bounces, complaints, or manual suppression. Cursor-paginated.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "Project ID (auto-resolved for API key auth)" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 100 + }, + "in": "query", + "name": "limit", + "required": false, + "description": "Max number of results (1-200)" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false, + "description": "Pagination cursor from a previous response. Omit to start from the beginning." + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data", + "has_more" + ], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "reason": { + "type": "string", + "enum": [ + "hard_bounce", + "complaint", + "manual", + "unsubscribe" + ] + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + } + } + }, + "has_more": { + "type": "boolean" + }, + "next_cursor": { + "type": [ + "null", + "string" + ] + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "addSuppression", + "summary": "Suppress an email address", + "tags": [ + "emails" + ], + "description": "Manually add an email address to the suppression list. Future sends to this address will be blocked.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email address to suppress" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "removeSuppression", + "summary": "Remove email from suppression list", + "tags": [ + "emails" + ], + "description": "Remove an email address from the suppression list so it can receive emails again.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "Project ID (auto-resolved for API key auth)" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "email", + "required": true, + "description": "Email address to unsuppress" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "removed": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/emails/suppressions/check": { + "get": { + "operationId": "checkSuppression", + "summary": "Check if an email is suppressed", + "tags": [ + "emails" + ], + "description": "Look up a specific email address to see if it is currently on the suppression list.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "Project ID (auto-resolved for API key auth)" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "email", + "required": true, + "description": "Email address to check" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "suppressed": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/emails/template": { + "post": { + "operationId": "sendTemplateEmail", + "summary": "Send an email using a template", + "tags": [ + "emails" + ], + "description": "Send a transactional email by rendering a saved template with optional merge variables. The template must have rendered HTML (saved at least once in the editor). The sender domain must be verified in the project workspace; verified sender domains are shared by every Developer Email API project in that workspace.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "from", + "to", + "templateId" + ], + "properties": { + "from": { + "type": "string", + "description": "Sender email address or \"Name \" format. Domain must be verified." + }, + "to": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "minItems": 1, + "maxItems": 1, + "description": "Exactly one recipient. Each request creates one independently tracked delivery." + }, + "cc": { + "type": "array", + "maxItems": 0, + "description": "CC is not supported by this endpoint." + }, + "bcc": { + "type": "array", + "maxItems": 0, + "description": "BCC is not supported by this endpoint." + }, + "templateId": { + "type": "string", + "pattern": "^[1-9][0-9]*$", + "description": "Template ID to use for the email body" + }, + "subject": { + "type": "string", + "maxLength": 998, + "description": "Email subject line. Supports {{variable}} merge syntax. Defaults to template name if omitted." + }, + "variables": { + "type": "object", + "maxProperties": 100, + "propertyNames": { + "maxLength": 64 + }, + "additionalProperties": { + "type": "string", + "maxLength": 100000 + }, + "description": "Merge variables to substitute in the template and subject. Use {{key}} syntax in your template." + }, + "text": { + "type": "string", + "description": "Plain text version of the email. Supports {{variable}} merge syntax." + }, + "replyTo": { + "type": "string", + "format": "email", + "description": "Reply-To email address" + }, + "tags": { + "type": "object", + "maxProperties": 10, + "propertyNames": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9_-]{1,64}$" + }, + "additionalProperties": { + "type": "string", + "maxLength": 256, + "pattern": "^[A-Za-z0-9_-]{0,256}$" + }, + "description": "Key-value tags for categorizing the email (e.g. {\"campaign\": \"welcome\"}). Max 10 tags. Keys (1-64 chars) and values (up to 256 chars) may only contain letters, numbers, underscores, and hyphens (the Amazon SES message-tag character set)." + }, + "headers": { + "type": "object", + "maxProperties": 9, + "propertyNames": { + "maxLength": 126, + "pattern": "^[Xx]-[A-Za-z0-9][A-Za-z0-9-]{0,123}$" + }, + "additionalProperties": { + "type": "string", + "minLength": 1, + "maxLength": 995, + "pattern": "^[ -~]+$" + }, + "description": "Custom email headers. Up to 9 printable-ASCII X-* headers are allowed. Header names may contain up to 126 characters and each name plus value may contain up to 996 characters." + }, + "attachments": { + "type": "array", + "maxItems": 10, + "items": { + "type": "object", + "required": [ + "filename", + "content", + "contentType" + ], + "properties": { + "filename": { + "type": "string", + "description": "The filename as it will appear to the recipient. Line breaks are rejected; quotes are stripped before it is written into the message." + }, + "content": { + "type": "string", + "description": "Base64-encoded file content. Whitespace and MIME line wrapping are removed before validation; invalid base64 is rejected with a 400 error." + }, + "contentType": { + "type": "string", + "enum": [ + "application/pdf", + "application/zip", + "application/json", + "application/xml", + "application/csv", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.ms-excel", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "text/plain", + "text/html", + "text/csv", + "text/xml", + "text/calendar", + "image/png", + "image/jpeg", + "image/gif", + "image/webp", + "image/svg+xml", + "audio/mpeg", + "audio/wav", + "video/mp4" + ], + "description": "MIME type of the attachment. Required; must be one of the allowed types." + } + } + }, + "description": "File attachments. Max 10 files per email, max 5 MB total payload size." + } + } + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string", + "maxLength": 255 + }, + "in": "header", + "name": "idempotency-key", + "required": false, + "description": "Unique key for idempotent sends (max 255 characters). Duplicate requests within 24 hours return the cached response." + } + ], + "responses": { + "202": { + "description": "Email accepted and queued for delivery", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "description": "Email accepted and queued for delivery", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique email ID (UUID). Use this with GET /v3/emails/:id to retrieve delivery status and events." + }, + "from": { + "type": "string", + "description": "The sender address the email was sent from." + }, + "to": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The single accepted recipient address." + }, + "subject": { + "type": "string", + "description": "The resolved subject line after merge variables were applied." + }, + "status": { + "type": "string", + "enum": [ + "queued", + "sending", + "sent", + "delivered", + "bounced", + "complained", + "failed" + ], + "description": "Usually \"queued\" for a fresh send. An idempotent replay of a previously accepted request returns that email's current status instead. Use webhooks or GET /v3/emails/:id for live delivery status." + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "When the email was accepted and queued for delivery (ISO-8601)." + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "409": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "429": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "503": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/me/subscription": { + "get": { + "operationId": "getMySubscription", + "summary": "Get current plan and features.", + "tags": [ + "me" + ], + "description": "Get the current plan, feature availability, and limits for a project. Used to answer \"can I do X\" / \"what plan do I need\" questions with ground-truth data instead of guessing.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "The project ID (required for PAT auth, auto-resolved for API key auth)" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "planName": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "nullable": true + }, + "expiresAt": { + "type": "string", + "nullable": true + }, + "features": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "available": { + "type": "boolean" + } + } + } + }, + "limits": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "number" + }, + "unit": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/projects/{id}": { + "get": { + "operationId": "getProject", + "summary": "Get project.", + "tags": [ + "projects" + ], + "description": "Get project details by ID.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "The resource ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The project ID." + }, + "name": { + "type": "string", + "description": "The project name." + }, + "status": { + "type": "string", + "description": "The project status." + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "When the project was created." + }, + "workspace": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/projects/{id}/ai-credits": { + "get": { + "operationId": "getProjectAiCredits", + "summary": "Get AI credit balance.", + "tags": [ + "ai-credits" + ], + "description": "Returns the current AI credit balance for the project. Credits are pooled per workspace — every project in a workspace shares one balance. Only credit counts are returned; token counts, model names, and costs are never exposed.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "The resource ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "credits_total": { + "type": "number", + "description": "Total AI credits available for the current period." + }, + "credits_used": { + "type": "number", + "description": "AI credits consumed so far in the current period." + }, + "credits_remaining": { + "type": "number", + "description": "AI credits remaining in the current period." + }, + "reset_date": { + "type": [ + "null", + "string" + ], + "format": "date-time", + "description": "When the current credit period resets, or null if there is no active billing period — including once a subscription is cancelled or its term has ended." + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/projects/{id}/ai-credits/settings": { + "get": { + "operationId": "getProjectAiCreditsSettings", + "summary": "Get AI credit settings.", + "tags": [ + "ai-credits" + ], + "description": "Returns a project's AI credit exhaustion behavior, alert thresholds, and webhook endpoint. The signing secret is never returned — only whether one exists (`has_signing_secret`).", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "The resource ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "exhaustion_behavior": { + "type": "string" + }, + "threshold_alerts": { + "type": "array", + "items": { + "type": "number" + } + }, + "webhook_url": { + "type": [ + "null", + "string" + ] + }, + "has_signing_secret": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + }, + "put": { + "operationId": "updateProjectAiCreditsSettings", + "summary": "Update AI credit settings.", + "tags": [ + "ai-credits" + ], + "description": "Configures AI credit exhaustion behavior, usage alert thresholds, and the webhook endpoint for a project. The HMAC signing secret is generated the first time a webhook URL is set and returned exactly once in the response — store it securely; it is never shown again.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "exhaustion_behavior": { + "type": "string", + "enum": [ + "disable", + "show_error" + ], + "description": "What the editor does when the credit balance is exhausted." + }, + "threshold_alerts": { + "type": "array", + "items": { + "type": "integer", + "minimum": 1, + "maximum": 100 + }, + "description": "Usage percentages (1-100) at which a threshold_reached webhook fires, once per crossing per period." + }, + "webhook_url": { + "type": [ + "string", + "null" + ], + "format": "uri", + "pattern": "^https://", + "description": "HTTPS endpoint that receives AI credit webhooks." + } + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "The resource ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "exhaustion_behavior": { + "type": "string" + }, + "threshold_alerts": { + "type": "array", + "items": { + "type": "number" + } + }, + "webhook_url": { + "type": [ + "null", + "string" + ] + }, + "has_signing_secret": { + "type": "boolean" + }, + "signing_secret": { + "type": "string", + "description": "The HMAC signing secret. Returned ONLY on the response that first generates it." + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/projects/{id}/ai-credits/settings/rotate-secret": { + "post": { + "operationId": "rotateProjectAiCreditsSigningSecret", + "summary": "Rotate the AI credit webhook signing secret.", + "tags": [ + "ai-credits" + ], + "description": "Generates a new HMAC signing secret for the project and returns it exactly once. The previous secret stops working immediately, so update your webhook verification before rotating. Requires a webhook URL to be configured first.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "The resource ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signing_secret": { + "type": "string", + "description": "The new HMAC signing secret. Shown only once." + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/projects/{id}/ai-credits/usage": { + "get": { + "operationId": "getProjectAiCreditsUsage", + "summary": "Get AI credit usage breakdown.", + "tags": [ + "ai-credits" + ], + "description": "Returns AI credit consumption for the project, broken down by end user and feature type. Filterable by date range, end user, and feature type. Usage is updated near real time and grouped by the UTC date when the AI activity occurred. Recent activity may take a short time to appear. Defaults to the current billing period. Only credit counts are returned; token counts, model names, and costs are never exposed. Per-end-user attribution requires the partner to pass `endUserId` on editor initialization.", + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "in": "query", + "name": "start", + "required": false, + "description": "Start date (inclusive), YYYY-MM-DD." + }, + { + "schema": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "in": "query", + "name": "end", + "required": false, + "description": "End date (inclusive), YYYY-MM-DD." + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "end_user_id", + "required": false, + "description": "Filter to a single end user id." + }, + { + "schema": { + "type": "string", + "enum": [ + "full_template_gen", + "block_edit", + "html_import", + "image_import", + "image_generation" + ] + }, + "in": "query", + "name": "feature_type", + "required": false, + "description": "Filter to a single feature type." + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 100 + }, + "in": "query", + "name": "limit", + "required": false, + "description": "Max breakdown rows to return (1-1000)." + }, + { + "schema": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "in": "query", + "name": "offset", + "required": false, + "description": "Number of breakdown rows to skip (pagination)." + }, + { + "schema": { + "type": "string", + "enum": [ + "credits", + "end_user_id", + "feature_type" + ], + "default": "credits" + }, + "in": "query", + "name": "sort", + "required": false, + "description": "Field the breakdown is ordered by. Defaults to credits." + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "desc" + }, + "in": "query", + "name": "order", + "required": false, + "description": "Sort direction. Defaults to desc (highest credits first)." + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "The resource ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "total_credits_used": { + "type": "number", + "description": "Total AI credits used across the full filtered range (not just the returned page)." + }, + "total": { + "type": "number", + "description": "Number of breakdown rows matching the filter (ignores paging)." + }, + "breakdown": { + "type": "array", + "items": { + "type": "object", + "properties": { + "end_user_id": { + "type": [ + "null", + "string" + ], + "description": "The end user id, or null for unattributed usage." + }, + "feature_type": { + "type": "string", + "enum": [ + "full_template_gen", + "block_edit", + "html_import", + "image_import", + "image_generation" + ], + "description": "The partner-facing feature type." + }, + "credits": { + "type": "number", + "description": "AI credits used by this end user and feature type." + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/projects/{id}/ai-credits/webhooks/deliveries": { + "get": { + "operationId": "listProjectAiCreditsWebhookDeliveries", + "summary": "List AI credit webhook deliveries.", + "tags": [ + "ai-credits" + ], + "description": "Returns the webhook delivery history for the project, newest first — the event, delivery status, attempt count, and last response code for each. Use it to spot failed deliveries and drive the retry endpoint. Payloads expose credits only.", + "parameters": [ + { + "schema": { + "type": "string", + "enum": [ + "pending", + "delivered", + "failed" + ] + }, + "in": "query", + "name": "status", + "required": false, + "description": "Filter to a single delivery status." + }, + { + "schema": { + "type": "string", + "enum": [ + "ai.credits.usage_recorded", + "ai.credits.threshold_reached", + "ai.credits.exhausted" + ] + }, + "in": "query", + "name": "event", + "required": false, + "description": "Filter to a single event type." + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "in": "query", + "name": "limit", + "required": false, + "description": "Max deliveries to return (1-100)." + }, + { + "schema": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "in": "query", + "name": "offset", + "required": false, + "description": "Number of deliveries to skip (pagination)." + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "The resource ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deliveries": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "event": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "delivered", + "failed" + ] + }, + "attempts": { + "type": "number" + }, + "last_status_code": { + "type": [ + "null", + "number" + ] + }, + "end_user_id": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "delivered_at": { + "type": [ + "null", + "string" + ] + }, + "payload": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "total": { + "type": "number", + "description": "Total deliveries matching the filter (ignores limit/offset)." + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/projects/{id}/ai-credits/webhooks/deliveries/{deliveryId}/attempts": { + "get": { + "operationId": "listProjectAiCreditsWebhookDeliveryAttempts", + "summary": "List a webhook delivery’s attempts.", + "tags": [ + "ai-credits" + ], + "description": "Returns the per-attempt history for a single delivery, newest attempt first — the response code, error, and time of each POST (including automatic retries). Returns 404 if the delivery is not found for this project.", + "parameters": [ + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "in": "query", + "name": "limit", + "required": false, + "description": "Max attempts to return (1-100)." + }, + { + "schema": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "in": "query", + "name": "offset", + "required": false, + "description": "Number of attempts to skip (pagination)." + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "The project ID" + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "deliveryId", + "required": true, + "description": "The webhook delivery ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "attempts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "attempt": { + "type": "number" + }, + "status_code": { + "type": [ + "null", + "number" + ] + }, + "error": { + "type": [ + "null", + "string" + ] + }, + "attempted_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "total": { + "type": "number", + "description": "Total attempts for the delivery (ignores limit/offset)." + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "409": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "429": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/projects/{id}/ai-credits/webhooks/deliveries/{deliveryId}/retry": { + "post": { + "operationId": "retryProjectAiCreditsWebhookDelivery", + "summary": "Retry a webhook delivery.", + "tags": [ + "ai-credits" + ], + "description": "Re-queues a single previously-failed (or pending) webhook delivery for another attempt. Returns 404 if the delivery is not found for this project, and 409 if it was already delivered.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "The project ID" + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "deliveryId", + "required": true, + "description": "The webhook delivery ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "requeued" + ] + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "409": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "429": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/templates": { + "get": { + "operationId": "listTemplates", + "summary": "List templates", + "tags": [ + "templates" + ], + "description": "List templates with cursor-based pagination. Returns templates in descending order by update time.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "The project ID to list templates for" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "in": "query", + "name": "limit", + "required": false, + "description": "Number of templates to return (1-100)" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false, + "description": "Pagination cursor from previous response" + }, + { + "schema": { + "type": "string", + "enum": [ + "email", + "web", + "document" + ] + }, + "in": "query", + "name": "displayMode", + "required": false, + "description": "Filter by template type" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "name", + "required": false, + "description": "Filter by name (case-insensitive search)" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data", + "has_more" + ], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Template ID" + }, + "name": { + "type": "string", + "description": "Template name" + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "document" + ], + "description": "Template type/display mode" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } + } + }, + "next_cursor": { + "type": "string", + "nullable": true, + "description": "Cursor for the next page. Null if no more results." + }, + "has_more": { + "type": "boolean", + "description": "Whether there are more results after this page" + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/templates/{id}": { + "get": { + "operationId": "getTemplate", + "summary": "Get template by ID.", + "tags": [ + "templates" + ], + "description": "Get template by ID.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "The project ID (required for PAT auth, auto-resolved for API key auth)" + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "The resource ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "document" + ] + }, + "design": { + "type": "object", + "additionalProperties": true + }, + "html": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/templates/convert/full-to-simple": { + "post": { + "operationId": "convertFullToSimple", + "summary": "Convert Full to Simple schema.", + "tags": [ + "templates" + ], + "description": "Convert design json from Full to Simple schema.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "design": { + "type": "object", + "properties": { + "body": { + "type": "object", + "additionalProperties": true + }, + "counters": { + "type": "object", + "additionalProperties": true + }, + "schemaVersion": { + "type": "number" + } + }, + "additionalProperties": true, + "required": [ + "body" + ] + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ], + "default": "email", + "description": "Display mode of the design (email, web, document, popup). Defaults to \"email\", matching /v3/templates/validate. Mode-specific repairs apply during conversion (email caps contentWidth at 900px, for example), so pass the design's actual mode — a web design converted under the email default can be altered." + }, + "includeDefaultValues": { + "type": "boolean", + "default": false + }, + "includeConversion": { + "type": "boolean", + "default": false, + "description": "When true, includes _conversion metadata in the response. This metadata can be passed to simple-to-full to restore original values without data loss." + } + }, + "required": [ + "design" + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "type": "object", + "properties": { + "design": { + "type": "object", + "additionalProperties": true + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "INVALID_DESIGN, or VALIDATION_ERROR for request-schema failures." + }, + "message": { + "type": "string", + "description": "Human-readable summary of the first issues." + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "message", + "code" + ], + "properties": { + "path": { + "type": "string" + }, + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "description": "Issue list for INVALID_DESIGN — same path/message/code shape as POST /v3/templates/validate, capped at 100 entries. Absent on VALIDATION_ERROR." + }, + "errorCount": { + "type": "number", + "description": "Total number of issues found; greater than errors.length when the list was capped." + } + } + } + } + } + } + } + } + }, + "/v3/templates/convert/simple-to-full": { + "post": { + "operationId": "convertSimpleToFull", + "summary": "Convert Simple to Full schema.", + "tags": [ + "templates" + ], + "description": "Convert design json from Simple to Full schema.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "design": { + "type": "object", + "properties": { + "body": { + "type": "object", + "additionalProperties": true + }, + "counters": { + "type": "object", + "additionalProperties": true + }, + "schemaVersion": { + "type": "number" + }, + "_conversion": { + "type": "object", + "properties": { + "data": { + "type": "string" + }, + "version": { + "type": "number" + } + } + } + }, + "additionalProperties": true, + "required": [ + "body" + ] + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ], + "default": "email", + "description": "Display mode of the design (email, web, document, popup). Defaults to \"email\", matching /v3/templates/validate. Mode-specific repairs apply during conversion (email caps contentWidth at 900px, for example), so pass the design's actual mode — a web design converted under the email default can be altered." + }, + "includeDefaultValues": { + "type": "boolean", + "default": false + } + }, + "required": [ + "design" + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "type": "object", + "properties": { + "design": { + "type": "object", + "additionalProperties": true + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "INVALID_DESIGN, or VALIDATION_ERROR for request-schema failures." + }, + "message": { + "type": "string", + "description": "Human-readable summary of the first issues." + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "message", + "code" + ], + "properties": { + "path": { + "type": "string" + }, + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "description": "Issue list for INVALID_DESIGN — same path/message/code shape as POST /v3/templates/validate, capped at 100 entries. Absent on VALIDATION_ERROR." + }, + "errorCount": { + "type": "number", + "description": "Total number of issues found; greater than errors.length when the list was capped." + } + } + } + } + } + } + } + } + }, + "/v3/templates/export/html": { + "post": { + "operationId": "exportHtml", + "summary": "Export HTML", + "tags": [ + "export" + ], + "description": "Export a design as rendered HTML.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "design" + ], + "properties": { + "design": { + "type": "object", + "description": "Unlayer design JSON" + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ] + }, + "customJS": { + "type": [ + "string", + "array" + ], + "items": { + "type": "string" + } + }, + "editorVersion": { + "type": "string" + }, + "mergeTags": { + "type": "object" + }, + "mergeTagsSchema": { + "type": "object" + }, + "designTags": { + "type": "object" + }, + "designTagsConfig": { + "type": "object" + }, + "safeHtml": { + "type": "boolean" + }, + "language": { + "type": "string" + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "The project ID (required for PAT auth, auto-resolved for API key auth)" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "html": { + "type": "string" + }, + "chunks": { + "type": "object", + "properties": { + "css": { + "type": "string" + }, + "js": { + "type": "string" + }, + "body": { + "type": "string" + }, + "fonts": { + "type": "array" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "design": { + "type": "object", + "additionalProperties": true + }, + "amp": { + "type": "object", + "additionalProperties": true + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "422": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "429": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "500": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "502": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "503": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/templates/export/image": { + "post": { + "operationId": "exportImage", + "summary": "Export image", + "tags": [ + "export" + ], + "description": "Export a design as a PNG image.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "design" + ], + "properties": { + "design": { + "type": "object", + "description": "Unlayer design JSON" + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ] + }, + "customJS": { + "type": [ + "string", + "array" + ], + "items": { + "type": "string" + } + }, + "editorVersion": { + "type": "string" + }, + "mergeTags": { + "type": "object" + }, + "mergeTagsSchema": { + "type": "object" + }, + "designTags": { + "type": "object" + }, + "designTagsConfig": { + "type": "object" + }, + "safeHtml": { + "type": "boolean" + }, + "language": { + "type": "string" + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + }, + "fullPage": { + "type": "boolean" + }, + "deviceScaleFactor": { + "type": "number" + } + } + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "The project ID (required for PAT auth, auto-resolved for API key auth)" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "422": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "429": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "500": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "502": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "503": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/templates/export/pdf": { + "post": { + "operationId": "exportPdf", + "summary": "Export PDF", + "tags": [ + "export" + ], + "description": "Export a design as a PDF document.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "design" + ], + "properties": { + "design": { + "type": "object", + "description": "Unlayer design JSON" + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ] + }, + "customJS": { + "type": [ + "string", + "array" + ], + "items": { + "type": "string" + } + }, + "editorVersion": { + "type": "string" + }, + "mergeTags": { + "type": "object" + }, + "mergeTagsSchema": { + "type": "object" + }, + "designTags": { + "type": "object" + }, + "designTagsConfig": { + "type": "object" + }, + "safeHtml": { + "type": "boolean" + }, + "language": { + "type": "string" + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "pageSize": { + "type": "string", + "enum": [ + "Letter", + "Legal", + "Tabloid", + "Ledger", + "A0", + "A1", + "A2", + "A3", + "A4", + "A5", + "A6" + ] + }, + "contentWidth": { + "oneOf": [ + { + "type": "number" + }, + { + "type": "string", + "enum": [ + "full" + ] + } + ] + } + } + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "The project ID (required for PAT auth, auto-resolved for API key auth)" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "422": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "429": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "500": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "502": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "503": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/templates/export/zip": { + "post": { + "operationId": "exportZip", + "summary": "Export ZIP", + "tags": [ + "export" + ], + "description": "Export a design as a ZIP archive containing HTML and assets.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "design" + ], + "properties": { + "design": { + "type": "object", + "description": "Unlayer design JSON" + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ] + }, + "customJS": { + "type": [ + "string", + "array" + ], + "items": { + "type": "string" + } + }, + "editorVersion": { + "type": "string" + }, + "mergeTags": { + "type": "object" + }, + "mergeTagsSchema": { + "type": "object" + }, + "designTags": { + "type": "object" + }, + "designTagsConfig": { + "type": "object" + }, + "safeHtml": { + "type": "boolean" + }, + "language": { + "type": "string" + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "The project ID (required for PAT auth, auto-resolved for API key auth)" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "422": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "429": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "500": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "502": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "503": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/templates/generate": { + "get": { + "responses": { + "200": { + "description": "Default Response" + } + } + }, + "post": { + "operationId": "generateDesign", + "summary": "AI design generation", + "tags": [ + "templates" + ], + "description": "Generate or modify an Unlayer design using AI. Send the conversation as `messages` (today only the last user message is consumed; earlier turns are accepted as chat history) and describe the target with `output.kind` + `output.displayMode`. Pass the current canvas state in `context` (full design JSON + selection pointer) to modify an existing design. Only `anthropic` and `openai` models are supported. To import existing HTML or an image instead, use POST /v3/templates/import.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "messages", + "output" + ], + "properties": { + "model": { + "type": "string", + "description": "Preferred AI model in \"provider/id\" form, e.g. \"anthropic/claude-opus-5\". Optional — server resolves a default per output kind." + }, + "fallbackModels": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "array", + "maxItems": 10, + "items": { + "type": "string", + "maxLength": 200 + } + } + ], + "description": "Transient-outage fallback controls. Omit to use Unlayer defaults only when no model is pinned; true always uses Unlayer defaults; false disables the outage tail; an ordered array replaces the default provider/model strings." + }, + "messages": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": { + "type": "object", + "required": [ + "role", + "content" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "user", + "assistant", + "system" + ] + }, + "content": { + "type": "array", + "minItems": 0, + "maxItems": 50, + "items": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "text", + "image", + "file" + ] + }, + "text": { + "type": "string", + "maxLength": 20000 + }, + "image": { + "type": "string", + "description": "URL or data URL of the image" + }, + "file": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "mediaType": { + "type": "string" + } + }, + "required": [ + "url" + ], + "additionalProperties": true + } + } + } + }, + "metadata": { + "type": "object", + "properties": { + "action": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": true + } + }, + "additionalProperties": true + } + } + }, + "description": "Conversation messages in chronological order, capped at 10 messages. The last `user` message is the prompt for this turn; the newest earlier `user`/`assistant` turns are forwarded within a 12,000-character aggregate history budget. A `user` message may carry a predefined prompt action via `metadata.action.id` (e.g. SPELLING, REPHRASE)." + }, + "output": { + "type": "object", + "required": [ + "kind", + "displayMode" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "template", + "page", + "body", + "header", + "footer", + "row", + "column", + "content", + "text" + ] + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ] + }, + "schemaVersion": { + "type": "integer" + } + } + }, + "context": { + "type": "object", + "properties": { + "fullDesign": { + "type": "object", + "nullable": true, + "additionalProperties": true + }, + "selection": { + "type": "object", + "nullable": true, + "properties": { + "collection": { + "type": "string", + "enum": [ + "pages", + "bodies", + "rows", + "columns", + "contents", + "headers", + "footers" + ] + }, + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "value": { + "type": "string" + } + }, + "required": [ + "collection", + "id" + ], + "additionalProperties": true + }, + "availableTools": { + "type": "array", + "items": { + "type": "string" + } + }, + "availableFonts": { + "type": "array", + "maxItems": 100, + "items": { + "type": "object", + "required": [ + "label", + "value" + ], + "properties": { + "label": { + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 500 + } + }, + "additionalProperties": false + } + }, + "customTools": { + "type": "array", + "items": { + "type": "object", + "required": [ + "slug", + "options" + ], + "properties": { + "slug": { + "type": "string" + }, + "options": { + "type": "object", + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + "brand": { + "type": "object", + "nullable": true, + "properties": { + "companyName": { + "type": "string", + "maxLength": 200 + }, + "productDescription": { + "type": "string", + "maxLength": 2000 + }, + "targetAudience": { + "type": "string", + "maxLength": 2000 + }, + "colors": { + "type": "object", + "properties": { + "primary": { + "type": "string" + }, + "secondary": { + "type": "string" + }, + "accent": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fonts": { + "type": "object", + "properties": { + "heading": { + "type": "string", + "maxLength": 200 + }, + "body": { + "type": "string", + "maxLength": 200 + } + }, + "additionalProperties": false + }, + "logos": { + "type": "object", + "properties": { + "primary": { + "type": "string", + "maxLength": 2048, + "format": "uri", + "pattern": "^https://" + }, + "secondary": { + "type": "string", + "maxLength": 2048, + "format": "uri", + "pattern": "^https://" + } + }, + "additionalProperties": false + }, + "voice": { + "type": "string", + "maxLength": 2000 + }, + "guidelines": { + "type": "string", + "maxLength": 2000 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": true + }, + "locale": { + "type": "string", + "maxLength": 100, + "pattern": "^[A-Za-z0-9]{1,8}(?:-[A-Za-z0-9]{1,8})*$", + "description": "BCP-47 fallback locale for AI status messages." + }, + "conversationId": { + "type": "string", + "description": "Reserved for future server-side conversation memory." + } + } + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "The project ID (required for PAT auth, auto-resolved for API key auth)" + } + ], + "responses": { + "200": { + "description": "The generated (or modified) design plus model metadata and optional usage metadata.", + "content": { + "application/json": { + "schema": { + "description": "The generated (or modified) design plus model metadata and optional usage metadata.", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Provider response id for the generation turn." + }, + "output": { + "type": "object", + "description": "The generated output for the requested block.", + "properties": { + "kind": { + "type": "string", + "description": "Echoes the requested `output.kind`." + }, + "data": { + "type": "object", + "additionalProperties": true, + "description": "The generated design JSON, scoped to the requested kind (the full design for template/page/body; the row/column/content/element for narrower kinds)." + } + } + }, + "model": { + "type": "object", + "description": "The provider + model that actually produced the output (may differ from the requested model after failover).", + "properties": { + "provider": { + "type": "string", + "description": "e.g. \"anthropic\", \"openai\"." + }, + "id": { + "type": "string", + "description": "Resolved model id, e.g. \"claude-opus-5\"." + } + } + }, + "usage": { + "type": "object", + "description": "Aggregate token usage and billed AI credits for the turn. Estimated provider cost is included only by builder copilot endpoints in local/dev/QA.", + "properties": { + "inputTokens": { + "type": "number" + }, + "outputTokens": { + "type": "number" + }, + "totalTokens": { + "type": "number" + }, + "cachedInputTokens": { + "type": "number" + }, + "reasoningTokens": { + "type": "number" + }, + "aiCreditsUsed": { + "type": "number", + "description": "Marked-up integer AI credits used by the complete turn, including failover attempts." + }, + "estimatedCostMicroUsd": { + "type": "number" + } + } + } + } + } + } + } + }, + "304": { + "description": "No changes detected — AI output is identical to input", + "content": { + "application/json": { + "schema": { + "description": "No changes detected — AI output is identical to input" + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "429": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/templates/import": { + "post": { + "operationId": "importTemplate", + "summary": "Import a template from HTML or an image", + "tags": [ + "templates" + ], + "description": "Import an existing template from HTML or an image (URL or base64) and return the resulting Unlayer design JSON. No template DB entry is created.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "displayMode", + "input" + ], + "properties": { + "model": { + "type": "string", + "maxLength": 200, + "description": "Preferred AI model. Accepts a provider/model string (e.g. \"anthropic/claude-opus-5\", \"openai/gpt-5.6-luna\"), a bare provider (\"anthropic\", \"openai\") which uses that provider's default model, or a bare model id (\"claude-opus-5\", \"gpt-5.6-luna\") with the provider inferred from the name. Optional — defaults to anthropic/claude-opus-5." + }, + "fallbackModels": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "array", + "maxItems": 10, + "items": { + "type": "string", + "maxLength": 200 + } + } + ], + "description": "Transient-outage fallback controls. Omit to use Unlayer defaults only when no model is pinned; true always uses Unlayer defaults; false disables the outage tail; an ordered array replaces the default provider/model strings." + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ], + "description": "Display mode for the imported design" + }, + "input": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "html", + "image", + "text" + ], + "description": "The type of input part. \"html\" or \"image\" carries the source content; \"text\" carries optional instructions to apply during import." + }, + "html": { + "type": "string", + "description": "HTML string to import (for type: \"html\")" + }, + "url": { + "type": "string", + "description": "Image URL to import (for type: \"image\")" + }, + "data": { + "type": "string", + "description": "Base64 image data URL, e.g. \"data:image/png;base64,…\" (for type: \"image\")" + }, + "text": { + "type": "string", + "maxLength": 20000, + "description": "Optional natural-language instructions to apply during import (for type: \"text\")" + } + } + }, + "description": "Array of input parts. Must contain exactly one \"html\" or \"image\" part; may also contain one or more \"text\" parts with optional instructions." + } + } + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": false, + "description": "The project ID (required for PAT auth, auto-resolved for API key auth)" + } + ], + "responses": { + "200": { + "description": "Successfully imported template", + "content": { + "application/json": { + "schema": { + "description": "Successfully imported template", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "output": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "blockType": { + "type": "string" + }, + "data": { + "type": "object", + "additionalProperties": true, + "description": "Imported design data" + } + } + }, + "model": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "usage": { + "type": "object", + "properties": { + "inputTokens": { + "type": "integer" + }, + "outputTokens": { + "type": "integer" + }, + "totalTokens": { + "type": "integer" + }, + "reasoningTokens": { + "type": "integer" + }, + "cachedInputTokens": { + "type": "integer" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/templates/schema": { + "get": { + "operationId": "getDesignSchema", + "summary": "Get the design JSON Schema.", + "tags": [ + "templates" + ], + "description": "Returns the canonical design schema as a standard JSON Schema document — the exact schema POST /v3/templates/validate checks against, ready to plug into any JSON Schema validator or editor tooling. Serves the Full schema by default; pass simple=true for the compact Simple schema. No authentication required. Responses carry a strong ETag and long-lived cache headers; send If-None-Match to revalidate for free.", + "parameters": [ + { + "schema": { + "type": "boolean", + "default": false + }, + "in": "query", + "name": "simple", + "required": false, + "description": "When true, returns the Simple schema instead of the Full schema." + }, + { + "schema": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ], + "default": "email" + }, + "in": "query", + "name": "displayMode", + "required": false, + "description": "Display mode whose rules the schema describes (email, web, document, popup). Defaults to \"email\"." + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + } + }, + "/v3/templates/validate": { + "post": { + "operationId": "validateDesign", + "summary": "Validate a design against the Unlayer schema.", + "tags": [ + "templates" + ], + "description": "Validate a design JSON against the Unlayer design schema. Returns { success: true, data: { valid: true } } when the payload conforms; otherwise data is { valid: false, errors: [...] } with descriptive issues. Every checked design gets HTTP 200 — `data.valid` is the source of truth, not the status code. Only malformed requests (e.g. a missing design field or an unknown displayMode) fail request validation with 400 VALIDATION_ERROR.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "design": { + "type": "object", + "description": "The design JSON to validate.", + "additionalProperties": true + }, + "schema": { + "type": "string", + "enum": [ + "full", + "simple" + ], + "default": "full", + "description": "Which form of the schema to validate against. Defaults to \"full\"." + }, + "displayMode": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ], + "default": "email", + "description": "Display mode for the design (email, web, document, popup). Some validation rules differ per mode. Defaults to \"email\" — without a default, options from every mode would apply at once, the strictest possible check, and real editor-saved designs could be reported invalid." + }, + "migrate": { + "type": "boolean", + "default": true, + "description": "When true (default), a full-form design with an older schemaVersion is upgraded to the current schema before validating — matching how the editor and the convert endpoints treat stored designs. Designs without a schemaVersion predate versioning and are fully migrated the same way. Set to false to check strict conformance with the current schema version. Designs with a newer schemaVersion than this API knows are validated as-if-current." + }, + "customTools": { + "type": "array", + "description": "Custom tool declarations, in the same shape passed to unlayer.registerTool. When provided, blocks matching a declared tool have their values checked against the tool's declared options (wrong types are reported at their exact path). Blocks of undeclared tools keep envelope-only validation.", + "maxItems": 100, + "items": { + "type": "object", + "required": [ + "slug", + "options" + ], + "properties": { + "slug": { + "type": "string" + }, + "type": { + "type": "string", + "default": "custom" + }, + "label": { + "type": "string" + }, + "options": { + "type": "object", + "maxProperties": 100, + "additionalProperties": { + "type": "object", + "properties": { + "options": { + "type": "object", + "maxProperties": 200 + } + } + } + }, + "values": { + "type": "object", + "additionalProperties": true + }, + "supportedDisplayModes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "email", + "web", + "popup", + "document" + ] + } + } + }, + "additionalProperties": true + } + } + }, + "required": [ + "design" + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "success", + "data" + ], + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "data": { + "type": "object", + "required": [ + "valid" + ], + "properties": { + "valid": { + "type": "boolean" + }, + "migratedFrom": { + "type": "number", + "description": "Present when the design was upgraded from an older schemaVersion before validation; carries the original version number." + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "message", + "code" + ], + "properties": { + "path": { + "type": "string" + }, + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "description": "Populated when valid is false, capped at 100 entries. Each issue carries the dotted path to the offending field, a human-readable message, and the underlying Zod issue code." + }, + "errorCount": { + "type": "number", + "description": "Total number of issues found; greater than errors.length when the list was capped." + } + } + } + } + } + } + } + }, + "400": { + "description": "The request itself is malformed — e.g. the design field is missing or displayMode is unknown. The design was not checked.", + "content": { + "application/json": { + "schema": { + "description": "The request itself is malformed — e.g. the design field is missing or displayMode is unknown. The design was not checked.", + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "VALIDATION_ERROR" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/webhooks": { + "get": { + "operationId": "listWebhooks", + "summary": "List webhooks", + "tags": [ + "webhooks" + ], + "description": "List all webhook endpoints configured for a project.", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Webhook ID" + }, + "url": { + "type": "string", + "description": "The HTTPS URL receiving webhook events" + }, + "events": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "email.sent", + "email.delivered", + "email.bounced", + "email.complained" + ] + }, + "description": "Event types this webhook is subscribed to" + }, + "active": { + "type": "boolean", + "description": "Whether the webhook is actively receiving events" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "When the webhook was created" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + }, + "post": { + "operationId": "createWebhook", + "summary": "Create a webhook", + "tags": [ + "webhooks" + ], + "description": "Create a new webhook endpoint. A signing secret is auto-generated and returned once. Use it to verify webhook signatures.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The HTTPS URL to receive webhook events" + }, + "events": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "email.sent", + "email.delivered", + "email.bounced", + "email.complained" + ] + }, + "description": "Event types to subscribe to. If omitted or empty, all events are sent." + }, + "active": { + "type": "boolean", + "default": true, + "description": "Whether the webhook is active" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Webhook ID" + }, + "url": { + "type": "string", + "description": "The HTTPS URL receiving webhook events" + }, + "events": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "email.sent", + "email.delivered", + "email.bounced", + "email.complained" + ] + }, + "description": "Event types this webhook is subscribed to" + }, + "active": { + "type": "boolean", + "description": "Whether the webhook is actively receiving events" + }, + "secret": { + "type": "string", + "description": "Signing secret — only returned on creation. Store it securely; you will not be able to retrieve it again." + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "When the webhook was created" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/webhooks/{id}": { + "get": { + "operationId": "getWebhook", + "summary": "Get webhook details", + "tags": [ + "webhooks" + ], + "description": "Get details of a specific webhook endpoint.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "Webhook ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Webhook ID" + }, + "url": { + "type": "string", + "description": "The HTTPS URL receiving webhook events" + }, + "events": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "email.sent", + "email.delivered", + "email.bounced", + "email.complained" + ] + }, + "description": "Event types this webhook is subscribed to" + }, + "active": { + "type": "boolean", + "description": "Whether the webhook is actively receiving events" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "When the webhook was created" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "When the webhook was last updated" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + }, + "patch": { + "operationId": "updateWebhook", + "summary": "Update a webhook", + "tags": [ + "webhooks" + ], + "description": "Update a webhook endpoint URL, events, or active status.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The HTTPS URL to receive webhook events" + }, + "events": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "email.sent", + "email.delivered", + "email.bounced", + "email.complained" + ] + }, + "description": "Event types to subscribe to. If omitted or empty, all events are sent." + }, + "active": { + "type": "boolean", + "description": "Whether the webhook is actively receiving events" + } + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "Webhook ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Webhook ID" + }, + "url": { + "type": "string", + "description": "The HTTPS URL receiving webhook events" + }, + "events": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "email.sent", + "email.delivered", + "email.bounced", + "email.complained" + ] + }, + "description": "Event types this webhook is subscribed to" + }, + "active": { + "type": "boolean", + "description": "Whether the webhook is actively receiving events" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "When the webhook was last updated" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + }, + "delete": { + "operationId": "deleteWebhook", + "summary": "Delete a webhook", + "tags": [ + "webhooks" + ], + "description": "Delete a webhook endpoint. It will no longer receive events.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "Webhook ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/webhooks/{id}/rotate-secret": { + "post": { + "operationId": "rotateWebhookSecret", + "summary": "Rotate webhook signing secret", + "tags": [ + "webhooks" + ], + "description": "Generate a new signing secret for a webhook. The new secret is returned once — store it securely. The old secret is invalidated immediately.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true, + "description": "Webhook ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "secret": { + "type": "string", + "description": "New signing secret — only returned once. Store it securely." + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/workspaces": { + "get": { + "operationId": "listWorkspaces", + "summary": "List accessible workspaces.", + "tags": [ + "workspaces" + ], + "description": "Get all workspaces accessible by the current token. Requires a Personal Access Token (PAT).", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + }, + "/v3/workspaces/{workspaceId}": { + "get": { + "operationId": "getWorkspace", + "summary": "Get workspace by ID.", + "tags": [ + "workspaces" + ], + "description": "Get a specific workspace by ID with its projects. Requires a Personal Access Token (PAT).", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "workspaceId", + "required": true, + "description": "The workspace ID" + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "projects": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "401": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "403": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + }, + "404": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + } + }, + "required": [ + "error", + "message" + ] + } + } + } + } + } + } + } + }, + "security": [ + { + "apiKeyAuth": [] + }, + { + "personalAccessTokenAuth": [] + } + ], + "tags": [ + { + "name": "projects", + "description": "Project details and configuration." + }, + { + "name": "templates", + "description": "Template management — list, retrieve, generate, import, export, and convert designs." + }, + { + "name": "workspaces", + "description": "Workspace access and management." + }, + { + "name": "blocks", + "description": "Reusable design blocks — list shared project blocks and end-user saved blocks for backup, migration, and usage reporting." + }, + { + "name": "ai-credits", + "description": "AI credit balance, usage breakdown, and webhook/alert settings. Credits are pooled per workspace; settings are per project." + } + ], + "x-tag-groups": [ + { + "name": "Resources", + "tags": [ + "projects", + "templates", + "workspaces", + "blocks", + "ai-credits" + ] + } + ], + "servers": [ + { + "url": "https://api.unlayer.com" + } + ] +} diff --git a/package.json b/package.json index af883de..7e238c5 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,25 @@ "name": "@unlayer/sdk", "version": "0.1.0", "description": "The official TypeScript library for the Unlayer API", - "author": "Unlayer ", - "types": "dist/index.d.ts", - "main": "dist/index.js", + "author": "Unlayer", + "types": "dist/index.d.mts", + "main": "dist/index.cjs", + "module": "dist/index.mjs", "type": "commonjs", - "repository": "github:unlayer/unlayer-typescript", + "repository": { + "type": "git", + "url": "git+https://github.com/unlayer/unlayer-typescript.git" + }, + "homepage": "https://github.com/unlayer/unlayer-typescript#readme", + "bugs": { + "url": "https://github.com/unlayer/unlayer-typescript/issues" + }, "license": "Apache-2.0", - "packageManager": "yarn@1.22.22", + "packageManager": "pnpm@11.20.0", + "engines": { + "node": ">=20" + }, + "sideEffects": false, "files": [ "**/*" ], @@ -17,53 +29,47 @@ "access": "public" }, "scripts": { - "test": "./scripts/test", + "test": "./scripts/lint", "build": "./scripts/build", - "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", + "generate": "openapi-ts && node scripts/postprocess-sdk.cjs", + "sync-spec": "node scripts/sync-openapi.cjs", + "prepublishOnly": "echo 'packages are released by the GitHub release workflow' && exit 1", "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": {}, "devDependencies": { - "@arethetypeswrong/cli": "^0.17.0", - "@swc/core": "^1.3.102", - "@swc/jest": "^0.2.29", - "@types/jest": "^29.4.0", - "@types/node": "^20.17.6", - "@typescript-eslint/eslint-plugin": "8.31.1", - "@typescript-eslint/parser": "8.31.1", - "eslint": "^9.39.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": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", - "tsconfig-paths": "^4.0.0", - "tslib": "^2.8.1", + "@arethetypeswrong/cli": "0.18.5", + "@hey-api/openapi-ts": "0.98.2", + "eslint": "9.39.1", + "eslint-plugin-prettier": "5.4.1", + "eslint-plugin-unused-imports": "4.1.4", + "prettier": "3.1.1", + "publint": "0.3.23", + "tsdown": "0.22.14", "typescript": "5.8.3", "typescript-eslint": "8.31.1" }, "exports": { ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.js" - }, - "./*.mjs": { - "default": "./dist/*.mjs" - }, - "./*.js": { - "default": "./dist/*.js" + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } }, - "./*": { - "import": "./dist/*.mjs", - "require": "./dist/*.js" + "./client": { + "import": { + "types": "./dist/client/index.d.mts", + "default": "./dist/client/index.mjs" + }, + "require": { + "types": "./dist/client/index.d.cts", + "default": "./dist/client/index.cjs" + } } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..dcdae31 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2463 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + js-yaml: 4.3.1 + +importers: + + .: + devDependencies: + '@arethetypeswrong/cli': + specifier: 0.18.5 + version: 0.18.5 + '@hey-api/openapi-ts': + specifier: 0.98.2 + version: 0.98.2(typescript@5.8.3) + eslint: + specifier: 9.39.1 + version: 9.39.1(jiti@2.7.0)(supports-color@7.2.0) + eslint-plugin-prettier: + specifier: 5.4.1 + version: 5.4.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(prettier@3.1.1) + eslint-plugin-unused-imports: + specifier: 4.1.4 + version: 4.1.4(@typescript-eslint/eslint-plugin@8.31.1(@typescript-eslint/parser@8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3))(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3))(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0)) + prettier: + specifier: 3.1.1 + version: 3.1.1 + publint: + specifier: 0.3.23 + version: 0.3.23 + tsdown: + specifier: 0.22.14 + version: 0.22.14(@arethetypeswrong/core@0.18.5)(publint@0.3.23)(typescript@5.8.3) + typescript: + specifier: 5.8.3 + version: 5.8.3 + typescript-eslint: + specifier: 8.31.1 + version: 8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3) + +packages: + + '@andrewbranch/untar.js@1.0.4': + resolution: {integrity: sha512-pVXSwPsLuw8IGLo2Di0EaOfsk+ntVvpkk942J/sHYIkwvtKUakEcPh7HBgZ6tuimgzKSEHgCvO4XgQ05DEbwDw==} + + '@arethetypeswrong/cli@0.18.5': + resolution: {integrity: sha512-gM+8vRsQOD/Uc7EnBedUhkG5OCsDWE4uoak5QvomGpMpaky0Eh41p04nIMgrWb8EOmqZUJGc6zz9hsP6E56R7g==} + engines: {node: '>=20'} + hasBin: true + + '@arethetypeswrong/core@0.18.5': + resolution: {integrity: sha512-9ytjzGwxjm9Uz7I9avfbt5vlQt6uk9uRRESzJjqrznl6WKvI6dwYTo+vJ3U02Wrq/mR3iql/PzhvHhKdJIAjDQ==} + engines: {node: '>=20'} + + '@braidai/lang@1.1.2': + resolution: {integrity: sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.6': + resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@hey-api/codegen-core@0.9.0': + resolution: {integrity: sha512-OK9/R8WuujwgvnrDIPnEiIf6WnfUOi3GaEr6kIngqoI5FUQwYbeDKHE/frTVUl2A76ZQPCrMknHtPx6Gqtwf8Q==} + engines: {node: '>=22.18.0'} + + '@hey-api/json-schema-ref-parser@1.4.3': + resolution: {integrity: sha512-UzGSDzh3QUhrnwl4atnHc2YqDO6KemYVEOwl1Ynowm/tcr0XlpdHOpyWr5UaWIJfiXTXdYRIC9k2Yxm19pcPzQ==} + engines: {node: '>=22.18.0'} + + '@hey-api/openapi-ts@0.98.2': + resolution: {integrity: sha512-2nVJXH8tpFPGTBOhxyjEd1Jw0hsRqJqeTQW3kltAjVdSU4YWxeu97x5sgNOmsbsfeg6Dqz7Wfzs26walBOuswA==} + engines: {node: '>=22.18.0'} + hasBin: true + peerDependencies: + typescript: '>=5.5.3 || >=6.0.0 || 6.0.1-rc' + + '@hey-api/shared@0.4.8': + resolution: {integrity: sha512-29Pg2FB0UW20pplYgcfiQn1hQYpbZ9D2gdDJc7nDK3xh3pvHOTGP0v3R2ueFpFnw9GN1SRhIdhiVuAYWMDimjA==} + engines: {node: '>=22.18.0'} + + '@hey-api/spec-types@0.2.0': + resolution: {integrity: sha512-ibQ8Is7evMavzr8GNyJCcTg975d8DpaMUyLmOrQ85UBdy1l6t1KuRAwgChAbesJsIlNV6gjmlXruWyegDX18Fg==} + + '@hey-api/types@0.1.4': + resolution: {integrity: sha512-thWfawrDIP7wSI9ioT13I5soaaqB5vAPIiZmgD8PbeEVKNrkonc0N/Sjj97ezl7oQgusZmaNphGdMKipPO6IBg==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + + '@loaderkit/resolve@1.0.6': + resolution: {integrity: sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==} + + '@lukeed/ms@2.0.2': + resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} + engines: {node: '>=8'} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@oxc-project/types@0.144.0': + resolution: {integrity: sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==} + + '@pkgr/core@0.3.6': + resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} + engines: {node: ^14.18.0 || >=16.0.0} + + '@publint/pack@0.1.6': + resolution: {integrity: sha512-3uVNyGcVplhPZSLVyeIpL7+cIRn1YCSNHLG/rUIlBQMVH8YuN9++YF+5+UDIIO9RW98dujiUoTltO7RDB5bFJA==} + engines: {node: '>=18'} + + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + + '@rolldown/binding-android-arm64@1.2.4': + resolution: {integrity: sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.4': + resolution: {integrity: sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.4': + resolution: {integrity: sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.4': + resolution: {integrity: sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': + resolution: {integrity: sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.4': + resolution: {integrity: sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.4': + resolution: {integrity: sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.4': + resolution: {integrity: sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.4': + resolution: {integrity: sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.4': + resolution: {integrity: sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.4': + resolution: {integrity: sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.4': + resolution: {integrity: sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.4': + resolution: {integrity: sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.4': + resolution: {integrity: sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@typescript-eslint/eslint-plugin@8.31.1': + resolution: {integrity: sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/parser@8.31.1': + resolution: {integrity: sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.31.1': + resolution: {integrity: sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.31.1': + resolution: {integrity: sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/types@8.31.1': + resolution: {integrity: sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.31.1': + resolution: {integrity: sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.31.1': + resolution: {integrity: sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.31.1': + resolution: {integrity: sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@yuku-codegen/binding-android-arm64@0.8.7': + resolution: {integrity: sha512-C/0zV5IhgVdYhGJTwrY0v8dknxlhiKwtVJkMUaexu9/QvRmzlV4vfU3hZlUSgqc2BxQHntL1mCVbDq8j0FRFDw==} + cpu: [arm64] + os: [android] + + '@yuku-codegen/binding-darwin-arm64@0.8.7': + resolution: {integrity: sha512-/u+REDMI4a0/lsJXTM4c53/w31OGZLOReZIyg62uhgLs0kc8NHsj/nOcxTdlQjq5gi0zhdkccD9LaLTXcdzPvw==} + cpu: [arm64] + os: [darwin] + + '@yuku-codegen/binding-darwin-x64@0.8.7': + resolution: {integrity: sha512-sMMzFOwCo4WXR+/6zIBThOocSC50iIIZZdfiIDbaLvj0Ax/rWt/iavyfEAqajyvzydLyCqR/ZItdLWSRlu1umw==} + cpu: [x64] + os: [darwin] + + '@yuku-codegen/binding-freebsd-x64@0.8.7': + resolution: {integrity: sha512-MpdpKXix9P+Y1rKgjvcNeNtGjXeL1CmttNhYINrWls8kRpm4xM/oBGTmn6w7to8lAlwj5jm8q03dQPl5mRv4Qw==} + cpu: [x64] + os: [freebsd] + + '@yuku-codegen/binding-linux-arm-gnu@0.8.7': + resolution: {integrity: sha512-rr1srFLlPAmC1vtxfc9C1YLDe3iH09YjfSeeIidBqKhzx1MATjOAq4mjlRUOnhr/L27MotWIYOFKwVsd5JZFOg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm-musl@0.8.7': + resolution: {integrity: sha512-eAufXh8qBRpiSO6ueaMDL+yyoXIGLhpUce72YbcACtZU2qhExwBIJyEtQf5kH2Ki0X2aqkSjSfog4OPtKXan3Q==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-arm64-gnu@0.8.7': + resolution: {integrity: sha512-gw4w6wPoHObBrdIC4duVWLmJOvpdE25j5D7yrM5mACNlK4klRz/lv8hK+ssQk9EJHBgZjSaqZIJVFgqNYbfv7A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm64-musl@0.8.7': + resolution: {integrity: sha512-L68N6Y4XkqcIaKo3Ra88JEvBEH4AHff44A4INcrxeVWZ8CZtu2tCpfVxe3hR8qQQMcvBSQlDn24KpkCKEhVvfA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-x64-gnu@0.8.7': + resolution: {integrity: sha512-dzyAbltJmf3Cqlb8HcFuYIf5Yn0fl1vTr3XJ9HiVNNvOlhqPSArOqtw9vI1p6/VTXTjrMLXlU+s+/kNHiIy/Cw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-x64-musl@0.8.7': + resolution: {integrity: sha512-Otw4MH3404q0Bbvl+YTdW9aoUV5vXmUw8260bWvt1XlaoIX/ceSgI4ygheRDMfBPoHt6FDayQaO9OLVUZAgkFA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-win32-arm64@0.8.7': + resolution: {integrity: sha512-qo/jyrzryiBuKEsFiuWaBCBe3tRMynQ0qFWFgOEjcCMQeZfBm+wKiVEUEFXXLc7bh8YezguAWp0Mtnhq4ARNyA==} + cpu: [arm64] + os: [win32] + + '@yuku-codegen/binding-win32-x64@0.8.7': + resolution: {integrity: sha512-D5lDsVDx6m00E6bWySlWdH72Ca4TPSaphDqB6QjU6MpuNLIJqoGoatYyq2rOmBE8Zv/kunot/o58KGL03P3eiA==} + cpu: [x64] + os: [win32] + + '@yuku-parser/binding-android-arm64@0.8.7': + resolution: {integrity: sha512-eGKYiUDX7Y0V7tDTmg+JTVnXnjMqfXXsorZ+EDf5kxwchQ3Or1HS14MzI2fw+jFhHR85fCWt+mtX33Yao73hIQ==} + cpu: [arm64] + os: [android] + + '@yuku-parser/binding-darwin-arm64@0.8.7': + resolution: {integrity: sha512-Re0RHelKLnjEURulY2/KxW+Ngb8zuNA4BRZuMwgGQNzVumT6u4U2N2hc01oeYVNVof0i7GrXE4UCNBgbpRRnjQ==} + cpu: [arm64] + os: [darwin] + + '@yuku-parser/binding-darwin-x64@0.8.7': + resolution: {integrity: sha512-Hn8DROtQkjlA1ACbPgj4a7eP9IuVOI504oiTwpkWPbpaDWD9KdmnVYCqW+1LfenNK/g7O9NhWGpXEdaCNX7lIA==} + cpu: [x64] + os: [darwin] + + '@yuku-parser/binding-freebsd-x64@0.8.7': + resolution: {integrity: sha512-bAP2OV8wRuzplX/jYxv9+vvqQT8JxyNphI8fLfXGL054Xs+4/J5u33cIm3y4rxY8rdoLmmdiJs2Tq7r7lrDRfA==} + cpu: [x64] + os: [freebsd] + + '@yuku-parser/binding-linux-arm-gnu@0.8.7': + resolution: {integrity: sha512-kTYwJQQgmZeAWdDIWabiReIZMpmfLueIj1tCmjStUtFGhR1Z0qwxonKVfUC4N7h/VhGGzLZ//7O1kgt1QKqgCg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm-musl@0.8.7': + resolution: {integrity: sha512-uL4jE8HPT2BLlxAXyD10LqgPuXa9eDa0BKpCdSANmzIJghq/2eZo3/gQNtaxPZMupWoxjYzSad9IXrwu7aYPXQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-arm64-gnu@0.8.7': + resolution: {integrity: sha512-3gVN4pWSKZmXiNX7cU164dR9MPvesCHnlH6nPfpK+yQsCuYphjKKplcb4SnZBrhiqmXbgb2HR0c2TS0IZUPhgA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm64-musl@0.8.7': + resolution: {integrity: sha512-S0mwfEjoLpxzXeZw802Wa4RaELsQiPtWqG6INcy8j4GtvNFtl4LCX3eGO1XLn9pyLAISLzTRyU3zUCBUPin8lg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-x64-gnu@0.8.7': + resolution: {integrity: sha512-lnbWdPmerE5D1uH1G4IEZKnPzCrWCStRGrtgpSIe1RibAo5bZIjDbbbPYXmMHCEh4F+x/JaJpElh26a3r+BPbg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-x64-musl@0.8.7': + resolution: {integrity: sha512-769uwndMvMzUvATWbAcEvyLHKA+DzhHSCl/obBUrRdYfRo26yxui6S8y3z7uJ+Naup7UKrDxrpK7OnQkxkl9KQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-win32-arm64@0.8.7': + resolution: {integrity: sha512-mEB/9PlaAkisJ6KWGz0zvywXoU6+80dTlR2LwS7s/jcXXoU6fm2+sitBZXtqu3+Q4DcDgPxM45uWMCzPs0TSRw==} + cpu: [arm64] + os: [win32] + + '@yuku-parser/binding-win32-x64@0.8.7': + resolution: {integrity: sha512-8vNB2DP0ou61nGb8tc/qfi41gfyDXz1MHr2zqL3nR+cJ6CEbiuWV/l/a/vv151gCgiZLLAyGkQGENpozdg716w==} + cpu: [x64] + os: [win32] + + '@yuku-toolchain/types@0.8.7': + resolution: {integrity: sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.3.0: + resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansis@4.3.1: + resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} + engines: {node: '>=14'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.18: + resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} + + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + c12@3.3.4: + resolution: {integrity: sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==} + peerDependencies: + magicast: '*' + peerDependenciesMeta: + magicast: + optional: true + + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + dts-resolver@3.0.0: + resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emojilib@2.4.0: + resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-prettier@5.4.1: + resolution: {integrity: sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-unused-imports@4.1.4: + resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + exsolve@1.1.1: + resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + get-tsconfig@5.0.0-beta.5: + resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} + engines: {node: '>=20.20.0'} + + giget@3.3.1: + resolution: {integrity: sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + hookable@6.1.1: + resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-without-cache@0.4.0: + resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} + engines: {node: ^22.18.0 || >=24.0.0} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + marked-terminal@7.3.0: + resolution: {integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==} + engines: {node: '>=16.0.0'} + peerDependencies: + marked: '>=1 <16' + + marked@9.1.6: + resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} + engines: {node: '>= 16'} + hasBin: true + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-emoji@2.2.0: + resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} + engines: {node: '>=18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + ohash@2.0.12: + resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==} + + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + pkg-types@2.3.1: + resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} + engines: {node: '>=6.0.0'} + + prettier@3.1.1: + resolution: {integrity: sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==} + engines: {node: '>=14'} + hasBin: true + + publint@0.3.23: + resolution: {integrity: sha512-5MQipUPcB7MWw84zLUkHrg/H/UBtk3LL+A0GngTTBSsiNJLQurMUaSIRG3edlOrRz4UFe0AOKK9TZdIWviV+jQ==} + engines: {node: '>=18'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + rc9@3.0.1: + resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} + + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rolldown-plugin-dts@0.27.14: + resolution: {integrity: sha512-ZvuDDwoIpRK9RPxDXratCpklFO9QZZWndf/sd0VBFb4LEj0jj07UcHK9OCh7V4XiFz2Z89ziyBC2K6tJiDjrbw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@typescript/native-preview': '*' + '@volar/typescript': ~2.4.0 + rolldown: ^1.0.0 + typescript: ^5.0.0 || ^6.0.0 || ~7.0.0 + vue-tsc: ~3.2.0 || ~3.3.0 + peerDependenciesMeta: + '@typescript/native-preview': + optional: true + '@volar/typescript': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.2.4: + resolution: {integrity: sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + semver@7.8.2: + resolution: {integrity: sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + skin-tone@2.0.0: + resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} + engines: {node: '>=8'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-hyperlinks@3.2.0: + resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} + engines: {node: '>=14.18'} + + synckit@0.11.13: + resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} + engines: {node: ^14.18.0 || >=16.0.0} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tsdown@0.22.14: + resolution: {integrity: sha512-ule7Y+fsAN2iZbLDoo7C4KYljFJNJJ+fLshyn+9gozeTspVersWHxwdGB+Dm2hzA38s6muFnUTl0jK3vJm9ifQ==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.22.14 + '@tsdown/exe': 0.22.14 + '@vitejs/devtools': '*' + publint: ^0.3.8 + tsx: '*' + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 + unplugin-unused: ^0.5.0 + unrun: '*' + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.31.1: + resolution: {integrity: sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + typescript@5.6.1-rc: + resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + + unicode-emoji-modifier-base@1.0.0: + resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} + engines: {node: '>=4'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + verkit@0.3.2: + resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} + engines: {node: '>=18.12.0'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs@16.2.2: + resolution: {integrity: sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==} + engines: {node: '>=10'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yuku-ast@0.8.7: + resolution: {integrity: sha512-h6+4bDfyootiMB9vckk5uKo5r5j0GHrkr17FQTDNfEsFT3DWlN9uu1HJwQwc64pgmLCI945fWM3lbTIqxjT3GQ==} + + yuku-codegen@0.8.7: + resolution: {integrity: sha512-adwDZSh8oVDzhE6Du9PwVWxcOxeV0e2EVhUuMKWfhSY4wkrDq9eqixlxFF3l/XGUV1E7UFzhpz9393MUumkyNw==} + + yuku-parser@0.8.7: + resolution: {integrity: sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ==} + +snapshots: + + '@andrewbranch/untar.js@1.0.4': {} + + '@arethetypeswrong/cli@0.18.5': + dependencies: + '@arethetypeswrong/core': 0.18.5 + chalk: 4.1.2 + cli-table3: 0.6.5 + commander: 10.0.1 + marked: 9.1.6 + marked-terminal: 7.3.0(marked@9.1.6) + semver: 7.8.5 + + '@arethetypeswrong/core@0.18.5': + dependencies: + '@andrewbranch/untar.js': 1.0.4 + '@loaderkit/resolve': 1.0.6 + cjs-module-lexer: 1.4.3 + fflate: 0.8.3 + lru-cache: 11.5.2 + semver: 7.8.5 + typescript: 5.6.1-rc + validate-npm-package-name: 5.0.1 + + '@braidai/lang@1.1.2': {} + + '@colors/colors@1.5.0': + optional: true + + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))': + dependencies: + eslint: 9.39.1(jiti@2.7.0)(supports-color@7.2.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2(supports-color@7.2.0)': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.6(supports-color@7.2.0)': + dependencies: + ajv: 6.15.0 + debug: 4.4.3(supports-color@7.2.0) + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.3.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.1': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@hey-api/codegen-core@0.9.0': + dependencies: + '@hey-api/types': 0.1.4 + ansi-colors: 4.1.3 + c12: 3.3.4 + color-support: 1.1.3 + transitivePeerDependencies: + - magicast + + '@hey-api/json-schema-ref-parser@1.4.3': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.3.1 + + '@hey-api/openapi-ts@0.98.2(typescript@5.8.3)': + dependencies: + '@hey-api/codegen-core': 0.9.0 + '@hey-api/json-schema-ref-parser': 1.4.3 + '@hey-api/shared': 0.4.8 + '@hey-api/spec-types': 0.2.0 + '@hey-api/types': 0.1.4 + '@lukeed/ms': 2.0.2 + ansi-colors: 4.1.3 + color-support: 1.1.3 + commander: 15.0.0 + get-tsconfig: 4.14.0 + typescript: 5.8.3 + transitivePeerDependencies: + - magicast + + '@hey-api/shared@0.4.8': + dependencies: + '@hey-api/codegen-core': 0.9.0 + '@hey-api/json-schema-ref-parser': 1.4.3 + '@hey-api/spec-types': 0.2.0 + '@hey-api/types': 0.1.4 + ansi-colors: 4.1.3 + cross-spawn: 7.0.6 + open: 11.0.0 + semver: 7.8.2 + transitivePeerDependencies: + - magicast + + '@hey-api/spec-types@0.2.0': + dependencies: + '@hey-api/types': 0.1.4 + + '@hey-api/types@0.1.4': {} + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jsdevtools/ono@7.1.3': {} + + '@loaderkit/resolve@1.0.6': + dependencies: + '@braidai/lang': 1.1.2 + + '@lukeed/ms@2.0.2': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@oxc-project/types@0.144.0': {} + + '@pkgr/core@0.3.6': {} + + '@publint/pack@0.1.6': + dependencies: + tinyexec: 1.3.0 + + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + + '@rolldown/binding-android-arm64@1.2.4': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.4': + optional: true + + '@rolldown/binding-darwin-x64@1.2.4': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.4': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.4': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.4': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.4': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.4': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.4': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@sindresorhus/is@4.6.0': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@typescript-eslint/eslint-plugin@8.31.1(@typescript-eslint/parser@8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3))(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.31.1 + '@typescript-eslint/type-utils': 8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3) + '@typescript-eslint/utils': 8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.31.1 + eslint: 9.39.1(jiti@2.7.0)(supports-color@7.2.0) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.31.1 + '@typescript-eslint/types': 8.31.1 + '@typescript-eslint/typescript-estree': 8.31.1(supports-color@7.2.0)(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.31.1 + debug: 4.4.3(supports-color@7.2.0) + eslint: 9.39.1(jiti@2.7.0)(supports-color@7.2.0) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.31.1': + dependencies: + '@typescript-eslint/types': 8.31.1 + '@typescript-eslint/visitor-keys': 8.31.1 + + '@typescript-eslint/type-utils@8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.31.1(supports-color@7.2.0)(typescript@5.8.3) + '@typescript-eslint/utils': 8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3) + debug: 4.4.3(supports-color@7.2.0) + eslint: 9.39.1(jiti@2.7.0)(supports-color@7.2.0) + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.31.1': {} + + '@typescript-eslint/typescript-estree@8.31.1(supports-color@7.2.0)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.31.1 + '@typescript-eslint/visitor-keys': 8.31.1 + debug: 4.4.3(supports-color@7.2.0) + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.9 + semver: 7.8.5 + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.31.1 + '@typescript-eslint/types': 8.31.1 + '@typescript-eslint/typescript-estree': 8.31.1(supports-color@7.2.0)(typescript@5.8.3) + eslint: 9.39.1(jiti@2.7.0)(supports-color@7.2.0) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.31.1': + dependencies: + '@typescript-eslint/types': 8.31.1 + eslint-visitor-keys: 4.2.1 + + '@yuku-codegen/binding-android-arm64@0.8.7': + optional: true + + '@yuku-codegen/binding-darwin-arm64@0.8.7': + optional: true + + '@yuku-codegen/binding-darwin-x64@0.8.7': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.8.7': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.8.7': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.8.7': + optional: true + + '@yuku-codegen/binding-win32-x64@0.8.7': + optional: true + + '@yuku-parser/binding-android-arm64@0.8.7': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.8.7': + optional: true + + '@yuku-parser/binding-darwin-x64@0.8.7': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.8.7': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.8.7': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.8.7': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.8.7': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.8.7': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.8.7': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.8.7': + optional: true + + '@yuku-parser/binding-win32-arm64@0.8.7': + optional: true + + '@yuku-parser/binding-win32-x64@0.8.7': + optional: true + + '@yuku-toolchain/types@0.8.7': {} + + acorn-jsx@5.3.2(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + + acorn@8.18.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-colors@4.1.3: {} + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.3.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansis@4.3.1: {} + + any-promise@1.3.0: {} + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.18: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.4: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + c12@3.3.4: + dependencies: + chokidar: 5.0.0 + confbox: 0.2.4 + defu: 6.1.7 + dotenv: 17.4.2 + exsolve: 1.1.1 + giget: 3.3.1 + jiti: 2.7.0 + ohash: 2.0.12 + pathe: 2.0.3 + perfect-debounce: 2.1.0 + pkg-types: 2.3.1 + rc9: 3.0.1 + + cac@7.0.0: {} + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + char-regex@1.0.2: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.1.1 + + cjs-module-lexer@1.4.3: {} + + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.2 + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-support@1.1.3: {} + + commander@10.0.1: {} + + commander@15.0.0: {} + + concat-map@0.0.1: {} + + confbox@0.2.4: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3(supports-color@7.2.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 + + deep-is@0.1.4: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + + defu@6.1.7: {} + + destr@2.0.5: {} + + dotenv@17.4.2: {} + + dts-resolver@3.0.0: {} + + emoji-regex@8.0.0: {} + + emojilib@2.4.0: {} + + empathic@2.0.1: {} + + environment@1.1.0: {} + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-prettier@5.4.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(prettier@3.1.1): + dependencies: + eslint: 9.39.1(jiti@2.7.0)(supports-color@7.2.0) + prettier: 3.1.1 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.13 + + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.31.1(@typescript-eslint/parser@8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3))(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3))(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0)): + dependencies: + eslint: 9.39.1(jiti@2.7.0)(supports-color@7.2.0) + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.31.1(@typescript-eslint/parser@8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3))(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2(supports-color@7.2.0) + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.6(supports-color@7.2.0) + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@7.2.0) + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + exsolve@1.1.1: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + fflate@0.8.3: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.4 + keyv: 4.5.4 + + flatted@3.4.4: {} + + get-caller-file@2.0.5: {} + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + get-tsconfig@5.0.0-beta.5: + dependencies: + resolve-pkg-maps: 1.0.0 + + giget@3.3.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + highlight.js@10.7.3: {} + + hookable@6.1.1: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-without-cache@0.4.0: {} + + imurmurhash@0.1.4: {} + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-number@7.0.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + jiti@2.7.0: {} + + js-yaml@4.3.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lru-cache@11.5.2: {} + + marked-terminal@7.3.0(marked@9.1.6): + dependencies: + ansi-escapes: 7.3.0 + ansi-regex: 6.3.0 + chalk: 5.6.2 + cli-highlight: 2.1.11 + cli-table3: 0.6.5 + marked: 9.1.6 + node-emoji: 2.2.0 + supports-hyperlinks: 3.2.0 + + marked@9.1.6: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.18 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.4 + + mri@1.2.0: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + natural-compare@1.4.0: {} + + node-emoji@2.2.0: + dependencies: + '@sindresorhus/is': 4.6.0 + char-regex: 1.0.2 + emojilib: 2.4.0 + skin-tone: 2.0.0 + + object-assign@4.1.1: {} + + obug@2.1.4: {} + + ohash@2.0.12: {} + + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-manager-detector@1.8.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.1: {} + + parse5@6.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + pathe@2.0.3: {} + + perfect-debounce@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.5: {} + + pkg-types@2.3.1: + dependencies: + confbox: 0.2.4 + exsolve: 1.1.1 + pathe: 2.0.3 + + powershell-utils@0.1.0: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier@3.1.1: {} + + publint@0.3.23: + dependencies: + '@publint/pack': 0.1.6 + package-manager-detector: 1.8.0 + picocolors: 1.1.1 + sade: 1.8.1 + + punycode@2.3.1: {} + + quansync@1.0.0: {} + + queue-microtask@1.2.3: {} + + rc9@3.0.1: + dependencies: + defu: 6.1.7 + destr: 2.0.5 + + readdirp@5.1.1: {} + + require-directory@2.1.1: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + reusify@1.1.0: {} + + rolldown-plugin-dts@0.27.14(rolldown@1.2.4)(typescript@5.8.3): + dependencies: + dts-resolver: 3.0.0 + get-tsconfig: 5.0.0-beta.5 + obug: 2.1.4 + rolldown: 1.2.4 + yuku-ast: 0.8.7 + yuku-codegen: 0.8.7 + yuku-parser: 0.8.7 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.2.4: + dependencies: + '@oxc-project/types': 0.144.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.2.4 + '@rolldown/binding-darwin-arm64': 1.2.4 + '@rolldown/binding-darwin-x64': 1.2.4 + '@rolldown/binding-freebsd-x64': 1.2.4 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.4 + '@rolldown/binding-linux-arm64-gnu': 1.2.4 + '@rolldown/binding-linux-arm64-musl': 1.2.4 + '@rolldown/binding-linux-ppc64-gnu': 1.2.4 + '@rolldown/binding-linux-s390x-gnu': 1.2.4 + '@rolldown/binding-linux-x64-gnu': 1.2.4 + '@rolldown/binding-linux-x64-musl': 1.2.4 + '@rolldown/binding-openharmony-arm64': 1.2.4 + '@rolldown/binding-win32-arm64-msvc': 1.2.4 + '@rolldown/binding-win32-x64-msvc': 1.2.4 + + run-applescript@7.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + semver@7.8.2: {} + + semver@7.8.5: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + skin-tone@2.0.0: + dependencies: + unicode-emoji-modifier-base: 1.0.0 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@3.2.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + synckit@0.11.13: + dependencies: + '@pkgr/core': 0.3.6 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinyexec@1.3.0: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tree-kill@1.2.2: {} + + ts-api-utils@2.5.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + tsdown@0.22.14(@arethetypeswrong/core@0.18.5)(publint@0.3.23)(typescript@5.8.3): + dependencies: + ansis: 4.3.1 + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.0.1 + hookable: 6.1.1 + import-without-cache: 0.4.0 + obug: 2.1.4 + picomatch: 4.0.5 + rolldown: 1.2.4 + rolldown-plugin-dts: 0.27.14(rolldown@1.2.4)(typescript@5.8.3) + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + verkit: 0.3.2 + optionalDependencies: + '@arethetypeswrong/core': 0.18.5 + publint: 0.3.23 + typescript: 5.8.3 + transitivePeerDependencies: + - '@typescript/native-preview' + - '@volar/typescript' + - oxc-resolver + - vue-tsc + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.31.1(@typescript-eslint/parser@8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3))(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3) + '@typescript-eslint/parser': 8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3) + '@typescript-eslint/utils': 8.31.1(eslint@9.39.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.8.3) + eslint: 9.39.1(jiti@2.7.0)(supports-color@7.2.0) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + typescript@5.6.1-rc: {} + + typescript@5.8.3: {} + + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + + unicode-emoji-modifier-base@1.0.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + validate-npm-package-name@5.0.1: {} + + verkit@0.3.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + + y18n@5.0.8: {} + + yargs-parser@20.2.9: {} + + yargs@16.2.2: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yocto-queue@0.1.0: {} + + yuku-ast@0.8.7: + dependencies: + '@yuku-toolchain/types': 0.8.7 + + yuku-codegen@0.8.7: + dependencies: + '@yuku-toolchain/types': 0.8.7 + optionalDependencies: + '@yuku-codegen/binding-android-arm64': 0.8.7 + '@yuku-codegen/binding-darwin-arm64': 0.8.7 + '@yuku-codegen/binding-darwin-x64': 0.8.7 + '@yuku-codegen/binding-freebsd-x64': 0.8.7 + '@yuku-codegen/binding-linux-arm-gnu': 0.8.7 + '@yuku-codegen/binding-linux-arm-musl': 0.8.7 + '@yuku-codegen/binding-linux-arm64-gnu': 0.8.7 + '@yuku-codegen/binding-linux-arm64-musl': 0.8.7 + '@yuku-codegen/binding-linux-x64-gnu': 0.8.7 + '@yuku-codegen/binding-linux-x64-musl': 0.8.7 + '@yuku-codegen/binding-win32-arm64': 0.8.7 + '@yuku-codegen/binding-win32-x64': 0.8.7 + + yuku-parser@0.8.7: + dependencies: + '@yuku-toolchain/types': 0.8.7 + yuku-ast: 0.8.7 + optionalDependencies: + '@yuku-parser/binding-android-arm64': 0.8.7 + '@yuku-parser/binding-darwin-arm64': 0.8.7 + '@yuku-parser/binding-darwin-x64': 0.8.7 + '@yuku-parser/binding-freebsd-x64': 0.8.7 + '@yuku-parser/binding-linux-arm-gnu': 0.8.7 + '@yuku-parser/binding-linux-arm-musl': 0.8.7 + '@yuku-parser/binding-linux-arm64-gnu': 0.8.7 + '@yuku-parser/binding-linux-arm64-musl': 0.8.7 + '@yuku-parser/binding-linux-x64-gnu': 0.8.7 + '@yuku-parser/binding-linux-x64-musl': 0.8.7 + '@yuku-parser/binding-win32-arm64': 0.8.7 + '@yuku-parser/binding-win32-x64': 0.8.7 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..5494ec3 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +overrides: + js-yaml: 4.3.1 diff --git a/release-please-config.json b/release-please-config.json index 1ebd0bd..57e529f 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -2,11 +2,9 @@ "packages": { ".": {} }, - "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", "include-v-in-tag": true, "include-component-in-tag": false, - "versioning": "prerelease", - "prerelease": true, "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": false, "pull-request-header": "Automated Release PR", @@ -59,6 +57,5 @@ "hidden": true } ], - "release-type": "node", - "extra-files": ["src/version.ts", "README.md"] + "release-type": "node" } diff --git a/scripts/bootstrap b/scripts/bootstrap index a8b69ff..5982ed5 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,23 +4,6 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then - brew bundle check >/dev/null 2>&1 || { - echo -n "==> Install Homebrew dependencies? (y/N): " - read -r response - case "$response" in - [yY][eE][sS]|[yY]) - brew bundle - ;; - *) - ;; - esac - echo - } -fi - echo "==> Installing Node dependencies…" -PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm") - -$PACKAGE_MANAGER install "$@" +corepack pnpm install "$@" diff --git a/scripts/build b/scripts/build index 6de20d4..7b888d8 100755 --- a/scripts/build +++ b/scripts/build @@ -4,16 +4,10 @@ set -exuo pipefail cd "$(dirname "$0")/.." -node scripts/utils/check-version.cjs +./node_modules/.bin/tsdown -# Build into dist and will publish the package from there, -# so that src/resources/foo.ts becomes /resources/foo.js -# This way importing from `"@unlayer/sdk/resources/foo"` works -# even with `"moduleResolution": "node"` - -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 +# Releases publish from dist/, so include package metadata and generated source +# beside the bundled CommonJS, ESM, and declaration output. cp -rp src README.md dist for file in LICENSE CHANGELOG.md; do if [ -e "${file}" ]; then cp "${file}" dist; fi @@ -30,22 +24,9 @@ fi # 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 -# 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 cp tsconfig.dist-src.json dist/src/tsconfig.json -node scripts/utils/postprocess-files.cjs - -# make sure that nothing crashes when we require the output CJS or -# import the output ESM -(cd dist && node -e 'require("@unlayer/sdk")') -(cd dist && node -e 'import("@unlayer/sdk")' --input-type=module) - -if [ -e ./scripts/build-deno ] -then - ./scripts/build-deno -fi +# Verify both public entry points in CommonJS and ESM. +(cd dist && node -e 'const sdk = require("@unlayer/sdk"); const client = require("@unlayer/sdk/client"); const unlayer = new sdk.Unlayer({ client: client.createClient({ auth: "test", baseUrl: "https://api.unlayer.com", throwOnError: true }) }); if (typeof unlayer.templates.listTemplates !== "function") process.exit(1)') +(cd dist && node -e 'const sdk = await import("@unlayer/sdk"); const client = await import("@unlayer/sdk/client"); const unlayer = new sdk.Unlayer({ client: client.createClient({ auth: "test", baseUrl: "https://api.unlayer.com", throwOnError: true }) }); if (typeof unlayer.templates.listTemplates !== "function") process.exit(1)' --input-type=module) +./node_modules/.bin/tsc --project tsconfig.consumer.json diff --git a/scripts/check-generated b/scripts/check-generated new file mode 100755 index 0000000..d86ff2c --- /dev/null +++ b/scripts/check-generated @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "$(dirname "$0")/.." + +tmp_root="${TMPDIR:-/tmp}" +check_dir=$(mktemp -d "$tmp_root/unlayer-sdk-generated.XXXXXX") +trap 'rm -rf "$check_dir"' EXIT + +cp -R src "$check_dir/src" +pnpm generate + +if ! diff -ru "$check_dir/src" src; then + echo "generated SDK source is stale; review and commit the regenerated files" >&2 + exit 1 +fi diff --git a/scripts/lint b/scripts/lint index 3ffb78a..c52a2d3 100755 --- a/scripts/lint +++ b/scripts/lint @@ -7,6 +7,9 @@ cd "$(dirname "$0")/.." echo "==> Running eslint" ./node_modules/.bin/eslint . +echo "==> Checking generated source" +./scripts/check-generated + echo "==> Building" ./scripts/build @@ -14,8 +17,30 @@ 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 +NPM_CONFIG_CACHE="${TMPDIR:-/tmp}/unlayer-sdk-npm-cache" ./node_modules/.bin/attw --pack dist echo "==> Running publint" ./node_modules/.bin/publint dist + +echo "==> Testing release safeguards" +RELEASE_VERSION="$(node -p 'require("./package.json").version')" +GITHUB_ACTIONS=true GITHUB_REF_TYPE=tag GITHUB_REF_NAME="v${RELEASE_VERSION}" \ + ./bin/check-release-ref + +if GITHUB_ACTIONS=true GITHUB_REF_TYPE=branch GITHUB_REF_NAME=main \ + ./bin/check-release-ref >/dev/null 2>&1; then + echo "release safeguard accepted a branch" >&2 + exit 1 +fi + +if GITHUB_ACTIONS=true GITHUB_REF_TYPE=tag GITHUB_REF_NAME=v999.0.0 \ + ./bin/check-release-ref >/dev/null 2>&1; then + echo "release safeguard accepted a mismatched tag" >&2 + exit 1 +fi + +node --test tests/sync-openapi.test.mjs +node --test tests/publish-npm.test.mjs + +echo "==> Testing the packed SDK" +./scripts/test-packed-sdk tests/packed-sdk-smoke.mjs diff --git a/scripts/mock b/scripts/mock deleted file mode 100755 index 0b28f6e..0000000 --- a/scripts/mock +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [[ -n "$1" && "$1" != '--'* ]]; then - URL="$1" - shift -else - URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" -fi - -# Check if the URL is empty -if [ -z "$URL" ]; then - echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" - exit 1 -fi - -echo "==> Starting mock server with URL ${URL}" - -# Run prism mock on the given spec -if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & - - # Wait for server to come online - echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do - echo -n "." - sleep 0.1 - done - - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - - echo -else - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" -fi diff --git a/scripts/postprocess-sdk.cjs b/scripts/postprocess-sdk.cjs new file mode 100644 index 0000000..35ae62d --- /dev/null +++ b/scripts/postprocess-sdk.cjs @@ -0,0 +1,147 @@ +#!/usr/bin/env node + +const fs = require('node:fs'); +const path = require('node:path'); + +const sdkPath = path.join(__dirname, '..', 'src', 'sdk.gen.ts'); +const clientIndexPath = path.join(__dirname, '..', 'src', 'client', 'index.ts'); +let source = fs.readFileSync(sdkPath, 'utf8'); +let clientIndexSource = fs.readFileSync(clientIndexPath, 'utf8'); + +source = source.replace(/[ \t]+$/gm, ''); + +const optionsPattern = /> = Options2 & \{/g; +const responseStylePattern = /^\s+responseStyle: 'data',\n/gm; +const optionsSpreadPattern = /^(\s+)\.\.\.options,?\n/gm; +const defaultClientImport = "import { client } from './client.gen';\n"; +const generatedClientBase = `class HeyApiClient { + protected client: Client; + + constructor(args?: { + client?: Client; + }) { + this.client = args?.client ?? client; + } +} +`; +const explicitClientBase = `class HeyApiClient { + protected client: Client; + + constructor(args: { + client: Client; + }) { + if (!args?.client) { + throw new TypeError('A client created with createClient() is required.'); + } + this.client = args.client; + } +} +`; +const generatedRegistry = `class HeyApiRegistry { + private readonly defaultKey = 'default'; + + private readonly instances: Map = new Map(); + + get(key?: string): T { + const instance = this.instances.get(key ?? this.defaultKey); + if (!instance) { + throw new Error(\`No SDK client found. Create one with "new Unlayer()" to fix this error.\`); + } + return instance; + } + + set(value: T, key?: string): void { + this.instances.set(key ?? this.defaultKey, value); + } +} + +`; +const generatedUnlayerConstructor = `export class Unlayer extends HeyApiClient { + public static readonly __registry: HeyApiRegistry = new HeyApiRegistry(); + + constructor(args?: { + client?: Client; + key?: string; + }) { + super(args); + Unlayer.__registry.set(this, args?.key); + } +`; +const explicitUnlayerConstructor = `export class Unlayer extends HeyApiClient { + constructor(args: { + client: Client; + }) { + super(args); + } +`; + +const optionsMatches = source.match(optionsPattern) ?? []; +const responseStyleMatches = source.match(responseStylePattern) ?? []; +const optionsSpreadMatches = source.match(optionsSpreadPattern) ?? []; + +if (optionsMatches.length !== 1) { + throw new Error(`Expected one generated SDK Options alias, found ${optionsMatches.length}`); +} + +if (responseStyleMatches.length === 0 || responseStyleMatches.length !== optionsSpreadMatches.length) { + throw new Error('Generated SDK request layout changed; refusing to apply unsafe contract fixes'); +} + +for (const [label, generatedFragment] of [ + ['default client import', defaultClientImport], + ['generated client base', generatedClientBase], + ['generated registry', generatedRegistry], + ['generated Unlayer constructor', generatedUnlayerConstructor], +]) { + if (source.split(generatedFragment).length !== 2) { + throw new Error(`Expected one ${label}; refusing to expose generated global client state`); + } +} + +source = source.replace( + optionsPattern, + "> = Omit, 'responseStyle' | 'throwOnError'> & {", +); +source = source.replace(responseStylePattern, ''); +source = source.replace( + optionsSpreadPattern, + (_, indentation) => + `${indentation}...options,\n` + + `${indentation}throwOnError: true as ThrowOnError,\n` + + `${indentation}responseStyle: 'data',\n`, +); +source = source.replace(defaultClientImport, ''); +source = source.replace(generatedClientBase, explicitClientBase); +source = source.replace(generatedRegistry, ''); +source = source.replace(generatedUnlayerConstructor, explicitUnlayerConstructor); +const clientHeader = '// This file is auto-generated by @hey-api/openapi-ts\n\n'; +const createClientExport = "export { createClient } from './client.gen';"; + +if (!clientIndexSource.startsWith(clientHeader)) { + throw new Error('Generated client entry header changed; refusing to apply Unlayer defaults'); +} + +if (clientIndexSource.split(createClientExport).length !== 2) { + throw new Error('Expected one generated createClient export'); +} + +clientIndexSource = clientIndexSource.replace( + clientHeader, + `${clientHeader}import { createClient as createHeyApiClient } from './client.gen';\n` + + "import type { Config as ClientConfig } from './types.gen';\n\n", +); +clientIndexSource = clientIndexSource.replace( + createClientExport, + `export const createClient = (config: ClientConfig = {}) =>\n` + + ` createHeyApiClient({\n` + + ` ...config,\n` + + ` baseUrl: config.baseUrl ?? 'https://api.unlayer.com',\n` + + ` throwOnError: config.throwOnError ?? true,\n` + + ` });`, +); + +fs.writeFileSync(sdkPath, source); +fs.writeFileSync(clientIndexPath, clientIndexSource); +process.stdout.write( + `Applied SDK contracts to ${responseStyleMatches.length} operations, removed global client state, and configured createClient\n`, +); diff --git a/scripts/sync-openapi.cjs b/scripts/sync-openapi.cjs new file mode 100644 index 0000000..c26e6d1 --- /dev/null +++ b/scripts/sync-openapi.cjs @@ -0,0 +1,80 @@ +#!/usr/bin/env node + +const fs = require('node:fs'); +const path = require('node:path'); + +const DEFAULT_SOURCE_URL = 'https://api.unlayer.com/v3/docs/json'; +const DEFAULT_OUTPUT_PATH = path.join(__dirname, '..', 'openapi.json'); +const DOWNLOAD_TIMEOUT_MS = 30_000; + +async function fetchOpenApiDocument( + sourceUrl, + fetcher = fetch, + signal = AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS), +) { + const response = await fetcher(sourceUrl, { + headers: { Accept: 'application/json' }, + signal, + }); + if (!response.ok) { + throw new Error(`OpenAPI download failed with HTTP ${response.status}`); + } + + const document = await response.json(); + if ( + typeof document !== 'object' || + document === null || + typeof document.paths !== 'object' || + document.paths === null || + Array.isArray(document.paths) || + Object.keys(document.paths).length === 0 + ) { + throw new Error('Downloaded OpenAPI document has no public paths'); + } + + // Remote input lets Hey infer this origin from the document URL. Preserve + // that behavior explicitly when generating from the local snapshot. + document.servers = [{ url: 'https://api.unlayer.com' }]; + return document; +} + +function writeOpenApiDocument(document, outputPath) { + const temporaryPath = `${outputPath}.tmp`; + + try { + fs.writeFileSync(temporaryPath, `${JSON.stringify(document, null, 2)}\n`); + fs.renameSync(temporaryPath, outputPath); + } finally { + fs.rmSync(temporaryPath, { force: true }); + } +} + +async function main({ + sourceUrl = process.env.SDK_OPENAPI_URL ?? DEFAULT_SOURCE_URL, + outputPath = process.env.SDK_OPENAPI_OUTPUT ?? DEFAULT_OUTPUT_PATH, + fetcher = fetch, + signal = AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS), +} = {}) { + const document = await fetchOpenApiDocument(sourceUrl, fetcher, signal); + writeOpenApiDocument(document, outputPath); + + process.stdout.write( + `Updated ${path.basename(outputPath)} from ${sourceUrl} (${Object.keys(document.paths).length} paths)\n`, + ); +} + +module.exports = { + DEFAULT_OUTPUT_PATH, + DEFAULT_SOURCE_URL, + DOWNLOAD_TIMEOUT_MS, + fetchOpenApiDocument, + main, + writeOpenApiDocument, +}; + +if (require.main === module) { + main().catch((error) => { + process.stderr.write(`${error.stack ?? error}\n`); + process.exitCode = 1; + }); +} diff --git a/scripts/test b/scripts/test deleted file mode 100755 index 7bce051..0000000 --- a/scripts/test +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color - -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 -} - -kill_server_on_port() { - pids=$(lsof -t -i tcp:"$1" || echo "") - if [ "$pids" != "" ]; then - kill "$pids" - echo "Stopped $pids." - fi -} - -function is_overriding_api_base_url() { - [ -n "$TEST_API_BASE_URL" ] -} - -if ! is_overriding_api_base_url && ! prism_is_running ; then - # When we exit this script, make sure to kill the background mock server process - trap 'kill_server_on_port 4010' EXIT - - # Start the dev server - ./scripts/mock --daemon -fi - -if is_overriding_api_base_url ; then - echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" - echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" - echo -e "running against your OpenAPI spec." - echo - echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" - echo - echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" - echo - - exit 1 -else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" - echo -fi - -echo "==> Running tests" -./node_modules/.bin/jest "$@" diff --git a/scripts/test-package-archive b/scripts/test-package-archive new file mode 100755 index 0000000..1f22785 --- /dev/null +++ b/scripts/test-package-archive @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "$(dirname "$0")/.." + +if [ "$#" -ne 2 ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +archive="$1" +smoke_test_file="$2" + +if [ ! -f "$archive" ]; then + echo "package archive not found: $archive" >&2 + exit 1 +fi + +if [ ! -f "$smoke_test_file" ]; then + echo "smoke test not found: $smoke_test_file" >&2 + exit 1 +fi + +archive_dir=$(cd "$(dirname "$archive")" && pwd) +archive="$archive_dir/$(basename "$archive")" + +tmp_root="${TMPDIR:-/tmp}" +consumer_dir=$(mktemp -d "$tmp_root/unlayer-sdk-consumer.XXXXXX") +trap 'rm -rf "$consumer_dir"' EXIT + +NPM_CONFIG_CACHE="$consumer_dir/npm-cache" npm install \ + --silent \ + --prefix "$consumer_dir" \ + --ignore-scripts \ + --no-audit \ + --no-fund \ + "$archive" >/dev/null + +cp "$smoke_test_file" "$consumer_dir/smoke-test.mjs" +node "$consumer_dir/smoke-test.mjs" diff --git a/scripts/test-packed-sdk b/scripts/test-packed-sdk new file mode 100755 index 0000000..cd94aad --- /dev/null +++ b/scripts/test-packed-sdk @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "$(dirname "$0")/.." + +if [ "$#" -ne 1 ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +smoke_test_file="$1" +if [ ! -f "$smoke_test_file" ]; then + echo "smoke test not found: $smoke_test_file" >&2 + exit 1 +fi + +tmp_root="${TMPDIR:-/tmp}" +test_dir=$(mktemp -d "$tmp_root/unlayer-sdk-package-test.XXXXXX") +trap 'rm -rf "$test_dir"' EXIT + +pack_dir="$test_dir/pack" +mkdir -p "$pack_dir" + +# Exercise npm deliberately here: pnpm manages this repository, while this +# smoke test verifies the registry tarball works for npm consumers too. +NPM_CONFIG_CACHE="$test_dir/npm-cache" npm pack \ + --silent \ + --pack-destination "$pack_dir" \ + ./dist >/dev/null + +archives=("$pack_dir"/*.tgz) +if [ "${#archives[@]}" -ne 1 ]; then + echo "expected exactly one packed SDK archive" >&2 + exit 1 +fi + +./scripts/test-package-archive "${archives[0]}" "$smoke_test_file" diff --git a/scripts/utils/attw-report.cjs b/scripts/utils/attw-report.cjs deleted file mode 100644 index b3477c0..0000000 --- a/scripts/utils/attw-report.cjs +++ /dev/null @@ -1,24 +0,0 @@ -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/check-is-in-git-install.sh b/scripts/utils/check-is-in-git-install.sh deleted file mode 100755 index 1354eb4..0000000 --- a/scripts/utils/check-is-in-git-install.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -# Check if you happen to call prepare for a repository that's already in node_modules. -[ "$(basename "$(dirname "$PWD")")" = 'node_modules' ] || -# The name of the containing directory that 'npm` uses, which looks like -# $HOME/.npm/_cacache/git-cloneXXXXXX -[ "$(basename "$(dirname "$PWD")")" = 'tmp' ] || -# The name of the containing directory that 'yarn` uses, which looks like -# $(yarn cache dir)/.tmp/XXXXX -[ "$(basename "$(dirname "$PWD")")" = '.tmp' ] diff --git a/scripts/utils/check-version.cjs b/scripts/utils/check-version.cjs deleted file mode 100644 index 86c56df..0000000 --- a/scripts/utils/check-version.cjs +++ /dev/null @@ -1,20 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const main = () => { - const pkg = require('../../package.json'); - const version = pkg['version']; - if (!version) throw 'The version property is not set in the package.json file'; - if (typeof version !== 'string') { - throw `Unexpected type for the package.json version field; got ${typeof version}, expected string`; - } - - const versionFile = path.resolve(__dirname, '..', '..', 'src', 'version.ts'); - const contents = fs.readFileSync(versionFile, 'utf8'); - const output = contents.replace(/(export const VERSION = ')(.*)(')/g, `$1${version}$3`); - fs.writeFileSync(versionFile, output); -}; - -if (require.main === module) { - main(); -} diff --git a/scripts/utils/fix-index-exports.cjs b/scripts/utils/fix-index-exports.cjs deleted file mode 100644 index e5e10b3..0000000 --- a/scripts/utils/fix-index-exports.cjs +++ /dev/null @@ -1,17 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const indexJs = - process.env['DIST_PATH'] ? - path.resolve(process.env['DIST_PATH'], 'index.js') - : path.resolve(__dirname, '..', '..', 'dist', 'index.js'); - -let before = fs.readFileSync(indexJs, 'utf8'); -let after = before.replace( - /^(\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/git-swap.sh b/scripts/utils/git-swap.sh deleted file mode 100755 index 79d1888..0000000 --- a/scripts/utils/git-swap.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -set -exuo pipefail -# the package is published to NPM from ./dist -# we want the final file structure for git installs to match the npm installs, so we - -# delete everything except ./dist and ./node_modules -find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' + - -# move everything from ./dist to . -mv dist/* . - -# delete the now-empty ./dist -rmdir dist diff --git a/scripts/utils/make-dist-package-json.cjs b/scripts/utils/make-dist-package-json.cjs index 7c24f56..54e8c58 100644 --- a/scripts/utils/make-dist-package-json.cjs +++ b/scripts/utils/make-dist-package-json.cjs @@ -14,8 +14,6 @@ for (const key of ['types', 'main', 'module']) { } delete pkgJson.devDependencies; -delete pkgJson.scripts.prepack; -delete pkgJson.scripts.prepublishOnly; -delete pkgJson.scripts.prepare; +delete pkgJson.scripts; console.log(JSON.stringify(pkgJson, null, 2)); diff --git a/scripts/utils/postprocess-files.cjs b/scripts/utils/postprocess-files.cjs deleted file mode 100644 index deae575..0000000 --- a/scripts/utils/postprocess-files.cjs +++ /dev/null @@ -1,94 +0,0 @@ -// @ts-check -const fs = require('fs'); -const path = require('path'); - -const distDir = - process.env['DIST_PATH'] ? - path.resolve(process.env['DIST_PATH']) - : path.resolve(__dirname, '..', '..', 'dist'); - -async function* walk(dir) { - for await (const d of await fs.promises.opendir(dir)) { - const entry = path.join(dir, d.name); - if (d.isDirectory()) yield* walk(entry); - else if (d.isFile()) yield entry; - } -} - -async function postprocess() { - for await (const file of walk(distDir)) { - if (!/(\.d)?[cm]?ts$/.test(file)) continue; - - const code = await fs.promises.readFile(file, 'utf8'); - - // strip out lib="dom", types="node", and types="react" references; these - // are needed at build time, but would pollute the user's TS environment - const transformed = code.replace( - /^ *\/\/\/ * ' '.repeat(match.length - 1) + '\n', - ); - - if (transformed !== code) { - 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 deleted file mode 100755 index f723e7a..0000000 --- a/scripts/utils/upload-artifact.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -exuo pipefail - -RESPONSE=$(curl -X POST "$URL" \ - -H "Authorization: Bearer $AUTH" \ - -H "Content-Type: application/json") - -SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') - -if [[ "$SIGNED_URL" == "null" ]]; then - echo -e "\033[31mFailed to get signed URL.\033[0m" - exit 1 -fi - -TARBALL=$(cd dist && npm pack --silent) - -UPLOAD_RESPONSE=$(curl -v -X PUT \ - -H "Content-Type: application/gzip" \ - --data-binary "@dist/$TARBALL" "$SIGNED_URL" 2>&1) - -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/unlayer-typescript/$SHA'\033[0m" -else - echo -e "\033[31mFailed to upload artifact.\033[0m" - exit 1 -fi diff --git a/src/api-promise.ts b/src/api-promise.ts deleted file mode 100644 index 8c775ee..0000000 --- a/src/api-promise.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/api-promise instead */ -export * from './core/api-promise'; diff --git a/src/client.gen.ts b/src/client.gen.ts new file mode 100644 index 0000000..c962d27 --- /dev/null +++ b/src/client.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type Client, type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client: Client = createClient(createConfig({ baseUrl: 'https://api.unlayer.com', throwOnError: true })); diff --git a/src/client.ts b/src/client.ts deleted file mode 100644 index 889cbc0..0000000 --- a/src/client.ts +++ /dev/null @@ -1,850 +0,0 @@ -// 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 { VERSION } from './version'; -import * as Errors from './core/error'; -import * as Pagination from './core/pagination'; -import { AbstractPage, type CursorPageParams, CursorPageResponse } from './core/pagination'; -import * as Uploads from './core/uploads'; -import * as API from './resources/index'; -import { APIPromise } from './core/api-promise'; -import { ProjectRetrieveResponse, Projects } from './resources/projects'; -import { - TemplateListParams, - TemplateListResponse, - TemplateListResponsesCursorPage, - TemplateRetrieveParams, - TemplateRetrieveResponse, - Templates, -} from './resources/templates'; -import { WorkspaceListResponse, WorkspaceRetrieveResponse, Workspaces } from './resources/workspaces'; -import { Convert } from './resources/convert/convert'; -import { type Fetch } from './internal/builtin-types'; -import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; -import { FinalRequestOptions, RequestOptions } from './internal/request-options'; -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 { - /** - * Defaults to process.env['UNLAYER_API_KEY']. - */ - apiKey?: string | null | undefined; - - /** - * Defaults to process.env['UNLAYER_PERSONAL_ACCESS_TOKEN']. - */ - personalAccessToken?: string | null | undefined; - - /** - * Defaults to process.env['UNLAYER_PROJECT_ID']. - */ - projectID?: string | null | undefined; - - /** - * Override the default base URL for the API, e.g., "https://api.example.com/v2/" - * - * Defaults to process.env['UNLAYER_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; - - /** - * Set the log level. - * - * Defaults to process.env['UNLAYER_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 Unlayer API. - */ -export class Unlayer { - apiKey: string | null; - personalAccessToken: string | null; - projectID: string | null; - - baseURL: string; - maxRetries: number; - timeout: number; - logger: Logger; - logLevel: LogLevel | undefined; - fetchOptions: MergedRequestInit | undefined; - - private fetch: Fetch; - #encoder: Opts.RequestEncoder; - protected idempotencyHeader?: string; - private _options: ClientOptions; - - /** - * API Client for interfacing with the Unlayer API. - * - * @param {string | null | undefined} [opts.apiKey=process.env['UNLAYER_API_KEY'] ?? null] - * @param {string | null | undefined} [opts.personalAccessToken=process.env['UNLAYER_PERSONAL_ACCESS_TOKEN'] ?? null] - * @param {string | null | undefined} [opts.projectID=process.env['UNLAYER_PROJECT_ID'] ?? null] - * @param {string} [opts.baseURL=process.env['UNLAYER_BASE_URL'] ?? https://api.unlayer.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. - */ - constructor({ - baseURL = readEnv('UNLAYER_BASE_URL'), - apiKey = readEnv('UNLAYER_API_KEY') ?? null, - personalAccessToken = readEnv('UNLAYER_PERSONAL_ACCESS_TOKEN') ?? null, - projectID = readEnv('UNLAYER_PROJECT_ID') ?? null, - ...opts - }: ClientOptions = {}) { - const options: ClientOptions = { - apiKey, - personalAccessToken, - projectID, - ...opts, - baseURL: baseURL || `https://api.unlayer.com`, - }; - - this.baseURL = options.baseURL!; - this.timeout = options.timeout ?? Unlayer.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('UNLAYER_LOG'), "process.env['UNLAYER_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.apiKey = apiKey; - this.personalAccessToken = personalAccessToken; - this.projectID = projectID; - } - - /** - * 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, - apiKey: this.apiKey, - personalAccessToken: this.personalAccessToken, - projectID: this.projectID, - ...options, - }); - return client; - } - - /** - * Check whether the base URL is set to its default. - */ - #baseURLOverridden(): boolean { - return this.baseURL !== 'https://api.unlayer.com'; - } - - protected defaultQuery(): Record | undefined { - return this._options.defaultQuery; - } - - protected validateHeaders({ values, nulls }: NullableHeaders) { - if (this.apiKey && values.get('authorization')) { - return; - } - if (nulls.has('authorization')) { - return; - } - - if (this.personalAccessToken && values.get('authorization')) { - return; - } - if (nulls.has('authorization')) { - return; - } - - throw new Error( - 'Could not resolve authentication method. Expected either apiKey or personalAccessToken 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.apiKeyAuth(opts), await this.personalAccessTokenAuth(opts)]); - } - - protected async apiKeyAuth(opts: FinalRequestOptions): Promise { - if (this.apiKey == null) { - return undefined; - } - return buildHeaders([{ Authorization: `Bearer ${this.apiKey}` }]); - } - - protected async personalAccessTokenAuth(opts: FinalRequestOptions): Promise { - if (this.personalAccessToken == null) { - return undefined; - } - return buildHeaders([{ Authorization: `Bearer ${this.personalAccessToken}` }]); - } - - /** - * Basic re-implementation of `qs.stringify` for primitive types. - */ - 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 Errors.UnlayerError( - `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('&'); - } - - 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) as any; - 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?: PromiseOrValue, - ): Pagination.PagePromise { - return this.requestAPIList( - Page, - opts && 'then' in opts ? - opts.then((opts) => ({ method: 'get', path, ...opts })) - : { method: 'get', path, ...opts }, - ); - } - - requestAPIList< - Item = unknown, - PageClass extends Pagination.AbstractPage = Pagination.AbstractPage, - >( - Page: new (...args: ConstructorParameters) => PageClass, - options: PromiseOrValue, - ): Pagination.PagePromise { - const request = this.makeRequest(options, null, undefined); - return new Pagination.PagePromise(this as any as Unlayer, request, Page); - } - - async fetchWithTimeout( - url: RequestInfo, - init: RequestInit | undefined, - ms: number, - controller: AbortController, - ): Promise { - const { signal, method, ...options } = init || {}; - const abort = this._makeAbort(controller); - if (signal) signal.addEventListener('abort', abort, { once: true }); - - const timeout = setTimeout(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(), - 'X-Project-ID': this.projectID, - }, - await this.authHeaders(options), - this._options.defaultHeaders, - bodyHeaders, - options.headers, - ]); - - this.validateHeaders(headers); - - return headers.values; - } - - private _makeAbort(controller: AbortController) { - // note: we can't just inline this method inside `fetchWithTimeout()` because then the closure - // would capture all request options, and cause a memory leak. - return () => controller.abort(); - } - - 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 if ( - typeof body === 'object' && - headers.values.get('content-type') === 'application/x-www-form-urlencoded' - ) { - return { - bodyHeaders: { 'content-type': 'application/x-www-form-urlencoded' }, - body: this.stringifyQuery(body as Record), - }; - } else { - return this.#encoder({ body, headers }); - } - } - - static Unlayer = this; - static DEFAULT_TIMEOUT = 60000; // 1 minute - - static UnlayerError = Errors.UnlayerError; - 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; - - convert: API.Convert = new API.Convert(this); - projects: API.Projects = new API.Projects(this); - templates: API.Templates = new API.Templates(this); - workspaces: API.Workspaces = new API.Workspaces(this); -} - -Unlayer.Convert = Convert; -Unlayer.Projects = Projects; -Unlayer.Templates = Templates; -Unlayer.Workspaces = Workspaces; - -export declare namespace Unlayer { - export type RequestOptions = Opts.RequestOptions; - - export import CursorPage = Pagination.CursorPage; - export { type CursorPageParams as CursorPageParams, type CursorPageResponse as CursorPageResponse }; - - export { Convert as Convert }; - - export { Projects as Projects, type ProjectRetrieveResponse as ProjectRetrieveResponse }; - - export { - Templates as Templates, - type TemplateRetrieveResponse as TemplateRetrieveResponse, - type TemplateListResponse as TemplateListResponse, - type TemplateListResponsesCursorPage as TemplateListResponsesCursorPage, - type TemplateRetrieveParams as TemplateRetrieveParams, - type TemplateListParams as TemplateListParams, - }; - - export { - Workspaces as Workspaces, - type WorkspaceRetrieveResponse as WorkspaceRetrieveResponse, - type WorkspaceListResponse as WorkspaceListResponse, - }; -} diff --git a/src/client/client.gen.ts b/src/client/client.gen.ts new file mode 100644 index 0000000..fc3f037 --- /dev/null +++ b/src/client/client.gen.ts @@ -0,0 +1,277 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { Client, Config, RequestOptions, ResolvedRequestOptions } from './types.gen'; +import { + buildUrl, + createConfig, + createInterceptors, + getParseAs, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: any; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors(); + + const beforeRequest = async < + TData = unknown, + TResponseStyle extends 'data' | 'fields' = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, + >( + options: RequestOptions, + ) => { + const opts = { + ..._config, + ...options, + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined as string | undefined, + }; + + if (opts.security) { + await setAuthParams(opts); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body) as string | undefined; + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + const resolvedOpts = opts as typeof opts & + ResolvedRequestOptions; + const url = buildUrl(resolvedOpts); + + return { opts: resolvedOpts, url }; + }; + + const request: Client['request'] = async (options) => { + const throwOnError = options.throwOnError ?? _config.throwOnError; + const responseStyle = options.responseStyle ?? _config.responseStyle; + + let request: Request | undefined; + let response: Response | undefined; + + try { + const { opts, url } = await beforeRequest(options); + const requestInit: ReqInit = { + redirect: 'follow', + ...opts, + body: getValidRequestBody(opts), + }; + + request = new Request(url, requestInit); + + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = opts.fetch!; + + response = await _fetch(request); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { + request, + response, + }; + + if (response.ok) { + const parseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + if (response.status === 204 || response.headers.get('Content-Length') === '0') { + let emptyData: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + emptyData = await response[parseAs](); + break; + case 'formData': + emptyData = new FormData(); + break; + case 'stream': + emptyData = response.body; + break; + case 'json': + default: + emptyData = {}; + break; + } + return opts.responseStyle === 'data' + ? emptyData + : { + data: emptyData, + ...result, + }; + } + + let data: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'text': + data = await response[parseAs](); + break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; read as text and parse if non-empty. + const text = await response.text(); + data = text ? JSON.parse(text) : {}; + break; + } + case 'stream': + return opts.responseStyle === 'data' + ? response.body + : { + data: response.body, + ...result, + }; + } + + if (parseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return opts.responseStyle === 'data' + ? data + : { + data, + ...result, + }; + } + + const textError = await response.text(); + let jsonError: unknown; + + try { + jsonError = JSON.parse(textError); + } catch { + // noop + } + + throw jsonError ?? textError; + } catch (error) { + let finalError = error; + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, options as ResolvedRequestOptions); + } + } + + finalError = finalError || {}; + + if (throwOnError) { + throw finalError; + } + + // TODO: we probably want to return error and improve types + return responseStyle === 'data' + ? undefined + : { + error: finalError, + request, + response, + }; + } + }; + + const makeMethodFn = (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method }); + + const makeSseFn = (method: Uppercase) => async (options: RequestOptions) => { + const { opts, url } = await beforeRequest(options); + return createSseClient({ + ...opts, + body: opts.body as BodyInit | null | undefined, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + return request; + }, + serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined, + url, + }); + }; + + const _buildUrl: Client['buildUrl'] = (options) => buildUrl({ ..._config, ...options }); + + return { + buildUrl: _buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/src/client/index.ts b/src/client/index.ts new file mode 100644 index 0000000..4cc4d1d --- /dev/null +++ b/src/client/index.ts @@ -0,0 +1,35 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient as createHeyApiClient } from './client.gen'; +import type { Config as ClientConfig } from './types.gen'; + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen'; +export type { ServerSentEventsResult } from '../core/serverSentEvents.gen'; +export type { ClientMeta } from '../core/types.gen'; +export const createClient = (config: ClientConfig = {}) => + createHeyApiClient({ + ...config, + baseUrl: config.baseUrl ?? 'https://api.unlayer.com', + throwOnError: config.throwOnError ?? true, + }); +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts new file mode 100644 index 0000000..193646c --- /dev/null +++ b/src/client/types.gen.ts @@ -0,0 +1,218 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { Client as CoreClient, Config as CoreConfig } from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, CoreConfig { + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: 'arrayBuffer' | 'auto' | 'blob' | 'formData' | 'json' | 'stream' | 'text'; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> + extends + Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onRequest' + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + headers: Headers; + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record ? TData[keyof TData] : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? (TData extends Record ? TData[keyof TData] : TData) | undefined + : ( + | { + data: TData extends Record ? TData[keyof TData] : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record ? TError[keyof TError] : TError; + } + ) & { + /** request may be undefined, because error may be from building the request object itself */ + request?: Request; + /** response may be undefined, because error may be from building the request object itself or from a network error */ + response?: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick>, 'method'>, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: TData & Options, +) => string; + +export type Client = CoreClient & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + ([TData] extends [never] ? unknown : Omit); diff --git a/src/client/utils.gen.ts b/src/client/utils.gen.ts new file mode 100644 index 0000000..d4a7284 --- /dev/null +++ b/src/client/utils.gen.ts @@ -0,0 +1,316 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; + +export const createQuerySerializer = ({ + parameters = {}, + ...args +}: QuerySerializerOptions = {}): ((queryParams: T) => string) => { + const querySerializer = (queryParams: T): string => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + const options = parameters[name] || args; + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: 'form', + value, + ...options.array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...options.object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved: options.allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = (contentType: string | null): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if (cleanContent.startsWith('application/json') || cleanContent.endsWith('+json')) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => cleanContent.startsWith(type)) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export async function setAuthParams( + options: Pick & { + headers: Headers; + }, +): Promise { + for (const auth of options.security ?? []) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +} + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e., their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +type ErrInterceptor = ( + error: Err, + /** response may be undefined due to a network error where no response object is produced */ + response: Res | undefined, + /** request may be undefined, because error may be from building the request object itself */ + request: Req | undefined, + options: Options, +) => Err | Promise; + +type ReqInterceptor = (request: Req, options: Options) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/src/core/README.md b/src/core/README.md deleted file mode 100644 index 485fce8..0000000 --- a/src/core/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `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 deleted file mode 100644 index 31b6b94..0000000 --- a/src/core/api-promise.ts +++ /dev/null @@ -1,92 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { type Unlayer } 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: Unlayer; - - constructor( - client: Unlayer, - private responsePromise: Promise, - private parseResponse: ( - client: Unlayer, - 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/auth.gen.ts b/src/core/auth.gen.ts new file mode 100644 index 0000000..c663664 --- /dev/null +++ b/src/core/auth.gen.ts @@ -0,0 +1,48 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * A unique identifier for the security scheme. + * + * Defined only when there are multiple security schemes whose `Auth` + * shape would otherwise be identical. + */ + key?: string; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/src/core/bodySerializer.gen.ts b/src/core/bodySerializer.gen.ts new file mode 100644 index 0000000..67daca6 --- /dev/null +++ b/src/core/bodySerializer.gen.ts @@ -0,0 +1,82 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ArrayStyle, ObjectStyle, SerializerOptions } from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: unknown) => unknown; + +type QuerySerializerOptionsObject = { + allowReserved?: boolean; + array?: Partial>; + object?: Partial>; +}; + +export type QuerySerializerOptions = QuerySerializerOptionsObject & { + /** + * Per-parameter serialization overrides. When provided, these settings + * override the global array/object settings for specific parameter names. + */ + parameters?: Record; +}; + +const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: (body: unknown): FormData => { + const data = new FormData(); + + Object.entries(body as Record).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: unknown): string => + JSON.stringify(body, (_key, value) => (typeof value === 'bigint' ? value.toString() : value)), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: (body: unknown): string => { + const data = new URLSearchParams(); + + Object.entries(body as Record).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/src/core/error.ts b/src/core/error.ts deleted file mode 100644 index b06c4dd..0000000 --- a/src/core/error.ts +++ /dev/null @@ -1,130 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { castToError } from '../internal/errors'; - -export class UnlayerError extends Error {} - -export class APIError< - TStatus extends number | undefined = number | undefined, - THeaders extends Headers | undefined = Headers | undefined, - TError extends Object | undefined = Object | undefined, -> extends UnlayerError { - /** 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 deleted file mode 100644 index 2d31c86..0000000 --- a/src/core/pagination.ts +++ /dev/null @@ -1,170 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { UnlayerError } from './error'; -import { FinalRequestOptions } from '../internal/request-options'; -import { defaultParseResponse } from '../internal/parse'; -import { type Unlayer } 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: Unlayer; - protected options: FinalRequestOptions; - - protected response: Response; - protected body: unknown; - - constructor(client: Unlayer, 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 UnlayerError( - '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: Unlayer, - 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 interface CursorPageResponse { - data: Array; - - next_cursor: string | null; - - has_more: boolean; -} - -export interface CursorPageParams { - cursor?: string; - - limit?: number; -} - -export class CursorPage extends AbstractPage implements CursorPageResponse { - data: Array; - - next_cursor: string | null; - - has_more: boolean; - - constructor( - client: Unlayer, - response: Response, - body: CursorPageResponse, - options: FinalRequestOptions, - ) { - super(client, response, body, options); - - this.data = body.data || []; - this.next_cursor = body.next_cursor || null; - this.has_more = body.has_more || false; - } - - getPaginatedItems(): Item[] { - return this.data ?? []; - } - - override hasNextPage(): boolean { - if (this.has_more === false) { - return false; - } - - return super.hasNextPage(); - } - - nextPageRequestOptions(): PageRequestOptions | null { - const cursor = this.next_cursor; - if (!cursor) { - return null; - } - - return { - ...this.options, - query: { - ...maybeObj(this.options.query), - cursor, - }, - }; - } -} diff --git a/src/core/params.gen.ts b/src/core/params.gen.ts new file mode 100644 index 0000000..0f50047 --- /dev/null +++ b/src/core/params.gen.ts @@ -0,0 +1,171 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + } + | { + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If `in` is omitted, `map` aliases `key` to the transport layer. + */ + map: Slot; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + | { + in: Slot; + map?: string; + } + | { + in?: never; + map: Slot; + } +>; + +function buildKeyMap(fields: FieldsConfig, map?: KeyMap): KeyMap { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if ('key' in config) { + map.set(config.key, { + map: config.map, + }); + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +} + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +type ParamsSlotMap = Record; + +function stripEmptySlots(params: ParamsSlotMap): void { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Array.isArray(value) && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +} + +export function buildClientParams(args: ReadonlyArray, fields: FieldsConfig): Params { + const params: ParamsSlotMap = { + body: Object.create(null), + headers: Object.create(null), + path: Object.create(null), + query: Object.create(null), + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + if (field.in) { + (params[field.in] as Record)[name] = arg; + } + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + if (field.in) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + params[field.map] = value; + } + } else { + const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix)); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[key.slice(prefix.length)] = value; + } else if ('allowExtra' in config && config.allowExtra) { + for (const [slot, allowed] of Object.entries(config.allowExtra)) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params as Params; +} diff --git a/src/core/pathSerializer.gen.ts b/src/core/pathSerializer.gen.ts new file mode 100644 index 0000000..fab1ed4 --- /dev/null +++ b/src/core/pathSerializer.gen.ts @@ -0,0 +1,171 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions extends SerializePrimitiveOptions, SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle): '.' | ';' | ',' | '&' => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle): ',' | '|' | '%20' => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle): '.' | ';' | ',' | '&' => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}): string => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam): string => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}): string => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; +}; diff --git a/src/core/queryKeySerializer.gen.ts b/src/core/queryKeySerializer.gen.ts new file mode 100644 index 0000000..773b065 --- /dev/null +++ b/src/core/queryKeySerializer.gen.ts @@ -0,0 +1,117 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * JSON-friendly union that mirrors what Pinia Colada can hash. + */ +export type JsonValue = + | null + | string + | number + | boolean + | JsonValue[] + | { [key: string]: JsonValue }; + +/** + * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes. + */ +export const queryKeyJsonReplacer = (_key: string, value: unknown): unknown | undefined => { + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { + return undefined; + } + if (typeof value === 'bigint') { + return value.toString(); + } + if (value instanceof Date) { + return value.toISOString(); + } + return value; +}; + +/** + * Safely stringifies a value and parses it back into a JsonValue. + */ +export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => { + try { + const json = JSON.stringify(input, queryKeyJsonReplacer); + if (json === undefined) { + return undefined; + } + return JSON.parse(json) as JsonValue; + } catch { + return undefined; + } +}; + +/** + * Detects plain objects (including objects with a null prototype). + */ +const isPlainObject = (value: unknown): value is Record => { + if (value === null || typeof value !== 'object') { + return false; + } + const prototype = Object.getPrototypeOf(value as object); + return prototype === Object.prototype || prototype === null; +}; + +/** + * Turns URLSearchParams into a sorted JSON object for deterministic keys. + */ +const serializeSearchParams = (params: URLSearchParams): JsonValue => { + const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b)); + const result: Record = {}; + + for (const [key, value] of entries) { + const existing = result[key]; + if (existing === undefined) { + result[key] = value; + continue; + } + + if (Array.isArray(existing)) { + (existing as string[]).push(value); + } else { + result[key] = [existing, value]; + } + } + + return result; +}; + +/** + * Normalizes any accepted value into a JSON-friendly shape for query keys. + */ +export const serializeQueryKeyValue = (value: unknown): JsonValue | undefined => { + if (value === null) { + return null; + } + + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return value; + } + + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { + return undefined; + } + + if (typeof value === 'bigint') { + return value.toString(); + } + + if (value instanceof Date) { + return value.toISOString(); + } + + if (Array.isArray(value)) { + return stringifyToJsonValue(value); + } + + if (typeof URLSearchParams !== 'undefined' && value instanceof URLSearchParams) { + return serializeSearchParams(value); + } + + if (isPlainObject(value)) { + return stringifyToJsonValue(value); + } + + return undefined; +}; diff --git a/src/core/resource.ts b/src/core/resource.ts deleted file mode 100644 index 5f2ee60..0000000 --- a/src/core/resource.ts +++ /dev/null @@ -1,11 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import type { Unlayer } from '../client'; - -export abstract class APIResource { - protected _client: Unlayer; - - constructor(client: Unlayer) { - this._client = client; - } -} diff --git a/src/core/serverSentEvents.gen.ts b/src/core/serverSentEvents.gen.ts new file mode 100644 index 0000000..ddf3c4d --- /dev/null +++ b/src/core/serverSentEvents.gen.ts @@ -0,0 +1,242 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export function createSseClient({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult { + let lastEventId: string | undefined; + + const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + buffer = buffer.replace(/\r\n?/g, '\n'); // normalize line endings + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt(line.replace(/^retry:\s*/, ''), 10); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if (sseMaxRetryAttempts !== undefined && attempt >= sseMaxRetryAttempts) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 30000); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +} diff --git a/src/core/types.gen.ts b/src/core/types.gen.ts new file mode 100644 index 0000000..c657c85 --- /dev/null +++ b/src/core/types.gen.ts @@ -0,0 +1,110 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] ? { sse?: never } : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + string | number | boolean | (string | number | boolean)[] | null | undefined | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g., converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +/** + * Arbitrary metadata passed through the `meta` request option. + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface ClientMeta {} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true ? never : K]: T[K]; +}; diff --git a/src/core/uploads.ts b/src/core/uploads.ts deleted file mode 100644 index 2882ca6..0000000 --- a/src/core/uploads.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { type Uploadable } from '../internal/uploads'; -export { toFile, type ToFileInput } from '../internal/to-file'; diff --git a/src/core/utils.gen.ts b/src/core/utils.gen.ts new file mode 100644 index 0000000..af56e07 --- /dev/null +++ b/src/core/utils.gen.ts @@ -0,0 +1,140 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE: RegExp = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer): string => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace(match, serializeArrayParam({ explode, name, style, value })); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}): string => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}): unknown { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e., client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/src/error.ts b/src/error.ts deleted file mode 100644 index fc55f46..0000000 --- a/src/error.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/error instead */ -export * from './core/error'; diff --git a/src/index.ts b/src/index.ts index 2a2ff83..4e3f3a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,23 +1,4 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +// This file is auto-generated by @hey-api/openapi-ts -export { Unlayer as default } from './client'; - -export { type Uploadable, toFile } from './core/uploads'; -export { APIPromise } from './core/api-promise'; -export { Unlayer, type ClientOptions } from './client'; -export { PagePromise } from './core/pagination'; -export { - UnlayerError, - APIError, - APIConnectionError, - APIConnectionTimeoutError, - APIUserAbortError, - NotFoundError, - ConflictError, - RateLimitError, - BadRequestError, - AuthenticationError, - InternalServerError, - PermissionDeniedError, - UnprocessableEntityError, -} from './core/error'; +export { AiCredits, Blocks, Domains, EditorSessions, Emails, Export, Me, type Options, Projects, Templates, Unlayer, Webhooks, Workspaces } from './sdk.gen'; +export type { AddSuppressionData, AddSuppressionError, AddSuppressionErrors, AddSuppressionResponse, AddSuppressionResponses, CheckSuppressionData, CheckSuppressionError, CheckSuppressionErrors, CheckSuppressionResponse, CheckSuppressionResponses, ClientOptions, ConvertFullToSimpleData, ConvertFullToSimpleError, ConvertFullToSimpleErrors, ConvertFullToSimpleResponse, ConvertFullToSimpleResponses, ConvertSimpleToFullData, ConvertSimpleToFullError, ConvertSimpleToFullErrors, ConvertSimpleToFullResponse, ConvertSimpleToFullResponses, CreateDomainData, CreateDomainError, CreateDomainErrors, CreateDomainResponse, CreateDomainResponses, CreateEditorSessionData, CreateEditorSessionError, CreateEditorSessionErrors, CreateEditorSessionResponse, CreateEditorSessionResponses, CreateWebhookData, CreateWebhookError, CreateWebhookErrors, CreateWebhookResponse, CreateWebhookResponses, DeleteDomainData, DeleteDomainError, DeleteDomainErrors, DeleteDomainResponse, DeleteDomainResponses, DeleteWebhookData, DeleteWebhookError, DeleteWebhookErrors, DeleteWebhookResponse, DeleteWebhookResponses, ExportHtmlData, ExportHtmlError, ExportHtmlErrors, ExportHtmlResponse, ExportHtmlResponses, ExportImageData, ExportImageError, ExportImageErrors, ExportImageResponse, ExportImageResponses, ExportPdfData, ExportPdfError, ExportPdfErrors, ExportPdfResponse, ExportPdfResponses, ExportZipData, ExportZipError, ExportZipErrors, ExportZipResponse, ExportZipResponses, GenerateDesignData, GenerateDesignError, GenerateDesignErrors, GenerateDesignResponse, GenerateDesignResponses, GetDesignSchemaData, GetDesignSchemaResponses, GetDomainData, GetDomainError, GetDomainErrors, GetDomainResponse, GetDomainResponses, GetEmailData, GetEmailError, GetEmailErrors, GetEmailEventsData, GetEmailEventsError, GetEmailEventsErrors, GetEmailEventsResponse, GetEmailEventsResponses, GetEmailResponse, GetEmailResponses, GetEmailSettingsData, GetEmailSettingsError, GetEmailSettingsErrors, GetEmailSettingsResponse, GetEmailSettingsResponses, GetEmailStatsData, GetEmailStatsError, GetEmailStatsErrors, GetEmailStatsResponse, GetEmailStatsResponses, GetMySubscriptionData, GetMySubscriptionError, GetMySubscriptionErrors, GetMySubscriptionResponse, GetMySubscriptionResponses, GetProjectAiCreditsData, GetProjectAiCreditsError, GetProjectAiCreditsErrors, GetProjectAiCreditsResponse, GetProjectAiCreditsResponses, GetProjectAiCreditsSettingsData, GetProjectAiCreditsSettingsError, GetProjectAiCreditsSettingsErrors, GetProjectAiCreditsSettingsResponse, GetProjectAiCreditsSettingsResponses, GetProjectAiCreditsUsageData, GetProjectAiCreditsUsageError, GetProjectAiCreditsUsageErrors, GetProjectAiCreditsUsageResponse, GetProjectAiCreditsUsageResponses, GetProjectData, GetProjectError, GetProjectErrors, GetProjectResponse, GetProjectResponses, GetTemplateData, GetTemplateError, GetTemplateErrors, GetTemplateResponse, GetTemplateResponses, GetV3TemplatesGenerateData, GetV3TemplatesGenerateResponses, GetWebhookData, GetWebhookError, GetWebhookErrors, GetWebhookResponse, GetWebhookResponses, GetWorkspaceData, GetWorkspaceError, GetWorkspaceErrors, GetWorkspaceResponse, GetWorkspaceResponses, ImportTemplateData, ImportTemplateError, ImportTemplateErrors, ImportTemplateResponse, ImportTemplateResponses, ListBlocksData, ListBlocksError, ListBlocksErrors, ListBlocksResponse, ListBlocksResponses, ListDomainsData, ListDomainsError, ListDomainsErrors, ListDomainsResponse, ListDomainsResponses, ListEmailsData, ListEmailsError, ListEmailsErrors, ListEmailsResponse, ListEmailsResponses, ListProjectAiCreditsWebhookDeliveriesData, ListProjectAiCreditsWebhookDeliveriesError, ListProjectAiCreditsWebhookDeliveriesErrors, ListProjectAiCreditsWebhookDeliveriesResponse, ListProjectAiCreditsWebhookDeliveriesResponses, ListProjectAiCreditsWebhookDeliveryAttemptsData, ListProjectAiCreditsWebhookDeliveryAttemptsError, ListProjectAiCreditsWebhookDeliveryAttemptsErrors, ListProjectAiCreditsWebhookDeliveryAttemptsResponse, ListProjectAiCreditsWebhookDeliveryAttemptsResponses, ListSuppressionsData, ListSuppressionsError, ListSuppressionsErrors, ListSuppressionsResponse, ListSuppressionsResponses, ListTemplatesData, ListTemplatesError, ListTemplatesErrors, ListTemplatesResponse, ListTemplatesResponses, ListWebhooksData, ListWebhooksError, ListWebhooksErrors, ListWebhooksResponse, ListWebhooksResponses, ListWorkspacesData, ListWorkspacesError, ListWorkspacesErrors, ListWorkspacesResponse, ListWorkspacesResponses, RemoveSuppressionData, RemoveSuppressionError, RemoveSuppressionErrors, RemoveSuppressionResponse, RemoveSuppressionResponses, RenderEmailData, RenderEmailError, RenderEmailErrors, RenderEmailResponse, RenderEmailResponses, RetryProjectAiCreditsWebhookDeliveryData, RetryProjectAiCreditsWebhookDeliveryError, RetryProjectAiCreditsWebhookDeliveryErrors, RetryProjectAiCreditsWebhookDeliveryResponse, RetryProjectAiCreditsWebhookDeliveryResponses, RotateProjectAiCreditsSigningSecretData, RotateProjectAiCreditsSigningSecretError, RotateProjectAiCreditsSigningSecretErrors, RotateProjectAiCreditsSigningSecretResponse, RotateProjectAiCreditsSigningSecretResponses, RotateWebhookSecretData, RotateWebhookSecretError, RotateWebhookSecretErrors, RotateWebhookSecretResponse, RotateWebhookSecretResponses, SendEmailData, SendEmailError, SendEmailErrors, SendEmailResponse, SendEmailResponses, SendTemplateEmailData, SendTemplateEmailError, SendTemplateEmailErrors, SendTemplateEmailResponse, SendTemplateEmailResponses, UpdateEmailSettingsData, UpdateEmailSettingsError, UpdateEmailSettingsErrors, UpdateEmailSettingsResponse, UpdateEmailSettingsResponses, UpdateProjectAiCreditsSettingsData, UpdateProjectAiCreditsSettingsError, UpdateProjectAiCreditsSettingsErrors, UpdateProjectAiCreditsSettingsResponse, UpdateProjectAiCreditsSettingsResponses, UpdateWebhookData, UpdateWebhookError, UpdateWebhookErrors, UpdateWebhookResponse, UpdateWebhookResponses, ValidateDesignData, ValidateDesignError, ValidateDesignErrors, ValidateDesignResponse, ValidateDesignResponses, VerifyDomainData, VerifyDomainError, VerifyDomainErrors, VerifyDomainResponse, VerifyDomainResponses } from './types.gen'; diff --git a/src/internal/README.md b/src/internal/README.md deleted file mode 100644 index 3ef5a25..0000000 --- a/src/internal/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `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 deleted file mode 100644 index c23d3bd..0000000 --- a/src/internal/builtin-types.ts +++ /dev/null @@ -1,93 +0,0 @@ -// 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 deleted file mode 100644 index e82d95c..0000000 --- a/src/internal/detect-platform.ts +++ /dev/null @@ -1,196 +0,0 @@ -// 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 deleted file mode 100644 index 82c7b14..0000000 --- a/src/internal/errors.ts +++ /dev/null @@ -1,33 +0,0 @@ -// 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 deleted file mode 100644 index c724a9d..0000000 --- a/src/internal/headers.ts +++ /dev/null @@ -1,97 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isReadonlyArray } from './utils/values'; - -type HeaderValue = string | undefined | null; -export type HeadersLike = - | Headers - | readonly HeaderValue[][] - | Record - | undefined - | null - | NullableHeaders; - -const brand_privateNullableHeaders = /* @__PURE__ */ Symbol('brand.privateNullableHeaders'); - -/** - * @internal - * Users can pass explicit nulls to unset default headers. When we parse them - * into a standard headers type we need to preserve that information. - */ -export type NullableHeaders = { - /** Brand check, prevent users from creating a NullableHeaders. */ - [brand_privateNullableHeaders]: true; - /** Parsed headers. */ - values: Headers; - /** Set of lowercase header names explicitly set to null. */ - nulls: Set; -}; - -function* iterateHeaders(headers: HeadersLike): IterableIterator { - if (!headers) return; - - if (brand_privateNullableHeaders in headers) { - const { values, nulls } = headers; - yield* values.entries(); - for (const name of nulls) { - yield [name, null]; - } - return; - } - - let shouldClear = false; - let iter: Iterable; - if (headers instanceof Headers) { - iter = headers.entries(); - } else if (isReadonlyArray(headers)) { - iter = headers; - } else { - shouldClear = true; - iter = Object.entries(headers ?? {}); - } - for (let row of iter) { - const name = row[0]; - if (typeof name !== 'string') throw new TypeError('expected header name to be a string'); - const values = isReadonlyArray(row[1]) ? row[1] : [row[1]]; - let didClear = false; - for (const value of values) { - if (value === undefined) continue; - - // Objects keys always overwrite older headers, they never append. - // Yield a null to clear the header before adding the new values. - if (shouldClear && !didClear) { - didClear = true; - yield [name, null]; - } - yield [name, value]; - } - } -} - -export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => { - const targetHeaders = new Headers(); - const nullHeaders = new Set(); - for (const headers of newHeaders) { - const seenHeaders = new Set(); - for (const [name, value] of iterateHeaders(headers)) { - const lowerName = name.toLowerCase(); - if (!seenHeaders.has(lowerName)) { - targetHeaders.delete(name); - seenHeaders.add(lowerName); - } - if (value === null) { - targetHeaders.delete(name); - nullHeaders.add(lowerName); - } else { - targetHeaders.append(name, value); - nullHeaders.delete(lowerName); - } - } - } - return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders }; -}; - -export const isEmptyHeaders = (headers: HeadersLike) => { - for (const _ of iterateHeaders(headers)) return false; - return true; -}; diff --git a/src/internal/parse.ts b/src/internal/parse.ts deleted file mode 100644 index 2af5769..0000000 --- a/src/internal/parse.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import type { FinalRequestOptions } from './request-options'; -import { type Unlayer } 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: Unlayer, 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 contentLength = response.headers.get('content-length'); - if (contentLength === '0') { - // if there is no content we can't do anything - return undefined as T; - } - - 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/request-options.ts b/src/internal/request-options.ts deleted file mode 100644 index 2aabf9a..0000000 --- a/src/internal/request-options.ts +++ /dev/null @@ -1,91 +0,0 @@ -// 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 deleted file mode 100644 index 8ddf7b0..0000000 --- a/src/internal/shim-types.ts +++ /dev/null @@ -1,26 +0,0 @@ -// 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 deleted file mode 100644 index 6a2681a..0000000 --- a/src/internal/shims.ts +++ /dev/null @@ -1,107 +0,0 @@ -// 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 Unlayer({ 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 deleted file mode 100644 index 30eada3..0000000 --- a/src/internal/to-file.ts +++ /dev/null @@ -1,154 +0,0 @@ -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 deleted file mode 100644 index b668dfc..0000000 --- a/src/internal/types.ts +++ /dev/null @@ -1,95 +0,0 @@ -// 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 deleted file mode 100644 index bce6d51..0000000 --- a/src/internal/uploads.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { type RequestOptions } from './request-options'; -import type { FilePropertyBag, Fetch } from './builtin-types'; -import type { Unlayer } 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: Unlayer | 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: Unlayer | 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: Unlayer | 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: Unlayer | 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 deleted file mode 100644 index 3cbfacc..0000000 --- a/src/internal/utils.ts +++ /dev/null @@ -1,8 +0,0 @@ -// 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 deleted file mode 100644 index d4d32fb..0000000 --- a/src/internal/utils/base64.ts +++ /dev/null @@ -1,40 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { UnlayerError } 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 UnlayerError('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 UnlayerError('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 deleted file mode 100644 index 8da627a..0000000 --- a/src/internal/utils/bytes.ts +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 100644 index 2d84800..0000000 --- a/src/internal/utils/env.ts +++ /dev/null @@ -1,18 +0,0 @@ -// 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 deleted file mode 100644 index 1726922..0000000 --- a/src/internal/utils/log.ts +++ /dev/null @@ -1,126 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { hasOwn } from './values'; -import { type Unlayer } 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: Unlayer, -): 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: Unlayer): 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 deleted file mode 100644 index 213ddcb..0000000 --- a/src/internal/utils/path.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { UnlayerError } 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 UnlayerError( - `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 deleted file mode 100644 index 65e5296..0000000 --- a/src/internal/utils/sleep.ts +++ /dev/null @@ -1,3 +0,0 @@ -// 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 deleted file mode 100644 index b0e53aa..0000000 --- a/src/internal/utils/uuid.ts +++ /dev/null @@ -1,17 +0,0 @@ -// 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 deleted file mode 100644 index 0fcd830..0000000 --- a/src/internal/utils/values.ts +++ /dev/null @@ -1,105 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { UnlayerError } 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 UnlayerError(`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 UnlayerError(`${name} must be an integer`); - } - if (n < 0) { - throw new UnlayerError(`${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 UnlayerError(`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 UnlayerError(`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/lib/.keep b/src/lib/.keep deleted file mode 100644 index 7554f8b..0000000 --- a/src/lib/.keep +++ /dev/null @@ -1,4 +0,0 @@ -File generated from our OpenAPI spec by Stainless. - -This directory can be used to store custom files to expand the SDK. -It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. diff --git a/src/pagination.ts b/src/pagination.ts deleted file mode 100644 index 90bf015..0000000 --- a/src/pagination.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/pagination instead */ -export * from './core/pagination'; diff --git a/src/resource.ts b/src/resource.ts deleted file mode 100644 index 363e351..0000000 --- a/src/resource.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/resource instead */ -export * from './core/resource'; diff --git a/src/resources.ts b/src/resources.ts deleted file mode 100644 index b283d57..0000000 --- a/src/resources.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './resources/index'; diff --git a/src/resources/convert.ts b/src/resources/convert.ts deleted file mode 100644 index 1334f91..0000000 --- a/src/resources/convert.ts +++ /dev/null @@ -1,3 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export * from './convert/index'; diff --git a/src/resources/convert/convert.ts b/src/resources/convert/convert.ts deleted file mode 100644 index d7930c4..0000000 --- a/src/resources/convert/convert.ts +++ /dev/null @@ -1,29 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../../core/resource'; -import * as FullToSimpleAPI from './full-to-simple'; -import { FullToSimple, FullToSimpleCreateParams, FullToSimpleCreateResponse } from './full-to-simple'; -import * as SimpleToFullAPI from './simple-to-full'; -import { SimpleToFull, SimpleToFullCreateParams, SimpleToFullCreateResponse } from './simple-to-full'; - -export class Convert extends APIResource { - fullToSimple: FullToSimpleAPI.FullToSimple = new FullToSimpleAPI.FullToSimple(this._client); - simpleToFull: SimpleToFullAPI.SimpleToFull = new SimpleToFullAPI.SimpleToFull(this._client); -} - -Convert.FullToSimple = FullToSimple; -Convert.SimpleToFull = SimpleToFull; - -export declare namespace Convert { - export { - FullToSimple as FullToSimple, - type FullToSimpleCreateResponse as FullToSimpleCreateResponse, - type FullToSimpleCreateParams as FullToSimpleCreateParams, - }; - - export { - SimpleToFull as SimpleToFull, - type SimpleToFullCreateResponse as SimpleToFullCreateResponse, - type SimpleToFullCreateParams as SimpleToFullCreateParams, - }; -} diff --git a/src/resources/convert/full-to-simple.ts b/src/resources/convert/full-to-simple.ts deleted file mode 100644 index 44e02b9..0000000 --- a/src/resources/convert/full-to-simple.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../../core/resource'; -import { APIPromise } from '../../core/api-promise'; -import { RequestOptions } from '../../internal/request-options'; - -export class FullToSimple extends APIResource { - /** - * Convert design json from Full to Simple schema. - */ - create(body: FullToSimpleCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/v3/convert/full-to-simple', { body, ...options }); - } -} - -export interface FullToSimpleCreateResponse { - data?: FullToSimpleCreateResponse.Data; - - success?: true; -} - -export namespace FullToSimpleCreateResponse { - export interface Data { - design?: { [key: string]: unknown }; - } -} - -export interface FullToSimpleCreateParams { - design: FullToSimpleCreateParams.Design; - - displayMode?: 'email' | 'web' | 'popup' | 'document'; - - /** - * When true, includes \_conversion metadata in the response. This metadata can be - * passed to simple-to-full to restore original values without data loss. - */ - includeConversion?: boolean; - - includeDefaultValues?: boolean; -} - -export namespace FullToSimpleCreateParams { - export interface Design { - body: { [key: string]: unknown }; - - counters?: { [key: string]: unknown }; - - schemaVersion?: number; - - [k: string]: unknown; - } -} - -export declare namespace FullToSimple { - export { - type FullToSimpleCreateResponse as FullToSimpleCreateResponse, - type FullToSimpleCreateParams as FullToSimpleCreateParams, - }; -} diff --git a/src/resources/convert/index.ts b/src/resources/convert/index.ts deleted file mode 100644 index 833a9fc..0000000 --- a/src/resources/convert/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export { Convert } from './convert'; -export { - FullToSimple, - type FullToSimpleCreateResponse, - type FullToSimpleCreateParams, -} from './full-to-simple'; -export { - SimpleToFull, - type SimpleToFullCreateResponse, - type SimpleToFullCreateParams, -} from './simple-to-full'; diff --git a/src/resources/convert/simple-to-full.ts b/src/resources/convert/simple-to-full.ts deleted file mode 100644 index c1051de..0000000 --- a/src/resources/convert/simple-to-full.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../../core/resource'; -import { APIPromise } from '../../core/api-promise'; -import { RequestOptions } from '../../internal/request-options'; - -export class SimpleToFull extends APIResource { - /** - * Convert design json from Simple to Full schema. - */ - create(body: SimpleToFullCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/v3/convert/simple-to-full', { body, ...options }); - } -} - -export interface SimpleToFullCreateResponse { - data?: SimpleToFullCreateResponse.Data; - - success?: true; -} - -export namespace SimpleToFullCreateResponse { - export interface Data { - design?: { [key: string]: unknown }; - } -} - -export interface SimpleToFullCreateParams { - design: SimpleToFullCreateParams.Design; - - displayMode?: 'email' | 'web' | 'popup' | 'document'; - - includeDefaultValues?: boolean; -} - -export namespace SimpleToFullCreateParams { - export interface Design { - body: { [key: string]: unknown }; - - _conversion?: Design._Conversion; - - counters?: { [key: string]: unknown }; - - schemaVersion?: number; - - [k: string]: unknown; - } - - export namespace Design { - export interface _Conversion { - data?: string; - - version?: number; - } - } -} - -export declare namespace SimpleToFull { - export { - type SimpleToFullCreateResponse as SimpleToFullCreateResponse, - type SimpleToFullCreateParams as SimpleToFullCreateParams, - }; -} diff --git a/src/resources/index.ts b/src/resources/index.ts deleted file mode 100644 index 303eae0..0000000 --- a/src/resources/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export { Convert } from './convert/convert'; -export { Projects, type ProjectRetrieveResponse } from './projects'; -export { - Templates, - type TemplateRetrieveResponse, - type TemplateListResponse, - type TemplateRetrieveParams, - type TemplateListParams, - type TemplateListResponsesCursorPage, -} from './templates'; -export { Workspaces, type WorkspaceRetrieveResponse, type WorkspaceListResponse } from './workspaces'; diff --git a/src/resources/projects.ts b/src/resources/projects.ts deleted file mode 100644 index 6b97668..0000000 --- a/src/resources/projects.ts +++ /dev/null @@ -1,57 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -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 Projects extends APIResource { - /** - * Get project details by ID. - */ - retrieve(id: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/v3/projects/${id}`, options); - } -} - -export interface ProjectRetrieveResponse { - data?: ProjectRetrieveResponse.Data; -} - -export namespace ProjectRetrieveResponse { - export interface Data { - /** - * The project ID. - */ - id?: number; - - /** - * When the project was created. - */ - createdAt?: string; - - /** - * The project name. - */ - name?: string; - - /** - * The project status. - */ - status?: string; - - workspace?: Data.Workspace; - } - - export namespace Data { - export interface Workspace { - id?: number; - - name?: string; - } - } -} - -export declare namespace Projects { - export { type ProjectRetrieveResponse as ProjectRetrieveResponse }; -} diff --git a/src/resources/templates.ts b/src/resources/templates.ts deleted file mode 100644 index 38eef4b..0000000 --- a/src/resources/templates.ts +++ /dev/null @@ -1,110 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; -import { CursorPage, type CursorPageParams, PagePromise } from '../core/pagination'; -import { RequestOptions } from '../internal/request-options'; -import { path } from '../internal/utils/path'; - -export class Templates extends APIResource { - /** - * Get template by ID. - */ - retrieve( - id: string, - query: TemplateRetrieveParams | null | undefined = {}, - options?: RequestOptions, - ): APIPromise { - return this._client.get(path`/v3/templates/${id}`, { query, ...options }); - } - - /** - * List templates with cursor-based pagination. Returns templates in descending - * order by update time. - */ - list( - query: TemplateListParams | null | undefined = {}, - options?: RequestOptions, - ): PagePromise { - return this._client.getAPIList('/v3/templates', CursorPage, { query, ...options }); - } -} - -export type TemplateListResponsesCursorPage = CursorPage; - -export interface TemplateRetrieveResponse { - data?: TemplateRetrieveResponse.Data; -} - -export namespace TemplateRetrieveResponse { - export interface Data { - id?: string; - - createdAt?: string; - - design?: { [key: string]: unknown }; - - displayMode?: 'email' | 'web' | 'document'; - - html?: string | null; - - name?: string; - - updatedAt?: string; - } -} - -export interface TemplateListResponse { - /** - * Template ID - */ - id?: string; - - createdAt?: string; - - /** - * Template type/display mode - */ - displayMode?: 'email' | 'web' | 'document'; - - /** - * Template name - */ - name?: string; - - updatedAt?: string; -} - -export interface TemplateRetrieveParams { - /** - * The project ID (required for PAT auth, auto-resolved for API key auth) - */ - projectId?: string; -} - -export interface TemplateListParams extends CursorPageParams { - /** - * Filter by template type - */ - displayMode?: 'email' | 'web' | 'document'; - - /** - * Filter by name (case-insensitive search) - */ - name?: string; - - /** - * The project ID to list templates for - */ - projectId?: string; -} - -export declare namespace Templates { - export { - type TemplateRetrieveResponse as TemplateRetrieveResponse, - type TemplateListResponse as TemplateListResponse, - type TemplateListResponsesCursorPage as TemplateListResponsesCursorPage, - type TemplateRetrieveParams as TemplateRetrieveParams, - type TemplateListParams as TemplateListParams, - }; -} diff --git a/src/resources/workspaces.ts b/src/resources/workspaces.ts deleted file mode 100644 index e51624e..0000000 --- a/src/resources/workspaces.ts +++ /dev/null @@ -1,67 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -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 Workspaces extends APIResource { - /** - * Get a specific workspace by ID with its projects. Requires a Personal Access - * Token (PAT). - */ - retrieve(workspaceID: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/v3/workspaces/${workspaceID}`, options); - } - - /** - * Get all workspaces accessible by the current token. Requires a Personal Access - * Token (PAT). - */ - list(options?: RequestOptions): APIPromise { - return this._client.get('/v3/workspaces', options); - } -} - -export interface WorkspaceRetrieveResponse { - data?: WorkspaceRetrieveResponse.Data; -} - -export namespace WorkspaceRetrieveResponse { - export interface Data { - id?: number; - - name?: string; - - projects?: Array; - } - - export namespace Data { - export interface Project { - id?: number; - - name?: string; - - status?: string; - } - } -} - -export interface WorkspaceListResponse { - data?: Array; -} - -export namespace WorkspaceListResponse { - export interface Data { - id?: number; - - name?: string; - } -} - -export declare namespace Workspaces { - export { - type WorkspaceRetrieveResponse as WorkspaceRetrieveResponse, - type WorkspaceListResponse as WorkspaceListResponse, - }; -} diff --git a/src/sdk.gen.ts b/src/sdk.gen.ts new file mode 100644 index 0000000..f6f16a0 --- /dev/null +++ b/src/sdk.gen.ts @@ -0,0 +1,1342 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Client, ClientMeta, Options as Options2, RequestResult, TDataShape } from './client'; +import type { AddSuppressionData, AddSuppressionErrors, AddSuppressionResponses, CheckSuppressionData, CheckSuppressionErrors, CheckSuppressionResponses, ConvertFullToSimpleData, ConvertFullToSimpleErrors, ConvertFullToSimpleResponses, ConvertSimpleToFullData, ConvertSimpleToFullErrors, ConvertSimpleToFullResponses, CreateDomainData, CreateDomainErrors, CreateDomainResponses, CreateEditorSessionData, CreateEditorSessionErrors, CreateEditorSessionResponses, CreateWebhookData, CreateWebhookErrors, CreateWebhookResponses, DeleteDomainData, DeleteDomainErrors, DeleteDomainResponses, DeleteWebhookData, DeleteWebhookErrors, DeleteWebhookResponses, ExportHtmlData, ExportHtmlErrors, ExportHtmlResponses, ExportImageData, ExportImageErrors, ExportImageResponses, ExportPdfData, ExportPdfErrors, ExportPdfResponses, ExportZipData, ExportZipErrors, ExportZipResponses, GenerateDesignData, GenerateDesignErrors, GenerateDesignResponses, GetDesignSchemaData, GetDesignSchemaResponses, GetDomainData, GetDomainErrors, GetDomainResponses, GetEmailData, GetEmailErrors, GetEmailEventsData, GetEmailEventsErrors, GetEmailEventsResponses, GetEmailResponses, GetEmailSettingsData, GetEmailSettingsErrors, GetEmailSettingsResponses, GetEmailStatsData, GetEmailStatsErrors, GetEmailStatsResponses, GetMySubscriptionData, GetMySubscriptionErrors, GetMySubscriptionResponses, GetProjectAiCreditsData, GetProjectAiCreditsErrors, GetProjectAiCreditsResponses, GetProjectAiCreditsSettingsData, GetProjectAiCreditsSettingsErrors, GetProjectAiCreditsSettingsResponses, GetProjectAiCreditsUsageData, GetProjectAiCreditsUsageErrors, GetProjectAiCreditsUsageResponses, GetProjectData, GetProjectErrors, GetProjectResponses, GetTemplateData, GetTemplateErrors, GetTemplateResponses, GetWebhookData, GetWebhookErrors, GetWebhookResponses, GetWorkspaceData, GetWorkspaceErrors, GetWorkspaceResponses, ImportTemplateData, ImportTemplateErrors, ImportTemplateResponses, ListBlocksData, ListBlocksErrors, ListBlocksResponses, ListDomainsData, ListDomainsErrors, ListDomainsResponses, ListEmailsData, ListEmailsErrors, ListEmailsResponses, ListProjectAiCreditsWebhookDeliveriesData, ListProjectAiCreditsWebhookDeliveriesErrors, ListProjectAiCreditsWebhookDeliveriesResponses, ListProjectAiCreditsWebhookDeliveryAttemptsData, ListProjectAiCreditsWebhookDeliveryAttemptsErrors, ListProjectAiCreditsWebhookDeliveryAttemptsResponses, ListSuppressionsData, ListSuppressionsErrors, ListSuppressionsResponses, ListTemplatesData, ListTemplatesErrors, ListTemplatesResponses, ListWebhooksData, ListWebhooksErrors, ListWebhooksResponses, ListWorkspacesData, ListWorkspacesErrors, ListWorkspacesResponses, RemoveSuppressionData, RemoveSuppressionErrors, RemoveSuppressionResponses, RenderEmailData, RenderEmailErrors, RenderEmailResponses, RetryProjectAiCreditsWebhookDeliveryData, RetryProjectAiCreditsWebhookDeliveryErrors, RetryProjectAiCreditsWebhookDeliveryResponses, RotateProjectAiCreditsSigningSecretData, RotateProjectAiCreditsSigningSecretErrors, RotateProjectAiCreditsSigningSecretResponses, RotateWebhookSecretData, RotateWebhookSecretErrors, RotateWebhookSecretResponses, SendEmailData, SendEmailErrors, SendEmailResponses, SendTemplateEmailData, SendTemplateEmailErrors, SendTemplateEmailResponses, UpdateEmailSettingsData, UpdateEmailSettingsErrors, UpdateEmailSettingsResponses, UpdateProjectAiCreditsSettingsData, UpdateProjectAiCreditsSettingsErrors, UpdateProjectAiCreditsSettingsResponses, UpdateWebhookData, UpdateWebhookErrors, UpdateWebhookResponses, ValidateDesignData, ValidateDesignErrors, ValidateDesignResponses, VerifyDomainData, VerifyDomainErrors, VerifyDomainResponses } from './types.gen'; + +export type Options = Omit, 'responseStyle' | 'throwOnError'> & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: keyof ClientMeta extends never ? Record : ClientMeta; +}; + +class HeyApiClient { + protected client: Client; + + constructor(args: { + client: Client; + }) { + if (!args?.client) { + throw new TypeError('A client created with createClient() is required.'); + } + this.client = args.client; + } +} + +export class Blocks extends HeyApiClient { + /** + * List blocks + * + * List blocks with cursor-based pagination. Returns both shared project blocks and blocks saved by end-users; each user-saved block carries the userId it was saved under (null for shared blocks), so usage can be aggregated per end-user without enumerating user IDs. Returns blocks in descending order by creation. + */ + public listBlocks(options?: Options): RequestResult { + return (options?.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/blocks', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } +} + +export class Domains extends HeyApiClient { + /** + * List sender domains + * + * List sender domains shared by every Developer Email API project in the workspace. Requires a personal access token belonging to a workspace owner or admin; project API keys cannot manage domains. + */ + public listDomains(options?: Options): RequestResult { + return (options?.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/domains', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Add a sender domain + * + * Register a sender domain shared by every Developer Email API project in the workspace. Requires a personal access token belonging to a workspace owner or admin. Verification requires the workspace-specific TXT record and the returned SES DKIM records. + */ + public createDomain(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/domains', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Delete a sender domain + * + * Delete a sender domain shared by every Developer Email API project in the workspace. Requires a personal access token belonging to a workspace owner or admin. The SES identity remains so a later reconciler can clean it up safely. + */ + public deleteDomain(options: Options): RequestResult { + return (options.client ?? this.client).delete({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/domains/{id}', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Get domain details + * + * Get the ownership TXT challenge and SES DKIM records for a sender domain shared by every Developer Email API project in the workspace. Requires a personal access token belonging to a workspace owner or admin. + */ + public getDomain(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/domains/{id}', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Verify domain status + * + * Verify the ownership TXT challenge and SES DKIM identity for a sender domain shared by every Developer Email API project in the workspace. Requires a personal access token belonging to a workspace owner or admin. + */ + public verifyDomain(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/domains/{id}/verify', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } +} + +export class EditorSessions extends HeyApiClient { + /** + * Create editor session + * + * Create an ephemeral, no-DB editor session for a design and return a hosted editor URL the user can open to edit it in the real Unlayer editor. + */ + public createEditorSession(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/editor-sessions', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } +} + +export class Emails extends HeyApiClient { + /** + * List sent emails + * + * List emails sent from this project within the rolling 90-day history window. Without a status filter, results and date bounds use acceptance time. With a status filter, results and date bounds use the time each email entered that status. + */ + public listEmails(options?: Options): RequestResult { + return (options?.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Send an email + * + * Send a transactional email with raw HTML content. The sender domain must be verified in the project workspace; verified sender domains are shared by every Developer Email API project in that workspace. + */ + public sendEmail(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Get email details + * + * Retrieve details of a sent email, including its current delivery status, during the rolling 90-day history window. Expired emails return 404. + */ + public getEmail(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails/{id}', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Get email event timeline + * + * Retrieve the operational event timeline for a sent email, showing send, delivery, bounce, and complaint events in chronological order during the rolling 90-day history window. Expired emails return 404. + */ + public getEmailEvents(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails/{id}/events', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Render an email template + * + * Render a saved email template with optional merge variables. Returns the final HTML without sending. Useful for previewing emails before sending. + */ + public renderEmail(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails/render', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Get email settings + * + * Get the email sender settings for this project. + */ + public getEmailSettings(options?: Options): RequestResult { + return (options?.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails/settings', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Update email settings + * + * Update the email sending configuration for this project. Only include the fields you want to change. + */ + public updateEmailSettings(options?: Options): RequestResult { + return (options?.client ?? this.client).patch({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails/settings', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); + } + + /** + * Get email statistics + * + * Get aggregated email delivery statistics for a project. Returns totals or daily breakdown for the specified period. Statistics are asynchronous and may lag by about one hour. + */ + public getEmailStats(options?: Options): RequestResult { + return (options?.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails/stats', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Remove email from suppression list + * + * Remove an email address from the suppression list so it can receive emails again. + */ + public removeSuppression(options: Options): RequestResult { + return (options.client ?? this.client).delete({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails/suppressions', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * List suppressed email addresses + * + * List all email addresses suppressed for this project due to bounces, complaints, or manual suppression. Cursor-paginated. + */ + public listSuppressions(options?: Options): RequestResult { + return (options?.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails/suppressions', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Suppress an email address + * + * Manually add an email address to the suppression list. Future sends to this address will be blocked. + */ + public addSuppression(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails/suppressions', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Check if an email is suppressed + * + * Look up a specific email address to see if it is currently on the suppression list. + */ + public checkSuppression(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails/suppressions/check', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Send an email using a template + * + * Send a transactional email by rendering a saved template with optional merge variables. The template must have rendered HTML (saved at least once in the editor). The sender domain must be verified in the project workspace; verified sender domains are shared by every Developer Email API project in that workspace. + */ + public sendTemplateEmail(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/emails/template', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } +} + +export class Me extends HeyApiClient { + /** + * Get current plan and features. + * + * Get the current plan, feature availability, and limits for a project. Used to answer "can I do X" / "what plan do I need" questions with ground-truth data instead of guessing. + */ + public getMySubscription(options?: Options): RequestResult { + return (options?.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/me/subscription', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } +} + +export class Projects extends HeyApiClient { + /** + * Get project. + * + * Get project details by ID. + */ + public getProject(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/projects/{id}', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } +} + +export class AiCredits extends HeyApiClient { + /** + * Get AI credit balance. + * + * Returns the current AI credit balance for the project. Credits are pooled per workspace — every project in a workspace shares one balance. Only credit counts are returned; token counts, model names, and costs are never exposed. + */ + public getProjectAiCredits(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/projects/{id}/ai-credits', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Get AI credit settings. + * + * Returns a project's AI credit exhaustion behavior, alert thresholds, and webhook endpoint. The signing secret is never returned — only whether one exists (`has_signing_secret`). + */ + public getProjectAiCreditsSettings(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/projects/{id}/ai-credits/settings', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Update AI credit settings. + * + * Configures AI credit exhaustion behavior, usage alert thresholds, and the webhook endpoint for a project. The HMAC signing secret is generated the first time a webhook URL is set and returned exactly once in the response — store it securely; it is never shown again. + */ + public updateProjectAiCreditsSettings(options: Options): RequestResult { + return (options.client ?? this.client).put({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/projects/{id}/ai-credits/settings', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Rotate the AI credit webhook signing secret. + * + * Generates a new HMAC signing secret for the project and returns it exactly once. The previous secret stops working immediately, so update your webhook verification before rotating. Requires a webhook URL to be configured first. + */ + public rotateProjectAiCreditsSigningSecret(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/projects/{id}/ai-credits/settings/rotate-secret', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Get AI credit usage breakdown. + * + * Returns AI credit consumption for the project, broken down by end user and feature type. Filterable by date range, end user, and feature type. Usage is updated near real time and grouped by the UTC date when the AI activity occurred. Recent activity may take a short time to appear. Defaults to the current billing period. Only credit counts are returned; token counts, model names, and costs are never exposed. Per-end-user attribution requires the partner to pass `endUserId` on editor initialization. + */ + public getProjectAiCreditsUsage(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/projects/{id}/ai-credits/usage', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * List AI credit webhook deliveries. + * + * Returns the webhook delivery history for the project, newest first — the event, delivery status, attempt count, and last response code for each. Use it to spot failed deliveries and drive the retry endpoint. Payloads expose credits only. + */ + public listProjectAiCreditsWebhookDeliveries(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/projects/{id}/ai-credits/webhooks/deliveries', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * List a webhook delivery’s attempts. + * + * Returns the per-attempt history for a single delivery, newest attempt first — the response code, error, and time of each POST (including automatic retries). Returns 404 if the delivery is not found for this project. + */ + public listProjectAiCreditsWebhookDeliveryAttempts(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/projects/{id}/ai-credits/webhooks/deliveries/{deliveryId}/attempts', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Retry a webhook delivery. + * + * Re-queues a single previously-failed (or pending) webhook delivery for another attempt. Returns 404 if the delivery is not found for this project, and 409 if it was already delivered. + */ + public retryProjectAiCreditsWebhookDelivery(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/projects/{id}/ai-credits/webhooks/deliveries/{deliveryId}/retry', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } +} + +export class Templates extends HeyApiClient { + /** + * List templates + * + * List templates with cursor-based pagination. Returns templates in descending order by update time. + */ + public listTemplates(options?: Options): RequestResult { + return (options?.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Get template by ID. + * + * Get template by ID. + */ + public getTemplate(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates/{id}', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Convert Full to Simple schema. + * + * Convert design json from Full to Simple schema. + */ + public convertFullToSimple(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates/convert/full-to-simple', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Convert Simple to Full schema. + * + * Convert design json from Simple to Full schema. + */ + public convertSimpleToFull(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates/convert/simple-to-full', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * AI design generation + * + * Generate or modify an Unlayer design using AI. Send the conversation as `messages` (today only the last user message is consumed; earlier turns are accepted as chat history) and describe the target with `output.kind` + `output.displayMode`. Pass the current canvas state in `context` (full design JSON + selection pointer) to modify an existing design. Only `anthropic` and `openai` models are supported. To import existing HTML or an image instead, use POST /v3/templates/import. + */ + public generateDesign(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates/generate', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Import a template from HTML or an image + * + * Import an existing template from HTML or an image (URL or base64) and return the resulting Unlayer design JSON. No template DB entry is created. + */ + public importTemplate(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates/import', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Get the design JSON Schema. + * + * Returns the canonical design schema as a standard JSON Schema document — the exact schema POST /v3/templates/validate checks against, ready to plug into any JSON Schema validator or editor tooling. Serves the Full schema by default; pass simple=true for the compact Simple schema. No authentication required. Responses carry a strong ETag and long-lived cache headers; send If-None-Match to revalidate for free. + */ + public getDesignSchema(options?: Options): RequestResult { + return (options?.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates/schema', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Validate a design against the Unlayer schema. + * + * Validate a design JSON against the Unlayer design schema. Returns { success: true, data: { valid: true } } when the payload conforms; otherwise data is { valid: false, errors: [...] } with descriptive issues. Every checked design gets HTTP 200 — `data.valid` is the source of truth, not the status code. Only malformed requests (e.g. a missing design field or an unknown displayMode) fail request validation with 400 VALIDATION_ERROR. + */ + public validateDesign(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates/validate', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } +} + +export class Export extends HeyApiClient { + /** + * Export HTML + * + * Export a design as rendered HTML. + */ + public exportHtml(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates/export/html', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Export image + * + * Export a design as a PNG image. + */ + public exportImage(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates/export/image', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Export PDF + * + * Export a design as a PDF document. + */ + public exportPdf(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates/export/pdf', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Export ZIP + * + * Export a design as a ZIP archive containing HTML and assets. + */ + public exportZip(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/templates/export/zip', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } +} + +export class Webhooks extends HeyApiClient { + /** + * List webhooks + * + * List all webhook endpoints configured for a project. + */ + public listWebhooks(options?: Options): RequestResult { + return (options?.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/webhooks', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Create a webhook + * + * Create a new webhook endpoint. A signing secret is auto-generated and returned once. Use it to verify webhook signatures. + */ + public createWebhook(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/webhooks', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Delete a webhook + * + * Delete a webhook endpoint. It will no longer receive events. + */ + public deleteWebhook(options: Options): RequestResult { + return (options.client ?? this.client).delete({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/webhooks/{id}', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Get webhook details + * + * Get details of a specific webhook endpoint. + */ + public getWebhook(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/webhooks/{id}', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Update a webhook + * + * Update a webhook endpoint URL, events, or active status. + */ + public updateWebhook(options: Options): RequestResult { + return (options.client ?? this.client).patch({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/webhooks/{id}', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Rotate webhook signing secret + * + * Generate a new signing secret for a webhook. The new secret is returned once — store it securely. The old secret is invalidated immediately. + */ + public rotateWebhookSecret(options: Options): RequestResult { + return (options.client ?? this.client).post({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/webhooks/{id}/rotate-secret', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } +} + +export class Workspaces extends HeyApiClient { + /** + * List accessible workspaces. + * + * Get all workspaces accessible by the current token. Requires a Personal Access Token (PAT). + */ + public listWorkspaces(options?: Options): RequestResult { + return (options?.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/workspaces', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } + + /** + * Get workspace by ID. + * + * Get a specific workspace by ID with its projects. Requires a Personal Access Token (PAT). + */ + public getWorkspace(options: Options): RequestResult { + return (options.client ?? this.client).get({ + security: [{ + key: 'apiKeyAuth', + scheme: 'bearer', + type: 'http' + }, { + key: 'personalAccessTokenAuth', + scheme: 'bearer', + type: 'http' + }], + url: '/v3/workspaces/{workspaceId}', + ...options, + throwOnError: true as ThrowOnError, + responseStyle: 'data', + }); + } +} + +export class Unlayer extends HeyApiClient { + constructor(args: { + client: Client; + }) { + super(args); + } + + private _blocks?: Blocks; + get blocks(): Blocks { + return this._blocks ??= new Blocks({ client: this.client }); + } + + private _domains?: Domains; + get domains(): Domains { + return this._domains ??= new Domains({ client: this.client }); + } + + private _editorSessions?: EditorSessions; + get editorSessions(): EditorSessions { + return this._editorSessions ??= new EditorSessions({ client: this.client }); + } + + private _emails?: Emails; + get emails(): Emails { + return this._emails ??= new Emails({ client: this.client }); + } + + private _me?: Me; + get me(): Me { + return this._me ??= new Me({ client: this.client }); + } + + private _projects?: Projects; + get projects(): Projects { + return this._projects ??= new Projects({ client: this.client }); + } + + private _aiCredits?: AiCredits; + get aiCredits(): AiCredits { + return this._aiCredits ??= new AiCredits({ client: this.client }); + } + + private _templates?: Templates; + get templates(): Templates { + return this._templates ??= new Templates({ client: this.client }); + } + + private _export?: Export; + get export(): Export { + return this._export ??= new Export({ client: this.client }); + } + + private _webhooks?: Webhooks; + get webhooks(): Webhooks { + return this._webhooks ??= new Webhooks({ client: this.client }); + } + + private _workspaces?: Workspaces; + get workspaces(): Workspaces { + return this._workspaces ??= new Workspaces({ client: this.client }); + } +} diff --git a/src/types.gen.ts b/src/types.gen.ts new file mode 100644 index 0000000..e767ff5 --- /dev/null +++ b/src/types.gen.ts @@ -0,0 +1,5505 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: 'https://api.unlayer.com' | (string & {}); +}; + +export type ListBlocksData = { + body?: never; + path?: never; + query?: { + /** + * The project ID to list blocks for + */ + projectId?: string; + /** + * Number of blocks to return (1-100) + */ + limit?: number; + /** + * Pagination cursor from previous response + */ + cursor?: string; + /** + * Filter by display mode + */ + displayMode?: 'email' | 'web' | 'popup' | 'document'; + /** + * Only blocks saved by this end-user (exact match on the user id your app passes to the editor) + */ + userId?: string; + /** + * Filter by block ownership: shared project blocks, end-user saved blocks, or both + */ + scope?: 'all' | 'shared' | 'user'; + /** + * Filter by category (case-insensitive search) + */ + category?: string; + /** + * Include the block design JSON in each item. Pass false for lightweight sweeps (e.g. usage reports). + */ + includeData?: boolean; + }; + url: '/v3/blocks'; +}; + +export type ListBlocksErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ListBlocksError = ListBlocksErrors[keyof ListBlocksErrors]; + +export type ListBlocksResponses = { + /** + * Default Response + */ + 200: { + data: Array<{ + /** + * Block ID + */ + id?: string; + /** + * End-user ID the block was saved under (the user id your app passes to the editor). Null for shared project blocks. + */ + userId?: string | null; + /** + * Display mode the block was saved for: email, web, popup, or document + */ + displayMode?: string; + /** + * Block category + */ + category?: string; + /** + * Block tags + */ + tags?: Array; + /** + * The block design JSON. Omitted when includeData=false is passed. + */ + data?: { + [key: string]: unknown; + }; + /** + * URL of the auto-generated block thumbnail, if available + */ + thumbnailUrl?: string | null; + /** + * Synced-block ID referenced by designs using this block. Null when the block has never been synced. + */ + syncId?: string | null; + /** + * Whether the block is currently a synced block + */ + isSyncEnabled?: boolean; + createdAt?: string; + updatedAt?: string; + }>; + /** + * Cursor for the next page. Null if no more results. + */ + next_cursor?: string | null; + /** + * Whether there are more results after this page + */ + has_more: boolean; + }; +}; + +export type ListBlocksResponse = ListBlocksResponses[keyof ListBlocksResponses]; + +export type ListDomainsData = { + body?: never; + path?: never; + query?: never; + url: '/v3/domains'; +}; + +export type ListDomainsErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ListDomainsError = ListDomainsErrors[keyof ListDomainsErrors]; + +export type ListDomainsResponses = { + /** + * Default Response + */ + 200: { + data: Array<{ + id?: number; + domain?: string; + status?: 'pending' | 'verified' | 'failed'; + createdAt?: string; + }>; + }; +}; + +export type ListDomainsResponse = ListDomainsResponses[keyof ListDomainsResponses]; + +export type CreateDomainData = { + body: { + /** + * Domain name to register, such as example.com. + */ + domain: string; + }; + path?: never; + query?: never; + url: '/v3/domains'; +}; + +export type CreateDomainErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type CreateDomainError = CreateDomainErrors[keyof CreateDomainErrors]; + +export type CreateDomainResponses = { + /** + * Default Response + */ + 200: { + data: { + id?: number; + domain?: string; + status?: 'pending' | 'verified' | 'failed'; + createdAt?: string; + dkimTokens?: Array; + dnsRecords?: Array<{ + type?: string; + name?: string; + value?: string; + purpose?: string; + }>; + }; + }; +}; + +export type CreateDomainResponse = CreateDomainResponses[keyof CreateDomainResponses]; + +export type DeleteDomainData = { + body?: never; + path: { + /** + * Domain ID + */ + id: string; + }; + query?: never; + url: '/v3/domains/{id}'; +}; + +export type DeleteDomainErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type DeleteDomainError = DeleteDomainErrors[keyof DeleteDomainErrors]; + +export type DeleteDomainResponses = { + /** + * Default Response + */ + 200: { + data?: { + success?: boolean; + }; + }; +}; + +export type DeleteDomainResponse = DeleteDomainResponses[keyof DeleteDomainResponses]; + +export type GetDomainData = { + body?: never; + path: { + /** + * Domain ID + */ + id: string; + }; + query?: never; + url: '/v3/domains/{id}'; +}; + +export type GetDomainErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetDomainError = GetDomainErrors[keyof GetDomainErrors]; + +export type GetDomainResponses = { + /** + * Default Response + */ + 200: { + data: { + id?: number; + domain?: string; + status?: string; + dkimTokens?: Array; + dnsRecords?: Array<{ + type?: string; + name?: string; + value?: string; + purpose?: string; + }>; + createdAt?: string; + }; + }; +}; + +export type GetDomainResponse = GetDomainResponses[keyof GetDomainResponses]; + +export type VerifyDomainData = { + body?: never; + path: { + /** + * Domain ID + */ + id: string; + }; + query?: never; + url: '/v3/domains/{id}/verify'; +}; + +export type VerifyDomainErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type VerifyDomainError = VerifyDomainErrors[keyof VerifyDomainErrors]; + +export type VerifyDomainResponses = { + /** + * Default Response + */ + 200: { + data: { + id?: number; + domain?: string; + status?: string; + ownership?: { + verified?: boolean; + }; + dkim?: { + status?: string; + tokens?: Array; + }; + }; + }; +}; + +export type VerifyDomainResponse = VerifyDomainResponses[keyof VerifyDomainResponses]; + +export type CreateEditorSessionData = { + body: { + /** + * Design JSON to load into the editor. + */ + design: { + [key: string]: unknown; + }; + /** + * Editor display mode. Defaults to email. + */ + displayMode?: 'email' | 'web' | 'popup' | 'document'; + }; + path?: never; + query?: { + /** + * The project ID (required for PAT auth, auto-resolved for API key auth) + */ + projectId?: string; + }; + url: '/v3/editor-sessions'; +}; + +export type CreateEditorSessionErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type CreateEditorSessionError = CreateEditorSessionErrors[keyof CreateEditorSessionErrors]; + +export type CreateEditorSessionResponses = { + /** + * Default Response + */ + 201: { + data?: { + token?: string; + editorUrl?: string; + expiresAt?: string; + }; + }; +}; + +export type CreateEditorSessionResponse = CreateEditorSessionResponses[keyof CreateEditorSessionResponses]; + +export type ListEmailsData = { + body?: never; + path?: never; + query?: { + /** + * Project ID (auto-resolved for API key auth) + */ + projectId?: string; + /** + * Filter by email delivery status + */ + status?: 'queued' | 'sending' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'failed'; + /** + * Search recipient addresses and subjects by case-sensitive substring + */ + search?: string; + /** + * Filter by tag in "key=value" format (e.g. "campaign=welcome") + */ + tag?: string; + /** + * Start date (ISO date). Bounds acceptance time normally, or status transition time when status is supplied. + */ + from?: string; + /** + * End date (ISO date). Bounds acceptance time normally, or status transition time when status is supplied. + */ + to?: string; + /** + * Number of emails to return (1-100) + */ + limit?: number; + /** + * Pagination cursor from previous response + */ + cursor?: string; + }; + url: '/v3/emails'; +}; + +export type ListEmailsErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ListEmailsError = ListEmailsErrors[keyof ListEmailsErrors]; + +export type ListEmailsResponses = { + /** + * Default Response + */ + 200: { + data: Array<{ + id?: string; + from?: string; + to?: unknown; + subject?: string | null; + status?: string; + createdAt?: string; + /** + * When the email entered its current status. For a newly queued email, this equals createdAt. + */ + statusUpdatedAt?: string; + }>; + /** + * Cursor for the next page. Null if no more results. + */ + next_cursor?: string | null; + /** + * Whether there are more results after this page + */ + has_more: boolean; + }; +}; + +export type ListEmailsResponse = ListEmailsResponses[keyof ListEmailsResponses]; + +export type SendEmailData = { + body: { + /** + * Sender email address or "Name " format. Domain must be verified. + */ + from: string; + /** + * Exactly one recipient. Each request creates one independently tracked delivery. + */ + to: [ + string + ]; + /** + * CC is not supported by this endpoint. + */ + cc?: Array; + /** + * BCC is not supported by this endpoint. + */ + bcc?: Array; + /** + * Email subject line + */ + subject: string; + /** + * HTML content of the email + */ + html: string; + /** + * Plain text version of the email. If provided, a multipart/alternative message is sent. + */ + text?: string; + /** + * Reply-To email address + */ + replyTo?: string; + /** + * Key-value tags for categorizing the email (e.g. {"campaign": "welcome"}). Max 10 tags. Keys (1-64 chars) and values (up to 256 chars) may only contain letters, numbers, underscores, and hyphens (the Amazon SES message-tag character set). + */ + tags?: { + [key: string]: string; + }; + /** + * Custom email headers. Up to 9 printable-ASCII X-* headers are allowed (e.g. {"X-Entity-Ref-ID": "abc123"}). Header names may contain up to 126 characters and each name plus value may contain up to 996 characters. + */ + headers?: { + [key: string]: string; + }; + /** + * File attachments. Max 10 files per email, max 5 MB total payload size (including headers and base64 overhead). + */ + attachments?: Array<{ + /** + * The filename as it will appear to the recipient. Line breaks are rejected; quotes are stripped before it is written into the message. + */ + filename: string; + /** + * Base64-encoded file content. Whitespace and MIME line wrapping are removed before validation; invalid base64 is rejected with a 400 error. + */ + content: string; + /** + * MIME type of the attachment. Required; must be one of the allowed types. + */ + contentType: 'application/pdf' | 'application/zip' | 'application/json' | 'application/xml' | 'application/csv' | 'application/msword' | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' | 'application/vnd.ms-excel' | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | 'application/vnd.ms-powerpoint' | 'application/vnd.openxmlformats-officedocument.presentationml.presentation' | 'text/plain' | 'text/html' | 'text/csv' | 'text/xml' | 'text/calendar' | 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp' | 'image/svg+xml' | 'audio/mpeg' | 'audio/wav' | 'video/mp4'; + }>; + }; + headers?: { + /** + * Unique key for idempotent sends (max 255 characters). If provided, duplicate requests within 24 hours return the cached response. + */ + 'idempotency-key'?: string; + }; + path?: never; + query?: never; + url: '/v3/emails'; +}; + +export type SendEmailErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 409: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 429: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 503: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type SendEmailError = SendEmailErrors[keyof SendEmailErrors]; + +export type SendEmailResponses = { + /** + * Email accepted and queued for delivery + */ + 202: { + data: { + /** + * Unique email ID (UUID). Use this with GET /v3/emails/:id to retrieve delivery status and events. + */ + id?: string; + /** + * The sender address the email was sent from, either a plain email or "Name " format. + */ + from?: string; + /** + * The single accepted recipient address. + */ + to?: Array; + /** + * The subject line of the email that was sent. + */ + subject?: string; + /** + * Usually "queued" for a fresh send. An idempotent replay of a previously accepted request returns that email's current status instead. Use webhooks or GET /v3/emails/:id for live delivery status. + */ + status?: 'queued' | 'sending' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'failed'; + /** + * When the email was accepted and queued for delivery (ISO-8601). + */ + createdAt?: string; + }; + }; +}; + +export type SendEmailResponse = SendEmailResponses[keyof SendEmailResponses]; + +export type GetEmailData = { + body?: never; + path: { + /** + * Email ID + */ + id: string; + }; + query?: never; + url: '/v3/emails/{id}'; +}; + +export type GetEmailErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetEmailError = GetEmailErrors[keyof GetEmailErrors]; + +export type GetEmailResponses = { + /** + * Default Response + */ + 200: { + data: { + id?: string; + from?: string; + to?: unknown; + cc?: Array | null; + bcc?: Array | null; + subject?: string | null; + status?: string; + failureReason?: string | null; + tags?: { + [key: string]: string; + } | null; + createdAt?: string; + }; + }; +}; + +export type GetEmailResponse = GetEmailResponses[keyof GetEmailResponses]; + +export type GetEmailEventsData = { + body?: never; + path: { + /** + * Email ID + */ + id: string; + }; + query?: never; + url: '/v3/emails/{id}/events'; +}; + +export type GetEmailEventsErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetEmailEventsError = GetEmailEventsErrors[keyof GetEmailEventsErrors]; + +export type GetEmailEventsResponses = { + /** + * Default Response + */ + 200: { + data: Array<{ + /** + * Event type (send, delivery, bounce, complaint) + */ + type?: string; + timestamp?: string; + metadata?: { + [key: string]: unknown; + } | null; + }>; + }; +}; + +export type GetEmailEventsResponse = GetEmailEventsResponses[keyof GetEmailEventsResponses]; + +export type RenderEmailData = { + body: { + /** + * Template ID to render + */ + templateId: string; + /** + * Merge variables to substitute. Use {{key}} syntax in your template. + */ + variables?: { + [key: string]: string; + }; + }; + path?: never; + query?: never; + url: '/v3/emails/render'; +}; + +export type RenderEmailErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type RenderEmailError = RenderEmailErrors[keyof RenderEmailErrors]; + +export type RenderEmailResponses = { + /** + * Default Response + */ + 200: { + data: { + /** + * Rendered HTML content + */ + html?: string; + /** + * Template name (can be used as default subject) + */ + subject?: string | null; + }; + }; +}; + +export type RenderEmailResponse = RenderEmailResponses[keyof RenderEmailResponses]; + +export type GetEmailSettingsData = { + body?: never; + path?: never; + query?: never; + url: '/v3/emails/settings'; +}; + +export type GetEmailSettingsErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetEmailSettingsError = GetEmailSettingsErrors[keyof GetEmailSettingsErrors]; + +export type GetEmailSettingsResponses = { + /** + * Default Response + */ + 200: { + data: { + /** + * Default sender display name + */ + defaultFromName?: string; + /** + * When the settings row was first created. + */ + createdAt?: string; + /** + * When the settings were last updated. + */ + updatedAt?: string; + }; + }; +}; + +export type GetEmailSettingsResponse = GetEmailSettingsResponses[keyof GetEmailSettingsResponses]; + +export type UpdateEmailSettingsData = { + body?: { + /** + * Default sender display name + */ + defaultFromName?: string; + }; + path?: never; + query?: never; + url: '/v3/emails/settings'; +}; + +export type UpdateEmailSettingsErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type UpdateEmailSettingsError = UpdateEmailSettingsErrors[keyof UpdateEmailSettingsErrors]; + +export type UpdateEmailSettingsResponses = { + /** + * Default Response + */ + 200: { + data: { + /** + * Default sender display name + */ + defaultFromName?: string; + /** + * When the settings row was first created. + */ + createdAt?: string; + /** + * When the settings were last updated. + */ + updatedAt?: string; + }; + }; +}; + +export type UpdateEmailSettingsResponse = UpdateEmailSettingsResponses[keyof UpdateEmailSettingsResponses]; + +export type GetEmailStatsData = { + body?: never; + path?: never; + query?: { + /** + * Project ID (auto-resolved for API key auth) + */ + projectId?: string; + /** + * Time period for stats + */ + period?: '7d' | '30d' | '90d'; + /** + * Group results by day for chart data + */ + groupBy?: 'day'; + }; + url: '/v3/emails/stats'; +}; + +export type GetEmailStatsErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetEmailStatsError = GetEmailStatsErrors[keyof GetEmailStatsErrors]; + +export type GetEmailStatsResponses = { + /** + * Email statistics. Shape depends on the `groupBy` query parameter: an aggregated totals object by default, or a daily breakdown array when groupBy=day. + */ + 200: { + data: { + /** + * The period these stats cover. + */ + period?: '7d' | '30d' | '90d'; + /** + * Total emails sent (one per recipient). + */ + sent?: number; + /** + * Number of successfully delivered emails. + */ + delivered?: number; + /** + * Number of emails that were bounced by the recipient mail server. + */ + bounced?: number; + /** + * Number of spam complaint events received. + */ + complained?: number; + /** + * Delivered / sent as a percentage (0-100, 2 decimal places). + */ + deliveryRate?: number; + /** + * Bounced / sent as a percentage (0-100, 2 decimal places). + */ + bounceRate?: number; + } | Array<{ + /** + * The email send-cohort day in YYYY-MM-DD format. + */ + date?: string; + /** + * Emails sent on this day. + */ + sent?: number; + /** + * Emails from this send cohort that were delivered. + */ + delivered?: number; + /** + * Emails bounced on this day. + */ + bounced?: number; + /** + * Spam complaints received for this send cohort. + */ + complained?: number; + }>; + }; +}; + +export type GetEmailStatsResponse = GetEmailStatsResponses[keyof GetEmailStatsResponses]; + +export type RemoveSuppressionData = { + body?: never; + path?: never; + query: { + /** + * Project ID (auto-resolved for API key auth) + */ + projectId?: string; + /** + * Email address to unsuppress + */ + email: string; + }; + url: '/v3/emails/suppressions'; +}; + +export type RemoveSuppressionErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type RemoveSuppressionError = RemoveSuppressionErrors[keyof RemoveSuppressionErrors]; + +export type RemoveSuppressionResponses = { + /** + * Default Response + */ + 200: { + data: { + email?: string; + removed?: boolean; + }; + }; +}; + +export type RemoveSuppressionResponse = RemoveSuppressionResponses[keyof RemoveSuppressionResponses]; + +export type ListSuppressionsData = { + body?: never; + path?: never; + query?: { + /** + * Project ID (auto-resolved for API key auth) + */ + projectId?: string; + /** + * Max number of results (1-200) + */ + limit?: number; + /** + * Pagination cursor from a previous response. Omit to start from the beginning. + */ + cursor?: string; + }; + url: '/v3/emails/suppressions'; +}; + +export type ListSuppressionsErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ListSuppressionsError = ListSuppressionsErrors[keyof ListSuppressionsErrors]; + +export type ListSuppressionsResponses = { + /** + * Default Response + */ + 200: { + data: Array<{ + email?: string; + reason?: 'hard_bounce' | 'complaint' | 'manual' | 'unsubscribe'; + createdAt?: string; + }>; + has_more: boolean; + next_cursor?: unknown; + }; +}; + +export type ListSuppressionsResponse = ListSuppressionsResponses[keyof ListSuppressionsResponses]; + +export type AddSuppressionData = { + body: { + /** + * Email address to suppress + */ + email: string; + }; + path?: never; + query?: never; + url: '/v3/emails/suppressions'; +}; + +export type AddSuppressionErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type AddSuppressionError = AddSuppressionErrors[keyof AddSuppressionErrors]; + +export type AddSuppressionResponses = { + /** + * Default Response + */ + 200: { + data: { + email?: string; + reason?: string; + createdAt?: string; + }; + }; +}; + +export type AddSuppressionResponse = AddSuppressionResponses[keyof AddSuppressionResponses]; + +export type CheckSuppressionData = { + body?: never; + path?: never; + query: { + /** + * Project ID (auto-resolved for API key auth) + */ + projectId?: string; + /** + * Email address to check + */ + email: string; + }; + url: '/v3/emails/suppressions/check'; +}; + +export type CheckSuppressionErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type CheckSuppressionError = CheckSuppressionErrors[keyof CheckSuppressionErrors]; + +export type CheckSuppressionResponses = { + /** + * Default Response + */ + 200: { + data: { + email?: string; + suppressed?: boolean; + }; + }; +}; + +export type CheckSuppressionResponse = CheckSuppressionResponses[keyof CheckSuppressionResponses]; + +export type SendTemplateEmailData = { + body: { + /** + * Sender email address or "Name " format. Domain must be verified. + */ + from: string; + /** + * Exactly one recipient. Each request creates one independently tracked delivery. + */ + to: [ + string + ]; + /** + * CC is not supported by this endpoint. + */ + cc?: Array; + /** + * BCC is not supported by this endpoint. + */ + bcc?: Array; + /** + * Template ID to use for the email body + */ + templateId: string; + /** + * Email subject line. Supports {{variable}} merge syntax. Defaults to template name if omitted. + */ + subject?: string; + /** + * Merge variables to substitute in the template and subject. Use {{key}} syntax in your template. + */ + variables?: { + [key: string]: string; + }; + /** + * Plain text version of the email. Supports {{variable}} merge syntax. + */ + text?: string; + /** + * Reply-To email address + */ + replyTo?: string; + /** + * Key-value tags for categorizing the email (e.g. {"campaign": "welcome"}). Max 10 tags. Keys (1-64 chars) and values (up to 256 chars) may only contain letters, numbers, underscores, and hyphens (the Amazon SES message-tag character set). + */ + tags?: { + [key: string]: string; + }; + /** + * Custom email headers. Up to 9 printable-ASCII X-* headers are allowed. Header names may contain up to 126 characters and each name plus value may contain up to 996 characters. + */ + headers?: { + [key: string]: string; + }; + /** + * File attachments. Max 10 files per email, max 5 MB total payload size. + */ + attachments?: Array<{ + /** + * The filename as it will appear to the recipient. Line breaks are rejected; quotes are stripped before it is written into the message. + */ + filename: string; + /** + * Base64-encoded file content. Whitespace and MIME line wrapping are removed before validation; invalid base64 is rejected with a 400 error. + */ + content: string; + /** + * MIME type of the attachment. Required; must be one of the allowed types. + */ + contentType: 'application/pdf' | 'application/zip' | 'application/json' | 'application/xml' | 'application/csv' | 'application/msword' | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' | 'application/vnd.ms-excel' | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | 'application/vnd.ms-powerpoint' | 'application/vnd.openxmlformats-officedocument.presentationml.presentation' | 'text/plain' | 'text/html' | 'text/csv' | 'text/xml' | 'text/calendar' | 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp' | 'image/svg+xml' | 'audio/mpeg' | 'audio/wav' | 'video/mp4'; + }>; + }; + headers?: { + /** + * Unique key for idempotent sends (max 255 characters). Duplicate requests within 24 hours return the cached response. + */ + 'idempotency-key'?: string; + }; + path?: never; + query?: never; + url: '/v3/emails/template'; +}; + +export type SendTemplateEmailErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 409: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 429: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 503: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type SendTemplateEmailError = SendTemplateEmailErrors[keyof SendTemplateEmailErrors]; + +export type SendTemplateEmailResponses = { + /** + * Email accepted and queued for delivery + */ + 202: { + data: { + /** + * Unique email ID (UUID). Use this with GET /v3/emails/:id to retrieve delivery status and events. + */ + id?: string; + /** + * The sender address the email was sent from. + */ + from?: string; + /** + * The single accepted recipient address. + */ + to?: Array; + /** + * The resolved subject line after merge variables were applied. + */ + subject?: string; + /** + * Usually "queued" for a fresh send. An idempotent replay of a previously accepted request returns that email's current status instead. Use webhooks or GET /v3/emails/:id for live delivery status. + */ + status?: 'queued' | 'sending' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'failed'; + /** + * When the email was accepted and queued for delivery (ISO-8601). + */ + createdAt?: string; + }; + }; +}; + +export type SendTemplateEmailResponse = SendTemplateEmailResponses[keyof SendTemplateEmailResponses]; + +export type GetMySubscriptionData = { + body?: never; + path?: never; + query?: { + /** + * The project ID (required for PAT auth, auto-resolved for API key auth) + */ + projectId?: string; + }; + url: '/v3/me/subscription'; +}; + +export type GetMySubscriptionErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetMySubscriptionError = GetMySubscriptionErrors[keyof GetMySubscriptionErrors]; + +export type GetMySubscriptionResponses = { + /** + * Default Response + */ + 200: { + data?: { + planName?: string | null; + status?: string | null; + expiresAt?: string | null; + features?: Array<{ + name?: string; + available?: boolean; + }>; + limits?: Array<{ + name?: string; + value?: number; + unit?: string; + }>; + }; + }; +}; + +export type GetMySubscriptionResponse = GetMySubscriptionResponses[keyof GetMySubscriptionResponses]; + +export type GetProjectData = { + body?: never; + path: { + /** + * The resource ID + */ + id: string; + }; + query?: never; + url: '/v3/projects/{id}'; +}; + +export type GetProjectErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetProjectError = GetProjectErrors[keyof GetProjectErrors]; + +export type GetProjectResponses = { + /** + * Default Response + */ + 200: { + data?: { + /** + * The project ID. + */ + id?: number; + /** + * The project name. + */ + name?: string; + /** + * The project status. + */ + status?: string; + /** + * When the project was created. + */ + createdAt?: string; + workspace?: { + id?: number; + name?: string; + }; + }; + }; +}; + +export type GetProjectResponse = GetProjectResponses[keyof GetProjectResponses]; + +export type GetProjectAiCreditsData = { + body?: never; + path: { + /** + * The resource ID + */ + id: string; + }; + query?: never; + url: '/v3/projects/{id}/ai-credits'; +}; + +export type GetProjectAiCreditsErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetProjectAiCreditsError = GetProjectAiCreditsErrors[keyof GetProjectAiCreditsErrors]; + +export type GetProjectAiCreditsResponses = { + /** + * Default Response + */ + 200: { + /** + * Total AI credits available for the current period. + */ + credits_total?: number; + /** + * AI credits consumed so far in the current period. + */ + credits_used?: number; + /** + * AI credits remaining in the current period. + */ + credits_remaining?: number; + /** + * When the current credit period resets, or null if there is no active billing period — including once a subscription is cancelled or its term has ended. + */ + reset_date?: unknown; + }; +}; + +export type GetProjectAiCreditsResponse = GetProjectAiCreditsResponses[keyof GetProjectAiCreditsResponses]; + +export type GetProjectAiCreditsSettingsData = { + body?: never; + path: { + /** + * The resource ID + */ + id: string; + }; + query?: never; + url: '/v3/projects/{id}/ai-credits/settings'; +}; + +export type GetProjectAiCreditsSettingsErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetProjectAiCreditsSettingsError = GetProjectAiCreditsSettingsErrors[keyof GetProjectAiCreditsSettingsErrors]; + +export type GetProjectAiCreditsSettingsResponses = { + /** + * Default Response + */ + 200: { + exhaustion_behavior?: string; + threshold_alerts?: Array; + webhook_url?: unknown; + has_signing_secret?: boolean; + }; +}; + +export type GetProjectAiCreditsSettingsResponse = GetProjectAiCreditsSettingsResponses[keyof GetProjectAiCreditsSettingsResponses]; + +export type UpdateProjectAiCreditsSettingsData = { + body?: { + /** + * What the editor does when the credit balance is exhausted. + */ + exhaustion_behavior?: 'disable' | 'show_error'; + /** + * Usage percentages (1-100) at which a threshold_reached webhook fires, once per crossing per period. + */ + threshold_alerts?: Array; + /** + * HTTPS endpoint that receives AI credit webhooks. + */ + webhook_url?: unknown; + }; + path: { + /** + * The resource ID + */ + id: string; + }; + query?: never; + url: '/v3/projects/{id}/ai-credits/settings'; +}; + +export type UpdateProjectAiCreditsSettingsErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type UpdateProjectAiCreditsSettingsError = UpdateProjectAiCreditsSettingsErrors[keyof UpdateProjectAiCreditsSettingsErrors]; + +export type UpdateProjectAiCreditsSettingsResponses = { + /** + * Default Response + */ + 200: { + exhaustion_behavior?: string; + threshold_alerts?: Array; + webhook_url?: unknown; + has_signing_secret?: boolean; + /** + * The HMAC signing secret. Returned ONLY on the response that first generates it. + */ + signing_secret?: string; + }; +}; + +export type UpdateProjectAiCreditsSettingsResponse = UpdateProjectAiCreditsSettingsResponses[keyof UpdateProjectAiCreditsSettingsResponses]; + +export type RotateProjectAiCreditsSigningSecretData = { + body?: never; + path: { + /** + * The resource ID + */ + id: string; + }; + query?: never; + url: '/v3/projects/{id}/ai-credits/settings/rotate-secret'; +}; + +export type RotateProjectAiCreditsSigningSecretErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type RotateProjectAiCreditsSigningSecretError = RotateProjectAiCreditsSigningSecretErrors[keyof RotateProjectAiCreditsSigningSecretErrors]; + +export type RotateProjectAiCreditsSigningSecretResponses = { + /** + * Default Response + */ + 200: { + /** + * The new HMAC signing secret. Shown only once. + */ + signing_secret?: string; + }; +}; + +export type RotateProjectAiCreditsSigningSecretResponse = RotateProjectAiCreditsSigningSecretResponses[keyof RotateProjectAiCreditsSigningSecretResponses]; + +export type GetProjectAiCreditsUsageData = { + body?: never; + path: { + /** + * The resource ID + */ + id: string; + }; + query?: { + /** + * Start date (inclusive), YYYY-MM-DD. + */ + start?: string; + /** + * End date (inclusive), YYYY-MM-DD. + */ + end?: string; + /** + * Filter to a single end user id. + */ + end_user_id?: string; + /** + * Filter to a single feature type. + */ + feature_type?: 'full_template_gen' | 'block_edit' | 'html_import' | 'image_import' | 'image_generation'; + /** + * Max breakdown rows to return (1-1000). + */ + limit?: number; + /** + * Number of breakdown rows to skip (pagination). + */ + offset?: number; + /** + * Field the breakdown is ordered by. Defaults to credits. + */ + sort?: 'credits' | 'end_user_id' | 'feature_type'; + /** + * Sort direction. Defaults to desc (highest credits first). + */ + order?: 'asc' | 'desc'; + }; + url: '/v3/projects/{id}/ai-credits/usage'; +}; + +export type GetProjectAiCreditsUsageErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetProjectAiCreditsUsageError = GetProjectAiCreditsUsageErrors[keyof GetProjectAiCreditsUsageErrors]; + +export type GetProjectAiCreditsUsageResponses = { + /** + * Default Response + */ + 200: { + /** + * Total AI credits used across the full filtered range (not just the returned page). + */ + total_credits_used?: number; + /** + * Number of breakdown rows matching the filter (ignores paging). + */ + total?: number; + breakdown?: Array<{ + /** + * The end user id, or null for unattributed usage. + */ + end_user_id?: unknown; + /** + * The partner-facing feature type. + */ + feature_type?: 'full_template_gen' | 'block_edit' | 'html_import' | 'image_import' | 'image_generation'; + /** + * AI credits used by this end user and feature type. + */ + credits?: number; + }>; + }; +}; + +export type GetProjectAiCreditsUsageResponse = GetProjectAiCreditsUsageResponses[keyof GetProjectAiCreditsUsageResponses]; + +export type ListProjectAiCreditsWebhookDeliveriesData = { + body?: never; + path: { + /** + * The resource ID + */ + id: string; + }; + query?: { + /** + * Filter to a single delivery status. + */ + status?: 'pending' | 'delivered' | 'failed'; + /** + * Filter to a single event type. + */ + event?: 'ai.credits.usage_recorded' | 'ai.credits.threshold_reached' | 'ai.credits.exhausted'; + /** + * Max deliveries to return (1-100). + */ + limit?: number; + /** + * Number of deliveries to skip (pagination). + */ + offset?: number; + }; + url: '/v3/projects/{id}/ai-credits/webhooks/deliveries'; +}; + +export type ListProjectAiCreditsWebhookDeliveriesErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ListProjectAiCreditsWebhookDeliveriesError = ListProjectAiCreditsWebhookDeliveriesErrors[keyof ListProjectAiCreditsWebhookDeliveriesErrors]; + +export type ListProjectAiCreditsWebhookDeliveriesResponses = { + /** + * Default Response + */ + 200: { + deliveries?: Array<{ + id?: string; + event?: string; + status?: 'pending' | 'delivered' | 'failed'; + attempts?: number; + last_status_code?: unknown; + end_user_id?: unknown; + created_at?: string; + delivered_at?: unknown; + payload?: { + [key: string]: unknown; + }; + }>; + /** + * Total deliveries matching the filter (ignores limit/offset). + */ + total?: number; + }; +}; + +export type ListProjectAiCreditsWebhookDeliveriesResponse = ListProjectAiCreditsWebhookDeliveriesResponses[keyof ListProjectAiCreditsWebhookDeliveriesResponses]; + +export type ListProjectAiCreditsWebhookDeliveryAttemptsData = { + body?: never; + path: { + /** + * The project ID + */ + id: string; + /** + * The webhook delivery ID + */ + deliveryId: string; + }; + query?: { + /** + * Max attempts to return (1-100). + */ + limit?: number; + /** + * Number of attempts to skip (pagination). + */ + offset?: number; + }; + url: '/v3/projects/{id}/ai-credits/webhooks/deliveries/{deliveryId}/attempts'; +}; + +export type ListProjectAiCreditsWebhookDeliveryAttemptsErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 409: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 429: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ListProjectAiCreditsWebhookDeliveryAttemptsError = ListProjectAiCreditsWebhookDeliveryAttemptsErrors[keyof ListProjectAiCreditsWebhookDeliveryAttemptsErrors]; + +export type ListProjectAiCreditsWebhookDeliveryAttemptsResponses = { + /** + * Default Response + */ + 200: { + attempts?: Array<{ + attempt?: number; + status_code?: unknown; + error?: unknown; + attempted_at?: string; + }>; + /** + * Total attempts for the delivery (ignores limit/offset). + */ + total?: number; + }; +}; + +export type ListProjectAiCreditsWebhookDeliveryAttemptsResponse = ListProjectAiCreditsWebhookDeliveryAttemptsResponses[keyof ListProjectAiCreditsWebhookDeliveryAttemptsResponses]; + +export type RetryProjectAiCreditsWebhookDeliveryData = { + body?: never; + path: { + /** + * The project ID + */ + id: string; + /** + * The webhook delivery ID + */ + deliveryId: string; + }; + query?: never; + url: '/v3/projects/{id}/ai-credits/webhooks/deliveries/{deliveryId}/retry'; +}; + +export type RetryProjectAiCreditsWebhookDeliveryErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 409: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 429: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type RetryProjectAiCreditsWebhookDeliveryError = RetryProjectAiCreditsWebhookDeliveryErrors[keyof RetryProjectAiCreditsWebhookDeliveryErrors]; + +export type RetryProjectAiCreditsWebhookDeliveryResponses = { + /** + * Default Response + */ + 200: { + status?: 'requeued'; + }; +}; + +export type RetryProjectAiCreditsWebhookDeliveryResponse = RetryProjectAiCreditsWebhookDeliveryResponses[keyof RetryProjectAiCreditsWebhookDeliveryResponses]; + +export type ListTemplatesData = { + body?: never; + path?: never; + query?: { + /** + * The project ID to list templates for + */ + projectId?: string; + /** + * Number of templates to return (1-100) + */ + limit?: number; + /** + * Pagination cursor from previous response + */ + cursor?: string; + /** + * Filter by template type + */ + displayMode?: 'email' | 'web' | 'document'; + /** + * Filter by name (case-insensitive search) + */ + name?: string; + }; + url: '/v3/templates'; +}; + +export type ListTemplatesErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ListTemplatesError = ListTemplatesErrors[keyof ListTemplatesErrors]; + +export type ListTemplatesResponses = { + /** + * Default Response + */ + 200: { + data: Array<{ + /** + * Template ID + */ + id?: string; + /** + * Template name + */ + name?: string; + /** + * Template type/display mode + */ + displayMode?: 'email' | 'web' | 'document'; + createdAt?: string; + updatedAt?: string; + }>; + /** + * Cursor for the next page. Null if no more results. + */ + next_cursor?: string | null; + /** + * Whether there are more results after this page + */ + has_more: boolean; + }; +}; + +export type ListTemplatesResponse = ListTemplatesResponses[keyof ListTemplatesResponses]; + +export type GetTemplateData = { + body?: never; + path: { + /** + * The resource ID + */ + id: string; + }; + query?: { + /** + * The project ID (required for PAT auth, auto-resolved for API key auth) + */ + projectId?: string; + }; + url: '/v3/templates/{id}'; +}; + +export type GetTemplateErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetTemplateError = GetTemplateErrors[keyof GetTemplateErrors]; + +export type GetTemplateResponses = { + /** + * Default Response + */ + 200: { + data?: { + id?: string; + name?: string; + displayMode?: 'email' | 'web' | 'document'; + design?: { + [key: string]: unknown; + }; + html?: string | null; + createdAt?: string; + updatedAt?: string; + }; + }; +}; + +export type GetTemplateResponse = GetTemplateResponses[keyof GetTemplateResponses]; + +export type ConvertFullToSimpleData = { + body: { + design: { + body: { + [key: string]: unknown; + }; + counters?: { + [key: string]: unknown; + }; + schemaVersion?: number; + [key: string]: unknown; + }; + /** + * Display mode of the design (email, web, document, popup). Defaults to "email", matching /v3/templates/validate. Mode-specific repairs apply during conversion (email caps contentWidth at 900px, for example), so pass the design's actual mode — a web design converted under the email default can be altered. + */ + displayMode?: 'email' | 'web' | 'popup' | 'document'; + includeDefaultValues?: boolean; + /** + * When true, includes _conversion metadata in the response. This metadata can be passed to simple-to-full to restore original values without data loss. + */ + includeConversion?: boolean; + }; + path?: never; + query?: never; + url: '/v3/templates/convert/full-to-simple'; +}; + +export type ConvertFullToSimpleErrors = { + /** + * Default Response + */ + 400: { + /** + * INVALID_DESIGN, or VALIDATION_ERROR for request-schema failures. + */ + error?: string; + /** + * Human-readable summary of the first issues. + */ + message?: string; + /** + * Issue list for INVALID_DESIGN — same path/message/code shape as POST /v3/templates/validate, capped at 100 entries. Absent on VALIDATION_ERROR. + */ + errors?: Array<{ + path: string; + message: string; + code: string; + }>; + /** + * Total number of issues found; greater than errors.length when the list was capped. + */ + errorCount?: number; + }; +}; + +export type ConvertFullToSimpleError = ConvertFullToSimpleErrors[keyof ConvertFullToSimpleErrors]; + +export type ConvertFullToSimpleResponses = { + /** + * Default Response + */ + 200: { + success?: true; + data?: { + design?: { + [key: string]: unknown; + }; + }; + }; +}; + +export type ConvertFullToSimpleResponse = ConvertFullToSimpleResponses[keyof ConvertFullToSimpleResponses]; + +export type ConvertSimpleToFullData = { + body: { + design: { + body: { + [key: string]: unknown; + }; + counters?: { + [key: string]: unknown; + }; + schemaVersion?: number; + _conversion?: { + data?: string; + version?: number; + }; + [key: string]: unknown; + }; + /** + * Display mode of the design (email, web, document, popup). Defaults to "email", matching /v3/templates/validate. Mode-specific repairs apply during conversion (email caps contentWidth at 900px, for example), so pass the design's actual mode — a web design converted under the email default can be altered. + */ + displayMode?: 'email' | 'web' | 'popup' | 'document'; + includeDefaultValues?: boolean; + }; + path?: never; + query?: never; + url: '/v3/templates/convert/simple-to-full'; +}; + +export type ConvertSimpleToFullErrors = { + /** + * Default Response + */ + 400: { + /** + * INVALID_DESIGN, or VALIDATION_ERROR for request-schema failures. + */ + error?: string; + /** + * Human-readable summary of the first issues. + */ + message?: string; + /** + * Issue list for INVALID_DESIGN — same path/message/code shape as POST /v3/templates/validate, capped at 100 entries. Absent on VALIDATION_ERROR. + */ + errors?: Array<{ + path: string; + message: string; + code: string; + }>; + /** + * Total number of issues found; greater than errors.length when the list was capped. + */ + errorCount?: number; + }; +}; + +export type ConvertSimpleToFullError = ConvertSimpleToFullErrors[keyof ConvertSimpleToFullErrors]; + +export type ConvertSimpleToFullResponses = { + /** + * Default Response + */ + 200: { + success?: true; + data?: { + design?: { + [key: string]: unknown; + }; + }; + }; +}; + +export type ConvertSimpleToFullResponse = ConvertSimpleToFullResponses[keyof ConvertSimpleToFullResponses]; + +export type ExportHtmlData = { + body: { + /** + * Unlayer design JSON + */ + design: { + [key: string]: unknown; + }; + displayMode?: 'email' | 'web' | 'popup' | 'document'; + customJS?: unknown; + editorVersion?: string; + mergeTags?: { + [key: string]: unknown; + }; + mergeTagsSchema?: { + [key: string]: unknown; + }; + designTags?: { + [key: string]: unknown; + }; + designTagsConfig?: { + [key: string]: unknown; + }; + safeHtml?: boolean; + language?: string; + languages?: Array; + }; + path?: never; + query?: { + /** + * The project ID (required for PAT auth, auto-resolved for API key auth) + */ + projectId?: string; + }; + url: '/v3/templates/export/html'; +}; + +export type ExportHtmlErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 422: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 429: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 500: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 502: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 503: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ExportHtmlError = ExportHtmlErrors[keyof ExportHtmlErrors]; + +export type ExportHtmlResponses = { + /** + * Default Response + */ + 200: { + success?: boolean; + data?: { + html?: string; + chunks?: { + css?: string; + js?: string; + body?: string; + fonts?: Array; + tags?: Array; + }; + design?: { + [key: string]: unknown; + }; + amp?: { + [key: string]: unknown; + }; + }; + }; +}; + +export type ExportHtmlResponse = ExportHtmlResponses[keyof ExportHtmlResponses]; + +export type ExportImageData = { + body: { + /** + * Unlayer design JSON + */ + design: { + [key: string]: unknown; + }; + displayMode?: 'email' | 'web' | 'popup' | 'document'; + customJS?: unknown; + editorVersion?: string; + mergeTags?: { + [key: string]: unknown; + }; + mergeTagsSchema?: { + [key: string]: unknown; + }; + designTags?: { + [key: string]: unknown; + }; + designTagsConfig?: { + [key: string]: unknown; + }; + safeHtml?: boolean; + language?: string; + languages?: Array; + width?: number; + height?: number; + fullPage?: boolean; + deviceScaleFactor?: number; + }; + path?: never; + query?: { + /** + * The project ID (required for PAT auth, auto-resolved for API key auth) + */ + projectId?: string; + }; + url: '/v3/templates/export/image'; +}; + +export type ExportImageErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 422: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 429: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 500: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 502: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 503: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ExportImageError = ExportImageErrors[keyof ExportImageErrors]; + +export type ExportImageResponses = { + /** + * Default Response + */ + 200: { + success?: boolean; + data?: { + url?: string; + }; + }; +}; + +export type ExportImageResponse = ExportImageResponses[keyof ExportImageResponses]; + +export type ExportPdfData = { + body: { + /** + * Unlayer design JSON + */ + design: { + [key: string]: unknown; + }; + displayMode?: 'email' | 'web' | 'popup' | 'document'; + customJS?: unknown; + editorVersion?: string; + mergeTags?: { + [key: string]: unknown; + }; + mergeTagsSchema?: { + [key: string]: unknown; + }; + designTags?: { + [key: string]: unknown; + }; + designTagsConfig?: { + [key: string]: unknown; + }; + safeHtml?: boolean; + language?: string; + languages?: Array; + pageSize?: 'Letter' | 'Legal' | 'Tabloid' | 'Ledger' | 'A0' | 'A1' | 'A2' | 'A3' | 'A4' | 'A5' | 'A6'; + contentWidth?: number | 'full'; + }; + path?: never; + query?: { + /** + * The project ID (required for PAT auth, auto-resolved for API key auth) + */ + projectId?: string; + }; + url: '/v3/templates/export/pdf'; +}; + +export type ExportPdfErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 422: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 429: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 500: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 502: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 503: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ExportPdfError = ExportPdfErrors[keyof ExportPdfErrors]; + +export type ExportPdfResponses = { + /** + * Default Response + */ + 200: { + success?: boolean; + data?: { + url?: string; + }; + }; +}; + +export type ExportPdfResponse = ExportPdfResponses[keyof ExportPdfResponses]; + +export type ExportZipData = { + body: { + /** + * Unlayer design JSON + */ + design: { + [key: string]: unknown; + }; + displayMode?: 'email' | 'web' | 'popup' | 'document'; + customJS?: unknown; + editorVersion?: string; + mergeTags?: { + [key: string]: unknown; + }; + mergeTagsSchema?: { + [key: string]: unknown; + }; + designTags?: { + [key: string]: unknown; + }; + designTagsConfig?: { + [key: string]: unknown; + }; + safeHtml?: boolean; + language?: string; + languages?: Array; + }; + path?: never; + query?: { + /** + * The project ID (required for PAT auth, auto-resolved for API key auth) + */ + projectId?: string; + }; + url: '/v3/templates/export/zip'; +}; + +export type ExportZipErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 422: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 429: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 500: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 502: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 503: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ExportZipError = ExportZipErrors[keyof ExportZipErrors]; + +export type ExportZipResponses = { + /** + * Default Response + */ + 200: { + success?: boolean; + data?: { + url?: string; + }; + }; +}; + +export type ExportZipResponse = ExportZipResponses[keyof ExportZipResponses]; + +export type GetV3TemplatesGenerateData = { + body?: never; + path?: never; + query?: never; + url: '/v3/templates/generate'; +}; + +export type GetV3TemplatesGenerateResponses = { + /** + * Default Response + */ + 200: unknown; +}; + +export type GenerateDesignData = { + body: { + /** + * Preferred AI model in "provider/id" form, e.g. "anthropic/claude-opus-5". Optional — server resolves a default per output kind. + */ + model?: string; + /** + * Transient-outage fallback controls. Omit to use Unlayer defaults only when no model is pinned; true always uses Unlayer defaults; false disables the outage tail; an ordered array replaces the default provider/model strings. + */ + fallbackModels?: boolean | Array; + /** + * Conversation messages in chronological order, capped at 10 messages. The last `user` message is the prompt for this turn; the newest earlier `user`/`assistant` turns are forwarded within a 12,000-character aggregate history budget. A `user` message may carry a predefined prompt action via `metadata.action.id` (e.g. SPELLING, REPHRASE). + */ + messages: Array<{ + role: 'user' | 'assistant' | 'system'; + content: Array<{ + type: 'text' | 'image' | 'file'; + text?: string; + /** + * URL or data URL of the image + */ + image?: string; + file?: { + url: string; + mediaType?: string; + [key: string]: unknown; + }; + }>; + metadata?: { + action?: { + id: string; + [key: string]: unknown; + }; + [key: string]: unknown; + }; + }>; + output: { + kind: 'template' | 'page' | 'body' | 'header' | 'footer' | 'row' | 'column' | 'content' | 'text'; + displayMode: 'email' | 'web' | 'popup' | 'document'; + schemaVersion?: number; + }; + context?: { + fullDesign?: { + [key: string]: unknown; + } | null; + selection?: { + collection: 'pages' | 'bodies' | 'rows' | 'columns' | 'contents' | 'headers' | 'footers'; + id: string | number; + value?: string; + [key: string]: unknown; + } | null; + availableTools?: Array; + availableFonts?: Array<{ + label: string; + value: string; + }>; + customTools?: Array<{ + slug: string; + options: { + [key: string]: unknown; + }; + [key: string]: unknown; + }>; + brand?: { + companyName?: string; + productDescription?: string; + targetAudience?: string; + colors?: { + primary?: string; + secondary?: string; + accent?: string; + }; + fonts?: { + heading?: string; + body?: string; + }; + logos?: { + primary?: string; + secondary?: string; + }; + voice?: string; + guidelines?: string; + } | null; + [key: string]: unknown; + }; + /** + * BCP-47 fallback locale for AI status messages. + */ + locale?: string; + /** + * Reserved for future server-side conversation memory. + */ + conversationId?: string; + }; + path?: never; + query?: { + /** + * The project ID (required for PAT auth, auto-resolved for API key auth) + */ + projectId?: string; + }; + url: '/v3/templates/generate'; +}; + +export type GenerateDesignErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 429: { + error?: string; + message?: string; + }; +}; + +export type GenerateDesignError = GenerateDesignErrors[keyof GenerateDesignErrors]; + +export type GenerateDesignResponses = { + /** + * The generated (or modified) design plus model metadata and optional usage metadata. + */ + 200: { + /** + * Provider response id for the generation turn. + */ + id?: string; + /** + * The generated output for the requested block. + */ + output?: { + /** + * Echoes the requested `output.kind`. + */ + kind?: string; + /** + * The generated design JSON, scoped to the requested kind (the full design for template/page/body; the row/column/content/element for narrower kinds). + */ + data?: { + [key: string]: unknown; + }; + }; + /** + * The provider + model that actually produced the output (may differ from the requested model after failover). + */ + model?: { + /** + * e.g. "anthropic", "openai". + */ + provider?: string; + /** + * Resolved model id, e.g. "claude-opus-5". + */ + id?: string; + }; + /** + * Aggregate token usage and billed AI credits for the turn. Estimated provider cost is included only by builder copilot endpoints in local/dev/QA. + */ + usage?: { + inputTokens?: number; + outputTokens?: number; + totalTokens?: number; + cachedInputTokens?: number; + reasoningTokens?: number; + /** + * Marked-up integer AI credits used by the complete turn, including failover attempts. + */ + aiCreditsUsed?: number; + estimatedCostMicroUsd?: number; + }; + }; +}; + +export type GenerateDesignResponse = GenerateDesignResponses[keyof GenerateDesignResponses]; + +export type ImportTemplateData = { + body: { + /** + * Preferred AI model. Accepts a provider/model string (e.g. "anthropic/claude-opus-5", "openai/gpt-5.6-luna"), a bare provider ("anthropic", "openai") which uses that provider's default model, or a bare model id ("claude-opus-5", "gpt-5.6-luna") with the provider inferred from the name. Optional — defaults to anthropic/claude-opus-5. + */ + model?: string; + /** + * Transient-outage fallback controls. Omit to use Unlayer defaults only when no model is pinned; true always uses Unlayer defaults; false disables the outage tail; an ordered array replaces the default provider/model strings. + */ + fallbackModels?: boolean | Array; + /** + * Display mode for the imported design + */ + displayMode: 'email' | 'web' | 'popup' | 'document'; + /** + * Array of input parts. Must contain exactly one "html" or "image" part; may also contain one or more "text" parts with optional instructions. + */ + input: Array<{ + /** + * The type of input part. "html" or "image" carries the source content; "text" carries optional instructions to apply during import. + */ + type: 'html' | 'image' | 'text'; + /** + * HTML string to import (for type: "html") + */ + html?: string; + /** + * Image URL to import (for type: "image") + */ + url?: string; + /** + * Base64 image data URL, e.g. "data:image/png;base64,…" (for type: "image") + */ + data?: string; + /** + * Optional natural-language instructions to apply during import (for type: "text") + */ + text?: string; + }>; + }; + path?: never; + query?: { + /** + * The project ID (required for PAT auth, auto-resolved for API key auth) + */ + projectId?: string; + }; + url: '/v3/templates/import'; +}; + +export type ImportTemplateErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ImportTemplateError = ImportTemplateErrors[keyof ImportTemplateErrors]; + +export type ImportTemplateResponses = { + /** + * Successfully imported template + */ + 200: { + id?: string; + output?: { + type?: string; + blockType?: string; + /** + * Imported design data + */ + data?: { + [key: string]: unknown; + }; + }; + model?: string; + provider?: string; + usage?: { + inputTokens?: number; + outputTokens?: number; + totalTokens?: number; + reasoningTokens?: number; + cachedInputTokens?: number; + }; + }; +}; + +export type ImportTemplateResponse = ImportTemplateResponses[keyof ImportTemplateResponses]; + +export type GetDesignSchemaData = { + body?: never; + path?: never; + query?: { + /** + * When true, returns the Simple schema instead of the Full schema. + */ + simple?: boolean; + /** + * Display mode whose rules the schema describes (email, web, document, popup). Defaults to "email". + */ + displayMode?: 'email' | 'web' | 'popup' | 'document'; + }; + url: '/v3/templates/schema'; +}; + +export type GetDesignSchemaResponses = { + /** + * Default Response + */ + 200: unknown; +}; + +export type ValidateDesignData = { + body: { + /** + * The design JSON to validate. + */ + design: { + [key: string]: unknown; + }; + /** + * Which form of the schema to validate against. Defaults to "full". + */ + schema?: 'full' | 'simple'; + /** + * Display mode for the design (email, web, document, popup). Some validation rules differ per mode. Defaults to "email" — without a default, options from every mode would apply at once, the strictest possible check, and real editor-saved designs could be reported invalid. + */ + displayMode?: 'email' | 'web' | 'popup' | 'document'; + /** + * When true (default), a full-form design with an older schemaVersion is upgraded to the current schema before validating — matching how the editor and the convert endpoints treat stored designs. Designs without a schemaVersion predate versioning and are fully migrated the same way. Set to false to check strict conformance with the current schema version. Designs with a newer schemaVersion than this API knows are validated as-if-current. + */ + migrate?: boolean; + /** + * Custom tool declarations, in the same shape passed to unlayer.registerTool. When provided, blocks matching a declared tool have their values checked against the tool's declared options (wrong types are reported at their exact path). Blocks of undeclared tools keep envelope-only validation. + */ + customTools?: Array<{ + slug: string; + type?: string; + label?: string; + options: { + [key: string]: { + options?: { + [key: string]: unknown; + }; + }; + }; + values?: { + [key: string]: unknown; + }; + supportedDisplayModes?: Array<'email' | 'web' | 'popup' | 'document'>; + [key: string]: unknown; + }>; + }; + path?: never; + query?: never; + url: '/v3/templates/validate'; +}; + +export type ValidateDesignErrors = { + /** + * The request itself is malformed — e.g. the design field is missing or displayMode is unknown. The design was not checked. + */ + 400: { + /** + * VALIDATION_ERROR + */ + error?: string; + message?: string; + }; +}; + +export type ValidateDesignError = ValidateDesignErrors[keyof ValidateDesignErrors]; + +export type ValidateDesignResponses = { + /** + * Default Response + */ + 200: { + success: true; + data: { + valid: boolean; + /** + * Present when the design was upgraded from an older schemaVersion before validation; carries the original version number. + */ + migratedFrom?: number; + /** + * Populated when valid is false, capped at 100 entries. Each issue carries the dotted path to the offending field, a human-readable message, and the underlying Zod issue code. + */ + errors?: Array<{ + path: string; + message: string; + code: string; + }>; + /** + * Total number of issues found; greater than errors.length when the list was capped. + */ + errorCount?: number; + }; + }; +}; + +export type ValidateDesignResponse = ValidateDesignResponses[keyof ValidateDesignResponses]; + +export type ListWebhooksData = { + body?: never; + path?: never; + query?: never; + url: '/v3/webhooks'; +}; + +export type ListWebhooksErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ListWebhooksError = ListWebhooksErrors[keyof ListWebhooksErrors]; + +export type ListWebhooksResponses = { + /** + * Default Response + */ + 200: { + data: Array<{ + /** + * Webhook ID + */ + id?: number; + /** + * The HTTPS URL receiving webhook events + */ + url?: string; + /** + * Event types this webhook is subscribed to + */ + events?: Array<'email.sent' | 'email.delivered' | 'email.bounced' | 'email.complained'>; + /** + * Whether the webhook is actively receiving events + */ + active?: boolean; + /** + * When the webhook was created + */ + createdAt?: string; + }>; + }; +}; + +export type ListWebhooksResponse = ListWebhooksResponses[keyof ListWebhooksResponses]; + +export type CreateWebhookData = { + body: { + /** + * The HTTPS URL to receive webhook events + */ + url: string; + /** + * Event types to subscribe to. If omitted or empty, all events are sent. + */ + events?: Array<'email.sent' | 'email.delivered' | 'email.bounced' | 'email.complained'>; + /** + * Whether the webhook is active + */ + active?: boolean; + }; + path?: never; + query?: never; + url: '/v3/webhooks'; +}; + +export type CreateWebhookErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type CreateWebhookError = CreateWebhookErrors[keyof CreateWebhookErrors]; + +export type CreateWebhookResponses = { + /** + * Default Response + */ + 200: { + data: { + /** + * Webhook ID + */ + id?: number; + /** + * The HTTPS URL receiving webhook events + */ + url?: string; + /** + * Event types this webhook is subscribed to + */ + events?: Array<'email.sent' | 'email.delivered' | 'email.bounced' | 'email.complained'>; + /** + * Whether the webhook is actively receiving events + */ + active?: boolean; + /** + * Signing secret — only returned on creation. Store it securely; you will not be able to retrieve it again. + */ + secret?: string; + /** + * When the webhook was created + */ + createdAt?: string; + }; + }; +}; + +export type CreateWebhookResponse = CreateWebhookResponses[keyof CreateWebhookResponses]; + +export type DeleteWebhookData = { + body?: never; + path: { + /** + * Webhook ID + */ + id: string; + }; + query?: never; + url: '/v3/webhooks/{id}'; +}; + +export type DeleteWebhookErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type DeleteWebhookError = DeleteWebhookErrors[keyof DeleteWebhookErrors]; + +export type DeleteWebhookResponses = { + /** + * Default Response + */ + 200: { + data?: { + success?: boolean; + }; + }; +}; + +export type DeleteWebhookResponse = DeleteWebhookResponses[keyof DeleteWebhookResponses]; + +export type GetWebhookData = { + body?: never; + path: { + /** + * Webhook ID + */ + id: string; + }; + query?: never; + url: '/v3/webhooks/{id}'; +}; + +export type GetWebhookErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetWebhookError = GetWebhookErrors[keyof GetWebhookErrors]; + +export type GetWebhookResponses = { + /** + * Default Response + */ + 200: { + data: { + /** + * Webhook ID + */ + id?: number; + /** + * The HTTPS URL receiving webhook events + */ + url?: string; + /** + * Event types this webhook is subscribed to + */ + events?: Array<'email.sent' | 'email.delivered' | 'email.bounced' | 'email.complained'>; + /** + * Whether the webhook is actively receiving events + */ + active?: boolean; + /** + * When the webhook was created + */ + createdAt?: string; + /** + * When the webhook was last updated + */ + updatedAt?: string; + }; + }; +}; + +export type GetWebhookResponse = GetWebhookResponses[keyof GetWebhookResponses]; + +export type UpdateWebhookData = { + body?: { + /** + * The HTTPS URL to receive webhook events + */ + url?: string; + /** + * Event types to subscribe to. If omitted or empty, all events are sent. + */ + events?: Array<'email.sent' | 'email.delivered' | 'email.bounced' | 'email.complained'>; + /** + * Whether the webhook is actively receiving events + */ + active?: boolean; + }; + path: { + /** + * Webhook ID + */ + id: string; + }; + query?: never; + url: '/v3/webhooks/{id}'; +}; + +export type UpdateWebhookErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type UpdateWebhookError = UpdateWebhookErrors[keyof UpdateWebhookErrors]; + +export type UpdateWebhookResponses = { + /** + * Default Response + */ + 200: { + data: { + /** + * Webhook ID + */ + id?: number; + /** + * The HTTPS URL receiving webhook events + */ + url?: string; + /** + * Event types this webhook is subscribed to + */ + events?: Array<'email.sent' | 'email.delivered' | 'email.bounced' | 'email.complained'>; + /** + * Whether the webhook is actively receiving events + */ + active?: boolean; + /** + * When the webhook was last updated + */ + updatedAt?: string; + }; + }; +}; + +export type UpdateWebhookResponse = UpdateWebhookResponses[keyof UpdateWebhookResponses]; + +export type RotateWebhookSecretData = { + body?: never; + path: { + /** + * Webhook ID + */ + id: string; + }; + query?: never; + url: '/v3/webhooks/{id}/rotate-secret'; +}; + +export type RotateWebhookSecretErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type RotateWebhookSecretError = RotateWebhookSecretErrors[keyof RotateWebhookSecretErrors]; + +export type RotateWebhookSecretResponses = { + /** + * Default Response + */ + 200: { + data: { + id?: number; + /** + * New signing secret — only returned once. Store it securely. + */ + secret?: string; + updatedAt?: string; + }; + }; +}; + +export type RotateWebhookSecretResponse = RotateWebhookSecretResponses[keyof RotateWebhookSecretResponses]; + +export type ListWorkspacesData = { + body?: never; + path?: never; + query?: never; + url: '/v3/workspaces'; +}; + +export type ListWorkspacesErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type ListWorkspacesError = ListWorkspacesErrors[keyof ListWorkspacesErrors]; + +export type ListWorkspacesResponses = { + /** + * Default Response + */ + 200: { + data?: Array<{ + id?: number; + name?: string; + }>; + }; +}; + +export type ListWorkspacesResponse = ListWorkspacesResponses[keyof ListWorkspacesResponses]; + +export type GetWorkspaceData = { + body?: never; + path: { + /** + * The workspace ID + */ + workspaceId: string; + }; + query?: never; + url: '/v3/workspaces/{workspaceId}'; +}; + +export type GetWorkspaceErrors = { + /** + * Default Response + */ + 400: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 401: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 403: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; + /** + * Default Response + */ + 404: { + /** + * Error code + */ + error: string; + /** + * Human-readable error message + */ + message: string; + }; +}; + +export type GetWorkspaceError = GetWorkspaceErrors[keyof GetWorkspaceErrors]; + +export type GetWorkspaceResponses = { + /** + * Default Response + */ + 200: { + data?: { + id?: number; + name?: string; + projects?: Array<{ + id?: number; + name?: string; + status?: string; + }>; + }; + }; +}; + +export type GetWorkspaceResponse = GetWorkspaceResponses[keyof GetWorkspaceResponses]; diff --git a/src/uploads.ts b/src/uploads.ts deleted file mode 100644 index b2ef647..0000000 --- a/src/uploads.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/uploads instead */ -export * from './core/uploads'; diff --git a/src/version.ts b/src/version.ts deleted file mode 100644 index 1baa228..0000000 --- a/src/version.ts +++ /dev/null @@ -1 +0,0 @@ -export const VERSION = '0.1.0'; // x-release-please-version diff --git a/tests/api-resources/convert/full-to-simple.test.ts b/tests/api-resources/convert/full-to-simple.test.ts deleted file mode 100644 index 831b256..0000000 --- a/tests/api-resources/convert/full-to-simple.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Unlayer from '@unlayer/sdk'; - -const client = new Unlayer({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource fullToSimple', () => { - test('create: only required params', async () => { - const responsePromise = client.convert.fullToSimple.create({ design: { body: { foo: 'bar' } } }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - test('create: required and optional params', async () => { - const response = await client.convert.fullToSimple.create({ - design: { - body: { foo: 'bar' }, - counters: { foo: 'bar' }, - schemaVersion: 0, - }, - displayMode: 'email', - includeConversion: true, - includeDefaultValues: true, - }); - }); -}); diff --git a/tests/api-resources/convert/simple-to-full.test.ts b/tests/api-resources/convert/simple-to-full.test.ts deleted file mode 100644 index a5f33bd..0000000 --- a/tests/api-resources/convert/simple-to-full.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Unlayer from '@unlayer/sdk'; - -const client = new Unlayer({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource simpleToFull', () => { - test('create: only required params', async () => { - const responsePromise = client.convert.simpleToFull.create({ design: { body: { foo: 'bar' } } }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - test('create: required and optional params', async () => { - const response = await client.convert.simpleToFull.create({ - design: { - body: { foo: 'bar' }, - _conversion: { data: 'data', version: 0 }, - counters: { foo: 'bar' }, - schemaVersion: 0, - }, - displayMode: 'email', - includeDefaultValues: true, - }); - }); -}); diff --git a/tests/api-resources/projects.test.ts b/tests/api-resources/projects.test.ts deleted file mode 100644 index 856e49a..0000000 --- a/tests/api-resources/projects.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Unlayer from '@unlayer/sdk'; - -const client = new Unlayer({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource projects', () => { - test('retrieve', async () => { - const responsePromise = client.projects.retrieve('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/tests/api-resources/templates.test.ts b/tests/api-resources/templates.test.ts deleted file mode 100644 index d40fd99..0000000 --- a/tests/api-resources/templates.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Unlayer from '@unlayer/sdk'; - -const client = new Unlayer({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource templates', () => { - test('retrieve', async () => { - const responsePromise = client.templates.retrieve('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - 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( - client.templates.retrieve('id', { projectId: 'projectId' }, { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(Unlayer.NotFoundError); - }); - - test('list', async () => { - const responsePromise = client.templates.list(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - 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( - client.templates.list( - { - cursor: 'cursor', - displayMode: 'email', - limit: 1, - name: 'name', - projectId: 'projectId', - }, - { path: '/_stainless_unknown_path' }, - ), - ).rejects.toThrow(Unlayer.NotFoundError); - }); -}); diff --git a/tests/api-resources/workspaces.test.ts b/tests/api-resources/workspaces.test.ts deleted file mode 100644 index ad10668..0000000 --- a/tests/api-resources/workspaces.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Unlayer from '@unlayer/sdk'; - -const client = new Unlayer({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource workspaces', () => { - test('retrieve', async () => { - const responsePromise = client.workspaces.retrieve('workspaceId'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - test('list', async () => { - const responsePromise = client.workspaces.list(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/tests/base64.test.ts b/tests/base64.test.ts deleted file mode 100644 index eff76d7..0000000 --- a/tests/base64.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { fromBase64, toBase64 } from '@unlayer/sdk/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 deleted file mode 100644 index 0509e8e..0000000 --- a/tests/buildHeaders.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { inspect } from 'node:util'; -import { buildHeaders, type HeadersLike, type NullableHeaders } from '@unlayer/sdk/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/consumer.cts b/tests/consumer.cts new file mode 100644 index 0000000..01c08ca --- /dev/null +++ b/tests/consumer.cts @@ -0,0 +1,35 @@ +import { Unlayer } from '@unlayer/sdk'; +import { createClient } from '@unlayer/sdk/client'; + +const client = createClient({ + auth: 'test-token', +}); + +const sdk = new Unlayer({ + client, +}); + +// @ts-expect-error the high-level SDK requires an explicitly configured client +new Unlayer(); + +// @ts-expect-error generated registry keys are not part of the public SDK +new Unlayer({ client, key: 'tenant' }); + +// @ts-expect-error generated clients are not globally discoverable +void Unlayer.__registry; + +void sdk.templates.getTemplate({ + path: { id: 'template-id' }, +}); + +void sdk.templates.getTemplate({ + path: { id: 'template-id' }, + // @ts-expect-error responseStyle is available only on the low-level client + responseStyle: 'fields', +}); + +void sdk.templates.getTemplate({ + path: { id: 'template-id' }, + // @ts-expect-error high-level SDK operations always throw + throwOnError: false, +}); diff --git a/tests/consumer.mts b/tests/consumer.mts new file mode 100644 index 0000000..c93ba6a --- /dev/null +++ b/tests/consumer.mts @@ -0,0 +1,34 @@ +import { Unlayer } from '@unlayer/sdk'; +import { createClient } from '@unlayer/sdk/client'; + +const client = createClient({ + auth: 'test-token', +}); + +const sdk = new Unlayer({ + client, +}); + +// @ts-expect-error the high-level SDK requires an explicitly configured client +new Unlayer(); + +// @ts-expect-error generated registry keys are not part of the public SDK +new Unlayer({ client, key: 'tenant' }); + +// @ts-expect-error generated clients are not globally discoverable +void Unlayer.__registry; + +void sdk.templates.listTemplates({ + query: { limit: 10, projectId: 'project-id' }, +}); + +// SDK methods have one stable, data-only response shape. +void sdk.templates.listTemplates({ + // @ts-expect-error responseStyle is available only on the low-level client + responseStyle: 'fields', +}); + +void sdk.templates.listTemplates({ + // @ts-expect-error high-level SDK operations always throw + throwOnError: false, +}); diff --git a/tests/form.test.ts b/tests/form.test.ts deleted file mode 100644 index 02d5fad..0000000 --- a/tests/form.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { multipartFormRequestOptions, createForm } from '@unlayer/sdk/internal/uploads'; -import { toFile } from '@unlayer/sdk/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' }), - }, - }, - fetch, - ); - }); - - test('null', async () => { - await expect(() => - multipartFormRequestOptions( - { - body: { - null: null, - }, - }, - fetch, - ), - ).rejects.toThrow(TypeError); - }); - - test('undefined is stripped', async () => { - 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, - }, - }, - fetch, - ); - expect(Array.from(form.entries())).toEqual([]); - - 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], - }, - fetch, - ); - expect(Array.from(form.entries())).toEqual([]); - - 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 deleted file mode 100644 index da4aec7..0000000 --- a/tests/index.test.ts +++ /dev/null @@ -1,766 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIPromise } from '@unlayer/sdk/core/api-promise'; - -import util from 'node:util'; -import Unlayer from '@unlayer/sdk'; -import { APIUserAbortError } from '@unlayer/sdk'; -const defaultFetch = fetch; - -describe('instantiate client', () => { - const env = process.env; - - beforeEach(() => { - jest.resetModules(); - process.env = { ...env }; - }); - - afterEach(() => { - process.env = env; - }); - - describe('defaultHeaders', () => { - const client = new Unlayer({ - baseURL: 'http://localhost:5000/', - defaultHeaders: { 'X-My-Default-Header': '2' }, - apiKey: 'My API Key', - }); - - test('they are used in the request', async () => { - const { req } = await client.buildRequest({ path: '/foo', method: 'post' }); - expect(req.headers.get('x-my-default-header')).toEqual('2'); - }); - - test('can ignore `undefined` and leave the default', async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - headers: { 'X-My-Default-Header': undefined }, - }); - expect(req.headers.get('x-my-default-header')).toEqual('2'); - }); - - test('can be removed with `null`', async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - headers: { 'X-My-Default-Header': null }, - }); - expect(req.headers.has('x-my-default-header')).toBe(false); - }); - }); - describe('logging', () => { - const env = process.env; - - beforeEach(() => { - process.env = { ...env }; - process.env['UNLAYER_LOG'] = undefined; - }); - - afterEach(() => { - process.env = env; - }); - - const forceAPIResponseForClient = async (client: Unlayer) => { - 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 Unlayer({ - logger: logger, - logLevel: 'debug', - apiKey: 'My API Key', - }); - - await forceAPIResponseForClient(client); - expect(debugMock).toHaveBeenCalled(); - }); - - test('default logLevel is warn', async () => { - const client = new Unlayer({ apiKey: 'My API Key' }); - 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 Unlayer({ - logger: logger, - logLevel: 'info', - apiKey: 'My API Key', - }); - - 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['UNLAYER_LOG'] = 'debug'; - const client = new Unlayer({ logger: logger, apiKey: 'My API Key' }); - 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['UNLAYER_LOG'] = 'not a log level'; - const client = new Unlayer({ logger: logger, apiKey: 'My API Key' }); - expect(client.logLevel).toBe('warn'); - expect(warnMock).toHaveBeenCalledWith( - 'process.env[\'UNLAYER_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['UNLAYER_LOG'] = 'debug'; - const client = new Unlayer({ - logger: logger, - logLevel: 'off', - apiKey: 'My API Key', - }); - - 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['UNLAYER_LOG'] = 'not a log level'; - const client = new Unlayer({ - logger: logger, - logLevel: 'debug', - apiKey: 'My API Key', - }); - expect(client.logLevel).toBe('debug'); - expect(warnMock).not.toHaveBeenCalled(); - }); - }); - - describe('defaultQuery', () => { - test('with null query params given', () => { - const client = new Unlayer({ - baseURL: 'http://localhost:5000/', - defaultQuery: { apiVersion: 'foo' }, - apiKey: 'My API Key', - }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo'); - }); - - test('multiple default query params', () => { - const client = new Unlayer({ - baseURL: 'http://localhost:5000/', - defaultQuery: { apiVersion: 'foo', hello: 'world' }, - apiKey: 'My API Key', - }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo&hello=world'); - }); - - test('overriding with `undefined`', () => { - const client = new Unlayer({ - baseURL: 'http://localhost:5000/', - defaultQuery: { hello: 'world' }, - apiKey: 'My API Key', - }); - expect(client.buildURL('/foo', { hello: undefined })).toEqual('http://localhost:5000/foo'); - }); - }); - - test('custom fetch', async () => { - const client = new Unlayer({ - baseURL: 'http://localhost:5000/', - apiKey: 'My API Key', - fetch: (url) => { - return Promise.resolve( - new Response(JSON.stringify({ url, custom: true }), { - headers: { 'Content-Type': 'application/json' }, - }), - ); - }, - }); - - const response = await client.get('/foo'); - expect(response).toEqual({ url: 'http://localhost:5000/foo', custom: true }); - }); - - test('explicit global fetch', async () => { - // make sure the global fetch type is assignable to our Fetch type - const client = new Unlayer({ - baseURL: 'http://localhost:5000/', - apiKey: 'My API Key', - fetch: defaultFetch, - }); - }); - - test('custom signal', async () => { - const client = new Unlayer({ - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', - apiKey: 'My API Key', - fetch: (...args) => { - return new Promise((resolve, reject) => - setTimeout( - () => - defaultFetch(...args) - .then(resolve) - .catch(reject), - 300, - ), - ); - }, - }); - - const controller = new AbortController(); - setTimeout(() => controller.abort(), 200); - - const spy = jest.spyOn(client, 'request'); - - await expect(client.get('/foo', { signal: controller.signal })).rejects.toThrowError(APIUserAbortError); - expect(spy).toHaveBeenCalledTimes(1); - }); - - test('normalized method', async () => { - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - capturedRequest = init; - return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new Unlayer({ - baseURL: 'http://localhost:5000/', - apiKey: 'My API Key', - fetch: testFetch, - }); - - await client.patch('/foo'); - expect(capturedRequest?.method).toEqual('PATCH'); - }); - - describe('baseUrl', () => { - test('trailing slash', () => { - const client = new Unlayer({ baseURL: 'http://localhost:5000/custom/path/', apiKey: 'My API Key' }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); - }); - - test('no trailing slash', () => { - const client = new Unlayer({ baseURL: 'http://localhost:5000/custom/path', apiKey: 'My API Key' }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); - }); - - afterEach(() => { - process.env['UNLAYER_BASE_URL'] = undefined; - }); - - test('explicit option', () => { - const client = new Unlayer({ baseURL: 'https://example.com', apiKey: 'My API Key' }); - expect(client.baseURL).toEqual('https://example.com'); - }); - - test('env variable', () => { - process.env['UNLAYER_BASE_URL'] = 'https://example.com/from_env'; - const client = new Unlayer({ apiKey: 'My API Key' }); - expect(client.baseURL).toEqual('https://example.com/from_env'); - }); - - test('empty env variable', () => { - process.env['UNLAYER_BASE_URL'] = ''; // empty - const client = new Unlayer({ apiKey: 'My API Key' }); - expect(client.baseURL).toEqual('https://api.unlayer.com'); - }); - - test('blank env variable', () => { - process.env['UNLAYER_BASE_URL'] = ' '; // blank - const client = new Unlayer({ apiKey: 'My API Key' }); - expect(client.baseURL).toEqual('https://api.unlayer.com'); - }); - - test('in request options', () => { - const client = new Unlayer({ apiKey: 'My API Key' }); - expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( - 'http://localhost:5000/option/foo', - ); - }); - - test('in request options overridden by client options', () => { - const client = new Unlayer({ apiKey: 'My API Key', baseURL: 'http://localhost:5000/client' }); - expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( - 'http://localhost:5000/client/foo', - ); - }); - - test('in request options overridden by env variable', () => { - process.env['UNLAYER_BASE_URL'] = 'http://localhost:5000/env'; - const client = new Unlayer({ apiKey: 'My API Key' }); - expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( - 'http://localhost:5000/env/foo', - ); - }); - }); - - test('maxRetries option is correctly set', () => { - const client = new Unlayer({ maxRetries: 4, apiKey: 'My API Key' }); - expect(client.maxRetries).toEqual(4); - - // default - const client2 = new Unlayer({ apiKey: 'My API Key' }); - expect(client2.maxRetries).toEqual(2); - }); - - describe('withOptions', () => { - test('creates a new client with overridden options', async () => { - const client = new Unlayer({ - baseURL: 'http://localhost:5000/', - maxRetries: 3, - apiKey: 'My API Key', - }); - - const newClient = client.withOptions({ - maxRetries: 5, - baseURL: 'http://localhost:5001/', - }); - - // 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 Unlayer({ - baseURL: 'http://localhost:5000/', - defaultHeaders: { 'X-Test-Header': 'test-value' }, - defaultQuery: { 'test-param': 'test-value' }, - apiKey: 'My API Key', - }); - - 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('respects runtime property changes when creating new client', () => { - const client = new Unlayer({ - baseURL: 'http://localhost:5000/', - timeout: 1000, - apiKey: 'My API Key', - }); - - // 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'); - }); - }); - - test('with environment variable arguments', () => { - // set options via env var - process.env['UNLAYER_API_KEY'] = 'My API Key'; - const client = new Unlayer(); - expect(client.apiKey).toBe('My API Key'); - }); - - test('with overridden environment variable arguments', () => { - // set options via env var - process.env['UNLAYER_API_KEY'] = 'another My API Key'; - const client = new Unlayer({ apiKey: 'My API Key' }); - expect(client.apiKey).toBe('My API Key'); - }); -}); - -describe('request building', () => { - const client = new Unlayer({ apiKey: 'My API Key' }); - - describe('custom headers', () => { - test('handles undefined', async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - body: { value: 'hello' }, - headers: { 'X-Foo': 'baz', 'x-foo': 'bar', 'x-Foo': undefined, 'x-baz': 'bam', 'X-Baz': null }, - }); - 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 Unlayer({ apiKey: 'My API Key' }); - - 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: string | URL | Request, - { signal }: RequestInit = {}, - ): Promise => { - if (count++ === 0) { - return new Promise( - (resolve, reject) => signal?.addEventListener('abort', () => reject(new Error('timed out'))), - ); - } - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new Unlayer({ - apiKey: 'My API Key', - timeout: 10, - fetch: testFetch, - }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - expect(count).toEqual(2); - expect( - await client - .request({ path: '/foo', method: 'get' }) - .asResponse() - .then((r) => r.text()), - ).toEqual(JSON.stringify({ a: 1 })); - expect(count).toEqual(3); - }); - - test('retry count header', async () => { - let count = 0; - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new Unlayer({ - apiKey: 'My API Key', - fetch: testFetch, - maxRetries: 4, - }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - - 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: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - const client = new Unlayer({ - apiKey: 'My API Key', - fetch: testFetch, - maxRetries: 4, - }); - - expect( - await client.request({ - path: '/foo', - method: 'get', - headers: { 'X-Stainless-Retry-Count': null }, - }), - ).toEqual({ a: 1 }); - - 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: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - const client = new Unlayer({ - apiKey: 'My API Key', - fetch: testFetch, - maxRetries: 4, - defaultHeaders: { 'X-Stainless-Retry-Count': null }, - }); - - expect( - await client.request({ - path: '/foo', - method: 'get', - }), - ).toEqual({ a: 1 }); - - expect(capturedRequest!.headers as Headers).not.toHaveProperty('x-stainless-retry-count'); - }); - - test('overwrite retry count header', async () => { - let count = 0; - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - const client = new Unlayer({ - apiKey: 'My API Key', - fetch: testFetch, - maxRetries: 4, - }); - - expect( - await client.request({ - path: '/foo', - method: 'get', - headers: { 'X-Stainless-Retry-Count': '42' }, - }), - ).toEqual({ a: 1 }); - - 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: string | URL | Request, - { signal }: RequestInit = {}, - ): Promise => { - if (count++ === 0) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new Unlayer({ apiKey: 'My API Key', fetch: testFetch }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - expect(count).toEqual(2); - expect( - await client - .request({ path: '/foo', method: 'get' }) - .asResponse() - .then((r) => r.text()), - ).toEqual(JSON.stringify({ a: 1 })); - expect(count).toEqual(3); - }); - - test('retry on 429 with retry-after-ms', async () => { - let count = 0; - const testFetch = async ( - url: string | URL | Request, - { signal }: RequestInit = {}, - ): Promise => { - if (count++ === 0) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After-Ms': '10', - }, - }); - } - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new Unlayer({ apiKey: 'My API Key', fetch: testFetch }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - expect(count).toEqual(2); - expect( - await client - .request({ path: '/foo', method: 'get' }) - .asResponse() - .then((r) => r.text()), - ).toEqual(JSON.stringify({ a: 1 })); - expect(count).toEqual(3); - }); -}); diff --git a/tests/packed-sdk-smoke.mjs b/tests/packed-sdk-smoke.mjs new file mode 100644 index 0000000..c10bb99 --- /dev/null +++ b/tests/packed-sdk-smoke.mjs @@ -0,0 +1,235 @@ +import assert from 'node:assert/strict'; +import http from 'node:http'; + +import * as sdkPackage from '@unlayer/sdk'; +import * as clientPackage from '@unlayer/sdk/client'; + +const { Unlayer } = sdkPackage; +const { createClient } = clientPackage; + +assert.equal(Object.hasOwn(sdkPackage, 'client'), false); +assert.equal(Object.hasOwn(clientPackage, 'client'), false); +assert.equal(Object.hasOwn(Unlayer, '__registry'), false); +assert.throws(() => new Unlayer(), /client created with createClient\(\) is required/); + +const requests = []; + +const server = http.createServer(async (request, response) => { + const chunks = []; + for await (const chunk of request) chunks.push(chunk); + + requests.push({ + body: Buffer.concat(chunks).toString('utf8'), + headers: request.headers, + method: request.method, + url: request.url, + }); + + response.setHeader('Content-Type', 'application/json'); + + if (request.url?.includes('name=unauthorized')) { + response.statusCode = 401; + response.end(JSON.stringify({ error: 'unauthorized', message: 'Bad token' })); + return; + } + + if (request.method === 'POST' && request.url === '/v3/domains') { + response.end( + JSON.stringify({ + data: { domain: 'example.com', id: 42, status: 'pending' }, + }), + ); + return; + } + + if (request.method === 'GET' && request.url?.startsWith('/v3/templates/folder')) { + response.end( + JSON.stringify({ + data: { id: 'folder/Welcome & Spring', name: 'Path template' }, + }), + ); + return; + } + + response.end( + JSON.stringify({ + data: [{ displayMode: 'email', id: 'template-1', name: 'Welcome' }], + has_more: false, + next_cursor: null, + }), + ); +}); + +await new Promise((resolve, reject) => { + server.once('error', reject); + server.listen(0, '127.0.0.1', resolve); +}); + +try { + const address = server.address(); + assert.notEqual(address, null); + assert.equal(typeof address, 'object'); + + const sdk = new Unlayer({ + client: createClient({ + auth: 'test-token', + baseUrl: `http://127.0.0.1:${address.port}`, + }), + }); + + const templates = await sdk.templates.listTemplates({ + query: { + displayMode: 'email', + limit: 10, + name: 'Summer & Sale', + projectId: 'project-1', + }, + // JavaScript callers can still pass this low-level option. SDK methods + // must keep their documented data-only shape at runtime. + responseStyle: 'fields', + }); + + assert.deepEqual(templates, { + data: [{ displayMode: 'email', id: 'template-1', name: 'Welcome' }], + has_more: false, + next_cursor: null, + }); + + const template = await sdk.templates.getTemplate({ + path: { id: 'folder/Welcome & Spring' }, + }); + + assert.deepEqual(template, { + data: { id: 'folder/Welcome & Spring', name: 'Path template' }, + }); + + const domain = await sdk.domains.createDomain({ + body: { domain: 'example.com' }, + }); + + assert.deepEqual(domain, { + data: { domain: 'example.com', id: 42, status: 'pending' }, + }); + + await assert.rejects(sdk.templates.listTemplates({ query: { name: 'unauthorized' } }), (error) => { + assert.deepEqual(error, { + error: 'unauthorized', + message: 'Bad token', + }); + return true; + }); + + await assert.rejects( + sdk.templates.listTemplates({ + query: { name: 'unauthorized-undefined' }, + throwOnError: undefined, + }), + (error) => { + assert.deepEqual(error, { + error: 'unauthorized', + message: 'Bad token', + }); + return true; + }, + ); + + await assert.rejects( + sdk.templates.listTemplates({ + query: { name: 'unauthorized-no-throw' }, + // JavaScript callers can pass options omitted from the TypeScript SDK + // surface. High-level operations must still preserve their contract. + throwOnError: false, + }), + (error) => { + assert.deepEqual(error, { + error: 'unauthorized', + message: 'Bad token', + }); + return true; + }, + ); + + let defaultFactoryRequest; + const defaultFactorySdk = new Unlayer({ + client: createClient({ + auth: 'factory-token', + fetch: async (request) => { + defaultFactoryRequest = request; + return new Response( + JSON.stringify({ + data: [], + has_more: false, + next_cursor: null, + }), + { headers: { 'Content-Type': 'application/json' } }, + ); + }, + }), + }); + + await defaultFactorySdk.templates.listTemplates(); + assert.equal(defaultFactoryRequest.url, 'https://api.unlayer.com/v3/templates'); + assert.equal(defaultFactoryRequest.headers.get('authorization'), 'Bearer factory-token'); + + const transportError = new TypeError('transport failed'); + const transportClient = createClient({ + fetch: async () => { + throw transportError; + }, + }); + const transportSdk = new Unlayer({ client: transportClient }); + + await assert.rejects(transportSdk.templates.listTemplates(), (error) => error === transportError); + + const transportFields = await transportClient.get({ + responseStyle: 'fields', + throwOnError: false, + url: '/v3/templates', + }); + assert.equal(transportFields.error, transportError); + assert.equal(transportFields.response, undefined); + + const abortController = new AbortController(); + const abortError = new DOMException('request aborted', 'AbortError'); + abortController.abort(abortError); + const abortSdk = new Unlayer({ + client: createClient({ + fetch: async (request) => { + assert.equal(request.signal.aborted, true); + throw request.signal.reason; + }, + }), + }); + + await assert.rejects( + abortSdk.templates.listTemplates({ signal: abortController.signal }), + (error) => error === abortError, + ); + + assert.equal(requests.length, 6); + + const listUrl = new URL(requests[0].url, 'http://localhost'); + assert.equal(requests[0].method, 'GET'); + assert.equal(requests[0].headers.authorization, 'Bearer test-token'); + assert.equal(listUrl.pathname, '/v3/templates'); + assert.equal(listUrl.searchParams.get('displayMode'), 'email'); + assert.equal(listUrl.searchParams.get('limit'), '10'); + assert.equal(listUrl.searchParams.get('name'), 'Summer & Sale'); + assert.equal(listUrl.searchParams.get('projectId'), 'project-1'); + + assert.equal(requests[1].method, 'GET'); + assert.equal(requests[1].url, '/v3/templates/folder%2FWelcome%20%26%20Spring'); + assert.equal(requests[1].headers.authorization, 'Bearer test-token'); + + assert.equal(requests[2].method, 'POST'); + assert.equal(requests[2].url, '/v3/domains'); + assert.equal(requests[2].headers.authorization, 'Bearer test-token'); + assert.match(requests[2].headers['content-type'], /^application\/json/); + assert.deepEqual(JSON.parse(requests[2].body), { domain: 'example.com' }); + + process.stdout.write( + 'Packed SDK smoke passed: isolated clients, auth, path/query/body serialization, factory defaults, HTTP, transport, and abort behavior\n', + ); +} finally { + await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))); +} diff --git a/tests/path.test.ts b/tests/path.test.ts deleted file mode 100644 index 510298d..0000000 --- a/tests/path.test.ts +++ /dev/null @@ -1,462 +0,0 @@ -import { createPathTagFunction, encodeURIPath } from '@unlayer/sdk/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/publish-npm.test.mjs b/tests/publish-npm.test.mjs new file mode 100644 index 0000000..c8dd7b5 --- /dev/null +++ b/tests/publish-npm.test.mjs @@ -0,0 +1,127 @@ +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const packageJson = JSON.parse(fs.readFileSync(path.join(repositoryRoot, 'package.json'), 'utf8')); + +function createHarness() { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'unlayer-sdk-publish-test.')); + const fakeBin = path.join(root, 'bin'); + const packageDir = path.join(root, 'package'); + const publishLog = path.join(root, 'publish.log'); + fs.mkdirSync(fakeBin); + fs.mkdirSync(packageDir); + + const pack = spawnSync('npm', ['pack', '--silent', '--pack-destination', packageDir, './dist'], { + cwd: repositoryRoot, + encoding: 'utf8', + env: { ...process.env, NPM_CONFIG_CACHE: path.join(root, 'npm-cache') }, + }); + assert.equal(pack.status, 0, pack.stderr); + + const archiveName = pack.stdout.trim(); + assert.notEqual(archiveName, ''); + const archive = path.join(packageDir, archiveName); + + const fakeNpm = path.join(fakeBin, 'npm'); + fs.writeFileSync( + fakeNpm, + `#!/usr/bin/env bash +set -euo pipefail + +case "\${1:-}" in + --version) + echo '11.5.1' + ;; + view) + case "\${FAKE_NPM_VIEW_MODE:-}" in + success) + echo '"0.1.0"' + ;; + e404) + echo '{"error":{"code":"E404"}}' + exit 1 + ;; + registry-error) + echo 'registry unavailable' >&2 + exit 1 + ;; + empty) + ;; + malformed) + echo 'not-json' + ;; + *) + echo "unexpected view mode: \${FAKE_NPM_VIEW_MODE:-}" >&2 + exit 1 + ;; + esac + ;; + publish) + printf '%s\\n' "$*" > "\$FAKE_NPM_PUBLISH_LOG" + ;; + *) + echo "unexpected npm command: $*" >&2 + exit 1 + ;; +esac +`, + ); + fs.chmodSync(fakeNpm, 0o755); + + const run = (mode) => + spawnSync('bash', ['./bin/publish-npm', archive], { + cwd: repositoryRoot, + encoding: 'utf8', + env: { + ...process.env, + FAKE_NPM_PUBLISH_LOG: publishLog, + FAKE_NPM_VIEW_MODE: mode, + GITHUB_ACTIONS: 'true', + GITHUB_REF_NAME: `v${packageJson.version}`, + GITHUB_REF_TYPE: 'tag', + PATH: `${fakeBin}:${process.env.PATH}`, + }, + }); + + return { + archive, + cleanup: () => fs.rmSync(root, { force: true, recursive: true }), + publishLog, + run, + }; +} + +for (const mode of ['success', 'e404']) { + test(`publishes after an accepted npm view ${mode} response`, () => { + const harness = createHarness(); + try { + const result = harness.run(mode); + assert.equal(result.status, 0, result.stderr); + assert.equal( + fs.readFileSync(harness.publishLog, 'utf8').trim(), + `publish ${harness.archive} --tag latest --access public`, + ); + } finally { + harness.cleanup(); + } + }); +} + +for (const mode of ['registry-error', 'empty', 'malformed']) { + test(`does not publish after an npm view ${mode} response`, () => { + const harness = createHarness(); + try { + const result = harness.run(mode); + assert.notEqual(result.status, 0); + assert.equal(fs.existsSync(harness.publishLog), false); + } finally { + harness.cleanup(); + } + }); +} diff --git a/tests/stringifyQuery.test.ts b/tests/stringifyQuery.test.ts deleted file mode 100644 index 4f47883..0000000 --- a/tests/stringifyQuery.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Unlayer } from '@unlayer/sdk'; - -const { stringifyQuery } = Unlayer.prototype as any; - -describe(stringifyQuery, () => { - for (const [input, expected] of [ - [{ a: '1', b: 2, c: true }, 'a=1&b=2&c=true'], - [{ a: null, b: false, c: undefined }, 'a=&b=false'], - [{ 'a/b': 1.28341 }, `${encodeURIComponent('a/b')}=1.28341`], - [ - { 'a/b': 'c/d', 'e=f': 'g&h' }, - `${encodeURIComponent('a/b')}=${encodeURIComponent('c/d')}&${encodeURIComponent( - 'e=f', - )}=${encodeURIComponent('g&h')}`, - ], - ]) { - it(`${JSON.stringify(input)} -> ${expected}`, () => { - expect(stringifyQuery(input)).toEqual(expected); - }); - } - - for (const value of [[], {}, new Date()]) { - it(`${JSON.stringify(value)} -> `, () => { - expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`); - }); - } -}); diff --git a/tests/sync-openapi.test.mjs b/tests/sync-openapi.test.mjs new file mode 100644 index 0000000..649d501 --- /dev/null +++ b/tests/sync-openapi.test.mjs @@ -0,0 +1,66 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { createRequire } from 'node:module'; +import test from 'node:test'; + +const require = createRequire(import.meta.url); +const { fetchOpenApiDocument, main } = require('../scripts/sync-openapi.cjs'); + +test('normalizes and writes a non-empty OpenAPI document', async (context) => { + const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'unlayer-sdk-openapi-')); + context.after(() => fs.rmSync(temporaryDirectory, { recursive: true })); + + const outputPath = path.join(temporaryDirectory, 'openapi.json'); + const sourceDocument = { + openapi: '3.0.0', + paths: { '/v3/templates': { get: {} } }, + servers: [{ url: 'http://internal.example.test' }], + }; + const fetcher = async (url, options) => { + assert.equal(url, 'https://example.test/openapi.json'); + assert.equal(options.headers.Accept, 'application/json'); + assert.ok(options.signal instanceof AbortSignal); + return new Response(JSON.stringify(sourceDocument)); + }; + + await main({ + sourceUrl: 'https://example.test/openapi.json', + outputPath, + fetcher, + }); + + const writtenDocument = JSON.parse(fs.readFileSync(outputPath, 'utf8')); + assert.deepEqual(writtenDocument.paths, sourceDocument.paths); + assert.deepEqual(writtenDocument.servers, [{ url: 'https://api.unlayer.com' }]); +}); + +test('rejects unsuccessful and empty OpenAPI responses', async () => { + await assert.rejects( + fetchOpenApiDocument( + 'https://example.test/openapi.json', + async () => new Response(null, { status: 503 }), + ), + /HTTP 503/, + ); + + await assert.rejects( + fetchOpenApiDocument( + 'https://example.test/openapi.json', + async () => new Response(JSON.stringify({ paths: {} })), + ), + /no public paths/, + ); +}); + +test('preserves transport failures', async () => { + const transportError = new TypeError('network unavailable'); + + await assert.rejects( + fetchOpenApiDocument('https://example.test/openapi.json', async () => { + throw transportError; + }), + (error) => error === transportError, + ); +}); diff --git a/tests/uploads.test.ts b/tests/uploads.test.ts deleted file mode 100644 index 7765432..0000000 --- a/tests/uploads.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import fs from 'fs'; -import type { ResponseLike } from '@unlayer/sdk/internal/to-file'; -import { toFile } from '@unlayer/sdk/core/uploads'; -import { File } from 'node:buffer'; - -class MyClass { - name: string = 'foo'; -} - -function mockResponse({ url, content }: { url: string; content?: Blob }): ResponseLike { - return { - url, - blob: async () => content || new Blob([]), - }; -} - -describe('toFile', () => { - it('throws a helpful error for mismatched types', async () => { - await expect( - // @ts-expect-error intentionally mismatched type - toFile({ foo: 'string' }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unexpected data type: object; constructor: Object; props: ["foo"]"`, - ); - - await expect( - // @ts-expect-error intentionally mismatched type - toFile(new MyClass()), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unexpected data type: object; constructor: MyClass; props: ["name"]"`, - ); - }); - - it('disallows string at the type-level', async () => { - // @ts-expect-error we intentionally do not type support for `string` - // to help people avoid passing a file path - const file = await toFile('contents'); - expect(file.text()).resolves.toEqual('contents'); - }); - - it('extracts a file name from a Response', async () => { - const response = mockResponse({ url: 'https://example.com/my/audio.mp3' }); - const file = await toFile(response); - expect(file.name).toEqual('audio.mp3'); - }); - - it('extracts a file name from a File', async () => { - const input = new File(['foo'], 'input.jsonl'); - const file = await toFile(input); - expect(file.name).toEqual('input.jsonl'); - }); - - it('extracts a file name from a ReadStream', async () => { - const input = fs.createReadStream('tests/uploads.test.ts'); - const file = await toFile(input); - expect(file.name).toEqual('uploads.test.ts'); - }); - - it('does not copy File objects', async () => { - const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); - const file = await toFile(input); - expect(file).toBe(input); - 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('@unlayer/sdk/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 deleted file mode 100644 index 384ddac..0000000 --- a/tsc-multi.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "targets": [ - { - "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 deleted file mode 100644 index 5a9d0af..0000000 --- a/tsconfig.build.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["dist/src"], - "exclude": [], - "compilerOptions": { - "rootDir": "./dist/src", - "paths": { - "@unlayer/sdk/*": ["./dist/src/*"], - "@unlayer/sdk": ["./dist/src/index.ts"] - }, - "noEmit": false, - "declaration": true, - "declarationMap": true, - "outDir": "dist", - "pretty": true, - "sourceMap": true - } -} diff --git a/tsconfig.consumer.json b/tsconfig.consumer.json new file mode 100644 index 0000000..19fa089 --- /dev/null +++ b/tsconfig.consumer.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2020"], + "module": "Node16", + "moduleResolution": "Node16", + "noEmit": true, + "strict": true, + "target": "ES2020" + }, + "include": ["tests/consumer.cts", "tests/consumer.mts"] +} diff --git a/tsconfig.deno.json b/tsconfig.deno.json deleted file mode 100644 index 849e070..0000000 --- a/tsconfig.deno.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["dist-deno"], - "exclude": [], - "compilerOptions": { - "rootDir": "./dist-deno", - "lib": ["es2020", "DOM"], - "noEmit": true, - "declaration": true, - "declarationMap": true, - "outDir": "dist-deno", - "pretty": true, - "sourceMap": true - } -} diff --git a/tsconfig.dist-src.json b/tsconfig.dist-src.json index c550e29..c3e708c 100644 --- a/tsconfig.dist-src.json +++ b/tsconfig.dist-src.json @@ -2,10 +2,10 @@ // this config is included in the published src directory to prevent TS errors // from appearing when users go to source, and VSCode opens the source .ts file // via declaration maps - "include": ["index.ts"], + "include": ["**/*.ts"], "compilerOptions": { - "target": "ES2015", - "lib": ["DOM", "DOM.Iterable", "ES2018"], + "target": "ES2020", + "lib": ["DOM", "DOM.Iterable", "ES2020"], "moduleResolution": "node" } } diff --git a/tsconfig.json b/tsconfig.json index e28a793..d20e8f1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,9 @@ { - "include": ["src", "tests", "examples"], + "include": ["src"], "exclude": [], "compilerOptions": { "target": "es2020", - "lib": ["es2020"], + "lib": ["DOM", "DOM.Iterable", "ES2020"], "module": "commonjs", "moduleResolution": "node", "esModuleInterop": true, @@ -26,7 +26,6 @@ "noImplicitThis": true, "noImplicitReturns": true, "alwaysStrict": true, - "exactOptionalPropertyTypes": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, diff --git a/tsdown.config.mts b/tsdown.config.mts new file mode 100644 index 0000000..d2c221f --- /dev/null +++ b/tsdown.config.mts @@ -0,0 +1,18 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + clean: true, + dts: { + sourcemap: true, + }, + entry: { + 'client/index': 'src/client/index.ts', + index: 'src/index.ts', + }, + failOnWarn: true, + fixedExtension: true, + format: ['esm', 'cjs'], + platform: 'neutral', + sourcemap: true, + target: 'es2020', +}); diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index fc9f262..0000000 --- a/yarn.lock +++ /dev/null @@ -1,3468 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@aashutoshrathi/word-wrap@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" - integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== - -"@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.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.28.6.tgz#72499312ec58b1e2245ba4a4f550c132be4982f7" - integrity sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q== - dependencies: - "@babel/helper-validator-identifier" "^7.28.5" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/compat-data@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.6.tgz#103f466803fa0f059e82ccac271475470570d74c" - integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.6.tgz#531bf883a1126e53501ba46eb3bb414047af507f" - integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw== - dependencies: - "@babel/code-frame" "^7.28.6" - "@babel/generator" "^7.28.6" - "@babel/helper-compilation-targets" "^7.28.6" - "@babel/helper-module-transforms" "^7.28.6" - "@babel/helpers" "^7.28.6" - "@babel/parser" "^7.28.6" - "@babel/template" "^7.28.6" - "@babel/traverse" "^7.28.6" - "@babel/types" "^7.28.6" - "@jridgewell/remapping" "^2.3.5" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.28.6", "@babel/generator@^7.7.2": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.6.tgz#48dcc65d98fcc8626a48f72b62e263d25fc3c3f1" - integrity sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw== - dependencies: - "@babel/parser" "^7.28.6" - "@babel/types" "^7.28.6" - "@jridgewell/gen-mapping" "^0.3.12" - "@jridgewell/trace-mapping" "^0.3.28" - jsesc "^3.0.2" - -"@babel/helper-compilation-targets@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" - integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== - dependencies: - "@babel/compat-data" "^7.28.6" - "@babel/helper-validator-option" "^7.27.1" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-globals@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" - integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== - -"@babel/helper-module-imports@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" - integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== - dependencies: - "@babel/traverse" "^7.28.6" - "@babel/types" "^7.28.6" - -"@babel/helper-module-transforms@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" - integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== - dependencies: - "@babel/helper-module-imports" "^7.28.6" - "@babel/helper-validator-identifier" "^7.28.5" - "@babel/traverse" "^7.28.6" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" - integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== - -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - -"@babel/helper-validator-identifier@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" - integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== - -"@babel/helper-validator-option@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" - integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== - -"@babel/helpers@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" - integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== - dependencies: - "@babel/template" "^7.28.6" - "@babel/types" "^7.28.6" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" - integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== - dependencies: - "@babel/types" "^7.28.6" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" - integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== - dependencies: - "@babel/helper-plugin-utils" "^7.28.6" - -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.7.2": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" - integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== - dependencies: - "@babel/helper-plugin-utils" "^7.28.6" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2" - integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== - dependencies: - "@babel/helper-plugin-utils" "^7.28.6" - -"@babel/template@^7.28.6", "@babel/template@^7.3.3": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" - integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== - dependencies: - "@babel/code-frame" "^7.28.6" - "@babel/parser" "^7.28.6" - "@babel/types" "^7.28.6" - -"@babel/traverse@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.6.tgz#871ddc79a80599a5030c53b1cc48cbe3a5583c2e" - integrity sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg== - dependencies: - "@babel/code-frame" "^7.28.6" - "@babel/generator" "^7.28.6" - "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.6" - "@babel/template" "^7.28.6" - "@babel/types" "^7.28.6" - debug "^4.3.1" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.6", "@babel/types@^7.3.3": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" - integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.28.5" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - 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" - integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== - -"@cspotcode/source-map-support@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" - integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== - dependencies: - "@cspotcode/source-map-consumer" "0.8.0" - -"@eslint-community/eslint-utils@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== - dependencies: - eslint-visitor-keys "^3.3.0" - -"@eslint-community/eslint-utils@^4.8.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" - integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== - dependencies: - eslint-visitor-keys "^3.4.3" - -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== - -"@eslint/config-array@^0.21.1": - version "0.21.1" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" - integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== - dependencies: - "@eslint/object-schema" "^2.1.7" - debug "^4.3.1" - minimatch "^3.1.2" - -"@eslint/config-helpers@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" - integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== - dependencies: - "@eslint/core" "^0.17.0" - -"@eslint/core@^0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" - integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== - dependencies: - "@types/json-schema" "^7.0.15" - -"@eslint/eslintrc@^3.3.1": - version "3.3.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz#26393a0806501b5e2b6a43aa588a4d8df67880ac" - integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^10.0.1" - globals "^14.0.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.1" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@9.39.1": - version "9.39.1" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.1.tgz#0dd59c3a9f40e3f1882975c321470969243e0164" - integrity sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw== - -"@eslint/object-schema@^2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" - integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== - -"@eslint/plugin-kit@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" - integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== - dependencies: - "@eslint/core" "^0.17.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/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.2": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" - integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" - integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - -"@jest/core@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" - integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== - dependencies: - "@jest/console" "^29.7.0" - "@jest/reporters" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^29.7.0" - jest-config "^29.7.0" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-resolve-dependencies "^29.7.0" - jest-runner "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - jest-watcher "^29.7.0" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/create-cache-key-function@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" - integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== - dependencies: - "@jest/types" "^29.6.3" - -"@jest/environment@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" - integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== - dependencies: - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== - dependencies: - jest-get-type "^29.6.3" - -"@jest/expect@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" - integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== - dependencies: - expect "^29.7.0" - jest-snapshot "^29.7.0" - -"@jest/fake-timers@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== - dependencies: - "@jest/types" "^29.6.3" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -"@jest/globals@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" - integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/types" "^29.6.3" - jest-mock "^29.7.0" - -"@jest/reporters@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" - integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^6.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - jest-worker "^29.7.0" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - v8-to-istanbul "^9.0.1" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/source-map@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" - integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" - integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== - dependencies: - "@jest/console" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" - integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== - dependencies: - "@jest/test-result" "^29.7.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - slash "^3.0.0" - -"@jest/transform@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" - integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" - integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/remapping@^2.3.5": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" - integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== - -"@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" - integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": - version "0.3.31" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" - integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@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" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - 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": - 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== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.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" - integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@swc/core-darwin-arm64@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.16.tgz#2cd45d709ce76d448d96bf8d0006849541436611" - integrity sha512-UOCcH1GvjRnnM/LWT6VCGpIk0OhHRq6v1U6QXuPt5wVsgXnXQwnf5k3sG5Cm56hQHDvhRPY6HCsHi/p0oek8oQ== - -"@swc/core-darwin-x64@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.4.16.tgz#a5bc7d8b1dd850adb0bb95c6b5c742b92201fd01" - integrity sha512-t3bgqFoYLWvyVtVL6KkFNCINEoOrIlyggT/kJRgi1y0aXSr0oVgcrQ4ezJpdeahZZ4N+Q6vT3ffM30yIunELNA== - -"@swc/core-linux-arm-gnueabihf@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.16.tgz#961744908ee5cbb79bc009dcf58cc8b831111f38" - integrity sha512-DvHuwvEF86YvSd0lwnzVcjOTZ0jcxewIbsN0vc/0fqm9qBdMMjr9ox6VCam1n3yYeRtj4VFgrjeNFksqbUejdQ== - -"@swc/core-linux-arm64-gnu@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.16.tgz#43713be3f26757d82d2745dc25f8b63400e0a3d0" - integrity sha512-9Uu5YlPbyCvbidjKtYEsPpyZlu16roOZ5c2tP1vHfnU9bgf5Tz5q5VovSduNxPHx+ed2iC1b1URODHvDzbbDuQ== - -"@swc/core-linux-arm64-musl@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.16.tgz#394a7d030f3a61902bd3947bb9d70d26d42f3c81" - integrity sha512-/YZq/qB1CHpeoL0eMzyqK5/tYZn/rzKoCYDviFU4uduSUIJsDJQuQA/skdqUzqbheOXKAd4mnJ1hT04RbJ8FPQ== - -"@swc/core-linux-x64-gnu@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.16.tgz#71eb108b784f9d551ee8a35ebcdaed972f567981" - integrity sha512-UUjaW5VTngZYDcA8yQlrFmqs1tLi1TxbKlnaJwoNhel9zRQ0yG1YEVGrzTvv4YApSuIiDK18t+Ip927bwucuVQ== - -"@swc/core-linux-x64-musl@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.16.tgz#10dbaedb4e3dfc7268e3a9a66ad3431471ef035b" - integrity sha512-aFhxPifevDTwEDKPi4eRYWzC0p/WYJeiFkkpNU5Uc7a7M5iMWPAbPFUbHesdlb9Jfqs5c07oyz86u+/HySBNPQ== - -"@swc/core-win32-arm64-msvc@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.16.tgz#80247adff6c245ff32b44d773c1a148858cd655f" - integrity sha512-bTD43MbhIHL2s5QgCwyleaGwl96Gk/scF2TaVKdUe4QlJCDV/YK9h5oIBAp63ckHtE8GHlH4c8dZNBiAXn4Org== - -"@swc/core-win32-ia32-msvc@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.16.tgz#e540afc3ccf3224267b4ddfb408f9d9737984686" - integrity sha512-/lmZeAN/qV5XbK2SEvi8e2RkIg8FQNYiSA8y2/Zb4gTUMKVO5JMLH0BSWMiIKMstKDPDSxMWgwJaQHF8UMyPmQ== - -"@swc/core-win32-x64-msvc@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.16.tgz#f880939fca32c181adfe7e3abd2b6b7857bd3489" - integrity sha512-BPAfFfODWXtUu6SwaTTftDHvcbDyWBSI/oanUeRbQR5vVWkXoQ3cxLTsDluc3H74IqXS5z1Uyoe0vNo2hB1opA== - -"@swc/core@^1.3.102": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.4.16.tgz#d175bae2acfecd53bcbd4293f1fba5ec316634a0" - integrity sha512-Xaf+UBvW6JNuV131uvSNyMXHn+bh6LyKN4tbv7tOUFQpXyz/t9YWRE04emtlUW9Y0qrm/GKFCbY8n3z6BpZbTA== - dependencies: - "@swc/counter" "^0.1.2" - "@swc/types" "^0.1.5" - optionalDependencies: - "@swc/core-darwin-arm64" "1.4.16" - "@swc/core-darwin-x64" "1.4.16" - "@swc/core-linux-arm-gnueabihf" "1.4.16" - "@swc/core-linux-arm64-gnu" "1.4.16" - "@swc/core-linux-arm64-musl" "1.4.16" - "@swc/core-linux-x64-gnu" "1.4.16" - "@swc/core-linux-x64-musl" "1.4.16" - "@swc/core-win32-arm64-msvc" "1.4.16" - "@swc/core-win32-ia32-msvc" "1.4.16" - "@swc/core-win32-x64-msvc" "1.4.16" - -"@swc/counter@^0.1.2", "@swc/counter@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" - integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== - -"@swc/jest@^0.2.29": - version "0.2.36" - resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.36.tgz#2797450a30d28b471997a17e901ccad946fe693e" - integrity sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw== - dependencies: - "@jest/create-cache-key-function" "^29.7.0" - "@swc/counter" "^0.1.3" - jsonc-parser "^3.2.0" - -"@swc/types@^0.1.5": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.6.tgz#2f13f748995b247d146de2784d3eb7195410faba" - integrity sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg== - dependencies: - "@swc/counter" "^0.1.3" - -"@tsconfig/node10@^1.0.7": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" - integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== - -"@tsconfig/node12@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" - integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== - -"@tsconfig/node14@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" - integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== - -"@tsconfig/node16@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" - integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== - -"@types/babel__core@^7.1.14": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.8" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" - integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.4" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.4.tgz#ec2c06fed6549df8bc0eb4615b683749a4a92e1b" - integrity sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA== - 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" - integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" - integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== - -"@types/istanbul-lib-report@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" - integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" - integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^29.4.0": - version "29.5.11" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c" - integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - -"@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@*": - 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 "~5.26.4" - -"@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" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" - integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== - -"@types/yargs-parser@*": - version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" - integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== - -"@types/yargs@^17.0.8": - version "17.0.32" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.32.tgz#030774723a2f7faafebf645f4e5a48371dca6229" - integrity sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== - dependencies: - "@types/yargs-parser" "*" - -"@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.3.1" - natural-compare "^1.4.0" - ts-api-utils "^2.0.1" - -"@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" "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@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" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - -"@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" "8.31.1" - "@typescript-eslint/utils" "8.31.1" - debug "^4.3.4" - ts-api-utils "^2.0.1" - -"@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@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" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^2.0.1" - -"@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" - "@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": - 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" "8.31.1" - eslint-visitor-keys "^4.2.0" - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-walk@^8.1.1: - version "8.2.0" - 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.15.0: - version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== - -acorn@^8.4.1: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-escapes@^4.2.1: - 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== - 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@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.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" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -babel-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" - integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== - dependencies: - "@jest/transform" "^29.7.0" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.6.3" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" - integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-import-attributes" "^7.24.7" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - -babel-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== - dependencies: - babel-plugin-jest-hoist "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -baseline-browser-mapping@^2.9.0: - version "2.9.14" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz#3b6af0bc032445bca04de58caa9a87cfe921cbb3" - integrity sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg== - -brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" - integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browserslist@^4.24.0: - version "4.28.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" - integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== - dependencies: - baseline-browser-mapping "^2.9.0" - caniuse-lite "^1.0.30001759" - electron-to-chromium "^1.5.263" - node-releases "^2.0.27" - update-browserslist-db "^1.2.0" - -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001759: - version "1.0.30001764" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz#03206c56469f236103b90f9ae10bcb8b9e1f6005" - integrity sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g== - -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== - dependencies: - 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" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - -cjs-module-lexer@^1.0.0: - version "1.2.3" - 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" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -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" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -create-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" - integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-config "^29.7.0" - jest-util "^29.7.0" - prompts "^2.0.1" - -create-require@^1.1.0: - version "1.1.1" - 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.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== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -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== - dependencies: - ms "^2.1.3" - -dedent@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" - integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -electron-to-chromium@^1.5.263: - version "1.5.267" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz#5d84f2df8cdb6bfe7e873706bb21bd4bfb574dc7" - integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw== - -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - -emoji-regex@^8.0.0: - version "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" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "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.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.11.7" - -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@^8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" - integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -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-visitor-keys@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" - integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== - -eslint@^9.39.1: - version "9.39.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.1.tgz#be8bf7c6de77dcc4252b5a8dcb31c2efff74a6e5" - integrity sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g== - dependencies: - "@eslint-community/eslint-utils" "^4.8.0" - "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.21.1" - "@eslint/config-helpers" "^0.4.2" - "@eslint/core" "^0.17.0" - "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.39.1" - "@eslint/plugin-kit" "^0.4.1" - "@humanfs/node" "^0.16.6" - "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.2" - "@types/estree" "^1.0.6" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.6" - debug "^4.3.2" - escape-string-regexp "^4.0.0" - eslint-scope "^8.4.0" - eslint-visitor-keys "^4.2.1" - espree "^10.4.0" - esquery "^1.5.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^8.0.0" - find-up "^5.0.0" - glob-parent "^6.0.2" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - json-stable-stringify-without-jsonify "^1.0.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - -espree@^10.0.1: - version "10.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" - integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== - dependencies: - acorn "^8.14.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.0" - -espree@^10.4.0: - version "10.4.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" - integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== - dependencies: - acorn "^8.15.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.1" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -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" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expect@^29.0.0, expect@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== - dependencies: - "@jest/expect-utils" "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - -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" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-diff@^1.1.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" - integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== - -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== - 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-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - 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@^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 "^4.0.0" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -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: - flatted "^3.2.9" - keyv "^4.5.4" - -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" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -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" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "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-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-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: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - 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@^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" - 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== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -hasown@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" - integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== - 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" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -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== - -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== - dependencies: - hasown "^2.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -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-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== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== - -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-instrument@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz#71e87707e8041428732518c6fb5211761753fbdf" - integrity sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^7.5.4" - -istanbul-lib-report@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.6" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" - integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jest-changed-files@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" - integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== - dependencies: - execa "^5.0.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - -jest-circus@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" - integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^1.0.0" - is-generator-fn "^2.0.0" - jest-each "^29.7.0" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - pretty-format "^29.7.0" - pure-rand "^6.0.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" - integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== - dependencies: - "@jest/core" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - chalk "^4.0.0" - create-jest "^29.7.0" - exit "^0.1.2" - import-local "^3.0.2" - jest-config "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - yargs "^17.3.1" - -jest-config@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" - integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.7.0" - "@jest/types" "^29.6.3" - babel-jest "^29.7.0" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^29.7.0" - jest-environment-node "^29.7.0" - jest-get-type "^29.6.3" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-runner "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" - integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.6.3" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-docblock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" - integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" - integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - jest-get-type "^29.6.3" - jest-util "^29.7.0" - pretty-format "^29.7.0" - -jest-environment-node@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" - integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== - -jest-haste-map@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" - integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== - dependencies: - "@jest/types" "^29.6.3" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - jest-worker "^29.7.0" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" - integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== - dependencies: - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-matcher-utils@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" - integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== - dependencies: - chalk "^4.0.0" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - -jest-resolve-dependencies@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" - integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== - dependencies: - jest-regex-util "^29.6.3" - jest-snapshot "^29.7.0" - -jest-resolve@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" - integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-pnp-resolver "^1.2.2" - jest-util "^29.7.0" - jest-validate "^29.7.0" - resolve "^1.20.0" - resolve.exports "^2.0.0" - slash "^3.0.0" - -jest-runner@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" - integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== - dependencies: - "@jest/console" "^29.7.0" - "@jest/environment" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.13.1" - graceful-fs "^4.2.9" - jest-docblock "^29.7.0" - jest-environment-node "^29.7.0" - jest-haste-map "^29.7.0" - jest-leak-detector "^29.7.0" - jest-message-util "^29.7.0" - jest-resolve "^29.7.0" - jest-runtime "^29.7.0" - jest-util "^29.7.0" - jest-watcher "^29.7.0" - jest-worker "^29.7.0" - p-limit "^3.1.0" - source-map-support "0.5.13" - -jest-runtime@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" - integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/globals" "^29.7.0" - "@jest/source-map" "^29.6.3" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" - integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-jsx" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^29.7.0" - graceful-fs "^4.2.9" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - natural-compare "^1.4.0" - pretty-format "^29.7.0" - semver "^7.5.3" - -jest-util@^29.0.0, jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" - integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== - dependencies: - "@jest/types" "^29.6.3" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.6.3" - leven "^3.1.0" - pretty-format "^29.7.0" - -jest-watcher@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" - integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== - dependencies: - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.13.1" - jest-util "^29.7.0" - string-length "^4.0.1" - -jest-worker@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== - dependencies: - "@types/node" "*" - jest-util "^29.7.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^29.4.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" - integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== - dependencies: - "@jest/core" "^29.7.0" - "@jest/types" "^29.6.3" - import-local "^3.0.2" - jest-cli "^29.7.0" - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.2" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" - integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" - integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== - dependencies: - argparse "^2.0.1" - -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - -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" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^2.2.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonc-parser@^3.2.0: - version "3.2.1" - 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" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.memoize@4.x: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.merge@^4.6.2: - version "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" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -make-error@1.x, make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -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: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -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== - -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.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-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: - "@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" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.27: - version "2.0.27" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" - integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== - -normalize-path@^3.0.0: - version "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" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.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" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" - integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== - dependencies: - "@aashutoshrathi/word-wrap" "^1.2.3" - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - -p-all@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" - integrity sha512-qUZbvbBFVXm6uJ7U/WDiO0fv6waBMbjlCm4E66oZdRR+egswICarIdHyVSZZHudH8T5SF8x/JG0q0duFzPnlBw== - dependencies: - p-map "^4.0.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2, p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - 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" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -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== - -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" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pirates@^4.0.4: - version "4.0.6" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" - integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.1.tgz#6ba9f23165d690b6cbdaa88cb0807278f7019848" - integrity sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw== - -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" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - 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" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== - -pure-rand@^6.0.0: - version "6.0.4" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" - integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -react-is@^18.0.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -readable-stream@^3.4.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve.exports@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" - integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== - -resolve@^1.20.0: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - 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" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"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== - -semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.5.3: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -semver@^7.5.4: - version "7.6.3" - 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" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -sisteransi@^1.0.5: - version "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" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -string-to-stream@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-3.0.1.tgz#480e6fb4d5476d31cb2221f75307a5dcb6638a42" - integrity sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg== - dependencies: - readable-stream "^3.4.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -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.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@^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== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - 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.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/core" "^0.2.4" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -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" - -"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" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -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" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" - integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== - dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" - jest-util "^29.0.0" - json5 "^2.2.3" - lodash.memoize "4.x" - make-error "1.x" - semver "^7.5.3" - yargs-parser "^21.0.1" - -ts-node@^10.5.0: - version "10.7.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" - integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== - dependencies: - "@cspotcode/source-map-support" "0.7.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.0" - yn "3.1.1" - -"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.7" - fast-glob "^3.3.2" - get-stdin "^8.0.0" - p-all "^3.0.0" - picocolors "^1.1.1" - signal-exit "^3.0.7" - string-to-stream "^3.0.1" - superstruct "^1.0.4" - tslib "^2.8.1" - yargs "^17.7.2" - -tsconfig-paths@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" - integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== - dependencies: - json5 "^2.2.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -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" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-detect@4.0.8: - version "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.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-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" - -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== - -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.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" - integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -v8-compile-cache-lib@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8" - integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== - -v8-to-istanbul@^9.0.1: - version "9.2.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" - integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@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" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.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" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "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@^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== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==