npm package: @oapiex/sdk-kit
Description: A TypeScript Base for building SDKs using OpenAPI specifications extracted by OAPIEX.
Latest version: 0.1.13

# @oapiex/sdk-kit

[![NPM Downloads](https://img.shields.io/npm/dt/@oapiex/sdk-kit.svg)](https://www.npmjs.com/package/@oapiex/sdk-kit)
[![npm version](https://img.shields.io/npm/v/@oapiex/sdk-kit.svg)](https://www.npmjs.com/package/@oapiex/sdk-kit)
[![License](https://img.shields.io/npm/l/@oapiex/sdk-kit.svg)](https://github.com/toneflix/oapiex/blob/main/LICENSE)
[![codecov](https://img.shields.io/codecov/c/github/toneflix/oapiex.svg?token=9C8arbqlX2)](https://codecov.io/gh/toneflix/oapiex)
[![Publish Package](https://github.com/toneflix/oapiex/actions/workflows/publish.yml/badge.svg)](https://github.com/toneflix/oapiex/actions/workflows/publish.yml)
[![Deploy Docs](https://github.com/toneflix/oapiex/actions/workflows/docs.yml/badge.svg)](https://github.com/toneflix/oapiex/actions/workflows/docs.yml)
[![Run Tests](https://github.com/toneflix/oapiex/actions/workflows/test.yml/badge.svg)](https://github.com/toneflix/oapiex/actions/workflows/test.yml)

Core runtime primitives for SDKs generated by oapiex.

This package provides the shared building blocks used by generated SDK packages:

- `Core` for authenticated SDK instances
- `BaseApi` for SDK-specific API binders and API classes
- `createSdk()` for manifest-driven runtime SDKs
- shared HTTP utilities, exceptions, contracts, and helper types

Most consumers will install a generated SDK package instead of using this package directly. Use `@oapiex/sdk-kit` when you want to build or customize an SDK on top of the generated OpenAPI manifest output.

## Install

```bash
pnpm add @oapiex/sdk-kit
```

## Quick Start

### Class-based SDK

```ts
import { BaseApi, Core, Http } from '@oapiex/sdk-kit';

class ExampleApi extends BaseApi {
  async list() {
    await this.core.validateAccess();

    const { data } = await Http.send(
      this.core.builder.buildTargetUrl('/v1/examples', {}, {}),
      'GET',
      {},
      {},
    );

    return data;
  }
}

class ApiBinder extends BaseApi {
  examples!: ExampleApi;

  protected override boot() {
    this.examples = new ExampleApi(this.core);
  }
}

class ExampleCore extends Core {
  static override apiClass = ApiBinder;

  declare api: ApiBinder;
}

const sdk = new ExampleCore({
  clientId: process.env.CLIENT_ID!,
  clientSecret: process.env.CLIENT_SECRET!,
  environment: 'sandbox',
  urls: {
    sandbox: 'https://sandbox-api.example.com',
  },
  headers: {
    'X-SDK-Version': '1.0.0',
  },
  timeout: 10000,
  auth: {
    type: 'bearer',
    token: process.env.ACCESS_TOKEN!,
  },
});

await sdk.api.examples.list();
```

### Runtime manifest SDK

```ts
import { createSdk, type RuntimeSdkBundle } from '@oapiex/sdk-kit';

const bundle = {
  document: {},
  manifest: {
    groups: [
      {
        className: 'Example',
        propertyName: 'examples',
        operations: [
          {
            path: '/v1/examples',
            method: 'GET',
            methodName: 'list',
            responseType: 'Example[]',
            inputType: 'Record<string, never>',
            queryType: 'Record<string, never>',
            headerType: 'Record<string, never>',
            paramsType: 'Record<string, never>',
            hasBody: false,
            bodyRequired: false,
            pathParams: [],
            queryParams: [],
            headerParams: [],
          },
        ],
      },
    ],
  },
} satisfies RuntimeSdkBundle;

const sdk = createSdk(bundle, {
  clientId: process.env.CLIENT_ID!,
  clientSecret: process.env.CLIENT_SECRET!,
  environment: 'sandbox',
  auth: {
    type: 'apiKey',
    name: 'X-API-Key',
    value: process.env.API_KEY!,
    in: 'header',
  },
});

await sdk.api.examples.list();
```

## Init Options

`Core` and `createSdk()` both accept the same `InitOptions` object.

- `clientId`: optional API client identifier, useful when your validator or auth exchange still needs client credentials
- `clientSecret`: optional when `auth` is already provided, otherwise required
- `environment`: selects `sandbox` or `live`
- `urls`: optional base URL overrides per environment
- `headers`: optional default headers added to every request
- `timeout`: optional Axios timeout in milliseconds
- `encryptionKey`: optional runtime encryption key override
- `auth`: one auth strategy or an array of strategies applied to outgoing requests
- `debugLevel`: optional HTTP debug verbosity, one of `0 | 1 | 2 | 3`

```ts
const sdk = new Core({
  clientId: process.env.CLIENT_ID!,
  clientSecret: process.env.CLIENT_SECRET!,
  environment: 'sandbox',
  urls: {
    sandbox: 'https://sandbox-api.example.com',
    live: 'https://api.example.com',
  },
  headers: {
    'X-Trace-Source': 'example-sdk',
  },
  timeout: 15000,
  debugLevel: 1,
});
```

If you already have a token or API key strategy, you can initialize without client credentials:

```ts
const sdk = new Core({
  environment: 'sandbox',
  auth: {
    type: 'bearer',
    token: process.env.ACCESS_TOKEN!,
  },
  debugLevel: 1,
});
```

`@oapiex/sdk-kit` can also read SDK init config from `oapiex.config.ts`, `oapiex.config.js`, or `oapiex.config.cjs` in the current working directory.

For an SDK-focused config file, prefer `defineConfig()` from `@oapiex/sdk-kit`:

```ts
import { defineConfig } from '@oapiex/sdk-kit';

export default defineConfig({
  clientId: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  environment: 'sandbox',
  urls: {
    sandbox: 'https://sandbox-api.example.com',
  },
  headers: {
    'X-Trace-Source': 'example-sdk',
  },
  timeout: 15000,
  auth: {
    type: 'bearer',
    token: process.env.ACCESS_TOKEN,
  },
  debugLevel: 1,
});
```

If you use the same `oapiex.config.*` file for extraction and SDK runtime defaults, prefer the root `oapiex` helper and scope the SDK settings under `sdkKit`:

```ts
import { defineConfig } from 'oapiex';

export default defineConfig({
  sdkKit: {
    clientId: process.env.CLIENT_ID,
    clientSecret: process.env.CLIENT_SECRET,
    environment: 'sandbox',
    urls: {
      sandbox: 'https://sandbox-api.example.com',
    },
    headers: {
      'X-Trace-Source': 'example-sdk',
    },
    timeout: 15000,
    auth: {
      type: 'bearer',
      token: process.env.ACCESS_TOKEN,
    },
    debugLevel: 1,
  },
});
```

Explicit constructor options override values loaded from `oapiex.config.*`.

## Debugging

SDK HTTP debugging is disabled by default.

- Use `sdk.debug(level)` after initialization when you want to enable or adjust request logging.
- Use `debugLevel` in `InitOptions` when you want debug logging active immediately during construction.
- Supported levels are `0 | 1 | 2 | 3`.

```ts
const sdk = new Core({
  environment: 'sandbox',
  auth: {
    type: 'bearer',
    token: process.env.ACCESS_TOKEN!,
  },
  debugLevel: 2,
});

sdk.debug(3);
```

## Auth Shapes

The `auth` option accepts any `AuthConfig` supported by the transport layer.

### Bearer

```ts
auth: {
  type: 'bearer',
  token: process.env.ACCESS_TOKEN!,
}
```

### Basic

```ts
auth: {
  type: 'basic',
  username: process.env.API_USERNAME!,
  password: process.env.API_PASSWORD!,
}
```

### API Key

```ts
auth: {
  type: 'apiKey',
  name: 'X-API-Key',
  value: process.env.API_KEY!,
  in: 'header',
}
```

### OAuth2 Token

```ts
auth: {
  type: 'oauth2',
  accessToken: process.env.ACCESS_TOKEN!,
  tokenType: 'Bearer',
}
```

### Multiple Strategies

```ts
auth: [
  {
    type: 'apiKey',
    name: 'X-Partner-Key',
    value: process.env.PARTNER_KEY!,
    in: 'header',
  },
  {
    type: 'apiKey',
    name: 'api_key',
    value: process.env.QUERY_KEY!,
    in: 'query',
  },
];
```

### Custom

```ts
auth: {
  type: 'custom',
  apply: async (request) => ({
    ...request,
    headers: {
      ...request.headers,
      Authorization: `Token ${process.env.CUSTOM_TOKEN!}`,
    },
  }),
}
```

Generated SDK packages can now expose auth helper functions derived from OpenAPI `securitySchemes`, plus `securitySchemes` and `security` metadata exports for inspection.

## Access Validator Auth Refresh

`setAccessValidator()` can be used to replace auth before the actual SDK request is sent. This is useful for APIs like Flutterwave where you first exchange API keys for a bearer token.

The validator can:

- return `true` to allow the request as-is
- return a string to fail validation with a custom message
- return an `AuthConfig` or `AuthConfig[]` to replace auth for the pending request
- return a partial config object such as `{ auth, headers, timeout }`
- call `core.setAuth(...)` or `core.configure(...)` directly

```ts
import axios from 'axios';
import { Core } from '@oapiex/sdk-kit';

const sdk = new Core({
  clientId: process.env.CLIENT_ID!,
  clientSecret: process.env.CLIENT_SECRET!,
  environment: 'sandbox',
  urls: {
    sandbox: 'https://developersandbox-api.example.com',
  },
});

sdk.setAccessValidator(async (core) => {
  const response = await axios.post(
    'https://developersandbox-api.example.com/auth/token',
    {
      client_id: core.getClientId(),
      client_secret: core.getClientSecret(),
    },
  );

  return {
    type: 'bearer',
    token: response.data.access_token,
  };
});

await sdk.api.examples.list();
```

If you need to update more than auth, return a config object instead:

```ts
sdk.setAccessValidator(async (core) => {
  const response = await axios.post(
    'https://developersandbox-api.example.com/auth/token',
    {
      client_id: core.getClientId(),
      client_secret: core.getClientSecret(),
    },
  );

  return {
    auth: {
      type: 'bearer',
      token: response.data.access_token,
    },
    headers: {
      'X-Token-Source': 'access-validator',
    },
  };
});
```

If the token endpoint returns an expiry value, use `createAccessTokenCache()` so the validator does not fetch a new token on every request.

```ts
import axios from 'axios';
import { Core, createAccessTokenCache } from '@oapiex/sdk-kit';

const sdk = new Core({
  clientId: process.env.CLIENT_ID!,
  clientSecret: process.env.CLIENT_SECRET!,
  environment: 'sandbox',
  urls: {
    sandbox: 'https://developersandbox-api.example.com',
  },
});

const tokenCache = createAccessTokenCache(async (core) => {
  const response = await axios.post(
    'https://developersandbox-api.example.com/auth/token',
    {
      client_id: core.getClientId(),
      client_secret: core.getClientSecret(),
    },
  );

  return {
    token: response.data.access_token,
    expiresInSeconds: Math.max((response.data.expires_in ?? 60) - 30, 1),
  };
});

sdk.setAccessValidator(tokenCache);
```

## Main Exports

- `Core`: base SDK client with environment setup, access validation, and runtime bundle support
- `BaseApi`: base class for SDK-specific API binders and API classes
- `createSdk()`: helper for creating a runtime SDK from a manifest bundle
- `AuthConfig`: shared auth strategy union used by `InitOptions.auth`
- `createAccessTokenCache()`: helper for caching validator-fetched tokens until expiry
- `setAccessValidator()`: hook for readiness checks, auth refresh, and config replacement before requests
- `Http`, `Builder`, and exceptions: lower-level request and error primitives
- `InitOptions`, `UnifiedResponse`, and runtime manifest types: shared contracts for generated SDKs

## Commands

Run these inside `packages/sdk-kit`:

```bash
pnpm test
pnpm build
pnpm coverage
```