diff --git a/.speakeasy/workflow.lock b/.speakeasy/workflow.lock index 5c53142..a923e26 100644 --- a/.speakeasy/workflow.lock +++ b/.speakeasy/workflow.lock @@ -1,9 +1,9 @@ -speakeasyVersion: 1.320.0 +speakeasyVersion: 1.386.0 sources: petstore: sourceNamespace: petstore - sourceRevisionDigest: sha256:4a8c53aa372a12ba7286627d4aedfa8519b22e90f5078986354eccac2517b323 - sourceBlobDigest: sha256:c98884351f4dbd0082c2aab44206c8b17385357097608a34ba00ed234d451247 + sourceRevisionDigest: sha256:b23d2e4295b237e0a0c3dd7f974793d6673f9b1e245251a175f02a8bb69787be + sourceBlobDigest: sha256:7c35756702030633a8d2ebb1d378d34f883126319b06edcd798ac7bb2fddb189 tags: - latest - main @@ -23,8 +23,8 @@ targets: third-target: source: petstore sourceNamespace: petstore - sourceRevisionDigest: sha256:4a8c53aa372a12ba7286627d4aedfa8519b22e90f5078986354eccac2517b323 - sourceBlobDigest: sha256:c98884351f4dbd0082c2aab44206c8b17385357097608a34ba00ed234d451247 + sourceRevisionDigest: sha256:b23d2e4295b237e0a0c3dd7f974793d6673f9b1e245251a175f02a8bb69787be + sourceBlobDigest: sha256:7c35756702030633a8d2ebb1d378d34f883126319b06edcd798ac7bb2fddb189 outLocation: ts workflow: workflowVersion: 1.0.0 diff --git a/ts/.gitignore b/ts/.gitignore index cc2681a..3dea90f 100644 --- a/ts/.gitignore +++ b/ts/.gitignore @@ -1,3 +1,9 @@ +/funcs +/core.* +/esm +/dist +/.tshy +/.tshy-* /models /models/errors /types diff --git a/ts/.npmignore b/ts/.npmignore index 709e684..3ef05d1 100644 --- a/ts/.npmignore +++ b/ts/.npmignore @@ -5,3 +5,5 @@ /.eslintrc.js /cjs +/.tshy +/.tshy-* diff --git a/ts/.speakeasy/gen.lock b/ts/.speakeasy/gen.lock index 80f29eb..97366df 100755 --- a/ts/.speakeasy/gen.lock +++ b/ts/.speakeasy/gen.lock @@ -3,10 +3,10 @@ id: f4be10ce-8651-4157-98c6-d75e2342372f management: docChecksum: 1bdb7a6f8bf3995d4b40475228403253 docVersion: 1.0.0 - speakeasyVersion: 1.320.0 - generationVersion: 2.354.1 - releaseVersion: 0.1.3 - configChecksum: 1552f23964d9ccd2f55c761daa8183c5 + speakeasyVersion: 1.386.0 + generationVersion: 2.407.2 + releaseVersion: 0.2.0 + configChecksum: 27613f0fb383aea1039a3e8685c9adbd repoURL: https://github.com/ryan-timothy-albert/multi-sdk-sample.git repoSubDirectory: ts installationURL: https://gitpkg.now.sh/ryan-timothy-albert/multi-sdk-sample/ts @@ -14,26 +14,37 @@ management: features: typescript: additionalDependencies: 0.1.0 - core: 3.10.2 + core: 3.14.2 + defaultEnabledRetries: 0.1.0 + envVarSecurityUsage: 0.1.1 flattening: 2.81.1 globalSecurityCallbacks: 0.1.0 globalServerURLs: 2.82.4 responseFormat: 0.2.3 + retries: 2.83.0 sdkHooks: 0.1.0 generatedFiles: + - src/funcs/petsListPets.ts + - src/funcs/petsCreatePets.ts + - src/funcs/petsShowPetById.ts - src/sdk/pets.ts - src/sdk/sdk.ts - .eslintrc.cjs - .npmignore + - FUNCTIONS.md - RUNTIMES.md - jsr.json - package.json + - src/core.ts - src/index.ts - src/lib/base64.ts - src/lib/config.ts + - src/lib/dlv.ts - src/lib/encodings.ts - src/lib/http.ts - src/lib/is-plain-object.ts + - src/lib/logger.ts + - src/lib/matchers.ts - src/lib/primitives.ts - src/lib/retries.ts - src/lib/schemas.ts @@ -41,10 +52,13 @@ generatedFiles: - src/lib/security.ts - src/lib/url.ts - src/sdk/index.ts + - src/models/errors/httpclienterrors.ts - src/models/errors/sdkerror.ts - src/models/errors/sdkvalidationerror.ts - src/types/blobs.ts + - src/types/constdatetime.ts - src/types/enums.ts + - src/types/fp.ts - src/types/index.ts - src/types/operations.ts - src/types/rfcdate.ts @@ -67,9 +81,11 @@ generatedFiles: - docs/models/components/pet.md - docs/models/components/httpmetadata.md - docs/sdks/ryts/README.md + - docs/lib/utils/retryconfig.md - docs/sdks/pets/README.md - USAGE.md - .gitattributes - src/hooks/hooks.ts - src/hooks/types.ts - src/hooks/index.ts + - CONTRIBUTING.md diff --git a/ts/.speakeasy/gen.yaml b/ts/.speakeasy/gen.yaml index 05bfe24..15e75df 100755 --- a/ts/.speakeasy/gen.yaml +++ b/ts/.speakeasy/gen.yaml @@ -12,7 +12,7 @@ generation: auth: oAuth2ClientCredentialsEnabled: true typescript: - version: 0.1.3 + version: 0.2.0 additionalDependencies: dependencies: {} devDependencies: {} @@ -33,6 +33,7 @@ typescript: inputModelSuffix: input maxMethodParams: 4 methodArguments: require-security-and-request + moduleFormat: commonjs outputModelSuffix: output packageName: ryan-test-1 responseFormat: envelope-http diff --git a/ts/CONTRIBUTING.md b/ts/CONTRIBUTING.md new file mode 100644 index 0000000..d585717 --- /dev/null +++ b/ts/CONTRIBUTING.md @@ -0,0 +1,26 @@ +# Contributing to This Repository + +Thank you for your interest in contributing to this repository. Please note that this repository contains generated code. As such, we do not accept direct changes or pull requests. Instead, we encourage you to follow the guidelines below to report issues and suggest improvements. + +## How to Report Issues + +If you encounter any bugs or have suggestions for improvements, please open an issue on GitHub. When reporting an issue, please provide as much detail as possible to help us reproduce the problem. This includes: + +- A clear and descriptive title +- Steps to reproduce the issue +- Expected and actual behavior +- Any relevant logs, screenshots, or error messages +- Information about your environment (e.g., operating system, software versions) + - For example can be collected using the `npx envinfo` command from your terminal if you have Node.js installed + +## Issue Triage and Upstream Fixes + +We will review and triage issues as quickly as possible. Our goal is to address bugs and incorporate improvements in the upstream source code. Fixes will be included in the next generation of the generated code. + +## Contact + +If you have any questions or need further assistance, please feel free to reach out by opening an issue. + +Thank you for your understanding and cooperation! + +The Maintainers diff --git a/ts/FUNCTIONS.md b/ts/FUNCTIONS.md new file mode 100644 index 0000000..71f6b2a --- /dev/null +++ b/ts/FUNCTIONS.md @@ -0,0 +1,102 @@ +# Standalone Functions + +> [!NOTE] +> This section is useful if you are using a bundler and targetting browsers and +> runtimes where the size of an application affects performance and load times. + +Every method in this SDK is also available as a standalone function. This +alternative API is suitable when targetting the browser or serverless runtimes +and using a bundler to build your application since all unused functionality +will be tree-shaken away. This includes code for unused methods, Zod schemas, +encoding helpers and response handlers. The result is dramatically smaller +impact on the application's final bundle size which grows very slowly as you use +more and more functionality from this SDK. + +Calling methods through the main SDK class remains a valid and generally more +more ergonomic option. Standalone functions represent an optimisation for a +specific category of applications. + +## Example + +```typescript +import { RyTsCore } from "ryan-test-1/core.js"; +import { petsListPets } from "ryan-test-1/funcs/petsListPets.js"; +import { SDKValidationError } from "ryan-test-1/models/errors/sdkvalidationerror.js"; + +// Use `RyTsCore` for best tree-shaking performance. +// You can create one instance of it to use across an application. +const ryTs = new RyTsCore(); + +async function run() { + const res = await petsListPets(ryTs); + + switch (true) { + case res.ok: + // The success case will be handled outside of the switch block + break; + case res.error instanceof SDKValidationError: + // Pretty-print validation errors. + return console.log(res.error.pretty()); + case res.error instanceof Error: + return console.log(res.error); + default: + // TypeScript's type checking will fail on the following line if the above + // cases were not exhaustive. + res.error satisfies never; + throw new Error("Assertion failed: expected error checks to be exhaustive: " + res.error); + } + + + const { value: result } = res; + + // Handle the result + console.log(result) +} + +run(); +``` + +## Result types + +Standalone functions differ from SDK methods in that they return a +`Result` type to capture _known errors_ and document them using +the type system. By avoiding throwing errors, application code maintains clear +control flow and error-handling become part of the regular flow of application +code. + +> We use the term "known errors" because standalone functions, and JavaScript +> code in general, can still throw unexpected errors such as `TypeError`s, +> `RangeError`s and `DOMException`s. Exhaustively catching all errors may be +> something this SDK addresses in the future. Nevertheless, there is still a lot +> of benefit from capturing most errors and turning them into values. + +The second reason for this style of programming is because these functions will +typically be used in front-end applications where exception throwing is +sometimes discouraged or considered unidiomatic. React and similar ecosystems +and libraries tend to promote this style of programming so that components +render useful content under all states (loading, success, error and so on). + +The general pattern when calling standalone functions looks like this: + +```typescript +import { Core } from ""; +import { fetchSomething } from "/funcs/fetchSomething.js"; + +const client = new Core(); + +async function run() { + const result = await fetchSomething(client, { id: "123" }); + if (!result.ok) { + // You can throw the error or handle it. It's your choice now. + throw result.error; + } + + console.log(result.value); +} + +run(); +``` + +Notably, `result.error` above will have an explicit type compared to a try-catch +variation where the error in the catch block can only be of type `unknown` (or +`any` depending on your TypeScript settings). \ No newline at end of file diff --git a/ts/README.md b/ts/README.md index c24dc3b..ebde1eb 100644 --- a/ts/README.md +++ b/ts/README.md @@ -16,9 +16,32 @@ It has been generated successfully based on your OpenAPI spec. However, it is no - [ ] 🎁 Publish your SDK to package managers by [configuring automatic publishing](https://www.speakeasyapi.dev/docs/advanced-setup/publish-sdks) - [ ] ✨ When ready to productionize, delete this section from the README + +## Summary + + + + + +## Table of Contents + +* [SDK Installation](#sdk-installation) +* [Requirements](#requirements) +* [SDK Example Usage](#sdk-example-usage) +* [Available Resources and Operations](#available-resources-and-operations) +* [Standalone functions](#standalone-functions) +* [Retries](#retries) +* [Error Handling](#error-handling) +* [Server Selection](#server-selection) +* [Custom HTTP Client](#custom-http-client) +* [Debugging](#debugging) + + ## SDK Installation +The SDK can be installed with either [npm](https://www.npmjs.com/), [pnpm](https://pnpm.io/), [bun](https://bun.sh/) or [yarn](https://classic.yarnpkg.com/en/) package managers. + ### NPM ```bash @@ -64,7 +87,7 @@ import { RyTs } from "ryan-test-1"; const ryTs = new RyTs(); async function run() { - const result = await ryTs.pets.listPets(21453); + const result = await ryTs.pets.listPets(); // Handle the result console.log(result); @@ -85,6 +108,91 @@ run(); * [showPetById](docs/sdks/pets/README.md#showpetbyid) - Info for a specific pet + +## Standalone functions + +All the methods listed above are available as standalone functions. These +functions are ideal for use in applications running in the browser, serverless +runtimes or other environments where application bundle size is a primary +concern. When using a bundler to build your application, all unused +functionality will be either excluded from the final bundle or tree-shaken away. + +To read more about standalone functions, check [FUNCTIONS.md](./FUNCTIONS.md). + +
+ +Available standalone functions + +- [petsCreatePets](docs/sdks/pets/README.md#createpets) +- [petsListPets](docs/sdks/pets/README.md#listpets) +- [petsShowPetById](docs/sdks/pets/README.md#showpetbyid) + + +
+ + + +## Retries + +Some of the endpoints in this SDK support retries. If you use the SDK without any configuration, it will fall back to the default retry strategy provided by the API. However, the default retry strategy can be overridden on a per-operation basis, or across the entire SDK. + +To change the default retry strategy for a single API call, simply provide a retryConfig object to the call: +```typescript +import { RyTs } from "ryan-test-1"; + +const ryTs = new RyTs(); + +async function run() { + const result = await ryTs.pets.listPets({ + retries: { + strategy: "backoff", + backoff: { + initialInterval: 1, + maxInterval: 50, + exponent: 1.1, + maxElapsedTime: 100, + }, + retryConnectionErrors: false, + }, + }); + + // Handle the result + console.log(result); +} + +run(); + +``` + +If you'd like to override the default retry strategy for all operations that support retries, you can provide a retryConfig at SDK initialization: +```typescript +import { RyTs } from "ryan-test-1"; + +const ryTs = new RyTs({ + retryConfig: { + strategy: "backoff", + backoff: { + initialInterval: 1, + maxInterval: 50, + exponent: 1.1, + maxElapsedTime: 100, + }, + retryConnectionErrors: false, + }, +}); + +async function run() { + const result = await ryTs.pets.listPets(); + + // Handle the result + console.log(result); +} + +run(); + +``` + + ## Error Handling @@ -106,7 +214,7 @@ const ryTs = new RyTs(); async function run() { let result; try { - result = await ryTs.pets.listPets(21453); + result = await ryTs.pets.listPets(); } catch (err) { switch (true) { case err instanceof SDKValidationError: { @@ -150,7 +258,7 @@ const ryTs = new RyTs({ }); async function run() { - const result = await ryTs.pets.listPets(21453); + const result = await ryTs.pets.listPets(); // Handle the result console.log(result); @@ -173,7 +281,7 @@ const ryTs = new RyTs({ }); async function run() { - const result = await ryTs.pets.listPets(21453); + const result = await ryTs.pets.listPets(); // Handle the result console.log(result); @@ -233,6 +341,23 @@ const sdk = new RyTs({ httpClient }); ``` + +## Debugging + +You can setup your SDK to emit debug logs for SDK requests and responses. + +You can pass a logger that matches `console`'s interface as an SDK option. + +> [!WARNING] +> Beware that debug logging will reveal secrets, like API tokens in headers, in log messages printed to a console or files. It's recommended to use this feature only during local development and not in production. + +```typescript +import { RyTs } from "ryan-test-1"; + +const sdk = new RyTs({ debugLogger: console }); +``` + + # Development diff --git a/ts/RELEASES.md b/ts/RELEASES.md index 2f32dd4..a899e5b 100644 --- a/ts/RELEASES.md +++ b/ts/RELEASES.md @@ -46,4 +46,14 @@ Based on: ### Generated - [typescript v0.1.3] ts ### Releases -- [NPM v0.1.3] https://www.npmjs.com/package/ryan-test-1/v/0.1.3 - ts \ No newline at end of file +- [NPM v0.1.3] https://www.npmjs.com/package/ryan-test-1/v/0.1.3 - ts + +## 2024-09-03 00:54:51 +### Changes +Based on: +- OpenAPI Doc +- Speakeasy CLI 1.386.0 (2.407.2) https://github.com/speakeasy-api/speakeasy +### Generated +- [typescript v0.2.0] ts +### Releases +- [NPM v0.2.0] https://www.npmjs.com/package/ryan-test-1/v/0.2.0 - ts \ No newline at end of file diff --git a/ts/RUNTIMES.md b/ts/RUNTIMES.md index 71fa3da..d08a0c0 100644 --- a/ts/RUNTIMES.md +++ b/ts/RUNTIMES.md @@ -3,7 +3,7 @@ This SDK is intended to be used in JavaScript runtimes that support the following features: * [Web Fetch API][web-fetch] -* [Web Streams API](web-streams) and in particular `ReadableStream` +* [Web Streams API][web-streams] and in particular `ReadableStream` * [Async iterables][async-iter] using `Symbol.asyncIterator` [web-fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API diff --git a/ts/USAGE.md b/ts/USAGE.md index 9ee28bf..b8c7fde 100644 --- a/ts/USAGE.md +++ b/ts/USAGE.md @@ -5,7 +5,7 @@ import { RyTs } from "ryan-test-1"; const ryTs = new RyTs(); async function run() { - const result = await ryTs.pets.listPets(21453); + const result = await ryTs.pets.listPets(); // Handle the result console.log(result); diff --git a/ts/docs/lib/utils/retryconfig.md b/ts/docs/lib/utils/retryconfig.md new file mode 100644 index 0000000..08f95f4 --- /dev/null +++ b/ts/docs/lib/utils/retryconfig.md @@ -0,0 +1,24 @@ +# RetryConfig + +Allows customizing the default retry configuration. It is only permitted in methods that accept retry policies. + +## Fields + +| Name | Type | Description | Example | +| ------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | ----------- | +| `strategy` | `"backoff" | "none"` | The retry strategy to use. | `"backoff"` | +| `backoff` | [BackoffStrategy](#backoffstrategy) | When strategy is "backoff", this configurates for the backoff parameters. | | +| `retryConnectionErrors` | `*boolean*` | When strategy is "backoff", this determines whether or not to retry on connection errors. | `true` | + +## BackoffStrategy + +The backoff strategy allows retrying a request with an exponential backoff between each retry. + +### Fields + +| Name | Type | Description | Example | +| ------------------ | ------------ | ----------------------------------------- | -------- | +| `initialInterval` | `*number*` | The initial interval in milliseconds. | `500` | +| `maxInterval` | `*number*` | The maximum interval in milliseconds. | `60000` | +| `exponent` | `*number*` | The exponent to use for the backoff. | `1.5` | +| `maxElapsedTime` | `*number*` | The maximum elapsed time in milliseconds. | `300000` | \ No newline at end of file diff --git a/ts/docs/models/components/errort.md b/ts/docs/models/components/errort.md index 582ea2c..329befe 100644 --- a/ts/docs/models/components/errort.md +++ b/ts/docs/models/components/errort.md @@ -1,5 +1,15 @@ # ErrorT +## Example Usage + +```typescript +import { ErrorT } from "ryan-test-1/models/components"; + +let value: ErrorT = { + code: 548814, + message: "", +}; +``` ## Fields diff --git a/ts/docs/models/components/httpmetadata.md b/ts/docs/models/components/httpmetadata.md index 8ce9328..03d2093 100644 --- a/ts/docs/models/components/httpmetadata.md +++ b/ts/docs/models/components/httpmetadata.md @@ -1,5 +1,17 @@ # HTTPMetadata +## Example Usage + +```typescript +import { HTTPMetadata } from "ryan-test-1/models/components"; + +let value: HTTPMetadata = { + response: new Response('{"message": "hello world"}', { + headers: { "Content-Type": "application/json" }, + }), + request: new Request("https://example.com"), +}; +``` ## Fields diff --git a/ts/docs/models/components/pet.md b/ts/docs/models/components/pet.md index f4d881f..4ab8064 100644 --- a/ts/docs/models/components/pet.md +++ b/ts/docs/models/components/pet.md @@ -1,5 +1,15 @@ # Pet +## Example Usage + +```typescript +import { Pet } from "ryan-test-1/models/components"; + +let value: Pet = { + id: 592845, + name: "", +}; +``` ## Fields diff --git a/ts/docs/models/operations/createpetsresponse.md b/ts/docs/models/operations/createpetsresponse.md index 628b65c..97acafa 100644 --- a/ts/docs/models/operations/createpetsresponse.md +++ b/ts/docs/models/operations/createpetsresponse.md @@ -1,5 +1,19 @@ # CreatePetsResponse +## Example Usage + +```typescript +import { CreatePetsResponse } from "ryan-test-1/models/operations"; + +let value: CreatePetsResponse = { + httpMeta: { + response: new Response('{"message": "hello world"}', { + headers: { "Content-Type": "application/json" }, + }), + request: new Request("https://example.com"), + }, +}; +``` ## Fields diff --git a/ts/docs/models/operations/listpetsrequest.md b/ts/docs/models/operations/listpetsrequest.md index 97ebaa9..68b6f6c 100644 --- a/ts/docs/models/operations/listpetsrequest.md +++ b/ts/docs/models/operations/listpetsrequest.md @@ -1,5 +1,12 @@ # ListPetsRequest +## Example Usage + +```typescript +import { ListPetsRequest } from "ryan-test-1/models/operations"; + +let value: ListPetsRequest = {}; +``` ## Fields diff --git a/ts/docs/models/operations/listpetsresponse.md b/ts/docs/models/operations/listpetsresponse.md index e05c375..63a8779 100644 --- a/ts/docs/models/operations/listpetsresponse.md +++ b/ts/docs/models/operations/listpetsresponse.md @@ -1,5 +1,22 @@ # ListPetsResponse +## Example Usage + +```typescript +import { ListPetsResponse } from "ryan-test-1/models/operations"; + +let value: ListPetsResponse = { + httpMeta: { + response: new Response('{"message": "hello world"}', { + headers: { "Content-Type": "application/json" }, + }), + request: new Request("https://example.com"), + }, + headers: { + key: [""], + }, +}; +``` ## Fields diff --git a/ts/docs/models/operations/showpetbyidrequest.md b/ts/docs/models/operations/showpetbyidrequest.md index cc75c5f..95ae9ff 100644 --- a/ts/docs/models/operations/showpetbyidrequest.md +++ b/ts/docs/models/operations/showpetbyidrequest.md @@ -1,5 +1,14 @@ # ShowPetByIdRequest +## Example Usage + +```typescript +import { ShowPetByIdRequest } from "ryan-test-1/models/operations"; + +let value: ShowPetByIdRequest = { + petId: "", +}; +``` ## Fields diff --git a/ts/docs/models/operations/showpetbyidresponse.md b/ts/docs/models/operations/showpetbyidresponse.md index 87aba59..8eac2e0 100644 --- a/ts/docs/models/operations/showpetbyidresponse.md +++ b/ts/docs/models/operations/showpetbyidresponse.md @@ -1,5 +1,19 @@ # ShowPetByIdResponse +## Example Usage + +```typescript +import { ShowPetByIdResponse } from "ryan-test-1/models/operations"; + +let value: ShowPetByIdResponse = { + httpMeta: { + response: new Response('{"message": "hello world"}', { + headers: { "Content-Type": "application/json" }, + }), + request: new Request("https://example.com"), + }, +}; +``` ## Fields diff --git a/ts/docs/sdks/pets/README.md b/ts/docs/sdks/pets/README.md index 905d11a..3b3adef 100644 --- a/ts/docs/sdks/pets/README.md +++ b/ts/docs/sdks/pets/README.md @@ -1,6 +1,8 @@ # Pets (*pets*) +## Overview + ### Available Operations * [listPets](#listpets) - List all pets @@ -19,7 +21,35 @@ import { RyTs } from "ryan-test-1"; const ryTs = new RyTs(); async function run() { - const result = await ryTs.pets.listPets(21453); + const result = await ryTs.pets.listPets(); + + // Handle the result + console.log(result) +} + +run(); +``` + +### Standalone function + +The standalone function version of this method: + +```typescript +import { RyTsCore } from "ryan-test-1/core.js"; +import { petsListPets } from "ryan-test-1/funcs/petsListPets.js"; + +// Use `RyTsCore` for best tree-shaking performance. +// You can create one instance of it to use across an application. +const ryTs = new RyTsCore(); + +async function run() { + const res = await petsListPets(ryTs); + + if (!res.ok) { + throw res.error; + } + + const { value: result } = res; // Handle the result console.log(result) @@ -35,17 +65,19 @@ run(); | `limit` | *number* | :heavy_minus_sign: | How many items to return at one time (max 100) | | `options` | RequestOptions | :heavy_minus_sign: | Used to set various options for making HTTP requests. | | `options.fetchOptions` | [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#options) | :heavy_minus_sign: | Options that are passed to the underlying HTTP request. This can be used to inject extra headers for examples. All `Request` options, except `method` and `body`, are allowed. | - +| `options.retries` | [RetryConfig](../../lib/utils/retryconfig.md) | :heavy_minus_sign: | Enables retrying HTTP requests under certain failure conditions. | ### Response **Promise\<[operations.ListPetsResponse](../../models/operations/listpetsresponse.md)\>** + ### Errors | Error Object | Status Code | Content Type | | --------------- | --------------- | --------------- | | errors.SDKError | 4xx-5xx | */* | + ## createPets Create a pet @@ -70,6 +102,37 @@ async function run() { run(); ``` +### Standalone function + +The standalone function version of this method: + +```typescript +import { RyTsCore } from "ryan-test-1/core.js"; +import { petsCreatePets } from "ryan-test-1/funcs/petsCreatePets.js"; + +// Use `RyTsCore` for best tree-shaking performance. +// You can create one instance of it to use across an application. +const ryTs = new RyTsCore(); + +async function run() { + const res = await petsCreatePets(ryTs, { + id: 589113, + name: "", + }); + + if (!res.ok) { + throw res.error; + } + + const { value: result } = res; + + // Handle the result + console.log(result) +} + +run(); +``` + ### Parameters | Parameter | Type | Required | Description | @@ -77,17 +140,19 @@ run(); | `request` | [components.Pet](../../models/components/pet.md) | :heavy_check_mark: | The request object to use for the request. | | `options` | RequestOptions | :heavy_minus_sign: | Used to set various options for making HTTP requests. | | `options.fetchOptions` | [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#options) | :heavy_minus_sign: | Options that are passed to the underlying HTTP request. This can be used to inject extra headers for examples. All `Request` options, except `method` and `body`, are allowed. | - +| `options.retries` | [RetryConfig](../../lib/utils/retryconfig.md) | :heavy_minus_sign: | Enables retrying HTTP requests under certain failure conditions. | ### Response **Promise\<[operations.CreatePetsResponse](../../models/operations/createpetsresponse.md)\>** + ### Errors | Error Object | Status Code | Content Type | | --------------- | --------------- | --------------- | | errors.SDKError | 4xx-5xx | */* | + ## showPetById Info for a specific pet @@ -109,6 +174,34 @@ async function run() { run(); ``` +### Standalone function + +The standalone function version of this method: + +```typescript +import { RyTsCore } from "ryan-test-1/core.js"; +import { petsShowPetById } from "ryan-test-1/funcs/petsShowPetById.js"; + +// Use `RyTsCore` for best tree-shaking performance. +// You can create one instance of it to use across an application. +const ryTs = new RyTsCore(); + +async function run() { + const res = await petsShowPetById(ryTs, ""); + + if (!res.ok) { + throw res.error; + } + + const { value: result } = res; + + // Handle the result + console.log(result) +} + +run(); +``` + ### Parameters | Parameter | Type | Required | Description | @@ -116,11 +209,12 @@ run(); | `petId` | *string* | :heavy_check_mark: | The id of the pet to retrieve | | `options` | RequestOptions | :heavy_minus_sign: | Used to set various options for making HTTP requests. | | `options.fetchOptions` | [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#options) | :heavy_minus_sign: | Options that are passed to the underlying HTTP request. This can be used to inject extra headers for examples. All `Request` options, except `method` and `body`, are allowed. | - +| `options.retries` | [RetryConfig](../../lib/utils/retryconfig.md) | :heavy_minus_sign: | Enables retrying HTTP requests under certain failure conditions. | ### Response **Promise\<[operations.ShowPetByIdResponse](../../models/operations/showpetbyidresponse.md)\>** + ### Errors | Error Object | Status Code | Content Type | diff --git a/ts/docs/sdks/ryts/README.md b/ts/docs/sdks/ryts/README.md index 1acde8f..2c3e862 100644 --- a/ts/docs/sdks/ryts/README.md +++ b/ts/docs/sdks/ryts/README.md @@ -1,7 +1,3 @@ # RyTs SDK - -## Overview - -### Available Operations - +## Overview \ No newline at end of file diff --git a/ts/jsr.json b/ts/jsr.json index a7686a2..0812893 100644 --- a/ts/jsr.json +++ b/ts/jsr.json @@ -2,7 +2,7 @@ { "name": "ryan-test-1", - "version": "0.1.3", + "version": "0.2.0", "exports": { ".": "./src/index.ts", "./models/errors": "./src/models/errors/index.ts", diff --git a/ts/package-lock.json b/ts/package-lock.json index 83ce260..1086bd0 100644 --- a/ts/package-lock.json +++ b/ts/package-lock.json @@ -1,14 +1,13 @@ { "name": "ryan-test-1", - "version": "0.1.3", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ryan-test-1", - "version": "0.1.3", + "version": "0.2.0", "devDependencies": { - "@types/jsonpath": "^0.2.4", "@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/parser": "^7.7.1", "eslint": "^8.57.0", @@ -201,12 +200,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/jsonpath": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@types/jsonpath/-/jsonpath-0.2.4.tgz", - "integrity": "sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==", - "dev": true - }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", diff --git a/ts/package.json b/ts/package.json index d4ac6b6..bf69fa7 100644 --- a/ts/package.json +++ b/ts/package.json @@ -1,7 +1,7 @@ { "name": "ryan-test-1", - "version": "0.1.3", - "author": "ryantimalbert", + "version": "0.2.0", + "author": "ryantimalbert", "main": "./index.js", "sideEffects": false, "repository": { @@ -18,7 +18,6 @@ "zod": ">= 3" }, "devDependencies": { - "@types/jsonpath": "^0.2.4", "@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/parser": "^7.7.1", "eslint": "^8.57.0", diff --git a/ts/src/core.ts b/ts/src/core.ts new file mode 100644 index 0000000..0924b4c --- /dev/null +++ b/ts/src/core.ts @@ -0,0 +1,13 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import { ClientSDK } from "./lib/sdks.js"; + +/** + * A minimal client to use when calling standalone SDK functions. Typically, an + * instance of this class would be instantiated once at the start of an + * application and passed around through some dependency injection mechanism to + * parts of an application that need to make SDK calls. + */ +export class RyTsCore extends ClientSDK {} diff --git a/ts/src/funcs/petsCreatePets.ts b/ts/src/funcs/petsCreatePets.ts new file mode 100644 index 0000000..202bde1 --- /dev/null +++ b/ts/src/funcs/petsCreatePets.ts @@ -0,0 +1,115 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import { RyTsCore } from "../core.js"; +import { encodeJSON as encodeJSON$ } from "../lib/encodings.js"; +import * as m$ from "../lib/matchers.js"; +import * as schemas$ from "../lib/schemas.js"; +import { RequestOptions } from "../lib/sdks.js"; +import { pathToFunc } from "../lib/url.js"; +import * as components from "../models/components/index.js"; +import { + ConnectionError, + InvalidRequestError, + RequestAbortedError, + RequestTimeoutError, + UnexpectedClientError, +} from "../models/errors/httpclienterrors.js"; +import { SDKError } from "../models/errors/sdkerror.js"; +import { SDKValidationError } from "../models/errors/sdkvalidationerror.js"; +import * as operations from "../models/operations/index.js"; +import { Result } from "../types/fp.js"; + +/** + * Create a pet + */ +export async function petsCreatePets( + client$: RyTsCore, + request: components.Pet, + options?: RequestOptions +): Promise< + Result< + operations.CreatePetsResponse, + | SDKError + | SDKValidationError + | UnexpectedClientError + | InvalidRequestError + | RequestAbortedError + | RequestTimeoutError + | ConnectionError + > +> { + const input$ = request; + + const parsed$ = schemas$.safeParse( + input$, + (value$) => components.Pet$outboundSchema.parse(value$), + "Input validation failed" + ); + if (!parsed$.ok) { + return parsed$; + } + const payload$ = parsed$.value; + const body$ = encodeJSON$("body", payload$, { explode: true }); + + const path$ = pathToFunc("/pets")(); + + const headers$ = new Headers({ + "Content-Type": "application/json", + Accept: "application/json", + }); + + const context = { operationID: "createPets", oAuth2Scopes: [], securitySource: null }; + + const requestRes = client$.createRequest$( + context, + { + method: "POST", + path: path$, + headers: headers$, + body: body$, + timeoutMs: options?.timeoutMs || client$.options$.timeoutMs || -1, + }, + options + ); + if (!requestRes.ok) { + return requestRes; + } + const request$ = requestRes.value; + + const doResult = await client$.do$(request$, { + context, + errorCodes: ["4XX", "5XX"], + retryConfig: options?.retries || client$.options$.retryConfig, + retryCodes: options?.retryCodes || ["429", "500", "502", "503", "504"], + }); + if (!doResult.ok) { + return doResult; + } + const response = doResult.value; + + const responseFields$ = { + HttpMeta: { Response: response, Request: request$ }, + }; + + const [result$] = await m$.match< + operations.CreatePetsResponse, + | SDKError + | SDKValidationError + | UnexpectedClientError + | InvalidRequestError + | RequestAbortedError + | RequestTimeoutError + | ConnectionError + >( + m$.nil(201, operations.CreatePetsResponse$inboundSchema), + m$.fail(["4XX", "5XX"]), + m$.json("default", operations.CreatePetsResponse$inboundSchema, { key: "Error" }) + )(response, request$, { extraFields: responseFields$ }); + if (!result$.ok) { + return result$; + } + + return result$; +} diff --git a/ts/src/funcs/petsListPets.ts b/ts/src/funcs/petsListPets.ts new file mode 100644 index 0000000..1585638 --- /dev/null +++ b/ts/src/funcs/petsListPets.ts @@ -0,0 +1,120 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import { RyTsCore } from "../core.js"; +import { encodeFormQuery as encodeFormQuery$ } from "../lib/encodings.js"; +import * as m$ from "../lib/matchers.js"; +import * as schemas$ from "../lib/schemas.js"; +import { RequestOptions } from "../lib/sdks.js"; +import { pathToFunc } from "../lib/url.js"; +import { + ConnectionError, + InvalidRequestError, + RequestAbortedError, + RequestTimeoutError, + UnexpectedClientError, +} from "../models/errors/httpclienterrors.js"; +import { SDKError } from "../models/errors/sdkerror.js"; +import { SDKValidationError } from "../models/errors/sdkvalidationerror.js"; +import * as operations from "../models/operations/index.js"; +import { Result } from "../types/fp.js"; + +/** + * List all pets + */ +export async function petsListPets( + client$: RyTsCore, + limit?: number | undefined, + options?: RequestOptions +): Promise< + Result< + operations.ListPetsResponse, + | SDKError + | SDKValidationError + | UnexpectedClientError + | InvalidRequestError + | RequestAbortedError + | RequestTimeoutError + | ConnectionError + > +> { + const input$: operations.ListPetsRequest = { + limit: limit, + }; + + const parsed$ = schemas$.safeParse( + input$, + (value$) => operations.ListPetsRequest$outboundSchema.parse(value$), + "Input validation failed" + ); + if (!parsed$.ok) { + return parsed$; + } + const payload$ = parsed$.value; + const body$ = null; + + const path$ = pathToFunc("/pets")(); + + const query$ = encodeFormQuery$({ + limit: payload$.limit, + }); + + const headers$ = new Headers({ + Accept: "application/json", + }); + + const context = { operationID: "listPets", oAuth2Scopes: [], securitySource: null }; + + const requestRes = client$.createRequest$( + context, + { + method: "GET", + path: path$, + headers: headers$, + query: query$, + body: body$, + timeoutMs: options?.timeoutMs || client$.options$.timeoutMs || -1, + }, + options + ); + if (!requestRes.ok) { + return requestRes; + } + const request$ = requestRes.value; + + const doResult = await client$.do$(request$, { + context, + errorCodes: ["4XX", "5XX"], + retryConfig: options?.retries || client$.options$.retryConfig, + retryCodes: options?.retryCodes || ["429", "500", "502", "503", "504"], + }); + if (!doResult.ok) { + return doResult; + } + const response = doResult.value; + + const responseFields$ = { + HttpMeta: { Response: response, Request: request$ }, + }; + + const [result$] = await m$.match< + operations.ListPetsResponse, + | SDKError + | SDKValidationError + | UnexpectedClientError + | InvalidRequestError + | RequestAbortedError + | RequestTimeoutError + | ConnectionError + >( + m$.json(200, operations.ListPetsResponse$inboundSchema, { hdrs: true, key: "Pets" }), + m$.fail(["4XX", "5XX"]), + m$.json("default", operations.ListPetsResponse$inboundSchema, { key: "Error" }) + )(response, request$, { extraFields: responseFields$ }); + if (!result$.ok) { + return result$; + } + + return result$; +} diff --git a/ts/src/funcs/petsShowPetById.ts b/ts/src/funcs/petsShowPetById.ts new file mode 100644 index 0000000..41b1f60 --- /dev/null +++ b/ts/src/funcs/petsShowPetById.ts @@ -0,0 +1,119 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import { RyTsCore } from "../core.js"; +import { encodeSimple as encodeSimple$ } from "../lib/encodings.js"; +import * as m$ from "../lib/matchers.js"; +import * as schemas$ from "../lib/schemas.js"; +import { RequestOptions } from "../lib/sdks.js"; +import { pathToFunc } from "../lib/url.js"; +import { + ConnectionError, + InvalidRequestError, + RequestAbortedError, + RequestTimeoutError, + UnexpectedClientError, +} from "../models/errors/httpclienterrors.js"; +import { SDKError } from "../models/errors/sdkerror.js"; +import { SDKValidationError } from "../models/errors/sdkvalidationerror.js"; +import * as operations from "../models/operations/index.js"; +import { Result } from "../types/fp.js"; + +/** + * Info for a specific pet + */ +export async function petsShowPetById( + client$: RyTsCore, + petId: string, + options?: RequestOptions +): Promise< + Result< + operations.ShowPetByIdResponse, + | SDKError + | SDKValidationError + | UnexpectedClientError + | InvalidRequestError + | RequestAbortedError + | RequestTimeoutError + | ConnectionError + > +> { + const input$: operations.ShowPetByIdRequest = { + petId: petId, + }; + + const parsed$ = schemas$.safeParse( + input$, + (value$) => operations.ShowPetByIdRequest$outboundSchema.parse(value$), + "Input validation failed" + ); + if (!parsed$.ok) { + return parsed$; + } + const payload$ = parsed$.value; + const body$ = null; + + const pathParams$ = { + petId: encodeSimple$("petId", payload$.petId, { explode: false, charEncoding: "percent" }), + }; + + const path$ = pathToFunc("/pets/{petId}")(pathParams$); + + const headers$ = new Headers({ + Accept: "application/json", + }); + + const context = { operationID: "showPetById", oAuth2Scopes: [], securitySource: null }; + + const requestRes = client$.createRequest$( + context, + { + method: "GET", + path: path$, + headers: headers$, + body: body$, + timeoutMs: options?.timeoutMs || client$.options$.timeoutMs || -1, + }, + options + ); + if (!requestRes.ok) { + return requestRes; + } + const request$ = requestRes.value; + + const doResult = await client$.do$(request$, { + context, + errorCodes: ["4XX", "5XX"], + retryConfig: options?.retries || client$.options$.retryConfig, + retryCodes: options?.retryCodes || ["429", "500", "502", "503", "504"], + }); + if (!doResult.ok) { + return doResult; + } + const response = doResult.value; + + const responseFields$ = { + HttpMeta: { Response: response, Request: request$ }, + }; + + const [result$] = await m$.match< + operations.ShowPetByIdResponse, + | SDKError + | SDKValidationError + | UnexpectedClientError + | InvalidRequestError + | RequestAbortedError + | RequestTimeoutError + | ConnectionError + >( + m$.json(200, operations.ShowPetByIdResponse$inboundSchema, { key: "Pet" }), + m$.fail(["4XX", "5XX"]), + m$.json("default", operations.ShowPetByIdResponse$inboundSchema, { key: "Error" }) + )(response, request$, { extraFields: responseFields$ }); + if (!result$.ok) { + return result$; + } + + return result$; +} diff --git a/ts/src/hooks/hooks.ts b/ts/src/hooks/hooks.ts index 6a1e31e..a6b6e17 100644 --- a/ts/src/hooks/hooks.ts +++ b/ts/src/hooks/hooks.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import { RequestInput } from "../lib/http.js"; diff --git a/ts/src/hooks/index.ts b/ts/src/hooks/index.ts index d5fdd74..8f0e4db 100644 --- a/ts/src/hooks/index.ts +++ b/ts/src/hooks/index.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ export * from "./types.js"; diff --git a/ts/src/hooks/types.ts b/ts/src/hooks/types.ts index dd23b02..3a31914 100644 --- a/ts/src/hooks/types.ts +++ b/ts/src/hooks/types.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import { HTTPClient, RequestInput } from "../lib/http.js"; diff --git a/ts/src/index.ts b/ts/src/index.ts index 8bc6ae3..ceb6380 100644 --- a/ts/src/index.ts +++ b/ts/src/index.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ export * from "./sdk/sdk.js"; diff --git a/ts/src/lib/base64.ts b/ts/src/lib/base64.ts index cda0764..c2d5b38 100644 --- a/ts/src/lib/base64.ts +++ b/ts/src/lib/base64.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import * as z from "zod"; diff --git a/ts/src/lib/config.ts b/ts/src/lib/config.ts index dcb5d55..c3b9eb4 100644 --- a/ts/src/lib/config.ts +++ b/ts/src/lib/config.ts @@ -1,8 +1,9 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import { HTTPClient } from "./http.js"; +import { Logger } from "./logger.js"; import { RetryConfig } from "./retries.js"; import { Params, pathToFunc } from "./url.js"; @@ -25,6 +26,8 @@ export type SDKOptions = { * Allows overriding the default retry config used by the SDK */ retryConfig?: RetryConfig; + timeoutMs?: number; + debugLogger?: Logger; }; export function serverURLFromOptions(options: SDKOptions): URL | null { @@ -47,7 +50,7 @@ export function serverURLFromOptions(options: SDKOptions): URL | null { export const SDK_METADATA = { language: "typescript", openapiDocVersion: "1.0.0", - sdkVersion: "0.1.3", - genVersion: "2.354.1", - userAgent: "speakeasy-sdk/typescript 0.1.3 2.354.1 1.0.0 ryan-test-1", + sdkVersion: "0.2.0", + genVersion: "2.407.2", + userAgent: "speakeasy-sdk/typescript 0.2.0 2.407.2 1.0.0 ryan-test-1", } as const; diff --git a/ts/src/lib/dlv.ts b/ts/src/lib/dlv.ts new file mode 100644 index 0000000..e81091f --- /dev/null +++ b/ts/src/lib/dlv.ts @@ -0,0 +1,53 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +/* +MIT License + +Copyright (c) 2024 Jason Miller (http://jasonformat.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/** + * @param obj The object to walk + * @param key The key path to walk the object with + * @param def A default value to return if the result is undefined + * + * @example + * dlv(obj, "a.b.c.d") + * @example + * dlv(object, ["a", "b", "c", "d"]) + * @example + * dlv(object, "foo.bar.baz", "Hello, default value!") + */ +export function dlv( + obj: any, + key: string | string[], + def?: T, + p?: number, + undef?: never, +): T | undefined { + key = Array.isArray(key) ? key : key.split("."); + for (p = 0; p < key.length; p++) { + const k = key[p]; + obj = k != null && obj ? obj[k] : undef; + } + return obj === undef ? def : obj; +} diff --git a/ts/src/lib/encodings.ts b/ts/src/lib/encodings.ts index 49f536b..44fa728 100644 --- a/ts/src/lib/encodings.ts +++ b/ts/src/lib/encodings.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import { bytesToBase64 } from "./base64.js"; @@ -212,6 +212,24 @@ export function encodeDeepObject( return ""; } + if (!isPlainObject(value)) { + throw new EncodingError( + `Value of parameter '${key}' which uses deepObject encoding must be an object`, + ); + } + + return encodeDeepObjectObject(key, value, options); +} + +export function encodeDeepObjectObject( + key: string, + value: unknown, + options?: { charEncoding?: "percent" | "none" }, +): string { + if (value == null) { + return ""; + } + let out = ""; const encodeString = (v: string) => { @@ -219,9 +237,7 @@ export function encodeDeepObject( }; if (!isPlainObject(value)) { - throw new EncodingError( - `Value of parameter '${key}' which uses deepObject encoding must be an object`, - ); + throw new EncodingError(`Expected parameter '${key}' to be an object.`); } Object.entries(value).forEach(([ck, cv]) => { @@ -232,9 +248,11 @@ export function encodeDeepObject( const pk = `${key}[${ck}]`; if (isPlainObject(cv)) { - throw new EncodingError( - `Value of parameter field '${pk}' cannot be an array or object.`, - ); + const objOut = encodeDeepObjectObject(pk, cv, options); + + out += `&${objOut}`; + + return; } const pairs: unknown[] = Array.isArray(cv) ? cv : [cv]; diff --git a/ts/src/lib/http.ts b/ts/src/lib/http.ts index b84292a..13cf1fd 100644 --- a/ts/src/lib/http.ts +++ b/ts/src/lib/http.ts @@ -1,41 +1,39 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ -import { never as znever } from "zod"; -import { parse } from "./schemas.js"; -import { isPlainObject } from "./is-plain-object.js"; -import { SDKError } from "../models/errors/sdkerror.js"; - -export type Fetcher = (input: RequestInfo | URL, init?: RequestInit) => Promise; +export type Fetcher = ( + input: RequestInfo | URL, + init?: RequestInit, +) => Promise; export type Awaitable = T | Promise; const DEFAULT_FETCHER: Fetcher = (input, init) => { - // If input is a Request and init is undefined, Bun will discard the method, - // headers, body and other options that were set on the request object. - // Node.js and browers would ignore an undefined init value. This check is - // therefore needed for interop with Bun. - if (init == null) { - return fetch(input); - } else { - return fetch(input, init); - } + // If input is a Request and init is undefined, Bun will discard the method, + // headers, body and other options that were set on the request object. + // Node.js and browers would ignore an undefined init value. This check is + // therefore needed for interop with Bun. + if (init == null) { + return fetch(input); + } else { + return fetch(input, init); + } }; export type RequestInput = { - /** - * The URL the request will use. - */ - url: URL; - /** - * Options used to create a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request). - */ - options?: RequestInit | undefined; + /** + * The URL the request will use. + */ + url: URL; + /** + * Options used to create a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request). + */ + options?: RequestInit | undefined; }; export interface HTTPClientOptions { - fetcher?: Fetcher; + fetcher?: Fetcher; } export type BeforeRequestHook = (req: Request) => Awaitable; @@ -43,114 +41,114 @@ export type RequestErrorHook = (err: unknown, req: Request) => Awaitable; export type ResponseHook = (res: Response, req: Request) => Awaitable; export class HTTPClient { - private fetcher: Fetcher; - private requestHooks: BeforeRequestHook[] = []; - private requestErrorHooks: RequestErrorHook[] = []; - private responseHooks: ResponseHook[] = []; - - constructor(private options: HTTPClientOptions = {}) { - this.fetcher = options.fetcher || DEFAULT_FETCHER; + private fetcher: Fetcher; + private requestHooks: BeforeRequestHook[] = []; + private requestErrorHooks: RequestErrorHook[] = []; + private responseHooks: ResponseHook[] = []; + + constructor(private options: HTTPClientOptions = {}) { + this.fetcher = options.fetcher || DEFAULT_FETCHER; + } + + async request(request: Request): Promise { + let req = request; + for (const hook of this.requestHooks) { + const nextRequest = await hook(req); + if (nextRequest) { + req = nextRequest; + } } - async request(request: Request): Promise { - let req = request; - for (const hook of this.requestHooks) { - const nextRequest = await hook(req); - if (nextRequest) { - req = nextRequest; - } - } - - try { - const res = await this.fetcher(req); - - for (const hook of this.responseHooks) { - await hook(res, req); - } - - return res; - } catch (err) { - for (const hook of this.requestErrorHooks) { - await hook(err, req); - } - - throw err; - } - } + try { + const res = await this.fetcher(req); + + for (const hook of this.responseHooks) { + await hook(res, req); + } + + return res; + } catch (err) { + for (const hook of this.requestErrorHooks) { + await hook(err, req); + } - /** - * Registers a hook that is called before a request is made. The hook function - * can mutate the request or return a new request. This may be useful to add - * additional information to request such as request IDs and tracing headers. - */ - addHook(hook: "beforeRequest", fn: BeforeRequestHook): this; - /** - * Registers a hook that is called when a request cannot be made due to a - * network error. - */ - addHook(hook: "requestError", fn: RequestErrorHook): this; - /** - * Registers a hook that is called when a response has been received from the - * server. - */ - addHook(hook: "response", fn: ResponseHook): this; - addHook( - ...args: - | [hook: "beforeRequest", fn: BeforeRequestHook] - | [hook: "requestError", fn: RequestErrorHook] - | [hook: "response", fn: ResponseHook] - ) { - if (args[0] === "beforeRequest") { - this.requestHooks.push(args[1]); - } else if (args[0] === "requestError") { - this.requestErrorHooks.push(args[1]); - } else if (args[0] === "response") { - this.responseHooks.push(args[1]); - } else { - throw new Error(`Invalid hook type: ${args[0]}`); - } - return this; + throw err; + } + } + + /** + * Registers a hook that is called before a request is made. The hook function + * can mutate the request or return a new request. This may be useful to add + * additional information to request such as request IDs and tracing headers. + */ + addHook(hook: "beforeRequest", fn: BeforeRequestHook): this; + /** + * Registers a hook that is called when a request cannot be made due to a + * network error. + */ + addHook(hook: "requestError", fn: RequestErrorHook): this; + /** + * Registers a hook that is called when a response has been received from the + * server. + */ + addHook(hook: "response", fn: ResponseHook): this; + addHook( + ...args: + | [hook: "beforeRequest", fn: BeforeRequestHook] + | [hook: "requestError", fn: RequestErrorHook] + | [hook: "response", fn: ResponseHook] + ) { + if (args[0] === "beforeRequest") { + this.requestHooks.push(args[1]); + } else if (args[0] === "requestError") { + this.requestErrorHooks.push(args[1]); + } else if (args[0] === "response") { + this.responseHooks.push(args[1]); + } else { + throw new Error(`Invalid hook type: ${args[0]}`); + } + return this; + } + + /** Removes a hook that was previously registered with `addHook`. */ + removeHook(hook: "beforeRequest", fn: BeforeRequestHook): this; + /** Removes a hook that was previously registered with `addHook`. */ + removeHook(hook: "requestError", fn: RequestErrorHook): this; + /** Removes a hook that was previously registered with `addHook`. */ + removeHook(hook: "response", fn: ResponseHook): this; + removeHook( + ...args: + | [hook: "beforeRequest", fn: BeforeRequestHook] + | [hook: "requestError", fn: RequestErrorHook] + | [hook: "response", fn: ResponseHook] + ): this { + let target: unknown[]; + if (args[0] === "beforeRequest") { + target = this.requestHooks; + } else if (args[0] === "requestError") { + target = this.requestErrorHooks; + } else if (args[0] === "response") { + target = this.responseHooks; + } else { + throw new Error(`Invalid hook type: ${args[0]}`); } - /** Removes a hook that was previously registered with `addHook`. */ - removeHook(hook: "beforeRequest", fn: BeforeRequestHook): this; - /** Removes a hook that was previously registered with `addHook`. */ - removeHook(hook: "requestError", fn: RequestErrorHook): this; - /** Removes a hook that was previously registered with `addHook`. */ - removeHook(hook: "response", fn: ResponseHook): this; - removeHook( - ...args: - | [hook: "beforeRequest", fn: BeforeRequestHook] - | [hook: "requestError", fn: RequestErrorHook] - | [hook: "response", fn: ResponseHook] - ): this { - let target: unknown[]; - if (args[0] === "beforeRequest") { - target = this.requestHooks; - } else if (args[0] === "requestError") { - target = this.requestErrorHooks; - } else if (args[0] === "response") { - target = this.responseHooks; - } else { - throw new Error(`Invalid hook type: ${args[0]}`); - } - - const index = target.findIndex((v) => v === args[1]); - if (index >= 0) { - target.splice(index, 1); - } - - return this; + const index = target.findIndex((v) => v === args[1]); + if (index >= 0) { + target.splice(index, 1); } - clone(): HTTPClient { - const child = new HTTPClient(this.options); - child.requestHooks = this.requestHooks.slice(); - child.requestErrorHooks = this.requestErrorHooks.slice(); - child.responseHooks = this.responseHooks.slice(); + return this; + } - return child; - } + clone(): HTTPClient { + const child = new HTTPClient(this.options); + child.requestHooks = this.requestHooks.slice(); + child.requestErrorHooks = this.requestErrorHooks.slice(); + child.responseHooks = this.responseHooks.slice(); + + return child; + } } export type StatusCodePredicate = number | string | (number | string)[]; @@ -159,344 +157,167 @@ export type StatusCodePredicate = number | string | (number | string)[]; // segments in a media type string. const mediaParamSeparator = /\s*;\s*/g; -function matchContentType(response: Response, pattern: string): boolean { - // `*` is a special case which means anything is acceptable. - if (pattern === "*") { - return true; +export function matchContentType(response: Response, pattern: string): boolean { + // `*` is a special case which means anything is acceptable. + if (pattern === "*") { + return true; + } + + let contentType = + response.headers.get("content-type")?.trim() || "application/octet-stream"; + contentType = contentType.toLowerCase(); + + const wantParts = pattern.toLowerCase().trim().split(mediaParamSeparator); + const [wantType = "", ...wantParams] = wantParts; + + if (wantType.split("/").length !== 2) { + return false; + } + + const gotParts = contentType.split(mediaParamSeparator); + const [gotType = "", ...gotParams] = gotParts; + + const [type = "", subtype = ""] = gotType.split("/"); + if (!type || !subtype) { + return false; + } + + if ( + wantType !== "*/*" && + gotType !== wantType && + `${type}/*` !== wantType && + `*/${subtype}` !== wantType + ) { + return false; + } + + if (gotParams.length < wantParams.length) { + return false; + } + + const params = new Set(gotParams); + for (const wantParam of wantParams) { + if (!params.has(wantParam)) { + return false; } + } - let contentType = response.headers.get("content-type")?.trim() || "application/octet-stream"; - contentType = contentType.toLowerCase(); + return true; +} - const wantParts = pattern.toLowerCase().trim().split(mediaParamSeparator); - const [wantType = "", ...wantParams] = wantParts; +const codeRangeRE = new RegExp("^[0-9]xx$", "i"); - if (wantType.split("/").length !== 2) { - return false; - } +export function matchStatusCode( + response: Response, + codes: StatusCodePredicate, +): boolean { + const actual = `${response.status}`; + const expectedCodes = Array.isArray(codes) ? codes : [codes]; + if (!expectedCodes.length) { + return false; + } - const gotParts = contentType.split(mediaParamSeparator); - const [gotType = "", ...gotParams] = gotParts; + return expectedCodes.some((ec) => { + const code = `${ec}`; - const [type = "", subtype = ""] = gotType.split("/"); - if (!type || !subtype) { - return false; + if (code === "default") { + return true; } - if ( - wantType !== "*/*" && - gotType !== wantType && - `${type}/*` !== wantType && - `*/${subtype}` !== wantType - ) { - return false; + if (!codeRangeRE.test(`${code}`)) { + return code === actual; } - if (gotParams.length < wantParams.length) { - return false; + const expectFamily = code.charAt(0); + if (!expectFamily) { + throw new Error("Invalid status code range"); } - const params = new Set(gotParams); - for (const wantParam of wantParams) { - if (!params.has(wantParam)) { - return false; - } + const actualFamily = actual.charAt(0); + if (!actualFamily) { + throw new Error(`Invalid response status code: ${actual}`); } - return true; -} - -const codeRangeRE = new RegExp("^[0-9]xx$", "i"); - -export function matchStatusCode(response: Response, codes: StatusCodePredicate): boolean { - const actual = `${response.status}`; - const expectedCodes = Array.isArray(codes) ? codes : [codes]; - if (!expectedCodes.length) { - return false; - } - - return expectedCodes.some((ec) => { - const code = `${ec}`; - - if (code === "default") { - return true; - } - - if (!codeRangeRE.test(`${code}`)) { - return code === actual; - } - - const expectFamily = code.charAt(0); - if (!expectFamily) { - throw new Error("Invalid status code range"); - } - - const actualFamily = actual.charAt(0); - if (!actualFamily) { - throw new Error(`Invalid response status code: ${actual}`); - } - - return actualFamily === expectFamily; - }); + return actualFamily === expectFamily; + }); } export function matchResponse( - response: Response, - code: StatusCodePredicate, - contentTypePattern: string + response: Response, + code: StatusCodePredicate, + contentTypePattern: string, ): boolean { - return matchStatusCode(response, code) && matchContentType(response, contentTypePattern); -} - -const headerValRE = /, */; -export function unpackHeaders(headers: Headers): Record { - const out: Record = {}; - - for (const [k, v] of headers.entries()) { - out[k] = v.split(headerValRE); - } - - return out; + return ( + matchStatusCode(response, code) && + matchContentType(response, contentTypePattern) + ); } -type ResponseMatcherSchema = - | { parse: (data: unknown) => T } - | { inboundSchema: { parse: (data: unknown) => T } }; +/** + * Uses various heurisitics to determine if an error is a connection error. + */ +export function isConnectionError(err: unknown): boolean { + if (typeof err !== "object" || err == null) { + return false; + } -type SerializationMethod = "sse" | "json" | "rawBytes" | "rawStream" | "text" | "void" | "fail"; + // Covers fetch in Deno as well + const isBrowserErr = + err instanceof TypeError && + err.message.toLowerCase().startsWith("failed to fetch"); -const defaultContentTypes: Record = { - sse: "text/event-stream", - json: "application/json", - rawBytes: "application/octet-stream", - rawStream: "application/octet-stream", - text: "text/plain", - void: "", - fail: "", -}; + const isNodeErr = + err instanceof TypeError && + err.message.toLowerCase().startsWith("fetch failed"); -type ResponsePredicateMatch = { - method: SerializationMethod; - codes: StatusCodePredicate; - ctype: string; - schema: ResponseMatcherSchema; - hdrs: boolean; - key: string | undefined; - err: boolean; - fail: boolean; -}; + const isBunErr = "name" in err && err.name === "ConnectionError"; -type ResponsePredicateOptions = { - /** Content type to match on. */ - ctype?: string; - /** Pass HTTP headers to deserializer. */ - hdrs?: boolean; -} & ( - | { - /** The result key to store the deserialized value into. */ - key?: string; - fail?: never; - err?: never; - } - | { - /** Indicates the matched response must throw the built-in error. */ - fail: true; - key?: never; - err?: never; - } - | { - /** Indicates the matched response is a custom error. */ - err: true; - key?: never; - fail?: never; - } -); - -export class ResponseMatcher { - private predicates: ResponsePredicateMatch[] = []; - - #any( - method: SerializationMethod, - codes: StatusCodePredicate, - schema: ResponseMatcherSchema, - opts?: ResponsePredicateOptions - ) { - const ctype = opts?.ctype || defaultContentTypes[method]; - const hdrs = !!opts?.hdrs; - const key = opts?.key; - const err = !!opts?.err; - const fail = !!opts?.fail; - this.predicates.push({ - method, - codes, - ctype, - schema, - hdrs, - key, - err, - fail, - }); - return this; - } + const isGenericErr = + "code" in err && + typeof err.code === "string" && + err.code.toLowerCase() === "econnreset"; - json( - codes: StatusCodePredicate, - schema: ResponseMatcherSchema, - opts?: ResponsePredicateOptions - ): this { - return this.#any("json", codes, schema, opts); - } - bytes( - codes: StatusCodePredicate, - schema: ResponseMatcherSchema, - opts?: ResponsePredicateOptions - ): this { - return this.#any("rawBytes", codes, schema, opts); - } - stream( - codes: StatusCodePredicate, - schema: ResponseMatcherSchema, - opts?: ResponsePredicateOptions - ): this { - return this.#any("rawStream", codes, schema, opts); - } - text( - codes: StatusCodePredicate, - schema: ResponseMatcherSchema, - opts?: ResponsePredicateOptions - ): this { - return this.#any("text", codes, schema, opts); - } - sse( - codes: StatusCodePredicate, - schema: ResponseMatcherSchema, - opts?: Omit - ): this { - return this.#any("sse", codes, schema, opts); - } - void( - codes: StatusCodePredicate, - schema: ResponseMatcherSchema, - opts?: Pick - ): this { - return this.#any("void", codes, schema, opts); - } - fail(codes: StatusCodePredicate): this { - return this.#any("fail", codes, znever(), { fail: true }); - } - - async match( - response: Response, - // envelope-http - request: Request, - - options?: { - resultKey?: string; - extraFields?: Record; - } - ): Promise<[result: Result, rawData: unknown]> { - let pred: ResponsePredicateMatch | undefined; - for (const predicate of this.predicates) { - const { codes, ctype } = predicate; - if (ctype && matchResponse(response, codes, ctype)) { - pred = predicate; - break; - } else if (!ctype && matchStatusCode(response, codes)) { - pred = predicate; - break; - } - } - if (pred == null) { - await discardResponseBody(response); - throw new SDKError("Unexpected API response status or content-type", { - response, - request, - }); - } - - const { method, schema } = pred; - - let raw: unknown; - switch (method) { - case "json": - raw = await response.json(); - break; - case "rawBytes": - raw = await response.arrayBuffer(); - break; - case "rawStream": - raw = response.body; - break; - case "text": - raw = await response.text(); - break; - case "sse": - raw = response.body; - break; - case "void": - raw = await discardResponseBody(response); - break; - case "fail": - raw = await discardResponseBody(response); - break; - default: - method satisfies never; - throw new Error(`Unsupported response type: ${method}`); - } - - const resultKey = pred.key || options?.resultKey; - let data: unknown; - if (pred.fail) { - throw new SDKError("API error occurred", { response, request }); - } else if (pred.err) { - data = { - ...options?.extraFields, - ...(pred.hdrs ? { Headers: unpackHeaders(response.headers) } : null), - ...(isPlainObject(raw) ? raw : null), - }; - } else if (resultKey) { - data = { - ...options?.extraFields, - ...(pred.hdrs ? { Headers: unpackHeaders(response.headers) } : null), - [resultKey]: raw, - }; - } else { - data = { - ...options?.extraFields, - ...(pred.hdrs ? { Headers: unpackHeaders(response.headers) } : null), - }; - } - - const parser = "inboundSchema" in schema ? schema.inboundSchema : schema; - const body = parse(data, (v: unknown) => parser.parse(v), "Response validation failed"); - - if (body instanceof Error) { - throw body; - } - - return [body, raw]; - } + return isBrowserErr || isNodeErr || isGenericErr || isBunErr; } /** - * Discards the response body to free up resources. - * - * To learn why this is need, see the undici docs: - * https://undici.nodejs.org/#/?id=garbage-collection + * Uses various heurisitics to determine if an error is a timeout error. */ -export async function discardResponseBody(res: Response) { - const reader = res.body?.getReader(); - if (reader == null) { - return; - } +export function isTimeoutError(err: unknown): boolean { + if (typeof err !== "object" || err == null) { + return false; + } + + // Fetch in browser, Node.js, Bun, Deno + const isNative = "name" in err && err.name === "TimeoutError"; + const isLegacyNative = "code" in err && err.code === 23; + + // Node.js HTTP client and Axios + const isGenericErr = + "code" in err && + typeof err.code === "string" && + err.code.toLowerCase() === "econnaborted"; + + return isNative || isLegacyNative || isGenericErr; +} - try { - let done = false; - while (!done) { - const res = await reader.read(); - done = res.done; - } - } finally { - reader.releaseLock(); - } +/** + * Uses various heurisitics to determine if an error is a abort error. + */ +export function isAbortError(err: unknown): boolean { + if (typeof err !== "object" || err == null) { + return false; + } + + // Fetch in browser, Node.js, Bun, Deno + const isNative = "name" in err && err.name === "AbortError"; + const isLegacyNative = "code" in err && err.code === 20; + + // Node.js HTTP client and Axios + const isGenericErr = + "code" in err && + typeof err.code === "string" && + err.code.toLowerCase() === "econnaborted"; + + return isNative || isLegacyNative || isGenericErr; } diff --git a/ts/src/lib/is-plain-object.ts b/ts/src/lib/is-plain-object.ts index af0d6e7..61070d3 100644 --- a/ts/src/lib/is-plain-object.ts +++ b/ts/src/lib/is-plain-object.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ /* diff --git a/ts/src/lib/logger.ts b/ts/src/lib/logger.ts new file mode 100644 index 0000000..d181f29 --- /dev/null +++ b/ts/src/lib/logger.ts @@ -0,0 +1,9 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +export interface Logger { + group(label?: string): void; + groupEnd(): void; + log(message: any, ...args: any[]): void; +} diff --git a/ts/src/lib/matchers.ts b/ts/src/lib/matchers.ts new file mode 100644 index 0000000..fd4672d --- /dev/null +++ b/ts/src/lib/matchers.ts @@ -0,0 +1,301 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import { safeParse } from "./schemas.js"; +import { StatusCodePredicate, matchResponse, matchStatusCode } from "./http.js"; +import { isPlainObject } from "./is-plain-object.js"; +import { SDKError } from "../models/errors/sdkerror.js"; +import { SDKValidationError } from "../models/errors/sdkvalidationerror.js"; +import { Result } from "../types/fp.js"; + +export type Encoding = "json" | "text" | "bytes" | "stream" | "sse" | "nil" | "fail"; + +const DEFAULT_CONTENT_TYPES: Record = { + json: "application/json", + text: "text/plain", + bytes: "application/octet-stream", + stream: "application/octet-stream", + sse: "text/event-stream", + nil: "*", + fail: "*", +}; + +type Schema = { parse(raw: unknown): T }; + +type MatchOptions = { ctype?: string; hdrs?: boolean; key?: string; sseSentinel?: string }; + +export type ValueMatcher = MatchOptions & { + enc: Encoding; + codes: StatusCodePredicate; + schema: Schema; +}; + +export type ErrorMatcher = MatchOptions & { + enc: Encoding; + codes: StatusCodePredicate; + schema: Schema; + err: true; +}; + +export type FailMatcher = { + enc: "fail"; + codes: StatusCodePredicate; +}; + +export type Matcher = ValueMatcher | ErrorMatcher | FailMatcher; + +export function jsonErr( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ErrorMatcher { + return { ...options, err: true, enc: "json", codes, schema }; +} +export function json( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ValueMatcher { + return { ...options, enc: "json", codes, schema }; +} + +export function textErr( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ErrorMatcher { + return { ...options, err: true, enc: "text", codes, schema }; +} +export function text( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ValueMatcher { + return { ...options, enc: "text", codes, schema }; +} + +export function bytesErr( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ErrorMatcher { + return { ...options, err: true, enc: "bytes", codes, schema }; +} +export function bytes( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ValueMatcher { + return { ...options, enc: "bytes", codes, schema }; +} + +export function streamErr( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ErrorMatcher { + return { ...options, err: true, enc: "stream", codes, schema }; +} +export function stream( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ValueMatcher { + return { ...options, enc: "stream", codes, schema }; +} + +export function sseErr( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ErrorMatcher { + return { ...options, err: true, enc: "sse", codes, schema }; +} +export function sse( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ValueMatcher { + return { ...options, enc: "sse", codes, schema }; +} + +export function nilErr( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ErrorMatcher { + return { ...options, err: true, enc: "nil", codes, schema }; +} +export function nil( + codes: StatusCodePredicate, + schema: Schema, + options?: MatchOptions +): ValueMatcher { + return { ...options, enc: "nil", codes, schema }; +} + +export function fail(codes: StatusCodePredicate): FailMatcher { + return { enc: "fail", codes }; +} + +export type MatchedValue = Matchers extends Matcher[] ? T : never; +export type MatchedError = Matchers extends Matcher[] ? E : never; +export type MatchFunc = ( + response: Response, + request: Request, + + options?: { resultKey?: string; extraFields?: Record } +) => Promise<[result: Result, raw: unknown]>; + +export function match( + ...matchers: Array> +): MatchFunc { + return async function matchFunc( + response: Response, + request: Request, + + options?: { resultKey?: string; extraFields?: Record } + ): Promise<[result: Result, raw: unknown]> { + let raw: unknown; + let matcher: Matcher | undefined; + for (const match of matchers) { + const { codes } = match; + const ctpattern = "ctype" in match ? match.ctype : DEFAULT_CONTENT_TYPES[match.enc]; + if (ctpattern && matchResponse(response, codes, ctpattern)) { + matcher = match; + break; + } else if (!ctpattern && matchStatusCode(response, codes)) { + matcher = match; + break; + } + } + + if (!matcher) { + await discardResponseBody(response); + return [ + { + ok: false, + error: new SDKError("Unexpected API response status or content-type", { + response, + request, + }), + }, + raw, + ]; + } + + const encoding = matcher.enc; + switch (encoding) { + case "json": + raw = await response.json(); + break; + case "bytes": + raw = await response.arrayBuffer(); + break; + case "stream": + raw = response.body; + break; + case "text": + raw = await response.text(); + break; + case "sse": + raw = response.body; + break; + case "nil": + raw = await discardResponseBody(response); + break; + case "fail": + raw = await discardResponseBody(response); + break; + default: + encoding satisfies never; + throw new Error(`Unsupported response type: ${encoding}`); + } + + if (matcher.enc === "fail") { + return [ + { ok: false, error: new SDKError("API error occurred", { response, request }) }, + raw, + ]; + } + + const resultKey = matcher.key || options?.resultKey; + let data: unknown; + + if ("err" in matcher) { + data = { + ...options?.extraFields, + ...(matcher.hdrs ? { Headers: unpackHeaders(response.headers) } : null), + ...(isPlainObject(raw) ? raw : null), + }; + } else if (resultKey) { + data = { + ...options?.extraFields, + ...(matcher.hdrs ? { Headers: unpackHeaders(response.headers) } : null), + [resultKey]: raw, + }; + } else { + data = { + ...options?.extraFields, + ...(matcher.hdrs ? { Headers: unpackHeaders(response.headers) } : null), + }; + } + + if ("err" in matcher) { + const result = safeParse( + data, + (v: unknown) => matcher.schema.parse(v), + "Response validation failed" + ); + return [result.ok ? { ok: false, error: result.value } : result, raw]; + } else { + return [ + safeParse( + data, + (v: unknown) => matcher.schema.parse(v), + "Response validation failed" + ), + raw, + ]; + } + }; +} + +const headerValRE = /, */; +/** + * Iterates over a Headers object and returns an object with all the header + * entries. Values are represented as an array to account for repeated headers. + */ +export function unpackHeaders(headers: Headers): Record { + const out: Record = {}; + + for (const [k, v] of headers.entries()) { + out[k] = v.split(headerValRE); + } + + return out; +} + +/** + * Discards the response body to free up resources. + * + * To learn why this is need, see the undici docs: + * https://undici.nodejs.org/#/?id=garbage-collection + */ +export async function discardResponseBody(res: Response) { + const reader = res.body?.getReader(); + if (reader == null) { + return; + } + + try { + let done = false; + while (!done) { + const res = await reader.read(); + done = res.done; + } + } finally { + reader.releaseLock(); + } +} diff --git a/ts/src/lib/primitives.ts b/ts/src/lib/primitives.ts index d3aae30..23794a2 100644 --- a/ts/src/lib/primitives.ts +++ b/ts/src/lib/primitives.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ export type Remap = { diff --git a/ts/src/lib/retries.ts b/ts/src/lib/retries.ts index ed9b495..df3e0bc 100644 --- a/ts/src/lib/retries.ts +++ b/ts/src/lib/retries.ts @@ -1,7 +1,9 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ +import { isConnectionError, isTimeoutError } from "./http.js"; + export type BackoffStrategy = { initialInterval: number; maxInterval: number; @@ -99,47 +101,6 @@ function wrapFetcher( }; } -function isConnectionError(err: unknown) { - if (typeof err !== "object" || err == null) { - return false; - } - - // Covers fetch in Deno as well - const isBrowserErr = - err instanceof TypeError && - err.message.toLowerCase().startsWith("failed to fetch"); - - const isNodeErr = - err instanceof TypeError && - err.message.toLowerCase().startsWith("fetch failed"); - - const isBunErr = "name" in err && err.name === "ConnectionError"; - - const isGenericErr = - "code" in err && - typeof err.code === "string" && - err.code.toLowerCase() === "econnreset"; - - return isBrowserErr || isNodeErr || isGenericErr || isBunErr; -} - -function isTimeoutError(err: unknown) { - if (typeof err !== "object" || err == null) { - return false; - } - - // Fetch in browser, Node.js, Bun, Deno - const isNative = "name" in err && err.name === "TimeoutError"; - - // Node.js HTTP client and Axios - const isGenericErr = - "code" in err && - typeof err.code === "string" && - err.code.toLowerCase() === "econnaborted"; - - return isNative || isGenericErr; -} - const codeRangeRE = new RegExp("^[0-9]xx$", "i"); function isRetryableResponse(res: Response, statusCodes: string[]): boolean { diff --git a/ts/src/lib/schemas.ts b/ts/src/lib/schemas.ts index 94b0535..37b0b13 100644 --- a/ts/src/lib/schemas.ts +++ b/ts/src/lib/schemas.ts @@ -1,9 +1,10 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import { output, ZodEffects, ZodError, ZodObject, ZodRawShape, ZodTypeAny } from "zod"; import { SDKValidationError } from "../models/errors/sdkvalidationerror.js"; +import { ERR, OK, Result } from "../types/fp.js"; /** * Utility function that executes some code which may throw a ZodError. It @@ -21,6 +22,23 @@ export function parse(rawValue: Inp, fn: (value: Inp) => Out, errorMes } } +/** + * Utility function that executes some code which may result in a ZodError. It + * intercepts this error and converts it to an SDKValidationError so as to not + * leak Zod implementation details to user code. + */ +export function safeParse( + rawValue: Inp, + fn: (value: Inp) => Out, + errorMessage: string +): Result { + try { + return OK(fn(rawValue)); + } catch (err) { + return ERR(new SDKValidationError(errorMessage, err, rawValue)); + } +} + export function collectExtraKeys< Shape extends ZodRawShape, Catchall extends ZodTypeAny, diff --git a/ts/src/lib/sdks.ts b/ts/src/lib/sdks.ts index cbda9d8..51b49bd 100644 --- a/ts/src/lib/sdks.ts +++ b/ts/src/lib/sdks.ts @@ -1,16 +1,51 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ -import { ResponseMatcher, HTTPClient, matchStatusCode } from "./http.js"; -import { SecurityState, resolveSecurity } from "./security.js"; -import { pathToFunc } from "./url.js"; +import { + HTTPClient, + matchContentType, + matchStatusCode, + isAbortError, + isTimeoutError, + isConnectionError, +} from "./http.js"; +import { SecurityState } from "./security.js"; +import { retry, RetryConfig } from "./retries.js"; +import { Logger } from "./logger.js"; import { encodeForm } from "./encodings.js"; import { stringToBase64 } from "./base64.js"; +import { SDKOptions, SDK_METADATA, serverURLFromOptions } from "./config.js"; import { SDKHooks } from "../hooks/hooks.js"; import { HookContext } from "../hooks/types.js"; +import { + ConnectionError, + InvalidRequestError, + RequestAbortedError, + RequestTimeoutError, + UnexpectedClientError, +} from "../models/errors/httpclienterrors.js"; +import { ERR, OK, Result } from "../types/fp.js"; export type RequestOptions = { + /** + * Sets a timeout, in milliseconds, on HTTP requests made by an SDK method. If + * `fetchOptions.signal` is set then it will take precedence over this option. + */ + timeoutMs?: number; + /** + * Set or override a retry policy on HTTP calls. + */ + retries?: RetryConfig; + /** + * Specifies the status codes which should be retried using the given retry policy. + */ + retryCodes?: string[]; + /** + * Sets various request options on the `fetch` call made by an SDK method. + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#options|Request} + */ fetchOptions?: Omit; }; @@ -22,35 +57,65 @@ type RequestConfig = { body?: RequestInit["body"]; headers?: HeadersInit; security?: SecurityState | null; + uaHeader?: string; + timeoutMs?: number; }; +const gt: unknown = typeof globalThis === "undefined" ? null : globalThis; +const webWorkerLike = + typeof gt === "object" && + gt != null && + "importScripts" in gt && + typeof gt["importScripts"] === "function"; +const isBrowserLike = + webWorkerLike || + (typeof navigator !== "undefined" && "serviceWorker" in navigator) || + (typeof window === "object" && typeof window.document !== "undefined"); + export class ClientSDK { - private readonly client: HTTPClient; + private readonly httpClient: HTTPClient; protected readonly baseURL: URL | null; protected readonly hooks$: SDKHooks; + protected readonly logger?: Logger | undefined; + public readonly options$: SDKOptions & { hooks?: SDKHooks }; - constructor(init: { client: HTTPClient; baseURL: URL | null; hooks: SDKHooks }) { - const url = init.baseURL; + constructor(options: SDKOptions = {}) { + const opt = options as unknown; + if ( + typeof opt === "object" && + opt != null && + "hooks" in opt && + opt.hooks instanceof SDKHooks + ) { + this.hooks$ = opt.hooks; + } else { + this.hooks$ = new SDKHooks(); + } + this.options$ = { ...options, hooks: this.hooks$ }; + + const url = serverURLFromOptions(options); if (url) { url.pathname = url.pathname.replace(/\/+$/, "") + "/"; } - - this.hooks$ = init.hooks; - const { baseURL, client } = this.hooks$.sdkInit({ baseURL: url, client: init.client }); + const { baseURL, client } = this.hooks$.sdkInit({ + baseURL: url, + client: options.httpClient || new HTTPClient(), + }); this.baseURL = baseURL; - this.client = client; + this.httpClient = client; + this.logger = options.debugLogger; } - protected createRequest$( + public createRequest$( context: HookContext, conf: RequestConfig, options?: RequestOptions - ): Request { + ): Result { const { method, path, query, headers: opHeaders, security } = conf; const base = conf.baseURL ?? this.baseURL; if (!base) { - throw new TypeError("No base URL provided for operation"); + return ERR(new InvalidRequestError("No base URL provided for operation")); } const reqURL = new URL(base); const inputURL = new URL(path, reqURL); @@ -100,48 +165,193 @@ export class ClientSDK { headers.set(k, v); } - const input = this.hooks$.beforeCreateRequest(context, { - url: reqURL, - options: { - ...options?.fetchOptions, - body: conf.body ?? null, - headers, - method, - }, - }); + // Only set user agent header in non-browser-like environments since CORS + // policy disallows setting it in browsers e.g. Chrome throws an error. + if (!isBrowserLike) { + headers.set(conf.uaHeader ?? "user-agent", SDK_METADATA.userAgent); + } - return new Request(input.url, input.options); + let fetchOptions = options?.fetchOptions; + if (!fetchOptions?.signal && conf.timeoutMs && conf.timeoutMs > 0) { + const timeoutSignal = AbortSignal.timeout(conf.timeoutMs); + if (!fetchOptions) { + fetchOptions = { signal: timeoutSignal }; + } else { + fetchOptions.signal = timeoutSignal; + } + } + + let input; + try { + input = this.hooks$.beforeCreateRequest(context, { + url: reqURL, + options: { + ...fetchOptions, + body: conf.body ?? null, + headers, + method, + }, + }); + } catch (err: unknown) { + return ERR( + new UnexpectedClientError("Create request hook failed to execute", { cause: err }) + ); + } + + return OK(new Request(input.url, input.options)); } - protected async do$( - req: Request, + public async do$( + request: Request, options: { context: HookContext; errorCodes: number | string | (number | string)[]; + retryConfig?: RetryConfig | undefined; + retryCodes?: string[] | undefined; } - ): Promise { + ): Promise< + Result< + Response, + RequestAbortedError | RequestTimeoutError | ConnectionError | UnexpectedClientError + > + > { const { context, errorCodes } = options; + const retryConfig = options.retryConfig || { strategy: "none" }; + const retryCodes = options.retryCodes || []; + + return retry( + async () => { + const req = await this.hooks$.beforeRequest(context, request.clone()); + await logRequest(this.logger, req).catch((e) => + this.logger?.log("Failed to log request:", e) + ); - let response = await this.client.request(await this.hooks$.beforeRequest(context, req)); + let response = await this.httpClient.request(req); - if (matchStatusCode(response, errorCodes)) { - const result = await this.hooks$.afterError(context, response, null); - if (result.error) { - throw result.error; + if (matchStatusCode(response, errorCodes)) { + const result = await this.hooks$.afterError(context, response, null); + if (result.error) { + throw result.error; + } + response = result.response || response; + } else { + response = await this.hooks$.afterSuccess(context, response); + } + + await logResponse(this.logger, response, req).catch((e) => + this.logger?.log("Failed to log response:", e) + ); + + return response; + }, + { config: retryConfig, statusCodes: retryCodes } + ).then( + (r) => OK(r), + (err) => { + switch (true) { + case isAbortError(err): + return ERR( + new RequestAbortedError("Request aborted by client", { cause: err }) + ); + case isTimeoutError(err): + return ERR(new RequestTimeoutError("Request timed out", { cause: err })); + case isConnectionError(err): + return ERR(new ConnectionError("Unable to make request", { cause: err })); + default: + return ERR( + new UnexpectedClientError("Unexpected HTTP client error", { + cause: err, + }) + ); + } } - response = result.response || response; - } else { - response = await this.hooks$.afterSuccess(context, response); + ); + } +} + +const jsonLikeContentTypeRE = /^application\/(?:.{0,100}\+)?json/; +async function logRequest(logger: Logger | undefined, req: Request) { + if (!logger) { + return; + } + + const contentType = req.headers.get("content-type"); + const ct = contentType?.split(";")[0] || ""; + + logger.group(`> Request: ${req.method} ${req.url}`); + + logger.group("Headers:"); + for (const [k, v] of req.headers.entries()) { + logger.log(`${k}: ${v}`); + } + logger.groupEnd(); + + logger.group("Body:"); + switch (true) { + case jsonLikeContentTypeRE.test(ct): + logger.log(await req.clone().json()); + break; + case ct.startsWith("text/"): + logger.log(await req.clone().text()); + break; + case ct === "multipart/form-data": { + const body = await req.clone().formData(); + for (const [k, v] of body) { + const vlabel = v instanceof Blob ? "" : v; + logger.log(`${k}: ${vlabel}`); + } + break; } + default: + logger.log(`<${contentType}>`); + break; + } + logger.groupEnd(); - return response; + logger.groupEnd(); +} + +async function logResponse(logger: Logger | undefined, res: Response, req: Request) { + if (!logger) { + return; } - protected matcher(): ResponseMatcher { - return new ResponseMatcher(); + const contentType = res.headers.get("content-type"); + const ct = contentType?.split(";")[0] || ""; + + logger.group(`< Response: ${req.method} ${req.url}`); + logger.log("Status Code:", res.status, res.statusText); + + logger.group("Headers:"); + for (const [k, v] of res.headers.entries()) { + logger.log(`${k}: ${v}`); } + logger.groupEnd(); - protected templateURLComponent = pathToFunc; + logger.group("Body:"); + switch (true) { + case matchContentType(res, "application/json") || jsonLikeContentTypeRE.test(ct): + logger.log(await res.clone().json()); + break; + case matchContentType(res, "text/event-stream"): + logger.log(`<${contentType}>`); + break; + case matchContentType(res, "text/*"): + logger.log(await res.clone().text()); + break; + case matchContentType(res, "multipart/form-data"): { + const body = await res.clone().formData(); + for (const [k, v] of body) { + const vlabel = v instanceof Blob ? "" : v; + logger.log(`${k}: ${vlabel}`); + } + break; + } + default: + logger.log(`<${contentType}>`); + break; + } + logger.groupEnd(); - protected resolveSecurity = resolveSecurity; + logger.groupEnd(); } diff --git a/ts/src/lib/security.ts b/ts/src/lib/security.ts index 560cb62..d529ec7 100644 --- a/ts/src/lib/security.ts +++ b/ts/src/lib/security.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ export enum SecurityErrorCode { @@ -68,13 +68,20 @@ type SecurityInputOAuth2ClientCredentials = { value: { clientID?: string | undefined; clientSecret?: string | undefined } | null | undefined; }; +type SecurityInputCustom = { + type: "http:custom"; + value: any | null | undefined; + fieldName: string; +}; + export type SecurityInput = | SecurityInputBasic | SecurityInputBearer | SecurityInputAPIKey | SecurityInputOAuth2 | SecurityInputOAuth2ClientCredentials - | SecurityInputOIDC; + | SecurityInputOIDC + | SecurityInputCustom; export function resolveSecurity(...options: SecurityInput[][]): SecurityState | null { const state: SecurityState = { @@ -90,6 +97,8 @@ export function resolveSecurity(...options: SecurityInput[][]): SecurityState | return false; } else if (o.type === "http:basic") { return o.value.username != null || o.value.password != null; + } else if (o.type === "http:custom") { + return null; } else if (o.type === "oauth2:client_credentials") { return o.value.clientID != null || o.value.clientSecret != null; } else if (typeof o.value === "string") { @@ -125,6 +134,8 @@ export function resolveSecurity(...options: SecurityInput[][]): SecurityState | case "http:basic": applyBasic(state, spec); break; + case "http:custom": + break; case "http:bearer": applyBearer(state, spec); break; diff --git a/ts/src/lib/url.ts b/ts/src/lib/url.ts index df58acb..6bc6356 100644 --- a/ts/src/lib/url.ts +++ b/ts/src/lib/url.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ const hasOwn = Object.prototype.hasOwnProperty; diff --git a/ts/src/models/components/error.ts b/ts/src/models/components/error.ts index 1ebc730..8caa141 100644 --- a/ts/src/models/components/error.ts +++ b/ts/src/models/components/error.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import * as z from "zod"; @@ -10,19 +10,32 @@ export type ErrorT = { }; /** @internal */ -export namespace ErrorT$ { - export const inboundSchema: z.ZodType = z.object({ - code: z.number().int(), - message: z.string(), - }); +export const ErrorT$inboundSchema: z.ZodType = z.object({ + code: z.number().int(), + message: z.string(), +}); - export type Outbound = { - code: number; - message: string; - }; +/** @internal */ +export type ErrorT$Outbound = { + code: number; + message: string; +}; - export const outboundSchema: z.ZodType = z.object({ - code: z.number().int(), - message: z.string(), - }); +/** @internal */ +export const ErrorT$outboundSchema: z.ZodType = z.object({ + code: z.number().int(), + message: z.string(), +}); + +/** + * @internal + * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. + */ +export namespace ErrorT$ { + /** @deprecated use `ErrorT$inboundSchema` instead. */ + export const inboundSchema = ErrorT$inboundSchema; + /** @deprecated use `ErrorT$outboundSchema` instead. */ + export const outboundSchema = ErrorT$outboundSchema; + /** @deprecated use `ErrorT$Outbound` instead. */ + export type Outbound = ErrorT$Outbound; } diff --git a/ts/src/models/components/httpmetadata.ts b/ts/src/models/components/httpmetadata.ts index 0517420..f4df509 100644 --- a/ts/src/models/components/httpmetadata.ts +++ b/ts/src/models/components/httpmetadata.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import { remap as remap$ } from "../../lib/primitives.js"; @@ -17,37 +17,54 @@ export type HTTPMetadata = { }; /** @internal */ -export namespace HTTPMetadata$ { - export const inboundSchema: z.ZodType = z - .object({ - Response: z.instanceof(Response), - Request: z.instanceof(Request), - }) - .transform((v) => { - return remap$(v, { - Response: "response", - Request: "request", - }); +export const HTTPMetadata$inboundSchema: z.ZodType = z + .object({ + Response: z.instanceof(Response), + Request: z.instanceof(Request), + }) + .transform((v) => { + return remap$(v, { + Response: "response", + Request: "request", }); + }); - export type Outbound = { - Response: never; - Request: never; - }; +/** @internal */ +export type HTTPMetadata$Outbound = { + Response: never; + Request: never; +}; - export const outboundSchema: z.ZodType = z - .object({ - response: z.instanceof(Response).transform(() => { - throw new Error("Response cannot be serialized"); - }), - request: z.instanceof(Request).transform(() => { - throw new Error("Response cannot be serialized"); - }), - }) - .transform((v) => { - return remap$(v, { - response: "Response", - request: "Request", - }); +/** @internal */ +export const HTTPMetadata$outboundSchema: z.ZodType< + HTTPMetadata$Outbound, + z.ZodTypeDef, + HTTPMetadata +> = z + .object({ + response: z.instanceof(Response).transform(() => { + throw new Error("Response cannot be serialized"); + }), + request: z.instanceof(Request).transform(() => { + throw new Error("Response cannot be serialized"); + }), + }) + .transform((v) => { + return remap$(v, { + response: "Response", + request: "Request", }); + }); + +/** + * @internal + * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. + */ +export namespace HTTPMetadata$ { + /** @deprecated use `HTTPMetadata$inboundSchema` instead. */ + export const inboundSchema = HTTPMetadata$inboundSchema; + /** @deprecated use `HTTPMetadata$outboundSchema` instead. */ + export const outboundSchema = HTTPMetadata$outboundSchema; + /** @deprecated use `HTTPMetadata$Outbound` instead. */ + export type Outbound = HTTPMetadata$Outbound; } diff --git a/ts/src/models/components/index.ts b/ts/src/models/components/index.ts index eb747c2..de5ce2b 100644 --- a/ts/src/models/components/index.ts +++ b/ts/src/models/components/index.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ export * from "./error.js"; diff --git a/ts/src/models/components/pet.ts b/ts/src/models/components/pet.ts index 4b766b0..333a92d 100644 --- a/ts/src/models/components/pet.ts +++ b/ts/src/models/components/pet.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import * as z from "zod"; @@ -11,22 +11,35 @@ export type Pet = { }; /** @internal */ -export namespace Pet$ { - export const inboundSchema: z.ZodType = z.object({ - id: z.number().int(), - name: z.string(), - tag: z.string().optional(), - }); +export const Pet$inboundSchema: z.ZodType = z.object({ + id: z.number().int(), + name: z.string(), + tag: z.string().optional(), +}); + +/** @internal */ +export type Pet$Outbound = { + id: number; + name: string; + tag?: string | undefined; +}; - export type Outbound = { - id: number; - name: string; - tag?: string | undefined; - }; +/** @internal */ +export const Pet$outboundSchema: z.ZodType = z.object({ + id: z.number().int(), + name: z.string(), + tag: z.string().optional(), +}); - export const outboundSchema: z.ZodType = z.object({ - id: z.number().int(), - name: z.string(), - tag: z.string().optional(), - }); +/** + * @internal + * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. + */ +export namespace Pet$ { + /** @deprecated use `Pet$inboundSchema` instead. */ + export const inboundSchema = Pet$inboundSchema; + /** @deprecated use `Pet$outboundSchema` instead. */ + export const outboundSchema = Pet$outboundSchema; + /** @deprecated use `Pet$Outbound` instead. */ + export type Outbound = Pet$Outbound; } diff --git a/ts/src/models/errors/httpclienterrors.ts b/ts/src/models/errors/httpclienterrors.ts new file mode 100644 index 0000000..cdb2995 --- /dev/null +++ b/ts/src/models/errors/httpclienterrors.ts @@ -0,0 +1,62 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +/** + * Base class for all HTTP errors. + */ +export class HTTPClientError extends Error { + /** The underlying cause of the error. */ + override readonly cause: unknown; + override name = "HTTPClientError"; + constructor(message: string, opts?: { cause?: unknown }) { + let msg = message; + if (opts?.cause) { + msg += `: ${opts.cause}`; + } + + super(msg, opts); + // In older runtimes, the cause field would not have been assigned through + // the super() call. + if (typeof this.cause === "undefined") { + this.cause = opts?.cause; + } + } +} + +/** + * An error to capture unrecognised or unexpected errors when making HTTP calls. + */ +export class UnexpectedClientError extends HTTPClientError { + override name = "UnexpectedClientError"; +} + +/** + * An error that is raised when any inputs used to create a request are invalid. + */ +export class InvalidRequestError extends HTTPClientError { + override name = "InvalidRequestError"; +} + +/** + * An error that is raised when a HTTP request was aborted by the client error. + */ +export class RequestAbortedError extends HTTPClientError { + override readonly name = "RequestAbortedError"; +} + +/** + * An error that is raised when a HTTP request timed out due to an AbortSignal + * signal timeout. + */ +export class RequestTimeoutError extends HTTPClientError { + override readonly name = "RequestTimeoutError"; +} + +/** + * An error that is raised when a HTTP client is unable to make a request to + * a server. + */ +export class ConnectionError extends HTTPClientError { + override readonly name = "ConnectionError"; +} diff --git a/ts/src/models/errors/index.ts b/ts/src/models/errors/index.ts index 629f225..900ee9d 100644 --- a/ts/src/models/errors/index.ts +++ b/ts/src/models/errors/index.ts @@ -1,6 +1,7 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ +export * from "./httpclienterrors.js"; export * from "./sdkerror.js"; export * from "./sdkvalidationerror.js"; diff --git a/ts/src/models/errors/sdkerror.ts b/ts/src/models/errors/sdkerror.ts index b726137..f4a15bc 100644 --- a/ts/src/models/errors/sdkerror.ts +++ b/ts/src/models/errors/sdkerror.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ export class SDKError extends Error { diff --git a/ts/src/models/errors/sdkvalidationerror.ts b/ts/src/models/errors/sdkvalidationerror.ts index 5eb9218..16929b9 100644 --- a/ts/src/models/errors/sdkvalidationerror.ts +++ b/ts/src/models/errors/sdkvalidationerror.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import * as z from "zod"; @@ -10,15 +10,17 @@ export class SDKValidationError extends Error { */ public readonly rawValue: unknown; + /** + * The raw message that failed validation. + */ + public readonly rawMessage: unknown; + constructor(message: string, cause: unknown, rawValue: unknown) { - super(message); + super(`${message}: ${cause}`); this.name = "SDKValidationError"; this.cause = cause; this.rawValue = rawValue; - } - - public override toString(): string { - return `${this.message}: ${this.cause}`; + this.rawMessage = message; } /** @@ -28,7 +30,7 @@ export class SDKValidationError extends Error { */ public pretty(): string { if (this.cause instanceof z.ZodError) { - return `${this.message}\n${formatZodError(this.cause)}`; + return `${this.rawMessage}\n${formatZodError(this.cause)}`; } else { return this.toString(); } diff --git a/ts/src/models/operations/createpets.ts b/ts/src/models/operations/createpets.ts index f0bd1ee..15814fa 100644 --- a/ts/src/models/operations/createpets.ts +++ b/ts/src/models/operations/createpets.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import { remap as remap$ } from "../../lib/primitives.js"; @@ -15,33 +15,54 @@ export type CreatePetsResponse = { }; /** @internal */ -export namespace CreatePetsResponse$ { - export const inboundSchema: z.ZodType = z - .object({ - HttpMeta: components.HTTPMetadata$.inboundSchema, - Error: components.ErrorT$.inboundSchema.optional(), - }) - .transform((v) => { - return remap$(v, { - HttpMeta: "httpMeta", - Error: "error", - }); +export const CreatePetsResponse$inboundSchema: z.ZodType< + CreatePetsResponse, + z.ZodTypeDef, + unknown +> = z + .object({ + HttpMeta: components.HTTPMetadata$inboundSchema, + Error: components.ErrorT$inboundSchema.optional(), + }) + .transform((v) => { + return remap$(v, { + HttpMeta: "httpMeta", + Error: "error", }); + }); - export type Outbound = { - HttpMeta: components.HTTPMetadata$.Outbound; - Error?: components.ErrorT$.Outbound | undefined; - }; +/** @internal */ +export type CreatePetsResponse$Outbound = { + HttpMeta: components.HTTPMetadata$Outbound; + Error?: components.ErrorT$Outbound | undefined; +}; - export const outboundSchema: z.ZodType = z - .object({ - httpMeta: components.HTTPMetadata$.outboundSchema, - error: components.ErrorT$.outboundSchema.optional(), - }) - .transform((v) => { - return remap$(v, { - httpMeta: "HttpMeta", - error: "Error", - }); +/** @internal */ +export const CreatePetsResponse$outboundSchema: z.ZodType< + CreatePetsResponse$Outbound, + z.ZodTypeDef, + CreatePetsResponse +> = z + .object({ + httpMeta: components.HTTPMetadata$outboundSchema, + error: components.ErrorT$outboundSchema.optional(), + }) + .transform((v) => { + return remap$(v, { + httpMeta: "HttpMeta", + error: "Error", }); + }); + +/** + * @internal + * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. + */ +export namespace CreatePetsResponse$ { + /** @deprecated use `CreatePetsResponse$inboundSchema` instead. */ + export const inboundSchema = CreatePetsResponse$inboundSchema; + /** @deprecated use `CreatePetsResponse$outboundSchema` instead. */ + export const outboundSchema = CreatePetsResponse$outboundSchema; + /** @deprecated use `CreatePetsResponse$Outbound` instead. */ + export type Outbound = CreatePetsResponse$Outbound; } diff --git a/ts/src/models/operations/index.ts b/ts/src/models/operations/index.ts index aa525d8..e0256cf 100644 --- a/ts/src/models/operations/index.ts +++ b/ts/src/models/operations/index.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ export * from "./createpets.js"; diff --git a/ts/src/models/operations/listpets.ts b/ts/src/models/operations/listpets.ts index 8d737c1..dbd1199 100644 --- a/ts/src/models/operations/listpets.ts +++ b/ts/src/models/operations/listpets.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import { remap as remap$ } from "../../lib/primitives.js"; @@ -27,58 +27,93 @@ export type ListPetsResponse = { }; /** @internal */ -export namespace ListPetsRequest$ { - export const inboundSchema: z.ZodType = z.object({ +export const ListPetsRequest$inboundSchema: z.ZodType = + z.object({ limit: z.number().int().optional(), }); - export type Outbound = { - limit?: number | undefined; - }; +/** @internal */ +export type ListPetsRequest$Outbound = { + limit?: number | undefined; +}; - export const outboundSchema: z.ZodType = z.object({ - limit: z.number().int().optional(), - }); +/** @internal */ +export const ListPetsRequest$outboundSchema: z.ZodType< + ListPetsRequest$Outbound, + z.ZodTypeDef, + ListPetsRequest +> = z.object({ + limit: z.number().int().optional(), +}); + +/** + * @internal + * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. + */ +export namespace ListPetsRequest$ { + /** @deprecated use `ListPetsRequest$inboundSchema` instead. */ + export const inboundSchema = ListPetsRequest$inboundSchema; + /** @deprecated use `ListPetsRequest$outboundSchema` instead. */ + export const outboundSchema = ListPetsRequest$outboundSchema; + /** @deprecated use `ListPetsRequest$Outbound` instead. */ + export type Outbound = ListPetsRequest$Outbound; } /** @internal */ -export namespace ListPetsResponse$ { - export const inboundSchema: z.ZodType = z - .object({ - HttpMeta: components.HTTPMetadata$.inboundSchema, - Pets: z.array(components.Pet$.inboundSchema).optional(), - Error: components.ErrorT$.inboundSchema.optional(), - Headers: z.record(z.array(z.string())), - }) - .transform((v) => { - return remap$(v, { - HttpMeta: "httpMeta", - Pets: "pets", - Error: "error", - Headers: "headers", - }); +export const ListPetsResponse$inboundSchema: z.ZodType = z + .object({ + HttpMeta: components.HTTPMetadata$inboundSchema, + Pets: z.array(components.Pet$inboundSchema).optional(), + Error: components.ErrorT$inboundSchema.optional(), + Headers: z.record(z.array(z.string())), + }) + .transform((v) => { + return remap$(v, { + HttpMeta: "httpMeta", + Pets: "pets", + Error: "error", + Headers: "headers", }); + }); - export type Outbound = { - HttpMeta: components.HTTPMetadata$.Outbound; - Pets?: Array | undefined; - Error?: components.ErrorT$.Outbound | undefined; - Headers: { [k: string]: Array }; - }; +/** @internal */ +export type ListPetsResponse$Outbound = { + HttpMeta: components.HTTPMetadata$Outbound; + Pets?: Array | undefined; + Error?: components.ErrorT$Outbound | undefined; + Headers: { [k: string]: Array }; +}; - export const outboundSchema: z.ZodType = z - .object({ - httpMeta: components.HTTPMetadata$.outboundSchema, - pets: z.array(components.Pet$.outboundSchema).optional(), - error: components.ErrorT$.outboundSchema.optional(), - headers: z.record(z.array(z.string())), - }) - .transform((v) => { - return remap$(v, { - httpMeta: "HttpMeta", - pets: "Pets", - error: "Error", - headers: "Headers", - }); +/** @internal */ +export const ListPetsResponse$outboundSchema: z.ZodType< + ListPetsResponse$Outbound, + z.ZodTypeDef, + ListPetsResponse +> = z + .object({ + httpMeta: components.HTTPMetadata$outboundSchema, + pets: z.array(components.Pet$outboundSchema).optional(), + error: components.ErrorT$outboundSchema.optional(), + headers: z.record(z.array(z.string())), + }) + .transform((v) => { + return remap$(v, { + httpMeta: "HttpMeta", + pets: "Pets", + error: "Error", + headers: "Headers", }); + }); + +/** + * @internal + * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. + */ +export namespace ListPetsResponse$ { + /** @deprecated use `ListPetsResponse$inboundSchema` instead. */ + export const inboundSchema = ListPetsResponse$inboundSchema; + /** @deprecated use `ListPetsResponse$outboundSchema` instead. */ + export const outboundSchema = ListPetsResponse$outboundSchema; + /** @deprecated use `ListPetsResponse$Outbound` instead. */ + export type Outbound = ListPetsResponse$Outbound; } diff --git a/ts/src/models/operations/showpetbyid.ts b/ts/src/models/operations/showpetbyid.ts index 932fc5f..6cc5c40 100644 --- a/ts/src/models/operations/showpetbyid.ts +++ b/ts/src/models/operations/showpetbyid.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import { remap as remap$ } from "../../lib/primitives.js"; @@ -26,53 +26,95 @@ export type ShowPetByIdResponse = { }; /** @internal */ -export namespace ShowPetByIdRequest$ { - export const inboundSchema: z.ZodType = z.object({ - petId: z.string(), - }); +export const ShowPetByIdRequest$inboundSchema: z.ZodType< + ShowPetByIdRequest, + z.ZodTypeDef, + unknown +> = z.object({ + petId: z.string(), +}); + +/** @internal */ +export type ShowPetByIdRequest$Outbound = { + petId: string; +}; - export type Outbound = { - petId: string; - }; +/** @internal */ +export const ShowPetByIdRequest$outboundSchema: z.ZodType< + ShowPetByIdRequest$Outbound, + z.ZodTypeDef, + ShowPetByIdRequest +> = z.object({ + petId: z.string(), +}); - export const outboundSchema: z.ZodType = z.object({ - petId: z.string(), - }); +/** + * @internal + * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. + */ +export namespace ShowPetByIdRequest$ { + /** @deprecated use `ShowPetByIdRequest$inboundSchema` instead. */ + export const inboundSchema = ShowPetByIdRequest$inboundSchema; + /** @deprecated use `ShowPetByIdRequest$outboundSchema` instead. */ + export const outboundSchema = ShowPetByIdRequest$outboundSchema; + /** @deprecated use `ShowPetByIdRequest$Outbound` instead. */ + export type Outbound = ShowPetByIdRequest$Outbound; } /** @internal */ -export namespace ShowPetByIdResponse$ { - export const inboundSchema: z.ZodType = z - .object({ - HttpMeta: components.HTTPMetadata$.inboundSchema, - Pet: components.Pet$.inboundSchema.optional(), - Error: components.ErrorT$.inboundSchema.optional(), - }) - .transform((v) => { - return remap$(v, { - HttpMeta: "httpMeta", - Pet: "pet", - Error: "error", - }); +export const ShowPetByIdResponse$inboundSchema: z.ZodType< + ShowPetByIdResponse, + z.ZodTypeDef, + unknown +> = z + .object({ + HttpMeta: components.HTTPMetadata$inboundSchema, + Pet: components.Pet$inboundSchema.optional(), + Error: components.ErrorT$inboundSchema.optional(), + }) + .transform((v) => { + return remap$(v, { + HttpMeta: "httpMeta", + Pet: "pet", + Error: "error", }); + }); - export type Outbound = { - HttpMeta: components.HTTPMetadata$.Outbound; - Pet?: components.Pet$.Outbound | undefined; - Error?: components.ErrorT$.Outbound | undefined; - }; +/** @internal */ +export type ShowPetByIdResponse$Outbound = { + HttpMeta: components.HTTPMetadata$Outbound; + Pet?: components.Pet$Outbound | undefined; + Error?: components.ErrorT$Outbound | undefined; +}; - export const outboundSchema: z.ZodType = z - .object({ - httpMeta: components.HTTPMetadata$.outboundSchema, - pet: components.Pet$.outboundSchema.optional(), - error: components.ErrorT$.outboundSchema.optional(), - }) - .transform((v) => { - return remap$(v, { - httpMeta: "HttpMeta", - pet: "Pet", - error: "Error", - }); +/** @internal */ +export const ShowPetByIdResponse$outboundSchema: z.ZodType< + ShowPetByIdResponse$Outbound, + z.ZodTypeDef, + ShowPetByIdResponse +> = z + .object({ + httpMeta: components.HTTPMetadata$outboundSchema, + pet: components.Pet$outboundSchema.optional(), + error: components.ErrorT$outboundSchema.optional(), + }) + .transform((v) => { + return remap$(v, { + httpMeta: "HttpMeta", + pet: "Pet", + error: "Error", }); + }); + +/** + * @internal + * @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module. + */ +export namespace ShowPetByIdResponse$ { + /** @deprecated use `ShowPetByIdResponse$inboundSchema` instead. */ + export const inboundSchema = ShowPetByIdResponse$inboundSchema; + /** @deprecated use `ShowPetByIdResponse$outboundSchema` instead. */ + export const outboundSchema = ShowPetByIdResponse$outboundSchema; + /** @deprecated use `ShowPetByIdResponse$Outbound` instead. */ + export type Outbound = ShowPetByIdResponse$Outbound; } diff --git a/ts/src/sdk/index.ts b/ts/src/sdk/index.ts index 34a84fc..ecac226 100644 --- a/ts/src/sdk/index.ts +++ b/ts/src/sdk/index.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ export * from "./sdk.js"; diff --git a/ts/src/sdk/pets.ts b/ts/src/sdk/pets.ts index f9c7f4e..27dcb8c 100644 --- a/ts/src/sdk/pets.ts +++ b/ts/src/sdk/pets.ts @@ -1,47 +1,16 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ -import { SDKHooks } from "../hooks/hooks.js"; -import { SDK_METADATA, SDKOptions, serverURLFromOptions } from "../lib/config.js"; -import { - encodeFormQuery as encodeFormQuery$, - encodeJSON as encodeJSON$, - encodeSimple as encodeSimple$, -} from "../lib/encodings.js"; -import { HTTPClient } from "../lib/http.js"; -import * as schemas$ from "../lib/schemas.js"; +import { petsCreatePets } from "../funcs/petsCreatePets.js"; +import { petsListPets } from "../funcs/petsListPets.js"; +import { petsShowPetById } from "../funcs/petsShowPetById.js"; import { ClientSDK, RequestOptions } from "../lib/sdks.js"; import * as components from "../models/components/index.js"; import * as operations from "../models/operations/index.js"; +import { unwrapAsync } from "../types/fp.js"; export class Pets extends ClientSDK { - private readonly options$: SDKOptions & { hooks?: SDKHooks }; - - constructor(options: SDKOptions = {}) { - const opt = options as unknown; - let hooks: SDKHooks; - if ( - typeof opt === "object" && - opt != null && - "hooks" in opt && - opt.hooks instanceof SDKHooks - ) { - hooks = opt.hooks; - } else { - hooks = new SDKHooks(); - } - - super({ - client: options.httpClient || new HTTPClient(), - baseURL: serverURLFromOptions(options), - hooks, - }); - - this.options$ = { ...options, hooks }; - void this.options$; - } - /** * List all pets */ @@ -49,48 +18,7 @@ export class Pets extends ClientSDK { limit?: number | undefined, options?: RequestOptions ): Promise { - const input$: operations.ListPetsRequest = { - limit: limit, - }; - const headers$ = new Headers(); - headers$.set("user-agent", SDK_METADATA.userAgent); - headers$.set("Accept", "application/json"); - - const payload$ = schemas$.parse( - input$, - (value$) => operations.ListPetsRequest$.outboundSchema.parse(value$), - "Input validation failed" - ); - const body$ = null; - - const path$ = this.templateURLComponent("/pets")(); - - const query$ = encodeFormQuery$({ - limit: payload$.limit, - }); - - const context = { operationID: "listPets", oAuth2Scopes: [], securitySource: null }; - - const doOptions = { context, errorCodes: ["4XX", "5XX"] }; - const request$ = this.createRequest$( - context, - { method: "GET", path: path$, headers: headers$, query: query$, body: body$ }, - options - ); - - const response = await this.do$(request$, doOptions); - - const responseFields$ = { - HttpMeta: { Response: response, Request: request$ }, - }; - - const [result$] = await this.matcher() - .json(200, operations.ListPetsResponse$, { hdrs: true, key: "Pets" }) - .fail(["4XX", "5XX"]) - .json("default", operations.ListPetsResponse$, { key: "Error" }) - .match(response, request$, { extraFields: responseFields$ }); - - return result$; + return unwrapAsync(petsListPets(this, limit, options)); } /** @@ -100,45 +28,7 @@ export class Pets extends ClientSDK { request: components.Pet, options?: RequestOptions ): Promise { - const input$ = request; - const headers$ = new Headers(); - headers$.set("user-agent", SDK_METADATA.userAgent); - headers$.set("Content-Type", "application/json"); - headers$.set("Accept", "application/json"); - - const payload$ = schemas$.parse( - input$, - (value$) => components.Pet$.outboundSchema.parse(value$), - "Input validation failed" - ); - const body$ = encodeJSON$("body", payload$, { explode: true }); - - const path$ = this.templateURLComponent("/pets")(); - - const query$ = ""; - - const context = { operationID: "createPets", oAuth2Scopes: [], securitySource: null }; - - const doOptions = { context, errorCodes: ["4XX", "5XX"] }; - const request$ = this.createRequest$( - context, - { method: "POST", path: path$, headers: headers$, query: query$, body: body$ }, - options - ); - - const response = await this.do$(request$, doOptions); - - const responseFields$ = { - HttpMeta: { Response: response, Request: request$ }, - }; - - const [result$] = await this.matcher() - .void(201, operations.CreatePetsResponse$) - .fail(["4XX", "5XX"]) - .json("default", operations.CreatePetsResponse$, { key: "Error" }) - .match(response, request$, { extraFields: responseFields$ }); - - return result$; + return unwrapAsync(petsCreatePets(this, request, options)); } /** @@ -148,51 +38,6 @@ export class Pets extends ClientSDK { petId: string, options?: RequestOptions ): Promise { - const input$: operations.ShowPetByIdRequest = { - petId: petId, - }; - const headers$ = new Headers(); - headers$.set("user-agent", SDK_METADATA.userAgent); - headers$.set("Accept", "application/json"); - - const payload$ = schemas$.parse( - input$, - (value$) => operations.ShowPetByIdRequest$.outboundSchema.parse(value$), - "Input validation failed" - ); - const body$ = null; - - const pathParams$ = { - petId: encodeSimple$("petId", payload$.petId, { - explode: false, - charEncoding: "percent", - }), - }; - const path$ = this.templateURLComponent("/pets/{petId}")(pathParams$); - - const query$ = ""; - - const context = { operationID: "showPetById", oAuth2Scopes: [], securitySource: null }; - - const doOptions = { context, errorCodes: ["4XX", "5XX"] }; - const request$ = this.createRequest$( - context, - { method: "GET", path: path$, headers: headers$, query: query$, body: body$ }, - options - ); - - const response = await this.do$(request$, doOptions); - - const responseFields$ = { - HttpMeta: { Response: response, Request: request$ }, - }; - - const [result$] = await this.matcher() - .json(200, operations.ShowPetByIdResponse$, { key: "Pet" }) - .fail(["4XX", "5XX"]) - .json("default", operations.ShowPetByIdResponse$, { key: "Error" }) - .match(response, request$, { extraFields: responseFields$ }); - - return result$; + return unwrapAsync(petsShowPetById(this, petId, options)); } } diff --git a/ts/src/sdk/sdk.ts b/ts/src/sdk/sdk.ts index 819bc43..d3eae7d 100644 --- a/ts/src/sdk/sdk.ts +++ b/ts/src/sdk/sdk.ts @@ -1,40 +1,11 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ -import { SDKHooks } from "../hooks/hooks.js"; -import { SDKOptions, serverURLFromOptions } from "../lib/config.js"; -import { HTTPClient } from "../lib/http.js"; import { ClientSDK } from "../lib/sdks.js"; import { Pets } from "./pets.js"; export class RyTs extends ClientSDK { - private readonly options$: SDKOptions & { hooks?: SDKHooks }; - - constructor(options: SDKOptions = {}) { - const opt = options as unknown; - let hooks: SDKHooks; - if ( - typeof opt === "object" && - opt != null && - "hooks" in opt && - opt.hooks instanceof SDKHooks - ) { - hooks = opt.hooks; - } else { - hooks = new SDKHooks(); - } - - super({ - client: options.httpClient || new HTTPClient(), - baseURL: serverURLFromOptions(options), - hooks, - }); - - this.options$ = { ...options, hooks }; - void this.options$; - } - private _pets?: Pets; get pets(): Pets { return (this._pets ??= new Pets(this.options$)); diff --git a/ts/src/types/blobs.ts b/ts/src/types/blobs.ts index cbb8a57..4ce8460 100644 --- a/ts/src/types/blobs.ts +++ b/ts/src/types/blobs.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ import * as z from "zod"; diff --git a/ts/src/types/constdatetime.ts b/ts/src/types/constdatetime.ts new file mode 100644 index 0000000..c0a4409 --- /dev/null +++ b/ts/src/types/constdatetime.ts @@ -0,0 +1,15 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +import * as z from "zod"; + +export function constDateTime( + val: string, +): z.ZodType { + return z.custom((v) => { + return ( + typeof v === "string" && new Date(v).getTime() === new Date(val).getTime() + ); + }, `Value must be equivelant to ${val}`); +} diff --git a/ts/src/types/enums.ts b/ts/src/types/enums.ts index 4de0142..6fb6d91 100644 --- a/ts/src/types/enums.ts +++ b/ts/src/types/enums.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ declare const __brand: unique symbol; diff --git a/ts/src/types/fp.ts b/ts/src/types/fp.ts new file mode 100644 index 0000000..ccbe51e --- /dev/null +++ b/ts/src/types/fp.ts @@ -0,0 +1,50 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +/** + * A monad that captures the result of a function call or an error if it was not + * successful. Railway programming, enabled by this type, can be a nicer + * alternative to traditional exception throwing because it allows functions to + * declare all _known_ errors with static types and then check for them + * exhaustively in application code. Thrown exception have a type of `unknown` + * and break out of regular control flow of programs making them harder to + * inspect and more verbose work with due to try-catch blocks. + */ +export type Result = + | { ok: true; value: T; error?: never } + | { ok: false; value?: never; error: E }; + +export function OK(value: V): Result { + return { ok: true, value }; +} + +export function ERR(error: E): Result { + return { ok: false, error }; +} + +/** + * unwrap is a convenience function for extracting a value from a result or + * throwing if there was an error. + */ +export function unwrap(r: Result): T { + if (!r.ok) { + throw r.error; + } + return r.value; +} + +/** + * unwrapAsync is a convenience function for resolving a value from a Promise + * of a result or rejecting if an error occurred. + */ +export async function unwrapAsync( + pr: Promise>, +): Promise { + const r = await pr; + if (!r.ok) { + throw r.error; + } + + return r.value; +} diff --git a/ts/src/types/index.ts b/ts/src/types/index.ts index 374373e..dbaa5e6 100644 --- a/ts/src/types/index.ts +++ b/ts/src/types/index.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ export { RFCDate } from "./rfcdate.js"; @@ -8,3 +8,4 @@ export type { Paginator, PageIterator } from "./operations.js"; export { createPageIterator } from "./operations.js"; export { catchUnrecognizedEnum } from "./enums.js"; export type { OpenEnum, ClosedEnum, Unrecognized } from "./enums.js"; +export type { Result } from "./fp.js"; diff --git a/ts/src/types/operations.ts b/ts/src/types/operations.ts index 7f83351..0952f6f 100644 --- a/ts/src/types/operations.ts +++ b/ts/src/types/operations.ts @@ -1,25 +1,99 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ -export type Paginator = () => Promise> | null; +import { Result } from "./fp.js"; -export type PageIterator = Result & { - next: Paginator; - [Symbol.asyncIterator]: () => AsyncIterableIterator; +export type Paginator = () => Promise }> | null; + +export type PageIterator = V & { + next: Paginator; + [Symbol.asyncIterator]: () => AsyncIterableIterator; }; -export function createPageIterator( - page: Result & { next: Paginator }, -): { [Symbol.asyncIterator]: () => AsyncIterableIterator } { +export function createPageIterator( + page: V & { next: Paginator }, + halt: (v: V) => boolean, +): { + [Symbol.asyncIterator]: () => AsyncIterableIterator; +} { return { [Symbol.asyncIterator]: async function* paginator() { yield page; + if (halt(page)) { + return; + } let p: typeof page | null = page; for (p = await p.next(); p != null; p = await p.next()) { yield p; + if (halt(p)) { + return; + } } }, }; } + +/** + * This utility create a special iterator that yields a single value and + * terminates. It is useful in paginated SDK functions that have early return + * paths when things go wrong. + */ +export function haltIterator(v: V): PageIterator { + return { + ...v, + next: () => null, + [Symbol.asyncIterator]: async function* paginator() { + yield v; + }, + }; +} + +/** + * Converts an async iterator of `Result` into an async iterator of `V`. + * When error results occur, the underlying error value is thrown. + */ +export async function unwrapResultIterator( + iteratorPromise: Promise>>, +): Promise> { + const resultIter = await iteratorPromise; + + if (!resultIter.ok) { + throw resultIter.error; + } + + return { + ...resultIter.value, + next: unwrapPaginator(resultIter.next), + [Symbol.asyncIterator]: async function* paginator() { + for await (const page of resultIter) { + if (!page.ok) { + throw page.error; + } + yield page.value; + } + }, + }; +} + +function unwrapPaginator( + paginator: Paginator>, +): Paginator { + return () => { + const nextResult = paginator(); + if (nextResult == null) { + return null; + } + return nextResult.then((res) => { + if (!res.ok) { + throw res.error; + } + const out = { + ...res.value, + next: unwrapPaginator(res.next), + }; + return out; + }); + }; +} diff --git a/ts/src/types/rfcdate.ts b/ts/src/types/rfcdate.ts index e80b693..c79b3f5 100644 --- a/ts/src/types/rfcdate.ts +++ b/ts/src/types/rfcdate.ts @@ -1,5 +1,5 @@ /* - * Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. */ const dateRE = /^\d{4}-\d{2}-\d{2}$/;