Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions plugins/plugin-effect-httpapiclient/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
---
layout: doc
title: Kubb Effect HttpApiClient Plugin
description: Generates Effect v4 HttpApi contracts and a typed HttpApiClient from your OpenAPI specification.
outline: deep
kind: plugin
id: plugin-effect-httpapiclient
name: Effect HttpApiClient
category: client
type: official
npmPackage: "@kubb/plugin-effect-httpapiclient"
repo: https://github.com/kubb-labs/plugins
docsPath: /plugins/plugin-effect-httpapiclient
maintainers:
- name: Stijn Van Hulle
github: stijnvanhulle
compatibility:
kubb: ">=5.0.0"
node: ">=22"
tags:
- effect
- api-client
- http-api
- security
- codegen
- openapi
dependencies:
- plugin-effect
resources:
documentation: https://kubb.dev/plugins/plugin-effect-httpapiclient
repository: https://github.com/kubb-labs/plugins
issues: https://github.com/kubb-labs/plugins/issues
changelog: https://github.com/kubb-labs/plugins/blob/main/packages/plugin-effect-httpapiclient/CHANGELOG.md
codesandbox: https://codesandbox.io/p/github/kubb-labs/plugins/main/examples/effect-httpapiclient
---

# @kubb/plugin-effect-httpapiclient

`@kubb/plugin-effect-httpapiclient` generates Effect `HttpApiEndpoint`, `HttpApiGroup`, and `HttpApi` contracts from OpenAPI. It also exports a typed `ApiClient` Effect created by `HttpApiClient.make`.

The plugin uses schemas generated by [`@kubb/plugin-effect`](/plugins/plugin-effect/), so include both plugins in your configuration.

> [!WARNING]
> The first release targets `effect@4.0.0-beta.98`. Other Effect v4 beta releases and the future stable release may require changes to generated code.

## Installation

Install both Kubb plugins as development dependencies and Effect as an application dependency.

::: code-group

```shell [bun]
bun add -d @kubb/plugin-effect@beta @kubb/plugin-effect-httpapiclient@beta
bun add effect@4.0.0-beta.98
```

```shell [pnpm]
pnpm add -D @kubb/plugin-effect@beta @kubb/plugin-effect-httpapiclient@beta
pnpm add effect@4.0.0-beta.98
```

```shell [npm]
npm install --save-dev @kubb/plugin-effect@beta @kubb/plugin-effect-httpapiclient@beta
npm install effect@4.0.0-beta.98
```

```shell [yarn]
yarn add -D @kubb/plugin-effect@beta @kubb/plugin-effect-httpapiclient@beta
yarn add effect@4.0.0-beta.98
```

:::

## Example

Set `baseURL` to embed the service URL in the generated client. The default `mode: 'tag'` groups client methods by their first OpenAPI tag.

```typescript twoslash [kubb.config.ts]
import { pluginEffect } from '@kubb/plugin-effect'
import { pluginEffectHttpApiClient } from '@kubb/plugin-effect-httpapiclient'
import { defineConfig } from 'kubb/config'

export default defineConfig({
input: './petStore.yaml',
output: { path: './src/gen' },
plugins: [
pluginEffect({ output: { path: 'effect' } }),
pluginEffectHttpApiClient({
output: { path: 'effectHttpApiClient' },
baseURL: 'https://petstore.example.com',
}),
],
})
```

Run the generated client as an Effect. Operations without a request body take `params`, `query`, and `headers` fields when their OpenAPI definition requires them. Operations with a body also take `payload`.

```typescript [getPet.ts]
import { Effect } from 'effect'
import { ApiClient } from './gen/effectHttpApiClient'

const getPet = Effect.gen(function* () {
const client = yield* ApiClient
return yield* client.pet.getPetById({ params: { petId: 10n } })
})
```

Set `mode: 'flat'` to expose operation methods directly on the client instead of grouping them by tag.

## Authentication

OpenAPI security requirements become required client middleware. The generated `makeSecurityLayer` accepts static credentials and an optional dynamic resolver. Static credentials take precedence, while the resolver supports refreshed tokens and OAuth scopes at request time.

```typescript [authenticated.ts]
import { Effect, Redacted } from 'effect'
import { ApiClient, makeSecurityLayer } from './gen/effectHttpApiClient'

const apiKey = process.env.PETSTORE_API_KEY
if (!apiKey) throw new Error('PETSTORE_API_KEY is required')

const program = Effect.gen(function* () {
const client = yield* ApiClient
return yield* client.pet.getPetById({ params: { petId: 10n } })
}).pipe(
Effect.provide(
makeSecurityLayer({
credentials: {
api_key: { _tag: 'ApiKey', value: Redacted.make(apiKey) },
},
}),
),
)
```

The middleware preserves OpenAPI security semantics:

- Alternatives in the security array use OR semantics. The first complete alternative is applied.
- Schemes in one requirement object use AND semantics.
- Operation security replaces global security. An empty operation security array permits anonymous access.
- OAuth and OpenID Connect schemes receive a bearer credential. The plugin passes declared scopes to the dynamic resolver but does not acquire tokens.

If no alternative can be satisfied, the request fails with `MissingSecurityCredentials` before it reaches the HTTP transport. A dynamic resolver failure becomes `SecurityCredentialResolutionError`.

## Content and data types

The generated contract uses Effect's native HttpApi schema support for JSON, URL-encoded forms, multipart forms, text, binary bodies, and server-sent events. XML and unrecognized text media types use a string fallback. Other unknown media types use `Uint8Array`.

When the OpenAPI adapter uses `dateType: 'date'`, a `date-time` schema decodes to Effect's `DateTime.Utc` type and encodes to a UTC ISO string.

Regular cookie parameters are exposed under `headers.cookies`. OpenAPI API keys stored in cookies are added by the generated security middleware.

## Current constraints

- `default` responses are omitted because Effect HttpApi contracts require concrete status codes. Generation fails for other nonnumeric response keys.
- The plugin generates client-side security middleware, not server-side authentication handlers.
- The generated `ApiClient` uses the `baseURL` configured during generation. It does not generate a client factory for changing the URL at runtime.

## See also

- [Options](/plugins/plugin-effect-httpapiclient/reference/options)
- [`@kubb/plugin-effect`](/plugins/plugin-effect/)
- [Effect HttpApi](https://effect.website/docs/http/http-api/)
- [Changelog](https://github.com/kubb-labs/plugins/blob/main/packages/plugin-effect-httpapiclient/CHANGELOG.md)
138 changes: 138 additions & 0 deletions plugins/plugin-effect-httpapiclient/reference/options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
layout: doc
title: Options
description: Configuration options for @kubb/plugin-effect-httpapiclient.
outline: deep
---

# Options

`pluginEffectHttpApiClient` accepts the following options.

| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| [`output`](#output) | `Output` | `{ path: 'effectHttpApiClient', barrel: { type: 'named' } }` | Where the generated files are written and exported |
| [`group`](#group) | `Group` | None | Split generated files into per-tag or per-path folders |
| [`baseURL`](#baseurl) | `string` | None | Embed a fixed base URL in the generated client |
| [`mode`](#mode) | `'tag' \| 'flat'` | `'tag'` | Group client methods by tag or expose them at the root |
| [`include`](#include) | `Array<Include>` | None | Keep only operations that match |
| [`exclude`](#exclude) | `Array<Exclude>` | `[]` | Skip operations that match |
| [`override`](#override) | `Array<Override>` | `[]` | Apply different options per pattern |
| [`resolver`](#resolver) | `ResolverPatch<ResolverEffectHttpApiClient>` | None | Customize generated names and file paths |
| [`macros`](#macros) | `Array<Macro>` | None | Rewrite AST nodes before printing |

### output

Controls where the generated `.ts` files are written and how Kubb exports them.

#### output.path

The plugin resolves this folder against the global `output.path` from `defineConfig`.

| | |
| --------: | :-------------------- |
| Type: | `string` |
| Required: | `false` |
| Default: | `'effectHttpApiClient'` |

#### output.barrel

<!--@include: ../../../snippets/how-to/barrel.md-->

#### output.banner

<!--@include: ../../../snippets/how-to/output-banner.md-->

#### output.footer

<!--@include: ../../../snippets/how-to/output-footer.md-->

### group

<!--@include: ../../../snippets/how-to/grouping.md-->

File grouping controls the output directory layout. It is separate from [`mode`](#mode), which controls the property layout on the generated `ApiClient`.

#### group.type

<!--@include: ../../../snippets/how-to/group-type.md-->

#### group.name

The function turns a tag or path key into the subdirectory under `output.path`.

| | |
| --------: | :--------------------------------------- |
| Type: | `(context: { group: string }) => string` |
| Required: | `false` |
| Default: | `({ group }) => camelCase(group)` |

### baseURL

Embeds a fixed string in the generated `HttpApiClient.make(Api, { baseUrl })` call. The plugin does not generate a factory for selecting another URL at runtime.

| | |
| --------: | :-------- |
| Type: | `string` |
| Required: | `false` |
| Default: | None |

### mode

Controls the shape returned by the generated client Effect.

- `'tag'` groups operations by their first OpenAPI tag. An operation without tags belongs to the `default` group.
- `'flat'` adds every operation directly to the client root.

| | |
| --------: | :---------------- |
| Type: | `'tag' \| 'flat'` |
| Required: | `false` |
| Default: | `'tag'` |

### include

<!--@include: ../../../snippets/how-to/include.md-->

### exclude

<!--@include: ../../../snippets/how-to/exclude.md-->

### override

<!--@include: ../../../snippets/how-to/override.md-->

### resolver

Changes generated symbols and file paths. Methods omitted from the patch keep the default behavior.

| Method | Purpose |
| ------ | ------- |
| `name(name)` | Resolve the shared base name |
| `file(name)` | Resolve a generated file name |
| `endpoint.name(operation)` | Resolve an exported endpoint constant |
| `endpoint.identifier(operation)` | Resolve the operation property on the client |
| `group.name(tag)` | Resolve an exported `HttpApiGroup` constant |
| `group.identifier(tag)` | Resolve the group property on a tagged client |
| `api.name()` | Resolve the root `HttpApi` constant |
| `client.name()` | Resolve the generated client type and Effect value |

```typescript [kubb.config.ts]
import { pluginEffectHttpApiClient } from '@kubb/plugin-effect-httpapiclient'

pluginEffectHttpApiClient({
resolver: {
client: {
name() {
return 'PetStoreClient'
},
},
},
})
```

See [Override a resolver](/docs/5.x/guide/going-further/resolvers) for the resolver context.

### macros

<!--@include: ../../../snippets/how-to/macros-option.md-->
Loading