npm package: @ows-sdk/kms-client
Description: Lightweight API key verification client for Opendex KMS. Protects any SDK with a single line of code.
Author: Opendex, Inc.
Latest version: 1.0.0

<div align="center">

# @ows-sdk/kms-client

### API Key Verification for Opendex SDKs

[![npm](https://img.shields.io/npm/v/@ows-sdk/kms-client.svg)](https://www.npmjs.com/package/@ows-sdk/kms-client)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-3178C6.svg)](https://www.typescriptlang.org/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
[![Bundle Size](https://img.shields.io/bundlephobia/minzip/@ows-sdk/kms-client)](https://bundlephobia.com/package/@ows-sdk/kms-client)

**Zero-dependency client to protect any Opendex SDK with API key verification.**

</div>

---

## Install

```bash
npm install @ows-sdk/kms-client
```

```bash
pnpm add @ows-sdk/kms-client
```

```bash
yarn add @ows-sdk/kms-client
```

## Quick Start

### Option 1: Guard (recommended for SDKs)

The simplest way to protect your SDK. Two lines: `init()` to verify, `requireInit()` to enforce.

```typescript
import { createGuard, KMSError } from '@ows-sdk/kms-client';

class MySDK {
  private guard = createGuard(apiKey, {
    sdkName: 'my-awesome-sdk',
    sdkVersion: '1.0.0',
  });

  async init() {
    await this.guard.init(); // Verifies API key against KMS
  }

  doSomething() {
    this.guard.requireInit(); // Throws if not verified
    // ... your SDK logic here
  }

  async doSomethingAsync() {
    this.guard.requireInit();
    // ... your async SDK logic here
  }
}
```

### Option 2: Client (for custom flows)

Full control over when and how verification happens.

```typescript
import { KMSClient, KMSError } from '@ows-sdk/kms-client';

const kms = new KMSClient({
  sdkName: 'my-sdk',
  sdkVersion: '2.0.0',
});

// Verify with full details
const result = await kms.verify('kms_abc123...');
console.log(result.scopes);      // ['verify', 'read']
console.log(result.environment); // 'production'
console.log(result.keyId);       // 'key_xxx'

// Simple boolean check (no throw)
const valid = await kms.isValid('kms_abc123...');
```

## End User's Code

This is what your SDK users write:

```typescript
import { MySDK } from 'my-awesome-sdk';

const sdk = new MySDK({ apiKey: 'kms_abc123...' });

try {
  await sdk.init();
  sdk.doSomething(); // Works — key is verified
} catch (error) {
  if (error instanceof KMSError) {
    console.error(`[${error.code}] ${error.userMessage}`);
  }
}
```

## Full SDK Example

Here's a complete example of how to integrate `@ows-sdk/kms-client` into an SDK:

```typescript
// my-sdk/src/index.ts

import { createGuard, KMSError, type KMSVerification } from '@ows-sdk/kms-client';

export interface MySDKOptions {
  apiKey: string;
  region?: string;
}

export class MySDK {
  private guard;

  constructor(options: MySDKOptions) {
    this.guard = createGuard(options.apiKey, {
      sdkName: 'my-sdk',
      sdkVersion: '1.0.0',
      requiredScope: 'verify', // Key must have 'verify' scope
    });
  }

  /**
   * Initialize the SDK. Must be called before any other method.
   */
  async init(): Promise<void> {
    await this.guard.init();
  }

  /**
   * Check if the SDK is ready to use.
   */
  get isReady(): boolean {
    return this.guard.isVerified;
  }

  /**
   * Get the verified key details.
   */
  get keyInfo(): KMSVerification | null {
    return this.guard.verification;
  }

  /**
   * Your SDK methods — all protected by requireInit()
   */
  async doSomething(): Promise<string> {
    const key = this.guard.requireInit();
    // key.scopes, key.environment, key.projectId are available
    return `Hello from ${key.environment}!`;
  }
}

// Re-export for user convenience
export { KMSError } from '@ows-sdk/kms-client';
```

## API Reference

### `KMSClient`

```typescript
const client = new KMSClient(options?: KMSClientOptions);
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `endpoint` | `string` | `https://kms.opendexapis.com` | KMS API URL |
| `timeout` | `number` | `5000` | Request timeout (ms) |
| `retries` | `number` | `2` | Retry attempts on network errors |
| `retryDelay` | `number` | `500` | Base retry delay (ms, doubles each retry) |
| `sdkName` | `string` | `'unknown-sdk'` | Your SDK name (for analytics) |
| `sdkVersion` | `string` | `'0.0.0'` | Your SDK version |
| `cache` | `boolean` | `true` | Cache verification results in memory |
| `cacheTtl` | `number` | `300000` | Cache TTL (ms, default 5 min) |

**Methods:**

| Method | Returns | Description |
|--------|---------|-------------|
| `verify(apiKey, scope?)` | `Promise<KMSVerification>` | Verify key (throws on invalid) |
| `isValid(apiKey, scope?)` | `Promise<boolean>` | Check validity (no throw) |
| `clearCache()` | `void` | Clear cached verifications |

### `createGuard()`

```typescript
const guard = createGuard(apiKey: string, options?: KMSGuardOptions);
```

Extends `KMSClientOptions` with:

| Option | Type | Description |
|--------|------|-------------|
| `requiredScope` | `string` | Scope the key must have |

**Properties & Methods:**

| Member | Type | Description |
|--------|------|-------------|
| `verification` | `KMSVerification \| null` | Cached result |
| `isVerified` | `boolean` | Whether init() has been called successfully |
| `init()` | `Promise<KMSVerification>` | Verify the key (safe to call multiple times) |
| `requireInit()` | `KMSVerification` | Assert initialized (throws if not) |

### `KMSVerification`

```typescript
interface KMSVerification {
  valid: true;
  keyId: string;        // 'key_abc123'
  projectId: string;    // 'proj_xyz789'
  name: string;         // 'Production Key'
  scopes: string[];     // ['verify', 'read']
  environment: string;  // 'production'
  rateLimit?: {
    remaining: number;
    reset: number;
    limit: number;
  };
}
```

### Error Classes

```typescript
import {
  KMSError,            // Base error (has .code and .userMessage)
  KMSValidationError,  // Invalid key (non-retryable)
  KMSNetworkError,     // Network failure (retryable)
  KMSTimeoutError,     // Timeout (retryable)
} from '@ows-sdk/kms-client';
```

| Error | Codes | Retryable |
|-------|-------|-----------|
| `KMSValidationError` | `INVALID_FORMAT`, `NOT_FOUND`, `DISABLED`, `EXPIRED`, `SCOPE_DENIED`, `IP_BLOCKED`, `ROTATION_REQUIRED` | No |
| `KMSNetworkError` | `NETWORK_ERROR` | Yes |
| `KMSTimeoutError` | `TIMEOUT` | Yes |
| `KMSError` | `RATE_LIMITED`, `UNKNOWN` | Yes |

### Error Handling

```typescript
import { KMSError, KMSValidationError, KMSTimeoutError } from '@ows-sdk/kms-client';

try {
  await sdk.init();
} catch (error) {
  if (error instanceof KMSValidationError) {
    // Key is definitely invalid — show error.userMessage to user
    console.error(error.userMessage);
    // error.code: 'EXPIRED', 'NOT_FOUND', 'DISABLED', etc.
  } else if (error instanceof KMSTimeoutError) {
    // Network issue — maybe retry later
    console.warn('KMS is unreachable, retrying...');
  } else if (error instanceof KMSError) {
    // Other KMS error (rate limited, unknown)
    console.error(`KMS [${error.code}]: ${error.userMessage}`);
  }
}
```

## Compatibility

- **Node.js** 18+ (uses native `fetch`)
- **Bun** — fully supported
- **Deno** — fully supported
- **Browsers** — works in modern browsers (uses `fetch` + `AbortController`)
- **Cloudflare Workers** — works in Workers/Pages
- **Edge runtimes** — any runtime with `fetch` support

## License

MIT © [Opendex, Inc.](https://opendex.dev)