├── Brewfile ├── src ├── resources.ts ├── version.ts ├── _shims │ ├── auto │ │ ├── types.js │ │ ├── types.mjs │ │ ├── runtime.ts │ │ ├── types-deno.ts │ │ ├── types-node.ts │ │ ├── runtime-bun.ts │ │ ├── runtime-deno.ts │ │ ├── runtime-node.ts │ │ └── types.d.ts │ ├── node-types.js │ ├── node-types.mjs │ ├── web-types.js │ ├── web-types.mjs │ ├── manual-types.js │ ├── manual-types.mjs │ ├── MultipartBody.ts │ ├── index.mjs │ ├── manual-types.d.ts │ ├── index.js │ ├── bun-runtime.ts │ ├── node-types.d.ts │ ├── web-types.d.ts │ ├── registry.ts │ ├── README.md │ ├── index-deno.ts │ ├── node-runtime.ts │ ├── index.d.ts │ └── web-runtime.ts ├── resources │ ├── sessions.ts │ ├── sessions │ │ ├── index.ts │ │ ├── captchas.ts │ │ └── files.ts │ ├── index.ts │ ├── files.ts │ ├── extensions.ts │ ├── top-level.ts │ └── credentials.ts ├── lib │ └── .keep ├── resource.ts ├── pagination.ts ├── shims │ ├── node.ts │ └── web.ts ├── error.ts ├── uploads.ts └── index.ts ├── .release-please-manifest.json ├── .prettierignore ├── .prettierrc.json ├── .gitignore ├── tsc-multi.json ├── scripts ├── format ├── lint ├── utils │ ├── git-swap.sh │ ├── fix-index-exports.cjs │ ├── check-is-in-git-install.sh │ ├── make-dist-package-json.cjs │ ├── check-version.cjs │ ├── upload-artifact.sh │ └── postprocess-files.cjs ├── bootstrap ├── mock ├── fast-format ├── test └── build ├── examples └── .keep ├── .stats.yml ├── .eslintrc.js ├── tsconfig.dist-src.json ├── tsconfig.deno.json ├── tsconfig.build.json ├── .devcontainer └── devcontainer.json ├── bin ├── check-release-environment └── publish-npm ├── .github └── workflows │ ├── release-doctor.yml │ ├── publish-npm.yml │ └── ci.yml ├── jest.config.ts ├── tests ├── responses.test.ts ├── stringifyQuery.test.ts ├── form.test.ts ├── api-resources │ ├── sessions │ │ ├── captchas.test.ts │ │ ├── files.test.ts │ │ └── sessions.test.ts │ ├── profiles.test.ts │ ├── top-level.test.ts │ ├── files.test.ts │ ├── credentials.test.ts │ └── extensions.test.ts └── uploads.test.ts ├── tsconfig.json ├── SECURITY.md ├── release-please-config.json ├── CONTRIBUTING.md ├── package.json ├── api.md ├── README.md └── LICENSE /Brewfile: -------------------------------------------------------------------------------- 1 | brew "node" 2 | -------------------------------------------------------------------------------- /src/resources.ts: -------------------------------------------------------------------------------- 1 | export * from './resources/index'; 2 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "0.16.0" 3 | } 4 | -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | export const VERSION = '0.16.0'; // x-release-please-version 2 | -------------------------------------------------------------------------------- /src/_shims/auto/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/auto/types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/node-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/node-types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/web-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/web-types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/manual-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/manual-types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | /ecosystem-tests/*/** 3 | /node_modules 4 | /deno 5 | 6 | # don't format tsc output, will break source maps 7 | /dist 8 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../web-runtime'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/types-deno.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../web-types'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/types-node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../node-types'; 5 | -------------------------------------------------------------------------------- /src/resources/sessions.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | export * from './sessions/index'; 4 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime-bun.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../bun-runtime'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime-deno.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../web-runtime'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime-node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../node-runtime'; 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "experimentalTernaries": true, 4 | "printWidth": 110, 5 | "singleQuote": true, 6 | "trailingComma": "all" 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .prism.log 2 | node_modules 3 | yarn-error.log 4 | codegen.log 5 | Brewfile.lock.json 6 | dist 7 | dist-deno 8 | /*.tgz 9 | .idea/ 10 | .eslintcache 11 | 12 | -------------------------------------------------------------------------------- /tsc-multi.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { "extname": ".js", "module": "commonjs" }, 4 | { "extname": ".mjs", "module": "esnext" } 5 | ], 6 | "projects": ["tsconfig.build.json"] 7 | } 8 | -------------------------------------------------------------------------------- /scripts/format: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | echo "==> Running eslint --fix" 8 | ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --fix --ext ts,js . 9 | -------------------------------------------------------------------------------- /src/lib/.keep: -------------------------------------------------------------------------------- 1 | File generated from our OpenAPI spec by Stainless. 2 | 3 | This directory can be used to store custom files to expand the SDK. 4 | It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. 5 | -------------------------------------------------------------------------------- /examples/.keep: -------------------------------------------------------------------------------- 1 | File generated from our OpenAPI spec by Stainless. 2 | 3 | This directory can be used to store example files demonstrating usage of this SDK. 4 | It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. 5 | -------------------------------------------------------------------------------- /scripts/lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | echo "==> Running eslint" 8 | ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --ext ts,js . 9 | 10 | echo "==> Running tsc" 11 | ./node_modules/.bin/tsc --noEmit 12 | -------------------------------------------------------------------------------- /src/_shims/MultipartBody.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export class MultipartBody { 5 | constructor(public body: any) {} 6 | get [Symbol.toStringTag](): string { 7 | return 'MultipartBody'; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.stats.yml: -------------------------------------------------------------------------------- 1 | configured_endpoints: 36 2 | openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/nen-labs%2Fsteel-aa04a4c92f7068c304082be35f7b4c1474d12772ab9756e0a3819db9f14e1063.yml 3 | openapi_spec_hash: 19b08d835a276c527167b13b86225ae1 4 | config_hash: e49b3f69d57d7ffa0420acf772d5c846 5 | -------------------------------------------------------------------------------- /src/resource.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import type { Steel } from './index'; 4 | 5 | export abstract class APIResource { 6 | protected _client: Steel; 7 | 8 | constructor(client: Steel) { 9 | this._client = client; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], 4 | rules: { 5 | 'no-unused-vars': 'off', 6 | 'prettier/prettier': 'error', 7 | 'unused-imports/no-unused-imports': 'error', 8 | }, 9 | root: true, 10 | }; 11 | -------------------------------------------------------------------------------- /src/_shims/index.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import * as shims from './registry.mjs'; 5 | import * as auto from 'steel-sdk/_shims/auto/runtime'; 6 | export const init = () => { 7 | if (!shims.kind) shims.setShims(auto.getRuntime(), { auto: true }); 8 | }; 9 | export * from './registry.mjs'; 10 | 11 | init(); 12 | -------------------------------------------------------------------------------- /tsconfig.dist-src.json: -------------------------------------------------------------------------------- 1 | { 2 | // this config is included in the published src directory to prevent TS errors 3 | // from appearing when users go to source, and VSCode opens the source .ts file 4 | // via declaration maps 5 | "include": ["index.ts"], 6 | "compilerOptions": { 7 | "target": "es2015", 8 | "lib": ["DOM"], 9 | "moduleResolution": "node" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["dist-deno"], 4 | "exclude": [], 5 | "compilerOptions": { 6 | "rootDir": "./dist-deno", 7 | "lib": ["es2020", "DOM"], 8 | "noEmit": true, 9 | "declaration": true, 10 | "declarationMap": true, 11 | "outDir": "dist-deno", 12 | "pretty": true, 13 | "sourceMap": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/_shims/manual-types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | /** 5 | * Types will get added to this namespace when you import one of the following: 6 | * 7 | * import 'steel-sdk/shims/node' 8 | * import 'steel-sdk/shims/web' 9 | * 10 | * Importing more than one will cause type and runtime errors. 11 | */ 12 | export namespace manual {} 13 | -------------------------------------------------------------------------------- /scripts/utils/git-swap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -exuo pipefail 3 | # the package is published to NPM from ./dist 4 | # we want the final file structure for git installs to match the npm installs, so we 5 | 6 | # delete everything except ./dist and ./node_modules 7 | find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' + 8 | 9 | # move everything from ./dist to . 10 | mv dist/* . 11 | 12 | # delete the now-empty ./dist 13 | rmdir dist 14 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["dist/src"], 4 | "exclude": ["dist/src/_shims/*-deno.ts"], 5 | "compilerOptions": { 6 | "rootDir": "./dist/src", 7 | "paths": { 8 | "steel-sdk/*": ["./dist/src/*"], 9 | "steel-sdk": ["./dist/src/index.ts"] 10 | }, 11 | "noEmit": false, 12 | "declaration": true, 13 | "declarationMap": true, 14 | "outDir": "dist", 15 | "pretty": true, 16 | "sourceMap": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/utils/fix-index-exports.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const indexJs = 5 | process.env['DIST_PATH'] ? 6 | path.resolve(process.env['DIST_PATH'], 'index.js') 7 | : path.resolve(__dirname, '..', '..', 'dist', 'index.js'); 8 | 9 | let before = fs.readFileSync(indexJs, 'utf8'); 10 | let after = before.replace( 11 | /^\s*exports\.default\s*=\s*(\w+)/m, 12 | 'exports = module.exports = $1;\nexports.default = $1', 13 | ); 14 | fs.writeFileSync(indexJs, after, 'utf8'); 15 | -------------------------------------------------------------------------------- /src/_shims/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | const shims = require('./registry'); 5 | const auto = require('steel-sdk/_shims/auto/runtime'); 6 | exports.init = () => { 7 | if (!shims.kind) shims.setShims(auto.getRuntime(), { auto: true }); 8 | }; 9 | for (const property of Object.keys(shims)) { 10 | Object.defineProperty(exports, property, { 11 | get() { 12 | return shims[property]; 13 | }, 14 | }); 15 | } 16 | 17 | exports.init(); 18 | -------------------------------------------------------------------------------- /scripts/utils/check-is-in-git-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Check if you happen to call prepare for a repository that's already in node_modules. 3 | [ "$(basename "$(dirname "$PWD")")" = 'node_modules' ] || 4 | # The name of the containing directory that 'npm` uses, which looks like 5 | # $HOME/.npm/_cacache/git-cloneXXXXXX 6 | [ "$(basename "$(dirname "$PWD")")" = 'tmp' ] || 7 | # The name of the containing directory that 'yarn` uses, which looks like 8 | # $(yarn cache dir)/.tmp/XXXXX 9 | [ "$(basename "$(dirname "$PWD")")" = '.tmp' ] 10 | -------------------------------------------------------------------------------- /src/_shims/bun-runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { type Shims } from './registry'; 5 | import { getRuntime as getWebRuntime } from './web-runtime'; 6 | import { ReadStream as FsReadStream } from 'node:fs'; 7 | 8 | export function getRuntime(): Shims { 9 | const runtime = getWebRuntime(); 10 | function isFsReadStream(value: any): value is FsReadStream { 11 | return value instanceof FsReadStream; 12 | } 13 | return { ...runtime, isFsReadStream }; 14 | } 15 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/debian 3 | { 4 | "name": "Development", 5 | "image": "mcr.microsoft.com/devcontainers/typescript-node:latest", 6 | "features": { 7 | "ghcr.io/devcontainers/features/node:1": {} 8 | }, 9 | "postCreateCommand": "yarn install", 10 | "customizations": { 11 | "vscode": { 12 | "extensions": ["esbenp.prettier-vscode"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /bin/check-release-environment: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | errors=() 4 | 5 | if [ -z "${NPM_TOKEN}" ]; then 6 | errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") 7 | fi 8 | 9 | lenErrors=${#errors[@]} 10 | 11 | if [[ lenErrors -gt 0 ]]; then 12 | echo -e "Found the following errors in the release environment:\n" 13 | 14 | for error in "${errors[@]}"; do 15 | echo -e "- $error\n" 16 | done 17 | 18 | exit 1 19 | fi 20 | 21 | echo "The environment is ready to push releases!" 22 | 23 | -------------------------------------------------------------------------------- /scripts/bootstrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then 8 | brew bundle check >/dev/null 2>&1 || { 9 | echo -n "==> Install Homebrew dependencies? (y/N): " 10 | read -r response 11 | case "$response" in 12 | [yY][eE][sS]|[yY]) 13 | brew bundle 14 | ;; 15 | *) 16 | ;; 17 | esac 18 | echo 19 | } 20 | fi 21 | 22 | echo "==> Installing Node dependencies…" 23 | 24 | PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm") 25 | 26 | $PACKAGE_MANAGER install 27 | -------------------------------------------------------------------------------- /.github/workflows/release-doctor.yml: -------------------------------------------------------------------------------- 1 | name: Release Doctor 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | workflow_dispatch: 7 | 8 | jobs: 9 | release_doctor: 10 | name: release doctor 11 | runs-on: ubuntu-latest 12 | if: github.repository == 'steel-dev/steel-node' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Check release environment 18 | run: | 19 | bash ./bin/check-release-environment 20 | env: 21 | NPM_TOKEN: ${{ secrets.STEEL_NPM_TOKEN || secrets.NPM_TOKEN }} 22 | 23 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { JestConfigWithTsJest } from 'ts-jest'; 2 | 3 | const config: JestConfigWithTsJest = { 4 | preset: 'ts-jest/presets/default-esm', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], 8 | }, 9 | moduleNameMapper: { 10 | '^steel-sdk$': '/src/index.ts', 11 | '^steel-sdk/_shims/auto/(.*)$': '/src/_shims/auto/$1-node', 12 | '^steel-sdk/(.*)$': '/src/$1', 13 | }, 14 | modulePathIgnorePatterns: [ 15 | '/ecosystem-tests/', 16 | '/dist/', 17 | '/deno/', 18 | '/deno_tests/', 19 | ], 20 | testPathIgnorePatterns: ['scripts'], 21 | }; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /scripts/utils/make-dist-package-json.cjs: -------------------------------------------------------------------------------- 1 | const pkgJson = require(process.env['PKG_JSON_PATH'] || '../../package.json'); 2 | 3 | function processExportMap(m) { 4 | for (const key in m) { 5 | const value = m[key]; 6 | if (typeof value === 'string') m[key] = value.replace(/^\.\/dist\//, './'); 7 | else processExportMap(value); 8 | } 9 | } 10 | processExportMap(pkgJson.exports); 11 | 12 | for (const key of ['types', 'main', 'module']) { 13 | if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './'); 14 | } 15 | 16 | delete pkgJson.devDependencies; 17 | delete pkgJson.scripts.prepack; 18 | delete pkgJson.scripts.prepublishOnly; 19 | delete pkgJson.scripts.prepare; 20 | 21 | console.log(JSON.stringify(pkgJson, null, 2)); 22 | -------------------------------------------------------------------------------- /scripts/utils/check-version.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const main = () => { 5 | const pkg = require('../../package.json'); 6 | const version = pkg['version']; 7 | if (!version) throw 'The version property is not set in the package.json file'; 8 | if (typeof version !== 'string') { 9 | throw `Unexpected type for the package.json version field; got ${typeof version}, expected string`; 10 | } 11 | 12 | const versionFile = path.resolve(__dirname, '..', '..', 'src', 'version.ts'); 13 | const contents = fs.readFileSync(versionFile, 'utf8'); 14 | const output = contents.replace(/(export const VERSION = ')(.*)(')/g, `$1${version}$3`); 15 | fs.writeFileSync(versionFile, output); 16 | }; 17 | 18 | if (require.main === module) { 19 | main(); 20 | } 21 | -------------------------------------------------------------------------------- /src/resources/sessions/index.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | export { 4 | Captchas, 5 | type CaptchaSolveImageResponse, 6 | type CaptchaStatusResponse, 7 | type CaptchaSolveImageParams, 8 | } from './captchas'; 9 | export { Files, type FileUploadParams } from './files'; 10 | export { 11 | SessionslistSessionsSessionsCursor, 12 | Sessions, 13 | type Session, 14 | type SessionContext, 15 | type Sessionslist, 16 | type SessionComputerResponse, 17 | type SessionEventsResponse, 18 | type SessionLiveDetailsResponse, 19 | type SessionReleaseResponse, 20 | type SessionReleaseAllResponse, 21 | type SessionCreateParams, 22 | type SessionListParams, 23 | type SessionComputerParams, 24 | type SessionReleaseParams, 25 | type SessionReleaseAllParams, 26 | } from './sessions'; 27 | -------------------------------------------------------------------------------- /scripts/utils/upload-artifact.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -exuo pipefail 3 | 4 | RESPONSE=$(curl -X POST "$URL" \ 5 | -H "Authorization: Bearer $AUTH" \ 6 | -H "Content-Type: application/json") 7 | 8 | SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') 9 | 10 | if [[ "$SIGNED_URL" == "null" ]]; then 11 | echo -e "\033[31mFailed to get signed URL.\033[0m" 12 | exit 1 13 | fi 14 | 15 | TARBALL=$(cd dist && npm pack --silent) 16 | 17 | UPLOAD_RESPONSE=$(curl -v -X PUT \ 18 | -H "Content-Type: application/gzip" \ 19 | --data-binary "@dist/$TARBALL" "$SIGNED_URL" 2>&1) 20 | 21 | if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then 22 | echo -e "\033[32mUploaded build to Stainless storage.\033[0m" 23 | echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/steel-node/$SHA'\033[0m" 24 | else 25 | echo -e "\033[31mFailed to upload artifact.\033[0m" 26 | exit 1 27 | fi 28 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | # This workflow is triggered when a GitHub release is created. 2 | # It can also be run manually to re-publish to NPM in case it failed for some reason. 3 | # You can run this workflow by navigating to https://www.github.com/steel-dev/steel-node/actions/workflows/publish-npm.yml 4 | name: Publish NPM 5 | on: 6 | workflow_dispatch: 7 | 8 | release: 9 | types: [published] 10 | 11 | jobs: 12 | publish: 13 | name: publish 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: '20' 23 | 24 | - name: Install dependencies 25 | run: | 26 | yarn install 27 | 28 | - name: Publish to NPM 29 | run: | 30 | bash ./bin/publish-npm 31 | env: 32 | NPM_TOKEN: ${{ secrets.STEEL_NPM_TOKEN || secrets.NPM_TOKEN }} 33 | -------------------------------------------------------------------------------- /scripts/mock: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | if [[ -n "$1" && "$1" != '--'* ]]; then 8 | URL="$1" 9 | shift 10 | else 11 | URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" 12 | fi 13 | 14 | # Check if the URL is empty 15 | if [ -z "$URL" ]; then 16 | echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" 17 | exit 1 18 | fi 19 | 20 | echo "==> Starting mock server with URL ${URL}" 21 | 22 | # Run prism mock on the given spec 23 | if [ "$1" == "--daemon" ]; then 24 | npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & 25 | 26 | # Wait for server to come online 27 | echo -n "Waiting for server" 28 | while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do 29 | echo -n "." 30 | sleep 0.1 31 | done 32 | 33 | if grep -q "✖ fatal" ".prism.log"; then 34 | cat .prism.log 35 | exit 1 36 | fi 37 | 38 | echo 39 | else 40 | npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" 41 | fi 42 | -------------------------------------------------------------------------------- /tests/responses.test.ts: -------------------------------------------------------------------------------- 1 | import { createResponseHeaders } from 'steel-sdk/core'; 2 | import { Headers } from 'steel-sdk/_shims/index'; 3 | 4 | describe('response parsing', () => { 5 | // TODO: test unicode characters 6 | test('headers are case agnostic', async () => { 7 | const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); 8 | expect(headers['content-type']).toEqual('foo'); 9 | expect(headers['Content-type']).toEqual('foo'); 10 | expect(headers['Content-Type']).toEqual('foo'); 11 | expect(headers['accept']).toEqual('text/plain'); 12 | expect(headers['Accept']).toEqual('text/plain'); 13 | expect(headers['Hello-World']).toBeUndefined(); 14 | }); 15 | 16 | test('duplicate headers are concatenated', () => { 17 | const headers = createResponseHeaders( 18 | new Headers([ 19 | ['Content-Type', 'text/xml'], 20 | ['Content-Type', 'application/json'], 21 | ]), 22 | ); 23 | expect(headers['content-type']).toBe('text/xml, application/json'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/stringifyQuery.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { Steel } from 'steel-sdk'; 4 | 5 | const { stringifyQuery } = Steel.prototype as any; 6 | 7 | describe(stringifyQuery, () => { 8 | for (const [input, expected] of [ 9 | [{ a: '1', b: 2, c: true }, 'a=1&b=2&c=true'], 10 | [{ a: null, b: false, c: undefined }, 'a=&b=false'], 11 | [{ 'a/b': 1.28341 }, `${encodeURIComponent('a/b')}=1.28341`], 12 | [ 13 | { 'a/b': 'c/d', 'e=f': 'g&h' }, 14 | `${encodeURIComponent('a/b')}=${encodeURIComponent('c/d')}&${encodeURIComponent( 15 | 'e=f', 16 | )}=${encodeURIComponent('g&h')}`, 17 | ], 18 | ]) { 19 | it(`${JSON.stringify(input)} -> ${expected}`, () => { 20 | expect(stringifyQuery(input)).toEqual(expected); 21 | }); 22 | } 23 | 24 | for (const value of [[], {}, new Date()]) { 25 | it(`${JSON.stringify(value)} -> `, () => { 26 | expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`); 27 | }); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "tests", "examples"], 3 | "exclude": ["src/_shims/**/*-deno.ts"], 4 | "compilerOptions": { 5 | "target": "es2020", 6 | "lib": ["es2020"], 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "paths": { 11 | "steel-sdk/_shims/auto/*": ["./src/_shims/auto/*-node"], 12 | "steel-sdk/*": ["./src/*"], 13 | "steel-sdk": ["./src/index.ts"] 14 | }, 15 | "noEmit": true, 16 | 17 | "resolveJsonModule": true, 18 | 19 | "forceConsistentCasingInFileNames": true, 20 | 21 | "strict": true, 22 | "noImplicitAny": true, 23 | "strictNullChecks": true, 24 | "strictFunctionTypes": true, 25 | "strictBindCallApply": true, 26 | "strictPropertyInitialization": true, 27 | "noImplicitThis": true, 28 | "noImplicitReturns": true, 29 | "alwaysStrict": true, 30 | "exactOptionalPropertyTypes": true, 31 | "noUncheckedIndexedAccess": true, 32 | "noImplicitOverride": true, 33 | "noPropertyAccessFromIndexSignature": true, 34 | "isolatedModules": false, 35 | 36 | "skipLibCheck": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting Security Issues 4 | 5 | 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. 6 | 7 | To report a security issue, please contact the Stainless team at security@stainlessapi.com. 8 | 9 | ## Responsible Disclosure 10 | 11 | We appreciate the efforts of security researchers and individuals who help us maintain the security of 12 | SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible 13 | disclosure practices by allowing us a reasonable amount of time to investigate and address the issue 14 | before making any information public. 15 | 16 | ## Reporting Non-SDK Related Security Issues 17 | 18 | If you encounter security issues that are not directly related to SDKs but pertain to the services 19 | or products provided by Steel, please follow the respective company's security reporting guidelines. 20 | 21 | ### Steel Terms and Policies 22 | 23 | Please contact team@steel.dev for any questions or concerns regarding the security of our services. 24 | 25 | --- 26 | 27 | Thank you for helping us keep the SDKs and systems they interact with secure. 28 | -------------------------------------------------------------------------------- /scripts/fast-format: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | echo "Script started with $# arguments" 6 | echo "Arguments: $*" 7 | echo "Script location: $(dirname "$0")" 8 | 9 | cd "$(dirname "$0")/.." 10 | echo "Changed to directory: $(pwd)" 11 | 12 | if [ $# -eq 0 ]; then 13 | echo "Usage: $0 [additional-formatter-args...]" 14 | echo "The file should contain one file path per line" 15 | exit 1 16 | fi 17 | 18 | FILE_LIST="$1" 19 | 20 | echo "Looking for file: $FILE_LIST" 21 | 22 | if [ ! -f "$FILE_LIST" ]; then 23 | echo "Error: File '$FILE_LIST' not found" 24 | exit 1 25 | fi 26 | 27 | echo "==> Running eslint --fix" 28 | ESLINT_FILES="$(grep '\.ts$' "$FILE_LIST" || true)" 29 | if ! [ -z "$ESLINT_FILES" ]; then 30 | echo "$ESLINT_FILES" | ESLINT_USE_FLAT_CONFIG="false" xargs ./node_modules/.bin/eslint --cache --fix 31 | fi 32 | 33 | echo "==> Running prettier --write" 34 | # format things eslint didn't 35 | PRETTIER_FILES="$(grep '\.\(js\|json\)$' "$FILE_LIST" || true)" 36 | if ! [ -z "$PRETTIER_FILES" ]; then 37 | echo "$PRETTIER_FILES" | xargs ./node_modules/.bin/prettier \ 38 | --write --cache --cache-strategy metadata --no-error-on-unmatched-pattern \ 39 | '!**/dist' '!**/*.ts' '!**/*.mts' '!**/*.cts' '!**/*.js' '!**/*.mjs' '!**/*.cjs' 40 | fi 41 | -------------------------------------------------------------------------------- /src/_shims/node-types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import * as nf from 'node-fetch'; 5 | import * as fd from 'formdata-node'; 6 | 7 | export { type Agent } from 'node:http'; 8 | export { type Readable } from 'node:stream'; 9 | export { type ReadStream as FsReadStream } from 'node:fs'; 10 | export { ReadableStream } from 'node:stream/web'; 11 | 12 | export const fetch: typeof nf.default; 13 | 14 | export type Request = nf.Request; 15 | export type RequestInfo = nf.RequestInfo; 16 | export type RequestInit = nf.RequestInit; 17 | 18 | export type Response = nf.Response; 19 | export type ResponseInit = nf.ResponseInit; 20 | export type ResponseType = nf.ResponseType; 21 | export type BodyInit = nf.BodyInit; 22 | export type Headers = nf.Headers; 23 | export type HeadersInit = nf.HeadersInit; 24 | 25 | type EndingType = 'native' | 'transparent'; 26 | export interface BlobPropertyBag { 27 | endings?: EndingType; 28 | type?: string; 29 | } 30 | 31 | export interface FilePropertyBag extends BlobPropertyBag { 32 | lastModified?: number; 33 | } 34 | 35 | export type FileFromPathOptions = Omit; 36 | 37 | export type FormData = fd.FormData; 38 | export const FormData: typeof fd.FormData; 39 | export type File = fd.File; 40 | export const File: typeof fd.File; 41 | export type Blob = fd.Blob; 42 | export const Blob: typeof fd.Blob; 43 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | ".": {} 4 | }, 5 | "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", 6 | "include-v-in-tag": true, 7 | "include-component-in-tag": false, 8 | "versioning": "prerelease", 9 | "prerelease": true, 10 | "bump-minor-pre-major": true, 11 | "bump-patch-for-minor-pre-major": false, 12 | "pull-request-header": "Automated Release PR", 13 | "pull-request-title-pattern": "release: ${version}", 14 | "changelog-sections": [ 15 | { 16 | "type": "feat", 17 | "section": "Features" 18 | }, 19 | { 20 | "type": "fix", 21 | "section": "Bug Fixes" 22 | }, 23 | { 24 | "type": "perf", 25 | "section": "Performance Improvements" 26 | }, 27 | { 28 | "type": "revert", 29 | "section": "Reverts" 30 | }, 31 | { 32 | "type": "chore", 33 | "section": "Chores" 34 | }, 35 | { 36 | "type": "docs", 37 | "section": "Documentation" 38 | }, 39 | { 40 | "type": "style", 41 | "section": "Styles" 42 | }, 43 | { 44 | "type": "refactor", 45 | "section": "Refactors" 46 | }, 47 | { 48 | "type": "test", 49 | "section": "Tests", 50 | "hidden": true 51 | }, 52 | { 53 | "type": "build", 54 | "section": "Build System" 55 | }, 56 | { 57 | "type": "ci", 58 | "section": "Continuous Integration", 59 | "hidden": true 60 | } 61 | ], 62 | "release-type": "node", 63 | "extra-files": ["src/version.ts", "README.md"] 64 | } 65 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | RED='\033[0;31m' 8 | GREEN='\033[0;32m' 9 | YELLOW='\033[0;33m' 10 | NC='\033[0m' # No Color 11 | 12 | function prism_is_running() { 13 | curl --silent "http://localhost:4010" >/dev/null 2>&1 14 | } 15 | 16 | kill_server_on_port() { 17 | pids=$(lsof -t -i tcp:"$1" || echo "") 18 | if [ "$pids" != "" ]; then 19 | kill "$pids" 20 | echo "Stopped $pids." 21 | fi 22 | } 23 | 24 | function is_overriding_api_base_url() { 25 | [ -n "$TEST_API_BASE_URL" ] 26 | } 27 | 28 | if ! is_overriding_api_base_url && ! prism_is_running ; then 29 | # When we exit this script, make sure to kill the background mock server process 30 | trap 'kill_server_on_port 4010' EXIT 31 | 32 | # Start the dev server 33 | ./scripts/mock --daemon 34 | fi 35 | 36 | if is_overriding_api_base_url ; then 37 | echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" 38 | echo 39 | elif ! prism_is_running ; then 40 | echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" 41 | echo -e "running against your OpenAPI spec." 42 | echo 43 | echo -e "To run the server, pass in the path or url of your OpenAPI" 44 | echo -e "spec to the prism command:" 45 | echo 46 | echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" 47 | echo 48 | 49 | exit 1 50 | else 51 | echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" 52 | echo 53 | fi 54 | 55 | echo "==> Running tests" 56 | ./node_modules/.bin/jest "$@" 57 | -------------------------------------------------------------------------------- /src/pagination.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { AbstractPage, Response, APIClient, FinalRequestOptions, PageInfo } from './core'; 4 | 5 | export interface SessionsCursorResponse { 6 | sessions: Array; 7 | } 8 | 9 | export interface SessionsCursorParams { 10 | cursorId?: string; 11 | 12 | limit?: number; 13 | } 14 | 15 | export class SessionsCursor 16 | extends AbstractPage 17 | implements SessionsCursorResponse 18 | { 19 | sessions: Array; 20 | 21 | constructor( 22 | client: APIClient, 23 | response: Response, 24 | body: SessionsCursorResponse, 25 | options: FinalRequestOptions, 26 | ) { 27 | super(client, response, body, options); 28 | 29 | this.sessions = body.sessions || []; 30 | } 31 | 32 | getPaginatedItems(): Item[] { 33 | return this.sessions ?? []; 34 | } 35 | 36 | // @deprecated Please use `nextPageInfo()` instead 37 | nextPageParams(): Partial | null { 38 | const info = this.nextPageInfo(); 39 | if (!info) return null; 40 | if ('params' in info) return info.params; 41 | const params = Object.fromEntries(info.url.searchParams); 42 | if (!Object.keys(params).length) return null; 43 | return params; 44 | } 45 | 46 | nextPageInfo(): PageInfo | null { 47 | const sessions = this.getPaginatedItems(); 48 | if (!sessions.length) { 49 | return null; 50 | } 51 | 52 | const id = sessions[sessions.length - 1]?.id; 53 | if (!id) { 54 | return null; 55 | } 56 | 57 | return { params: { cursorId: id } }; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/resources/index.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | export { 4 | Credentials, 5 | type CredentialCreateResponse, 6 | type CredentialUpdateResponse, 7 | type CredentialListResponse, 8 | type CredentialDeleteResponse, 9 | type CredentialCreateParams, 10 | type CredentialUpdateParams, 11 | type CredentialListParams, 12 | type CredentialDeleteParams, 13 | } from './credentials'; 14 | export { 15 | Extensions, 16 | type ExtensionUpdateResponse, 17 | type ExtensionListResponse, 18 | type ExtensionDeleteResponse, 19 | type ExtensionDeleteAllResponse, 20 | type ExtensionDownloadResponse, 21 | type ExtensionUploadResponse, 22 | type ExtensionUpdateParams, 23 | type ExtensionUploadParams, 24 | } from './extensions'; 25 | export { Files, type File, type Fileslist, type FileUploadParams } from './files'; 26 | export { 27 | Profiles, 28 | type ProfileCreateResponse, 29 | type ProfileListResponse, 30 | type ProfileCreateParams, 31 | } from './profiles'; 32 | export { 33 | SessionslistSessionsSessionsCursor, 34 | Sessions, 35 | type Session, 36 | type SessionContext, 37 | type Sessionslist, 38 | type SessionComputerResponse, 39 | type SessionEventsResponse, 40 | type SessionLiveDetailsResponse, 41 | type SessionReleaseResponse, 42 | type SessionReleaseAllResponse, 43 | type SessionCreateParams, 44 | type SessionListParams, 45 | type SessionComputerParams, 46 | type SessionReleaseParams, 47 | type SessionReleaseAllParams, 48 | } from './sessions/sessions'; 49 | export { 50 | type PdfResponse, 51 | type ScrapeResponse, 52 | type ScreenshotResponse, 53 | type PdfParams, 54 | type ScrapeParams, 55 | type ScreenshotParams, 56 | } from './top-level'; 57 | -------------------------------------------------------------------------------- /src/shims/node.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import * as types from '../_shims/node-types'; 3 | import { setShims } from '../_shims/registry'; 4 | import { getRuntime } from '../_shims/node-runtime'; 5 | setShims(getRuntime()); 6 | 7 | declare module '../_shims/manual-types' { 8 | export namespace manual { 9 | // @ts-ignore 10 | export type Agent = types.Agent; 11 | // @ts-ignore 12 | export import fetch = types.fetch; 13 | // @ts-ignore 14 | export type Request = types.Request; 15 | // @ts-ignore 16 | export type RequestInfo = types.RequestInfo; 17 | // @ts-ignore 18 | export type RequestInit = types.RequestInit; 19 | // @ts-ignore 20 | export type Response = types.Response; 21 | // @ts-ignore 22 | export type ResponseInit = types.ResponseInit; 23 | // @ts-ignore 24 | export type ResponseType = types.ResponseType; 25 | // @ts-ignore 26 | export type BodyInit = types.BodyInit; 27 | // @ts-ignore 28 | export type Headers = types.Headers; 29 | // @ts-ignore 30 | export type HeadersInit = types.HeadersInit; 31 | // @ts-ignore 32 | export type BlobPropertyBag = types.BlobPropertyBag; 33 | // @ts-ignore 34 | export type FilePropertyBag = types.FilePropertyBag; 35 | // @ts-ignore 36 | export type FileFromPathOptions = types.FileFromPathOptions; 37 | // @ts-ignore 38 | export import FormData = types.FormData; 39 | // @ts-ignore 40 | export import File = types.File; 41 | // @ts-ignore 42 | export import Blob = types.Blob; 43 | // @ts-ignore 44 | export type Readable = types.Readable; 45 | // @ts-ignore 46 | export type FsReadStream = types.FsReadStream; 47 | // @ts-ignore 48 | export import ReadableStream = types.ReadableStream; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/shims/web.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import * as types from '../_shims/web-types'; 3 | import { setShims } from '../_shims/registry'; 4 | import { getRuntime } from '../_shims/web-runtime'; 5 | setShims(getRuntime({ manuallyImported: true })); 6 | 7 | declare module '../_shims/manual-types' { 8 | export namespace manual { 9 | // @ts-ignore 10 | export type Agent = types.Agent; 11 | // @ts-ignore 12 | export import fetch = types.fetch; 13 | // @ts-ignore 14 | export type Request = types.Request; 15 | // @ts-ignore 16 | export type RequestInfo = types.RequestInfo; 17 | // @ts-ignore 18 | export type RequestInit = types.RequestInit; 19 | // @ts-ignore 20 | export type Response = types.Response; 21 | // @ts-ignore 22 | export type ResponseInit = types.ResponseInit; 23 | // @ts-ignore 24 | export type ResponseType = types.ResponseType; 25 | // @ts-ignore 26 | export type BodyInit = types.BodyInit; 27 | // @ts-ignore 28 | export type Headers = types.Headers; 29 | // @ts-ignore 30 | export type HeadersInit = types.HeadersInit; 31 | // @ts-ignore 32 | export type BlobPropertyBag = types.BlobPropertyBag; 33 | // @ts-ignore 34 | export type FilePropertyBag = types.FilePropertyBag; 35 | // @ts-ignore 36 | export type FileFromPathOptions = types.FileFromPathOptions; 37 | // @ts-ignore 38 | export import FormData = types.FormData; 39 | // @ts-ignore 40 | export import File = types.File; 41 | // @ts-ignore 42 | export import Blob = types.Blob; 43 | // @ts-ignore 44 | export type Readable = types.Readable; 45 | // @ts-ignore 46 | export type FsReadStream = types.FsReadStream; 47 | // @ts-ignore 48 | export import ReadableStream = types.ReadableStream; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /bin/publish-npm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" 6 | 7 | yarn build 8 | cd dist 9 | 10 | # Get package name and version from package.json 11 | PACKAGE_NAME="$(jq -r -e '.name' ./package.json)" 12 | VERSION="$(jq -r -e '.version' ./package.json)" 13 | 14 | # Get latest version from npm 15 | # 16 | # If the package doesn't exist, npm will return: 17 | # { 18 | # "error": { 19 | # "code": "E404", 20 | # "summary": "Unpublished on 2025-06-05T09:54:53.528Z", 21 | # "detail": "'the_package' is not in this registry..." 22 | # } 23 | # } 24 | NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>/dev/null || true)" 25 | 26 | # Check if we got an E404 error 27 | if echo "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then 28 | # Package doesn't exist yet, no last version 29 | LAST_VERSION="" 30 | elif echo "$NPM_INFO" | jq -e '.error' > /dev/null 2>&1; then 31 | # Report other errors 32 | echo "ERROR: npm returned unexpected data:" 33 | echo "$NPM_INFO" 34 | exit 1 35 | else 36 | # Success - get the version 37 | LAST_VERSION=$(echo "$NPM_INFO" | jq -r '.') # strip quotes 38 | fi 39 | 40 | # Check if current version is pre-release (e.g. alpha / beta / rc) 41 | CURRENT_IS_PRERELEASE=false 42 | if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then 43 | CURRENT_IS_PRERELEASE=true 44 | CURRENT_TAG="${BASH_REMATCH[1]}" 45 | fi 46 | 47 | # Check if last version is a stable release 48 | LAST_IS_STABLE_RELEASE=true 49 | if [[ -z "$LAST_VERSION" || "$LAST_VERSION" =~ -([a-zA-Z]+) ]]; then 50 | LAST_IS_STABLE_RELEASE=false 51 | fi 52 | 53 | # Use a corresponding alpha/beta tag if there already is a stable release and we're publishing a prerelease. 54 | if $CURRENT_IS_PRERELEASE && $LAST_IS_STABLE_RELEASE; then 55 | TAG="$CURRENT_TAG" 56 | else 57 | TAG="latest" 58 | fi 59 | 60 | # Publish with the appropriate tag 61 | yarn publish --tag "$TAG" 62 | -------------------------------------------------------------------------------- /tests/form.test.ts: -------------------------------------------------------------------------------- 1 | import { multipartFormRequestOptions, createForm } from 'steel-sdk/core'; 2 | import { Blob } from 'steel-sdk/_shims/index'; 3 | import { toFile } from 'steel-sdk'; 4 | 5 | describe('form data validation', () => { 6 | test('valid values do not error', async () => { 7 | await multipartFormRequestOptions({ 8 | body: { 9 | foo: 'foo', 10 | string: 1, 11 | bool: true, 12 | file: await toFile(Buffer.from('some-content')), 13 | blob: new Blob(['Some content'], { type: 'text/plain' }), 14 | }, 15 | }); 16 | }); 17 | 18 | test('null', async () => { 19 | await expect(() => 20 | multipartFormRequestOptions({ 21 | body: { 22 | null: null, 23 | }, 24 | }), 25 | ).rejects.toThrow(TypeError); 26 | }); 27 | 28 | test('undefined is stripped', async () => { 29 | const form = await createForm({ 30 | foo: undefined, 31 | bar: 'baz', 32 | }); 33 | expect(form.has('foo')).toBe(false); 34 | expect(form.get('bar')).toBe('baz'); 35 | }); 36 | 37 | test('nested undefined property is stripped', async () => { 38 | const form = await createForm({ 39 | bar: { 40 | baz: undefined, 41 | }, 42 | }); 43 | expect(Array.from(form.entries())).toEqual([]); 44 | 45 | const form2 = await createForm({ 46 | bar: { 47 | foo: 'string', 48 | baz: undefined, 49 | }, 50 | }); 51 | expect(Array.from(form2.entries())).toEqual([['bar[foo]', 'string']]); 52 | }); 53 | 54 | test('nested undefined array item is stripped', async () => { 55 | const form = await createForm({ 56 | bar: [undefined, undefined], 57 | }); 58 | expect(Array.from(form.entries())).toEqual([]); 59 | 60 | const form2 = await createForm({ 61 | bar: [undefined, 'foo'], 62 | }); 63 | expect(Array.from(form2.entries())).toEqual([['bar[]', 'foo']]); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | node scripts/utils/check-version.cjs 8 | 9 | # Build into dist and will publish the package from there, 10 | # so that src/resources/foo.ts becomes /resources/foo.js 11 | # This way importing from `"steel-sdk/resources/foo"` works 12 | # even with `"moduleResolution": "node"` 13 | 14 | rm -rf dist; mkdir dist 15 | # Copy src to dist/src and build from dist/src into dist, so that 16 | # the source map for index.js.map will refer to ./src/index.ts etc 17 | cp -rp src README.md dist 18 | rm dist/src/_shims/*-deno.ts dist/src/_shims/auto/*-deno.ts 19 | for file in LICENSE CHANGELOG.md; do 20 | if [ -e "${file}" ]; then cp "${file}" dist; fi 21 | done 22 | if [ -e "bin/cli" ]; then 23 | mkdir dist/bin 24 | cp -p "bin/cli" dist/bin/; 25 | fi 26 | # this converts the export map paths for the dist directory 27 | # and does a few other minor things 28 | node scripts/utils/make-dist-package-json.cjs > dist/package.json 29 | 30 | # build to .js/.mjs/.d.ts files 31 | ./node_modules/.bin/tsc-multi 32 | # copy over handwritten .js/.mjs/.d.ts files 33 | cp src/_shims/*.{d.ts,js,mjs,md} dist/_shims 34 | cp src/_shims/auto/*.{d.ts,js,mjs} dist/_shims/auto 35 | # we need to add exports = module.exports = Steel to index.js; 36 | # No way to get that from index.ts because it would cause compile errors 37 | # when building .mjs 38 | node scripts/utils/fix-index-exports.cjs 39 | # with "moduleResolution": "nodenext", if ESM resolves to index.d.ts, 40 | # it'll have TS errors on the default import. But if it resolves to 41 | # index.d.mts the default import will work (even though both files have 42 | # the same export default statement) 43 | cp dist/index.d.ts dist/index.d.mts 44 | cp tsconfig.dist-src.json dist/src/tsconfig.json 45 | 46 | node scripts/utils/postprocess-files.cjs 47 | 48 | # make sure that nothing crashes when we require the output CJS or 49 | # import the output ESM 50 | (cd dist && node -e 'require("steel-sdk")') 51 | (cd dist && node -e 'import("steel-sdk")' --input-type=module) 52 | 53 | if [ -e ./scripts/build-deno ] 54 | then 55 | ./scripts/build-deno 56 | fi 57 | -------------------------------------------------------------------------------- /tests/api-resources/sessions/captchas.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Steel from 'steel-sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Steel({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); 7 | 8 | describe('resource captchas', () => { 9 | test('solveImage: only required params', async () => { 10 | const responsePromise = client.sessions.captchas.solveImage('sessionId', { 11 | imageXPath: 'imageXPath', 12 | inputXPath: 'inputXPath', 13 | }); 14 | const rawResponse = await responsePromise.asResponse(); 15 | expect(rawResponse).toBeInstanceOf(Response); 16 | const response = await responsePromise; 17 | expect(response).not.toBeInstanceOf(Response); 18 | const dataAndResponse = await responsePromise.withResponse(); 19 | expect(dataAndResponse.data).toBe(response); 20 | expect(dataAndResponse.response).toBe(rawResponse); 21 | }); 22 | 23 | test('solveImage: required and optional params', async () => { 24 | const response = await client.sessions.captchas.solveImage('sessionId', { 25 | imageXPath: 'imageXPath', 26 | inputXPath: 'inputXPath', 27 | url: 'url', 28 | }); 29 | }); 30 | 31 | test('status', async () => { 32 | const responsePromise = client.sessions.captchas.status('sessionId'); 33 | const rawResponse = await responsePromise.asResponse(); 34 | expect(rawResponse).toBeInstanceOf(Response); 35 | const response = await responsePromise; 36 | expect(response).not.toBeInstanceOf(Response); 37 | const dataAndResponse = await responsePromise.withResponse(); 38 | expect(dataAndResponse.data).toBe(response); 39 | expect(dataAndResponse.response).toBe(rawResponse); 40 | }); 41 | 42 | test('status: request options instead of params are passed correctly', async () => { 43 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 44 | await expect( 45 | client.sessions.captchas.status('sessionId', { path: '/_stainless_unknown_path' }), 46 | ).rejects.toThrow(Steel.NotFoundError); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/uploads.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { toFile, type ResponseLike } from 'steel-sdk/uploads'; 3 | import { File } from 'steel-sdk/_shims/index'; 4 | 5 | class MyClass { 6 | name: string = 'foo'; 7 | } 8 | 9 | function mockResponse({ url, content }: { url: string; content?: Blob }): ResponseLike { 10 | return { 11 | url, 12 | blob: async () => content as any, 13 | }; 14 | } 15 | 16 | describe('toFile', () => { 17 | it('throws a helpful error for mismatched types', async () => { 18 | await expect( 19 | // @ts-expect-error intentionally mismatched type 20 | toFile({ foo: 'string' }), 21 | ).rejects.toThrowErrorMatchingInlineSnapshot( 22 | `"Unexpected data type: object; constructor: Object; props: ["foo"]"`, 23 | ); 24 | 25 | await expect( 26 | // @ts-expect-error intentionally mismatched type 27 | toFile(new MyClass()), 28 | ).rejects.toThrowErrorMatchingInlineSnapshot( 29 | `"Unexpected data type: object; constructor: MyClass; props: ["name"]"`, 30 | ); 31 | }); 32 | 33 | it('disallows string at the type-level', async () => { 34 | // @ts-expect-error we intentionally do not type support for `string` 35 | // to help people avoid passing a file path 36 | const file = await toFile('contents'); 37 | expect(file.text()).resolves.toEqual('contents'); 38 | }); 39 | 40 | it('extracts a file name from a Response', async () => { 41 | const response = mockResponse({ url: 'https://example.com/my/audio.mp3' }); 42 | const file = await toFile(response); 43 | expect(file.name).toEqual('audio.mp3'); 44 | }); 45 | 46 | it('extracts a file name from a File', async () => { 47 | const input = new File(['foo'], 'input.jsonl'); 48 | const file = await toFile(input); 49 | expect(file.name).toEqual('input.jsonl'); 50 | }); 51 | 52 | it('extracts a file name from a ReadStream', async () => { 53 | const input = fs.createReadStream('tests/uploads.test.ts'); 54 | const file = await toFile(input); 55 | expect(file.name).toEqual('uploads.test.ts'); 56 | }); 57 | 58 | it('does not copy File objects', async () => { 59 | const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); 60 | const file = await toFile(input); 61 | expect(file).toBe(input); 62 | expect(file.name).toEqual('input.jsonl'); 63 | expect(file.type).toBe('jsonl'); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/_shims/web-types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export type Agent = any; 5 | 6 | declare const _fetch: typeof fetch; 7 | export { _fetch as fetch }; 8 | 9 | type _Request = Request; 10 | export { _Request as Request }; 11 | 12 | type _RequestInfo = RequestInfo; 13 | export { type _RequestInfo as RequestInfo }; 14 | 15 | type _RequestInit = RequestInit; 16 | export { type _RequestInit as RequestInit }; 17 | 18 | type _Response = Response; 19 | export { _Response as Response }; 20 | 21 | type _ResponseInit = ResponseInit; 22 | export { type _ResponseInit as ResponseInit }; 23 | 24 | type _ResponseType = ResponseType; 25 | export { type _ResponseType as ResponseType }; 26 | 27 | type _BodyInit = BodyInit; 28 | export { type _BodyInit as BodyInit }; 29 | 30 | type _Headers = Headers; 31 | export { _Headers as Headers }; 32 | 33 | type _HeadersInit = HeadersInit; 34 | export { type _HeadersInit as HeadersInit }; 35 | 36 | type EndingType = 'native' | 'transparent'; 37 | 38 | export interface BlobPropertyBag { 39 | endings?: EndingType; 40 | type?: string; 41 | } 42 | 43 | export interface FilePropertyBag extends BlobPropertyBag { 44 | lastModified?: number; 45 | } 46 | 47 | export type FileFromPathOptions = Omit; 48 | 49 | type _FormData = FormData; 50 | declare const _FormData: typeof FormData; 51 | export { _FormData as FormData }; 52 | 53 | type _File = File; 54 | declare const _File: typeof File; 55 | export { _File as File }; 56 | 57 | type _Blob = Blob; 58 | declare const _Blob: typeof Blob; 59 | export { _Blob as Blob }; 60 | 61 | export declare class Readable { 62 | readable: boolean; 63 | readonly readableEnded: boolean; 64 | readonly readableFlowing: boolean | null; 65 | readonly readableHighWaterMark: number; 66 | readonly readableLength: number; 67 | readonly readableObjectMode: boolean; 68 | destroyed: boolean; 69 | read(size?: number): any; 70 | pause(): this; 71 | resume(): this; 72 | isPaused(): boolean; 73 | destroy(error?: Error): this; 74 | [Symbol.asyncIterator](): AsyncIterableIterator; 75 | } 76 | 77 | export declare class FsReadStream extends Readable { 78 | path: {}; // node type is string | Buffer 79 | } 80 | 81 | type _ReadableStream = ReadableStream; 82 | declare const _ReadableStream: typeof ReadableStream; 83 | export { _ReadableStream as ReadableStream }; 84 | -------------------------------------------------------------------------------- /src/resources/sessions/captchas.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../../resource'; 4 | import * as Core from '../../core'; 5 | 6 | export class Captchas extends APIResource { 7 | /** 8 | * Solves an image captcha using XPath selectors 9 | */ 10 | solveImage( 11 | sessionId: string, 12 | body: CaptchaSolveImageParams, 13 | options?: Core.RequestOptions, 14 | ): Core.APIPromise { 15 | return this._client.post(`/v1/sessions/${sessionId}/captchas/solve-image`, { body, ...options }); 16 | } 17 | 18 | /** 19 | * Gets the current captcha status for a session 20 | */ 21 | status(sessionId: string, options?: Core.RequestOptions): Core.APIPromise { 22 | return this._client.get(`/v1/sessions/${sessionId}/captchas/status`, options); 23 | } 24 | } 25 | 26 | export interface CaptchaSolveImageResponse { 27 | /** 28 | * Whether the action was successful 29 | */ 30 | success: boolean; 31 | 32 | /** 33 | * Response message 34 | */ 35 | message?: string; 36 | } 37 | 38 | export type CaptchaStatusResponse = Array; 39 | 40 | export namespace CaptchaStatusResponse { 41 | export interface CaptchaStatusResponseItem { 42 | /** 43 | * Whether a captcha is currently being solved 44 | */ 45 | isSolvingCaptcha: boolean; 46 | 47 | /** 48 | * The page ID where the captcha is located 49 | */ 50 | pageId: string; 51 | 52 | /** 53 | * Array of captcha tasks 54 | */ 55 | tasks: Array; 56 | 57 | /** 58 | * The URL where the captcha is located 59 | */ 60 | url: string; 61 | 62 | /** 63 | * Timestamp when the state was created 64 | */ 65 | created?: number; 66 | 67 | /** 68 | * Timestamp when the state was last updated 69 | */ 70 | lastUpdated?: number; 71 | } 72 | } 73 | 74 | export interface CaptchaSolveImageParams { 75 | /** 76 | * XPath to the captcha image element 77 | */ 78 | imageXPath: string; 79 | 80 | /** 81 | * XPath to the captcha input element 82 | */ 83 | inputXPath: string; 84 | 85 | /** 86 | * URL where the captcha is located. Defaults to the current page URL 87 | */ 88 | url?: string; 89 | } 90 | 91 | export declare namespace Captchas { 92 | export { 93 | type CaptchaSolveImageResponse as CaptchaSolveImageResponse, 94 | type CaptchaStatusResponse as CaptchaStatusResponse, 95 | type CaptchaSolveImageParams as CaptchaSolveImageParams, 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /tests/api-resources/profiles.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Steel from 'steel-sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Steel({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); 7 | 8 | describe('resource profiles', () => { 9 | test('create', async () => { 10 | const responsePromise = client.profiles.create(); 11 | const rawResponse = await responsePromise.asResponse(); 12 | expect(rawResponse).toBeInstanceOf(Response); 13 | const response = await responsePromise; 14 | expect(response).not.toBeInstanceOf(Response); 15 | const dataAndResponse = await responsePromise.withResponse(); 16 | expect(dataAndResponse.data).toBe(response); 17 | expect(dataAndResponse.response).toBe(rawResponse); 18 | }); 19 | 20 | test('create: request options instead of params are passed correctly', async () => { 21 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 22 | await expect(client.profiles.create({ path: '/_stainless_unknown_path' })).rejects.toThrow( 23 | Steel.NotFoundError, 24 | ); 25 | }); 26 | 27 | test('create: request options and params are passed correctly', async () => { 28 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 29 | await expect( 30 | client.profiles.create( 31 | { 32 | dimensions: { height: 0, width: 0 }, 33 | proxyUrl: 'https://example.com', 34 | userAgent: 'userAgent', 35 | userDataDir: {}, 36 | }, 37 | { path: '/_stainless_unknown_path' }, 38 | ), 39 | ).rejects.toThrow(Steel.NotFoundError); 40 | }); 41 | 42 | test('list', async () => { 43 | const responsePromise = client.profiles.list(); 44 | const rawResponse = await responsePromise.asResponse(); 45 | expect(rawResponse).toBeInstanceOf(Response); 46 | const response = await responsePromise; 47 | expect(response).not.toBeInstanceOf(Response); 48 | const dataAndResponse = await responsePromise.withResponse(); 49 | expect(dataAndResponse.data).toBe(response); 50 | expect(dataAndResponse.response).toBe(rawResponse); 51 | }); 52 | 53 | test('list: request options instead of params are passed correctly', async () => { 54 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 55 | await expect(client.profiles.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( 56 | Steel.NotFoundError, 57 | ); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/_shims/registry.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { type RequestOptions } from '../core'; 5 | 6 | export interface Shims { 7 | kind: string; 8 | fetch: any; 9 | Request: any; 10 | Response: any; 11 | Headers: any; 12 | FormData: any; 13 | Blob: any; 14 | File: any; 15 | ReadableStream: any; 16 | getMultipartRequestOptions: >( 17 | form: Shims['FormData'], 18 | opts: RequestOptions, 19 | ) => Promise>; 20 | getDefaultAgent: (url: string) => any; 21 | fileFromPath: 22 | | ((path: string, filename?: string, options?: {}) => Promise) 23 | | ((path: string, options?: {}) => Promise); 24 | isFsReadStream: (value: any) => boolean; 25 | } 26 | 27 | export let auto = false; 28 | export let kind: Shims['kind'] | undefined = undefined; 29 | export let fetch: Shims['fetch'] | undefined = undefined; 30 | export let Request: Shims['Request'] | undefined = undefined; 31 | export let Response: Shims['Response'] | undefined = undefined; 32 | export let Headers: Shims['Headers'] | undefined = undefined; 33 | export let FormData: Shims['FormData'] | undefined = undefined; 34 | export let Blob: Shims['Blob'] | undefined = undefined; 35 | export let File: Shims['File'] | undefined = undefined; 36 | export let ReadableStream: Shims['ReadableStream'] | undefined = undefined; 37 | export let getMultipartRequestOptions: Shims['getMultipartRequestOptions'] | undefined = undefined; 38 | export let getDefaultAgent: Shims['getDefaultAgent'] | undefined = undefined; 39 | export let fileFromPath: Shims['fileFromPath'] | undefined = undefined; 40 | export let isFsReadStream: Shims['isFsReadStream'] | undefined = undefined; 41 | 42 | export function setShims(shims: Shims, options: { auto: boolean } = { auto: false }) { 43 | if (auto) { 44 | throw new Error( 45 | `you must \`import 'steel-sdk/shims/${shims.kind}'\` before importing anything else from steel-sdk`, 46 | ); 47 | } 48 | if (kind) { 49 | throw new Error( 50 | `can't \`import 'steel-sdk/shims/${shims.kind}'\` after \`import 'steel-sdk/shims/${kind}'\``, 51 | ); 52 | } 53 | auto = options.auto; 54 | kind = shims.kind; 55 | fetch = shims.fetch; 56 | Request = shims.Request; 57 | Response = shims.Response; 58 | Headers = shims.Headers; 59 | FormData = shims.FormData; 60 | Blob = shims.Blob; 61 | File = shims.File; 62 | ReadableStream = shims.ReadableStream; 63 | getMultipartRequestOptions = shims.getMultipartRequestOptions; 64 | getDefaultAgent = shims.getDefaultAgent; 65 | fileFromPath = shims.fileFromPath; 66 | isFsReadStream = shims.isFsReadStream; 67 | } 68 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches-ignore: 5 | - 'generated' 6 | - 'codegen/**' 7 | - 'integrated/**' 8 | - 'stl-preview-head/**' 9 | - 'stl-preview-base/**' 10 | pull_request: 11 | branches-ignore: 12 | - 'stl-preview-head/**' 13 | - 'stl-preview-base/**' 14 | 15 | jobs: 16 | lint: 17 | timeout-minutes: 10 18 | name: lint 19 | runs-on: ${{ github.repository == 'stainless-sdks/steel-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} 20 | if: github.event_name == 'push' || github.event.pull_request.head.repo.fork 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: Set up Node 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: '18' 28 | 29 | - name: Bootstrap 30 | run: ./scripts/bootstrap 31 | 32 | - name: Check types 33 | run: ./scripts/lint 34 | 35 | build: 36 | timeout-minutes: 5 37 | name: build 38 | runs-on: ${{ github.repository == 'stainless-sdks/steel-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} 39 | if: github.event_name == 'push' || github.event.pull_request.head.repo.fork 40 | permissions: 41 | contents: read 42 | id-token: write 43 | steps: 44 | - uses: actions/checkout@v4 45 | 46 | - name: Set up Node 47 | uses: actions/setup-node@v4 48 | with: 49 | node-version: '18' 50 | 51 | - name: Bootstrap 52 | run: ./scripts/bootstrap 53 | 54 | - name: Check build 55 | run: ./scripts/build 56 | 57 | - name: Get GitHub OIDC Token 58 | if: github.repository == 'stainless-sdks/steel-node' 59 | id: github-oidc 60 | uses: actions/github-script@v6 61 | with: 62 | script: core.setOutput('github_token', await core.getIDToken()); 63 | 64 | - name: Upload tarball 65 | if: github.repository == 'stainless-sdks/steel-node' 66 | env: 67 | URL: https://pkg.stainless.com/s 68 | AUTH: ${{ steps.github-oidc.outputs.github_token }} 69 | SHA: ${{ github.sha }} 70 | run: ./scripts/utils/upload-artifact.sh 71 | test: 72 | timeout-minutes: 10 73 | name: test 74 | runs-on: ${{ github.repository == 'stainless-sdks/steel-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} 75 | if: github.event_name == 'push' || github.event.pull_request.head.repo.fork 76 | steps: 77 | - uses: actions/checkout@v4 78 | 79 | - name: Set up Node 80 | uses: actions/setup-node@v4 81 | with: 82 | node-version: '20' 83 | 84 | - name: Bootstrap 85 | run: ./scripts/bootstrap 86 | 87 | - name: Run tests 88 | run: ./scripts/test 89 | -------------------------------------------------------------------------------- /src/_shims/README.md: -------------------------------------------------------------------------------- 1 | # 👋 Wondering what everything in here does? 2 | 3 | `steel-sdk` supports a wide variety of runtime environments like Node.js, Deno, Bun, browsers, and various 4 | edge runtimes, as well as both CommonJS (CJS) and EcmaScript Modules (ESM). 5 | 6 | To do this, `steel-sdk` provides shims for either using `node-fetch` when in Node (because `fetch` is still experimental there) or the global `fetch` API built into the environment when not in Node. 7 | 8 | It uses [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) to 9 | automatically select the correct shims for each environment. However, conditional exports are a fairly new 10 | feature and not supported everywhere. For instance, the TypeScript `"moduleResolution": "node"` 11 | 12 | setting doesn't consult the `exports` map, compared to `"moduleResolution": "nodeNext"`, which does. 13 | Unfortunately that's still the default setting, and it can result in errors like 14 | getting the wrong raw `Response` type from `.asResponse()`, for example. 15 | 16 | The user can work around these issues by manually importing one of: 17 | 18 | - `import 'steel-sdk/shims/node'` 19 | - `import 'steel-sdk/shims/web'` 20 | 21 | All of the code here in `_shims` handles selecting the automatic default shims or manual overrides. 22 | 23 | ### How it works - Runtime 24 | 25 | Runtime shims get installed by calling `setShims` exported by `steel-sdk/_shims/registry`. 26 | 27 | Manually importing `steel-sdk/shims/node` or `steel-sdk/shims/web`, calls `setShims` with the respective runtime shims. 28 | 29 | All client code imports shims from `steel-sdk/_shims/index`, which: 30 | 31 | - checks if shims have been set manually 32 | - if not, calls `setShims` with the shims from `steel-sdk/_shims/auto/runtime` 33 | - re-exports the installed shims from `steel-sdk/_shims/registry`. 34 | 35 | `steel-sdk/_shims/auto/runtime` exports web runtime shims. 36 | If the `node` export condition is set, the export map replaces it with `steel-sdk/_shims/auto/runtime-node`. 37 | 38 | ### How it works - Type time 39 | 40 | All client code imports shim types from `steel-sdk/_shims/index`, which selects the manual types from `steel-sdk/_shims/manual-types` if they have been declared, otherwise it exports the auto types from `steel-sdk/_shims/auto/types`. 41 | 42 | `steel-sdk/_shims/manual-types` exports an empty namespace. 43 | Manually importing `steel-sdk/shims/node` or `steel-sdk/shims/web` merges declarations into this empty namespace, so they get picked up by `steel-sdk/_shims/index`. 44 | 45 | `steel-sdk/_shims/auto/types` exports web type definitions. 46 | If the `node` export condition is set, the export map replaces it with `steel-sdk/_shims/auto/types-node`, though TS only picks this up if `"moduleResolution": "nodenext"` or `"moduleResolution": "bundler"`. 47 | -------------------------------------------------------------------------------- /tests/api-resources/top-level.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Steel from 'steel-sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Steel({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); 7 | 8 | describe('top level methods', () => { 9 | test('pdf: only required params', async () => { 10 | const responsePromise = client.pdf({ url: 'https://example.com' }); 11 | const rawResponse = await responsePromise.asResponse(); 12 | expect(rawResponse).toBeInstanceOf(Response); 13 | const response = await responsePromise; 14 | expect(response).not.toBeInstanceOf(Response); 15 | const dataAndResponse = await responsePromise.withResponse(); 16 | expect(dataAndResponse.data).toBe(response); 17 | expect(dataAndResponse.response).toBe(rawResponse); 18 | }); 19 | 20 | test('pdf: required and optional params', async () => { 21 | const response = await client.pdf({ 22 | url: 'https://example.com', 23 | delay: 0, 24 | region: 'region', 25 | useProxy: true, 26 | }); 27 | }); 28 | 29 | test('scrape: only required params', async () => { 30 | const responsePromise = client.scrape({ url: 'url' }); 31 | const rawResponse = await responsePromise.asResponse(); 32 | expect(rawResponse).toBeInstanceOf(Response); 33 | const response = await responsePromise; 34 | expect(response).not.toBeInstanceOf(Response); 35 | const dataAndResponse = await responsePromise.withResponse(); 36 | expect(dataAndResponse.data).toBe(response); 37 | expect(dataAndResponse.response).toBe(rawResponse); 38 | }); 39 | 40 | test('scrape: required and optional params', async () => { 41 | const response = await client.scrape({ 42 | url: 'url', 43 | delay: 0, 44 | format: ['html'], 45 | pdf: true, 46 | region: 'region', 47 | screenshot: true, 48 | useProxy: true, 49 | }); 50 | }); 51 | 52 | test('screenshot: only required params', async () => { 53 | const responsePromise = client.screenshot({ url: 'https://example.com' }); 54 | const rawResponse = await responsePromise.asResponse(); 55 | expect(rawResponse).toBeInstanceOf(Response); 56 | const response = await responsePromise; 57 | expect(response).not.toBeInstanceOf(Response); 58 | const dataAndResponse = await responsePromise.withResponse(); 59 | expect(dataAndResponse.data).toBe(response); 60 | expect(dataAndResponse.response).toBe(rawResponse); 61 | }); 62 | 63 | test('screenshot: required and optional params', async () => { 64 | const response = await client.screenshot({ 65 | url: 'https://example.com', 66 | delay: 0, 67 | fullPage: true, 68 | region: 'region', 69 | useProxy: true, 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Setting up the environment 2 | 3 | This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install). 4 | Other package managers may work but are not officially supported for development. 5 | 6 | To set up the repository, run: 7 | 8 | ```sh 9 | $ yarn 10 | $ yarn build 11 | ``` 12 | 13 | This will install all the required dependencies and build output files to `dist/`. 14 | 15 | ## Modifying/Adding code 16 | 17 | Most of the SDK is generated code. Modifications to code will be persisted between generations, but may 18 | result in merge conflicts between manual patches and changes from the generator. The generator will never 19 | modify the contents of the `src/lib/` and `examples/` directories. 20 | 21 | ## Adding and running examples 22 | 23 | All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. 24 | 25 | ```ts 26 | // add an example to examples/.ts 27 | 28 | #!/usr/bin/env -S npm run tsn -T 29 | … 30 | ``` 31 | 32 | ```sh 33 | $ chmod +x examples/.ts 34 | # run the example against your api 35 | $ yarn tsn -T examples/.ts 36 | ``` 37 | 38 | ## Using the repository from source 39 | 40 | If you’d like to use the repository from source, you can either install from git or link to a cloned repository: 41 | 42 | To install via git: 43 | 44 | ```sh 45 | $ npm install git+ssh://git@github.com:steel-dev/steel-node.git 46 | ``` 47 | 48 | Alternatively, to link a local copy of the repo: 49 | 50 | ```sh 51 | # Clone 52 | $ git clone https://www.github.com/steel-dev/steel-node 53 | $ cd steel-node 54 | 55 | # With yarn 56 | $ yarn link 57 | $ cd ../my-package 58 | $ yarn link steel-sdk 59 | 60 | # With pnpm 61 | $ pnpm link --global 62 | $ cd ../my-package 63 | $ pnpm link -—global steel-sdk 64 | ``` 65 | 66 | ## Running tests 67 | 68 | Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. 69 | 70 | ```sh 71 | $ npx prism mock path/to/your/openapi.yml 72 | ``` 73 | 74 | ```sh 75 | $ yarn run test 76 | ``` 77 | 78 | ## Linting and formatting 79 | 80 | This repository uses [prettier](https://www.npmjs.com/package/prettier) and 81 | [eslint](https://www.npmjs.com/package/eslint) to format the code in the repository. 82 | 83 | To lint: 84 | 85 | ```sh 86 | $ yarn lint 87 | ``` 88 | 89 | To format and fix all lint issues automatically: 90 | 91 | ```sh 92 | $ yarn fix 93 | ``` 94 | 95 | ## Publishing and releases 96 | 97 | Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If 98 | the changes aren't made through the automated pipeline, you may want to make releases manually. 99 | 100 | ### Publish with a GitHub workflow 101 | 102 | You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/steel-dev/steel-node/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up. 103 | 104 | ### Publish manually 105 | 106 | If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on 107 | the environment. 108 | -------------------------------------------------------------------------------- /src/_shims/index-deno.ts: -------------------------------------------------------------------------------- 1 | import { MultipartBody } from './MultipartBody'; 2 | import { type RequestOptions } from '../core'; 3 | 4 | export const kind: string = 'web'; 5 | 6 | export type Agent = any; 7 | 8 | const _fetch = fetch; 9 | type _fetch = typeof fetch; 10 | export { _fetch as fetch }; 11 | 12 | const _Request = Request; 13 | type _Request = Request; 14 | export { _Request as Request }; 15 | 16 | type _RequestInfo = RequestInfo; 17 | export { type _RequestInfo as RequestInfo }; 18 | 19 | type _RequestInit = RequestInit; 20 | export { type _RequestInit as RequestInit }; 21 | 22 | const _Response = Response; 23 | type _Response = Response; 24 | export { _Response as Response }; 25 | 26 | type _ResponseInit = ResponseInit; 27 | export { type _ResponseInit as ResponseInit }; 28 | 29 | type _ResponseType = ResponseType; 30 | export { type _ResponseType as ResponseType }; 31 | 32 | type _BodyInit = BodyInit; 33 | export { type _BodyInit as BodyInit }; 34 | 35 | const _Headers = Headers; 36 | type _Headers = Headers; 37 | export { _Headers as Headers }; 38 | 39 | type _HeadersInit = HeadersInit; 40 | export { type _HeadersInit as HeadersInit }; 41 | 42 | type EndingType = 'native' | 'transparent'; 43 | 44 | export interface BlobPropertyBag { 45 | endings?: EndingType; 46 | type?: string; 47 | } 48 | 49 | export interface FilePropertyBag extends BlobPropertyBag { 50 | lastModified?: number; 51 | } 52 | 53 | export type FileFromPathOptions = Omit; 54 | 55 | const _FormData = FormData; 56 | type _FormData = FormData; 57 | export { _FormData as FormData }; 58 | 59 | const _File = File; 60 | type _File = File; 61 | export { _File as File }; 62 | 63 | const _Blob = Blob; 64 | type _Blob = Blob; 65 | export { _Blob as Blob }; 66 | 67 | export async function getMultipartRequestOptions>( 68 | form: FormData, 69 | opts: RequestOptions, 70 | ): Promise> { 71 | return { 72 | ...opts, 73 | body: new MultipartBody(form) as any, 74 | }; 75 | } 76 | 77 | export function getDefaultAgent(url: string) { 78 | return undefined; 79 | } 80 | export function fileFromPath() { 81 | throw new Error( 82 | 'The `fileFromPath` function is only supported in Node. See the README for more details: https://www.github.com/steel-dev/steel-node#file-uploads', 83 | ); 84 | } 85 | 86 | export const isFsReadStream = (value: any) => false; 87 | 88 | export declare class Readable { 89 | readable: boolean; 90 | readonly readableEnded: boolean; 91 | readonly readableFlowing: boolean | null; 92 | readonly readableHighWaterMark: number; 93 | readonly readableLength: number; 94 | readonly readableObjectMode: boolean; 95 | destroyed: boolean; 96 | read(size?: number): any; 97 | pause(): this; 98 | resume(): this; 99 | isPaused(): boolean; 100 | destroy(error?: Error): this; 101 | [Symbol.asyncIterator](): AsyncIterableIterator; 102 | } 103 | 104 | export declare class FsReadStream extends Readable { 105 | path: {}; // node type is string | Buffer 106 | } 107 | 108 | const _ReadableStream = ReadableStream; 109 | type _ReadableStream = ReadableStream; 110 | export { _ReadableStream as ReadableStream }; 111 | 112 | export const init = () => {}; 113 | -------------------------------------------------------------------------------- /src/resources/files.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../resource'; 4 | import { isRequestOptions } from '../core'; 5 | import * as Core from '../core'; 6 | import { type Response } from '../_shims/index'; 7 | 8 | export class Files extends APIResource { 9 | /** 10 | * List all global files for the organization in descending order. 11 | */ 12 | list(options?: Core.RequestOptions): Core.APIPromise { 13 | return this._client.get('/v1/files', options); 14 | } 15 | 16 | /** 17 | * Delete a file from global storage 18 | */ 19 | delete(path_: string, options?: Core.RequestOptions): Core.APIPromise { 20 | return this._client.delete(`/v1/files/${path_}`, { 21 | ...options, 22 | headers: { Accept: '*/*', ...options?.headers }, 23 | }); 24 | } 25 | 26 | /** 27 | * Download a file from global storage 28 | */ 29 | download(path_: string, options?: Core.RequestOptions): Core.APIPromise { 30 | return this._client.get(`/v1/files/${path_}`, { 31 | ...options, 32 | headers: { Accept: 'application/octet-stream', ...options?.headers }, 33 | __binaryResponse: true, 34 | }); 35 | } 36 | 37 | /** 38 | * Uploads a file to global storage via `multipart/form-data` with a `file` field 39 | * that accepts either binary data or a URL string to download from, and an 40 | * optional `path` field for the file storage path. 41 | */ 42 | upload(body?: FileUploadParams, options?: Core.RequestOptions): Core.APIPromise; 43 | upload(options?: Core.RequestOptions): Core.APIPromise; 44 | upload( 45 | body: FileUploadParams | Core.RequestOptions = {}, 46 | options?: Core.RequestOptions, 47 | ): Core.APIPromise { 48 | if (isRequestOptions(body)) { 49 | return this.upload({}, body); 50 | } 51 | return this._client.post('/v1/files', Core.multipartFormRequestOptions({ body, ...options })); 52 | } 53 | } 54 | 55 | export interface File { 56 | /** 57 | * Timestamp when the file was created 58 | */ 59 | lastModified: string; 60 | 61 | /** 62 | * Path to the file in the storage system 63 | */ 64 | path: string; 65 | 66 | /** 67 | * Size of the file in bytes 68 | */ 69 | size: number; 70 | } 71 | 72 | export interface Fileslist { 73 | /** 74 | * Array of files for the current page 75 | */ 76 | data: Array; 77 | } 78 | 79 | export namespace Fileslist { 80 | export interface Data { 81 | /** 82 | * Timestamp when the file was created 83 | */ 84 | lastModified: string; 85 | 86 | /** 87 | * Path to the file in the storage system 88 | */ 89 | path: string; 90 | 91 | /** 92 | * Size of the file in bytes 93 | */ 94 | size: number; 95 | } 96 | } 97 | 98 | export interface FileUploadParams { 99 | /** 100 | * The file to upload (binary) or URL string to download from 101 | */ 102 | file?: unknown; 103 | 104 | /** 105 | * Path to the file in the storage system 106 | */ 107 | path?: string; 108 | } 109 | 110 | export declare namespace Files { 111 | export { type File as File, type Fileslist as Fileslist, type FileUploadParams as FileUploadParams }; 112 | } 113 | -------------------------------------------------------------------------------- /src/_shims/node-runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import * as nf from 'node-fetch'; 5 | import * as fd from 'formdata-node'; 6 | import { type File, type FilePropertyBag } from 'formdata-node'; 7 | import KeepAliveAgent from 'agentkeepalive'; 8 | import { AbortController as AbortControllerPolyfill } from 'abort-controller'; 9 | import { ReadStream as FsReadStream } from 'node:fs'; 10 | import { type Agent } from 'node:http'; 11 | import { FormDataEncoder } from 'form-data-encoder'; 12 | import { Readable } from 'node:stream'; 13 | import { type RequestOptions } from '../core'; 14 | import { MultipartBody } from './MultipartBody'; 15 | import { type Shims } from './registry'; 16 | import { ReadableStream } from 'node:stream/web'; 17 | 18 | type FileFromPathOptions = Omit; 19 | 20 | let fileFromPathWarned = false; 21 | 22 | /** 23 | * @deprecated use fs.createReadStream('./my/file.txt') instead 24 | */ 25 | async function fileFromPath(path: string): Promise; 26 | async function fileFromPath(path: string, filename?: string): Promise; 27 | async function fileFromPath(path: string, options?: FileFromPathOptions): Promise; 28 | async function fileFromPath(path: string, filename?: string, options?: FileFromPathOptions): Promise; 29 | async function fileFromPath(path: string, ...args: any[]): Promise { 30 | // this import fails in environments that don't handle export maps correctly, like old versions of Jest 31 | const { fileFromPath: _fileFromPath } = await import('formdata-node/file-from-path'); 32 | 33 | if (!fileFromPathWarned) { 34 | console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path)}) instead`); 35 | fileFromPathWarned = true; 36 | } 37 | // @ts-ignore 38 | return await _fileFromPath(path, ...args); 39 | } 40 | 41 | const defaultHttpAgent: Agent = new KeepAliveAgent({ keepAlive: true, timeout: 5 * 60 * 1000 }); 42 | const defaultHttpsAgent: Agent = new KeepAliveAgent.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1000 }); 43 | 44 | async function getMultipartRequestOptions>( 45 | form: fd.FormData, 46 | opts: RequestOptions, 47 | ): Promise> { 48 | const encoder = new FormDataEncoder(form); 49 | const readable = Readable.from(encoder); 50 | const body = new MultipartBody(readable); 51 | const headers = { 52 | ...opts.headers, 53 | ...encoder.headers, 54 | 'Content-Length': encoder.contentLength, 55 | }; 56 | 57 | return { ...opts, body: body as any, headers }; 58 | } 59 | 60 | export function getRuntime(): Shims { 61 | // Polyfill global object if needed. 62 | if (typeof AbortController === 'undefined') { 63 | // @ts-expect-error (the types are subtly different, but compatible in practice) 64 | globalThis.AbortController = AbortControllerPolyfill; 65 | } 66 | return { 67 | kind: 'node', 68 | fetch: nf.default, 69 | Request: nf.Request, 70 | Response: nf.Response, 71 | Headers: nf.Headers, 72 | FormData: fd.FormData, 73 | Blob: fd.Blob, 74 | File: fd.File, 75 | ReadableStream, 76 | getMultipartRequestOptions, 77 | getDefaultAgent: (url: string): Agent => (url.startsWith('https') ? defaultHttpsAgent : defaultHttpAgent), 78 | fileFromPath, 79 | isFsReadStream: (value: any): value is FsReadStream => value instanceof FsReadStream, 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/_shims/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { manual } from './manual-types'; 5 | import * as auto from 'steel-sdk/_shims/auto/types'; 6 | import { type RequestOptions } from '../core'; 7 | 8 | type SelectType = unknown extends Manual ? Auto : Manual; 9 | 10 | export const kind: string; 11 | 12 | // @ts-ignore 13 | export type Agent = SelectType; 14 | 15 | // @ts-ignore 16 | export const fetch: SelectType; 17 | 18 | // @ts-ignore 19 | export type Request = SelectType; 20 | // @ts-ignore 21 | export type RequestInfo = SelectType; 22 | // @ts-ignore 23 | export type RequestInit = SelectType; 24 | 25 | // @ts-ignore 26 | export type Response = SelectType; 27 | // @ts-ignore 28 | export type ResponseInit = SelectType; 29 | // @ts-ignore 30 | export type ResponseType = SelectType; 31 | // @ts-ignore 32 | export type BodyInit = SelectType; 33 | // @ts-ignore 34 | export type Headers = SelectType; 35 | // @ts-ignore 36 | export const Headers: SelectType; 37 | // @ts-ignore 38 | export type HeadersInit = SelectType; 39 | 40 | // @ts-ignore 41 | export type BlobPropertyBag = SelectType; 42 | // @ts-ignore 43 | export type FilePropertyBag = SelectType; 44 | // @ts-ignore 45 | export type FileFromPathOptions = SelectType; 46 | // @ts-ignore 47 | export type FormData = SelectType; 48 | // @ts-ignore 49 | export const FormData: SelectType; 50 | // @ts-ignore 51 | export type File = SelectType; 52 | // @ts-ignore 53 | export const File: SelectType; 54 | // @ts-ignore 55 | export type Blob = SelectType; 56 | // @ts-ignore 57 | export const Blob: SelectType; 58 | 59 | // @ts-ignore 60 | export type Readable = SelectType; 61 | // @ts-ignore 62 | export type FsReadStream = SelectType; 63 | // @ts-ignore 64 | export type ReadableStream = SelectType; 65 | // @ts-ignore 66 | export const ReadableStream: SelectType; 67 | 68 | export function getMultipartRequestOptions>( 69 | form: FormData, 70 | opts: RequestOptions, 71 | ): Promise>; 72 | 73 | export function getDefaultAgent(url: string): any; 74 | 75 | // @ts-ignore 76 | export type FileFromPathOptions = SelectType; 77 | 78 | export function fileFromPath(path: string, options?: FileFromPathOptions): Promise; 79 | export function fileFromPath(path: string, filename?: string, options?: FileFromPathOptions): Promise; 80 | 81 | export function isFsReadStream(value: any): value is FsReadStream; 82 | 83 | export const init: () => void; 84 | -------------------------------------------------------------------------------- /src/_shims/auto/types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export type Agent = any; 5 | 6 | // @ts-ignore 7 | declare const _fetch: unknown extends typeof fetch ? never : typeof fetch; 8 | export { _fetch as fetch }; 9 | 10 | // @ts-ignore 11 | type _Request = unknown extends Request ? never : Request; 12 | export { _Request as Request }; 13 | 14 | // @ts-ignore 15 | type _RequestInfo = unknown extends RequestInfo ? never : RequestInfo; 16 | export { type _RequestInfo as RequestInfo }; 17 | 18 | // @ts-ignore 19 | type _RequestInit = unknown extends RequestInit ? never : RequestInit; 20 | export { type _RequestInit as RequestInit }; 21 | 22 | // @ts-ignore 23 | type _Response = unknown extends Response ? never : Response; 24 | export { _Response as Response }; 25 | 26 | // @ts-ignore 27 | type _ResponseInit = unknown extends ResponseInit ? never : ResponseInit; 28 | export { type _ResponseInit as ResponseInit }; 29 | 30 | // @ts-ignore 31 | type _ResponseType = unknown extends ResponseType ? never : ResponseType; 32 | export { type _ResponseType as ResponseType }; 33 | 34 | // @ts-ignore 35 | type _BodyInit = unknown extends BodyInit ? never : BodyInit; 36 | export { type _BodyInit as BodyInit }; 37 | 38 | // @ts-ignore 39 | type _Headers = unknown extends Headers ? never : Headers; 40 | export { _Headers as Headers }; 41 | 42 | // @ts-ignore 43 | type _HeadersInit = unknown extends HeadersInit ? never : HeadersInit; 44 | export { type _HeadersInit as HeadersInit }; 45 | 46 | type EndingType = 'native' | 'transparent'; 47 | 48 | export interface BlobPropertyBag { 49 | endings?: EndingType; 50 | type?: string; 51 | } 52 | 53 | export interface FilePropertyBag extends BlobPropertyBag { 54 | lastModified?: number; 55 | } 56 | 57 | export type FileFromPathOptions = Omit; 58 | 59 | // @ts-ignore 60 | type _FormData = unknown extends FormData ? never : FormData; 61 | // @ts-ignore 62 | declare const _FormData: unknown extends typeof FormData ? never : typeof FormData; 63 | export { _FormData as FormData }; 64 | 65 | // @ts-ignore 66 | type _File = unknown extends File ? never : File; 67 | // @ts-ignore 68 | declare const _File: unknown extends typeof File ? never : typeof File; 69 | export { _File as File }; 70 | 71 | // @ts-ignore 72 | type _Blob = unknown extends Blob ? never : Blob; 73 | // @ts-ignore 74 | declare const _Blob: unknown extends typeof Blob ? never : typeof Blob; 75 | export { _Blob as Blob }; 76 | 77 | export declare class Readable { 78 | readable: boolean; 79 | readonly readableEnded: boolean; 80 | readonly readableFlowing: boolean | null; 81 | readonly readableHighWaterMark: number; 82 | readonly readableLength: number; 83 | readonly readableObjectMode: boolean; 84 | destroyed: boolean; 85 | read(size?: number): any; 86 | pause(): this; 87 | resume(): this; 88 | isPaused(): boolean; 89 | destroy(error?: Error): this; 90 | [Symbol.asyncIterator](): AsyncIterableIterator; 91 | } 92 | 93 | export declare class FsReadStream extends Readable { 94 | path: {}; // node type is string | Buffer 95 | } 96 | 97 | // @ts-ignore 98 | type _ReadableStream = unknown extends ReadableStream ? never : ReadableStream; 99 | // @ts-ignore 100 | declare const _ReadableStream: unknown extends typeof ReadableStream ? never : typeof ReadableStream; 101 | export { _ReadableStream as ReadableStream }; 102 | -------------------------------------------------------------------------------- /src/resources/sessions/files.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../../resource'; 4 | import { isRequestOptions } from '../../core'; 5 | import * as Core from '../../core'; 6 | import * as FilesAPI from '../files'; 7 | import { type Response } from '../../_shims/index'; 8 | 9 | export class Files extends APIResource { 10 | /** 11 | * List all files from the session in descending order. 12 | */ 13 | list(sessionId: string, options?: Core.RequestOptions): Core.APIPromise { 14 | return this._client.get(`/v1/sessions/${sessionId}/files`, options); 15 | } 16 | 17 | /** 18 | * Delete a file from a session 19 | */ 20 | delete(sessionId: string, path_: string, options?: Core.RequestOptions): Core.APIPromise { 21 | return this._client.delete(`/v1/sessions/${sessionId}/files/${path_}`, { 22 | ...options, 23 | headers: { Accept: '*/*', ...options?.headers }, 24 | }); 25 | } 26 | 27 | /** 28 | * Delete all files from a session 29 | */ 30 | deleteAll(sessionId: string, options?: Core.RequestOptions): Core.APIPromise { 31 | return this._client.delete(`/v1/sessions/${sessionId}/files`, { 32 | ...options, 33 | headers: { Accept: '*/*', ...options?.headers }, 34 | }); 35 | } 36 | 37 | /** 38 | * Download a file from a session 39 | */ 40 | download(sessionId: string, path_: string, options?: Core.RequestOptions): Core.APIPromise { 41 | return this._client.get(`/v1/sessions/${sessionId}/files/${path_}`, { 42 | ...options, 43 | headers: { Accept: 'application/octet-stream', ...options?.headers }, 44 | __binaryResponse: true, 45 | }); 46 | } 47 | 48 | /** 49 | * Download all files from the session as a zip archive. 50 | */ 51 | downloadArchive(sessionId: string, options?: Core.RequestOptions): Core.APIPromise { 52 | return this._client.get(`/v1/sessions/${sessionId}/files.zip`, { 53 | ...options, 54 | headers: { Accept: 'application/zip', ...options?.headers }, 55 | __binaryResponse: true, 56 | }); 57 | } 58 | 59 | /** 60 | * Uploads a file to a session via `multipart/form-data` with a `file` field that 61 | * accepts either binary data or a URL string to download from, and an optional 62 | * `path` field for the file storage path. 63 | */ 64 | upload( 65 | sessionId: string, 66 | body?: FileUploadParams, 67 | options?: Core.RequestOptions, 68 | ): Core.APIPromise; 69 | upload(sessionId: string, options?: Core.RequestOptions): Core.APIPromise; 70 | upload( 71 | sessionId: string, 72 | body: FileUploadParams | Core.RequestOptions = {}, 73 | options?: Core.RequestOptions, 74 | ): Core.APIPromise { 75 | if (isRequestOptions(body)) { 76 | return this.upload(sessionId, {}, body); 77 | } 78 | return this._client.post( 79 | `/v1/sessions/${sessionId}/files`, 80 | Core.multipartFormRequestOptions({ body, ...options }), 81 | ); 82 | } 83 | } 84 | 85 | export interface FileUploadParams { 86 | /** 87 | * The file to upload (binary) or URL string to download from 88 | */ 89 | file?: unknown; 90 | 91 | /** 92 | * Path to the file in the storage system 93 | */ 94 | path?: string; 95 | } 96 | 97 | export declare namespace Files { 98 | export { type FileUploadParams as FileUploadParams }; 99 | } 100 | -------------------------------------------------------------------------------- /src/_shims/web-runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { MultipartBody } from './MultipartBody'; 5 | import { type RequestOptions } from '../core'; 6 | import { type Shims } from './registry'; 7 | 8 | export function getRuntime({ manuallyImported }: { manuallyImported?: boolean } = {}): Shims { 9 | const recommendation = 10 | manuallyImported ? 11 | `You may need to use polyfills` 12 | : `Add one of these imports before your first \`import … from 'steel-sdk'\`: 13 | - \`import 'steel-sdk/shims/node'\` (if you're running on Node) 14 | - \`import 'steel-sdk/shims/web'\` (otherwise) 15 | `; 16 | 17 | let _fetch, _Request, _Response, _Headers; 18 | try { 19 | // @ts-ignore 20 | _fetch = fetch; 21 | // @ts-ignore 22 | _Request = Request; 23 | // @ts-ignore 24 | _Response = Response; 25 | // @ts-ignore 26 | _Headers = Headers; 27 | } catch (error) { 28 | throw new Error( 29 | `this environment is missing the following Web Fetch API type: ${ 30 | (error as any).message 31 | }. ${recommendation}`, 32 | ); 33 | } 34 | 35 | return { 36 | kind: 'web', 37 | fetch: _fetch, 38 | Request: _Request, 39 | Response: _Response, 40 | Headers: _Headers, 41 | FormData: 42 | // @ts-ignore 43 | typeof FormData !== 'undefined' ? FormData : ( 44 | class FormData { 45 | // @ts-ignore 46 | constructor() { 47 | throw new Error( 48 | `file uploads aren't supported in this environment yet as 'FormData' is undefined. ${recommendation}`, 49 | ); 50 | } 51 | } 52 | ), 53 | Blob: 54 | typeof Blob !== 'undefined' ? Blob : ( 55 | class Blob { 56 | constructor() { 57 | throw new Error( 58 | `file uploads aren't supported in this environment yet as 'Blob' is undefined. ${recommendation}`, 59 | ); 60 | } 61 | } 62 | ), 63 | File: 64 | // @ts-ignore 65 | typeof File !== 'undefined' ? File : ( 66 | class File { 67 | // @ts-ignore 68 | constructor() { 69 | throw new Error( 70 | `file uploads aren't supported in this environment yet as 'File' is undefined. ${recommendation}`, 71 | ); 72 | } 73 | } 74 | ), 75 | ReadableStream: 76 | // @ts-ignore 77 | typeof ReadableStream !== 'undefined' ? ReadableStream : ( 78 | class ReadableStream { 79 | // @ts-ignore 80 | constructor() { 81 | throw new Error( 82 | `streaming isn't supported in this environment yet as 'ReadableStream' is undefined. ${recommendation}`, 83 | ); 84 | } 85 | } 86 | ), 87 | getMultipartRequestOptions: async >( 88 | // @ts-ignore 89 | form: FormData, 90 | opts: RequestOptions, 91 | ): Promise> => ({ 92 | ...opts, 93 | body: new MultipartBody(form) as any, 94 | }), 95 | getDefaultAgent: (url: string) => undefined, 96 | fileFromPath: () => { 97 | throw new Error( 98 | 'The `fileFromPath` function is only supported in Node. See the README for more details: https://www.github.com/steel-dev/steel-node#file-uploads', 99 | ); 100 | }, 101 | isFsReadStream: (value: any) => false, 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /tests/api-resources/files.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Steel from 'steel-sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Steel({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); 7 | 8 | describe('resource files', () => { 9 | test('list', async () => { 10 | const responsePromise = client.files.list(); 11 | const rawResponse = await responsePromise.asResponse(); 12 | expect(rawResponse).toBeInstanceOf(Response); 13 | const response = await responsePromise; 14 | expect(response).not.toBeInstanceOf(Response); 15 | const dataAndResponse = await responsePromise.withResponse(); 16 | expect(dataAndResponse.data).toBe(response); 17 | expect(dataAndResponse.response).toBe(rawResponse); 18 | }); 19 | 20 | test('list: request options instead of params are passed correctly', async () => { 21 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 22 | await expect(client.files.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( 23 | Steel.NotFoundError, 24 | ); 25 | }); 26 | 27 | test('delete', async () => { 28 | const responsePromise = client.files.delete('path'); 29 | const rawResponse = await responsePromise.asResponse(); 30 | expect(rawResponse).toBeInstanceOf(Response); 31 | const response = await responsePromise; 32 | expect(response).not.toBeInstanceOf(Response); 33 | const dataAndResponse = await responsePromise.withResponse(); 34 | expect(dataAndResponse.data).toBe(response); 35 | expect(dataAndResponse.response).toBe(rawResponse); 36 | }); 37 | 38 | test('delete: request options instead of params are passed correctly', async () => { 39 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 40 | await expect(client.files.delete('path', { path: '/_stainless_unknown_path' })).rejects.toThrow( 41 | Steel.NotFoundError, 42 | ); 43 | }); 44 | 45 | test('download: request options instead of params are passed correctly', async () => { 46 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 47 | await expect(client.files.download('path', { path: '/_stainless_unknown_path' })).rejects.toThrow( 48 | Steel.NotFoundError, 49 | ); 50 | }); 51 | 52 | test('upload', async () => { 53 | const responsePromise = client.files.upload(); 54 | const rawResponse = await responsePromise.asResponse(); 55 | expect(rawResponse).toBeInstanceOf(Response); 56 | const response = await responsePromise; 57 | expect(response).not.toBeInstanceOf(Response); 58 | const dataAndResponse = await responsePromise.withResponse(); 59 | expect(dataAndResponse.data).toBe(response); 60 | expect(dataAndResponse.response).toBe(rawResponse); 61 | }); 62 | 63 | test('upload: request options instead of params are passed correctly', async () => { 64 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 65 | await expect(client.files.upload({ path: '/_stainless_unknown_path' })).rejects.toThrow( 66 | Steel.NotFoundError, 67 | ); 68 | }); 69 | 70 | test('upload: request options and params are passed correctly', async () => { 71 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 72 | await expect( 73 | client.files.upload({ file: {}, path: 'path' }, { path: '/_stainless_unknown_path' }), 74 | ).rejects.toThrow(Steel.NotFoundError); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "steel-sdk", 3 | "version": "0.16.0", 4 | "description": "The official TypeScript library for the Steel API", 5 | "author": "Steel ", 6 | "types": "dist/index.d.ts", 7 | "main": "dist/index.js", 8 | "type": "commonjs", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/steel-dev/steel-node.git" 12 | }, 13 | "homepage": "https://steel.dev", 14 | "license": "Apache-2.0", 15 | "packageManager": "yarn@1.22.22", 16 | "files": [ 17 | "**/*" 18 | ], 19 | "private": false, 20 | "publishConfig": { 21 | "access": "public" 22 | }, 23 | "scripts": { 24 | "test": "./scripts/test", 25 | "build": "./scripts/build", 26 | "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", 27 | "format": "prettier --write --cache --cache-strategy metadata . !dist", 28 | "prepare": "if ./scripts/utils/check-is-in-git-install.sh; then ./scripts/build && ./scripts/utils/git-swap.sh; fi", 29 | "tsn": "ts-node -r tsconfig-paths/register", 30 | "lint": "./scripts/lint", 31 | "fix": "./scripts/format" 32 | }, 33 | "dependencies": { 34 | "@types/node": "^18.11.18", 35 | "@types/node-fetch": "^2.6.4", 36 | "abort-controller": "^3.0.0", 37 | "agentkeepalive": "^4.2.1", 38 | "form-data-encoder": "1.7.2", 39 | "formdata-node": "^4.3.2", 40 | "node-fetch": "^2.6.7" 41 | }, 42 | "devDependencies": { 43 | "@swc/core": "^1.3.102", 44 | "@swc/jest": "^0.2.29", 45 | "@types/jest": "^29.4.0", 46 | "@typescript-eslint/eslint-plugin": "^6.7.0", 47 | "@typescript-eslint/parser": "^6.7.0", 48 | "eslint": "^8.49.0", 49 | "eslint-plugin-prettier": "^5.0.1", 50 | "eslint-plugin-unused-imports": "^3.0.0", 51 | "iconv-lite": "^0.6.3", 52 | "jest": "^29.4.0", 53 | "prettier": "^3.0.0", 54 | "ts-jest": "^29.1.0", 55 | "ts-node": "^10.5.0", 56 | "tsc-multi": "^1.1.0", 57 | "tsconfig-paths": "^4.0.0", 58 | "typescript": "^4.8.2" 59 | }, 60 | "sideEffects": [ 61 | "./_shims/index.js", 62 | "./_shims/index.mjs", 63 | "./shims/node.js", 64 | "./shims/node.mjs", 65 | "./shims/web.js", 66 | "./shims/web.mjs" 67 | ], 68 | "exports": { 69 | "./_shims/auto/*": { 70 | "deno": { 71 | "types": "./dist/_shims/auto/*.d.ts", 72 | "require": "./dist/_shims/auto/*.js", 73 | "default": "./dist/_shims/auto/*.mjs" 74 | }, 75 | "bun": { 76 | "types": "./dist/_shims/auto/*.d.ts", 77 | "require": "./dist/_shims/auto/*-bun.js", 78 | "default": "./dist/_shims/auto/*-bun.mjs" 79 | }, 80 | "browser": { 81 | "types": "./dist/_shims/auto/*.d.ts", 82 | "require": "./dist/_shims/auto/*.js", 83 | "default": "./dist/_shims/auto/*.mjs" 84 | }, 85 | "worker": { 86 | "types": "./dist/_shims/auto/*.d.ts", 87 | "require": "./dist/_shims/auto/*.js", 88 | "default": "./dist/_shims/auto/*.mjs" 89 | }, 90 | "workerd": { 91 | "types": "./dist/_shims/auto/*.d.ts", 92 | "require": "./dist/_shims/auto/*.js", 93 | "default": "./dist/_shims/auto/*.mjs" 94 | }, 95 | "node": { 96 | "types": "./dist/_shims/auto/*-node.d.ts", 97 | "require": "./dist/_shims/auto/*-node.js", 98 | "default": "./dist/_shims/auto/*-node.mjs" 99 | }, 100 | "types": "./dist/_shims/auto/*.d.ts", 101 | "require": "./dist/_shims/auto/*.js", 102 | "default": "./dist/_shims/auto/*.mjs" 103 | }, 104 | ".": { 105 | "require": { 106 | "types": "./dist/index.d.ts", 107 | "default": "./dist/index.js" 108 | }, 109 | "types": "./dist/index.d.mts", 110 | "default": "./dist/index.mjs" 111 | }, 112 | "./*.mjs": { 113 | "types": "./dist/*.d.ts", 114 | "default": "./dist/*.mjs" 115 | }, 116 | "./*.js": { 117 | "types": "./dist/*.d.ts", 118 | "default": "./dist/*.js" 119 | }, 120 | "./*": { 121 | "types": "./dist/*.d.ts", 122 | "require": "./dist/*.js", 123 | "default": "./dist/*.mjs" 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/error.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { castToError, Headers } from './core'; 4 | 5 | export class SteelError extends Error {} 6 | 7 | export class APIError< 8 | TStatus extends number | undefined = number | undefined, 9 | THeaders extends Headers | undefined = Headers | undefined, 10 | TError extends Object | undefined = Object | undefined, 11 | > extends SteelError { 12 | /** HTTP status for the response that caused the error */ 13 | readonly status: TStatus; 14 | /** HTTP headers for the response that caused the error */ 15 | readonly headers: THeaders; 16 | /** JSON body of the response that caused the error */ 17 | readonly error: TError; 18 | 19 | constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) { 20 | super(`${APIError.makeMessage(status, error, message)}`); 21 | this.status = status; 22 | this.headers = headers; 23 | this.error = error; 24 | } 25 | 26 | private static makeMessage(status: number | undefined, error: any, message: string | undefined) { 27 | const msg = 28 | error?.message ? 29 | typeof error.message === 'string' ? 30 | error.message 31 | : JSON.stringify(error.message) 32 | : error ? JSON.stringify(error) 33 | : message; 34 | 35 | if (status && msg) { 36 | return `${status} ${msg}`; 37 | } 38 | if (status) { 39 | return `${status} status code (no body)`; 40 | } 41 | if (msg) { 42 | return msg; 43 | } 44 | return '(no status code or body)'; 45 | } 46 | 47 | static generate( 48 | status: number | undefined, 49 | errorResponse: Object | undefined, 50 | message: string | undefined, 51 | headers: Headers | undefined, 52 | ): APIError { 53 | if (!status || !headers) { 54 | return new APIConnectionError({ message, cause: castToError(errorResponse) }); 55 | } 56 | 57 | const error = errorResponse as Record; 58 | 59 | if (status === 400) { 60 | return new BadRequestError(status, error, message, headers); 61 | } 62 | 63 | if (status === 401) { 64 | return new AuthenticationError(status, error, message, headers); 65 | } 66 | 67 | if (status === 403) { 68 | return new PermissionDeniedError(status, error, message, headers); 69 | } 70 | 71 | if (status === 404) { 72 | return new NotFoundError(status, error, message, headers); 73 | } 74 | 75 | if (status === 409) { 76 | return new ConflictError(status, error, message, headers); 77 | } 78 | 79 | if (status === 422) { 80 | return new UnprocessableEntityError(status, error, message, headers); 81 | } 82 | 83 | if (status === 429) { 84 | return new RateLimitError(status, error, message, headers); 85 | } 86 | 87 | if (status >= 500) { 88 | return new InternalServerError(status, error, message, headers); 89 | } 90 | 91 | return new APIError(status, error, message, headers); 92 | } 93 | } 94 | 95 | export class APIUserAbortError extends APIError { 96 | constructor({ message }: { message?: string } = {}) { 97 | super(undefined, undefined, message || 'Request was aborted.', undefined); 98 | } 99 | } 100 | 101 | export class APIConnectionError extends APIError { 102 | constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) { 103 | super(undefined, undefined, message || 'Connection error.', undefined); 104 | // in some environments the 'cause' property is already declared 105 | // @ts-ignore 106 | if (cause) this.cause = cause; 107 | } 108 | } 109 | 110 | export class APIConnectionTimeoutError extends APIConnectionError { 111 | constructor({ message }: { message?: string } = {}) { 112 | super({ message: message ?? 'Request timed out.' }); 113 | } 114 | } 115 | 116 | export class BadRequestError extends APIError<400, Headers> {} 117 | 118 | export class AuthenticationError extends APIError<401, Headers> {} 119 | 120 | export class PermissionDeniedError extends APIError<403, Headers> {} 121 | 122 | export class NotFoundError extends APIError<404, Headers> {} 123 | 124 | export class ConflictError extends APIError<409, Headers> {} 125 | 126 | export class UnprocessableEntityError extends APIError<422, Headers> {} 127 | 128 | export class RateLimitError extends APIError<429, Headers> {} 129 | 130 | export class InternalServerError extends APIError {} 131 | -------------------------------------------------------------------------------- /tests/api-resources/credentials.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Steel from 'steel-sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Steel({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); 7 | 8 | describe('resource credentials', () => { 9 | test('create: only required params', async () => { 10 | const responsePromise = client.credentials.create({ value: { foo: 'string' } }); 11 | const rawResponse = await responsePromise.asResponse(); 12 | expect(rawResponse).toBeInstanceOf(Response); 13 | const response = await responsePromise; 14 | expect(response).not.toBeInstanceOf(Response); 15 | const dataAndResponse = await responsePromise.withResponse(); 16 | expect(dataAndResponse.data).toBe(response); 17 | expect(dataAndResponse.response).toBe(rawResponse); 18 | }); 19 | 20 | test('create: required and optional params', async () => { 21 | const response = await client.credentials.create({ 22 | value: { foo: 'string' }, 23 | label: 'label', 24 | namespace: 'namespace', 25 | origin: 'origin', 26 | }); 27 | }); 28 | 29 | test('update', async () => { 30 | const responsePromise = client.credentials.update(); 31 | const rawResponse = await responsePromise.asResponse(); 32 | expect(rawResponse).toBeInstanceOf(Response); 33 | const response = await responsePromise; 34 | expect(response).not.toBeInstanceOf(Response); 35 | const dataAndResponse = await responsePromise.withResponse(); 36 | expect(dataAndResponse.data).toBe(response); 37 | expect(dataAndResponse.response).toBe(rawResponse); 38 | }); 39 | 40 | test('update: request options instead of params are passed correctly', async () => { 41 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 42 | await expect(client.credentials.update({ path: '/_stainless_unknown_path' })).rejects.toThrow( 43 | Steel.NotFoundError, 44 | ); 45 | }); 46 | 47 | test('update: request options and params are passed correctly', async () => { 48 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 49 | await expect( 50 | client.credentials.update( 51 | { label: 'label', namespace: 'namespace', origin: 'origin', value: { foo: 'string' } }, 52 | { path: '/_stainless_unknown_path' }, 53 | ), 54 | ).rejects.toThrow(Steel.NotFoundError); 55 | }); 56 | 57 | test('list', async () => { 58 | const responsePromise = client.credentials.list(); 59 | const rawResponse = await responsePromise.asResponse(); 60 | expect(rawResponse).toBeInstanceOf(Response); 61 | const response = await responsePromise; 62 | expect(response).not.toBeInstanceOf(Response); 63 | const dataAndResponse = await responsePromise.withResponse(); 64 | expect(dataAndResponse.data).toBe(response); 65 | expect(dataAndResponse.response).toBe(rawResponse); 66 | }); 67 | 68 | test('list: request options instead of params are passed correctly', async () => { 69 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 70 | await expect(client.credentials.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( 71 | Steel.NotFoundError, 72 | ); 73 | }); 74 | 75 | test('list: request options and params are passed correctly', async () => { 76 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 77 | await expect( 78 | client.credentials.list( 79 | { namespace: 'namespace', origin: 'origin' }, 80 | { path: '/_stainless_unknown_path' }, 81 | ), 82 | ).rejects.toThrow(Steel.NotFoundError); 83 | }); 84 | 85 | test('delete: only required params', async () => { 86 | const responsePromise = client.credentials.delete({ origin: 'origin' }); 87 | const rawResponse = await responsePromise.asResponse(); 88 | expect(rawResponse).toBeInstanceOf(Response); 89 | const response = await responsePromise; 90 | expect(response).not.toBeInstanceOf(Response); 91 | const dataAndResponse = await responsePromise.withResponse(); 92 | expect(dataAndResponse.data).toBe(response); 93 | expect(dataAndResponse.response).toBe(rawResponse); 94 | }); 95 | 96 | test('delete: required and optional params', async () => { 97 | const response = await client.credentials.delete({ origin: 'origin', namespace: 'namespace' }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /tests/api-resources/sessions/files.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Steel from 'steel-sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Steel({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); 7 | 8 | describe('resource files', () => { 9 | test('list', async () => { 10 | const responsePromise = client.sessions.files.list('sessionId'); 11 | const rawResponse = await responsePromise.asResponse(); 12 | expect(rawResponse).toBeInstanceOf(Response); 13 | const response = await responsePromise; 14 | expect(response).not.toBeInstanceOf(Response); 15 | const dataAndResponse = await responsePromise.withResponse(); 16 | expect(dataAndResponse.data).toBe(response); 17 | expect(dataAndResponse.response).toBe(rawResponse); 18 | }); 19 | 20 | test('list: request options instead of params are passed correctly', async () => { 21 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 22 | await expect( 23 | client.sessions.files.list('sessionId', { path: '/_stainless_unknown_path' }), 24 | ).rejects.toThrow(Steel.NotFoundError); 25 | }); 26 | 27 | test('delete', async () => { 28 | const responsePromise = client.sessions.files.delete('sessionId', 'path'); 29 | const rawResponse = await responsePromise.asResponse(); 30 | expect(rawResponse).toBeInstanceOf(Response); 31 | const response = await responsePromise; 32 | expect(response).not.toBeInstanceOf(Response); 33 | const dataAndResponse = await responsePromise.withResponse(); 34 | expect(dataAndResponse.data).toBe(response); 35 | expect(dataAndResponse.response).toBe(rawResponse); 36 | }); 37 | 38 | test('delete: request options instead of params are passed correctly', async () => { 39 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 40 | await expect( 41 | client.sessions.files.delete('sessionId', 'path', { path: '/_stainless_unknown_path' }), 42 | ).rejects.toThrow(Steel.NotFoundError); 43 | }); 44 | 45 | test('deleteAll', async () => { 46 | const responsePromise = client.sessions.files.deleteAll('sessionId'); 47 | const rawResponse = await responsePromise.asResponse(); 48 | expect(rawResponse).toBeInstanceOf(Response); 49 | const response = await responsePromise; 50 | expect(response).not.toBeInstanceOf(Response); 51 | const dataAndResponse = await responsePromise.withResponse(); 52 | expect(dataAndResponse.data).toBe(response); 53 | expect(dataAndResponse.response).toBe(rawResponse); 54 | }); 55 | 56 | test('deleteAll: request options instead of params are passed correctly', async () => { 57 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 58 | await expect( 59 | client.sessions.files.deleteAll('sessionId', { path: '/_stainless_unknown_path' }), 60 | ).rejects.toThrow(Steel.NotFoundError); 61 | }); 62 | 63 | test('download: request options instead of params are passed correctly', async () => { 64 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 65 | await expect( 66 | client.sessions.files.download('sessionId', 'path', { path: '/_stainless_unknown_path' }), 67 | ).rejects.toThrow(Steel.NotFoundError); 68 | }); 69 | 70 | test('downloadArchive: request options instead of params are passed correctly', async () => { 71 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 72 | await expect( 73 | client.sessions.files.downloadArchive('sessionId', { path: '/_stainless_unknown_path' }), 74 | ).rejects.toThrow(Steel.NotFoundError); 75 | }); 76 | 77 | test('upload', async () => { 78 | const responsePromise = client.sessions.files.upload('sessionId'); 79 | const rawResponse = await responsePromise.asResponse(); 80 | expect(rawResponse).toBeInstanceOf(Response); 81 | const response = await responsePromise; 82 | expect(response).not.toBeInstanceOf(Response); 83 | const dataAndResponse = await responsePromise.withResponse(); 84 | expect(dataAndResponse.data).toBe(response); 85 | expect(dataAndResponse.response).toBe(rawResponse); 86 | }); 87 | 88 | test('upload: request options instead of params are passed correctly', async () => { 89 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 90 | await expect( 91 | client.sessions.files.upload('sessionId', { path: '/_stainless_unknown_path' }), 92 | ).rejects.toThrow(Steel.NotFoundError); 93 | }); 94 | 95 | test('upload: request options and params are passed correctly', async () => { 96 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 97 | await expect( 98 | client.sessions.files.upload( 99 | 'sessionId', 100 | { file: {}, path: 'path' }, 101 | { path: '/_stainless_unknown_path' }, 102 | ), 103 | ).rejects.toThrow(Steel.NotFoundError); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /src/resources/extensions.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../resource'; 4 | import { isRequestOptions } from '../core'; 5 | import * as Core from '../core'; 6 | 7 | export class Extensions extends APIResource { 8 | /** 9 | * Update a Chrome extension (.zip/.crx file or Chrome Web Store URL) for the 10 | * organization 11 | */ 12 | update( 13 | extensionId: string, 14 | body?: ExtensionUpdateParams, 15 | options?: Core.RequestOptions, 16 | ): Core.APIPromise; 17 | update(extensionId: string, options?: Core.RequestOptions): Core.APIPromise; 18 | update( 19 | extensionId: string, 20 | body: ExtensionUpdateParams | Core.RequestOptions = {}, 21 | options?: Core.RequestOptions, 22 | ): Core.APIPromise { 23 | if (isRequestOptions(body)) { 24 | return this.update(extensionId, {}, body); 25 | } 26 | return this._client.put( 27 | `/v1/extensions/${extensionId}`, 28 | Core.multipartFormRequestOptions({ body, ...options }), 29 | ); 30 | } 31 | 32 | /** 33 | * List all extensions for the organization 34 | */ 35 | list(options?: Core.RequestOptions): Core.APIPromise { 36 | return this._client.get('/v1/extensions', options); 37 | } 38 | 39 | /** 40 | * Delete an extension by ID 41 | */ 42 | delete(extensionId: string, options?: Core.RequestOptions): Core.APIPromise { 43 | return this._client.delete(`/v1/extensions/${extensionId}`, options); 44 | } 45 | 46 | /** 47 | * Delete all extensions for the organization 48 | */ 49 | deleteAll(options?: Core.RequestOptions): Core.APIPromise { 50 | return this._client.delete('/v1/extensions', options); 51 | } 52 | 53 | /** 54 | * Download an extension file by extension ID 55 | */ 56 | download(extensionId: string, options?: Core.RequestOptions): Core.APIPromise { 57 | return this._client.get(`/v1/extensions/${extensionId}`, options); 58 | } 59 | 60 | /** 61 | * Upload a Chrome extension (.zip/.crx file or Chrome Web Store URL) for the 62 | * organization 63 | */ 64 | upload( 65 | body?: ExtensionUploadParams, 66 | options?: Core.RequestOptions, 67 | ): Core.APIPromise; 68 | upload(options?: Core.RequestOptions): Core.APIPromise; 69 | upload( 70 | body: ExtensionUploadParams | Core.RequestOptions = {}, 71 | options?: Core.RequestOptions, 72 | ): Core.APIPromise { 73 | if (isRequestOptions(body)) { 74 | return this.upload({}, body); 75 | } 76 | return this._client.post('/v1/extensions', Core.multipartFormRequestOptions({ body, ...options })); 77 | } 78 | } 79 | 80 | export interface ExtensionUpdateResponse { 81 | /** 82 | * Unique extension identifier (e.g., ext_12345) 83 | */ 84 | id: string; 85 | 86 | /** 87 | * Creation timestamp 88 | */ 89 | createdAt: string; 90 | 91 | /** 92 | * Extension name 93 | */ 94 | name: string; 95 | 96 | /** 97 | * Last update timestamp 98 | */ 99 | updatedAt: string; 100 | } 101 | 102 | /** 103 | * Response containing a list of extensions for the organization 104 | */ 105 | export interface ExtensionListResponse { 106 | /** 107 | * Total number of extensions 108 | */ 109 | count: number; 110 | 111 | /** 112 | * List of extensions for the organization 113 | */ 114 | extensions: Array; 115 | } 116 | 117 | export namespace ExtensionListResponse { 118 | export interface Extension { 119 | /** 120 | * Unique extension identifier (e.g., ext_12345) 121 | */ 122 | id: string; 123 | 124 | /** 125 | * Creation timestamp 126 | */ 127 | createdAt: string; 128 | 129 | /** 130 | * Extension name 131 | */ 132 | name: string; 133 | 134 | /** 135 | * Last update timestamp 136 | */ 137 | updatedAt: string; 138 | } 139 | } 140 | 141 | export interface ExtensionDeleteResponse { 142 | message: string; 143 | } 144 | 145 | export interface ExtensionDeleteAllResponse { 146 | message: string; 147 | } 148 | 149 | /** 150 | * Extension zip file 151 | */ 152 | export type ExtensionDownloadResponse = Core.Uploadable; 153 | 154 | export interface ExtensionUploadResponse { 155 | /** 156 | * Unique extension identifier (e.g., ext_12345) 157 | */ 158 | id: string; 159 | 160 | /** 161 | * Creation timestamp 162 | */ 163 | createdAt: string; 164 | 165 | /** 166 | * Extension name 167 | */ 168 | name: string; 169 | 170 | /** 171 | * Last update timestamp 172 | */ 173 | updatedAt: string; 174 | } 175 | 176 | export interface ExtensionUpdateParams { 177 | /** 178 | * Extension .zip/.crx file 179 | */ 180 | file?: unknown; 181 | 182 | /** 183 | * Extension URL 184 | */ 185 | url?: string; 186 | } 187 | 188 | export interface ExtensionUploadParams { 189 | /** 190 | * Extension .zip/.crx file 191 | */ 192 | file?: unknown; 193 | 194 | /** 195 | * Extension URL 196 | */ 197 | url?: string; 198 | } 199 | 200 | export declare namespace Extensions { 201 | export { 202 | type ExtensionUpdateResponse as ExtensionUpdateResponse, 203 | type ExtensionListResponse as ExtensionListResponse, 204 | type ExtensionDeleteResponse as ExtensionDeleteResponse, 205 | type ExtensionDeleteAllResponse as ExtensionDeleteAllResponse, 206 | type ExtensionDownloadResponse as ExtensionDownloadResponse, 207 | type ExtensionUploadResponse as ExtensionUploadResponse, 208 | type ExtensionUpdateParams as ExtensionUpdateParams, 209 | type ExtensionUploadParams as ExtensionUploadParams, 210 | }; 211 | } 212 | -------------------------------------------------------------------------------- /src/resources/top-level.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | export interface PdfResponse { 4 | /** 5 | * URL where the PDF is hosted 6 | */ 7 | url: string; 8 | } 9 | 10 | /** 11 | * Response from a successful scrape request 12 | */ 13 | export interface ScrapeResponse { 14 | content: ScrapeResponse.Content; 15 | 16 | links: Array; 17 | 18 | metadata: ScrapeResponse.Metadata; 19 | 20 | pdf?: ScrapeResponse.Pdf; 21 | 22 | screenshot?: ScrapeResponse.Screenshot; 23 | } 24 | 25 | export namespace ScrapeResponse { 26 | export interface Content { 27 | /** 28 | * Cleaned HTML content of the webpage 29 | */ 30 | cleaned_html?: string; 31 | 32 | /** 33 | * Raw HTML content of the webpage 34 | */ 35 | html?: string; 36 | 37 | /** 38 | * Webpage content converted to Markdown 39 | */ 40 | markdown?: string; 41 | 42 | /** 43 | * Webpage content in Readability format 44 | */ 45 | readability?: { [key: string]: unknown }; 46 | } 47 | 48 | export interface Link { 49 | /** 50 | * Text content of the link 51 | */ 52 | text: string; 53 | 54 | /** 55 | * URL of the link 56 | */ 57 | url: string; 58 | } 59 | 60 | export interface Metadata { 61 | /** 62 | * HTTP status code of the response 63 | */ 64 | statusCode: number; 65 | 66 | /** 67 | * Author of the article content 68 | */ 69 | articleAuthor?: string; 70 | 71 | /** 72 | * Author of the webpage content 73 | */ 74 | author?: string; 75 | 76 | /** 77 | * Canonical URL of the webpage 78 | */ 79 | canonical?: string; 80 | 81 | /** 82 | * Description of the webpage 83 | */ 84 | description?: string; 85 | 86 | /** 87 | * Favicon URL of the website 88 | */ 89 | favicon?: string; 90 | 91 | /** 92 | * JSON-LD structured data from the webpage 93 | */ 94 | jsonLd?: unknown; 95 | 96 | /** 97 | * Keywords associated with the webpage 98 | */ 99 | keywords?: string; 100 | 101 | /** 102 | * Detected language of the webpage 103 | */ 104 | language?: string; 105 | 106 | /** 107 | * Last modification time of the content 108 | */ 109 | modifiedTime?: string; 110 | 111 | /** 112 | * Open Graph description 113 | */ 114 | ogDescription?: string; 115 | 116 | /** 117 | * Open Graph image URL 118 | */ 119 | ogImage?: string; 120 | 121 | /** 122 | * Open Graph site name 123 | */ 124 | ogSiteName?: string; 125 | 126 | /** 127 | * Open Graph title 128 | */ 129 | ogTitle?: string; 130 | 131 | /** 132 | * Open Graph URL 133 | */ 134 | ogUrl?: string; 135 | 136 | /** 137 | * Publication time of the content 138 | */ 139 | publishedTime?: string; 140 | 141 | /** 142 | * Timestamp when the scrape was performed 143 | */ 144 | timestamp?: string; 145 | 146 | /** 147 | * Title of the webpage 148 | */ 149 | title?: string; 150 | 151 | /** 152 | * Source URL of the scraped page 153 | */ 154 | urlSource?: string; 155 | } 156 | 157 | export interface Pdf { 158 | /** 159 | * URL of the generated PDF 160 | */ 161 | url: string; 162 | } 163 | 164 | export interface Screenshot { 165 | /** 166 | * URL of the screenshot image 167 | */ 168 | url: string; 169 | } 170 | } 171 | 172 | export interface ScreenshotResponse { 173 | /** 174 | * URL where the screenshot is hosted 175 | */ 176 | url: string; 177 | } 178 | 179 | export interface PdfParams { 180 | /** 181 | * URL of the webpage to convert to PDF 182 | */ 183 | url: string; 184 | 185 | /** 186 | * Delay before generating the PDF (in milliseconds) 187 | */ 188 | delay?: number; 189 | 190 | /** 191 | * The desired region for the action to be performed in 192 | */ 193 | region?: string; 194 | 195 | /** 196 | * Use a Steel-provided residential proxy for generating the PDF 197 | */ 198 | useProxy?: boolean; 199 | } 200 | 201 | export interface ScrapeParams { 202 | /** 203 | * URL of the webpage to scrape 204 | */ 205 | url: string; 206 | 207 | /** 208 | * Delay before scraping (in milliseconds) 209 | */ 210 | delay?: number; 211 | 212 | /** 213 | * Desired format(s) for the scraped content. Default is `html`. 214 | */ 215 | format?: Array<'html' | 'readability' | 'cleaned_html' | 'markdown'>; 216 | 217 | /** 218 | * Include a PDF in the response 219 | */ 220 | pdf?: boolean; 221 | 222 | /** 223 | * The desired region for the action to be performed in 224 | */ 225 | region?: string; 226 | 227 | /** 228 | * Include a screenshot in the response 229 | */ 230 | screenshot?: boolean; 231 | 232 | /** 233 | * Use a Steel-provided residential proxy for the scrape 234 | */ 235 | useProxy?: boolean; 236 | } 237 | 238 | export interface ScreenshotParams { 239 | /** 240 | * URL of the webpage to capture 241 | */ 242 | url: string; 243 | 244 | /** 245 | * Delay before capturing the screenshot (in milliseconds) 246 | */ 247 | delay?: number; 248 | 249 | /** 250 | * Capture the full page screenshot. Default is `false`. 251 | */ 252 | fullPage?: boolean; 253 | 254 | /** 255 | * The desired region for the action to be performed in 256 | */ 257 | region?: string; 258 | 259 | /** 260 | * Use a Steel-provided residential proxy for capturing the screenshot 261 | */ 262 | useProxy?: boolean; 263 | } 264 | 265 | export declare namespace TopLevel { 266 | export { 267 | type PdfResponse as PdfResponse, 268 | type ScrapeResponse as ScrapeResponse, 269 | type ScreenshotResponse as ScreenshotResponse, 270 | type PdfParams as PdfParams, 271 | type ScrapeParams as ScrapeParams, 272 | type ScreenshotParams as ScreenshotParams, 273 | }; 274 | } 275 | -------------------------------------------------------------------------------- /scripts/utils/postprocess-files.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { parse } = require('@typescript-eslint/parser'); 4 | 5 | const pkgImportPath = process.env['PKG_IMPORT_PATH'] ?? 'steel-sdk/'; 6 | 7 | const distDir = 8 | process.env['DIST_PATH'] ? 9 | path.resolve(process.env['DIST_PATH']) 10 | : path.resolve(__dirname, '..', '..', 'dist'); 11 | const distSrcDir = path.join(distDir, 'src'); 12 | 13 | /** 14 | * Quick and dirty AST traversal 15 | */ 16 | function traverse(node, visitor) { 17 | if (!node || typeof node.type !== 'string') return; 18 | visitor.node?.(node); 19 | visitor[node.type]?.(node); 20 | for (const key in node) { 21 | const value = node[key]; 22 | if (Array.isArray(value)) { 23 | for (const elem of value) traverse(elem, visitor); 24 | } else if (value instanceof Object) { 25 | traverse(value, visitor); 26 | } 27 | } 28 | } 29 | 30 | /** 31 | * Helper method for replacing arbitrary ranges of text in input code. 32 | * 33 | * The `replacer` is a function that will be called with a mini-api. For example: 34 | * 35 | * replaceRanges('foobar', ({ replace }) => replace([0, 3], 'baz')) // 'bazbar' 36 | * 37 | * The replaced ranges must not be overlapping. 38 | */ 39 | function replaceRanges(code, replacer) { 40 | const replacements = []; 41 | replacer({ replace: (range, replacement) => replacements.push({ range, replacement }) }); 42 | 43 | if (!replacements.length) return code; 44 | replacements.sort((a, b) => a.range[0] - b.range[0]); 45 | const overlapIndex = replacements.findIndex( 46 | (r, index) => index > 0 && replacements[index - 1].range[1] > r.range[0], 47 | ); 48 | if (overlapIndex >= 0) { 49 | throw new Error( 50 | `replacements overlap: ${JSON.stringify(replacements[overlapIndex - 1])} and ${JSON.stringify( 51 | replacements[overlapIndex], 52 | )}`, 53 | ); 54 | } 55 | 56 | const parts = []; 57 | let end = 0; 58 | for (const { 59 | range: [from, to], 60 | replacement, 61 | } of replacements) { 62 | if (from > end) parts.push(code.substring(end, from)); 63 | parts.push(replacement); 64 | end = to; 65 | } 66 | if (end < code.length) parts.push(code.substring(end)); 67 | return parts.join(''); 68 | } 69 | 70 | /** 71 | * Like calling .map(), where the iteratee is called on the path in every import or export from statement. 72 | * @returns the transformed code 73 | */ 74 | function mapModulePaths(code, iteratee) { 75 | const ast = parse(code, { range: true }); 76 | return replaceRanges(code, ({ replace }) => 77 | traverse(ast, { 78 | node(node) { 79 | switch (node.type) { 80 | case 'ImportDeclaration': 81 | case 'ExportNamedDeclaration': 82 | case 'ExportAllDeclaration': 83 | case 'ImportExpression': 84 | if (node.source) { 85 | const { range, value } = node.source; 86 | const transformed = iteratee(value); 87 | if (transformed !== value) { 88 | replace(range, JSON.stringify(transformed)); 89 | } 90 | } 91 | } 92 | }, 93 | }), 94 | ); 95 | } 96 | 97 | async function* walk(dir) { 98 | for await (const d of await fs.promises.opendir(dir)) { 99 | const entry = path.join(dir, d.name); 100 | if (d.isDirectory()) yield* walk(entry); 101 | else if (d.isFile()) yield entry; 102 | } 103 | } 104 | 105 | async function postprocess() { 106 | for await (const file of walk(path.resolve(__dirname, '..', '..', 'dist'))) { 107 | if (!/\.([cm]?js|(\.d)?[cm]?ts)$/.test(file)) continue; 108 | 109 | const code = await fs.promises.readFile(file, 'utf8'); 110 | 111 | let transformed = mapModulePaths(code, (importPath) => { 112 | if (file.startsWith(distSrcDir)) { 113 | if (importPath.startsWith(pkgImportPath)) { 114 | // convert self-references in dist/src to relative paths 115 | let relativePath = path.relative( 116 | path.dirname(file), 117 | path.join(distSrcDir, importPath.substring(pkgImportPath.length)), 118 | ); 119 | if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`; 120 | return relativePath; 121 | } 122 | return importPath; 123 | } 124 | if (importPath.startsWith('.')) { 125 | // add explicit file extensions to relative imports 126 | const { dir, name } = path.parse(importPath); 127 | const ext = /\.mjs$/.test(file) ? '.mjs' : '.js'; 128 | return `${dir}/${name}${ext}`; 129 | } 130 | return importPath; 131 | }); 132 | 133 | if (file.startsWith(distSrcDir) && !file.endsWith('_shims/index.d.ts')) { 134 | // strip out `unknown extends Foo ? never :` shim guards in dist/src 135 | // to prevent errors from appearing in Go To Source 136 | transformed = transformed.replace( 137 | new RegExp('unknown extends (typeof )?\\S+ \\? \\S+ :\\s*'.replace(/\s+/, '\\s+'), 'gm'), 138 | // replace with same number of characters to avoid breaking source maps 139 | (match) => ' '.repeat(match.length), 140 | ); 141 | } 142 | 143 | if (file.endsWith('.d.ts')) { 144 | // work around bad tsc behavior 145 | // if we have `import { type Readable } from 'steel-sdk/_shims/index'`, 146 | // tsc sometimes replaces `Readable` with `import("stream").Readable` inline 147 | // in the output .d.ts 148 | transformed = transformed.replace(/import\("stream"\).Readable/g, 'Readable'); 149 | } 150 | 151 | // strip out lib="dom" and types="node" references; these are needed at build time, 152 | // but would pollute the user's TS environment 153 | transformed = transformed.replace( 154 | /^ *\/\/\/ * ' '.repeat(match.length - 1) + '\n', 157 | ); 158 | 159 | if (transformed !== code) { 160 | await fs.promises.writeFile(file, transformed, 'utf8'); 161 | console.error(`wrote ${path.relative(process.cwd(), file)}`); 162 | } 163 | } 164 | } 165 | postprocess(); 166 | -------------------------------------------------------------------------------- /src/resources/credentials.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../resource'; 4 | import { isRequestOptions } from '../core'; 5 | import * as Core from '../core'; 6 | 7 | export class Credentials extends APIResource { 8 | /** 9 | * Encrypts and stores credentials for an origin 10 | */ 11 | create( 12 | body: CredentialCreateParams, 13 | options?: Core.RequestOptions, 14 | ): Core.APIPromise { 15 | return this._client.post('/v1/credentials', { body, ...options }); 16 | } 17 | 18 | /** 19 | * Encrypts and updates credentials for an origin 20 | */ 21 | update( 22 | body?: CredentialUpdateParams, 23 | options?: Core.RequestOptions, 24 | ): Core.APIPromise; 25 | update(options?: Core.RequestOptions): Core.APIPromise; 26 | update( 27 | body: CredentialUpdateParams | Core.RequestOptions = {}, 28 | options?: Core.RequestOptions, 29 | ): Core.APIPromise { 30 | if (isRequestOptions(body)) { 31 | return this.update({}, body); 32 | } 33 | return this._client.put('/v1/credentials', { body, ...options }); 34 | } 35 | 36 | /** 37 | * Fetches all credential metadata for the current organization. 38 | */ 39 | list(query?: CredentialListParams, options?: Core.RequestOptions): Core.APIPromise; 40 | list(options?: Core.RequestOptions): Core.APIPromise; 41 | list( 42 | query: CredentialListParams | Core.RequestOptions = {}, 43 | options?: Core.RequestOptions, 44 | ): Core.APIPromise { 45 | if (isRequestOptions(query)) { 46 | return this.list({}, query); 47 | } 48 | return this._client.get('/v1/credentials', { query, ...options }); 49 | } 50 | 51 | /** 52 | * Deletes encrypted credentials from the database 53 | */ 54 | delete( 55 | body: CredentialDeleteParams, 56 | options?: Core.RequestOptions, 57 | ): Core.APIPromise { 58 | return this._client.delete('/v1/credentials', { body, ...options }); 59 | } 60 | } 61 | 62 | export interface CredentialCreateResponse { 63 | /** 64 | * Date and time the credential was created 65 | */ 66 | createdAt: string; 67 | 68 | /** 69 | * Date and time the credential was last updated 70 | */ 71 | updatedAt: string; 72 | 73 | /** 74 | * Label for the credential 75 | */ 76 | label?: string; 77 | 78 | /** 79 | * The namespace the credential is stored against. Defaults to "default". 80 | */ 81 | namespace?: string; 82 | 83 | /** 84 | * Website origin the credential is for 85 | */ 86 | origin?: string; 87 | } 88 | 89 | export interface CredentialUpdateResponse { 90 | /** 91 | * Date and time the credential was created 92 | */ 93 | createdAt: string; 94 | 95 | /** 96 | * Date and time the credential was last updated 97 | */ 98 | updatedAt: string; 99 | 100 | /** 101 | * Label for the credential 102 | */ 103 | label?: string; 104 | 105 | /** 106 | * The namespace the credential is stored against. Defaults to "default". 107 | */ 108 | namespace?: string; 109 | 110 | /** 111 | * Website origin the credential is for 112 | */ 113 | origin?: string; 114 | } 115 | 116 | export interface CredentialListResponse { 117 | credentials: Array; 118 | } 119 | 120 | export namespace CredentialListResponse { 121 | export interface Credential { 122 | /** 123 | * Date and time the credential was created 124 | */ 125 | createdAt: string; 126 | 127 | /** 128 | * Date and time the credential was last updated 129 | */ 130 | updatedAt: string; 131 | 132 | /** 133 | * Label for the credential 134 | */ 135 | label?: string; 136 | 137 | /** 138 | * The namespace the credential is stored against. Defaults to "default". 139 | */ 140 | namespace?: string; 141 | 142 | /** 143 | * Website origin the credential is for 144 | */ 145 | origin?: string; 146 | } 147 | } 148 | 149 | export interface CredentialDeleteResponse { 150 | success: boolean; 151 | } 152 | 153 | export interface CredentialCreateParams { 154 | /** 155 | * Value for the credential 156 | */ 157 | value: { [key: string]: string }; 158 | 159 | /** 160 | * Label for the credential 161 | */ 162 | label?: string; 163 | 164 | /** 165 | * The namespace the credential is stored against. Defaults to "default". 166 | */ 167 | namespace?: string; 168 | 169 | /** 170 | * Website origin the credential is for 171 | */ 172 | origin?: string; 173 | } 174 | 175 | export interface CredentialUpdateParams { 176 | /** 177 | * Label for the credential 178 | */ 179 | label?: string; 180 | 181 | /** 182 | * The namespace the credential is stored against. Defaults to "default". 183 | */ 184 | namespace?: string; 185 | 186 | /** 187 | * Website origin the credential is for 188 | */ 189 | origin?: string; 190 | 191 | /** 192 | * Value for the credential 193 | */ 194 | value?: { [key: string]: string }; 195 | } 196 | 197 | export interface CredentialListParams { 198 | /** 199 | * namespace credential is stored against 200 | */ 201 | namespace?: string; 202 | 203 | /** 204 | * website origin the credential is for 205 | */ 206 | origin?: string; 207 | } 208 | 209 | export interface CredentialDeleteParams { 210 | /** 211 | * Website origin the credential is for 212 | */ 213 | origin: string; 214 | 215 | /** 216 | * The namespace the credential is stored against. Defaults to "default". 217 | */ 218 | namespace?: string; 219 | } 220 | 221 | export declare namespace Credentials { 222 | export { 223 | type CredentialCreateResponse as CredentialCreateResponse, 224 | type CredentialUpdateResponse as CredentialUpdateResponse, 225 | type CredentialListResponse as CredentialListResponse, 226 | type CredentialDeleteResponse as CredentialDeleteResponse, 227 | type CredentialCreateParams as CredentialCreateParams, 228 | type CredentialUpdateParams as CredentialUpdateParams, 229 | type CredentialListParams as CredentialListParams, 230 | type CredentialDeleteParams as CredentialDeleteParams, 231 | }; 232 | } 233 | -------------------------------------------------------------------------------- /tests/api-resources/extensions.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Steel from 'steel-sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Steel({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); 7 | 8 | describe('resource extensions', () => { 9 | test('update', async () => { 10 | const responsePromise = client.extensions.update('extensionId'); 11 | const rawResponse = await responsePromise.asResponse(); 12 | expect(rawResponse).toBeInstanceOf(Response); 13 | const response = await responsePromise; 14 | expect(response).not.toBeInstanceOf(Response); 15 | const dataAndResponse = await responsePromise.withResponse(); 16 | expect(dataAndResponse.data).toBe(response); 17 | expect(dataAndResponse.response).toBe(rawResponse); 18 | }); 19 | 20 | test('update: request options instead of params are passed correctly', async () => { 21 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 22 | await expect( 23 | client.extensions.update('extensionId', { path: '/_stainless_unknown_path' }), 24 | ).rejects.toThrow(Steel.NotFoundError); 25 | }); 26 | 27 | test('update: request options and params are passed correctly', async () => { 28 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 29 | await expect( 30 | client.extensions.update( 31 | 'extensionId', 32 | { file: {}, url: 'https://example.com' }, 33 | { path: '/_stainless_unknown_path' }, 34 | ), 35 | ).rejects.toThrow(Steel.NotFoundError); 36 | }); 37 | 38 | test('list', async () => { 39 | const responsePromise = client.extensions.list(); 40 | const rawResponse = await responsePromise.asResponse(); 41 | expect(rawResponse).toBeInstanceOf(Response); 42 | const response = await responsePromise; 43 | expect(response).not.toBeInstanceOf(Response); 44 | const dataAndResponse = await responsePromise.withResponse(); 45 | expect(dataAndResponse.data).toBe(response); 46 | expect(dataAndResponse.response).toBe(rawResponse); 47 | }); 48 | 49 | test('list: request options instead of params are passed correctly', async () => { 50 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 51 | await expect(client.extensions.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( 52 | Steel.NotFoundError, 53 | ); 54 | }); 55 | 56 | test('delete', async () => { 57 | const responsePromise = client.extensions.delete('extensionId'); 58 | const rawResponse = await responsePromise.asResponse(); 59 | expect(rawResponse).toBeInstanceOf(Response); 60 | const response = await responsePromise; 61 | expect(response).not.toBeInstanceOf(Response); 62 | const dataAndResponse = await responsePromise.withResponse(); 63 | expect(dataAndResponse.data).toBe(response); 64 | expect(dataAndResponse.response).toBe(rawResponse); 65 | }); 66 | 67 | test('delete: request options instead of params are passed correctly', async () => { 68 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 69 | await expect( 70 | client.extensions.delete('extensionId', { path: '/_stainless_unknown_path' }), 71 | ).rejects.toThrow(Steel.NotFoundError); 72 | }); 73 | 74 | test('deleteAll', async () => { 75 | const responsePromise = client.extensions.deleteAll(); 76 | const rawResponse = await responsePromise.asResponse(); 77 | expect(rawResponse).toBeInstanceOf(Response); 78 | const response = await responsePromise; 79 | expect(response).not.toBeInstanceOf(Response); 80 | const dataAndResponse = await responsePromise.withResponse(); 81 | expect(dataAndResponse.data).toBe(response); 82 | expect(dataAndResponse.response).toBe(rawResponse); 83 | }); 84 | 85 | test('deleteAll: request options instead of params are passed correctly', async () => { 86 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 87 | await expect(client.extensions.deleteAll({ path: '/_stainless_unknown_path' })).rejects.toThrow( 88 | Steel.NotFoundError, 89 | ); 90 | }); 91 | 92 | test('download', async () => { 93 | const responsePromise = client.extensions.download('extensionId'); 94 | const rawResponse = await responsePromise.asResponse(); 95 | expect(rawResponse).toBeInstanceOf(Response); 96 | const response = await responsePromise; 97 | expect(response).not.toBeInstanceOf(Response); 98 | const dataAndResponse = await responsePromise.withResponse(); 99 | expect(dataAndResponse.data).toBe(response); 100 | expect(dataAndResponse.response).toBe(rawResponse); 101 | }); 102 | 103 | test('download: request options instead of params are passed correctly', async () => { 104 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 105 | await expect( 106 | client.extensions.download('extensionId', { path: '/_stainless_unknown_path' }), 107 | ).rejects.toThrow(Steel.NotFoundError); 108 | }); 109 | 110 | test('upload', async () => { 111 | const responsePromise = client.extensions.upload(); 112 | const rawResponse = await responsePromise.asResponse(); 113 | expect(rawResponse).toBeInstanceOf(Response); 114 | const response = await responsePromise; 115 | expect(response).not.toBeInstanceOf(Response); 116 | const dataAndResponse = await responsePromise.withResponse(); 117 | expect(dataAndResponse.data).toBe(response); 118 | expect(dataAndResponse.response).toBe(rawResponse); 119 | }); 120 | 121 | test('upload: request options instead of params are passed correctly', async () => { 122 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 123 | await expect(client.extensions.upload({ path: '/_stainless_unknown_path' })).rejects.toThrow( 124 | Steel.NotFoundError, 125 | ); 126 | }); 127 | 128 | test('upload: request options and params are passed correctly', async () => { 129 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 130 | await expect( 131 | client.extensions.upload( 132 | { file: {}, url: 'https://example.com' }, 133 | { path: '/_stainless_unknown_path' }, 134 | ), 135 | ).rejects.toThrow(Steel.NotFoundError); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /api.md: -------------------------------------------------------------------------------- 1 | # Steel 2 | 3 | Types: 4 | 5 | - PdfResponse 6 | - ScrapeResponse 7 | - ScreenshotResponse 8 | 9 | Methods: 10 | 11 | - client.pdf({ ...params }) -> PdfResponse 12 | - client.scrape({ ...params }) -> ScrapeResponse 13 | - client.screenshot({ ...params }) -> ScreenshotResponse 14 | 15 | # Credentials 16 | 17 | Types: 18 | 19 | - CredentialCreateResponse 20 | - CredentialUpdateResponse 21 | - CredentialListResponse 22 | - CredentialDeleteResponse 23 | 24 | Methods: 25 | 26 | - client.credentials.create({ ...params }) -> CredentialCreateResponse 27 | - client.credentials.update({ ...params }) -> CredentialUpdateResponse 28 | - client.credentials.list({ ...params }) -> CredentialListResponse 29 | - client.credentials.delete({ ...params }) -> CredentialDeleteResponse 30 | 31 | # Files 32 | 33 | Types: 34 | 35 | - File 36 | - Fileslist 37 | 38 | Methods: 39 | 40 | - client.files.list() -> Fileslist 41 | - client.files.delete(path\_) -> void 42 | - client.files.download(path\_) -> Response 43 | - client.files.upload({ ...params }) -> File 44 | 45 | # Sessions 46 | 47 | Types: 48 | 49 | - Session 50 | - SessionContext 51 | - Sessionslist 52 | - SessionComputerResponse 53 | - SessionEventsResponse 54 | - SessionLiveDetailsResponse 55 | - SessionReleaseResponse 56 | - SessionReleaseAllResponse 57 | 58 | Methods: 59 | 60 | - client.sessions.create({ ...params }) -> Session 61 | - client.sessions.retrieve(id) -> Session 62 | - client.sessions.list({ ...params }) -> SessionslistSessionsSessionsCursor 63 | - client.sessions.computer(sessionId, { ...params }) -> SessionComputerResponse 64 | - client.sessions.context(id) -> SessionContext 65 | - client.sessions.events(id) -> SessionEventsResponse 66 | - client.sessions.liveDetails(id) -> SessionLiveDetailsResponse 67 | - client.sessions.release(id) -> SessionReleaseResponse 68 | - client.sessions.releaseAll() -> SessionReleaseAllResponse 69 | 70 | ## Files 71 | 72 | Methods: 73 | 74 | - client.sessions.files.list(sessionId) -> Fileslist 75 | - client.sessions.files.delete(sessionId, path\_) -> void 76 | - client.sessions.files.deleteAll(sessionId) -> void 77 | - client.sessions.files.download(sessionId, path\_) -> Response 78 | - client.sessions.files.downloadArchive(sessionId) -> Response 79 | - client.sessions.files.upload(sessionId, { ...params }) -> File 80 | 81 | ## Captchas 82 | 83 | Types: 84 | 85 | - CaptchaSolveImageResponse 86 | - CaptchaStatusResponse 87 | 88 | Methods: 89 | 90 | - client.sessions.captchas.solveImage(sessionId, { ...params }) -> CaptchaSolveImageResponse 91 | - client.sessions.captchas.status(sessionId) -> CaptchaStatusResponse 92 | 93 | # Extensions 94 | 95 | Types: 96 | 97 | - ExtensionUpdateResponse 98 | - ExtensionListResponse 99 | - ExtensionDeleteResponse 100 | - ExtensionDeleteAllResponse 101 | - ExtensionDownloadResponse 102 | - ExtensionUploadResponse 103 | 104 | Methods: 105 | 106 | - client.extensions.update(extensionId, { ...params }) -> ExtensionUpdateResponse 107 | - client.extensions.list() -> ExtensionListResponse 108 | - client.extensions.delete(extensionId) -> ExtensionDeleteResponse 109 | - client.extensions.deleteAll() -> ExtensionDeleteAllResponse 110 | - client.extensions.download(extensionId) -> string 111 | - client.extensions.upload({ ...params }) -> ExtensionUploadResponse 112 | 113 | # Profiles 114 | 115 | Types: 116 | 117 | - ProfileCreateResponse 118 | - ProfileListResponse 119 | 120 | Methods: 121 | 122 | - client.profiles.create({ ...params }) -> ProfileCreateResponse 123 | - client.profiles.list() -> ProfileListResponse 124 | -------------------------------------------------------------------------------- /src/uploads.ts: -------------------------------------------------------------------------------- 1 | import { type RequestOptions } from './core'; 2 | import { 3 | FormData, 4 | File, 5 | type Blob, 6 | type FilePropertyBag, 7 | getMultipartRequestOptions, 8 | type FsReadStream, 9 | isFsReadStream, 10 | } from './_shims/index'; 11 | import { MultipartBody } from './_shims/MultipartBody'; 12 | export { fileFromPath } from './_shims/index'; 13 | 14 | type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | Uint8Array | DataView; 15 | export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | Uint8Array | DataView; 16 | 17 | /** 18 | * Typically, this is a native "File" class. 19 | * 20 | * We provide the {@link toFile} utility to convert a variety of objects 21 | * into the File class. 22 | * 23 | * For convenience, you can also pass a fetch Response, or in Node, 24 | * the result of fs.createReadStream(). 25 | */ 26 | export type Uploadable = FileLike | ResponseLike | FsReadStream; 27 | 28 | /** 29 | * Intended to match web.Blob, node.Blob, node-fetch.Blob, etc. 30 | */ 31 | export interface BlobLike { 32 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ 33 | readonly size: number; 34 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ 35 | readonly type: string; 36 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ 37 | text(): Promise; 38 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ 39 | slice(start?: number, end?: number): BlobLike; 40 | // unfortunately @types/node-fetch@^2.6.4 doesn't type the arrayBuffer method 41 | } 42 | 43 | /** 44 | * Intended to match web.File, node.File, node-fetch.File, etc. 45 | */ 46 | export interface FileLike extends BlobLike { 47 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ 48 | readonly lastModified: number; 49 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ 50 | readonly name: string; 51 | } 52 | 53 | /** 54 | * Intended to match web.Response, node.Response, node-fetch.Response, etc. 55 | */ 56 | export interface ResponseLike { 57 | url: string; 58 | blob(): Promise; 59 | } 60 | 61 | export const isResponseLike = (value: any): value is ResponseLike => 62 | value != null && 63 | typeof value === 'object' && 64 | typeof value.url === 'string' && 65 | typeof value.blob === 'function'; 66 | 67 | export const isFileLike = (value: any): value is FileLike => 68 | value != null && 69 | typeof value === 'object' && 70 | typeof value.name === 'string' && 71 | typeof value.lastModified === 'number' && 72 | isBlobLike(value); 73 | 74 | /** 75 | * The BlobLike type omits arrayBuffer() because @types/node-fetch@^2.6.4 lacks it; but this check 76 | * adds the arrayBuffer() method type because it is available and used at runtime 77 | */ 78 | export const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => 79 | value != null && 80 | typeof value === 'object' && 81 | typeof value.size === 'number' && 82 | typeof value.type === 'string' && 83 | typeof value.text === 'function' && 84 | typeof value.slice === 'function' && 85 | typeof value.arrayBuffer === 'function'; 86 | 87 | export const isUploadable = (value: any): value is Uploadable => { 88 | return isFileLike(value) || isResponseLike(value) || isFsReadStream(value); 89 | }; 90 | 91 | export type ToFileInput = Uploadable | Exclude | AsyncIterable; 92 | 93 | /** 94 | * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats 95 | * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s 96 | * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible 97 | * @param {Object=} options additional properties 98 | * @param {string=} options.type the MIME type of the content 99 | * @param {number=} options.lastModified the last modified timestamp 100 | * @returns a {@link File} with the given properties 101 | */ 102 | export async function toFile( 103 | value: ToFileInput | PromiseLike, 104 | name?: string | null | undefined, 105 | options?: FilePropertyBag | undefined, 106 | ): Promise { 107 | // If it's a promise, resolve it. 108 | value = await value; 109 | 110 | // If we've been given a `File` we don't need to do anything 111 | if (isFileLike(value)) { 112 | return value; 113 | } 114 | 115 | if (isResponseLike(value)) { 116 | const blob = await value.blob(); 117 | name ||= new URL(value.url).pathname.split(/[\\/]/).pop() ?? 'unknown_file'; 118 | 119 | // we need to convert the `Blob` into an array buffer because the `Blob` class 120 | // that `node-fetch` defines is incompatible with the web standard which results 121 | // in `new File` interpreting it as a string instead of binary data. 122 | const data = isBlobLike(blob) ? [(await blob.arrayBuffer()) as any] : [blob]; 123 | 124 | return new File(data, name, options); 125 | } 126 | 127 | const bits = await getBytes(value); 128 | 129 | name ||= getName(value) ?? 'unknown_file'; 130 | 131 | if (!options?.type) { 132 | const type = (bits[0] as any)?.type; 133 | if (typeof type === 'string') { 134 | options = { ...options, type }; 135 | } 136 | } 137 | 138 | return new File(bits, name, options); 139 | } 140 | 141 | async function getBytes(value: ToFileInput): Promise> { 142 | let parts: Array = []; 143 | if ( 144 | typeof value === 'string' || 145 | ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc. 146 | value instanceof ArrayBuffer 147 | ) { 148 | parts.push(value); 149 | } else if (isBlobLike(value)) { 150 | parts.push(await value.arrayBuffer()); 151 | } else if ( 152 | isAsyncIterableIterator(value) // includes Readable, ReadableStream, etc. 153 | ) { 154 | for await (const chunk of value) { 155 | parts.push(chunk as BlobPart); // TODO, consider validating? 156 | } 157 | } else { 158 | throw new Error( 159 | `Unexpected data type: ${typeof value}; constructor: ${value?.constructor 160 | ?.name}; props: ${propsForError(value)}`, 161 | ); 162 | } 163 | 164 | return parts; 165 | } 166 | 167 | function propsForError(value: any): string { 168 | const props = Object.getOwnPropertyNames(value); 169 | return `[${props.map((p) => `"${p}"`).join(', ')}]`; 170 | } 171 | 172 | function getName(value: any): string | undefined { 173 | return ( 174 | getStringFromMaybeBuffer(value.name) || 175 | getStringFromMaybeBuffer(value.filename) || 176 | // For fs.ReadStream 177 | getStringFromMaybeBuffer(value.path)?.split(/[\\/]/).pop() 178 | ); 179 | } 180 | 181 | const getStringFromMaybeBuffer = (x: string | Buffer | unknown): string | undefined => { 182 | if (typeof x === 'string') return x; 183 | if (typeof Buffer !== 'undefined' && x instanceof Buffer) return String(x); 184 | return undefined; 185 | }; 186 | 187 | const isAsyncIterableIterator = (value: any): value is AsyncIterableIterator => 188 | value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; 189 | 190 | export const isMultipartBody = (body: any): body is MultipartBody => 191 | body && typeof body === 'object' && body.body && body[Symbol.toStringTag] === 'MultipartBody'; 192 | 193 | /** 194 | * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value. 195 | * Otherwise returns the request as is. 196 | */ 197 | export const maybeMultipartFormRequestOptions = async >( 198 | opts: RequestOptions, 199 | ): Promise> => { 200 | if (!hasUploadableValue(opts.body)) return opts; 201 | 202 | const form = await createForm(opts.body); 203 | return getMultipartRequestOptions(form, opts); 204 | }; 205 | 206 | export const multipartFormRequestOptions = async >( 207 | opts: RequestOptions, 208 | ): Promise> => { 209 | const form = await createForm(opts.body); 210 | return getMultipartRequestOptions(form, opts); 211 | }; 212 | 213 | export const createForm = async >(body: T | undefined): Promise => { 214 | const form = new FormData(); 215 | await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))); 216 | return form; 217 | }; 218 | 219 | const hasUploadableValue = (value: unknown): boolean => { 220 | if (isUploadable(value)) return true; 221 | if (Array.isArray(value)) return value.some(hasUploadableValue); 222 | if (value && typeof value === 'object') { 223 | for (const k in value) { 224 | if (hasUploadableValue((value as any)[k])) return true; 225 | } 226 | } 227 | return false; 228 | }; 229 | 230 | const addFormValue = async (form: FormData, key: string, value: unknown): Promise => { 231 | if (value === undefined) return; 232 | if (value == null) { 233 | throw new TypeError( 234 | `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`, 235 | ); 236 | } 237 | 238 | // TODO: make nested formats configurable 239 | if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { 240 | form.append(key, String(value)); 241 | } else if (isUploadable(value)) { 242 | const file = await toFile(value); 243 | form.append(key, file as File); 244 | } else if (Array.isArray(value)) { 245 | await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); 246 | } else if (typeof value === 'object') { 247 | await Promise.all( 248 | Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), 249 | ); 250 | } else { 251 | throw new TypeError( 252 | `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`, 253 | ); 254 | } 255 | }; 256 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Steel Node API Library 2 | 3 | [![NPM version](https://img.shields.io/npm/v/steel-sdk.svg)](https://npmjs.org/package/steel-sdk) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/steel-sdk) 4 | 5 | This library provides convenient access to the Steel REST API from server-side TypeScript or JavaScript. 6 | 7 | The REST API documentation can be found on [docs.steel.dev](https://docs.steel.dev). The full API of this library can be found in [api.md](api.md). 8 | 9 | It is generated with [Stainless](https://www.stainlessapi.com/). 10 | 11 | ## Installation 12 | 13 | ```sh 14 | npm install steel-sdk 15 | ``` 16 | 17 | ## Usage 18 | 19 | The full API of this library can be found in [api.md](api.md). 20 | 21 | 22 | ```js 23 | import Steel from 'steel-sdk'; 24 | 25 | const client = new Steel(); 26 | 27 | const session = await client.sessions.create({ timeout: 20000, useProxy: true }); 28 | 29 | console.log(session.id); 30 | ``` 31 | 32 | ### Request & Response types 33 | 34 | This library includes TypeScript definitions for all request params and response fields. You may import and use them like so: 35 | 36 | 37 | ```ts 38 | import Steel from 'steel-sdk'; 39 | 40 | const client = new Steel(); 41 | 42 | const session: Steel.Session = await client.sessions.create(); 43 | ``` 44 | 45 | Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors. 46 | 47 | ## Handling errors 48 | 49 | When the library is unable to connect to the API, 50 | or if the API returns a non-success status code (i.e., 4xx or 5xx response), 51 | a subclass of `APIError` will be thrown: 52 | 53 | 54 | ```ts 55 | const session = await client.sessions.create().catch(async (err) => { 56 | if (err instanceof Steel.APIError) { 57 | console.log(err.status); // 400 58 | console.log(err.name); // BadRequestError 59 | console.log(err.headers); // {server: 'nginx', ...} 60 | } else { 61 | throw err; 62 | } 63 | }); 64 | ``` 65 | 66 | Error codes are as follows: 67 | 68 | | Status Code | Error Type | 69 | | ----------- | -------------------------- | 70 | | 400 | `BadRequestError` | 71 | | 401 | `AuthenticationError` | 72 | | 403 | `PermissionDeniedError` | 73 | | 404 | `NotFoundError` | 74 | | 422 | `UnprocessableEntityError` | 75 | | 429 | `RateLimitError` | 76 | | >=500 | `InternalServerError` | 77 | | N/A | `APIConnectionError` | 78 | 79 | ### Retries 80 | 81 | Certain errors will be automatically retried 2 times by default, with a short exponential backoff. 82 | Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 83 | 429 Rate Limit, and >=500 Internal errors will all be retried by default. 84 | 85 | You can use the `maxRetries` option to configure or disable this: 86 | 87 | 88 | ```js 89 | // Configure the default for all requests: 90 | const client = new Steel({ 91 | maxRetries: 0, // default is 2 92 | }); 93 | 94 | // Or, configure per-request: 95 | await client.sessions.create({ 96 | maxRetries: 5, 97 | }); 98 | ``` 99 | 100 | ### Timeouts 101 | 102 | Requests time out after 1 minute by default. You can configure this with a `timeout` option: 103 | 104 | 105 | ```ts 106 | // Configure the default for all requests: 107 | const client = new Steel({ 108 | timeout: 20 * 1000, // 20 seconds (default is 1 minute) 109 | }); 110 | 111 | // Override per-request: 112 | await client.sessions.create({ 113 | timeout: 5 * 1000, 114 | }); 115 | ``` 116 | 117 | On timeout, an `APIConnectionTimeoutError` is thrown. 118 | 119 | Note that requests which time out will be [retried twice by default](#retries). 120 | 121 | ## Auto-pagination 122 | 123 | List methods in the Steel API are paginated. 124 | You can use the `for await … of` syntax to iterate through items across all pages: 125 | 126 | ```ts 127 | async function fetchAllSessions(params) { 128 | const allSessions = []; 129 | // Automatically fetches more pages as needed. 130 | for await (const session of client.sessions.list({ status: 'live' })) { 131 | allSessions.push(session); 132 | } 133 | return allSessions; 134 | } 135 | ``` 136 | 137 | Alternatively, you can request a single page at a time: 138 | 139 | ```ts 140 | let page = await client.sessions.list({ status: 'live' }); 141 | for (const session of page.sessions) { 142 | console.log(session); 143 | } 144 | 145 | // Convenience methods are provided for manually paginating: 146 | while (page.hasNextPage()) { 147 | page = await page.getNextPage(); 148 | // ... 149 | } 150 | ``` 151 | 152 | ## Advanced Usage 153 | 154 | ### Accessing raw Response data (e.g., headers) 155 | 156 | The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return. 157 | 158 | You can also use the `.withResponse()` method to get the raw `Response` along with the parsed data. 159 | 160 | 161 | ```ts 162 | const client = new Steel(); 163 | 164 | const response = await client.sessions.create().asResponse(); 165 | console.log(response.headers.get('X-My-Header')); 166 | console.log(response.statusText); // access the underlying Response object 167 | 168 | const { data: session, response: raw } = await client.sessions.create().withResponse(); 169 | console.log(raw.headers.get('X-My-Header')); 170 | console.log(session.id); 171 | ``` 172 | 173 | ### Making custom/undocumented requests 174 | 175 | This library is typed for convenient access to the documented API. If you need to access undocumented 176 | endpoints, params, or response properties, the library can still be used. 177 | 178 | #### Undocumented endpoints 179 | 180 | To make requests to undocumented endpoints, you can use `client.get`, `client.post`, and other HTTP verbs. 181 | Options on the client, such as retries, will be respected when making these requests. 182 | 183 | ```ts 184 | await client.post('/some/path', { 185 | body: { some_prop: 'foo' }, 186 | query: { some_query_arg: 'bar' }, 187 | }); 188 | ``` 189 | 190 | #### Undocumented request params 191 | 192 | To make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented 193 | parameter. This library doesn't validate at runtime that the request matches the type, so any extra values you 194 | send will be sent as-is. 195 | 196 | ```ts 197 | client.foo.create({ 198 | foo: 'my_param', 199 | bar: 12, 200 | // @ts-expect-error baz is not yet public 201 | baz: 'undocumented option', 202 | }); 203 | ``` 204 | 205 | For requests with the `GET` verb, any extra params will be in the query, all other requests will send the 206 | extra param in the body. 207 | 208 | If you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request 209 | options. 210 | 211 | #### Undocumented response properties 212 | 213 | To access undocumented response properties, you may access the response object with `// @ts-expect-error` on 214 | the response object, or cast the response object to the requisite type. Like the request params, we do not 215 | validate or strip extra properties from the response from the API. 216 | 217 | ### Customizing the fetch client 218 | 219 | By default, this library uses `node-fetch` in Node, and expects a global `fetch` function in other environments. 220 | 221 | If you would prefer to use a global, web-standards-compliant `fetch` function even in a Node environment, 222 | (for example, if you are running Node with `--experimental-fetch` or using NextJS which polyfills with `undici`), 223 | add the following import before your first import `from "Steel"`: 224 | 225 | ```ts 226 | // Tell TypeScript and the package to use the global web fetch instead of node-fetch. 227 | // Note, despite the name, this does not add any polyfills, but expects them to be provided if needed. 228 | import 'steel-sdk/shims/web'; 229 | import Steel from 'steel-sdk'; 230 | ``` 231 | 232 | To do the inverse, add `import "steel-sdk/shims/node"` (which does import polyfills). 233 | This can also be useful if you are getting the wrong TypeScript types for `Response` ([more details](https://github.com/steel-dev/steel-node/tree/main/src/_shims#readme)). 234 | 235 | ### Logging and middleware 236 | 237 | You may also provide a custom `fetch` function when instantiating the client, 238 | which can be used to inspect or alter the `Request` or `Response` before/after each request: 239 | 240 | ```ts 241 | import { fetch } from 'undici'; // as one example 242 | import Steel from 'steel-sdk'; 243 | 244 | const client = new Steel({ 245 | fetch: async (url: RequestInfo, init?: RequestInit): Promise => { 246 | console.log('About to make a request', url, init); 247 | const response = await fetch(url, init); 248 | console.log('Got response', response); 249 | return response; 250 | }, 251 | }); 252 | ``` 253 | 254 | Note that if given a `DEBUG=true` environment variable, this library will log all requests and responses automatically. 255 | This is intended for debugging purposes only and may change in the future without notice. 256 | 257 | ### Configuring an HTTP(S) Agent (e.g., for proxies) 258 | 259 | By default, this library uses a stable agent for all http/https requests to reuse TCP connections, eliminating many TCP & TLS handshakes and shaving around 100ms off most requests. 260 | 261 | If you would like to disable or customize this behavior, for example to use the API behind a proxy, you can pass an `httpAgent` which is used for all requests (be they http or https), for example: 262 | 263 | 264 | ```ts 265 | import http from 'http'; 266 | import { HttpsProxyAgent } from 'https-proxy-agent'; 267 | 268 | // Configure the default for all requests: 269 | const client = new Steel({ 270 | httpAgent: new HttpsProxyAgent(process.env.PROXY_URL), 271 | }); 272 | 273 | // Override per-request: 274 | await client.sessions.create({ 275 | httpAgent: new http.Agent({ keepAlive: false }), 276 | }); 277 | ``` 278 | 279 | ## Semantic versioning 280 | 281 | 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: 282 | 283 | 1. Changes that only affect static types, without breaking runtime behavior. 284 | 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.)_ 285 | 3. Changes that we do not expect to impact the vast majority of users in practice. 286 | 287 | We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. 288 | 289 | We are keen for your feedback; please open an [issue](https://www.github.com/steel-dev/steel-node/issues) with questions, bugs, or suggestions. 290 | 291 | ## Requirements 292 | 293 | TypeScript >= 4.5 is supported. 294 | 295 | The following runtimes are supported: 296 | 297 | - Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more) 298 | - Node.js 18 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions. 299 | - Deno v1.28.0 or higher. 300 | - Bun 1.0 or later. 301 | - Cloudflare Workers. 302 | - Vercel Edge Runtime. 303 | - Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time). 304 | - Nitro v2.6 or greater. 305 | 306 | Note that React Native is not supported at this time. 307 | 308 | If you are interested in other runtime environments, please open or upvote an issue on GitHub. 309 | 310 | ## Contributing 311 | 312 | See [the contributing documentation](./CONTRIBUTING.md). 313 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2025 Steel 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /tests/api-resources/sessions/sessions.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Steel from 'steel-sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Steel({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); 7 | 8 | describe('resource sessions', () => { 9 | test('create', async () => { 10 | const responsePromise = client.sessions.create(); 11 | const rawResponse = await responsePromise.asResponse(); 12 | expect(rawResponse).toBeInstanceOf(Response); 13 | const response = await responsePromise; 14 | expect(response).not.toBeInstanceOf(Response); 15 | const dataAndResponse = await responsePromise.withResponse(); 16 | expect(dataAndResponse.data).toBe(response); 17 | expect(dataAndResponse.response).toBe(rawResponse); 18 | }); 19 | 20 | test('create: request options and params are passed correctly', async () => { 21 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 22 | await expect( 23 | client.sessions.create( 24 | { 25 | blockAds: true, 26 | concurrency: 0, 27 | credentials: { autoSubmit: true, blurFields: true, exactOrigin: true }, 28 | debugConfig: { interactive: true, systemCursor: true }, 29 | deviceConfig: { device: 'desktop' }, 30 | dimensions: { height: 0, width: 0 }, 31 | extensionIds: ['string'], 32 | headless: true, 33 | isSelenium: true, 34 | namespace: 'namespace', 35 | optimizeBandwidth: true, 36 | persistProfile: true, 37 | profileId: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', 38 | proxyUrl: 'https://example.com', 39 | region: 'region', 40 | sessionContext: { 41 | cookies: [ 42 | { 43 | name: 'name', 44 | value: 'value', 45 | domain: 'domain', 46 | expires: 0, 47 | httpOnly: true, 48 | partitionKey: { hasCrossSiteAncestor: true, topLevelSite: 'topLevelSite' }, 49 | path: 'path', 50 | priority: 'Low', 51 | sameParty: true, 52 | sameSite: 'Strict', 53 | secure: true, 54 | session: true, 55 | size: 0, 56 | sourcePort: 0, 57 | sourceScheme: 'Unset', 58 | url: 'url', 59 | }, 60 | ], 61 | indexedDB: { 62 | foo: [ 63 | { 64 | id: 0, 65 | data: [ 66 | { 67 | id: 0, 68 | name: 'name', 69 | records: [ 70 | { 71 | blobFiles: [ 72 | { 73 | blobNumber: 0, 74 | mimeType: 'mimeType', 75 | size: 0, 76 | filename: 'filename', 77 | lastModified: '2019-12-27T18:11:19.117Z', 78 | path: 'path', 79 | }, 80 | ], 81 | key: {}, 82 | value: {}, 83 | }, 84 | ], 85 | }, 86 | ], 87 | name: 'name', 88 | }, 89 | ], 90 | }, 91 | localStorage: { foo: { foo: 'string' } }, 92 | sessionStorage: { foo: { foo: 'string' } }, 93 | }, 94 | sessionId: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', 95 | solveCaptcha: true, 96 | stealthConfig: { humanizeInteractions: true, skipFingerprintInjection: true }, 97 | timeout: 0, 98 | useProxy: {}, 99 | userAgent: 'userAgent', 100 | }, 101 | { path: '/_stainless_unknown_path' }, 102 | ), 103 | ).rejects.toThrow(Steel.NotFoundError); 104 | }); 105 | 106 | test('retrieve', async () => { 107 | const responsePromise = client.sessions.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); 108 | const rawResponse = await responsePromise.asResponse(); 109 | expect(rawResponse).toBeInstanceOf(Response); 110 | const response = await responsePromise; 111 | expect(response).not.toBeInstanceOf(Response); 112 | const dataAndResponse = await responsePromise.withResponse(); 113 | expect(dataAndResponse.data).toBe(response); 114 | expect(dataAndResponse.response).toBe(rawResponse); 115 | }); 116 | 117 | test('retrieve: request options instead of params are passed correctly', async () => { 118 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 119 | await expect( 120 | client.sessions.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { path: '/_stainless_unknown_path' }), 121 | ).rejects.toThrow(Steel.NotFoundError); 122 | }); 123 | 124 | test('list', async () => { 125 | const responsePromise = client.sessions.list(); 126 | const rawResponse = await responsePromise.asResponse(); 127 | expect(rawResponse).toBeInstanceOf(Response); 128 | const response = await responsePromise; 129 | expect(response).not.toBeInstanceOf(Response); 130 | const dataAndResponse = await responsePromise.withResponse(); 131 | expect(dataAndResponse.data).toBe(response); 132 | expect(dataAndResponse.response).toBe(rawResponse); 133 | }); 134 | 135 | test('list: request options instead of params are passed correctly', async () => { 136 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 137 | await expect(client.sessions.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( 138 | Steel.NotFoundError, 139 | ); 140 | }); 141 | 142 | test('list: request options and params are passed correctly', async () => { 143 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 144 | await expect( 145 | client.sessions.list( 146 | { cursorId: 'cursorId', limit: 0, status: 'live' }, 147 | { path: '/_stainless_unknown_path' }, 148 | ), 149 | ).rejects.toThrow(Steel.NotFoundError); 150 | }); 151 | 152 | test('computer: only required params', async () => { 153 | const responsePromise = client.sessions.computer('sessionId', { 154 | action: 'move_mouse', 155 | coordinates: [0, 0], 156 | }); 157 | const rawResponse = await responsePromise.asResponse(); 158 | expect(rawResponse).toBeInstanceOf(Response); 159 | const response = await responsePromise; 160 | expect(response).not.toBeInstanceOf(Response); 161 | const dataAndResponse = await responsePromise.withResponse(); 162 | expect(dataAndResponse.data).toBe(response); 163 | expect(dataAndResponse.response).toBe(rawResponse); 164 | }); 165 | 166 | test('computer: required and optional params', async () => { 167 | const response = await client.sessions.computer('sessionId', { 168 | action: 'move_mouse', 169 | coordinates: [0, 0], 170 | hold_keys: ['string'], 171 | screenshot: true, 172 | }); 173 | }); 174 | 175 | test('context', async () => { 176 | const responsePromise = client.sessions.context('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); 177 | const rawResponse = await responsePromise.asResponse(); 178 | expect(rawResponse).toBeInstanceOf(Response); 179 | const response = await responsePromise; 180 | expect(response).not.toBeInstanceOf(Response); 181 | const dataAndResponse = await responsePromise.withResponse(); 182 | expect(dataAndResponse.data).toBe(response); 183 | expect(dataAndResponse.response).toBe(rawResponse); 184 | }); 185 | 186 | test('context: request options instead of params are passed correctly', async () => { 187 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 188 | await expect( 189 | client.sessions.context('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { path: '/_stainless_unknown_path' }), 190 | ).rejects.toThrow(Steel.NotFoundError); 191 | }); 192 | 193 | test('events', async () => { 194 | const responsePromise = client.sessions.events('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); 195 | const rawResponse = await responsePromise.asResponse(); 196 | expect(rawResponse).toBeInstanceOf(Response); 197 | const response = await responsePromise; 198 | expect(response).not.toBeInstanceOf(Response); 199 | const dataAndResponse = await responsePromise.withResponse(); 200 | expect(dataAndResponse.data).toBe(response); 201 | expect(dataAndResponse.response).toBe(rawResponse); 202 | }); 203 | 204 | test('events: request options instead of params are passed correctly', async () => { 205 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 206 | await expect( 207 | client.sessions.events('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { path: '/_stainless_unknown_path' }), 208 | ).rejects.toThrow(Steel.NotFoundError); 209 | }); 210 | 211 | test('liveDetails', async () => { 212 | const responsePromise = client.sessions.liveDetails('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); 213 | const rawResponse = await responsePromise.asResponse(); 214 | expect(rawResponse).toBeInstanceOf(Response); 215 | const response = await responsePromise; 216 | expect(response).not.toBeInstanceOf(Response); 217 | const dataAndResponse = await responsePromise.withResponse(); 218 | expect(dataAndResponse.data).toBe(response); 219 | expect(dataAndResponse.response).toBe(rawResponse); 220 | }); 221 | 222 | test('liveDetails: request options instead of params are passed correctly', async () => { 223 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 224 | await expect( 225 | client.sessions.liveDetails('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { 226 | path: '/_stainless_unknown_path', 227 | }), 228 | ).rejects.toThrow(Steel.NotFoundError); 229 | }); 230 | 231 | test('release', async () => { 232 | const responsePromise = client.sessions.release('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); 233 | const rawResponse = await responsePromise.asResponse(); 234 | expect(rawResponse).toBeInstanceOf(Response); 235 | const response = await responsePromise; 236 | expect(response).not.toBeInstanceOf(Response); 237 | const dataAndResponse = await responsePromise.withResponse(); 238 | expect(dataAndResponse.data).toBe(response); 239 | expect(dataAndResponse.response).toBe(rawResponse); 240 | }); 241 | 242 | test('release: request options and params are passed correctly', async () => { 243 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 244 | await expect( 245 | client.sessions.release( 246 | '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', 247 | {}, 248 | { path: '/_stainless_unknown_path' }, 249 | ), 250 | ).rejects.toThrow(Steel.NotFoundError); 251 | }); 252 | 253 | test('releaseAll', async () => { 254 | const responsePromise = client.sessions.releaseAll(); 255 | const rawResponse = await responsePromise.asResponse(); 256 | expect(rawResponse).toBeInstanceOf(Response); 257 | const response = await responsePromise; 258 | expect(response).not.toBeInstanceOf(Response); 259 | const dataAndResponse = await responsePromise.withResponse(); 260 | expect(dataAndResponse.data).toBe(response); 261 | expect(dataAndResponse.response).toBe(rawResponse); 262 | }); 263 | 264 | test('releaseAll: request options and params are passed correctly', async () => { 265 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 266 | await expect(client.sessions.releaseAll({}, { path: '/_stainless_unknown_path' })).rejects.toThrow( 267 | Steel.NotFoundError, 268 | ); 269 | }); 270 | }); 271 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { type Agent } from './_shims/index'; 4 | import * as Core from './core'; 5 | import * as Errors from './error'; 6 | import * as Pagination from './pagination'; 7 | import { type SessionsCursorParams, SessionsCursorResponse } from './pagination'; 8 | import * as Uploads from './uploads'; 9 | import * as API from './resources/index'; 10 | import * as TopLevelAPI from './resources/top-level'; 11 | import { 12 | PdfParams, 13 | PdfResponse, 14 | ScrapeParams, 15 | ScrapeResponse, 16 | ScreenshotParams, 17 | ScreenshotResponse, 18 | } from './resources/top-level'; 19 | import { 20 | CredentialCreateParams, 21 | CredentialCreateResponse, 22 | CredentialDeleteParams, 23 | CredentialDeleteResponse, 24 | CredentialListParams, 25 | CredentialListResponse, 26 | CredentialUpdateParams, 27 | CredentialUpdateResponse, 28 | Credentials, 29 | } from './resources/credentials'; 30 | import { 31 | ExtensionDeleteAllResponse, 32 | ExtensionDeleteResponse, 33 | ExtensionDownloadResponse, 34 | ExtensionListResponse, 35 | ExtensionUpdateParams, 36 | ExtensionUpdateResponse, 37 | ExtensionUploadParams, 38 | ExtensionUploadResponse, 39 | Extensions, 40 | } from './resources/extensions'; 41 | import { File, FileUploadParams, Files, Fileslist } from './resources/files'; 42 | import { 43 | ProfileCreateParams, 44 | ProfileCreateResponse, 45 | ProfileListResponse, 46 | Profiles, 47 | } from './resources/profiles'; 48 | import { 49 | Session, 50 | SessionComputerParams, 51 | SessionComputerResponse, 52 | SessionContext, 53 | SessionCreateParams, 54 | SessionEventsResponse, 55 | SessionListParams, 56 | SessionLiveDetailsResponse, 57 | SessionReleaseAllParams, 58 | SessionReleaseAllResponse, 59 | SessionReleaseParams, 60 | SessionReleaseResponse, 61 | Sessions, 62 | Sessionslist, 63 | SessionslistSessionsSessionsCursor, 64 | } from './resources/sessions/sessions'; 65 | 66 | export interface ClientOptions { 67 | /** 68 | * The API key required to authenticate the request. Typically provided in the header. 69 | */ 70 | steelAPIKey?: string | null | undefined; 71 | 72 | /** 73 | * Override the default base URL for the API, e.g., "https://api.example.com/v2/" 74 | * 75 | * Defaults to process.env['STEEL_BASE_URL']. 76 | */ 77 | baseURL?: string | null | undefined; 78 | 79 | /** 80 | * The maximum amount of time (in milliseconds) that the client should wait for a response 81 | * from the server before timing out a single request. 82 | * 83 | * Note that request timeouts are retried by default, so in a worst-case scenario you may wait 84 | * much longer than this timeout before the promise succeeds or fails. 85 | * 86 | * @unit milliseconds 87 | */ 88 | timeout?: number | undefined; 89 | 90 | /** 91 | * An HTTP agent used to manage HTTP(S) connections. 92 | * 93 | * If not provided, an agent will be constructed by default in the Node.js environment, 94 | * otherwise no agent is used. 95 | */ 96 | httpAgent?: Agent | undefined; 97 | 98 | /** 99 | * Specify a custom `fetch` function implementation. 100 | * 101 | * If not provided, we use `node-fetch` on Node.js and otherwise expect that `fetch` is 102 | * defined globally. 103 | */ 104 | fetch?: Core.Fetch | undefined; 105 | 106 | /** 107 | * The maximum number of times that the client will retry a request in case of a 108 | * temporary failure, like a network error or a 5XX error from the server. 109 | * 110 | * @default 2 111 | */ 112 | maxRetries?: number | undefined; 113 | 114 | /** 115 | * Default headers to include with every request to the API. 116 | * 117 | * These can be removed in individual requests by explicitly setting the 118 | * header to `undefined` or `null` in request options. 119 | */ 120 | defaultHeaders?: Core.Headers | undefined; 121 | 122 | /** 123 | * Default query parameters to include with every request to the API. 124 | * 125 | * These can be removed in individual requests by explicitly setting the 126 | * param to `undefined` in request options. 127 | */ 128 | defaultQuery?: Core.DefaultQuery | undefined; 129 | } 130 | 131 | /** 132 | * API Client for interfacing with the Steel API. 133 | */ 134 | export class Steel extends Core.APIClient { 135 | steelAPIKey: string | null; 136 | 137 | private _options: ClientOptions; 138 | 139 | /** 140 | * API Client for interfacing with the Steel API. 141 | * 142 | * @param {string | null | undefined} [opts.steelAPIKey=process.env['STEEL_API_KEY'] ?? null] 143 | * @param {string} [opts.baseURL=process.env['STEEL_BASE_URL'] ?? https://api.steel.dev] - Override the default base URL for the API. 144 | * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. 145 | * @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections. 146 | * @param {Core.Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. 147 | * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. 148 | * @param {Core.Headers} opts.defaultHeaders - Default headers to include with every request to the API. 149 | * @param {Core.DefaultQuery} opts.defaultQuery - Default query parameters to include with every request to the API. 150 | */ 151 | constructor({ 152 | baseURL = Core.readEnv('STEEL_BASE_URL'), 153 | steelAPIKey = Core.readEnv('STEEL_API_KEY') ?? null, 154 | ...opts 155 | }: ClientOptions = {}) { 156 | const options: ClientOptions = { 157 | steelAPIKey, 158 | ...opts, 159 | baseURL: baseURL || `https://api.steel.dev`, 160 | }; 161 | 162 | super({ 163 | baseURL: options.baseURL!, 164 | baseURLOverridden: baseURL ? baseURL !== 'https://api.steel.dev' : false, 165 | timeout: options.timeout ?? 60000 /* 1 minute */, 166 | httpAgent: options.httpAgent, 167 | maxRetries: options.maxRetries, 168 | fetch: options.fetch, 169 | }); 170 | 171 | this._options = options; 172 | 173 | this.steelAPIKey = steelAPIKey; 174 | } 175 | 176 | credentials: API.Credentials = new API.Credentials(this); 177 | files: API.Files = new API.Files(this); 178 | sessions: API.Sessions = new API.Sessions(this); 179 | extensions: API.Extensions = new API.Extensions(this); 180 | profiles: API.Profiles = new API.Profiles(this); 181 | 182 | /** 183 | * Check whether the base URL is set to its default. 184 | */ 185 | #baseURLOverridden(): boolean { 186 | return this.baseURL !== 'https://api.steel.dev'; 187 | } 188 | 189 | /** 190 | * Generates a PDF from a specified webpage. 191 | */ 192 | pdf(body: TopLevelAPI.PdfParams, options?: Core.RequestOptions): Core.APIPromise { 193 | return this.post('/v1/pdf', { body, ...options }); 194 | } 195 | 196 | /** 197 | * Extracts content from a specified URL. 198 | */ 199 | scrape( 200 | body: TopLevelAPI.ScrapeParams, 201 | options?: Core.RequestOptions, 202 | ): Core.APIPromise { 203 | return this.post('/v1/scrape', { body, ...options }); 204 | } 205 | 206 | /** 207 | * Captures a screenshot of a specified webpage. 208 | */ 209 | screenshot( 210 | body: TopLevelAPI.ScreenshotParams, 211 | options?: Core.RequestOptions, 212 | ): Core.APIPromise { 213 | return this.post('/v1/screenshot', { body, ...options }); 214 | } 215 | 216 | protected override defaultQuery(): Core.DefaultQuery | undefined { 217 | return this._options.defaultQuery; 218 | } 219 | 220 | protected override defaultHeaders(opts: Core.FinalRequestOptions): Core.Headers { 221 | return { 222 | ...super.defaultHeaders(opts), 223 | ...this._options.defaultHeaders, 224 | }; 225 | } 226 | 227 | protected override authHeaders(opts: Core.FinalRequestOptions): Core.Headers { 228 | if (this.steelAPIKey == null) { 229 | return {}; 230 | } 231 | return { 'steel-api-key': this.steelAPIKey }; 232 | } 233 | 234 | static Steel = this; 235 | static DEFAULT_TIMEOUT = 60000; // 1 minute 236 | 237 | static SteelError = Errors.SteelError; 238 | static APIError = Errors.APIError; 239 | static APIConnectionError = Errors.APIConnectionError; 240 | static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; 241 | static APIUserAbortError = Errors.APIUserAbortError; 242 | static NotFoundError = Errors.NotFoundError; 243 | static ConflictError = Errors.ConflictError; 244 | static RateLimitError = Errors.RateLimitError; 245 | static BadRequestError = Errors.BadRequestError; 246 | static AuthenticationError = Errors.AuthenticationError; 247 | static InternalServerError = Errors.InternalServerError; 248 | static PermissionDeniedError = Errors.PermissionDeniedError; 249 | static UnprocessableEntityError = Errors.UnprocessableEntityError; 250 | 251 | static toFile = Uploads.toFile; 252 | static fileFromPath = Uploads.fileFromPath; 253 | } 254 | 255 | Steel.Credentials = Credentials; 256 | Steel.Files = Files; 257 | Steel.Sessions = Sessions; 258 | Steel.SessionslistSessionsSessionsCursor = SessionslistSessionsSessionsCursor; 259 | Steel.Extensions = Extensions; 260 | Steel.Profiles = Profiles; 261 | 262 | export declare namespace Steel { 263 | export type RequestOptions = Core.RequestOptions; 264 | 265 | export import SessionsCursor = Pagination.SessionsCursor; 266 | export { 267 | type SessionsCursorParams as SessionsCursorParams, 268 | type SessionsCursorResponse as SessionsCursorResponse, 269 | }; 270 | 271 | export { 272 | type PdfResponse as PdfResponse, 273 | type ScrapeResponse as ScrapeResponse, 274 | type ScreenshotResponse as ScreenshotResponse, 275 | type PdfParams as PdfParams, 276 | type ScrapeParams as ScrapeParams, 277 | type ScreenshotParams as ScreenshotParams, 278 | }; 279 | 280 | export { 281 | Credentials as Credentials, 282 | type CredentialCreateResponse as CredentialCreateResponse, 283 | type CredentialUpdateResponse as CredentialUpdateResponse, 284 | type CredentialListResponse as CredentialListResponse, 285 | type CredentialDeleteResponse as CredentialDeleteResponse, 286 | type CredentialCreateParams as CredentialCreateParams, 287 | type CredentialUpdateParams as CredentialUpdateParams, 288 | type CredentialListParams as CredentialListParams, 289 | type CredentialDeleteParams as CredentialDeleteParams, 290 | }; 291 | 292 | export { 293 | Files as Files, 294 | type File as File, 295 | type Fileslist as Fileslist, 296 | type FileUploadParams as FileUploadParams, 297 | }; 298 | 299 | export { 300 | Sessions as Sessions, 301 | type Session as Session, 302 | type SessionContext as SessionContext, 303 | type Sessionslist as Sessionslist, 304 | type SessionComputerResponse as SessionComputerResponse, 305 | type SessionEventsResponse as SessionEventsResponse, 306 | type SessionLiveDetailsResponse as SessionLiveDetailsResponse, 307 | type SessionReleaseResponse as SessionReleaseResponse, 308 | type SessionReleaseAllResponse as SessionReleaseAllResponse, 309 | SessionslistSessionsSessionsCursor as SessionslistSessionsSessionsCursor, 310 | type SessionCreateParams as SessionCreateParams, 311 | type SessionListParams as SessionListParams, 312 | type SessionComputerParams as SessionComputerParams, 313 | type SessionReleaseParams as SessionReleaseParams, 314 | type SessionReleaseAllParams as SessionReleaseAllParams, 315 | }; 316 | 317 | export { 318 | Extensions as Extensions, 319 | type ExtensionUpdateResponse as ExtensionUpdateResponse, 320 | type ExtensionListResponse as ExtensionListResponse, 321 | type ExtensionDeleteResponse as ExtensionDeleteResponse, 322 | type ExtensionDeleteAllResponse as ExtensionDeleteAllResponse, 323 | type ExtensionDownloadResponse as ExtensionDownloadResponse, 324 | type ExtensionUploadResponse as ExtensionUploadResponse, 325 | type ExtensionUpdateParams as ExtensionUpdateParams, 326 | type ExtensionUploadParams as ExtensionUploadParams, 327 | }; 328 | 329 | export { 330 | Profiles as Profiles, 331 | type ProfileCreateResponse as ProfileCreateResponse, 332 | type ProfileListResponse as ProfileListResponse, 333 | type ProfileCreateParams as ProfileCreateParams, 334 | }; 335 | } 336 | 337 | export { toFile, fileFromPath } from './uploads'; 338 | export { 339 | SteelError, 340 | APIError, 341 | APIConnectionError, 342 | APIConnectionTimeoutError, 343 | APIUserAbortError, 344 | NotFoundError, 345 | ConflictError, 346 | RateLimitError, 347 | BadRequestError, 348 | AuthenticationError, 349 | InternalServerError, 350 | PermissionDeniedError, 351 | UnprocessableEntityError, 352 | } from './error'; 353 | 354 | export default Steel; 355 | --------------------------------------------------------------------------------