diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6b6073..e3530dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: pull_request: branches: - main + - next jobs: lint: @@ -21,13 +22,30 @@ jobs: with: node-version: '18' - - name: Install dependencies - run: | - yarn install + - name: Bootstrap + run: ./scripts/bootstrap - name: Check types - run: | - yarn build + run: ./scripts/lint + + build: + name: build + runs-on: ubuntu-latest + + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Bootstrap + run: ./scripts/bootstrap + + - name: Check build + run: ./scripts/build test: name: test runs-on: ubuntu-latest diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 6d38dbe..e7af3f0 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -1,6 +1,8 @@ name: Release Doctor on: pull_request: + branches: + - main workflow_dispatch: jobs: @@ -17,3 +19,4 @@ jobs: bash ./bin/check-release-environment env: NPM_TOKEN: ${{ secrets.BOOKLET_NPM_TOKEN || secrets.NPM_TOKEN }} + diff --git a/.gitignore b/.gitignore index 9a5858a..d98d51a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ +.prism.log node_modules yarn-error.log codegen.log Brewfile.lock.json dist -/deno +dist-deno /*.tgz .idea/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 063a4ae..d7a8735 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.1-alpha.1" + ".": "0.1.0-alpha.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 656e9a9..0424898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # Changelog +## 0.1.0-alpha.1 (2025-01-18) + +Full Changelog: [v0.0.1-alpha.1...v0.1.0-alpha.1](https://github.com/contraptionco/booklet-node/compare/v0.0.1-alpha.1...v0.1.0-alpha.1) + +### Features + +* **api:** OpenAPI spec update via Stainless API ([#5](https://github.com/contraptionco/booklet-node/issues/5)) ([9f9c4e6](https://github.com/contraptionco/booklet-node/commit/9f9c4e64078f331bf01e944cd565f016df9582b9)) +* **api:** update via SDK Studio ([#7](https://github.com/contraptionco/booklet-node/issues/7)) ([b4832fe](https://github.com/contraptionco/booklet-node/commit/b4832fe2213779d8f59d6f0a7191d46486d4ad93)) +* **internal:** make git install file structure match npm ([#14](https://github.com/contraptionco/booklet-node/issues/14)) ([7be6b86](https://github.com/contraptionco/booklet-node/commit/7be6b86ff0f49a2dcbdc9c19927770e38004ca3c)) + + +### Bug Fixes + +* **client:** normalize method ([#20](https://github.com/contraptionco/booklet-node/issues/20)) ([d9d12fd](https://github.com/contraptionco/booklet-node/commit/d9d12fdd69451ad2ba1a2173d1ed261540835d1b)) + + +### Chores + +* **internal:** bump cross-spawn to v7.0.6 ([#16](https://github.com/contraptionco/booklet-node/issues/16)) ([e65eb19](https://github.com/contraptionco/booklet-node/commit/e65eb1996762efc9cbab31ed900dad0ef6a8b71d)) +* **internal:** codegen related update ([#19](https://github.com/contraptionco/booklet-node/issues/19)) ([a9637a6](https://github.com/contraptionco/booklet-node/commit/a9637a67a5691574b1f9256112e2a07460898751)) +* **internal:** codegen related update ([#21](https://github.com/contraptionco/booklet-node/issues/21)) ([f6c9ebd](https://github.com/contraptionco/booklet-node/commit/f6c9ebdb532a7a44740a10c05116a495e1f6d4c2)) +* **internal:** codegen related update ([#22](https://github.com/contraptionco/booklet-node/issues/22)) ([1bffa88](https://github.com/contraptionco/booklet-node/commit/1bffa8808e2c5c8df861da259fe448247c5aa532)) +* **internal:** codegen related update ([#23](https://github.com/contraptionco/booklet-node/issues/23)) ([7fdb428](https://github.com/contraptionco/booklet-node/commit/7fdb428f3ed4e0ac22a7321ee94bfde4386ad029)) +* **internal:** codegen related update ([#24](https://github.com/contraptionco/booklet-node/issues/24)) ([eb451a5](https://github.com/contraptionco/booklet-node/commit/eb451a5a9320d1383221d7772a15d222f7d35c54)) +* **internal:** remove unnecessary getRequestClient function ([#15](https://github.com/contraptionco/booklet-node/issues/15)) ([fb6add5](https://github.com/contraptionco/booklet-node/commit/fb6add5b2815fa11a00fefb1ec3b6e5f19d5bf89)) +* **internal:** update isAbsoluteURL ([#18](https://github.com/contraptionco/booklet-node/issues/18)) ([444c551](https://github.com/contraptionco/booklet-node/commit/444c551093f053027d44cafb2fafb7b2f889adcd)) +* rebuild project due to codegen change ([#10](https://github.com/contraptionco/booklet-node/issues/10)) ([21d7ffd](https://github.com/contraptionco/booklet-node/commit/21d7ffdf216113e48b5d0ece5ad9ca7768e5bb03)) +* rebuild project due to codegen change ([#11](https://github.com/contraptionco/booklet-node/issues/11)) ([b8d1379](https://github.com/contraptionco/booklet-node/commit/b8d13794f060c11e57c83f262a5b305ca215f8ed)) +* rebuild project due to codegen change ([#8](https://github.com/contraptionco/booklet-node/issues/8)) ([42c5432](https://github.com/contraptionco/booklet-node/commit/42c5432581e52d934a3e5bc8828c2e68a8f569ec)) +* rebuild project due to codegen change ([#9](https://github.com/contraptionco/booklet-node/issues/9)) ([9357498](https://github.com/contraptionco/booklet-node/commit/935749842956eda69d60880005b43790945da997)) +* remove redundant word in comment ([#13](https://github.com/contraptionco/booklet-node/issues/13)) ([7569cd5](https://github.com/contraptionco/booklet-node/commit/7569cd546273316f4de5fad7507aee4c84c9496e)) +* **types:** nicer error class types + jsdocs ([#17](https://github.com/contraptionco/booklet-node/issues/17)) ([56b5355](https://github.com/contraptionco/booklet-node/commit/56b53550e582c2b91a99aea3cb8f3460478e6839)) + + +### Documentation + +* remove suggestion to use `npm` call out ([#12](https://github.com/contraptionco/booklet-node/issues/12)) ([c23a86f](https://github.com/contraptionco/booklet-node/commit/c23a86fd4ba036653925f16d330093a404e46a80)) + ## 0.0.1-alpha.1 (2024-05-09) Full Changelog: [v0.0.1-alpha.0...v0.0.1-alpha.1](https://github.com/contraptionco/booklet-node/compare/v0.0.1-alpha.0...v0.0.1-alpha.1) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 343c268..499e89c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,38 +1,38 @@ ## Setting up the environment -This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable). +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. To set up the repository, run: -```bash -yarn -yarn build +```sh +$ yarn +$ yarn build ``` This will install all the required dependencies and build output files to `dist/`. ## Modifying/Adding code -Most of the SDK is generated code, and any modified code will be overridden on the next generation. The -`src/lib/` and `examples/` directories are exceptions and will never be overridden. +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. ## Adding and running examples -All files in the `examples/` directory are not modified by the Stainless generator and can be freely edited or -added to. +All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. -```bash +```ts // add an example to examples/.ts #!/usr/bin/env -S npm run tsn -T … ``` -``` -chmod +x examples/.ts +```sh +$ chmod +x examples/.ts # run the example against your api -yarn tsn -T examples/.ts +$ yarn tsn -T examples/.ts ``` ## Using the repository from source @@ -41,38 +41,38 @@ If you’d like to use the repository from source, you can either install from g To install via git: -```bash -npm install git+ssh://git@github.com:contraptionco/booklet-node.git +```sh +$ npm install git+ssh://git@github.com:contraptionco/booklet-node.git ``` Alternatively, to link a local copy of the repo: -```bash +```sh # Clone -git clone https://www.github.com/contraptionco/booklet-node -cd booklet-node +$ git clone https://www.github.com/contraptionco/booklet-node +$ cd booklet-node # With yarn -yarn link -cd ../my-package -yarn link bklt +$ yarn link +$ cd ../my-package +$ yarn link bklt # With pnpm -pnpm link --global -cd ../my-package -pnpm link -—global bklt +$ pnpm link --global +$ cd ../my-package +$ pnpm link -—global bklt ``` ## Running tests Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. -```bash -npx prism mock path/to/your/openapi.yml +```sh +$ npx prism mock path/to/your/openapi.yml ``` -```bash -yarn run test +```sh +$ yarn run test ``` ## Linting and formatting @@ -82,14 +82,14 @@ This repository uses [prettier](https://www.npmjs.com/package/prettier) and To lint: -```bash -yarn lint +```sh +$ yarn lint ``` To format and fix all lint issues automatically: -```bash -yarn fix +```sh +$ yarn fix ``` ## Publishing and releases diff --git a/LICENSE b/LICENSE index b58ffc0..dd8caad 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2024 Booklet + Copyright 2025 Booklet Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 38fe7ca..1a83127 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Booklet Node API Library -[![NPM version](https://img.shields.io/npm/v/bklt.svg)](https://npmjs.org/package/bklt) +[![NPM version](https://img.shields.io/npm/v/bklt.svg)](https://npmjs.org/package/bklt) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/bklt) This library provides convenient access to the Booklet REST API from server-side TypeScript or JavaScript. -The REST API documentation can be found [on docs.booklet.com](https://docs.booklet.com). The full API of this library can be found in [api.md](api.md). +The REST API documentation can be found on [docs.booklet.group](https://docs.booklet.group). The full API of this library can be found in [api.md](api.md). It is generated with [Stainless](https://www.stainlessapi.com/). @@ -22,14 +22,14 @@ The full API of this library can be found in [api.md](api.md). ```js import Booklet from 'bklt'; -const booklet = new Booklet({ +const client = new Booklet({ apiKey: process.env['BOOKLET_API_KEY'], // This is the default and can be omitted }); async function main() { - const memberRetrieveResponse = await booklet.members.retrieve('REPLACE_ME'); + const member = await client.members.retrieve('REPLACE_ME'); - console.log(memberRetrieveResponse.id); + console.log(member.id); } main(); @@ -43,12 +43,12 @@ This library includes TypeScript definitions for all request params and response ```ts import Booklet from 'bklt'; -const booklet = new Booklet({ +const client = new Booklet({ apiKey: process.env['BOOKLET_API_KEY'], // This is the default and can be omitted }); async function main() { - const memberRetrieveResponse: Booklet.MemberRetrieveResponse = await booklet.members.retrieve('REPLACE_ME'); + const member: Booklet.MemberRetrieveResponse = await client.members.retrieve('REPLACE_ME'); } main(); @@ -65,7 +65,7 @@ a subclass of `APIError` will be thrown: ```ts async function main() { - const memberRetrieveResponse = await booklet.members.retrieve('REPLACE_ME').catch(async (err) => { + const member = await client.members.retrieve('REPLACE_ME').catch(async (err) => { if (err instanceof Booklet.APIError) { console.log(err.status); // 400 console.log(err.name); // BadRequestError @@ -103,12 +103,12 @@ You can use the `maxRetries` option to configure or disable this: ```js // Configure the default for all requests: -const booklet = new Booklet({ +const client = new Booklet({ maxRetries: 0, // default is 2 }); // Or, configure per-request: -await booklet.members.retrieve('REPLACE_ME', { +await client.members.retrieve('REPLACE_ME', { maxRetries: 5, }); ``` @@ -120,12 +120,12 @@ Requests time out after 1 minute by default. You can configure this with a `time ```ts // Configure the default for all requests: -const booklet = new Booklet({ +const client = new Booklet({ timeout: 20 * 1000, // 20 seconds (default is 1 minute) }); // Override per-request: -await booklet.members.retrieve('REPLACE_ME', { +await client.members.retrieve('REPLACE_ME', { timeout: 5 * 1000, }); ``` @@ -144,17 +144,15 @@ You can also use the `.withResponse()` method to get the raw `Response` along wi ```ts -const booklet = new Booklet(); +const client = new Booklet(); -const response = await booklet.members.retrieve('REPLACE_ME').asResponse(); +const response = await client.members.retrieve('REPLACE_ME').asResponse(); console.log(response.headers.get('X-My-Header')); console.log(response.statusText); // access the underlying Response object -const { data: memberRetrieveResponse, response: raw } = await booklet.members - .retrieve('REPLACE_ME') - .withResponse(); +const { data: member, response: raw } = await client.members.retrieve('REPLACE_ME').withResponse(); console.log(raw.headers.get('X-My-Header')); -console.log(memberRetrieveResponse.id); +console.log(member.id); ``` ### Making custom/undocumented requests @@ -253,12 +251,12 @@ import http from 'http'; import { HttpsProxyAgent } from 'https-proxy-agent'; // Configure the default for all requests: -const booklet = new Booklet({ +const client = new Booklet({ httpAgent: new HttpsProxyAgent(process.env.PROXY_URL), }); // Override per-request: -await booklet.members.retrieve('REPLACE_ME', { +await client.members.retrieve('REPLACE_ME', { httpAgent: new http.Agent({ keepAlive: false }), }); ``` @@ -268,7 +266,7 @@ await booklet.members.retrieve('REPLACE_ME', { 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)_. +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. @@ -281,8 +279,9 @@ TypeScript >= 4.5 is supported. The following runtimes are supported: +- Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more) - Node.js 18 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions. -- Deno v1.28.0 or higher, using `import Booklet from "npm:bklt"`. +- Deno v1.28.0 or higher. - Bun 1.0 or later. - Cloudflare Workers. - Vercel Edge Runtime. @@ -292,3 +291,7 @@ The following runtimes are supported: Note that React Native is not supported at this time. If you are interested in other runtime environments, please open or upvote an issue on GitHub. + +## Contributing + +See [the contributing documentation](./CONTRIBUTING.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..adf2936 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,27 @@ +# Security Policy + +## Reporting Security Issues + +This SDK is generated by [Stainless Software Inc](http://stainlessapi.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@stainlessapi.com. + +## 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 +disclosure practices by allowing us a reasonable amount of time to investigate and address the issue +before making any information public. + +## Reporting Non-SDK Related Security Issues + +If you encounter security issues that are not directly related to SDKs but pertain to the services +or products provided by Booklet please follow the respective company's security reporting guidelines. + +### Booklet Terms and Policies + +Please contact booklet@contraption.co for any questions or concerns regarding security of our services. + +--- + +Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/bin/check-release-environment b/bin/check-release-environment index 130c14e..429d585 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -1,20 +1,9 @@ #!/usr/bin/env bash -warnings=() errors=() if [ -z "${NPM_TOKEN}" ]; then - warnings+=("The BOOKLET_NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") -fi - -lenWarnings=${#warnings[@]} - -if [[ lenWarnings -gt 0 ]]; then - echo -e "Found the following warnings in the release environment:\n" - - for warning in "${warnings[@]}"; do - echo -e "- $warning\n" - done + errors+=("The BOOKLET_NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") fi lenErrors=${#errors[@]} @@ -30,3 +19,4 @@ if [[ lenErrors -gt 0 ]]; then fi echo "The environment is ready to push releases!" + diff --git a/bin/publish-npm b/bin/publish-npm index 4d6c9f3..4c21181 100644 --- a/bin/publish-npm +++ b/bin/publish-npm @@ -2,8 +2,24 @@ set -eux -npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN +npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" +# Build the project yarn build + +# Navigate to the dist directory cd dist -yarn publish --access public + +# Get the version from package.json +VERSION="$(node -p "require('./package.json').version")" + +# Extract the pre-release tag if it exists +if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then + # Extract the part before any dot in the pre-release identifier + TAG="${BASH_REMATCH[1]}" +else + TAG="latest" +fi + +# Publish with the appropriate tag +yarn publish --access public --tag "$TAG" diff --git a/package.json b/package.json index 0a3bb40..d162ca5 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "bklt", - "version": "0.0.1-alpha.1", + "version": "0.1.0-alpha.1", "description": "The official TypeScript library for the Booklet API", - "author": "Booklet ", + "author": "Booklet ", "types": "dist/index.d.ts", "main": "dist/index.js", "type": "commonjs", @@ -10,19 +10,18 @@ "license": "Apache-2.0", "packageManager": "yarn@1.22.22", "files": [ - "*" + "**/*" ], "private": false, "scripts": { "test": "./scripts/test", "build": "./scripts/build", - "prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1", "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", "format": "prettier --write --cache --cache-strategy metadata . !dist", - "prepare": "if ./scripts/utils/check-is-in-git-install.sh; then ./scripts/build; fi", + "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": "eslint --fix --ext ts,js ." + "fix": "./scripts/format" }, "dependencies": { "@types/node": "^18.11.18", @@ -31,8 +30,7 @@ "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7", - "web-streams-polyfill": "^3.2.1" + "node-fetch": "^2.6.7" }, "devDependencies": { "@swc/core": "^1.3.102", @@ -43,10 +41,10 @@ "eslint": "^8.49.0", "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-unused-imports": "^3.0.0", + "iconv-lite": "^0.6.3", "jest": "^29.4.0", "prettier": "^3.0.0", "ts-jest": "^29.1.0", - "ts-morph": "^19.0.0", "ts-node": "^10.5.0", "tsc-multi": "^1.1.0", "tsconfig-paths": "^4.0.0", diff --git a/scripts/build b/scripts/build index f057fa5..f69a852 100755 --- a/scripts/build +++ b/scripts/build @@ -32,7 +32,7 @@ npm exec tsc-multi # copy over handwritten .js/.mjs/.d.ts files cp src/_shims/*.{d.ts,js,mjs,md} dist/_shims cp src/_shims/auto/*.{d.ts,js,mjs} dist/_shims/auto -# we need to add exports = module.exports = Booklet Node to index.js; +# we need to add exports = module.exports = Booklet to index.js; # No way to get that from index.ts because it would cause compile errors # when building .mjs node scripts/utils/fix-index-exports.cjs @@ -50,7 +50,7 @@ node scripts/utils/postprocess-files.cjs (cd dist && node -e 'require("bklt")') (cd dist && node -e 'import("bklt")' --input-type=module) -if command -v deno &> /dev/null && [ -e ./scripts/build-deno ] +if [ -e ./scripts/build-deno ] then ./scripts/build-deno fi diff --git a/scripts/format b/scripts/format new file mode 100755 index 0000000..a6bb9d0 --- /dev/null +++ b/scripts/format @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Running eslint --fix" +ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --fix --ext ts,js . diff --git a/scripts/lint b/scripts/lint index 4f05d66..6ba75df 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,4 +4,8 @@ set -e cd "$(dirname "$0")/.." -./node_modules/.bin/eslint --ext ts,js . +echo "==> Running eslint" +ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --ext ts,js . + +echo "==> Running tsc" +./node_modules/.bin/tsc --noEmit diff --git a/scripts/mock b/scripts/mock index fe89a1d..d2814ae 100755 --- a/scripts/mock +++ b/scripts/mock @@ -21,7 +21,7 @@ echo "==> Starting mock server with URL ${URL}" # Run prism mock on the given spec if [ "$1" == "--daemon" ]; then - npm exec --package=@stoplight/prism-cli@~5.8 -- prism mock "$URL" &> .prism.log & + npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log & # Wait for server to come online echo -n "Waiting for server" @@ -37,5 +37,5 @@ if [ "$1" == "--daemon" ]; then echo else - npm exec --package=@stoplight/prism-cli@~5.8 -- prism mock "$URL" + npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" fi diff --git a/scripts/test b/scripts/test index b62a7cc..2049e31 100755 --- a/scripts/test +++ b/scripts/test @@ -52,6 +52,5 @@ else echo fi -# Run tests echo "==> Running tests" ./node_modules/.bin/jest "$@" diff --git a/scripts/utils/check-is-in-git-install.sh b/scripts/utils/check-is-in-git-install.sh index 36bcedc..1354eb4 100755 --- a/scripts/utils/check-is-in-git-install.sh +++ b/scripts/utils/check-is-in-git-install.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/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 diff --git a/scripts/utils/git-swap.sh b/scripts/utils/git-swap.sh new file mode 100755 index 0000000..79d1888 --- /dev/null +++ b/scripts/utils/git-swap.sh @@ -0,0 +1,13 @@ +#!/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/src/_shims/node-runtime.ts b/src/_shims/node-runtime.ts index a9c42eb..ab9f2ab 100644 --- a/src/_shims/node-runtime.ts +++ b/src/_shims/node-runtime.ts @@ -13,9 +13,7 @@ import { Readable } from 'node:stream'; import { type RequestOptions } from '../core'; import { MultipartBody } from './MultipartBody'; import { type Shims } from './registry'; - -// @ts-ignore (this package does not have proper export maps for this export) -import { ReadableStream } from 'web-streams-polyfill/dist/ponyfill.es2018.js'; +import { ReadableStream } from 'node:stream/web'; type FileFromPathOptions = Omit; diff --git a/src/_shims/node-types.d.ts b/src/_shims/node-types.d.ts index b31698f..c159e5f 100644 --- a/src/_shims/node-types.d.ts +++ b/src/_shims/node-types.d.ts @@ -7,7 +7,7 @@ import * as fd from 'formdata-node'; export { type Agent } from 'node:http'; export { type Readable } from 'node:stream'; export { type ReadStream as FsReadStream } from 'node:fs'; -export { ReadableStream } from 'web-streams-polyfill'; +export { ReadableStream } from 'node:stream/web'; export const fetch: typeof nf.default; diff --git a/src/core.ts b/src/core.ts index aa88001..46ea6ac 100644 --- a/src/core.ts +++ b/src/core.ts @@ -18,7 +18,7 @@ import { type HeadersInit, } from './_shims/index'; export { type Response }; -import { isMultipartBody } from './uploads'; +import { BlobLike, isBlobLike, isMultipartBody } from './uploads'; export { maybeMultipartFormRequestOptions, multipartFormRequestOptions, @@ -84,8 +84,10 @@ export class APIPromise extends Promise { }); } - _thenUnwrap(transform: (data: T) => U): APIPromise { - return new APIPromise(this.responsePromise, async (props) => transform(await this.parseResponse(props))); + _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { + return new APIPromise(this.responsePromise, async (props) => + transform(await this.parseResponse(props), props), + ); } /** @@ -161,7 +163,7 @@ export abstract class APIClient { maxRetries = 2, timeout = 60000, // 1 minute httpAgent, - fetch: overridenFetch, + fetch: overriddenFetch, }: { baseURL: string; maxRetries?: number | undefined; @@ -174,7 +176,7 @@ export abstract class APIClient { this.timeout = validatePositiveInteger('timeout', timeout); this.httpAgent = httpAgent; - this.fetch = overridenFetch ?? fetch; + this.fetch = overriddenFetch ?? fetch; } protected authHeaders(opts: FinalRequestOptions): Headers { @@ -235,7 +237,17 @@ export abstract class APIClient { path: string, opts?: PromiseOrValue>, ): APIPromise { - return this.request(Promise.resolve(opts).then((opts) => ({ method, path, ...opts }))); + return this.request( + Promise.resolve(opts).then(async (opts) => { + const body = + opts && isBlobLike(opts?.body) ? new DataView(await opts.body.arrayBuffer()) + : opts?.body instanceof DataView ? opts.body + : opts?.body instanceof ArrayBuffer ? new DataView(opts.body) + : opts && ArrayBuffer.isView(opts?.body) ? new DataView(opts.body.buffer) + : opts?.body; + return { method, path, ...opts, body }; + }), + ); } getAPIList = AbstractPage>( @@ -257,16 +269,23 @@ export abstract class APIClient { const encoded = encoder.encode(body); return encoded.length.toString(); } + } else if (ArrayBuffer.isView(body)) { + return body.byteLength.toString(); } return null; } - buildRequest(options: FinalRequestOptions): { req: RequestInit; url: string; timeout: number } { + buildRequest( + options: FinalRequestOptions, + { retryCount = 0 }: { retryCount?: number } = {}, + ): { req: RequestInit; url: string; timeout: number } { const { method, path, query, headers: headers = {} } = options; const body = - isMultipartBody(options.body) ? options.body.body + ArrayBuffer.isView(options.body) || (options.__binaryRequest && typeof options.body === 'string') ? + options.body + : isMultipartBody(options.body) ? options.body.body : options.body ? JSON.stringify(options.body, null, 2) : null; const contentLength = this.calculateContentLength(body); @@ -292,7 +311,7 @@ export abstract class APIClient { headers[this.idempotencyHeader] = options.idempotencyKey; } - const reqHeaders = this.buildHeaders({ options, headers, contentLength }); + const reqHeaders = this.buildHeaders({ options, headers, contentLength, retryCount }); const req: RequestInit = { method, @@ -311,10 +330,12 @@ export abstract class APIClient { options, headers, contentLength, + retryCount, }: { options: FinalRequestOptions; headers: Record; contentLength: string | null | undefined; + retryCount: number; }): Record { const reqHeaders: Record = {}; if (contentLength) { @@ -330,6 +351,16 @@ export abstract class APIClient { delete reqHeaders['content-type']; } + // Don't set the retry count header if it was already set or removed through default headers or by the + // caller. We check `defaultHeaders` and `headers`, which can contain nulls, instead of `reqHeaders` to + // account for the removal case. + if ( + getHeader(defaultHeaders, 'x-stainless-retry-count') === undefined && + getHeader(headers, 'x-stainless-retry-count') === undefined + ) { + reqHeaders['x-stainless-retry-count'] = String(retryCount); + } + this.validateHeaders(reqHeaders, headers); return reqHeaders; @@ -365,7 +396,7 @@ export abstract class APIClient { error: Object | undefined, message: string | undefined, headers: Headers | undefined, - ) { + ): APIError { return APIError.generate(status, error, message, headers); } @@ -381,13 +412,14 @@ export abstract class APIClient { retriesRemaining: number | null, ): Promise { const options = await optionsInput; + const maxRetries = options.maxRetries ?? this.maxRetries; if (retriesRemaining == null) { - retriesRemaining = options.maxRetries ?? this.maxRetries; + retriesRemaining = maxRetries; } await this.prepareOptions(options); - const { req, url, timeout } = this.buildRequest(options); + const { req, url, timeout } = this.buildRequest(options, { retryCount: maxRetries - retriesRemaining }); await this.prepareRequest(req, { url, options }); @@ -490,20 +522,24 @@ export abstract class APIClient { const timeout = setTimeout(() => controller.abort(), ms); + const fetchOptions = { + signal: controller.signal as any, + ...options, + }; + if (fetchOptions.method) { + // Custom methods like 'patch' need to be uppercased + // See https://github.com/nodejs/undici/issues/2294 + fetchOptions.method = fetchOptions.method.toUpperCase(); + } + return ( - this.getRequestClient() - // use undefined this binding; fetch errors if bound to something else in browser/cloudflare - .fetch.call(undefined, url, { signal: controller.signal as any, ...options }) - .finally(() => { - clearTimeout(timeout); - }) + // use undefined this binding; fetch errors if bound to something else in browser/cloudflare + this.fetch.call(undefined, url, fetchOptions).finally(() => { + clearTimeout(timeout); + }) ); } - protected getRequestClient(): RequestClient { - return { fetch: this.fetch }; - } - private shouldRetry(response: Response): boolean { // Note this is not a standard header. const shouldRetryHeader = response.headers.get('x-should-retry'); @@ -636,9 +672,9 @@ export abstract class AbstractPage implements AsyncIterable { return await this.#client.requestAPIList(this.constructor as any, nextOptions); } - async *iterPages() { + async *iterPages(): AsyncGenerator { // eslint-disable-next-line @typescript-eslint/no-this-alias - let page: AbstractPage = this; + let page: this = this; yield page; while (page.hasNextPage()) { page = await page.getNextPage(); @@ -646,7 +682,7 @@ export abstract class AbstractPage implements AsyncIterable { } } - async *[Symbol.asyncIterator]() { + async *[Symbol.asyncIterator](): AsyncGenerator { for await (const page of this.iterPages()) { for (const item of page.getPaginatedItems()) { yield item; @@ -689,7 +725,7 @@ export class PagePromise< * console.log(item) * } */ - async *[Symbol.asyncIterator]() { + async *[Symbol.asyncIterator](): AsyncGenerator { const page = await this; for await (const item of page) { yield item; @@ -721,7 +757,9 @@ export type Headers = Record; export type DefaultQuery = Record; export type KeysEnum = { [P in keyof Required]: true }; -export type RequestOptions | Readable> = { +export type RequestOptions< + Req = unknown | Record | Readable | BlobLike | ArrayBufferView | ArrayBuffer, +> = { method?: HTTPMethod; path?: string; query?: Req | undefined; @@ -735,6 +773,7 @@ export type RequestOptions | Readable> = signal?: AbortSignal | undefined | null; idempotencyKey?: string; + __binaryRequest?: boolean | undefined; __binaryResponse?: boolean | undefined; }; @@ -755,6 +794,7 @@ const requestOptionsKeys: KeysEnum = { signal: true, idempotencyKey: true, + __binaryRequest: true, __binaryResponse: true, }; @@ -767,10 +807,11 @@ export const isRequestOptions = (obj: unknown): obj is RequestOptions => { ); }; -export type FinalRequestOptions | Readable> = RequestOptions & { - method: HTTPMethod; - path: string; -}; +export type FinalRequestOptions | Readable | DataView> = + RequestOptions & { + method: HTTPMethod; + path: string; + }; declare const Deno: any; declare const EdgeRuntime: any; @@ -939,8 +980,8 @@ export const safeJSON = (text: string) => { } }; -// https://stackoverflow.com/a/19709846 -const startsWithSchemeRegexp = new RegExp('^(?:[a-z]+:)?//', 'i'); +// https://url.spec.whatwg.org/#url-scheme-string +const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i; const isAbsoluteURL = (url: string): boolean => { return startsWithSchemeRegexp.test(url); }; @@ -959,6 +1000,11 @@ const validatePositiveInteger = (name: string, n: unknown): number => { export const castToError = (err: any): Error => { if (err instanceof Error) return err; + if (typeof err === 'object' && err !== null) { + try { + return new Error(JSON.stringify(err)); + } catch {} + } return new Error(err); }; @@ -1096,7 +1142,15 @@ export const isHeadersProtocol = (headers: any): headers is HeadersProtocol => { return typeof headers?.get === 'function'; }; -export const getRequiredHeader = (headers: HeadersLike, header: string): string => { +export const getRequiredHeader = (headers: HeadersLike | Headers, header: string): string => { + const foundHeader = getHeader(headers, header); + if (foundHeader === undefined) { + throw new Error(`Could not find ${header} header`); + } + return foundHeader; +}; + +export const getHeader = (headers: HeadersLike | Headers, header: string): string | undefined => { const lowerCasedHeader = header.toLowerCase(); if (isHeadersProtocol(headers)) { // to deal with the case where the header looks like Stainless-Event-Id @@ -1122,7 +1176,7 @@ export const getRequiredHeader = (headers: HeadersLike, header: string): string } } - throw new Error(`Could not find ${header} header`); + return undefined; }; /** diff --git a/src/error.ts b/src/error.ts index ce3fb95..0f06950 100644 --- a/src/error.ts +++ b/src/error.ts @@ -4,17 +4,19 @@ import { castToError, Headers } from './core'; export class BookletError extends Error {} -export class APIError extends BookletError { - readonly status: number | undefined; - readonly headers: Headers | undefined; - readonly error: Object | undefined; - - constructor( - status: number | undefined, - error: Object | undefined, - message: string | undefined, - headers: Headers | undefined, - ) { +export class APIError< + TStatus extends number | undefined = number | undefined, + THeaders extends Headers | undefined = Headers | undefined, + TError extends Object | undefined = Object | undefined, +> extends BookletError { + /** 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; @@ -47,9 +49,9 @@ export class APIError extends BookletError { errorResponse: Object | undefined, message: string | undefined, headers: Headers | undefined, - ) { - if (!status) { - return new APIConnectionError({ cause: castToError(errorResponse) }); + ): APIError { + if (!status || !headers) { + return new APIConnectionError({ message, cause: castToError(errorResponse) }); } const error = errorResponse as Record; @@ -90,18 +92,14 @@ export class APIError extends BookletError { } } -export class APIUserAbortError extends APIError { - override readonly status: undefined = undefined; - +export class APIUserAbortError extends APIError { constructor({ message }: { message?: string } = {}) { super(undefined, undefined, message || 'Request was aborted.', undefined); } } -export class APIConnectionError extends APIError { - override readonly status: undefined = undefined; - - constructor({ message, cause }: { message?: string; cause?: Error | 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 @@ -115,32 +113,18 @@ export class APIConnectionTimeoutError extends APIConnectionError { } } -export class BadRequestError extends APIError { - override readonly status: 400 = 400; -} +export class BadRequestError extends APIError<400, Headers> {} -export class AuthenticationError extends APIError { - override readonly status: 401 = 401; -} +export class AuthenticationError extends APIError<401, Headers> {} -export class PermissionDeniedError extends APIError { - override readonly status: 403 = 403; -} +export class PermissionDeniedError extends APIError<403, Headers> {} -export class NotFoundError extends APIError { - override readonly status: 404 = 404; -} +export class NotFoundError extends APIError<404, Headers> {} -export class ConflictError extends APIError { - override readonly status: 409 = 409; -} +export class ConflictError extends APIError<409, Headers> {} -export class UnprocessableEntityError extends APIError { - override readonly status: 422 = 422; -} +export class UnprocessableEntityError extends APIError<422, Headers> {} -export class RateLimitError extends APIError { - override readonly status: 429 = 429; -} +export class RateLimitError extends APIError<429, Headers> {} -export class InternalServerError extends APIError {} +export class InternalServerError extends APIError {} diff --git a/src/index.ts b/src/index.ts index 1ecc042..d44607e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,18 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import { type Agent } from './_shims/index'; import * as Core from './core'; import * as Errors from './error'; -import { type Agent } from './_shims/index'; import * as Uploads from './uploads'; -import * as API from 'bklt/resources/index'; +import * as API from './resources/index'; +import { + MemberCreateParams, + MemberListParams, + MemberListResponse, + MemberRetrieveResponse, + MemberUpdateParams, + Members, +} from './resources/members'; export interface ClientOptions { /** @@ -26,7 +34,7 @@ export interface ClientOptions { * 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. */ - timeout?: number; + timeout?: number | undefined; /** * An HTTP agent used to manage HTTP(S) connections. @@ -34,7 +42,7 @@ export interface ClientOptions { * If not provided, an agent will be constructed by default in the Node.js environment, * otherwise no agent is used. */ - httpAgent?: Agent; + httpAgent?: Agent | undefined; /** * Specify a custom `fetch` function implementation. @@ -50,7 +58,7 @@ export interface ClientOptions { * * @default 2 */ - maxRetries?: number; + maxRetries?: number | undefined; /** * Default headers to include with every request to the API. @@ -58,7 +66,7 @@ export interface ClientOptions { * These can be removed in individual requests by explicitly setting the * header to `undefined` or `null` in request options. */ - defaultHeaders?: Core.Headers; + defaultHeaders?: Core.Headers | undefined; /** * Default query parameters to include with every request to the API. @@ -66,10 +74,12 @@ export interface ClientOptions { * These can be removed in individual requests by explicitly setting the * param to `undefined` in request options. */ - defaultQuery?: Core.DefaultQuery; + defaultQuery?: Core.DefaultQuery | undefined; } -/** API Client for interfacing with the Booklet API. */ +/** + * API Client for interfacing with the Booklet API. + */ export class Booklet extends Core.APIClient { apiKey: string; @@ -111,6 +121,7 @@ export class Booklet extends Core.APIClient { maxRetries: options.maxRetries, fetch: options.fetch, }); + this._options = options; this.apiKey = apiKey; @@ -134,6 +145,7 @@ export class Booklet extends Core.APIClient { } static Booklet = this; + static DEFAULT_TIMEOUT = 60000; // 1 minute static BookletError = Errors.BookletError; static APIError = Errors.APIError; @@ -153,7 +165,22 @@ export class Booklet extends Core.APIClient { static fileFromPath = Uploads.fileFromPath; } -export const { +Booklet.Members = Members; +export declare namespace Booklet { + export type RequestOptions = Core.RequestOptions; + + export { + Members as Members, + type MemberRetrieveResponse as MemberRetrieveResponse, + type MemberListResponse as MemberListResponse, + type MemberCreateParams as MemberCreateParams, + type MemberUpdateParams as MemberUpdateParams, + type MemberListParams as MemberListParams, + }; +} + +export { toFile, fileFromPath } from './uploads'; +export { BookletError, APIError, APIConnectionError, @@ -167,20 +194,6 @@ export const { InternalServerError, PermissionDeniedError, UnprocessableEntityError, -} = Errors; - -export import toFile = Uploads.toFile; -export import fileFromPath = Uploads.fileFromPath; - -export namespace Booklet { - export import RequestOptions = Core.RequestOptions; - - export import Members = API.Members; - export import MemberRetrieveResponse = API.MemberRetrieveResponse; - export import MemberListResponse = API.MemberListResponse; - export import MemberCreateParams = API.MemberCreateParams; - export import MemberUpdateParams = API.MemberUpdateParams; - export import MemberListParams = API.MemberListParams; -} +} from './error'; export default Booklet; diff --git a/src/resources/index.ts b/src/resources/index.ts index dd02aea..4e544c6 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -1,10 +1,10 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. export { - MemberRetrieveResponse, - MemberListResponse, - MemberCreateParams, - MemberUpdateParams, - MemberListParams, Members, + type MemberRetrieveResponse, + type MemberListResponse, + type MemberCreateParams, + type MemberUpdateParams, + type MemberListParams, } from './members'; diff --git a/src/resources/members.ts b/src/resources/members.ts index 2e141ca..7889500 100644 --- a/src/resources/members.ts +++ b/src/resources/members.ts @@ -1,9 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import * as Core from 'bklt/core'; -import { APIResource } from 'bklt/resource'; -import { isRequestOptions } from 'bklt/core'; -import * as MembersAPI from 'bklt/resources/members'; +import { APIResource } from '../resource'; +import { isRequestOptions } from '../core'; +import * as Core from '../core'; export class Members extends APIResource { /** @@ -170,10 +169,12 @@ export interface MemberListParams { page?: number; } -export namespace Members { - export import MemberRetrieveResponse = MembersAPI.MemberRetrieveResponse; - export import MemberListResponse = MembersAPI.MemberListResponse; - export import MemberCreateParams = MembersAPI.MemberCreateParams; - export import MemberUpdateParams = MembersAPI.MemberUpdateParams; - export import MemberListParams = MembersAPI.MemberListParams; +export declare namespace Members { + export { + type MemberRetrieveResponse as MemberRetrieveResponse, + type MemberListResponse as MemberListResponse, + type MemberCreateParams as MemberCreateParams, + type MemberUpdateParams as MemberUpdateParams, + type MemberListParams as MemberListParams, + }; } diff --git a/src/uploads.ts b/src/uploads.ts index 081827c..8fd2154 100644 --- a/src/uploads.ts +++ b/src/uploads.ts @@ -107,21 +107,28 @@ export async function toFile( // If it's a promise, resolve it. value = await value; - // Use the file's options if there isn't one provided - options ??= isFileLike(value) ? { lastModified: value.lastModified, type: value.type } : {}; + // If we've been given a `File` we don't need to do anything + if (isFileLike(value)) { + return value; + } if (isResponseLike(value)) { const blob = await value.blob(); name ||= new URL(value.url).pathname.split(/[\\/]/).pop() ?? 'unknown_file'; - return new File([blob as any], name, options); + // we need to convert the `Blob` into an array buffer because the `Blob` class + // that `node-fetch` defines is incompatible with the web standard which results + // in `new File` interpreting it as a string instead of binary data. + const data = isBlobLike(blob) ? [(await blob.arrayBuffer()) as any] : [blob]; + + return new File(data, name, options); } const bits = await getBytes(value); name ||= getName(value) ?? 'unknown_file'; - if (!options.type) { + if (!options?.type) { const type = (bits[0] as any)?.type; if (typeof type === 'string') { options = { ...options, type }; diff --git a/src/version.ts b/src/version.ts index 2c77b51..b0bfd9e 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.0.1-alpha.1'; // x-release-please-version +export const VERSION = '0.1.0-alpha.1'; // x-release-please-version diff --git a/tests/api-resources/members.test.ts b/tests/api-resources/members.test.ts index 8f5841d..dbc9d2b 100644 --- a/tests/api-resources/members.test.ts +++ b/tests/api-resources/members.test.ts @@ -3,14 +3,14 @@ import Booklet from 'bklt'; import { Response } from 'node-fetch'; -const booklet = new Booklet({ +const client = new Booklet({ apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); describe('resource members', () => { test('create: only required params', async () => { - const responsePromise = booklet.members.create({ email: 'string' }); + const responsePromise = client.members.create({ email: 'email' }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -21,20 +21,20 @@ describe('resource members', () => { }); test('create: required and optional params', async () => { - const response = await booklet.members.create({ - email: 'string', - about: 'string', - name: 'string', - permission: 'string', - photo: 'string', - quarantined_at: 'string', + const response = await client.members.create({ + email: 'email', + about: 'about', + name: 'name', + permission: 'permission', + photo: 'photo', + quarantined_at: 'quarantined_at', send_welcome: true, subscribed_at: true, }); }); test('retrieve', async () => { - const responsePromise = booklet.members.retrieve('string'); + const responsePromise = client.members.retrieve('id'); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -46,13 +46,13 @@ describe('resource members', () => { test('retrieve: request options instead of params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(booklet.members.retrieve('string', { path: '/_stainless_unknown_path' })).rejects.toThrow( + await expect(client.members.retrieve('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( Booklet.NotFoundError, ); }); test('update', async () => { - const responsePromise = booklet.members.update('string'); + const responsePromise = client.members.update('id'); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -64,7 +64,7 @@ describe('resource members', () => { test('update: request options instead of params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(booklet.members.update('string', { path: '/_stainless_unknown_path' })).rejects.toThrow( + await expect(client.members.update('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( Booklet.NotFoundError, ); }); @@ -72,16 +72,22 @@ describe('resource members', () => { test('update: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( - booklet.members.update( - 'string', - { about: 'string', locked_at: 'string', name: 'string', photo: 'string', quarantined_at: 'string' }, + client.members.update( + 'id', + { + about: 'about', + locked_at: 'locked_at', + name: 'name', + photo: 'photo', + quarantined_at: 'quarantined_at', + }, { path: '/_stainless_unknown_path' }, ), ).rejects.toThrow(Booklet.NotFoundError); }); test('list', async () => { - const responsePromise = booklet.members.list(); + const responsePromise = client.members.list(); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -93,7 +99,7 @@ describe('resource members', () => { test('list: request options instead of params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect(booklet.members.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( + await expect(client.members.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( Booklet.NotFoundError, ); }); @@ -101,7 +107,7 @@ describe('resource members', () => { 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( - booklet.members.list({ items: 0, page: 0 }, { path: '/_stainless_unknown_path' }), + client.members.list({ items: 0, page: 0 }, { path: '/_stainless_unknown_path' }), ).rejects.toThrow(Booklet.NotFoundError); }); }); diff --git a/tests/index.test.ts b/tests/index.test.ts index 8aa2ac1..d55906e 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -122,6 +122,19 @@ describe('instantiate client', () => { expect(spy).toHaveBeenCalledTimes(1); }); + test('normalized method', async () => { + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + capturedRequest = init; + return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new Booklet({ 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 Booklet({ baseURL: 'http://localhost:5000/custom/path/', apiKey: 'My API Key' }); @@ -177,7 +190,7 @@ describe('instantiate client', () => { expect(client.apiKey).toBe('My API Key'); }); - test('with overriden environment variable arguments', () => { + test('with overridden environment variable arguments', () => { // set options via env var process.env['BOOKLET_API_KEY'] = 'another My API Key'; const client = new Booklet({ apiKey: 'My API Key' }); @@ -241,6 +254,122 @@ describe('retries', () => { expect(count).toEqual(3); }); + test('retry count header', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: RequestInfo, 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 Booklet({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); + + expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); + + expect((capturedRequest!.headers as Headers)['x-stainless-retry-count']).toEqual('2'); + expect(count).toEqual(3); + }); + + test('omit retry count header', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + 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 Booklet({ 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).not.toHaveProperty('x-stainless-retry-count'); + }); + + test('omit retry count header by default', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: RequestInfo, 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 Booklet({ + 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: RequestInfo, 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 Booklet({ 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)['x-stainless-retry-count']).toBe('42'); + }); + test('retry on 429 with retry-after', async () => { let count = 0; const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise => { diff --git a/tests/stringifyQuery.test.ts b/tests/stringifyQuery.test.ts index 221aa01..b101d0e 100644 --- a/tests/stringifyQuery.test.ts +++ b/tests/stringifyQuery.test.ts @@ -1,8 +1,10 @@ -import { APIClient } from 'bklt/core'; +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -const { stringifyQuery } = APIClient.prototype as any; +import { Booklet } from 'bklt'; -describe('APIClient.stringifyQuery', () => { +const { stringifyQuery } = Booklet.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'], @@ -18,6 +20,7 @@ describe('APIClient.stringifyQuery', () => { 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/uploads.test.ts b/tests/uploads.test.ts index 6a5dd7d..bc168e5 100644 --- a/tests/uploads.test.ts +++ b/tests/uploads.test.ts @@ -54,4 +54,12 @@ describe('toFile', () => { 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'); + }); }); diff --git a/tsconfig.deno.json b/tsconfig.deno.json index 2d00bd0..849e070 100644 --- a/tsconfig.deno.json +++ b/tsconfig.deno.json @@ -1,19 +1,14 @@ { "extends": "./tsconfig.json", - "include": ["deno"], + "include": ["dist-deno"], "exclude": [], "compilerOptions": { - "rootDir": "./deno", + "rootDir": "./dist-deno", "lib": ["es2020", "DOM"], - "paths": { - "bklt/_shims/auto/*": ["deno/_shims/auto/*-deno"], - "bklt/*": ["deno/*"], - "bklt": ["deno/index.ts"], - }, "noEmit": true, "declaration": true, "declarationMap": true, - "outDir": "deno", + "outDir": "dist-deno", "pretty": true, "sourceMap": true } diff --git a/tsconfig.json b/tsconfig.json index bd3ecd0..63d87a2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ "paths": { "bklt/_shims/auto/*": ["src/_shims/auto/*-node"], "bklt/*": ["src/*"], - "bklt": ["src/index.ts"], + "bklt": ["src/index.ts"] }, "noEmit": true, @@ -32,6 +32,7 @@ "noUncheckedIndexedAccess": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, + "isolatedModules": false, "skipLibCheck": true } diff --git a/yarn.lock b/yarn.lock index dda4d2e..bb17942 100644 --- a/yarn.lock +++ b/yarn.lock @@ -322,9 +322,9 @@ eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.5.1": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.9.0.tgz#7ccb5f58703fa61ffdcbf39e2c604a109e781162" - integrity sha512-zJmuCWj2VLBt4c25CfBIbMZLGLyhkvs7LznyVX5HfpzeocThgIj5XQK4L+g3U36mMcx8bPMhGyPpwCATamC4jQ== + version "4.11.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.1.tgz#a547badfc719eb3e5f4b556325e542fbe9d7a18f" + integrity sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q== "@eslint-community/regexpp@^4.6.1": version "4.6.2" @@ -759,16 +759,6 @@ dependencies: "@swc/counter" "^0.1.3" -"@ts-morph/common@~0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.20.0.tgz#3f161996b085ba4519731e4d24c35f6cba5b80af" - integrity sha512-7uKjByfbPpwuzkstL3L5MQyuXPSKdoNG93Fmi2JoDcTf3pEP731JdRFAduRVkOs8oqxPsXKA+ScrWkdQ8t/I+Q== - dependencies: - fast-glob "^3.2.12" - minimatch "^7.4.3" - mkdirp "^2.1.6" - path-browserify "^1.0.1" - "@tsconfig/node10@^1.0.7": version "1.0.8" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" @@ -857,9 +847,9 @@ pretty-format "^29.0.0" "@types/json-schema@^7.0.12": - version "7.0.13" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" - integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/node-fetch@^2.6.4": version "2.6.4" @@ -882,9 +872,9 @@ integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== "@types/semver@^7.5.0": - version "7.5.3" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04" - integrity sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw== + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== "@types/stack-utils@^2.0.0": version "2.0.3" @@ -904,15 +894,15 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^6.7.0": - version "6.7.3" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.3.tgz#d98046e9f7102d49a93d944d413c6055c47fafd7" - integrity sha512-vntq452UHNltxsaaN+L9WyuMch8bMd9CqJ3zhzTPXXidwbf5mqqKCVXEuvRZUqLJSTLeWE65lQwyXsRGnXkCTA== + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" + integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== dependencies: "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.7.3" - "@typescript-eslint/type-utils" "6.7.3" - "@typescript-eslint/utils" "6.7.3" - "@typescript-eslint/visitor-keys" "6.7.3" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/type-utils" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" @@ -921,71 +911,72 @@ ts-api-utils "^1.0.1" "@typescript-eslint/parser@^6.7.0": - version "6.7.3" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.3.tgz#aaf40092a32877439e5957e18f2d6a91c82cc2fd" - integrity sha512-TlutE+iep2o7R8Lf+yoer3zU6/0EAUc8QIBB3GYBc1KGz4c4TRm83xwXUZVPlZ6YCLss4r77jbu6j3sendJoiQ== - dependencies: - "@typescript-eslint/scope-manager" "6.7.3" - "@typescript-eslint/types" "6.7.3" - "@typescript-eslint/typescript-estree" "6.7.3" - "@typescript-eslint/visitor-keys" "6.7.3" + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" + integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== + dependencies: + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@6.7.3": - version "6.7.3" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.3.tgz#07e5709c9bdae3eaf216947433ef97b3b8b7d755" - integrity sha512-wOlo0QnEou9cHO2TdkJmzF7DFGvAKEnB82PuPNHpT8ZKKaZu6Bm63ugOTn9fXNJtvuDPanBc78lGUGGytJoVzQ== +"@typescript-eslint/scope-manager@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" + integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== dependencies: - "@typescript-eslint/types" "6.7.3" - "@typescript-eslint/visitor-keys" "6.7.3" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" -"@typescript-eslint/type-utils@6.7.3": - version "6.7.3" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.3.tgz#c2c165c135dda68a5e70074ade183f5ad68f3400" - integrity sha512-Fc68K0aTDrKIBvLnKTZ5Pf3MXK495YErrbHb1R6aTpfK5OdSFj0rVN7ib6Tx6ePrZ2gsjLqr0s98NG7l96KSQw== +"@typescript-eslint/type-utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" + integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== dependencies: - "@typescript-eslint/typescript-estree" "6.7.3" - "@typescript-eslint/utils" "6.7.3" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/utils" "6.21.0" debug "^4.3.4" ts-api-utils "^1.0.1" -"@typescript-eslint/types@6.7.3": - version "6.7.3" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.3.tgz#0402b5628a63f24f2dc9d4a678e9a92cc50ea3e9" - integrity sha512-4g+de6roB2NFcfkZb439tigpAMnvEIg3rIjWQ+EM7IBaYt/CdJt6em9BJ4h4UpdgaBWdmx2iWsafHTrqmgIPNw== +"@typescript-eslint/types@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== -"@typescript-eslint/typescript-estree@6.7.3": - version "6.7.3" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.3.tgz#ec5bb7ab4d3566818abaf0e4a8fa1958561b7279" - integrity sha512-YLQ3tJoS4VxLFYHTw21oe1/vIZPRqAO91z6Uv0Ss2BKm/Ag7/RVQBcXTGcXhgJMdA4U+HrKuY5gWlJlvoaKZ5g== +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" + integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== dependencies: - "@typescript-eslint/types" "6.7.3" - "@typescript-eslint/visitor-keys" "6.7.3" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" + minimatch "9.0.3" semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.7.3": - version "6.7.3" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.3.tgz#96c655816c373135b07282d67407cb577f62e143" - integrity sha512-vzLkVder21GpWRrmSR9JxGZ5+ibIUSudXlW52qeKpzUEQhRSmyZiVDDj3crAth7+5tmN1ulvgKaCU2f/bPRCzg== +"@typescript-eslint/utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" + integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.7.3" - "@typescript-eslint/types" "6.7.3" - "@typescript-eslint/typescript-estree" "6.7.3" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" semver "^7.5.4" -"@typescript-eslint/visitor-keys@6.7.3": - version "6.7.3" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.3.tgz#83809631ca12909bd2083558d2f93f5747deebb2" - integrity sha512-HEVXkU9IB+nk9o63CeICMHxFWbHWr3E1mpilIQBe9+7L/lH97rleFLVtYsfnWB+JVMaiFnEaxvknvmIzX+CqVg== +"@typescript-eslint/visitor-keys@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" + integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== dependencies: - "@typescript-eslint/types" "6.7.3" + "@typescript-eslint/types" "6.21.0" eslint-visitor-keys "^3.4.1" abort-controller@^3.0.0: @@ -1200,12 +1191,12 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +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.0.1" + fill-range "^7.1.1" browserslist@^4.22.2: version "4.22.2" @@ -1314,11 +1305,6 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== -code-block-writer@^12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770" - integrity sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w== - 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" @@ -1384,21 +1370,28 @@ create-require@^1.1.0: integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + 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, debug@^4.3.4: +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: + 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" @@ -1546,12 +1539,7 @@ eslint-scope@^7.2.2: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: - version "3.4.2" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz#8c2095440eca8c933bedcadf16fefa44dbe9ba5f" - integrity sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw== - -eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, 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== @@ -1709,18 +1697,7 @@ fast-glob@^3.2.12: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.2.9: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-glob@^3.3.0: +fast-glob@^3.2.9, fast-glob@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -1742,9 +1719,9 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + 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" @@ -1762,10 +1739,10 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +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" @@ -1959,10 +1936,17 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ignore@^5.2.0, ignore@^5.2.4: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + 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" @@ -2638,11 +2622,11 @@ merge2@^1.3.0, merge2@^1.4.1: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + 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.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.51.0: @@ -2667,6 +2651,13 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -2674,29 +2665,17 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^7.4.3: - version "7.4.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.6.tgz#845d6f254d8f4a5e4fd6baf44d5f10c8448365fb" - integrity sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw== - 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== -mkdirp@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" - integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0: +ms@^2.0.0, 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== @@ -2854,11 +2833,6 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -path-browserify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" - integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -3041,18 +3015,28 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +"safer-buffer@>= 2.1.2 < 3.0.0": + 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, semver@^7.5.4: +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== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -3249,9 +3233,9 @@ tr46@~0.0.3: integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= ts-api-utils@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" - integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== ts-jest@^29.1.0: version "29.1.1" @@ -3267,14 +3251,6 @@ ts-jest@^29.1.0: semver "^7.5.3" yargs-parser "^21.0.1" -ts-morph@^19.0.0: - version "19.0.0" - resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-19.0.0.tgz#43e95fb0156c3fe3c77c814ac26b7d0be2f93169" - integrity sha512-D6qcpiJdn46tUqV45vr5UGM2dnIEuTGNxVhg0sk5NX11orcouwj6i1bMqZIz2mZTZB1Hcgy7C3oEVhAT+f6mbQ== - dependencies: - "@ts-morph/common" "~0.20.0" - code-block-writer "^12.0.0" - ts-node@^10.5.0: version "10.7.0" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" @@ -3412,11 +3388,6 @@ web-streams-polyfill@4.0.0-beta.1: resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz#3b19b9817374b7cee06d374ba7eeb3aeb80e8c95" integrity sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ== -web-streams-polyfill@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" - integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== - webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"